implement MIDI timecode output
This commit is contained in:
parent
eb521a6dac
commit
826538e41b
|
@ -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; i<DIV_MAX_CHANS; i++) {
|
||||
keyHit[i]=false;
|
||||
}
|
||||
curMidiTimePiece=0;
|
||||
if (output) if (!skipping && output->midiOut!=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<unsigned char[]>());
|
||||
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;
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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; i<midiTotal; i++) {
|
||||
if (--midiTimeCycles<=0) {
|
||||
if (curMidiTimePiece==0) {
|
||||
curMidiTimeCode=curMidiTime;
|
||||
}
|
||||
if (!(curMidiTimePiece&3)) curMidiTime++;
|
||||
|
||||
double frameRate=96.0;
|
||||
int timeRate=midiOutTimeRate;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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 (cycles<runLeftG) {
|
||||
for (int i=0; i<song.systemLen; i++) {
|
||||
int total=(cycles*disCont[i].runtotal)/(size<<MASTER_CLOCK_PREC);
|
||||
|
|
|
@ -1402,8 +1402,10 @@ class FurnaceGUI {
|
|||
int channelFont;
|
||||
int channelTextCenter;
|
||||
int midiOutClock;
|
||||
int midiOutTime;
|
||||
int midiOutProgramChange;
|
||||
int midiOutMode;
|
||||
int midiOutTimeRate;
|
||||
int maxRecentFile;
|
||||
int centerPattern;
|
||||
int ordersCursor;
|
||||
|
@ -1545,8 +1547,10 @@ class FurnaceGUI {
|
|||
channelFont(1),
|
||||
channelTextCenter(1),
|
||||
midiOutClock(0),
|
||||
midiOutTime(0),
|
||||
midiOutProgramChange(0),
|
||||
midiOutMode(1),
|
||||
midiOutTimeRate(0),
|
||||
maxRecentFile(10),
|
||||
centerPattern(0),
|
||||
ordersCursor(1),
|
||||
|
|
|
@ -1146,14 +1146,38 @@ void FurnaceGUI::drawSettings() {
|
|||
settings.midiOutMode=2;
|
||||
}*/
|
||||
|
||||
bool midiOutProgramChangeB=settings.midiOutProgramChange;
|
||||
if (ImGui::Checkbox("Send Program Change",&midiOutProgramChangeB)) {
|
||||
settings.midiOutProgramChange=midiOutProgramChangeB;
|
||||
}
|
||||
|
||||
bool midiOutClockB=settings.midiOutClock;
|
||||
if (ImGui::Checkbox("Send MIDI clock",&midiOutClockB)) {
|
||||
settings.midiOutClock=midiOutClockB;
|
||||
}
|
||||
|
||||
bool midiOutProgramChangeB=settings.midiOutProgramChange;
|
||||
if (ImGui::Checkbox("Send Program Change",&midiOutProgramChangeB)) {
|
||||
settings.midiOutProgramChange=midiOutProgramChangeB;
|
||||
bool midiOutTimeB=settings.midiOutTime;
|
||||
if (ImGui::Checkbox("Send MIDI timecode",&midiOutTimeB)) {
|
||||
settings.midiOutTime=midiOutTimeB;
|
||||
}
|
||||
|
||||
if (settings.midiOutTime) {
|
||||
ImGui::Text("Timecode frame rate:");
|
||||
if (ImGui::RadioButton("Closest to Tick Rate",settings.midiOutTimeRate==0)) {
|
||||
settings.midiOutTimeRate=0;
|
||||
}
|
||||
if (ImGui::RadioButton("Film (24fps)",settings.midiOutTimeRate==1)) {
|
||||
settings.midiOutTimeRate=1;
|
||||
}
|
||||
if (ImGui::RadioButton("PAL (25fps)",settings.midiOutTimeRate==2)) {
|
||||
settings.midiOutTimeRate=2;
|
||||
}
|
||||
if (ImGui::RadioButton("NTSC drop (29.97fps)",settings.midiOutTimeRate==3)) {
|
||||
settings.midiOutTimeRate=3;
|
||||
}
|
||||
if (ImGui::RadioButton("NTSC non-drop (30fps)",settings.midiOutTimeRate==4)) {
|
||||
settings.midiOutTimeRate=4;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::TreePop();
|
||||
|
@ -2629,8 +2653,10 @@ void FurnaceGUI::syncSettings() {
|
|||
settings.channelTextCenter=e->getConfInt("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);
|
||||
|
|
Loading…
Reference in a new issue