//========================================================= // MusE // Linux Music Editor // $Id: midiseq.cpp,v 1.30.2.21 2009/12/20 05:00:35 terminator356 Exp $ // // high priority task for scheduling midi events // // (C) Copyright 2003 Werner Schweer (ws@seh.de) //========================================================= #include #include #include #include #include #include #include "globals.h" #include "midi.h" #include "midiseq.h" #include "midiport.h" #include "mididev.h" #include "midictrl.h" #include "audio.h" #include "driver/alsamidi.h" #include "driver/jackmidi.h" #include "sync.h" #include "synth.h" #include "song.h" #include "gconfig.h" MidiSeq* midiSeq; int MidiSeq::ticker = 0; volatile bool midiBusy=false; //--------------------------------------------------------- // readMsg //--------------------------------------------------------- static void readMsg(void* p, void*) { MidiSeq* at = (MidiSeq*)p; at->readMsg(); } //--------------------------------------------------------- // processMsg //--------------------------------------------------------- void MidiSeq::processMsg(const ThreadMsg* m) { AudioMsg* msg = (AudioMsg*)m; switch(msg->id) { // This does not appear to be used anymore. Was sent in Audio::process1, // now Audio::processMidi is called directly. p4.0.15 Tim. //case MS_PROCESS: // audio->processMidi(); // break; case SEQM_SEEK: processSeek(); break; case MS_STOP: processStop(); break; case MS_SET_RTC: doSetuid(); setRtcTicks(); undoSetuid(); break; case MS_UPDATE_POLL_FD: updatePollFd(); break; case SEQM_ADD_TRACK: song->insertTrack2(msg->track, msg->ival); updatePollFd(); break; case SEQM_REMOVE_TRACK: song->cmdRemoveTrack(msg->track); updatePollFd(); break; case SEQM_CHANGE_TRACK: song->changeTrack((Track*)(msg->p1), (Track*)(msg->p2)); updatePollFd(); break; case SEQM_ADD_PART: song->cmdAddPart((Part*)msg->p1); break; case SEQM_REMOVE_PART: song->cmdRemovePart((Part*)msg->p1); break; case SEQM_CHANGE_PART: //song->cmdChangePart((Part*)msg->p1, (Part*)msg->p2); song->cmdChangePart((Part*)msg->p1, (Part*)msg->p2, msg->a, msg->b); break; case SEQM_SET_TRACK_OUT_CHAN: { MidiTrack* track = (MidiTrack*)(msg->p1); track->setOutChanAndUpdate(msg->a); } break; case SEQM_SET_TRACK_OUT_PORT: { MidiTrack* track = (MidiTrack*)(msg->p1); track->setOutPortAndUpdate(msg->a); } break; case SEQM_REMAP_PORT_DRUM_CTL_EVS: song->remapPortDrumCtrlEvents(msg->ival, msg->a, msg->b, msg->c); break; case SEQM_CHANGE_ALL_PORT_DRUM_CTL_EVS: song->changeAllPortDrumCtrlEvents((bool)msg->a, (bool)msg->b); break; case SEQM_SET_MIDI_DEVICE: ((MidiPort*)(msg->p1))->setMidiDevice((MidiDevice*)(msg->p2)); updatePollFd(); break; case SEQM_IDLE: idle = msg->a; break; default: printf("MidiSeq::processMsg() unknown id %d\n", msg->id); break; } } //--------------------------------------------------------- // processStop //--------------------------------------------------------- void MidiSeq::processStop() { // p3.3.28 playStateExt = false; // not playing // // stop stuck notes // for (iMidiDevice id = midiDevices.begin(); id != midiDevices.end(); ++id) { MidiDevice* md = *id; if (md->midiPort() == -1) continue; MPEventList* pel = md->playEvents(); MPEventList* sel = md->stuckNotes(); pel->clear(); for (iMPEvent i = sel->begin(); i != sel->end(); ++i) { MidiPlayEvent ev = *i; ev.setTime(0); pel->add(ev); } sel->clear(); //md->setNextPlayEvent(pel->begin()); // Removed p4.0.15 } } //--------------------------------------------------------- // processSeek //--------------------------------------------------------- void MidiSeq::processSeek() { int pos = audio->tickPos(); if (pos == 0 && !song->record()) audio->initDevices(); //--------------------------------------------------- // set all controller //--------------------------------------------------- for (iMidiDevice i = midiDevices.begin(); i != midiDevices.end(); ++i) { MidiDevice* dev = *i; int port = dev->midiPort(); if (port == -1) continue; MidiPort* mp = &midiPorts[port]; MidiCtrlValListList* cll = mp->controller(); MPEventList* el = dev->playEvents(); if (audio->isPlaying()) { // stop all notes el->clear(); MPEventList* sel = dev->stuckNotes(); for (iMPEvent i = sel->begin(); i != sel->end(); ++i) { MidiPlayEvent ev = *i; ev.setTime(0); el->add(ev); } sel->clear(); } //else // Removed p4.0.15 Device now leaves beginning pointing at next event, // immediately after playing some notes. // NOTE: This removal needs testing. I'm not sure about this. // el->erase(el->begin(), dev->nextPlayEvent()); for (iMidiCtrlValList ivl = cll->begin(); ivl != cll->end(); ++ivl) { MidiCtrlValList* vl = ivl->second; //int val = vl->value(pos); //if (val != CTRL_VAL_UNKNOWN) { // int channel = ivl->first >> 24; // el->add(MidiPlayEvent(0, port, channel, ME_CONTROLLER, vl->num(), val)); // } iMidiCtrlVal imcv = vl->iValue(pos); if(imcv != vl->end()) { Part* p = imcv->second.part; unsigned t = (unsigned)imcv->first; // Do not add values that are outside of the part. if(p && t >= p->tick() && t < (p->tick() + p->lenTick()) ) el->add(MidiPlayEvent(0, port, ivl->first >> 24, ME_CONTROLLER, vl->num(), imcv->second.val)); } } //dev->setNextPlayEvent(el->begin()); // Removed p4.0.15 } } //--------------------------------------------------------- // MidiSeq //--------------------------------------------------------- //MidiSeq::MidiSeq(int priority, const char* name) // : Thread(priority, name) MidiSeq::MidiSeq(const char* name) : Thread(name) { // Changed by Tim. p3.3.17 //prio = priority; prio = 0; idle = false; midiClock = 0; mclock1 = 0.0; mclock2 = 0.0; songtick1 = songtick2 = 0; lastTempo = 0; storedtimediffs = 0; playStateExt = false; // not playing doSetuid(); timerFd=selectTimer(); undoSetuid(); } //--------------------------------------------------------- // ~MidiSeq //--------------------------------------------------------- MidiSeq::~MidiSeq() { delete timer; } //--------------------------------------------------------- // selectTimer() // select one of the supported timers to use during this run //--------------------------------------------------------- signed int MidiSeq::selectTimer() { int tmrFd; printf("Trying RTC timer...\n"); timer = new RtcTimer(); tmrFd = timer->initTimer(); if (tmrFd != -1) { // ok! printf("got timer = %d\n", tmrFd); return tmrFd; } delete timer; printf("Trying ALSA timer...\n"); timer = new AlsaTimer(); tmrFd = timer->initTimer(); if ( tmrFd!= -1) { // ok! printf("got timer = %d\n", tmrFd); return tmrFd; } delete timer; timer=NULL; QMessageBox::critical( 0, /*tr*/(QString("Failed to start timer!")), /*tr*/(QString("No functional timer was available.\n" "RTC timer not available, check if /dev/rtc is available and readable by current user\n" "Alsa timer not available, check if module snd_timer is available and /dev/snd/timer is available"))); printf("No functional timer available!!!\n"); exit(1); } //--------------------------------------------------------- // threadStart // called from loop() //--------------------------------------------------------- void MidiSeq::threadStart(void*) { // Removed by Tim. p3.3.17 /* struct sched_param rt_param; memset(&rt_param, 0, sizeof(rt_param)); int prio_min = sched_get_priority_min(SCHED_FIFO); int prio_max = sched_get_priority_max(SCHED_FIFO); if (prio < prio_min) prio = prio_min; else if (prio > prio_max) prio = prio_max; rt_param.sched_priority = prio; int rv = pthread_setschedparam(pthread_self(), SCHED_FIFO, &rt_param); if (rv != 0) perror("set realtime scheduler"); */ int policy; if ((policy = sched_getscheduler (0)) < 0) { printf("Cannot get current client scheduler: %s\n", strerror(errno)); } if (policy != SCHED_FIFO) printf("midi thread %d _NOT_ running SCHED_FIFO\n", getpid()); updatePollFd(); } //--------------------------------------------------------- // alsaMidiRead //--------------------------------------------------------- static void alsaMidiRead(void*, void*) { // calls itself midiDevice->recordEvent(MidiRecordEvent): alsaProcessMidiInput(); } //--------------------------------------------------------- // midiRead //--------------------------------------------------------- static void midiRead(void*, void* d) { MidiDevice* dev = (MidiDevice*) d; dev->processInput(); } //--------------------------------------------------------- // synthIRead //--------------------------------------------------------- #if 0 static void synthIRead(void*, void* d) { SynthI* syn = (SynthI*) d; syn->processInput(); } #endif //--------------------------------------------------------- // midiWrite //--------------------------------------------------------- static void midiWrite(void*, void* d) { MidiDevice* dev = (MidiDevice*) d; dev->flush(); } //--------------------------------------------------------- // updatePollFd //--------------------------------------------------------- void MidiSeq::updatePollFd() { if (!isRunning()) return; clearPollFd(); addPollFd(timerFd, POLLIN, midiTick, this, 0); if (timerFd == -1) { fprintf(stderr, "updatePollFd: no timer fd\n"); if (!debugMode) exit(-1); } addPollFd(toThreadFdr, POLLIN, ::readMsg, this, 0); //--------------------------------------------------- // midi ports //--------------------------------------------------- for (iMidiDevice imd = midiDevices.begin(); imd != midiDevices.end(); ++imd) { MidiDevice* dev = *imd; int port = dev->midiPort(); const QString name = dev->name(); if (port == -1) continue; if ((dev->rwFlags() & 0x2) || (extSyncFlag.value() //&& (rxSyncPort == port || rxSyncPort == -1))) { //&& (dev->syncInfo().MCIn()))) { && (midiPorts[port].syncInfo().MCIn()))) { if(dev->selectRfd() < 0){ //fprintf(stderr, "WARNING: read-file-descriptor for {%s} is negative\n", name.toLatin1()); } addPollFd(dev->selectRfd(), POLLIN, ::midiRead, this, dev); } if (dev->bytesToWrite()){ if(dev->selectWfd() < 0){ //fprintf(stderr, "WARNING: write-file-descriptor for {%s} is negative\n", name.toLatin1()); } addPollFd(dev->selectWfd(), POLLOUT, ::midiWrite, this, dev); } } // special handling for alsa midi: // (one fd for all devices) // this allows for processing of some alsa events // even if no alsa driver is active (assigned to a port) addPollFd(alsaSelectRfd(), POLLIN, ::alsaMidiRead, this, 0); } //--------------------------------------------------------- // threadStop // called from loop() //--------------------------------------------------------- void MidiSeq::threadStop() { timer->stopTimer(); //timer.stopTimer(); } //--------------------------------------------------------- // setRtcTicks // return true on success //--------------------------------------------------------- bool MidiSeq::setRtcTicks() { //timer.setTimerFreq(config.rtcTicks); //timer.startTimer(); timer->setTimerFreq(config.rtcTicks); timer->startTimer(); realRtcTicks = config.rtcTicks; return true; } //--------------------------------------------------------- // start // return true on error //--------------------------------------------------------- //bool MidiSeq::start() void MidiSeq::start(int priority) { // Changed by Tim. p3.3.17 prio = priority; //timerFd = -1; doSetuid(); //timerFd = selectTimer(); //timerFd = timer.initTimer(); //printf("timerFd=%d\n",timerFd); setRtcTicks(); undoSetuid(); //Thread::start(); Thread::start(priority); //return false; } //--------------------------------------------------------- // processMidiClock //--------------------------------------------------------- void MidiSeq::processMidiClock() { // if (genMCSync) { // midiPorts[txSyncPort].sendClock(); // } /* if (state == START_PLAY) { // start play on sync state = PLAY; _midiTick = playTickPos; midiClock = playTickPos; int bar, beat, tick; sigmap.tickValues(_midiTick, &bar, &beat, &tick); midiClick = sigmap.bar2tick(bar, beat+1, 0); double cpos = tempomap.tick2time(playTickPos); samplePosStart = samplePos - lrint(cpos * sampleRate); rtcTickStart = rtcTick - lrint(cpos * realRtcTicks); endSlice = playTickPos; recTick = playTickPos; lastTickPos = playTickPos; tempoSN = tempomap.tempoSN(); startRecordPos.setPosTick(playTickPos); } */ // midiClock += config.division/24; } //--------------------------------------------------------- // midiTick //--------------------------------------------------------- void MidiSeq::midiTick(void* p, void*) { MidiSeq* at = (MidiSeq*)p; at->processTimerTick(); if (TIMER_DEBUG) { if(MidiSeq::ticker++ > 100) { printf("tick!\n"); MidiSeq::ticker=0; } } } //--------------------------------------------------------- // processTimerTick //--------------------------------------------------------- void MidiSeq::processTimerTick() { // Disabled by Tim. p3.3.22 // extern int watchMidi; // ++watchMidi; // make a simple watchdog happy //--------------------------------------------------- // read elapsed rtc timer ticks //--------------------------------------------------- // This is required otherwise it freezes. unsigned long nn; if (timerFd != -1) { nn = timer->getTimerTicks(); //nn = timer.getTimerTicks(); nn >>= 8; } if (idle) { // printf("IDLE\n"); return; } if (midiBusy) { // we hit audio: midiSeq->msgProcess (actually this has been audio->processMidi for some time now - Tim) // miss this timer tick return; } unsigned curFrame = audio->curFrame(); // Keep the sync detectors running... // No, done in Song::beat(), (at the much slower heartbeat rate). // //for(int port = 0; port < MIDI_PORTS; ++port) //{ // Must keep them running even if there's no device... //if(midiPorts[port].device()) // midiPorts[port].syncInfo().setCurFrame(curFrame); //} //for(iMidiDevice imd = midiDevices.begin(); imd != midiDevices.end(); ++imd) // (*imd)->syncInfo().setCurFrame(curFrame); if (!extSyncFlag.value()) { //int curTick = tempomap.frame2tick(curFrame); // Copied from Tempomap. //int curTick = lrint((double(curFrame)/double(sampleRate)) * tempomap.globalTempo() * config.division * 10000.0 / double(tempomap.tempo(song->cpos()))); //int curTick = lrint((double(curFrame)/double(sampleRate)) * tempomap.globalTempo() * 240000.0 / double(tempomap.tempo(song->cpos()))); int curTick = lrint((double(curFrame)/double(sampleRate)) * double(tempomap.globalTempo()) * double(config.division) * 10000.0 / double(tempomap.tempo(song->cpos()))); //int curTick = int((double(curFrame)/double(sampleRate)) * double(tempomap.globalTempo()) * double(config.division * 10000.0) / double(tempomap.tempo(song->cpos()))); /* if ( midiClock > curTick + 100) // reinitialize { midiClock = curTick; } else if( curTick > midiClock + 100) // reinitialize { midiClock = curTick; }*/ if(midiClock > curTick) midiClock = curTick; int div = config.division/24; if(curTick >= midiClock + div) { //if(curTick >= midiClock) { //processMidiClock(); int perr = (curTick - midiClock) / div; //int perr = curTick - midiClock; bool used = false; //if(genMCSync) //{ //midiPorts[txSyncPort].sendClock(); for(int port = 0; port < MIDI_PORTS; ++port) { MidiPort* mp = &midiPorts[port]; // No device? Clock out not turned on? //MidiDevice* dev = mp->device(); //if(!dev || !mp->syncInfo().MCOut()) if(!mp->device() || !mp->syncInfo().MCOut()) continue; // Shall we check open flags? //if(!(dev->rwFlags() & 0x1) || !(dev->openFlags() & 1)) //if(!(dev->openFlags() & 1)) // continue; used = true; mp->sendClock(); } /* for(iMidiDevice imd = midiDevices.begin(); imd != midiDevices.end(); ++imd) { MidiDevice* dev = *imd; if(!dev->syncInfo().MCOut()) continue; // Shall we check open flags? //if(!(dev->rwFlags() & 0x1) || !(dev->openFlags() & 1)) //if(!(dev->openFlags() & 1)) // continue; int port = dev->midiPort(); // Without this -1 check, interesting sync things can be done by the user without ever // assigning any devices to ports ! //if(port < 0 || port > MIDI_PORTS) if(port < -1 || port > MIDI_PORTS) continue; if(port == -1) // Send straight to the device... Copied from MidiPort. { MidiPlayEvent event(0, 0, 0, ME_CLOCK, 0, 0); dev->putEvent(event); } else // Go through the port... midiPorts[port].sendClock(); } */ if(debugMsg && used && perr > 1) printf("Dropped %d midi out clock(s). curTick:%d midiClock:%d div:%d\n", perr, curTick, midiClock, div); //} // Increment as if we had caught the timer exactly on the mark, even if the timer // has passed beyond the mark, or even beyond 2 * div. // If we missed some chances to send clock, resume the count where it would have been, // had we not missed chances. // We can't do anything about missed chances except send right away, and make up // for gained time by losing time in the next count... // In other words, use equalization periods to counter gained/lost time, so that // ultimately, over time, the receiver remains in phase, despite any short dropouts / phase glitches. // (midiClock only increments by div units). // // Tested: With midi thread set to high priority, very few clock dropouts ocurred (P4 1.6Ghz). // But target device tick drifts out of phase with muse tick slowly over time, say 20 bars or so. // May need more tweaking, possibly use round with/instead of lrint (above), and/or // do not use equalization periods - set midiClock to fractions of div. // Tested: With RTC resolution at 1024, stability was actually better than with 8192! // It stayed in sync more than 64 bars... // // // Using equalization periods... midiClock += (perr * div); //midiClock += perr; // // No equalization periods... TODO: //midiClock += (perr * div); } } // if (genMTCSync) { // printf("Midi Time Code Sync generation not impl.\n"); // } // p3.3.25 int tickpos = audio->tickPos(); bool extsync = extSyncFlag.value(); // // play all events upto curFrame // for (iMidiDevice id = midiDevices.begin(); id != midiDevices.end(); ++id) { MidiDevice* md = *id; // Is it a Jack midi device? They are iterated in Audio::processMidi. p3.3.36 //MidiJackDevice* mjd = dynamic_cast(md); //if(mjd) if(md->deviceType() == MidiDevice::JACK_MIDI) continue; if(md->isSynti()) // syntis are handled by audio thread continue; int port = md->midiPort(); MidiPort* mp = port != -1 ? &midiPorts[port] : 0; MPEventList* el = md->playEvents(); if (el->empty()) continue; ///iMPEvent i = md->nextPlayEvent(); iMPEvent i = el->begin(); // p4.0.15 Tim. for (; i != el->end(); ++i) { // 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 (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); // p4.0.15 We are done with these events. Let us erase them here instead of Audio::processMidi. // That way we can simply set the next play event to the beginning. // This also allows other events to be inserted without the problems caused by the next play event // being at the 'end' iterator and not being *easily* set to some new place beginning of the newer insertions. // The way that MPEventList sorts made it difficult to predict where the iterator of the first newly inserted items was. // The erasure in Audio::processMidi was missing some events because of that. el->erase(el->begin(), i); //md->setNextPlayEvent(el->begin()); // Removed p4.0.15 } } //--------------------------------------------------------- // msgMsg //--------------------------------------------------------- void MidiSeq::msgMsg(int id) { AudioMsg msg; msg.id = id; Thread::sendMsg(&msg); } //--------------------------------------------------------- // msgSetMidiDevice // to avoid timeouts in the RT-thread, setMidiDevice // is done in GUI context after setting the midi thread // into idle mode //--------------------------------------------------------- void MidiSeq::msgSetMidiDevice(MidiPort* port, MidiDevice* device) { AudioMsg msg; msg.id = SEQM_IDLE; msg.a = true; Thread::sendMsg(&msg); port->setMidiDevice(device); msg.id = SEQM_IDLE; msg.a = false; Thread::sendMsg(&msg); } // This does not appear to be used anymore. Was called in Audio::process1, now Audio::processMidi is called directly. p4.0.15 Tim. //void MidiSeq::msgProcess() { msgMsg(MS_PROCESS); } void MidiSeq::msgSeek() { msgMsg(SEQM_SEEK); } void MidiSeq::msgStop() { msgMsg(MS_STOP); } void MidiSeq::msgSetRtc() { msgMsg(MS_SET_RTC); } void MidiSeq::msgUpdatePollFd() { msgMsg(MS_UPDATE_POLL_FD); }