void Audio::collectEvents(MidiTrack* track, unsigned int cts, unsigned int nts) { int port = track->outPort(); int channel = track->outChannel(); int defaultPort = port; MidiDevice* md = midiPorts[port].device(); MPEventList* playEvents = md->playEvents(); MPEventList* stuckNotes = md->stuckNotes(); PartList* pl = track->parts(); for (iPart p = pl->begin(); p != pl->end(); ++p) { MidiPart* part = (MidiPart*) (p->second); // dont play muted parts if (part->mute()) continue; EventList* events = part->events(); unsigned partTick = part->tick(); unsigned partLen = part->lenTick(); int delay = track->delay; if (cts > nts) { printf("processMidi: FATAL: cur > next %d > %d\n", cts, nts); return; } unsigned offset = delay + partTick; if (offset > nts) continue; unsigned stick = (offset > cts) ? 0 : cts - offset; unsigned etick = nts - offset; // By T356. Do not play events which are past the end of this part. if (etick > partLen) continue; iEvent ie = events->lower_bound(stick); iEvent iend = events->lower_bound(etick); for (; ie != iend; ++ie) { Event ev = ie->second; port = defaultPort; //Reset each loop // // dont play any meta events // if (ev.type() == Meta) continue; if (track->type() == Track::DRUM) { int instr = ev.pitch(); // ignore muted drums if (ev.isNote() && drumMap[instr].mute) continue; } unsigned tick = ev.tick() + offset; unsigned frame = tempomap.tick2frame(tick) + frameOffset; switch (ev.type()) { case Note: { int len = ev.lenTick(); int pitch = ev.pitch(); int velo = ev.velo(); if (track->type() == Track::DRUM) { // // Map drum-notes to the drum-map values // int instr = ev.pitch(); pitch = drumMap[instr].anote; port = drumMap[instr].port; //This changes to non-default port channel = drumMap[instr].channel; velo = int(double(velo) * (double(drumMap[instr].vol) / 100.0)); } else { // // transpose non drum notes // pitch += (track->transposition + song->globalPitchShift()); } if (pitch > 127) pitch = 127; if (pitch < 0) pitch = 0; velo += track->velocity; velo = (velo * track->compression) / 100; if (velo > 127) velo = 127; if (velo < 1) // no off event velo = 1; len = (len * track->len) / 100; if (len <= 0) // dont allow zero length len = 1; int veloOff = ev.veloOff(); if (port == defaultPort) { //printf("Adding event normally: frame=%d port=%d channel=%d pitch=%d velo=%d\n",frame, port, channel, pitch, velo); // p3.3.25 // If syncing to external midi sync, we cannot use the tempo map. // Therefore we cannot get sub-tick resolution. Just use ticks instead of frames. if (extSyncFlag.value()) playEvents->add(MidiPlayEvent(tick, port, channel, 0x90, pitch, velo)); else playEvents->add(MidiPlayEvent(frame, port, channel, 0x90, pitch, velo)); stuckNotes->add(MidiPlayEvent(tick + len, port, channel, veloOff ? 0x80 : 0x90, pitch, veloOff)); } else { //Handle events to different port than standard. MidiDevice* mdAlt = midiPorts[port].device(); if (mdAlt) { // p3.3.25 if (extSyncFlag.value()) mdAlt->playEvents()->add(MidiPlayEvent(tick, port, channel, 0x90, pitch, velo)); else mdAlt->playEvents()->add(MidiPlayEvent(frame, port, channel, 0x90, pitch, velo)); mdAlt->stuckNotes()->add(MidiPlayEvent(tick + len, port, channel, veloOff ? 0x80 : 0x90, pitch, veloOff)); } } if (velo > track->activity()) track->setActivity(velo); } break; // Added by T356. case Controller: { //int len = ev.lenTick(); //int pitch = ev.pitch(); if (track->type() == Track::DRUM) { int ctl = ev.dataA(); // Is it a drum controller event, according to the track port's instrument? MidiController *mc = midiPorts[defaultPort].drumController(ctl); if (mc) { int instr = ctl & 0x7f; ctl &= ~0xff; int pitch = drumMap[instr].anote & 0x7f; port = drumMap[instr].port; //This changes to non-default port channel = drumMap[instr].channel; MidiDevice* mdAlt = midiPorts[port].device(); if (mdAlt) { // p3.3.25 // If syncing to external midi sync, we cannot use the tempo map. // Therefore we cannot get sub-tick resolution. Just use ticks instead of frames. if (extSyncFlag.value()) mdAlt->playEvents()->add(MidiPlayEvent(tick, port, channel, ME_CONTROLLER, ctl | pitch, ev.dataB())); else //playEvents->add(MidiPlayEvent(frame, port, channel, ev)); mdAlt->playEvents()->add(MidiPlayEvent(frame, port, channel, ME_CONTROLLER, ctl | pitch, ev.dataB())); } break; } } // p3.3.25 if (extSyncFlag.value()) playEvents->add(MidiPlayEvent(tick, port, channel, ev)); else playEvents->add(MidiPlayEvent(frame, port, channel, ev)); } break; default: // p3.3.25 if (extSyncFlag.value()) playEvents->add(MidiPlayEvent(tick, port, channel, ev)); else playEvents->add(MidiPlayEvent(frame, port, channel, ev)); break; } } } }
void Audio::processMidi() { midiBusy = true; // // TODO: syntis should directly write into recordEventList // for (iMidiDevice id = midiDevices.begin(); id != midiDevices.end(); ++id) { MidiDevice* md = *id; MPEventList* playEvents = md->playEvents(); // // erase already played events: // iMPEvent nextPlayEvent = md->nextPlayEvent(); playEvents->erase(playEvents->begin(), nextPlayEvent); // klumsy hack for synti devices: if (md->isSynti()) { SynthI* s = (SynthI*) md; while (s->eventsPending()) { MidiRecordEvent ev = s->receiveEvent(); md->recordEvent(ev); } } // Is it a Jack midi device? //MidiJackDevice* mjd = dynamic_cast<MidiJackDevice*>(md); //if(mjd) // mjd->collectMidiEvents(); md->collectMidiEvents(); // Take snapshots of the current sizes of the recording fifos, // because they may change while here in process, asynchronously. md->beforeProcess(); } MPEventList* playEvents = metronome->playEvents(); iMPEvent nextPlayEvent = metronome->nextPlayEvent(); playEvents->erase(playEvents->begin(), nextPlayEvent); // p3.3.25 bool extsync = extSyncFlag.value(); for (iMidiTrack t = song->midis()->begin(); t != song->midis()->end(); ++t) { MidiTrack* track = *t; int port = track->outPort(); MidiDevice* md = midiPorts[port].device(); // Changed by Tim. p3.3.8 //if(md == 0) // continue; //MPEventList* playEvents = md->playEvents(); //if (playEvents == 0) // continue; //if (!track->isMute()) MPEventList* playEvents = 0; if (md) { playEvents = md->playEvents(); // only add track events if the track is unmuted if (!track->isMute()) { if (isPlaying() && (curTickPos < nextTickPos)) collectEvents(track, curTickPos, nextTickPos); } } // //----------midi recording // if (track->recordFlag()) { //int portMask = track->inPortMask(); // p3.3.38 Removed //unsigned int portMask = track->inPortMask(); //int channelMask = track->inChannelMask(); MPEventList* rl = track->mpevents(); MidiPort* tport = &midiPorts[port]; // p3.3.38 //for (iMidiDevice id = midiDevices.begin(); id != midiDevices.end(); ++id) //{ RouteList* irl = track->inRoutes(); for (ciRoute r = irl->begin(); r != irl->end(); ++r) { //if(!r->isValid() || (r->type != Route::ALSA_MIDI_ROUTE && r->type != Route::JACK_MIDI_ROUTE)) //if(!r->isValid() || (r->type != Route::MIDI_DEVICE_ROUTE)) if (!r->isValid() || (r->type != Route::MIDI_PORT_ROUTE)) // p3.3.49 continue; int devport = r->midiPort; // p3.3.49 if (devport == -1) continue; //MidiDevice* dev = *id; //MidiDevice* dev = r->device; MidiDevice* dev = midiPorts[devport].device(); // p3.3.49 if (!dev) continue; // p3.3.50 Removed //int channel = r->channel; // NOTE: TODO: Special for input device sysex 'channel' marked as -1, ** IF we end up going with that method **. // This would mean having a separate 'System' channel listed in the routing popups. // The other alternative is to accept sysex from a device as long as ANY regular channel is routed from it, // this does not require a 'System' channel listed in the routing popups. // But that requires more code below... Done. //if(channel == -1) //channel = MIDI_CHANNELS; // Special channel '17' // continue; //int devport = dev->midiPort(); // record only from ports marked in portMask: //if (devport == -1 || !(portMask & (1 << devport))) //if (devport == -1) // continue; //MREventList* el = dev->recordEvents(); //MidiFifo& rf = dev->recordEvents(); int channelMask = r->channel; // p3.3.50 if (channelMask == -1 || channelMask == 0) continue; for (int channel = 0; channel < MIDI_CHANNELS; ++channel) // p3.3.50 { if (!(channelMask & (1 << channel))) continue; if (!dev->sysexFIFOProcessed()) { // Set to the sysex fifo at first. MidiFifo& rf = dev->recordEvents(MIDI_CHANNELS); // Get the frozen snapshot of the size. int count = dev->tmpRecordCount(MIDI_CHANNELS); for (int i = 0; i < count; ++i) { MidiPlayEvent event(rf.peek(i)); //unsigned time = event.time() + segmentSize*(segmentCount-1); //unsigned time = event.time() + (extsync ? config.division/24 : segmentSize*(segmentCount-1)); //unsigned time = extsync ? curTickPos : (event.time() + segmentSize*(segmentCount-1)); //event.setTime(time); //if(!extsync) // event.setTime(event.time() + segmentSize*(segmentCount-1)); event.setPort(port); // dont't echo controller changes back to software // synthesizer: if (!dev->isSynti() && md && track->recEcho()) playEvents->add(event); // If syncing externally the event time is already in units of ticks, set above. if (!extsync) { //time = tempomap.frame2tick(event.time()); //event.setTime(time); // set tick time event.setTime(tempomap.frame2tick(event.time())); // set tick time } if (recording) rl->add(event); } dev->setSysexFIFOProcessed(true); } // Set to the sysex fifo at first. ///MidiFifo& rf = dev->recordEvents(MIDI_CHANNELS); // Get the frozen snapshot of the size. ///int count = dev->tmpRecordCount(MIDI_CHANNELS); // Iterate once for sysex fifo (if needed), once for channel fifos. ///for(int sei = 0; sei < 2; ++sei) { // If on first pass, do sysex fifo. /* if(sei == 0) { // Ignore any further channel routes on this device if already done here. if(dev->sysexFIFOProcessed()) continue; // Go ahead and set this now. dev->setSysexFIFOProcessed(true); // Allow it to fall through with the sysex fifo and count... } else { // We're on the second pass, do channel fifos. rf = dev->recordEvents(channel); // Get the frozen snapshot of the size. count = dev->tmpRecordCount(channel); } */ MidiFifo& rf = dev->recordEvents(channel); int count = dev->tmpRecordCount(channel); //for (iMREvent ie = el->begin(); ie != el->end(); ++ie) for (int i = 0; i < count; ++i) { MidiPlayEvent event(rf.peek(i)); //int channel = ie->channel(); ///int channel = event.channel(); int defaultPort = devport; ///if (!(channelMask & (1 << channel))) ///{ /// continue; ///} //MidiPlayEvent event(*ie); int drumRecPitch = 0; //prevent compiler warning: variable used without initialization MidiController *mc = 0; int ctl = 0; //Hmmm, hehhh... // TODO: Clean up a bit around here when it comes to separate events for rec & for playback. // But not before 0.7 (ml) int prePitch = 0, preVelo = 0; event.setChannel(track->outChannel()); if (event.isNote() || event.isNoteOff()) { // // apply track values // //Apply drum inkey: if (track->type() == Track::DRUM) { int pitch = event.dataA(); //Map note that is played according to drumInmap drumRecPitch = drumMap[(unsigned int) drumInmap[pitch]].enote; devport = drumMap[(unsigned int) drumInmap[pitch]].port; event.setPort(devport); channel = drumMap[(unsigned int) drumInmap[pitch]].channel; event.setA(drumMap[(unsigned int) drumInmap[pitch]].anote); event.setChannel(channel); } else { //Track transpose if non-drum prePitch = event.dataA(); int pitch = prePitch + track->transposition; if (pitch > 127) pitch = 127; if (pitch < 0) pitch = 0; event.setA(pitch); } if (!event.isNoteOff()) { preVelo = event.dataB(); int velo = preVelo + track->velocity; velo = (velo * track->compression) / 100; if (velo > 127) velo = 127; if (velo < 1) velo = 1; event.setB(velo); } } // Added by T356. else if (event.type() == ME_CONTROLLER) { //printf("11111111111111111111111111111111111111111111111111111\n"); if (track->type() == Track::DRUM) { //printf("2222222222222222222222222222222222222222222222222222222222\n"); ctl = event.dataA(); // Regardless of what port the event came from, is it a drum controller event // according to the track port's instrument? mc = tport->drumController(ctl); if (mc) { //printf("333333333333333333333333333333333333333333333333\n"); int pitch = ctl & 0x7f; ctl &= ~0xff; int dmindex = drumInmap[pitch] & 0x7f; //Map note that is played according to drumInmap drumRecPitch = drumMap[dmindex].enote; devport = drumMap[dmindex].port; event.setPort(devport); channel = drumMap[dmindex].channel; event.setA(ctl | drumMap[dmindex].anote); event.setChannel(channel); } } } // p3.3.25 // OOMidi uses a fixed clocks per quarternote of 24. // At standard 384 ticks per quarternote for example, // 384/24=16 for a division of 16 sub-frames (16 OOMidi 'ticks'). // That is what we'll use if syncing externally. //unsigned time = event.time() + segmentSize*(segmentCount-1); //unsigned time = event.time() + (extsync ? config.division/24 : segmentSize*(segmentCount-1)); // p3.3.34 // Oops, use the current tick. //unsigned time = extsync ? curTickPos : (event.time() + segmentSize*(segmentCount-1)); //event.setTime(time); // p3.3.35 // If ext sync, events are now time-stamped with last tick in MidiDevice::recordEvent(). // TODO: Tested, but record resolution not so good. Switch to wall clock based separate list in MidiDevice. // p3.3.36 //if(!extsync) // event.setTime(event.time() + segmentSize*(segmentCount-1)); // dont't echo controller changes back to software // synthesizer: if (!dev->isSynti()) { //printf("444444444444444444444444444444444444444444444444444444\n"); //Check if we're outputting to another port than default: if (devport == defaultPort) { //printf("5555555555555555555555555555555555555555555\n"); event.setPort(port); if (md && track->recEcho()) playEvents->add(event); } else { //printf("66666666666666666666666666666666666666\n"); // Hmm, this appears to work, but... Will this induce trouble with md->setNextPlayEvent?? MidiDevice* mdAlt = midiPorts[devport].device(); if (mdAlt && track->recEcho()) mdAlt->playEvents()->add(event); } // Shall we activate meters even while rec echo is off? Sure, why not... if (event.isNote() && event.dataB() > track->activity()) track->setActivity(event.dataB()); } // p3.3.25 // If syncing externally the event time is already in units of ticks, set above. if (!extsync) { //printf("7777777777777777777777777777777777777777777\n"); // p3.3.35 //time = tempomap.frame2tick(event.time()); //event.setTime(time); // set tick time event.setTime(tempomap.frame2tick(event.time())); // set tick time } // Special handling of events stored in rec-lists. a bit hACKish. TODO: Clean up (after 0.7)! :-/ (ml) if (recording) { //printf("888888888888888888888888888888888888888888888888\n"); // In these next steps, it is essential to set the recorded event's port // to the track port so buildMidiEventList will accept it. Even though // the port may have no device "<none>". // if (track->type() == Track::DRUM) { //printf("99999999999999999999999999999999999999999999999999\n"); // Is it a drum controller event? if (mc) { //printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n"); MidiPlayEvent drumRecEvent = event; drumRecEvent.setA(ctl | drumRecPitch); // In this case, preVelo is simply the controller value. drumRecEvent.setB(preVelo); drumRecEvent.setPort(port); //rec-event to current port drumRecEvent.setChannel(track->outChannel()); //rec-event to current channel rl->add(drumRecEvent); } else { //printf("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n"); MidiPlayEvent drumRecEvent = event; drumRecEvent.setA(drumRecPitch); drumRecEvent.setB(preVelo); // Changed by T356. // Tested: Events were not being recorded for a drum map entry pointing to a // different port. This must have been wrong - buildMidiEventList would ignore this. //drumRecEvent.setPort(devport); drumRecEvent.setPort(port); //rec-event to current port drumRecEvent.setChannel(track->outChannel()); //rec-event to current channel rl->add(drumRecEvent); } } else { //printf("ccccccccccccccccccccccccccccccccccccccccccccc\n"); // Restore record-pitch to non-transposed value since we don't want the note transposed twice next MidiPlayEvent recEvent = event; if (prePitch) recEvent.setA(prePitch); if (preVelo) recEvent.setB(preVelo); recEvent.setPort(port); recEvent.setChannel(track->outChannel()); rl->add(recEvent); } } } } } } } // Added by Tim. p3.3.8 if (md) { //printf("zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz\n"); md->setNextPlayEvent(playEvents->begin()); } } // // clear all recorded events in midiDevices // process stuck notes // for (iMidiDevice id = midiDevices.begin(); id != midiDevices.end(); ++id) { //printf("--------------------------aaaaaaaaaaaaaaaaaaaaaaaaaaaa\n"); MidiDevice* md = *id; ///md->recordEvents()->clear(); // By T356. Done processing this rec buffer, now flip to the other one. ///md->flipRecBuffer(); // We are done with the 'frozen' recording fifos, remove the events. md->afterProcess(); MPEventList* stuckNotes = md->stuckNotes(); MPEventList* playEvents = md->playEvents(); iMPEvent k; for (k = stuckNotes->begin(); k != stuckNotes->end(); ++k) { if (k->time() >= nextTickPos) break; MidiPlayEvent ev(*k); // p3.3.25 //int frame = tempomap.tick2frame(k->time()) + frameOffset; if (extsync) { ev.setTime(k->time()); } else { int frame = tempomap.tick2frame(k->time()) + frameOffset; ev.setTime(frame); } // p3.3.25 //ev.setTime(frame); playEvents->add(ev); } stuckNotes->erase(stuckNotes->begin(), k); md->setNextPlayEvent(playEvents->begin()); } //--------------------------------------------------- // insert metronome clicks //--------------------------------------------------- MidiDevice* md = 0; if (midiClickFlag) md = midiPorts[clickPort].device(); if (song->click() && (isPlaying() || state == PRECOUNT)) { MPEventList* playEvents = 0; MPEventList* stuckNotes = 0; if (md) { playEvents = md->playEvents(); stuckNotes = md->stuckNotes(); } int bar, beat; unsigned tick; bool isMeasure = false; while (midiClick < nextTickPos) { if (isPlaying()) { ///sigmap.tickValues(midiClick, &bar, &beat, &tick); AL::sigmap.tickValues(midiClick, &bar, &beat, &tick); isMeasure = beat == 0; } else if (state == PRECOUNT) { isMeasure = (clickno % clicksMeasure) == 0; } // p3.3.25 //int frame = tempomap.tick2frame(midiClick) + frameOffset; int evtime = extsync ? midiClick : tempomap.tick2frame(midiClick) + frameOffset; // p3.3.25 //MidiPlayEvent ev(frame, clickPort, clickChan, ME_NOTEON, MidiPlayEvent ev(evtime, clickPort, clickChan, ME_NOTEON, beatClickNote, beatClickVelo); if (md) { // p3.3.25 //MidiPlayEvent ev(frame, clickPort, clickChan, ME_NOTEON, MidiPlayEvent ev(evtime, clickPort, clickChan, ME_NOTEON, beatClickNote, beatClickVelo); if (isMeasure) { ev.setA(measureClickNote); ev.setB(measureClickVelo); } playEvents->add(ev); } if (audioClickFlag) { // p3.3.25 //MidiPlayEvent ev1(frame, 0, 0, ME_NOTEON, 0, 0); MidiPlayEvent ev1(evtime, 0, 0, ME_NOTEON, 0, 0); ev1.setA(isMeasure ? 0 : 1); metronome->playEvents()->add(ev1); } if (md) { ev.setB(0); // p3.3.25 // Removed. Why was this here? //frame = tempomap.tick2frame(midiClick+20) + frameOffset; // // Does it mean this should be changed too? // No, stuck notes are in units of ticks, not frames like (normal, non-external) play events... ev.setTime(midiClick + 10); if (md) stuckNotes->add(ev); } if (isPlaying()) ///midiClick = sigmap.bar2tick(bar, beat+1, 0); midiClick = AL::sigmap.bar2tick(bar, beat + 1, 0); else if (state == PRECOUNT) { midiClick += ticksBeat; if (clickno) --clickno; else state = START_PLAY; } } if (md) md->setNextPlayEvent(playEvents->begin()); if (audioClickFlag) metronome->setNextPlayEvent(metronome->playEvents()->begin()); } if (state == STOP) { //--------------------------------------------------- // end all notes //--------------------------------------------------- for (iMidiDevice imd = midiDevices.begin(); imd != midiDevices.end(); ++imd) { MidiDevice* md = *imd; MPEventList* playEvents = md->playEvents(); MPEventList* stuckNotes = md->stuckNotes(); for (iMPEvent k = stuckNotes->begin(); k != stuckNotes->end(); ++k) { MidiPlayEvent ev(*k); ev.setTime(0); // play now playEvents->add(ev); } stuckNotes->clear(); } } // p3.3.36 //int tickpos = audio->tickPos(); //bool extsync = extSyncFlag.value(); // // Special for Jack midi devices: Play all Jack midi events up to curFrame. // for (iMidiDevice id = midiDevices.begin(); id != midiDevices.end(); ++id) { (*id)->processMidi(); /* int port = md->midiPort(); MidiPort* mp = port != -1 ? &midiPorts[port] : 0; MPEventList* el = md->playEvents(); if (el->empty()) continue; iMPEvent i = md->nextPlayEvent(); for(; i != el->end(); ++i) { // If syncing to external midi sync, we cannot use the tempo map. // Therefore we cannot get sub-tick resolution. Just use ticks instead of frames. //if(i->time() > curFrame) if(i->time() > (extsync ? tickpos : curFrame)) { //printf(" curT %d frame %d\n", i->time(), curFrame); break; // skip this event } if(mp) { if(mp->sendEvent(*i)) break; } else { if(md->putEvent(*i)) break; } } md->setNextPlayEvent(i); */ } midiBusy = false; }