diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 4963cb685..2ff58bde3 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2037,7 +2037,7 @@ String DivEngine::getPlaybackDebugInfo() { "midiClockCycles: %d\n" "midiClockDrift: %f\n" "midiTimeCycles: %d\n" - "midiTimeDrift: %d\n" + "midiTimeDrift: %f\n" "changeOrd: %d\n" "changePos: %d\n" "totalSeconds: %d\n" @@ -2190,6 +2190,8 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) { totalTicksR=0; curMidiClock=0; curMidiTime=0; + curMidiTimeCode=0; + curMidiTimePiece=0; totalLoops=0; lastLoopPos=-1; } @@ -2403,9 +2405,74 @@ void DivEngine::play() { for (int i=0; imidiOut!=NULL) { - int pos=curMidiClock; - output->midiOut->send(TAMidiMessage(TA_MIDI_POSITION,(pos>>7)&0x7f,pos&0x7f)); + if (midiOutClock) { + output->midiOut->send(TAMidiMessage(TA_MIDI_POSITION,(curMidiClock>>7)&0x7f,curMidiClock&0x7f)); + } + if (midiOutTime) { + TAMidiMessage msg; + msg.type=TA_MIDI_SYSEX; + msg.sysExData.reset(new unsigned char[10],std::default_delete()); + msg.sysExLen=10; + unsigned char* msgData=msg.sysExData.get(); + int actualTime=curMidiTime; + int timeRate=midiOutTimeRate; + int drop=0; + if (timeRate<1 || timeRate>4) { + if (curSubSong->hz>=47.98 && curSubSong->hz<=48.02) { + timeRate=1; + } else if (curSubSong->hz>=49.98 && curSubSong->hz<=50.02) { + timeRate=2; + } else if (curSubSong->hz>=59.9 && curSubSong->hz<=60.11) { + timeRate=4; + } else { + timeRate=4; + } + } + + switch (timeRate) { + case 1: // 24 + msgData[5]=(actualTime/(60*60*24))%24; + msgData[6]=(actualTime/(60*24))%60; + msgData[7]=(actualTime/24)%60; + msgData[8]=actualTime%24; + break; + case 2: // 25 + msgData[5]=(actualTime/(60*60*25))%24; + msgData[6]=(actualTime/(60*25))%60; + msgData[7]=(actualTime/25)%60; + msgData[8]=actualTime%25; + break; + case 3: // 29.97 (NTSC drop) + // drop + drop=((actualTime/(30*60))-(actualTime/(30*600)))*2; + actualTime+=drop; + + msgData[5]=(actualTime/(60*60*30))%24; + msgData[6]=(actualTime/(60*30))%60; + msgData[7]=(actualTime/30)%60; + msgData[8]=actualTime%30; + break; + case 4: // 30 (NTSC non-drop) + default: + msgData[5]=(actualTime/(60*60*30))%24; + msgData[6]=(actualTime/(60*30))%60; + msgData[7]=(actualTime/30)%60; + msgData[8]=actualTime%30; + break; + } + + msgData[5]|=(timeRate-1)<<5; + + msgData[0]=0xf0; + msgData[1]=0x7f; + msgData[2]=0x7f; + msgData[3]=0x01; + msgData[4]=0x01; + msgData[9]=0xf7; + output->midiOut->send(msg); + } output->midiOut->send(TAMidiMessage(TA_MIDI_MACHINE_PLAY,0,0)); } BUSY_END; @@ -4410,6 +4477,8 @@ void DivEngine::quitDispatch() { totalTicksR=0; curMidiClock=0; curMidiTime=0; + curMidiTimeCode=0; + curMidiTimePiece=0; totalCmds=0; lastCmds=0; cmdsPerSecond=0; @@ -4436,6 +4505,8 @@ bool DivEngine::initAudioBackend() { lowLatency=getConfInt("lowLatency",0); metroVol=(float)(getConfInt("metroVol",100))/100.0f; midiOutClock=getConfInt("midiOutClock",0); + midiOutTime=getConfInt("midiOutTime",0); + midiOutTimeRate=getConfInt("midiOutTimeRate",0); midiOutProgramChange = getConfInt("midiOutProgramChange",0); midiOutMode=getConfInt("midiOutMode",DIV_MIDI_MODE_NOTE); if (metroVol<0.0f) metroVol=0.0f; diff --git a/src/engine/engine.h b/src/engine/engine.h index 1cc853836..2ef8dc4d6 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -369,8 +369,10 @@ class DivEngine { bool systemsRegistered; bool hasLoadedSomething; bool midiOutClock; + bool midiOutTime; bool midiOutProgramChange; int midiOutMode; + int midiOutTimeRate; int softLockCount; int subticks, ticks, curRow, curOrder, prevRow, prevOrder, remainingLoops, totalLoops, lastLoopPos, exportLoopCount, nextSpeed, elapsedBars, elapsedBeats, curSpeed; size_t curSubSongIndex; @@ -384,6 +386,7 @@ class DivEngine { double midiTimeDrift; int stepPlay; int changeOrd, changePos, totalSeconds, totalTicks, totalTicksR, curMidiClock, curMidiTime, totalCmds, lastCmds, cmdsPerSecond, globalPitch; + int curMidiTimePiece, curMidiTimeCode; unsigned char extValue, pendingMetroTick; DivGroovePattern speeds; short tempoAccum; @@ -1128,8 +1131,10 @@ class DivEngine { systemsRegistered(false), hasLoadedSomething(false), midiOutClock(false), + midiOutTime(false), midiOutProgramChange(false), midiOutMode(DIV_MIDI_MODE_NOTE), + midiOutTimeRate(0), softLockCount(0), subticks(0), ticks(0), @@ -1166,6 +1171,8 @@ class DivEngine { lastCmds(0), cmdsPerSecond(0), globalPitch(0), + curMidiTimePiece(0), + curMidiTimeCode(0), extValue(0), pendingMetroTick(0), tempoAccum(0), diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 2930d3060..caaf92364 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1852,7 +1852,6 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi if (speedSum<1.0) speedSum=1.0; if (vD<1) vD=1; double bpm=((24.0*divider)/(timeBase*hl*speedSum))*(double)curSubSong->virtualTempoN/vD; - logV("bpm: %f %f",bpm,divider); midiClockCycles=got.rate*pow(2,MASTER_CLOCK_PREC)/(bpm); midiClockDrift+=fmod(got.rate*pow(2,MASTER_CLOCK_PREC),(double)(bpm)); @@ -1863,7 +1862,115 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi } } - // 4. tick the clock and fill buffers as needed + // 4. run MIDI timecode + for (int i=0; i4) { + if (curSubSong->hz>=47.98 && curSubSong->hz<=48.02) { + timeRate=1; + } else if (curSubSong->hz>=49.98 && curSubSong->hz<=50.02) { + timeRate=2; + } else if (curSubSong->hz>=59.9 && curSubSong->hz<=60.11) { + timeRate=4; + } else { + timeRate=4; + } + } + + int hour=0; + int minute=0; + int second=0; + int frame=0; + int drop=0; + int actualTime=curMidiTimeCode; + + switch (timeRate) { + case 1: // 24 + frameRate=96.0; + hour=(actualTime/(60*60*24))%24; + minute=(actualTime/(60*24))%60; + second=(actualTime/24)%60; + frame=actualTime%24; + break; + case 2: // 25 + frameRate=100.0; + hour=(actualTime/(60*60*25))%24; + minute=(actualTime/(60*25))%60; + second=(actualTime/25)%60; + frame=actualTime%25; + break; + case 3: // 29.97 (NTSC drop) + frameRate=120.0*(1000.0/1001.0); + + // drop + drop=((actualTime/(30*60))-(actualTime/(30*600)))*2; + actualTime+=drop; + + hour=(actualTime/(60*60*30))%24; + minute=(actualTime/(60*30))%60; + second=(actualTime/30)%60; + frame=actualTime%30; + break; + case 4: // 30 (NTSC non-drop) + default: + frameRate=120.0; + hour=(actualTime/(60*60*30))%24; + minute=(actualTime/(60*30))%60; + second=(actualTime/30)%60; + frame=actualTime%30; + break; + } + + if (output) if (!skipping && output->midiOut!=NULL && midiOutTime) { + unsigned char val=0; + switch (curMidiTimePiece) { + case 0: + val=frame&15; + break; + case 1: + val=frame>>4; + break; + case 2: + val=second&15; + break; + case 3: + val=second>>4; + break; + case 4: + val=minute&15; + break; + case 5: + val=minute>>4; + break; + case 6: + val=hour&15; + break; + case 7: + val=(hour>>4)|((timeRate-1)<<1); + break; + } + val|=curMidiTimePiece<<4; + output->midiOut->send(TAMidiMessage(TA_MIDI_MTC_FRAME,val,0)); + } + curMidiTimePiece=(curMidiTimePiece+1)&7; + + midiTimeCycles=got.rate*pow(2,MASTER_CLOCK_PREC)/(frameRate); + midiTimeDrift+=fmod(got.rate*pow(2,MASTER_CLOCK_PREC),(double)(frameRate)); + if (midiTimeDrift>=(frameRate)) { + midiTimeDrift-=(frameRate); + midiTimeCycles++; + } + } + } + + // 5. tick the clock and fill buffers as needed if (cyclesgetConfInt("channelTextCenter",1); settings.maxRecentFile=e->getConfInt("maxRecentFile",10); settings.midiOutClock=e->getConfInt("midiOutClock",0); + settings.midiOutTime=e->getConfInt("midiOutTime",0); settings.midiOutProgramChange=e->getConfInt("midiOutProgramChange",0); settings.midiOutMode=e->getConfInt("midiOutMode",1); + settings.midiOutTimeRate=e->getConfInt("midiOutTimeRate",0); settings.centerPattern=e->getConfInt("centerPattern",0); settings.ordersCursor=e->getConfInt("ordersCursor",1); settings.persistFadeOut=e->getConfInt("persistFadeOut",1); @@ -2749,8 +2775,10 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.channelTextCenter,0,1); clampSetting(settings.maxRecentFile,0,30); clampSetting(settings.midiOutClock,0,1); + clampSetting(settings.midiOutTime,0,1); clampSetting(settings.midiOutProgramChange,0,1); clampSetting(settings.midiOutMode,0,2); + clampSetting(settings.midiOutTimeRate,0,4); clampSetting(settings.centerPattern,0,1); clampSetting(settings.ordersCursor,0,1); clampSetting(settings.persistFadeOut,0,1); @@ -2960,8 +2988,10 @@ void FurnaceGUI::commitSettings() { e->setConf("channelTextCenter",settings.channelTextCenter); e->setConf("maxRecentFile",settings.maxRecentFile); e->setConf("midiOutClock",settings.midiOutClock); + e->setConf("midiOutTime",settings.midiOutTime); e->setConf("midiOutProgramChange",settings.midiOutProgramChange); e->setConf("midiOutMode",settings.midiOutMode); + e->setConf("midiOutTimeRate",settings.midiOutTimeRate); e->setConf("centerPattern",settings.centerPattern); e->setConf("ordersCursor",settings.ordersCursor); e->setConf("persistFadeOut",settings.persistFadeOut);