| @@ -18,8 +18,8 @@ struct MIDI_CC : Module { | |||||
| midi::InputQueue midiInput; | midi::InputQueue midiInput; | ||||
| int8_t values[128]; | int8_t values[128]; | ||||
| int learningId = -1; | |||||
| int learnedCcs[16] = {}; | |||||
| int learningId; | |||||
| int learnedCcs[16]; | |||||
| dsp::ExponentialFilter valueFilters[16]; | dsp::ExponentialFilter valueFilters[16]; | ||||
| int8_t lastValues[16] = {}; | int8_t lastValues[16] = {}; | ||||
| @@ -76,20 +76,24 @@ struct MIDI_CC : Module { | |||||
| switch (msg.getStatus()) { | switch (msg.getStatus()) { | ||||
| // cc | // cc | ||||
| case 0xb: { | case 0xb: { | ||||
| uint8_t cc = msg.getNote(); | |||||
| // Learn | |||||
| if (learningId >= 0 && values[cc] != msg.data2) { | |||||
| learnedCcs[learningId] = cc; | |||||
| learningId = -1; | |||||
| } | |||||
| // Allow CC to be negative if the 8th bit is set. | |||||
| // The gamepad driver abuses this, for example. | |||||
| values[cc] = msg.data2; | |||||
| processCC(msg); | |||||
| } break; | } break; | ||||
| default: break; | default: break; | ||||
| } | } | ||||
| } | } | ||||
| void processCC(midi::Message msg) { | |||||
| uint8_t cc = msg.getNote(); | |||||
| // Learn | |||||
| if (learningId >= 0 && values[cc] != msg.data2) { | |||||
| learnedCcs[learningId] = cc; | |||||
| learningId = -1; | |||||
| } | |||||
| // Allow CC to be negative if the 8th bit is set. | |||||
| // The gamepad driver abuses this, for example. | |||||
| values[cc] = msg.data2; | |||||
| } | |||||
| json_t *dataToJson() override { | json_t *dataToJson() override { | ||||
| json_t *rootJ = json_object(); | json_t *rootJ = json_object(); | ||||
| @@ -46,31 +46,6 @@ struct MIDI_Gate : Module { | |||||
| } | } | ||||
| } | } | ||||
| void pressNote(uint8_t note, uint8_t vel) { | |||||
| // Learn | |||||
| if (learningId >= 0) { | |||||
| learnedNotes[learningId] = note; | |||||
| learningId = -1; | |||||
| } | |||||
| // Find id | |||||
| for (int i = 0; i < 16; i++) { | |||||
| if (learnedNotes[i] == note) { | |||||
| gates[i] = true; | |||||
| gateTimes[i] = 1e-3f; | |||||
| velocities[i] = vel; | |||||
| } | |||||
| } | |||||
| } | |||||
| void releaseNote(uint8_t note) { | |||||
| // Find id | |||||
| for (int i = 0; i < 16; i++) { | |||||
| if (learnedNotes[i] == note) { | |||||
| gates[i] = false; | |||||
| } | |||||
| } | |||||
| } | |||||
| void step() override { | void step() override { | ||||
| midi::Message msg; | midi::Message msg; | ||||
| while (midiInput.shift(&msg)) { | while (midiInput.shift(&msg)) { | ||||
| @@ -113,6 +88,31 @@ struct MIDI_Gate : Module { | |||||
| } | } | ||||
| } | } | ||||
| void pressNote(uint8_t note, uint8_t vel) { | |||||
| // Learn | |||||
| if (learningId >= 0) { | |||||
| learnedNotes[learningId] = note; | |||||
| learningId = -1; | |||||
| } | |||||
| // Find id | |||||
| for (int i = 0; i < 16; i++) { | |||||
| if (learnedNotes[i] == note) { | |||||
| gates[i] = true; | |||||
| gateTimes[i] = 1e-3f; | |||||
| velocities[i] = vel; | |||||
| } | |||||
| } | |||||
| } | |||||
| void releaseNote(uint8_t note) { | |||||
| // Find id | |||||
| for (int i = 0; i < 16; i++) { | |||||
| if (learnedNotes[i] == note) { | |||||
| gates[i] = false; | |||||
| } | |||||
| } | |||||
| } | |||||
| json_t *dataToJson() override { | json_t *dataToJson() override { | ||||
| json_t *rootJ = json_object(); | json_t *rootJ = json_object(); | ||||
| @@ -17,8 +17,9 @@ struct MIDI_Map : Module { | |||||
| midi::InputQueue midiInput; | midi::InputQueue midiInput; | ||||
| int8_t values[128]; | int8_t values[128]; | ||||
| int learningId = -1; | |||||
| int learnedCcs[16] = {}; | |||||
| int learningId; | |||||
| int lastLearnedCc; | |||||
| int learnedCcs[8]; | |||||
| dsp::ExponentialFilter valueFilters[8]; | dsp::ExponentialFilter valueFilters[8]; | ||||
| MIDI_Map() { | MIDI_Map() { | ||||
| @@ -27,10 +28,71 @@ struct MIDI_Map : Module { | |||||
| } | } | ||||
| void onReset() override { | void onReset() override { | ||||
| learningId = -1; | |||||
| lastLearnedCc = -1; | |||||
| for (int i = 0; i < 8; i++) { | |||||
| learnedCcs[i] = -1; | |||||
| } | |||||
| midiInput.reset(); | midiInput.reset(); | ||||
| } | } | ||||
| void step() override { | void step() override { | ||||
| midi::Message msg; | |||||
| while (midiInput.shift(&msg)) { | |||||
| processMessage(msg); | |||||
| } | |||||
| } | |||||
| void processMessage(midi::Message msg) { | |||||
| switch (msg.getStatus()) { | |||||
| // cc | |||||
| case 0xb: { | |||||
| processCC(msg); | |||||
| } break; | |||||
| default: break; | |||||
| } | |||||
| } | |||||
| void processCC(midi::Message msg) { | |||||
| uint8_t cc = msg.getNote(); | |||||
| // Learn | |||||
| if (learningId >= 0 && values[cc] != msg.data2) { | |||||
| if (lastLearnedCc != cc) { | |||||
| learnedCcs[learningId] = cc; | |||||
| lastLearnedCc = cc; | |||||
| if (++learningId >= 8) | |||||
| learningId = -1; | |||||
| } | |||||
| } | |||||
| values[cc] = msg.getValue(); | |||||
| } | |||||
| json_t *dataToJson() override { | |||||
| json_t *rootJ = json_object(); | |||||
| json_t *ccsJ = json_array(); | |||||
| for (int i = 0; i < 8; i++) { | |||||
| json_array_append_new(ccsJ, json_integer(learnedCcs[i])); | |||||
| } | |||||
| json_object_set_new(rootJ, "ccs", ccsJ); | |||||
| json_object_set_new(rootJ, "midi", midiInput.toJson()); | |||||
| return rootJ; | |||||
| } | |||||
| void dataFromJson(json_t *rootJ) override { | |||||
| json_t *ccsJ = json_object_get(rootJ, "ccs"); | |||||
| if (ccsJ) { | |||||
| for (int i = 0; i < 8; i++) { | |||||
| json_t *ccJ = json_array_get(ccsJ, i); | |||||
| if (ccJ) | |||||
| learnedCcs[i] = json_integer_value(ccJ); | |||||
| } | |||||
| } | |||||
| json_t *midiJ = json_object_get(rootJ, "midi"); | |||||
| if (midiJ) | |||||
| midiInput.fromJson(midiJ); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -66,11 +128,24 @@ struct MIDI_MapChoice : LedDisplayChoice { | |||||
| color.a = 1.0; | color.a = 1.0; | ||||
| bgColor = color; | bgColor = color; | ||||
| bgColor.a = 0.15; | bgColor.a = 0.15; | ||||
| // HACK | |||||
| if (APP->event->selectedWidget != this) | |||||
| APP->event->setSelected(this); | |||||
| } | |||||
| else if (module->learnedCcs[id] >= 0) { | |||||
| text = string::f("CC%d", module->learnedCcs[id]); | |||||
| color.a = 1.0; | |||||
| bgColor = nvgRGBA(0, 0, 0, 0); | |||||
| } | } | ||||
| else { | else { | ||||
| text = "Unmapped"; | text = "Unmapped"; | ||||
| color.a = 0.5; | color.a = 0.5; | ||||
| bgColor = nvgRGBA(0, 0, 0, 0); | bgColor = nvgRGBA(0, 0, 0, 0); | ||||
| // HACK | |||||
| if (APP->event->selectedWidget == this) | |||||
| APP->event->setSelected(NULL); | |||||
| } | } | ||||
| } | } | ||||
| }; | }; | ||||
| @@ -87,6 +87,8 @@ struct CcChoice : LedDisplayChoice { | |||||
| else { | else { | ||||
| text = string::f("%d", module->learnedCcs[id]); | text = string::f("%d", module->learnedCcs[id]); | ||||
| color.a = 1.0; | color.a = 1.0; | ||||
| // HACK | |||||
| if (APP->event->selectedWidget == this) | if (APP->event->selectedWidget == this) | ||||
| APP->event->setSelected(NULL); | APP->event->setSelected(NULL); | ||||
| } | } | ||||
| @@ -170,6 +172,7 @@ struct NoteChoice : LedDisplayChoice { | |||||
| text = string::f("%s%d", noteNames[semi], oct); | text = string::f("%s%d", noteNames[semi], oct); | ||||
| color.a = 1.0; | color.a = 1.0; | ||||
| // HACK | |||||
| if (APP->event->selectedWidget == this) | if (APP->event->selectedWidget == this) | ||||
| APP->event->setSelected(NULL); | APP->event->setSelected(NULL); | ||||
| } | } | ||||