From 052dcb25768056728207c6cb3f2585198734f56d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 1 Apr 2022 01:50:01 -0500 Subject: [PATCH] implement more MIDI stuff --- src/engine/engine.h | 2 +- src/gui/gui.cpp | 194 +++++++++++++++++++++++++++++++++---------- src/gui/gui.h | 31 ++++++- src/gui/guiConst.cpp | 4 + src/gui/guiConst.h | 1 + src/gui/midiMap.cpp | 49 +++++++++++ src/gui/settings.cpp | 79 ++++++++++++++++-- 7 files changed, 305 insertions(+), 55 deletions(-) diff --git a/src/engine/engine.h b/src/engine/engine.h index f2ec5798..01bed21f 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -251,7 +251,7 @@ class DivEngine { size_t totalProcessed; // MIDI stuff - std::function midiCallback=[](const TAMidiMessage&) -> int {return -1;}; + std::function midiCallback=[](const TAMidiMessage&) -> int {return -2;}; DivSystem systemFromFile(unsigned char val); unsigned char systemToFile(DivSystem val); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index ce80c172..e8a40a66 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -805,6 +805,64 @@ void FurnaceGUI::noteInput(int num, int key, int vol) { curNibble=false; } +void FurnaceGUI::valueInput(int num, bool direct, int target) { + DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][e->getOrder()],true); + prepareUndo(GUI_UNDO_PATTERN_EDIT); + if (target==-1) target=cursor.xFine+1; + if (direct) { + pat->data[cursor.y][target]=num&0xff; + } else { + if (pat->data[cursor.y][target]==-1) pat->data[cursor.y][target]=0; + pat->data[cursor.y][target]=((pat->data[cursor.y][target]<<4)|num)&0xff; + } + if (cursor.xFine==1) { // instrument + if (pat->data[cursor.y][target]>=(int)e->song.ins.size()) { + pat->data[cursor.y][target]&=0x0f; + if (pat->data[cursor.y][target]>=(int)e->song.ins.size()) { + pat->data[cursor.y][target]=(int)e->song.ins.size()-1; + } + } + makeUndo(GUI_UNDO_PATTERN_EDIT); + if (direct) { + curNibble=false; + } else { + if (e->song.ins.size()<16) { + curNibble=false; + editAdvance(); + } else { + curNibble=!curNibble; + if (!curNibble) editAdvance(); + } + } + } else if (cursor.xFine==2) { + if (curNibble) { + if (pat->data[cursor.y][target]>e->getMaxVolumeChan(cursor.xCoarse)) pat->data[cursor.y][target]=e->getMaxVolumeChan(cursor.xCoarse); + } else { + pat->data[cursor.y][target]&=15; + } + makeUndo(GUI_UNDO_PATTERN_EDIT); + if (direct) { + curNibble=false; + } else { + if (e->getMaxVolumeChan(cursor.xCoarse)<16) { + curNibble=false; + editAdvance(); + } else { + curNibble=!curNibble; + if (!curNibble) editAdvance(); + } + } + } else { + makeUndo(GUI_UNDO_PATTERN_EDIT); + if (direct) { + curNibble=false; + } else { + curNibble=!curNibble; + if (!curNibble) editAdvance(); + } + } +} + void FurnaceGUI::keyDown(SDL_Event& ev) { if (ImGuiFileDialog::Instance()->IsOpened()) return; if (aboutOpen) return; @@ -879,44 +937,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { } else if (edit) { // value try { int num=valueKeys.at(ev.key.keysym.sym); - DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][e->getOrder()],true); - prepareUndo(GUI_UNDO_PATTERN_EDIT); - if (pat->data[cursor.y][cursor.xFine+1]==-1) pat->data[cursor.y][cursor.xFine+1]=0; - pat->data[cursor.y][cursor.xFine+1]=((pat->data[cursor.y][cursor.xFine+1]<<4)|num)&0xff; - if (cursor.xFine==1) { // instrument - if (pat->data[cursor.y][cursor.xFine+1]>=(int)e->song.ins.size()) { - pat->data[cursor.y][cursor.xFine+1]&=0x0f; - if (pat->data[cursor.y][cursor.xFine+1]>=(int)e->song.ins.size()) { - pat->data[cursor.y][cursor.xFine+1]=(int)e->song.ins.size()-1; - } - } - makeUndo(GUI_UNDO_PATTERN_EDIT); - if (e->song.ins.size()<16) { - curNibble=false; - editAdvance(); - } else { - curNibble=!curNibble; - if (!curNibble) editAdvance(); - } - } else if (cursor.xFine==2) { - if (curNibble) { - if (pat->data[cursor.y][cursor.xFine+1]>e->getMaxVolumeChan(cursor.xCoarse)) pat->data[cursor.y][cursor.xFine+1]=e->getMaxVolumeChan(cursor.xCoarse); - } else { - pat->data[cursor.y][cursor.xFine+1]&=15; - } - makeUndo(GUI_UNDO_PATTERN_EDIT); - if (e->getMaxVolumeChan(cursor.xCoarse)<16) { - curNibble=false; - editAdvance(); - } else { - curNibble=!curNibble; - if (!curNibble) editAdvance(); - } - } else { - makeUndo(GUI_UNDO_PATTERN_EDIT); - curNibble=!curNibble; - if (!curNibble) editAdvance(); - } + valueInput(num); } catch (std::out_of_range& e) { } } @@ -1931,7 +1952,7 @@ bool FurnaceGUI::loop() { midiMap.binds[learning].type=msg.type>>4; midiMap.binds[learning].channel=msg.type&15; midiMap.binds[learning].data1=msg.data[0]; - switch (msg.type>>4) { + switch (msg.type&0xf0) { case TA_MIDI_NOTE_OFF: case TA_MIDI_NOTE_ON: case TA_MIDI_AFTERTOUCH: @@ -1951,12 +1972,35 @@ bool FurnaceGUI::loop() { doAction(action); } else switch (msg.type&0xf0) { case TA_MIDI_NOTE_ON: - if (edit && msg.data[1]!=0) { - noteInput( - msg.data[0]-12, - 0, - midiMap.volInput?((int)(pow((double)msg.data[2]/127.0,midiMap.volExp)*127.0)):-1 - ); + if (midiMap.valueInputStyle==0 || midiMap.valueInputStyle>3 || cursor.xFine==0) { + if (midiMap.noteInput && edit && msg.data[1]!=0) { + noteInput( + msg.data[0]-12, + 0, + midiMap.volInput?((int)(pow((double)msg.data[1]/127.0,midiMap.volExp)*127.0)):-1 + ); + } + } else { + if (edit && msg.data[1]!=0) { + switch (midiMap.valueInputStyle) { + case 1: { + int val=msg.data[0]%24; + if (val<16) { + valueInput(val); + } + break; + } + case 2: + valueInput(msg.data[0]&15); + break; + case 3: + int val=altValues[msg.data[0]%24]; + if (val>=0) { + valueInput(val); + } + break; + } + } } break; case TA_MIDI_PROGRAM: @@ -1965,6 +2009,64 @@ bool FurnaceGUI::loop() { if (curIns>=(int)e->song.ins.size()) curIns=e->song.ins.size()-1; } break; + case TA_MIDI_CONTROL: + bool gchanged=false; + if (msg.data[0]==midiMap.valueInputControlMSB) { + midiMap.valueInputCurMSB=msg.data[1]; + gchanged=true; + } + if (msg.data[0]==midiMap.valueInputControlLSB) { + midiMap.valueInputCurLSB=msg.data[1]; + gchanged=true; + } + if (msg.data[0]==midiMap.valueInputControlSingle) { + midiMap.valueInputCurSingle=msg.data[1]; + gchanged=true; + } + if (gchanged && cursor.xFine>0) { + switch (midiMap.valueInputStyle) { + case 4: // dual CC + valueInput(((midiMap.valueInputCurMSB>>3)<<4)|(midiMap.valueInputCurLSB>>3),true); + break; + case 5: // 14-bit + valueInput((midiMap.valueInputCurMSB<<1)|(midiMap.valueInputCurLSB>>6),true); + break; + case 6: // single CC + valueInput((midiMap.valueInputCurSingle*255)/127,true); + break; + } + } + + for (int i=0; i<18; i++) { + bool changed=false; + if (midiMap.valueInputSpecificStyle[i]!=0) { + if (msg.data[0]==midiMap.valueInputSpecificMSB[i]) { + changed=true; + midiMap.valueInputCurMSBS[i]=msg.data[1]; + } + if (msg.data[0]==midiMap.valueInputSpecificLSB[i]) { + changed=true; + midiMap.valueInputCurLSBS[i]=msg.data[1]; + } + if (msg.data[0]==midiMap.valueInputSpecificSingle[i]) { + changed=true; + midiMap.valueInputCurSingleS[i]=msg.data[1]; + } + + if (changed) switch (midiMap.valueInputStyle) { + case 1: // dual CC + valueInput(((midiMap.valueInputCurMSBS[i]>>3)<<4)|(midiMap.valueInputCurLSBS[i]>>3),true,i+2); + break; + case 2: // 14-bit + valueInput((midiMap.valueInputCurMSBS[i]<<1)|(midiMap.valueInputCurLSBS[i]>>6),true,i+2); + break; + case 3: // single CC + valueInput((midiMap.valueInputCurSingleS[i]*255)/127,true,i+2); + break; + } + } + } + break; } } @@ -2734,6 +2836,8 @@ bool FurnaceGUI::init() { midiQueue.push(msg); midiLock.unlock(); e->setMidiBaseChan(cursor.xCoarse); + if (midiMap.valueInputStyle!=0 && cursor.xFine!=0 && edit) return -2; + if (!midiMap.noteInput) return -2; if (learning!=-1) return -2; if (midiMap.at(msg)) return -2; return curIns; diff --git a/src/gui/gui.h b/src/gui/gui.h index 93d7727f..bf2050f0 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -539,11 +539,27 @@ struct MIDIMap { // // 4: use dual CC for value input (nibble) // 5: use 14-bit CC for value input (MSB/LSB) + // 6: use single CC for value input (may be imprecise) int valueInputStyle; int valueInputControlMSB; // on 4 int valueInputControlLSB; // on 4 + int valueInputControlSingle; + + // 0: disabled + // 1: use dual CC (nibble) + // 2: use 14-bit CC (MSB/LSB) + // 3: use single CC (may be imprecise) + int valueInputSpecificStyle[18]; + int valueInputSpecificMSB[18]; + int valueInputSpecificLSB[18]; + int valueInputSpecificSingle[18]; float volExp; + int valueInputCurMSB, valueInputCurLSB, valueInputCurSingle; + int valueInputCurMSBS[18]; + int valueInputCurLSBS[18]; + int valueInputCurSingleS[18]; + void compile(); void deinit(); int at(const TAMidiMessage& where); @@ -560,7 +576,19 @@ struct MIDIMap { midiClock(false), midiTimeCode(false), valueInputStyle(1), - volExp(1.0f) {} + volExp(1.0f), + valueInputCurMSB(0), + valueInputCurLSB(0), + valueInputCurSingle(0) { + memset(valueInputSpecificStyle,0,18*sizeof(int)); + memset(valueInputSpecificMSB,0,18*sizeof(int)); + memset(valueInputSpecificLSB,0,18*sizeof(int)); + memset(valueInputSpecificSingle,0,18*sizeof(int)); + + memset(valueInputCurMSBS,0,18*sizeof(int)); + memset(valueInputCurLSBS,0,18*sizeof(int)); + memset(valueInputCurSingleS,0,18*sizeof(int)); + } }; struct Particle { @@ -983,6 +1011,7 @@ class FurnaceGUI { void doRedo(); void editOptions(bool topMenu); void noteInput(int num, int key, int vol=-1); + void valueInput(int num, bool direct=false, int target=-1); void doUndoSample(); void doRedoSample(); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index b21f9b7a..01c60373 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -66,6 +66,10 @@ const char* pitchLabel[11]={ "1/6", "1/5", "1/4", "1/3", "1/2", "1x", "2x", "3x", "4x", "5x", "6x" }; +const int altValues[24]={ + 0, 10, 1, 11, 2, 3, 12, 4, 13, 5, 14, 6, 7, 15, 8, -1, 9, -1, -1, -1, -1, -1, -1, -1 +}; + const char* insTypes[DIV_INS_MAX]={ "Standard", "FM (4-operator)", diff --git a/src/gui/guiConst.h b/src/gui/guiConst.h index 6811845e..6b55df86 100644 --- a/src/gui/guiConst.h +++ b/src/gui/guiConst.h @@ -28,3 +28,4 @@ extern const char* sampleDepths[17]; extern const char* resampleStrats[]; extern const int availableSystems[]; extern const char* guiActions[][2]; +extern const int altValues[24]; \ No newline at end of file diff --git a/src/gui/midiMap.cpp b/src/gui/midiMap.cpp index a7232441..a38fe11b 100644 --- a/src/gui/midiMap.cpp +++ b/src/gui/midiMap.cpp @@ -62,6 +62,14 @@ int MIDIMap::at(const TAMidiMessage& where) { x=std::stof(optionValueS); \ } +#define UNDERSTAND_ARRAY_OPTION(x,yMax) if (optionNameS==#x) { \ + if (optionIndex<0 || optionIndex>=yMax) { \ + logW("MIDI map array option %d out of range (0-%d) at line %d: %s\n",optionIndex,yMax,curLine,line); \ + break; \ + } \ + x[optionIndex]=std::stoi(optionValueS); \ +} + bool MIDIMap::read(String path) { char line[4096]; int curLine=1; @@ -77,6 +85,37 @@ bool MIDIMap::read(String path) { while (fgets(line,4096,f)) { char* nlPos=strrchr(line,'\n'); if (nlPos!=NULL) *nlPos=0; + if (strstr(line,"aOption")==line) { + char optionName[256]; + int optionIndex=-1; + char optionValue[256]; + String optionNameS, optionValueS; + + int result=sscanf(line,"aOption %255s %d %255s",optionName,&optionIndex,optionValue); + if (result!=3) { + logW("MIDI map garbage data at line %d: %s\n",curLine,line); + break; + } + + optionNameS=optionName; + optionValueS=optionValue; + + try { + UNDERSTAND_ARRAY_OPTION(valueInputSpecificStyle,18) else + UNDERSTAND_ARRAY_OPTION(valueInputSpecificMSB,18) else + UNDERSTAND_ARRAY_OPTION(valueInputSpecificLSB,18) else + UNDERSTAND_ARRAY_OPTION(valueInputSpecificSingle,18) else { + logW("MIDI map unknown array option %s at line %d: %s\n",optionName,curLine,line); + } + } catch (std::out_of_range& e) { + logW("MIDI map invalid value %s for array option %s at line %d: %s\n",optionValue,optionName,curLine,line); + } catch (std::invalid_argument& e) { + logW("MIDI map invalid value %s for array option %s at line %d: %s\n",optionValue,optionName,curLine,line); + } + + curLine++; + continue; + } if (strstr(line,"option")==line) { char optionName[256]; char optionValue[256]; @@ -102,6 +141,7 @@ bool MIDIMap::read(String path) { UNDERSTAND_OPTION(valueInputStyle) else UNDERSTAND_OPTION(valueInputControlMSB) else UNDERSTAND_OPTION(valueInputControlLSB) else + UNDERSTAND_OPTION(valueInputControlSingle) else UNDERSTAND_FLOAT_OPTION(volExp) else { logW("MIDI map unknown option %s at line %d: %s\n",optionName,curLine,line); } @@ -146,6 +186,7 @@ bool MIDIMap::read(String path) { #define WRITE_OPTION(x) fprintf(f,"option " #x " %d\n",x); #define WRITE_FLOAT_OPTION(x) fprintf(f,"option " #x " %f\n",x); +#define WRITE_ARRAY_OPTION(x,y) fprintf(f,"aOption " #x " %d %d\n",y,x[y]); bool MIDIMap::write(String path) { FILE* f=fopen(path.c_str(),"wb"); @@ -165,8 +206,16 @@ bool MIDIMap::write(String path) { WRITE_OPTION(valueInputStyle); WRITE_OPTION(valueInputControlMSB); WRITE_OPTION(valueInputControlLSB); + WRITE_OPTION(valueInputControlSingle); WRITE_FLOAT_OPTION(volExp); + for (int i=0; i<18; i++) { + WRITE_ARRAY_OPTION(valueInputSpecificStyle,i); + WRITE_ARRAY_OPTION(valueInputSpecificMSB,i); + WRITE_ARRAY_OPTION(valueInputSpecificLSB,i); + WRITE_ARRAY_OPTION(valueInputSpecificSingle,i); + } + for (MIDIBind& i: binds) { if (fprintf(f,"%d %d %d %d %s\n",i.type,i.channel,i.data1,i.data2,guiActions[i.action][0])<0) { logW("did not write MIDI mapping entirely! %s\n",strerror(errno)); diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 9f04fd0f..83649a31 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -87,7 +87,15 @@ const char* valueInputStyles[]={ "Raw (note number is value)", "Two octaves alternate (lower keys are 0-9, upper keys are A-F)", "Use dual control change (one for each nibble)", - "Use 14-bit control change" + "Use 14-bit control change", + "Use single control change (imprecise)" +}; + +const char* valueSInputStyles[]={ + "Disabled/custom", + "Use dual control change (one for each nibble)", + "Use 14-bit control change", + "Use single control change (imprecise)" }; const char* messageTypes[]={ @@ -113,6 +121,27 @@ const char* messageChannels[]={ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "Any" }; +const char* specificControls[18]={ + "Instrument", + "Volume", + "Effect 1 type", + "Effect 1 value", + "Effect 2 type", + "Effect 2 value", + "Effect 3 type", + "Effect 3 value", + "Effect 4 type", + "Effect 4 value", + "Effect 5 type", + "Effect 5 value", + "Effect 6 type", + "Effect 6 value", + "Effect 7 type", + "Effect 7 value", + "Effect 8 type", + "Effect 8 value" +}; + #define SAMPLE_RATE_SELECTABLE(x) \ if (ImGui::Selectable(#x,settings.audioRate==x)) { \ settings.audioRate=x; \ @@ -370,16 +399,50 @@ void FurnaceGUI::drawSettings() { ImGui::Checkbox("Program change is instrument selection",&midiMap.programChange); ImGui::Checkbox("Listen to MIDI clock",&midiMap.midiClock); ImGui::Checkbox("Listen to MIDI time code",&midiMap.midiTimeCode); - ImGui::Combo("Value input style",&midiMap.valueInputStyle,valueInputStyles,6); + ImGui::Combo("Value input style",&midiMap.valueInputStyle,valueInputStyles,7); if (midiMap.valueInputStyle>3) { - if (ImGui::InputInt((midiMap.valueInputStyle==4)?"CC of upper nibble##valueCC1":"MSB CC##valueCC1",&midiMap.valueInputControlMSB,1,16)) { - if (midiMap.valueInputControlMSB<0) midiMap.valueInputControlMSB=0; - if (midiMap.valueInputControlMSB>127) midiMap.valueInputControlMSB=127; + if (midiMap.valueInputStyle==6) { + if (ImGui::InputInt("Control##valueCCS",&midiMap.valueInputControlSingle,1,16)) { + if (midiMap.valueInputControlSingle<0) midiMap.valueInputControlSingle=0; + if (midiMap.valueInputControlSingle>127) midiMap.valueInputControlSingle=127; + } + } else { + if (ImGui::InputInt((midiMap.valueInputStyle==4)?"CC of upper nibble##valueCC1":"MSB CC##valueCC1",&midiMap.valueInputControlMSB,1,16)) { + if (midiMap.valueInputControlMSB<0) midiMap.valueInputControlMSB=0; + if (midiMap.valueInputControlMSB>127) midiMap.valueInputControlMSB=127; + } + if (ImGui::InputInt((midiMap.valueInputStyle==4)?"CC of lower nibble##valueCC2":"LSB CC##valueCC2",&midiMap.valueInputControlLSB,1,16)) { + if (midiMap.valueInputControlLSB<0) midiMap.valueInputControlLSB=0; + if (midiMap.valueInputControlLSB>127) midiMap.valueInputControlLSB=127; + } } - if (ImGui::InputInt((midiMap.valueInputStyle==4)?"CC of lower nibble##valueCC2":"LSB CC##valueCC2",&midiMap.valueInputControlLSB,1,16)) { - if (midiMap.valueInputControlLSB<0) midiMap.valueInputControlLSB=0; - if (midiMap.valueInputControlLSB>127) midiMap.valueInputControlLSB=127; + } + if (ImGui::TreeNode("Per-column control change")) { + for (int i=0; i<18; i++) { + ImGui::PushID(i); + ImGui::Combo(specificControls[i],&midiMap.valueInputSpecificStyle[i],valueSInputStyles,4); + if (midiMap.valueInputSpecificStyle[i]>0) { + ImGui::Indent(); + if (midiMap.valueInputSpecificStyle[i]==3) { + if (ImGui::InputInt("Control##valueCCS",&midiMap.valueInputSpecificSingle[i],1,16)) { + if (midiMap.valueInputSpecificSingle[i]<0) midiMap.valueInputSpecificSingle[i]=0; + if (midiMap.valueInputSpecificSingle[i]>127) midiMap.valueInputSpecificSingle[i]=127; + } + } else { + if (ImGui::InputInt((midiMap.valueInputSpecificStyle[i]==4)?"CC of upper nibble##valueCC1":"MSB CC##valueCC1",&midiMap.valueInputSpecificMSB[i],1,16)) { + if (midiMap.valueInputSpecificMSB[i]<0) midiMap.valueInputSpecificMSB[i]=0; + if (midiMap.valueInputSpecificMSB[i]>127) midiMap.valueInputSpecificMSB[i]=127; + } + if (ImGui::InputInt((midiMap.valueInputSpecificStyle[i]==4)?"CC of lower nibble##valueCC2":"LSB CC##valueCC2",&midiMap.valueInputSpecificLSB[i],1,16)) { + if (midiMap.valueInputSpecificLSB[i]<0) midiMap.valueInputSpecificLSB[i]=0; + if (midiMap.valueInputSpecificLSB[i]>127) midiMap.valueInputSpecificLSB[i]=127; + } + } + ImGui::Unindent(); + } + ImGui::PopID(); } + ImGui::TreePop(); } if (ImGui::SliderFloat("Volume curve",&midiMap.volExp,0.01,8.0,"%.2f")) { if (midiMap.volExp<0.01) midiMap.volExp=0.01;