//========================================================= // MusE // Linux Music Editor // $Id: midi.cpp,v 1.43.2.22 2009/11/09 20:28:28 terminator356 Exp $ // // (C) Copyright 1999/2004 Werner Schweer (ws@seh.de) // (C) Copyright 2011-2012 Tim E. Real (terminator356 on users dot sourceforge dot net) // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; version 2 of // the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // //========================================================= #include #include #include "song.h" #include "midi.h" #include "drummap.h" #include "event.h" #include "globals.h" #include "midictrl.h" #include "marker/marker.h" #include "midiport.h" #include "midictrl.h" #include "sync.h" #include "audio.h" #include "mididev.h" #include "driver/alsamidi.h" #include "driver/jackmidi.h" #include "wave.h" #include "synth.h" #include "sync.h" #include "midiseq.h" #include "gconfig.h" #include "ticksynth.h" namespace MusECore { extern void dump(const unsigned char* p, int n); const unsigned char gmOnMsg[] = { 0x7e, 0x7f, 0x09, 0x01 }; const unsigned char gsOnMsg[] = { 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7f, 0x00, 0x41 }; const unsigned char gsOnMsg2[] = { 0x41, 0x10, 0x42, 0x12, 0x40, 0x01, 0x33, 0x50, 0x3c }; const unsigned char gsOnMsg3[] = { 0x41, 0x10, 0x42, 0x12, 0x40, 0x01, 0x34, 0x50, 0x3b }; const unsigned char xgOnMsg[] = { 0x43, 0x10, 0x4c, 0x00, 0x00, 0x7e, 0x00 }; const unsigned int gmOnMsgLen = sizeof(gmOnMsg); const unsigned int gsOnMsgLen = sizeof(gsOnMsg); const unsigned int gsOnMsg2Len = sizeof(gsOnMsg2); const unsigned int gsOnMsg3Len = sizeof(gsOnMsg3); const unsigned int xgOnMsgLen = sizeof(xgOnMsg); const unsigned char mmcDeferredPlayMsg[] = { 0x7f, 0x7f, 0x06, 0x03 }; const unsigned char mmcStopMsg[] = { 0x7f, 0x7f, 0x06, 0x01 }; const unsigned char mmcLocateMsg[] = { 0x7f, 0x7f, 0x06, 0x44, 0x06, 0x01, 0, 0, 0, 0, 0 }; const unsigned int mmcDeferredPlayMsgLen = sizeof(mmcDeferredPlayMsg); const unsigned int mmcStopMsgLen = sizeof(mmcStopMsg); const unsigned int mmcLocateMsgLen = sizeof(mmcLocateMsg); #define CALC_TICK(the_tick) lrintf((float(the_tick) * float(MusEGlobal::config.division) + float(div/2)) / float(div)); /*--------------------------------------------------------- * midi_meta_name *---------------------------------------------------------*/ QString midiMetaName(int meta) { const char* s = ""; switch (meta) { case 0: s = "Text 0: Sequence Number"; break; case 1: s = "Text 1: Track comment"; break; case 2: s = "Text 2: Copyright"; break; case 3: s = "Text 3: Sequence/Track Name"; break; case 4: s = "Text 4: Instrument Name"; break; case 5: s = "Text 5: Lyric"; break; case 6: s = "Text 6: Marker"; break; case 7: s = "Text 7: Cue Point"; break; case 8: s = "Text 8"; break; case 9: s = "Text 9: Device Name"; break; case 0x0a: s = "Text A"; break; case 0x0b: s = "Text B"; break; case 0x0c: s = "Text C"; break; case 0x0d: s = "Text D"; break; case 0x0e: s = "Text E"; break; case 0x0f: s = "Text F"; break; case 0x20: s = "Channel Prefix"; break; case 0x21: s = "Port Change"; break; case 0x2f: s = "End of Track"; break; case 0x51: s = "Set Tempo"; break; case 0x54: s = "SMPTE Offset"; break; case 0x58: s = "Time Signature"; break; case 0x59: s = "Key Signature"; break; case 0x74: s = "Sequencer-Specific1"; break; case 0x7f: s = "Sequencer-Specific2"; break; default: break; } return QString(s); } //--------------------------------------------------------- // QString nameSysex //--------------------------------------------------------- QString nameSysex(unsigned int len, const unsigned char* buf) { QString s; if(len == 0) return s; switch(buf[0]) { case 0x00: if(len < 3) return s; if (buf[1] == 0 && buf[2] == 0x41) s = "Microsoft"; break; case 0x01: s = "Sequential Circuits: "; break; case 0x02: s = "Big Briar: "; break; case 0x03: s = "Octave / Plateau: "; break; case 0x04: s = "Moog: "; break; case 0x05: s = "Passport Designs: "; break; case 0x06: s = "Lexicon: "; break; case 0x07: s = "Kurzweil"; break; case 0x08: s = "Fender"; break; case 0x09: s = "Gulbransen"; break; case 0x0a: s = "Delta Labas"; break; case 0x0b: s = "Sound Comp."; break; case 0x0c: s = "General Electro"; break; case 0x0d: s = "Techmar"; break; case 0x0e: s = "Matthews Research"; break; case 0x10: s = "Oberheim"; break; case 0x11: s = "PAIA: "; break; case 0x12: s = "Simmons: "; break; case 0x13: s = "DigiDesign"; break; case 0x14: s = "Fairlight: "; break; case 0x15: s = "JL Cooper"; break; case 0x16: s = "Lowery"; break; case 0x17: s = "Lin"; break; case 0x18: s = "Emu"; break; case 0x1b: s = "Peavy"; break; case 0x20: s = "Bon Tempi: "; break; case 0x21: s = "S.I.E.L: "; break; case 0x23: s = "SyntheAxe: "; break; case 0x24: s = "Hohner"; break; case 0x25: s = "Crumar"; break; case 0x26: s = "Solton"; break; case 0x27: s = "Jellinghaus Ms"; break; case 0x28: s = "CTS"; break; case 0x29: s = "PPG"; break; case 0x2f: s = "Elka"; break; case 0x36: s = "Cheetah"; break; case 0x3e: s = "Waldorf"; break; case 0x40: s = "Kawai: "; break; case 0x41: s = "Roland: "; break; case 0x42: s = "Korg: "; break; case 0x43: s = "Yamaha: "; break; case 0x44: s = "Casio"; break; case 0x45: s = "Akai"; break; case MUSE_SYNTH_SYSEX_MFG_ID: s = "MusE Soft Synth"; break; case 0x7d: s = "Educational Use"; break; case 0x7e: s = "Universal: Non Real Time"; break; case 0x7f: s = "Universal: Real Time"; break; default: s = "??: "; break; } // // following messages should not show up in event list // they are filtered while importing midi files // if ((len == gmOnMsgLen) && memcmp(buf, gmOnMsg, gmOnMsgLen) == 0) s += "GM-ON"; else if ((len == gsOnMsgLen) && memcmp(buf, gsOnMsg, gsOnMsgLen) == 0) s += "GS-ON"; else if ((len == xgOnMsgLen) && memcmp(buf, xgOnMsg, xgOnMsgLen) == 0) s += "XG-ON"; return s; } //--------------------------------------------------------- // buildMidiEventList // TODO: // parse data increment/decrement controller // NRPN/RPN fine/course data 7/14 Bit // must we set datah/datal to zero after change // of NRPN/RPN register? // generally: how to handle incomplete messages //--------------------------------------------------------- void buildMidiEventList(EventList* del, const MPEventList& el, MidiTrack* track, int div, bool addSysexMeta, bool doLoops) { int hbank = 0xff; int lbank = 0xff; int rpnh = -1; int rpnl = -1; int datah = 0; int datal = 0; int dataType = 0; // 0 : disabled, 0x20000 : rpn, 0x30000 : nrpn EventList mel; for (iMPEvent i = el.begin(); i != el.end(); ++i) { MidiPlayEvent ev = *i; if (!addSysexMeta && (ev.type() == ME_SYSEX || ev.type() == ME_META)) continue; if (!(ev.type() == ME_SYSEX || ev.type() == ME_META || ((ev.channel() == track->outChannel()) && (ev.port() == track->outPort())))) continue; unsigned tick = ev.time(); if(doLoops) { if(tick >= MusEGlobal::song->lPos().tick() && tick < MusEGlobal::song->rPos().tick()) { int loopn = ev.loopNum(); int loopc = MusEGlobal::audio->loopCount(); int cmode = MusEGlobal::song->cycleMode(); // CYCLE_NORMAL, CYCLE_MIX, CYCLE_REPLACE // If we want REPLACE and the event was recorded in a previous loop, // just ignore it. This will effectively ignore ALL previous loop events inside // the left and right markers, regardless of where recording was started or stopped. // We want to keep any loop 0 note-offs from notes which crossed over the left marker. // To avoid more searching here, just keep ALL note-offs from loop 0, and let code below // sort out and keep which ones had note-ons. if(!(ev.isNoteOff() && loopn == 0)) { if(cmode == Song::CYCLE_REPLACE && loopn < loopc) continue; // If we want NORMAL, same as REPLACE except keep all events from the previous loop // from rec stop position to right marker (and beyond). if(cmode == Song::CYCLE_NORMAL) { // Not sure of accuracy here. Adjust? Adjusted when used elsewhere? unsigned endRec = MusEGlobal::audio->getEndRecordPos().tick(); if((tick < endRec && loopn < loopc) || (tick >= endRec && loopn < (loopc - 1))) continue; } } } } Event e; switch(ev.type()) { case ME_NOTEON: e.setType(Note); if (track->type() == Track::DRUM) { int instr = MusEGlobal::drumInmap[ev.dataA()]; e.setPitch(instr); } else e.setPitch(ev.dataA()); e.setVelo(ev.dataB()); e.setLenTick(0); break; case ME_NOTEOFF: e.setType(Note); if (track->type() == Track::DRUM) { int instr = MusEGlobal::drumInmap[ev.dataA()]; e.setPitch(instr); } else e.setPitch(ev.dataA()); e.setVelo(0); e.setVeloOff(ev.dataB()); e.setLenTick(0); break; case ME_POLYAFTER: e.setType(Controller); e.setA((CTRL_POLYAFTER & ~0xff) | (ev.dataA() & 0x7f)); e.setB(ev.dataB()); break; case ME_CONTROLLER: { int val = ev.dataB(); switch(ev.dataA()) { case CTRL_HBANK: hbank = val; break; case CTRL_LBANK: lbank = val; break; case CTRL_HDATA: datah = val; // check if a CTRL_LDATA follows // e.g. wie have a 14 bit controller: { iMPEvent ii = i; ++ii; bool found = false; for (; ii != el.end(); ++ii) { MidiPlayEvent ev = *ii; if (ev.type() == ME_CONTROLLER) { if (ev.dataA() == CTRL_LDATA) { // handle later found = true; } break; } } if (!found) { if (rpnh == -1 || rpnl == -1) { printf("parameter number not defined, data 0x%x\n", datah); } else { int ctrl = dataType | (rpnh << 8) | rpnl; e.setType(Controller); e.setA(ctrl); e.setB(datah); } } } break; case CTRL_LDATA: datal = val; if (rpnh == -1 || rpnl == -1) { printf("parameter number not defined, data 0x%x 0x%x, tick %d, channel %d\n", datah, datal, tick, track->outChannel()); break; } // assume that the sequence is always // CTRL_HDATA - CTRL_LDATA // eg. that LDATA is always send last e.setType(Controller); // 14 Bit RPN/NRPN e.setA((dataType+0x30000) | (rpnh << 8) | rpnl); e.setB((datah << 7) | datal); break; case CTRL_HNRPN: rpnh = val; dataType = 0x30000; break; case CTRL_LNRPN: rpnl = val; dataType = 0x30000; break; case CTRL_HRPN: rpnh = val; dataType = 0x20000; break; case CTRL_LRPN: rpnl = val; dataType = 0x20000; break; default: e.setType(Controller); int ctl = ev.dataA(); e.setA(ctl); if(track->type() == Track::DRUM) { // Is it a drum controller event, according to the track port's instrument? MidiController *mc = MusEGlobal::midiPorts[track->outPort()].drumController(ctl); if(mc) // Store an index into the drum map. e.setA((ctl & ~0xff) | MusEGlobal::drumInmap[ctl & 0x7f]); } e.setB(val); break; } } break; case ME_PROGRAM: e.setType(Controller); e.setA(CTRL_PROGRAM); e.setB((hbank << 16) | (lbank << 8) | ev.dataA()); break; case ME_AFTERTOUCH: e.setType(Controller); e.setA(CTRL_AFTERTOUCH); e.setB(ev.dataA()); break; case ME_PITCHBEND: e.setType(Controller); e.setA(CTRL_PITCH); e.setB(ev.dataA()); break; case ME_SYSEX: e.setType(Sysex); e.setData(ev.data(), ev.len()); break; case ME_META: { const unsigned char* data = ev.data(); switch (ev.dataA()) { case ME_META_TEXT_1_COMMENT: // Text if (track->comment().isEmpty()) track->setComment(QString((const char*)data)); else track->setComment(track->comment() + "\n" + QString((const char*)data)); break; case ME_META_TEXT_3_TRACK_NAME: // Sequence-/TrackName track->setName(QString((char*)data)); break; case ME_META_TEXT_6_MARKER: // Marker { unsigned ltick = CALC_TICK(tick); MusEGlobal::song->addMarker(QString((const char*)(data)), ltick, false); } break; case ME_META_TEXT_5_LYRIC: // Lyrics case ME_META_TEXT_8: // text case ME_META_TEXT_9_DEVICE_NAME: case ME_META_TEXT_A: break; case ME_META_TEXT_F_TRACK_COMMENT: // Track Comment track->setComment(QString((char*)data)); break; case ME_META_SET_TEMPO: // Tempo { unsigned tempo = data[2] + (data[1] << 8) + (data[0] <<16); unsigned ltick = CALC_TICK(tick); // FIXME: After ca 10 mins 32 bits will not be enough... This expression has to be changed/factorized or so in some "sane" way... MusEGlobal::tempomap.addTempo(ltick, tempo); } break; case ME_META_TIME_SIGNATURE: // Time Signature { int timesig_z = data[0]; int n = data[1]; int timesig_n = 1; for (int i = 0; i < n; i++) timesig_n *= 2; int ltick = CALC_TICK(tick); AL::sigmap.add(ltick, AL::TimeSignature(timesig_z, timesig_n)); } break; case ME_META_KEY_SIGNATURE: // Key Signature break; default: printf("buildMidiEventList: unknown Meta 0x%x %d unabsorbed, adding instead to track:%s\n", ev.dataA(), ev.dataA(), track->name().toLatin1().constData()); e.setType(Meta); e.setA(ev.dataA()); e.setData(ev.data(), ev.len()); } } break; } // switch(ev.type() if (!e.empty()) { e.setTick(tick); mel.add(e); } } // i != el.end() //--------------------------------------------------- // read NoteOn events and remove corresponding NoteOffs //--------------------------------------------------- for (iEvent i = mel.begin(); i != mel.end(); ++i) { Event ev = i->second; if (ev.isNote()) { if (!ev.isNoteOff()) { // If the event length is not zero, it means the event and its // note on/off have already been taken care of. So ignore it. if(ev.lenTick() != 0) continue; iEvent k; for (k = mel.lower_bound(ev.tick()); k != mel.end(); ++k) { Event event = k->second; if (ev.isNoteOff(event)) { int t = k->first - i->first; if (t <= 0) { if (MusEGlobal::debugMsg) { printf("Note len is (%d-%d)=%d, set to 1\n", k->first, i->first, k->first - i->first); ev.dump(); event.dump(); } t = 1; } ev.setLenTick(t); ev.setVeloOff(event.veloOff()); break; } } if (k == mel.end()) { printf("-no note-off! %d pitch %d velo %d\n", ev.tick(), ev.pitch(), ev.velo()); // // switch off at end of measure // int endTick = MusEGlobal::song->roundUpBar(ev.tick()+1); ev.setLenTick(endTick-ev.tick()); } else { if (k==i) //this will never happen, because i->second has to be a NOTE ON, //while k has to be a NOTE OFF. but in case something changes: printf("ERROR: THIS SHOULD NEVER HAPPEN: k==i in midi.cpp:buildMidiEventList()\n"); else mel.erase(k); i = mel.begin(); continue; } } } } for (iEvent i = mel.begin(); i != mel.end(); ++i) { Event ev = i->second; if (ev.isNoteOff()) { printf("+extra note-off! %d pitch %d velo %d\n", i->first, ev.pitch(), ev.velo()); continue; } int tick = CALC_TICK(ev.tick()); if (ev.isNote()) { int lenTick = CALC_TICK(ev.lenTick()); ev.setLenTick(lenTick); } ev.setTick(tick); del->add(ev); } } } // namespace MusECore namespace MusECore { //--------------------------------------------------------- // midiPortsChanged //--------------------------------------------------------- void Audio::midiPortsChanged() { write(sigFd, "P", 1); } //--------------------------------------------------------- // sendLocalOff //--------------------------------------------------------- void Audio::sendLocalOff() { for (int k = 0; k < MIDI_PORTS; ++k) { for (int i = 0; i < MIDI_CHANNELS; ++i) MusEGlobal::midiPorts[k].sendEvent(MusECore::MidiPlayEvent(0, k, i, MusECore::ME_CONTROLLER, MusECore::CTRL_LOCAL_OFF, 0), true); } } //--------------------------------------------------------- // panic //--------------------------------------------------------- void Audio::panic() { for (int i = 0; i < MIDI_PORTS; ++i) { MusECore::MidiPort* port = &MusEGlobal::midiPorts[i]; if (port == 0) // ?? continue; for (int chan = 0; chan < MIDI_CHANNELS; ++chan) { if (MusEGlobal::debugMsg) printf("send all sound of to midi port %d channel %d\n", i, chan); port->sendEvent(MusECore::MidiPlayEvent(0, i, chan, MusECore::ME_CONTROLLER, MusECore::CTRL_ALL_SOUNDS_OFF, 0), true); port->sendEvent(MusECore::MidiPlayEvent(0, i, chan, MusECore::ME_CONTROLLER, MusECore::CTRL_RESET_ALL_CTRL, 0), true); } } } //--------------------------------------------------------- // initDevices // - called when instrument init sequences plus controller // defaults should be checked and/or sent // - called from arranger pulldown menu //--------------------------------------------------------- void Audio::initDevices(bool force) { for (int i = 0; i < MIDI_PORTS; ++i) { MusEGlobal::midiPorts[i].sendPendingInitializations(force); } } //--------------------------------------------------------- // collectEvents // collect events for next audio segment //--------------------------------------------------------- void Audio::collectEvents(MusECore::MidiTrack* track, unsigned int cts, unsigned int nts) { int port = track->outPort(); int channel = track->outChannel(); int defaultPort = port; MidiDevice* md = MusEGlobal::midiPorts[port].device(); PartList* pl = track->parts(); for (iPart p = pl->begin(); p != pl->end(); ++p) { MusECore::MidiPart* part = (MusECore::MidiPart*)(p->second); // dont play muted parts if (part->mute()) continue; const 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; // Do not play events which are past the end of this part. if(etick > partLen) continue; ciEvent ie = events.lower_bound(stick); ciEvent 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() && MusEGlobal::drumMap[instr].mute) continue; } else if (track->type() == Track::NEW_DRUM) { int instr = ev.pitch(); // ignore muted drums if (ev.isNote() && track->drummap()[instr].mute) continue; } unsigned tick = ev.tick() + offset; unsigned frame = MusEGlobal::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 = MusEGlobal::drumMap[instr].anote; // Default to track port if -1 and track channel if -1. port = MusEGlobal::drumMap[instr].port; //This changes to non-default port if(port == -1) port = track->outPort(); channel = MusEGlobal::drumMap[instr].channel; if(channel == -1) channel = track->outChannel(); velo = int(double(velo) * (double(MusEGlobal::drumMap[instr].vol) / 100.0)) ; } else if (track->type() != Track::NEW_DRUM) { // transpose non drum notes pitch += (track->transposition + MusEGlobal::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) { // 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. p3.3.25 if(MusEGlobal::extSyncFlag.value()) md->addScheduledEvent(MusECore::MidiPlayEvent(tick, port, channel, MusECore::ME_NOTEON, pitch, velo)); else md->addScheduledEvent(MusECore::MidiPlayEvent(frame, port, channel, MusECore::ME_NOTEON, pitch, velo)); md->addStuckNote(MusECore::MidiPlayEvent(tick + len, port, channel, veloOff ? MusECore::ME_NOTEOFF : MusECore::ME_NOTEON, pitch, veloOff)); } else { //Handle events to different port than standard. MidiDevice* mdAlt = MusEGlobal::midiPorts[port].device(); if (mdAlt) { if(MusEGlobal::extSyncFlag.value()) // p3.3.25 mdAlt->addScheduledEvent(MusECore::MidiPlayEvent(tick, port, channel, MusECore::ME_NOTEON, pitch, velo)); else mdAlt->addScheduledEvent(MusECore::MidiPlayEvent(frame, port, channel, MusECore::ME_NOTEON, pitch, velo)); mdAlt->addStuckNote(MusECore::MidiPlayEvent(tick + len, port, channel, veloOff ? MusECore::ME_NOTEOFF : MusECore::ME_NOTEON, pitch, veloOff)); } } if(velo > track->activity()) track->setActivity(velo); } break; case Controller: { if (track->type() == Track::DRUM) { int ctl = ev.dataA(); // Is it a drum controller event, according to the track port's instrument? MusECore::MidiController *mc = MusEGlobal::midiPorts[defaultPort].drumController(ctl); if(mc) { int instr = ctl & 0x7f; ctl &= ~0xff; int pitch = MusEGlobal::drumMap[instr].anote & 0x7f; // Default to track port if -1 and track channel if -1. port = MusEGlobal::drumMap[instr].port; //This changes to non-default port if(port == -1) port = track->outPort(); channel = MusEGlobal::drumMap[instr].channel; if(channel == -1) channel = track->outChannel(); MidiDevice* mdAlt = MusEGlobal::midiPorts[port].device(); if(mdAlt) { // 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. p3.3.25 if(MusEGlobal::extSyncFlag.value()) mdAlt->addScheduledEvent(MusECore::MidiPlayEvent(tick, port, channel, MusECore::ME_CONTROLLER, ctl | pitch, ev.dataB())); else mdAlt->addScheduledEvent(MusECore::MidiPlayEvent(frame, port, channel, MusECore::ME_CONTROLLER, ctl | pitch, ev.dataB())); } break; } } if(MusEGlobal::extSyncFlag.value()) // p3.3.25 md->addScheduledEvent(MusECore::MidiPlayEvent(tick, port, channel, ev)); else md->addScheduledEvent(MusECore::MidiPlayEvent(frame, port, channel, ev)); } break; default: if(MusEGlobal::extSyncFlag.value()) // p3.3.25 md->addScheduledEvent(MusECore::MidiPlayEvent(tick, port, channel, ev)); else md->addScheduledEvent(MusECore::MidiPlayEvent(frame, port, channel, ev)); break; } } } } //--------------------------------------------------------- // processMidi // - collects midi events for current audio segment and // sends them to midi thread // - current audio segment position is (curTickPos, nextTickPos) // - called from midiseq thread, // executed in audio thread //--------------------------------------------------------- void Audio::processMidi() { MusEGlobal::midiBusy=true; bool extsync = MusEGlobal::extSyncFlag.value(); // // TODO: syntis should directly write into recordEventList // for (iMidiDevice id = MusEGlobal::midiDevices.begin(); id != MusEGlobal::midiDevices.end(); ++id) { MidiDevice* md = *id; // klumsy hack for MESS synti devices: if(md->isSynti()) { SynthI* s = (SynthI*)md; while (s->eventsPending()) { MusECore::MidiRecordEvent ev = s->receiveEvent(); // FIXME: This is for recording the events sent by GUI. // It never gets a chance to be processed since reading of // record FIFOs is done only by connected input ROUTES, below. // To be useful, the synth itself must be allowed to be chosen // as an input route, which is simple enough, but we currently don't // list synths as inputs for fear of too many INCOMPATIBLE messages // from DIFFERING synths. However, we could allow ONLY THIS synth // to be listed and therefore be automatically connected too, if desired. //md->recordEvent(ev); // // For now, instead of recording, here is the minimum that we must do: // // Update hardware state so knobs and boxes are updated. Optimize to avoid re-setting existing values. // Same code as in MidiPort::sendEvent() if(md->midiPort() != -1) { MidiPort* mp = &MusEGlobal::midiPorts[md->midiPort()]; if(ev.type() == ME_CONTROLLER) { int da = ev.dataA(); int db = ev.dataB(); db = mp->limitValToInstrCtlRange(da, db); mp->setHwCtrlState(ev.channel(), da, db); } else if(ev.type() == ME_PITCHBEND) { int da = mp->limitValToInstrCtlRange(CTRL_PITCH, ev.dataA()); mp->setHwCtrlState(ev.channel(), CTRL_PITCH, da); } else if(ev.type() == ME_AFTERTOUCH) { int da = mp->limitValToInstrCtlRange(CTRL_AFTERTOUCH, ev.dataA()); mp->setHwCtrlState(ev.channel(), CTRL_AFTERTOUCH, da); } else if(ev.type() == ME_POLYAFTER) { int ctl = (CTRL_POLYAFTER & ~0xff) | (ev.dataA() & 0x7f); int db = mp->limitValToInstrCtlRange(ctl, ev.dataB()); mp->setHwCtrlState(ev.channel(), ctl , db); } else if(ev.type() == ME_PROGRAM) { mp->setHwCtrlState(ev.channel(), CTRL_PROGRAM, ev.dataA()); } } } } md->collectMidiEvents(); // Take snapshots of the current sizes of the recording fifos, // because they may change while here in process, asynchronously. md->beforeProcess(); // // --------- Handle midi events for audio tracks ----------- // int port = md->midiPort(); // Port should be same as event.port() from this device. Same idea event.channel(). if(port < 0) continue; for(int chan = 0; chan < MIDI_CHANNELS; ++chan) { MusECore::MidiRecFifo& rf = md->recordEvents(chan); int count = md->tmpRecordCount(chan); for(int i = 0; i < count; ++i) { MusECore::MidiRecordEvent event(rf.peek(i)); int etype = event.type(); if(etype == MusECore::ME_CONTROLLER || etype == MusECore::ME_PITCHBEND || etype == MusECore::ME_PROGRAM) { int ctl, val; if(etype == MusECore::ME_CONTROLLER) { ctl = event.dataA(); val = event.dataB(); } else if(etype == MusECore::ME_PITCHBEND) { ctl = MusECore::CTRL_PITCH; val = event.dataA(); } else if(etype == MusECore::ME_PROGRAM) { ctl = MusECore::CTRL_PROGRAM; val = event.dataA(); } // Midi learn! MusEGlobal::midiLearnPort = port; MusEGlobal::midiLearnChan = chan; MusEGlobal::midiLearnCtrl = ctl; // Send to audio tracks... for (MusECore::iTrack t = MusEGlobal::song->tracks()->begin(); t != MusEGlobal::song->tracks()->end(); ++t) { if((*t)->isMidiTrack()) continue; MusECore::AudioTrack* track = static_cast(*t); MidiAudioCtrlMap* macm = track->controller()->midiControls(); int h = macm->index_hash(port, chan, ctl); std::pair range = macm->equal_range(h); for(ciMidiAudioCtrlMap imacm = range.first; imacm != range.second; ++imacm) { const MidiAudioCtrlStruct* macs = &imacm->second; int actrl = macs->audioCtrlId(); iCtrlList icl = track->controller()->find(actrl); if(icl == track->controller()->end()) continue; CtrlList* cl = icl->second; double dval = midi2AudioCtrlValue(cl, macs, ctl, val); // Time here needs to be frames always. unsigned int ev_t = event.time(); unsigned int t = ev_t; #ifdef _AUDIO_USE_TRUE_FRAME_ unsigned int pframe = _previousPos.frame(); #else unsigned int pframe = _pos.frame(); #endif if(pframe > t) // Technically that's an error, shouldn't happen t = 0; else // Subtract the current audio position frame t -= pframe; // Add the current running sync frame to make the control processing happy t += syncFrame; track->addScheduledControlEvent(actrl, dval, t); // Rec automation... // For the record time, if stopped we don't want the circular running position, // just the static one. unsigned int rec_t = isPlaying() ? ev_t : pframe; if(!MusEGlobal::automation) continue; AutomationType at = track->automationType(); // Unlike our built-in gui controls, there is not much choice here but to // just do this: if ( (at == AUTO_WRITE) || (at == AUTO_READ && !MusEGlobal::audio->isPlaying()) || (at == AUTO_TOUCH) ) track->enableController(actrl, false); if(isPlaying()) { if(at == AUTO_WRITE || at == AUTO_TOUCH) track->recEvents()->push_back(CtrlRecVal(rec_t, actrl, dval)); } else { if(at == AUTO_WRITE) track->recEvents()->push_back(CtrlRecVal(rec_t, actrl, dval)); else if(at == AUTO_TOUCH) // In touch mode and not playing. Send directly to controller list. // Add will replace if found. cl->add(rec_t, dval); } } } } } } } for (MusECore::iMidiTrack t = MusEGlobal::song->midis()->begin(); t != MusEGlobal::song->midis()->end(); ++t) { MusECore::MidiTrack* track = *t; int port = track->outPort(); MidiDevice* md = MusEGlobal::midiPorts[port].device(); if(md) { // only add track events if the track is unmuted and turned on if(!track->isMute() && !track->off()) { if(isPlaying() && (curTickPos < nextTickPos)) collectEvents(track, curTickPos, nextTickPos); } } // //----------midi recording // if (track->recordFlag()) { MusECore::MidiPort* tport = &MusEGlobal::midiPorts[port]; RouteList* irl = track->inRoutes(); for(ciRoute r = irl->begin(); r != irl->end(); ++r) { if(!r->isValid() || (r->type != Route::MIDI_PORT_ROUTE)) continue; int devport = r->midiPort; if (devport == -1) continue; MidiDevice* dev = MusEGlobal::midiPorts[devport].device(); if(!dev) continue; int channelMask = r->channel; if(channelMask == -1 || channelMask == 0) continue; for(int channel = 0; channel < MIDI_CHANNELS; ++channel) { if(!(channelMask & (1 << channel))) continue; if(!dev->sysexFIFOProcessed()) { // Set to the sysex fifo at first. MusECore::MidiRecFifo& 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) { MusECore::MidiRecordEvent event(rf.peek(i)); event.setPort(port); // dont't echo controller changes back to software // synthesizer: if(!dev->isSynti() && md && track->recEcho()) { // All recorded events arrived in the previous period. Shift into this period for playback. unsigned int et = event.time(); #ifdef _AUDIO_USE_TRUE_FRAME_ unsigned int t = et - _previousPos.frame() + _pos.frame() + frameOffset; #else unsigned int t = et + frameOffset; #endif event.setTime(t); md->addScheduledEvent(event); event.setTime(et); // Restore for recording. } // Make sure the event is recorded in units of ticks. if(extsync) event.setTime(event.tick()); // HACK: Transfer the tick to the frame time else event.setTime(MusEGlobal::tempomap.frame2tick(event.time())); if(recording) track->mpevents.add(event); } dev->setSysexFIFOProcessed(true); } MusECore::MidiRecFifo& rf = dev->recordEvents(channel); int count = dev->tmpRecordCount(channel); for(int i = 0; i < count; ++i) { MusECore::MidiRecordEvent event(rf.peek(i)); int defaultPort = devport; int drumRecPitch=0; //prevent compiler warning: variable used without initialization MusECore::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 MusEGlobal::drumInmap drumRecPitch = MusEGlobal::drumMap[(unsigned int)MusEGlobal::drumInmap[pitch]].enote; // Default to track port if -1 and track channel if -1. devport = MusEGlobal::drumMap[(unsigned int)MusEGlobal::drumInmap[pitch]].port; if(devport == -1) devport = track->outPort(); event.setPort(devport); int mapchan = MusEGlobal::drumMap[(unsigned int)MusEGlobal::drumInmap[pitch]].channel; if(mapchan != -1) event.setChannel(mapchan); event.setA(MusEGlobal::drumMap[(unsigned int)MusEGlobal::drumInmap[pitch]].anote); } else if (track->type() == Track::NEW_DRUM) { event.setA(track->map_drum_in(event.dataA())); if (MusEGlobal::config.newDrumRecordCondition & MusECore::DONT_REC_HIDDEN && track->drummap_hidden()[event.dataA()] ) continue; // skip that event, proceed with the next if (MusEGlobal::config.newDrumRecordCondition & MusECore::DONT_REC_MUTED && track->drummap()[event.dataA()].mute ) continue; // skip that event, proceed with the next } 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); } } else if(event.type() == MusECore::ME_CONTROLLER) { if(track->type() == Track::DRUM) { 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) { int pitch = ctl & 0x7f; ctl &= ~0xff; int dmindex = MusEGlobal::drumInmap[pitch] & 0x7f; //Map note that is played according to MusEGlobal::drumInmap drumRecPitch = MusEGlobal::drumMap[dmindex].enote; // Default to track port if -1 and track channel if -1. devport = MusEGlobal::drumMap[dmindex].port; if(devport == -1) devport = track->outPort(); event.setPort(devport); int mapchan = MusEGlobal::drumMap[dmindex].channel; if(mapchan != -1) event.setChannel(mapchan); event.setA(ctl | MusEGlobal::drumMap[dmindex].anote); } } else if (track->type() == Track::NEW_DRUM) //FINDMICHJETZT TEST { ctl = event.dataA(); if (tport->drumController(ctl)) // is it a drum controller? { int pitch = ctl & 0x7f; // pitch is now the incoming pitch pitch = track->map_drum_in(pitch); // pitch is now the mapped (recorded) pitch event.setA((ctl & ~0xff) | pitch); // map the drum ctrl's value accordingly if (MusEGlobal::config.newDrumRecordCondition & MusECore::DONT_REC_HIDDEN && track->drummap_hidden()[pitch] ) continue; // skip that event, proceed with the next if (MusEGlobal::config.newDrumRecordCondition & MusECore::DONT_REC_MUTED && track->drummap()[pitch].mute ) continue; // skip that event, proceed with the next } } } // MusE 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 MusE 'ticks'). // If ext sync, events are now time-stamped with last tick in MidiDevice::recordEvent(). p3.3.35 // TODO: Tested, but record resolution not so good. Switch to wall clock based separate list in MidiDevice. // dont't echo controller changes back to software // synthesizer: if (!dev->isSynti()) { // All recorded events arrived in previous period. Shift into this period for playback. // frameoffset needed to make process happy. unsigned int et = event.time(); #ifdef _AUDIO_USE_TRUE_FRAME_ unsigned int t = et - _previousPos.frame() + _pos.frame() + frameOffset; #else unsigned int t = et + frameOffset; #endif event.setTime(t); // Check if we're outputting to another port than default: if (devport == defaultPort) { event.setPort(port); if(md && track->recEcho()) md->addScheduledEvent(event); } else { // Hmm, this appears to work, but... Will this induce trouble with md->setNextPlayEvent?? MidiDevice* mdAlt = MusEGlobal::midiPorts[devport].device(); if(mdAlt && track->recEcho()) mdAlt->addScheduledEvent(event); } event.setTime(et); // Restore for recording. // Shall we activate meters even while rec echo is off? Sure, why not... if(event.isNote() && event.dataB() > track->activity()) track->setActivity(event.dataB()); } // Make sure the event is recorded in units of ticks. if(extsync) event.setTime(event.tick()); // HACK: Transfer the tick to the frame time else event.setTime(MusEGlobal::tempomap.frame2tick(event.time())); // Special handling of events stored in rec-lists. a bit hACKish. TODO: Clean up (after 0.7)! :-/ (ml) if (recording) { // 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 "". // if (track->type() == Track::DRUM) //FINDMICHJETZT no changes. TEST { // Is it a drum controller event? if(mc) { MusECore::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 track->mpevents.add(drumRecEvent); } else { MusECore::MidiPlayEvent drumRecEvent = event; drumRecEvent.setA(drumRecPitch); drumRecEvent.setB(preVelo); // Changed to 'port'. Events were not being recorded for a drum map entry pointing to a // different port. That must have been wrong - buildMidiEventList would ignore that. Tim. drumRecEvent.setPort(port); //rec-event to current port drumRecEvent.setChannel(track->outChannel()); //rec-event to current channel track->mpevents.add(drumRecEvent); } } else { // Restore record-pitch to non-transposed value since we don't want the note transposed twice next MusECore::MidiPlayEvent recEvent = event; if (prePitch) recEvent.setA(prePitch); if (preVelo) recEvent.setB(preVelo); recEvent.setPort(port); recEvent.setChannel(track->outChannel()); track->mpevents.add(recEvent); } } } } } } } //--------------------------------------------------- // insert metronome clicks //--------------------------------------------------- MidiDevice* md = 0; if (MusEGlobal::midiClickFlag) md = MusEGlobal::midiPorts[MusEGlobal::clickPort].device(); if (MusEGlobal::song->click() && (isPlaying() || state == PRECOUNT)) { int bar, beat, z, n; unsigned tick; AudioTickSound audioTickSound = MusECore::beatSound; while (midiClick < nextTickPos) { if (isPlaying()) { AL::sigmap.tickValues(midiClick, &bar, &beat, &tick); AL::sigmap.timesig(midiClick, z, n); //n = 2; if (tick == 0 && beat == 0) { audioTickSound = MusECore::measureSound; if (MusEGlobal::debugMsg) printf("meas: midiClick %d nextPos %d bar %d beat %d tick %d z %d n %d div %d\n", midiClick, nextTickPos, bar, beat, tick, z, n, MusEGlobal::config.division); } else if (tick == unsigned(MusEGlobal::config.division - (MusEGlobal::config.division/(n*2)))) { audioTickSound = MusECore::accent2Sound; if (MusEGlobal::debugMsg) printf("acc2: midiClick %d nextPos %d bar %d beat %d tick %d z %d n %d div %d\n", midiClick, nextTickPos, bar, beat, tick, z, n, MusEGlobal::config.division); } else if (tick == unsigned(MusEGlobal::config.division - (MusEGlobal::config.division/n))) { audioTickSound = MusECore::accent1Sound; if (MusEGlobal::debugMsg) printf("acc1: midiClick %d nextPos %d bar %d beat %d tick %d z %d n %d div %d\n", midiClick, nextTickPos, bar, beat, tick, z, n, MusEGlobal::config.division); } else { if (MusEGlobal::debugMsg) printf("beat: midiClick %d nextPos %d bar %d beat %d tick %d z %d n %d div %d\n", midiClick, nextTickPos, bar, beat, tick, z, n, MusEGlobal::config.division); } } else if (state == PRECOUNT) { if ((clickno % clicksMeasure) == 0) { audioTickSound = MusECore::measureSound; } } int evtime = extsync ? midiClick : MusEGlobal::tempomap.tick2frame(midiClick) + frameOffset; // p3.3.25 MusECore::MidiPlayEvent ev(evtime, MusEGlobal::clickPort, MusEGlobal::clickChan, MusECore::ME_NOTEON, MusEGlobal::beatClickNote, MusEGlobal::beatClickVelo); if (audioTickSound == MusECore::measureSound) { ev.setA(MusEGlobal::measureClickNote); ev.setB(MusEGlobal::measureClickVelo); } if (audioTickSound == MusECore::accent1Sound) { ev.setA(MusEGlobal::accentClick1); ev.setB(MusEGlobal::accentClick1Velo); } if (audioTickSound == MusECore::accent2Sound) { ev.setA(MusEGlobal::accentClick2); ev.setB(MusEGlobal::accentClick2Velo); } if (md) { md->addScheduledEvent(ev); ev.setB(0); ev.setTime(midiClick+10); md->addStuckNote(ev); } if (MusEGlobal::audioClickFlag) { ev.setA(audioTickSound); metronome->addScheduledEvent(ev); // Built-in metronome synth does not use stuck notes... } if (isPlaying()) { // State machine to select next midiClick position. if (MusEGlobal::clickSamples == MusEGlobal::newSamples) { if (tick == 0) {// ON key midiClick = AL::sigmap.bar2tick(bar, beat, MusEGlobal::config.division - ((MusEGlobal::config.division/n))); } else if (tick >= unsigned(MusEGlobal::config.division - (MusEGlobal::config.division/(n*2)))) { // second accent tick midiClick = AL::sigmap.bar2tick(bar, beat+1, 0); } else if (tick < unsigned(MusEGlobal::config.division - ((MusEGlobal::config.division/(n*2))))) { // first accent tick midiClick = AL::sigmap.bar2tick(bar, beat, MusEGlobal::config.division - (MusEGlobal::config.division/(n*2))); } } else { midiClick = AL::sigmap.bar2tick(bar, beat+1, 0); } } else if (state == PRECOUNT) { midiClick += ticksBeat; if (clickno) --clickno; else state = START_PLAY; } } } // // Play all midi events up to curFrame. // for(iMidiDevice id = MusEGlobal::midiDevices.begin(); id != MusEGlobal::midiDevices.end(); ++id) { // We are done with the 'frozen' recording fifos, remove the events. (*id)->afterProcess(); // ALSA devices handled by another thread. if((*id)->deviceType() != MidiDevice::ALSA_MIDI) (*id)->processMidi(); } MusEGlobal::midiBusy=false; } } // namespace MusECore