diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index a7eac1b68..46f7ad03a 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -3683,6 +3683,8 @@ bool DivEngine::initAudioBackend() { clampSamples=getConfInt("clampSamples",0); lowLatency=getConfInt("lowLatency",0); metroVol=(float)(getConfInt("metroVol",100))/100.0f; + midiOutClock=getConfInt("midiOutClock",0); + midiOutMode=getConfInt("midiOutMode",DIV_MIDI_MODE_NOTE); if (metroVol<0.0f) metroVol=0.0f; if (metroVol>2.0f) metroVol=2.0f; diff --git a/src/engine/engine.h b/src/engine/engine.h index 0e56b12f3..2219b6e2c 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -83,6 +83,12 @@ enum DivHaltPositions { DIV_HALT_BREAKPOINT }; +enum DivMIDIModes { + DIV_MIDI_MODE_OFF=0, + DIV_MIDI_MODE_NOTE, + DIV_MIDI_MODE_LIGHT_SHOW +}; + struct DivChannelState { std::vector delayed; int note, oldNote, lastIns, pitch, portaSpeed, portaNote; @@ -340,6 +346,8 @@ class DivEngine { bool lowLatency; bool systemsRegistered; bool hasLoadedSomething; + bool midiOutClock; + int midiOutMode; int softLockCount; int subticks, ticks, curRow, curOrder, prevRow, prevOrder, remainingLoops, totalLoops, lastLoopPos, exportLoopCount, nextSpeed; size_t curSubSongIndex; @@ -1008,6 +1016,8 @@ class DivEngine { lowLatency(false), systemsRegistered(false), hasLoadedSomething(false), + midiOutClock(false), + midiOutMode(DIV_MIDI_MODE_NOTE), softLockCount(0), subticks(0), ticks(0), diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index d9eb18f84..fedfc94eb 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -257,52 +257,79 @@ int DivEngine::dispatchCmd(DivCommand c) { if (output) if (!skipping && output->midiOut!=NULL) { if (output->midiOut->isDeviceOpen()) { - int scaledVol=(chan[c.chan].volume*127)/MAX(1,chan[c.chan].volMax); - if (scaledVol<0) scaledVol=0; - if (scaledVol>127) scaledVol=127; - switch (c.cmd) { - case DIV_CMD_NOTE_ON: - case DIV_CMD_LEGATO: - if (chan[c.chan].curMidiNote>=0) { - output->midiOut->send(TAMidiMessage(0x80|(c.chan&15),chan[c.chan].curMidiNote,scaledVol)); + if (midiOutMode==DIV_MIDI_MODE_NOTE) { + int scaledVol=(chan[c.chan].volume*127)/MAX(1,chan[c.chan].volMax); + if (scaledVol<0) scaledVol=0; + if (scaledVol>127) scaledVol=127; + switch (c.cmd) { + case DIV_CMD_NOTE_ON: + case DIV_CMD_LEGATO: + if (chan[c.chan].curMidiNote>=0) { + output->midiOut->send(TAMidiMessage(0x80|(c.chan&15),chan[c.chan].curMidiNote,scaledVol)); + } + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].curMidiNote=c.value+12; + if (chan[c.chan].curMidiNote<0) chan[c.chan].curMidiNote=0; + if (chan[c.chan].curMidiNote>127) chan[c.chan].curMidiNote=127; + } + output->midiOut->send(TAMidiMessage(0x90|(c.chan&15),chan[c.chan].curMidiNote,scaledVol)); + break; + case DIV_CMD_NOTE_OFF: + case DIV_CMD_NOTE_OFF_ENV: + if (chan[c.chan].curMidiNote>=0) { + output->midiOut->send(TAMidiMessage(0x80|(c.chan&15),chan[c.chan].curMidiNote,scaledVol)); + } + chan[c.chan].curMidiNote=-1; + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].lastIns!=c.value) { + output->midiOut->send(TAMidiMessage(0xc0|(c.chan&15),c.value,0)); + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].curMidiNote>=0 && chan[c.chan].midiAftertouch) { + chan[c.chan].midiAftertouch=false; + output->midiOut->send(TAMidiMessage(0xa0|(c.chan&15),chan[c.chan].curMidiNote,scaledVol)); + } + break; + case DIV_CMD_PITCH: { + int pitchBend=8192+(c.value<<5); + if (pitchBend<0) pitchBend=0; + if (pitchBend>16383) pitchBend=16383; + if (pitchBend!=chan[c.chan].midiPitch) { + chan[c.chan].midiPitch=pitchBend; + output->midiOut->send(TAMidiMessage(0xe0|(c.chan&15),pitchBend&0x7f,pitchBend>>7)); + } + break; } - if (c.value!=DIV_NOTE_NULL) { - chan[c.chan].curMidiNote=c.value+12; - if (chan[c.chan].curMidiNote<0) chan[c.chan].curMidiNote=0; - if (chan[c.chan].curMidiNote>127) chan[c.chan].curMidiNote=127; + case DIV_CMD_PANNING: { + int pan=convertPanSplitToLinearLR(c.value,c.value2,127); + output->midiOut->send(TAMidiMessage(0xb0|(c.chan&15),0x0a,pan)); + break; } - output->midiOut->send(TAMidiMessage(0x90|(c.chan&15),chan[c.chan].curMidiNote,scaledVol)); - break; - case DIV_CMD_NOTE_OFF: - case DIV_CMD_NOTE_OFF_ENV: - if (chan[c.chan].curMidiNote>=0) { - output->midiOut->send(TAMidiMessage(0x80|(c.chan&15),chan[c.chan].curMidiNote,scaledVol)); + case DIV_CMD_HINT_PORTA: { + if (c.value2>0) { + if (c.value<=0 || c.value>=255) break; + //output->midiOut->send(TAMidiMessage(0x80|(c.chan&15),chan[c.chan].curMidiNote,scaledVol)); + int target=c.value+12; + if (target<0) target=0; + if (target>127) target=127; + + if (chan[c.chan].curMidiNote>=0) { + output->midiOut->send(TAMidiMessage(0xb0|(c.chan&15),0x54,chan[c.chan].curMidiNote)); + } + output->midiOut->send(TAMidiMessage(0xb0|(c.chan&15),0x05,1/*MIN(0x7f,c.value2/4)*/)); + output->midiOut->send(TAMidiMessage(0xb0|(c.chan&15),0x41,0x7f)); + + output->midiOut->send(TAMidiMessage(0x90|(c.chan&15),target,scaledVol)); + } else { + output->midiOut->send(TAMidiMessage(0xb0|(c.chan&15),0x41,0)); + } + break; } - chan[c.chan].curMidiNote=-1; - break; - case DIV_CMD_INSTRUMENT: - if (chan[c.chan].lastIns!=c.value) { - output->midiOut->send(TAMidiMessage(0xc0|(c.chan&15),c.value,0)); - } - break; - case DIV_CMD_VOLUME: - if (chan[c.chan].curMidiNote>=0 && chan[c.chan].midiAftertouch) { - chan[c.chan].midiAftertouch=false; - output->midiOut->send(TAMidiMessage(0xa0|(c.chan&15),chan[c.chan].curMidiNote,scaledVol)); - } - break; - case DIV_CMD_PITCH: { - int pitchBend=8192+(c.value<<5); - if (pitchBend<0) pitchBend=0; - if (pitchBend>16383) pitchBend=16383; - if (pitchBend!=chan[c.chan].midiPitch) { - chan[c.chan].midiPitch=pitchBend; - output->midiOut->send(TAMidiMessage(0xe0|(c.chan&15),pitchBend&0x7f,pitchBend>>7)); - } - break; + default: + break; } - default: - break; } } } @@ -1055,11 +1082,6 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { cycles++; } - // MIDI clock - if (output) if (!skipping && output->midiOut!=NULL) { - //output->midiOut->send(TAMidiMessage(TA_MIDI_CLOCK,0,0)); - } - if (!pendingNotes.empty()) { bool isOn[DIV_MAX_CHANS]; memset(isOn,0,DIV_MAX_CHANS*sizeof(bool)); @@ -1106,6 +1128,12 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { if (!freelance) { if (--subticks<=0) { subticks=tickMult; + + // MIDI clock + if (output) if (!skipping && output->midiOut!=NULL && midiOutClock) { + output->midiOut->send(TAMidiMessage(TA_MIDI_CLOCK,0,0)); + } + if (stepPlay!=1) { tempoAccum+=curSubSong->virtualTempoN; while (tempoAccum>=curSubSong->virtualTempoD) { diff --git a/src/gui/gui.h b/src/gui/gui.h index a873f8431..76a25efd4 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1195,6 +1195,8 @@ class FurnaceGUI { int channelFeedbackStyle; int channelFont; int channelTextCenter; + int midiOutClock; + int midiOutMode; int maxRecentFile; unsigned int maxUndoSteps; String mainFontPath; @@ -1318,6 +1320,8 @@ class FurnaceGUI { channelFeedbackStyle(1), channelFont(1), channelTextCenter(1), + midiOutClock(0), + midiOutMode(1), maxRecentFile(10), maxUndoSteps(100), mainFontPath(""), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index cc3f044bf..805072f16 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -955,6 +955,26 @@ void FurnaceGUI::drawSettings() { ImGui::EndTable(); } + ImGui::TreePop(); + } + if (ImGui::TreeNode("MIDI output settings")) { + ImGui::Text("Output mode:"); + if (ImGui::RadioButton("Off (use for TX81Z)",settings.midiOutMode==0)) { + settings.midiOutMode=0; + } + if (ImGui::RadioButton("Melodic",settings.midiOutMode==1)) { + settings.midiOutMode=1; + } + /* + if (ImGui::RadioButton("Light Show (use for Launchpad)",settings.midiOutMode==2)) { + settings.midiOutMode=2; + }*/ + + bool midiOutClockB=settings.midiOutClock; + if (ImGui::Checkbox("Send MIDI clock",&midiOutClockB)) { + settings.midiOutClock=midiOutClockB; + } + ImGui::TreePop(); } } @@ -2314,6 +2334,8 @@ void FurnaceGUI::syncSettings() { settings.channelFont=e->getConfInt("channelFont",1); settings.channelTextCenter=e->getConfInt("channelTextCenter",1); settings.maxRecentFile=e->getConfInt("maxRecentFile",10); + settings.midiOutClock=e->getConfInt("midiOutClock",0); + settings.midiOutMode=e->getConfInt("midiOutMode",1); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -2415,6 +2437,8 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.channelFont,0,1); clampSetting(settings.channelTextCenter,0,1); clampSetting(settings.maxRecentFile,0,30); + clampSetting(settings.midiOutClock,0,1); + clampSetting(settings.midiOutMode,0,2); settings.initialSys=e->decodeSysDesc(e->getConfString("initialSys","")); if (settings.initialSys.size()<4) { @@ -2572,6 +2596,8 @@ void FurnaceGUI::commitSettings() { e->setConf("channelFont",settings.channelFont); e->setConf("channelTextCenter",settings.channelTextCenter); e->setConf("maxRecentFile",settings.maxRecentFile); + e->setConf("midiOutClock",settings.midiOutClock); + e->setConf("midiOutMode",settings.midiOutMode); // colors for (int i=0; i