//========================================================= // 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) // // 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 #include #include #include #include #include "app.h" #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" #include "warn_bad_timing.h" namespace MusEGlobal { MusECore::MidiSeq* midiSeq; volatile bool midiBusy=false; } namespace MusECore { int MidiSeq::ticker = 0; void initMidiSequencer() { MusEGlobal::midiSeq = new MidiSeq("Midi"); } //--------------------------------------------------------- // readMsg //--------------------------------------------------------- static void readMsg(void* p, void*) { MidiSeq* at = (MidiSeq*)p; at->readMsg(); } //--------------------------------------------------------- // processMsg //--------------------------------------------------------- void MidiSeq::processMsg(const ThreadMsg* m) { MusECore::AudioMsg* msg = (MusECore::AudioMsg*)m; switch(msg->id) { // This does not appear to be used anymore. Was sent in Audio::process1, DELETETHIS 5 ?? // now Audio::processMidi is called directly. p4.0.15 Tim. //case MusECore::MS_PROCESS: // audio->processMidi(); // break; case MusECore::SEQM_SEEK: processSeek(); break; case MusECore::MS_STOP: processStop(); break; case MusECore::MS_SET_RTC: MusEGlobal::doSetuid(); setRtcTicks(); MusEGlobal::undoSetuid(); break; case MusECore::MS_UPDATE_POLL_FD: updatePollFd(); break; // Moved into Song::processMsg p4.0.34 ... case MusECore::SEQM_ADD_TRACK: MusEGlobal::song->insertTrack2(msg->track, msg->ival); updatePollFd(); break; case MusECore::SEQM_REMOVE_TRACK: MusEGlobal::song->cmdRemoveTrack(msg->track); updatePollFd(); break; case MusECore::SEQM_ADD_PART: MusEGlobal::song->cmdAddPart((Part*)msg->p1); break; case MusECore::SEQM_REMOVE_PART: MusEGlobal::song->cmdRemovePart((Part*)msg->p1); break; case MusECore::SEQM_SET_TRACK_OUT_CHAN: { MidiTrack* track = (MidiTrack*)(msg->p1); track->setOutChanAndUpdate(msg->a); } break; case MusECore::SEQM_SET_TRACK_OUT_PORT: { MidiTrack* track = (MidiTrack*)(msg->p1); track->setOutPortAndUpdate(msg->a); } break; case MusECore::SEQM_REMAP_PORT_DRUM_CTL_EVS: MusEGlobal::song->remapPortDrumCtrlEvents(msg->ival, msg->a, msg->b, msg->c); break; case MusECore::SEQM_CHANGE_ALL_PORT_DRUM_CTL_EVS: MusEGlobal::song->changeAllPortDrumCtrlEvents((bool)msg->a, (bool)msg->b); break; case MusECore::SEQM_SET_MIDI_DEVICE: ((MidiPort*)(msg->p1))->setMidiDevice((MidiDevice*)(msg->p2)); updatePollFd(); break; case MusECore::SEQM_IDLE: idle = msg->a; break; default: printf("MidiSeq::processMsg() unknown id %d\n", msg->id); break; } } #if 1 // DELETETHIS the #if and #endif? //--------------------------------------------------------- // processStop //--------------------------------------------------------- void MidiSeq::processStop() { // TODO Try to move this into Audio::stopRolling(). playStateExt = false; // not playing // clear Alsa midi device notes and stop stuck notes for(iMidiDevice id = MusEGlobal::midiDevices.begin(); id != MusEGlobal::midiDevices.end(); ++id) (*id)->handleStop(); } #endif //--------------------------------------------------------- // processSeek //--------------------------------------------------------- void MidiSeq::processSeek() { //--------------------------------------------------- // set all controller //--------------------------------------------------- for (iMidiDevice i = MusEGlobal::midiDevices.begin(); i != MusEGlobal::midiDevices.end(); ++i) (*i)->handleSeek(); } //--------------------------------------------------------- // MidiSeq //--------------------------------------------------------- MidiSeq::MidiSeq(const char* name) : Thread(name) { prio = 0; idle = false; midiClock = 0; mclock1 = 0.0; mclock2 = 0.0; songtick1 = songtick2 = 0; lastTempo = 0; storedtimediffs = 0; playStateExt = false; // not playing _clockAveragerStages = new int[16]; // Max stages is 16! setSyncRecFilterPreset(MusEGlobal::syncRecFilterPreset); for(int i = 0; i < _clockAveragerPoles; ++i) { _avgClkDiffCounter[i] = 0; _averagerFull[i] = false; } _tempoQuantizeAmount = 1.0; _lastRealTempo = 0.0; MusEGlobal::doSetuid(); timerFd=selectTimer(); MusEGlobal::undoSetuid(); } //--------------------------------------------------------- // ~MidiSeq //--------------------------------------------------------- MidiSeq::~MidiSeq() { delete timer; delete _clockAveragerStages; } //--------------------------------------------------------- // 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*) { 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(); } //--------------------------------------------------------- // 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 (!MusEGlobal::debugMode) exit(-1); } addPollFd(toThreadFdr, POLLIN, MusECore::readMsg, this, 0); //--------------------------------------------------- // midi ports //--------------------------------------------------- for (iMidiDevice imd = MusEGlobal::midiDevices.begin(); imd != MusEGlobal::midiDevices.end(); ++imd) { MidiDevice* dev = *imd; int port = dev->midiPort(); if (port == -1) continue; if ((dev->rwFlags() & 0x2) || (MusEGlobal::extSyncFlag.value() && (MusEGlobal::midiPorts[port].syncInfo().MCIn()))) addPollFd(dev->selectRfd(), POLLIN, MusECore::midiRead, this, dev); if (dev->bytesToWrite()) addPollFd(dev->selectWfd(), POLLOUT, MusECore::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, MusECore::alsaMidiRead, this, 0); } //--------------------------------------------------------- // threadStop // called from loop() //--------------------------------------------------------- void MidiSeq::threadStop() { timer->stopTimer(); } //--------------------------------------------------------- // setRtcTicks // returns actual tick frequency //--------------------------------------------------------- int MidiSeq::setRtcTicks() { int gotTicks = timer->setTimerFreq(MusEGlobal::config.rtcTicks); if (MusEGlobal::config.rtcTicks-24 > gotTicks) { printf("INFO: Could not get the wanted frequency %d, got %d, still it should suffice.\n", MusEGlobal::config.rtcTicks, gotTicks); } timer->startTimer(); return gotTicks; } //--------------------------------------------------------- // start // return true on error //--------------------------------------------------------- void MidiSeq::start(int priority) { prio = priority; MusEGlobal::doSetuid(); setRtcTicks(); MusEGlobal::undoSetuid(); Thread::start(priority); } //--------------------------------------------------------- // checkAndReportTimingResolution //--------------------------------------------------------- void MidiSeq::checkAndReportTimingResolution() { int freq = timer->getTimerFreq(); printf("Aquired timer frequency: %d\n", freq); if (freq < 500) { if(MusEGlobal::config.warnIfBadTiming) { MusEGui::WarnBadTimingDialog dlg; dlg.setLabelText(qApp->translate("@default", QT_TRANSLATE_NOOP("@default", "Timing source frequency is %1hz, which is below the recommended minimum: 500hz!\n" "This could lead to audible timing problems for MIDI.\n" "Please see the FAQ on http://muse-sequencer.org for remedies.\n" "Also please check console output for any further error messages.\n ")).arg(freq) ); dlg.exec(); bool warn = !dlg.dontAsk(); if(warn != MusEGlobal::config.warnIfBadTiming) { MusEGlobal::config.warnIfBadTiming = warn; //MusEGlobal::muse->changeConfig(true); // Save settings? No, wait till close. } } } } //--------------------------------------------------------- // setSyncRecFilterPreset // To be called in realtime thread only. //--------------------------------------------------------- void MidiSeq::setSyncRecFilterPreset(MidiSyncInfo::SyncRecFilterPresetType type) { _syncRecFilterPreset = type; alignAllTicks(); switch(_syncRecFilterPreset) { // NOTE: Max _clockAveragerPoles is 16 and maximum stages is 48 per pole ! case MidiSyncInfo::NONE: _clockAveragerPoles = 0; _preDetect = false; break; case MidiSyncInfo::TINY: _clockAveragerPoles = 2; _clockAveragerStages[0] = 4; _clockAveragerStages[1] = 4; _preDetect = false; break; case MidiSyncInfo::SMALL: _clockAveragerPoles = 3; _clockAveragerStages[0] = 12; _clockAveragerStages[1] = 8; _clockAveragerStages[2] = 4; _preDetect = false; break; case MidiSyncInfo::MEDIUM: _clockAveragerPoles = 3; _clockAveragerStages[0] = 28; _clockAveragerStages[1] = 12; _clockAveragerStages[2] = 8; _preDetect = false; break; case MidiSyncInfo::LARGE: _clockAveragerPoles = 4; _clockAveragerStages[0] = 48; _clockAveragerStages[1] = 48; _clockAveragerStages[2] = 48; _clockAveragerStages[3] = 48; _preDetect = false; break; case MidiSyncInfo::LARGE_WITH_PRE_DETECT: _clockAveragerPoles = 4; _clockAveragerStages[0] = 8; _clockAveragerStages[1] = 48; _clockAveragerStages[2] = 48; _clockAveragerStages[3] = 48; _preDetect = true; break; default: printf("MidiSeq::setSyncRecFilterPreset unknown preset type:%d\n", (int)type); } } //--------------------------------------------------------- // 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() { //--------------------------------------------------- // read elapsed rtc timer ticks //--------------------------------------------------- // This is required otherwise it freezes. unsigned long nn; if (timerFd != -1) { nn = timer->getTimerTicks(); nn >>= 8; } if (idle) return; if (MusEGlobal::midiBusy) { // we hit MusEGlobal::audio: MusEGlobal::midiSeq->msgProcess (actually this has been MusEGlobal::audio->processMidi for some time now - Tim) // miss this timer tick return; } unsigned curFrame = MusEGlobal::audio->curFrame(); if (!MusEGlobal::extSyncFlag.value()) { int curTick = lrint((double(curFrame)/double(MusEGlobal::sampleRate)) * double(MusEGlobal::tempomap.globalTempo()) * double(MusEGlobal::config.division) * 10000.0 / double(MusEGlobal::tempomap.tempo(MusEGlobal::song->cpos()))); if(midiClock > curTick) midiClock = curTick; int div = MusEGlobal::config.division/24; if(curTick >= midiClock + div) { int perr = (curTick - midiClock) / div; bool used = false; for(int port = 0; port < MIDI_PORTS; ++port) { MidiPort* mp = &MusEGlobal::midiPorts[port]; // No device? Clock out not turned on? DELETETHIS 3 if(!mp->device() || !mp->syncInfo().MCOut()) continue; used = true; mp->sendClock(); } if(MusEGlobal::debugMsg && used && perr > 1) printf("Dropped %d midi out clock(s). curTick:%d midiClock:%d div:%d\n", perr, curTick, midiClock, div); // Using equalization periods... midiClock += (perr * div); } } // play all events upto curFrame for (iMidiDevice id = MusEGlobal::midiDevices.begin(); id != MusEGlobal::midiDevices.end(); ++id) if((*id)->deviceType() == MidiDevice::ALSA_MIDI) (*id)->processMidi(); } //--------------------------------------------------------- // msgMsg //--------------------------------------------------------- void MidiSeq::msgMsg(int id) { MusECore::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) { MusECore::AudioMsg msg; msg.id = MusECore::SEQM_IDLE; msg.a = true; Thread::sendMsg(&msg); port->setMidiDevice(device); msg.id = MusECore::SEQM_IDLE; msg.a = false; Thread::sendMsg(&msg); } void MidiSeq::msgSeek() { msgMsg(MusECore::SEQM_SEEK); } void MidiSeq::msgStop() { msgMsg(MusECore::MS_STOP); } void MidiSeq::msgSetRtc() { msgMsg(MusECore::MS_SET_RTC); } void MidiSeq::msgUpdatePollFd() { msgMsg(MusECore::MS_UPDATE_POLL_FD); } } // namespace MusECore