//============================================================================= // MusE // Linux Music Editor // $Id:$ // // Copyright (C) 2002-2006 by Werner Schweer and others // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2. // // 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., 675 Mass Ave, Cambridge, MA 02139, USA. //============================================================================= #include "sync.h" #include "song.h" #include "widgets/utils.h" #include "globals.h" #include "audio.h" #include "audiodev.h" #include "gconfig.h" #include "al/tempo.h" #include "al/al.h" int rxSyncPort = -1; // receive from all ports int txSyncPort = 1; bool debugSync = false; MTC mtcOffset; bool extSyncFlag = false; bool genMTCSync = false; // output MTC Sync bool genMCSync = false; // output MidiClock Sync bool genMMC = false; // output Midi Machine Control bool acceptMTC = false; bool acceptMC = true; bool acceptMMC = true; static MTC mtcCurTime; // static int mtcState; // 0-7 next expected quarter message // static bool mtcValid; // static int mtcLost; // static bool mtcSync; // receive complete mtc frame? //static bool mcStart = false; //static int mcStartTick; enum { MMC_STOP = 1, MMC_PLAY = 2, MMC_DEFERRED_PLAY = 3, MMC_FAST_FORWARD = 4, MMC_REWIND = 5, MMC_RECORD_STROBE = 6, // Punch In MMC_RECORD_EXIT = 7, // Punch Out MMC_PAUSE = 9, MMC_RESET = 13, MMC_GOTO = 0x44 }; #if 0 //--------------------------------------------------------- // mmcInput // Midi Machine Control Input received //--------------------------------------------------------- void MidiSeq::mmcInput(int id, int cmd, const Pos& pos) { #if 0 int rxDeviceId = 127; if (!extSyncFlag || !acceptMMC || (id != 127 && id != rxDeviceId)) return; if (debugMsg) printf("mmcInput: id %d cmd %02x %02x\n", id, cmd, cmd); switch (cmd) { case MMC_STOP: if (debugSync) printf(" MMC: STOP\n"); if (audio->isPlaying()) audioDriver->stopTransport(); else // reset REC audio->sendMsgToGui(MSG_STOP); break; case MMC_PLAY: case MMC_DEFERRED_PLAY: audioDriver->startTransport(); break; case MMC_FAST_FORWARD: printf("MMC: FF not implemented\n"); break; case MMC_REWIND: printf("MMC: REWIND not implemented\n"); break; case MMC_RECORD_STROBE: printf("MMC: REC STROBE not implemented\n"); break; case MMC_RECORD_EXIT: printf("MMC: REC EXIT not implemented\n"); break; case MMC_PAUSE: audio->sendMsgToGui(MSG_RECORD); break; case MMC_RESET: printf("MMC: RESET not implemented\n"); break; case MMC_GOTO: audioDriver->seekTransport(pos.frame()); break; default: printf("MMC id %x cmd %x, unknown\n", id, cmd); break; } #endif } //--------------------------------------------------------- // mtcInputQuarter // process Quarter Frame Message //--------------------------------------------------------- void MidiSeq::mtcInputQuarter(int, unsigned char c) { #if 0 static int hour, min, sec, frame; if (!extSyncFlag) return; int valL = c & 0xf; int valH = valL << 4; int _state = (c & 0x70) >> 4; if (mtcState != _state) mtcLost += _state - mtcState; mtcState = _state + 1; switch(_state) { case 7: hour = (hour & 0x0f) | valH; break; case 6: hour = (hour & 0xf0) | valL; break; case 5: min = (min & 0x0f) | valH; break; case 4: min = (min & 0xf0) | valL; break; case 3: sec = (sec & 0x0f) | valH; break; case 2: sec = (sec & 0xf0) | valL; break; case 1: frame = (frame & 0x0f) | valH; break; case 0: frame = (frame & 0xf0) | valL; break; } frame &= 0x1f; // 0-29 sec &= 0x3f; // 0-59 min &= 0x3f; // 0-59 hour &= 0x1f; if (mtcState == 8) { mtcValid = (mtcLost == 0); mtcState = 0; mtcLost = 0; if (mtcValid) { mtcCurTime.set(hour, min, sec, frame); mtcSyncMsg(mtcCurTime, !mtcSync); mtcSync = true; } } else if (mtcValid && (mtcLost == 0)) { mtcCurTime.incQuarter(); mtcSyncMsg(mtcCurTime, false); } #endif } //--------------------------------------------------------- // mtcInputFull // process Frame Message //--------------------------------------------------------- void MidiSeq::mtcInputFull(const unsigned char* p, int n) { #if 0 if (debugSync) printf("mtcInputFull\n"); if (!extSyncFlag) return; if (p[3] != 1) { if (p[3] != 2) { // silently ignore user bits printf("unknown mtc msg subtype 0x%02x\n", p[3]); dump(p, n); } return; } int hour = p[4]; int min = p[5]; int sec = p[6]; int frame = p[7]; frame &= 0x1f; // 0-29 sec &= 0x3f; // 0-59 min &= 0x3f; // 0-59 // int type = (hour >> 5) & 3; hour &= 0x1f; mtcCurTime.set(hour, min, sec, frame); mtcState = 0; mtcValid = true; mtcLost = 0; #endif } //--------------------------------------------------------- // nonRealtimeSystemSysex //--------------------------------------------------------- void MidiSeq::nonRealtimeSystemSysex(const unsigned char* p, int n) { #if 0 // int chan = p[2]; switch(p[3]) { case 4: printf("NRT Setup\n"); break; default: printf("unknown NRT Msg 0x%02x\n", p[3]); dump(p, n); break; } #endif } //--------------------------------------------------------- // setSongPosition // MidiBeat is a 14 Bit value. Each MidiBeat spans // 6 MIDI Clocks. Inother words, each MIDI Beat is a // 16th note (since there are 24 MIDI Clocks in a // quarter note). //--------------------------------------------------------- void MidiSeq::setSongPosition(int port, int midiBeat) { #if 0 if (midiInputTrace) printf("set song position port:%d %d\n", port, midiBeat); if (!extSyncFlag) return; Pos pos((config.division * midiBeat) / 4, AL::TICKS); audioDriver->seekTransport(pos.frame()); if (debugSync) printf("setSongPosition %d\n", pos.tick()); #endif } //--------------------------------------------------------- // realtimeSystemInput // real time message received //--------------------------------------------------------- void MidiSeq::realtimeSystemInput(int port, int c) { #if 0 if (midiInputTrace) printf("realtimeSystemInput port:%d 0x%x\n", port+1, c); if (midiInputTrace && (rxSyncPort != port) && rxSyncPort != -1) { if (debugSync) printf("rxSyncPort configured as %d; received sync from port %d\n", rxSyncPort, port); return; } if (!extSyncFlag) return; switch(c) { case 0xf8: // midi clock (24 ticks / quarter note) { double mclock0 = curTime(); // Difference in time last 2 rounds: double tdiff0 = mclock0 - mclock1; double tdiff1 = mclock1 - mclock2; double averagetimediff = 0.0; if (mclock1 != 0.0) { if (storedtimediffs < 24) { timediff[storedtimediffs] = mclock0 - mclock1; storedtimediffs++; } else { for (int i=0; i<23; i++) { timediff[i] = timediff[i+1]; } timediff[23] = mclock0 - mclock1; } // Calculate average timediff: for (int i=0; i < storedtimediffs; i++) { averagetimediff += timediff[i]/storedtimediffs; } } processMidiClock(); // Compare w audio if playing: if (audio->isPlaying() /*state == PLAY*/) { //BEGIN standard setup: recTick += config.division / 24; // The one we're syncing to int tempo = AL::tempomap.tempo(0); unsigned curFrame = audio->pos().frame(); double songtick = (double(curFrame)/double(AL::sampleRate)) * config.division * 1000000.0 / double(tempo); double scale = tdiff0/averagetimediff; double tickdiff = songtick - ((double) recTick - 24 + scale*24.0); //END standard setup if (debugSync) { // // Create debug values for printing out which beat we're at, etc etc... yaddayadda... // int m, b, t; audio->pos().mbt(&m, &b, &t); int song_beat = b + m*4; // if the time-signature is different than 4/4, this will be wrong. int sync_beat = recTick/config.division; printf("pT=%.3f rT=%d diff=%.3f songB=%d syncB=%d scale=%.3f", songtick, recTick, tickdiff, song_beat, sync_beat, scale); } //if ((mclock2 !=0.0) && (tdiff1 > 0.0) && tickdiff != 0.0 && lastTempo != 0) { if ((mclock2 !=0.0) && (tdiff1 > 0.0) && fabs(tickdiff) > 2.0 && lastTempo != 0) { // Interpolate: double tickdiff1 = songtick1 - recTick1; double tickdiff2 = songtick2 - recTick2; //double newtickdiff = tickdiff/3.0 + tickdiff1/5.0 + tickdiff2/7.0; //2 min 15 sec, 120BPM, -p 512 jackd //double newtickdiff = tickdiff/4.0 + tickdiff1/4.0 + tickdiff2/4.0; // Not long... :-P //double newtickdiff = tickdiff/5.0 + tickdiff1/8.0 + tickdiff2/12.0; //3,5 mins on 120BPM, -p 512 jackd //double newtickdiff = tickdiff/7.0 + tickdiff1/8.0 + tickdiff2/9.0; //2 min 15 sec, 120BPM, -p 512 jackd //double newtickdiff = tickdiff/5.0 + tickdiff1/8.0 + tickdiff2/16.0; //3,5 mins on 120BPM, -p 512 jackd double newtickdiff = tickdiff/5.0 + tickdiff1/16.0 + tickdiff2/24.0; //5 mins 30 secs on 116BPM, -p 512 jackd //double newtickdiff = tickdiff/5.0 + tickdiff1/23.0 + tickdiff2/31.0; //5 mins on 116BPM, -p 512 jackd //double newtickdiff = tickdiff + tickdiff1/8.0 + tickdiff2/16.0; // Not long... if (newtickdiff != 0.0) { int newTempo = AL::tempomap.tempo(0); //newTempo += int(24.0 * newtickdiff * scale); newTempo += int(24.0 * newtickdiff); if (debugSync) printf(" tdiff=%f ntd=%f lt=%d tmpo=%.3f", tdiff0, newtickdiff, lastTempo, (float)((1000000.0 * 60.0)/newTempo)); AL::tempomap.setTempo(0,newTempo); } if (debugSync) printf("\n"); } else if (debugSync) printf("\n"); //BEGIN post calc lastTempo = tempo; recTick2 = recTick1; recTick1 = recTick; mclock2 = mclock1; mclock1 = mclock0; songtick2 = songtick1; songtick1 = songtick; //END post calc break; } // END state play // // Pre-sync (when audio is not running) // Calculate tempo depending on time per pulse // if (mclock1 == 0.0) { //TODO3 midiPorts[port].device()->discardInput(); if (debugSync) printf("Discarding input from port %d\n", port); } if ((mclock2 != 0.0) && (tdiff0 > 0.0)) { int tempo0 = int(24000000.0 * tdiff0 + .5); int tempo1 = int(24000000.0 * tdiff1 + .5); int tempo = AL::tempomap.tempo(0); int diff0 = tempo0 - tempo; int diff1 = tempo1 - tempo0; if (diff0) { int newTempo = tempo + diff0/8 + diff1/16; if (debugSync) printf("setting new tempo %d = %f\n", newTempo, (float)((1000000.0 * 60.0)/newTempo)); AL::tempomap.setTempo(0, newTempo); } } mclock2 = mclock1; mclock1 = mclock0; } break; case 0xf9: // midi tick (every 10 msec) if (mcStart) { song->setPos(0, mcStartTick); mcStart = false; return; } break; case 0xfa: // start if (debugSync) printf(" start\n"); if (!audio->isPlaying() /*state == IDLE*/) { //seek(0); audioDriver->seekTransport(0); unsigned curFrame = audioDriver->framePos(); recTick = recTick1 = recTick2 = 0; mclock1 = 0.0; mclock2 = 0.0; songtick1 = songtick2 = 0; if (debugSync) printf(" curFrame: %d curTick: %d tempo: %d\n", curFrame, recTick, AL::tempomap.tempo(0)); //startPlay(); storedtimediffs = 0; for (int i=0; i<24; i++) timediff[i] = 0.0; audio->msgPlay(true); } break; case 0xfb: // continue if (debugSync) printf(" continue\n"); if (!audio->isPlaying() /*state == IDLE */) { unsigned curFrame = audioDriver->framePos(); recTick = AL::tempomap.frame2tick(curFrame); // don't think this will work... (ml) audio->msgPlay(true); } break; case 0xfc: // stop if (debugSync) printf(" stop\n"); if (audio->isPlaying() /*state == PLAY*/) audio->msgPlay(false); break; case 0xfd: // unknown case 0xfe: // active sensing case 0xff: // system reset break; } #endif } //--------------------------------------------------------- // mtcSyncMsg // process received mtc Sync // seekFlag - first complete mtc frame received after // start //--------------------------------------------------------- void MidiSeq::mtcSyncMsg(const MTC& /*mtc*/, bool /*seekFlag*/) { #if 0 double time = mtc.time(); if (debugSync) printf("mtcSyncMsg: time %f\n", time); if (seekFlag && state == START_PLAY) { // int tick = tempomap.time2tick(time); state = PLAY; sendMsgToGui(MSG_PLAY); return; } // double curT = curTime(); if (tempoSN != tempomap.tempoSN()) { double cpos = tempomap.tick2time(_midiTick, 0); samplePosStart = samplePos - lrint(cpos * sampleRate); rtcTickStart = rtcTick - lrint(cpos * realRtcTicks); tempoSN = tempomap.tempoSN(); } // // diff is the time in sec MusE is out of sync // double diff = time - (double(samplePosStart)/double(sampleRate)); if (debugSync) printf(" state %d diff %f\n", mtcState, diff); #endif } #endif