implement more MIDI stuff

This commit is contained in:
tildearrow 2022-04-01 01:50:01 -05:00
parent a08f7507fd
commit 052dcb2576
7 changed files with 305 additions and 55 deletions

View File

@ -251,7 +251,7 @@ class DivEngine {
size_t totalProcessed;
// MIDI stuff
std::function<int(const TAMidiMessage&)> midiCallback=[](const TAMidiMessage&) -> int {return -1;};
std::function<int(const TAMidiMessage&)> midiCallback=[](const TAMidiMessage&) -> int {return -2;};
DivSystem systemFromFile(unsigned char val);
unsigned char systemToFile(DivSystem val);

View File

@ -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;

View File

@ -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();

View File

@ -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)",

View File

@ -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];

View File

@ -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));

View File

@ -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;