| @@ -1,34 +1,27 @@ | |||
| #include "Core.hpp" | |||
| template <int N> | |||
| struct CCMidiOutput : midi::Output { | |||
| int ccs[N]; | |||
| int lastValues[N]; | |||
| int lastValues[128]; | |||
| CCMidiOutput() { | |||
| reset(); | |||
| } | |||
| void reset() { | |||
| for (int n = 0; n < N; n++) { | |||
| ccs[n] = n; | |||
| for (int n = 0; n < 128; n++) { | |||
| lastValues[n] = -1; | |||
| } | |||
| } | |||
| void setCC(int cc, int n) { | |||
| ccs[n] = cc; | |||
| } | |||
| void setValue(int value, int n) { | |||
| if (value == lastValues[n]) | |||
| void setValue(int value, int cc) { | |||
| if (value == lastValues[cc]) | |||
| return; | |||
| lastValues[n] = value; | |||
| lastValues[cc] = value; | |||
| // CC | |||
| midi::Message m; | |||
| m.setStatus(0xb); | |||
| m.setNote(ccs[n]); | |||
| m.setNote(cc); | |||
| m.setValue(value); | |||
| sendMessage(m); | |||
| } | |||
| @@ -50,11 +43,23 @@ struct CV_CC : Module { | |||
| NUM_LIGHTS | |||
| }; | |||
| CCMidiOutput<16> midiOutput; | |||
| CCMidiOutput midiOutput; | |||
| float rateLimiterPhase = 0.f; | |||
| int learningId = -1; | |||
| int learnedCcs[16] = {}; | |||
| CV_CC() { | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| onReset(); | |||
| } | |||
| void onReset() override { | |||
| for (int i = 0; i < 16; i++) { | |||
| learnedCcs[i] = i; | |||
| } | |||
| learningId = -1; | |||
| midiOutput.reset(); | |||
| midiOutput.midi::Output::reset(); | |||
| } | |||
| void step() override { | |||
| @@ -67,11 +72,39 @@ struct CV_CC : Module { | |||
| return; | |||
| } | |||
| for (int n = 0; n < 16; n++) { | |||
| int value = (int) std::round(inputs[CC_INPUTS + n].getVoltage() / 10.f * 127); | |||
| for (int i = 0; i < 16; i++) { | |||
| int value = (int) std::round(inputs[CC_INPUTS + i].getVoltage() / 10.f * 127); | |||
| value = clamp(value, 0, 127); | |||
| midiOutput.setValue(value, n); | |||
| midiOutput.setValue(value, learnedCcs[i]); | |||
| } | |||
| } | |||
| json_t *dataToJson() override { | |||
| json_t *rootJ = json_object(); | |||
| json_t *ccsJ = json_array(); | |||
| for (int i = 0; i < 16; i++) { | |||
| json_array_append_new(ccsJ, json_integer(learnedCcs[i])); | |||
| } | |||
| json_object_set_new(rootJ, "ccs", ccsJ); | |||
| json_object_set_new(rootJ, "midi", midiOutput.toJson()); | |||
| return rootJ; | |||
| } | |||
| void dataFromJson(json_t *rootJ) override { | |||
| json_t *ccsJ = json_object_get(rootJ, "ccs"); | |||
| if (ccsJ) { | |||
| for (int i = 0; i < 16; 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) | |||
| midiOutput.fromJson(midiJ); | |||
| } | |||
| }; | |||
| @@ -103,11 +136,12 @@ struct CV_CCWidget : ModuleWidget { | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(31, 112)), module, CV_CC::CC_INPUTS + 14)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(43, 112)), module, CV_CC::CC_INPUTS + 15)); | |||
| MidiWidget *midiWidget = createWidget<MidiWidget>(mm2px(Vec(3.4, 14.839))); | |||
| typedef Grid16MidiWidget<CcChoice<CV_CC>> TMidiWidget; | |||
| TMidiWidget *midiWidget = createWidget<TMidiWidget>(mm2px(Vec(3.399621, 14.837339))); | |||
| midiWidget->box.size = mm2px(Vec(44, 54.667)); | |||
| if (module) | |||
| midiWidget->midiIO = &module->midiOutput; | |||
| // midiWidget->createGridChoices(); | |||
| midiWidget->setModule(module); | |||
| addChild(midiWidget); | |||
| } | |||
| }; | |||
| @@ -1,28 +1,25 @@ | |||
| #include "Core.hpp" | |||
| template <int N> | |||
| struct GateMidiOutput : midi::Output { | |||
| int vels[N]; | |||
| bool lastGates[N]; | |||
| int notes[N]; | |||
| int vels[128]; | |||
| bool lastGates[128]; | |||
| GateMidiOutput() { | |||
| reset(); | |||
| } | |||
| void reset() { | |||
| for (int n = 0; n < N; n++) { | |||
| vels[n] = 100; | |||
| lastGates[n] = false; | |||
| notes[n] = 60 + n; | |||
| for (int note = 0; note < 128; note++) { | |||
| vels[note] = 100; | |||
| lastGates[note] = false; | |||
| } | |||
| } | |||
| void panic() { | |||
| reset(); | |||
| // Send all note off commands | |||
| for (int note = 0; note <= 127; note++) { | |||
| for (int note = 0; note < 128; note++) { | |||
| // Note off | |||
| midi::Message m; | |||
| m.setStatus(0x8); | |||
| @@ -32,48 +29,28 @@ struct GateMidiOutput : midi::Output { | |||
| } | |||
| } | |||
| void setVelocity(int vel, int n) { | |||
| vels[n] = vel; | |||
| void setVelocity(int vel, int note) { | |||
| vels[note] = vel; | |||
| } | |||
| void setGate(bool gate, int n) { | |||
| if (gate && !lastGates[n]) { | |||
| void setGate(bool gate, int note) { | |||
| if (gate && !lastGates[note]) { | |||
| // Note on | |||
| midi::Message m; | |||
| m.setStatus(0x9); | |||
| m.setNote(notes[n]); | |||
| m.setValue(vels[n]); | |||
| m.setNote(note); | |||
| m.setValue(vels[note]); | |||
| sendMessage(m); | |||
| } | |||
| else if (!gate && lastGates[n]) { | |||
| else if (!gate && lastGates[note]) { | |||
| // Note off | |||
| midi::Message m; | |||
| m.setStatus(0x8); | |||
| m.setNote(notes[n]); | |||
| m.setValue(vels[n]); | |||
| m.setNote(note); | |||
| m.setValue(vels[note]); | |||
| sendMessage(m); | |||
| } | |||
| lastGates[n] = gate; | |||
| } | |||
| void setNote(int note, int n) { | |||
| if (note == notes[n]) | |||
| return; | |||
| if (lastGates[n]) { | |||
| // Note off | |||
| midi::Message m1; | |||
| m1.setStatus(0x8); | |||
| m1.setNote(notes[n]); | |||
| m1.setValue(vels[n]); | |||
| sendMessage(m1); | |||
| // Note on | |||
| midi::Message m2; | |||
| m2.setStatus(0x9); | |||
| m2.setNote(note); | |||
| m2.setValue(vels[n]); | |||
| sendMessage(m2); | |||
| } | |||
| notes[n] = note; | |||
| lastGates[note] = gate; | |||
| } | |||
| }; | |||
| @@ -93,27 +70,75 @@ struct CV_Gate : Module { | |||
| NUM_LIGHTS | |||
| }; | |||
| GateMidiOutput<16> midiOutput; | |||
| GateMidiOutput midiOutput; | |||
| bool velocityMode = false; | |||
| int learningId = -1; | |||
| uint8_t learnedNotes[16] = {}; | |||
| CV_Gate() { | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| onReset(); | |||
| } | |||
| void onReset() override { | |||
| for (int i = 0; i < 16; i++) { | |||
| learnedNotes[i] = i + 36; | |||
| } | |||
| learningId = -1; | |||
| midiOutput.reset(); | |||
| midiOutput.midi::Output::reset(); | |||
| } | |||
| void step() override { | |||
| for (int n = 0; n < 16; n++) { | |||
| for (int i = 0; i < 16; i++) { | |||
| int note = learnedNotes[i]; | |||
| if (velocityMode) { | |||
| int vel = (int) std::round(inputs[GATE_INPUTS + n].getVoltage() / 10.f * 127); | |||
| int vel = (int) std::round(inputs[GATE_INPUTS + i].getVoltage() / 10.f * 127); | |||
| vel = clamp(vel, 0, 127); | |||
| midiOutput.setVelocity(vel, n); | |||
| midiOutput.setGate(vel > 0, n); | |||
| midiOutput.setVelocity(vel, note); | |||
| midiOutput.setGate(vel > 0, note); | |||
| } | |||
| else { | |||
| bool gate = inputs[GATE_INPUTS + n].getVoltage() >= 1.f; | |||
| midiOutput.setVelocity(100, n); | |||
| midiOutput.setGate(gate, n); | |||
| bool gate = inputs[GATE_INPUTS + i].getVoltage() >= 1.f; | |||
| midiOutput.setVelocity(100, note); | |||
| midiOutput.setGate(gate, note); | |||
| } | |||
| } | |||
| } | |||
| json_t *dataToJson() override { | |||
| json_t *rootJ = json_object(); | |||
| json_t *notesJ = json_array(); | |||
| for (int i = 0; i < 16; i++) { | |||
| json_t *noteJ = json_integer(learnedNotes[i]); | |||
| json_array_append_new(notesJ, noteJ); | |||
| } | |||
| json_object_set_new(rootJ, "notes", notesJ); | |||
| json_object_set_new(rootJ, "velocity", json_boolean(velocityMode)); | |||
| json_object_set_new(rootJ, "midi", midiOutput.toJson()); | |||
| return rootJ; | |||
| } | |||
| void dataFromJson(json_t *rootJ) override { | |||
| json_t *notesJ = json_object_get(rootJ, "notes"); | |||
| if (notesJ) { | |||
| for (int i = 0; i < 16; i++) { | |||
| json_t *noteJ = json_array_get(notesJ, i); | |||
| if (noteJ) | |||
| learnedNotes[i] = json_integer_value(noteJ); | |||
| } | |||
| } | |||
| json_t *velocityJ = json_object_get(rootJ, "velocity"); | |||
| if (velocityJ) | |||
| velocityMode = json_boolean_value(velocityJ); | |||
| json_t *midiJ = json_object_get(rootJ, "midi"); | |||
| if (midiJ) | |||
| midiOutput.fromJson(midiJ); | |||
| } | |||
| }; | |||
| @@ -145,11 +170,12 @@ struct CV_GateWidget : ModuleWidget { | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(31, 112)), module, CV_Gate::GATE_INPUTS + 14)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(43, 112)), module, CV_Gate::GATE_INPUTS + 15)); | |||
| MidiWidget *midiWidget = createWidget<MidiWidget>(mm2px(Vec(3.4, 14.839))); | |||
| typedef Grid16MidiWidget<NoteChoice<CV_Gate>> TMidiWidget; | |||
| TMidiWidget *midiWidget = createWidget<TMidiWidget>(mm2px(Vec(3.399621, 14.837339))); | |||
| midiWidget->box.size = mm2px(Vec(44, 54.667)); | |||
| if (module) | |||
| midiWidget->midiIO = &module->midiOutput; | |||
| // midiWidget->createGridChoices(); | |||
| midiWidget->setModule(module); | |||
| addChild(midiWidget); | |||
| } | |||
| }; | |||
| @@ -233,6 +233,12 @@ struct CV_MIDI : Module { | |||
| CV_MIDI() { | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| onReset(); | |||
| } | |||
| void onReset() override { | |||
| midiOutput.reset(); | |||
| midiOutput.midi::Output::reset(); | |||
| } | |||
| void step() override { | |||
| @@ -292,6 +298,18 @@ struct CV_MIDI : Module { | |||
| bool cont = inputs[CONTINUE_INPUT].value >= 1.f; | |||
| midiOutput.setContinue(cont); | |||
| } | |||
| json_t *dataToJson() override { | |||
| json_t *rootJ = json_object(); | |||
| json_object_set_new(rootJ, "midi", midiOutput.toJson()); | |||
| return rootJ; | |||
| } | |||
| void dataFromJson(json_t *rootJ) override { | |||
| json_t *midiJ = json_object_get(rootJ, "midi"); | |||
| if (midiJ) | |||
| midiOutput.fromJson(midiJ); | |||
| } | |||
| }; | |||
| @@ -16,40 +16,36 @@ extern Model *modelBlank; | |||
| extern Model *modelNotes; | |||
| struct GridChoice : LedDisplayChoice { | |||
| virtual void setId(int id) {} | |||
| }; | |||
| template <class TChoice> | |||
| struct Grid16MidiWidget : MidiWidget { | |||
| LedDisplaySeparator *hSeparators[4]; | |||
| LedDisplaySeparator *vSeparators[4]; | |||
| GridChoice *gridChoices[4][4]; | |||
| TChoice *choices[4][4]; | |||
| void createGridChoices() { | |||
| Grid16MidiWidget() { | |||
| Vec pos = channelChoice->box.getBottomLeft(); | |||
| // Add vSeparators | |||
| for (int x = 1; x < 4; x++) { | |||
| vSeparators[x] = createWidget<LedDisplaySeparator>(pos); | |||
| addChild(vSeparators[x]); | |||
| } | |||
| // Add hSeparators and choice widgets | |||
| for (int y = 0; y < 4; y++) { | |||
| hSeparators[y] = createWidget<LedDisplaySeparator>(pos); | |||
| addChild(hSeparators[y]); | |||
| for (int x = 0; x < 4; x++) { | |||
| GridChoice *gridChoice = createGridChoice(); | |||
| assert(gridChoice); | |||
| gridChoice->box.pos = pos; | |||
| gridChoice->setId(4*y + x); | |||
| gridChoices[x][y] = gridChoice; | |||
| addChild(gridChoice); | |||
| choices[x][y] = new TChoice; | |||
| choices[x][y]->box.pos = pos; | |||
| choices[x][y]->setId(4*y + x); | |||
| addChild(choices[x][y]); | |||
| } | |||
| pos = gridChoices[0][y]->box.getBottomLeft(); | |||
| pos = choices[0][y]->box.getBottomLeft(); | |||
| } | |||
| for (int x = 1; x < 4; x++) { | |||
| vSeparators[x]->box.size.y = pos.y - vSeparators[x]->box.pos.y; | |||
| } | |||
| } | |||
| void step() override { | |||
| MidiWidget::step(); | |||
| for (int x = 1; x < 4; x++) { | |||
| @@ -58,10 +54,145 @@ struct Grid16MidiWidget : MidiWidget { | |||
| for (int y = 0; y < 4; y++) { | |||
| hSeparators[y]->box.size.x = box.size.x; | |||
| for (int x = 0; x < 4; x++) { | |||
| gridChoices[x][y]->box.size.x = box.size.x / 4; | |||
| gridChoices[x][y]->box.pos.x = box.size.x / 4 * x; | |||
| choices[x][y]->box.size.x = box.size.x / 4; | |||
| choices[x][y]->box.pos.x = box.size.x / 4 * x; | |||
| } | |||
| } | |||
| } | |||
| template <class TModule> | |||
| void setModule(TModule *module) { | |||
| for (int y = 0; y < 4; y++) { | |||
| for (int x = 0; x < 4; x++) { | |||
| choices[x][y]->module = module; | |||
| } | |||
| } | |||
| } | |||
| virtual GridChoice *createGridChoice() {return NULL;} | |||
| }; | |||
| }; | |||
| template <class TModule> | |||
| struct CcChoice : LedDisplayChoice { | |||
| TModule *module; | |||
| int id; | |||
| int focusCc; | |||
| CcChoice() { | |||
| box.size.y = mm2px(6.666); | |||
| textOffset.y -= 4; | |||
| } | |||
| void setId(int id) { | |||
| this->id = id; | |||
| } | |||
| void step() override { | |||
| if (!module) { | |||
| text = ""; | |||
| return; | |||
| } | |||
| if (module->learningId == id) { | |||
| if (0 <= focusCc) | |||
| text = string::f("%d", focusCc); | |||
| else | |||
| text = "LRN"; | |||
| color.a = 0.5; | |||
| } | |||
| else { | |||
| text = string::f("%d", module->learnedCcs[id]); | |||
| color.a = 1.0; | |||
| if (app()->event->selectedWidget == this) | |||
| app()->event->selectedWidget = NULL; | |||
| } | |||
| } | |||
| void onSelect(const event::Select &e) override { | |||
| e.consume(this); | |||
| if (!module) | |||
| return; | |||
| module->learningId = id; | |||
| focusCc = -1; | |||
| } | |||
| void onDeselect(const event::Deselect &e) override { | |||
| if (!module) | |||
| return; | |||
| if (0 <= focusCc && focusCc < 128) { | |||
| module->learnedCcs[id] = focusCc; | |||
| } | |||
| module->learningId = -1; | |||
| } | |||
| void onSelectText(const event::SelectText &e) override { | |||
| int c = e.codepoint - '0'; | |||
| if (0 <= c && c <= 9) { | |||
| if (focusCc < 0) | |||
| focusCc = 0; | |||
| focusCc = focusCc * 10 + c; | |||
| } | |||
| if (focusCc >= 128) | |||
| focusCc = 0; | |||
| e.consume(this); | |||
| } | |||
| void onSelectKey(const event::SelectKey &e) override { | |||
| if ((e.key == GLFW_KEY_ENTER || e.key == GLFW_KEY_KP_ENTER) && e.action == GLFW_PRESS && (e.mods & WINDOW_MOD_MASK) == 0) { | |||
| event::Deselect eDeselect; | |||
| onDeselect(eDeselect); | |||
| app()->event->selectedWidget = NULL; | |||
| e.consume(this); | |||
| } | |||
| } | |||
| }; | |||
| template <class TModule> | |||
| struct NoteChoice : LedDisplayChoice { | |||
| TModule *module; | |||
| int id; | |||
| NoteChoice() { | |||
| box.size.y = mm2px(6.666); | |||
| textOffset.y -= 4; | |||
| textOffset.x -= 4; | |||
| } | |||
| void setId(int id) { | |||
| this->id = id; | |||
| } | |||
| void step() override { | |||
| if (!module) | |||
| return; | |||
| if (module->learningId == id) { | |||
| text = "LRN"; | |||
| color.a = 0.5; | |||
| } | |||
| else { | |||
| uint8_t note = module->learnedNotes[id]; | |||
| static const char *noteNames[] = { | |||
| "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" | |||
| }; | |||
| int oct = note / 12 - 1; | |||
| int semi = note % 12; | |||
| text = string::f("%s%d", noteNames[semi], oct); | |||
| color.a = 1.0; | |||
| if (app()->event->selectedWidget == this) | |||
| app()->event->selectedWidget = NULL; | |||
| } | |||
| } | |||
| void onSelect(const event::Select &e) override { | |||
| e.consume(this); | |||
| if (!module) | |||
| return; | |||
| module->learningId = id; | |||
| } | |||
| void onDeselect(const event::Deselect &e) override { | |||
| if (!module) | |||
| return; | |||
| module->learningId = -1; | |||
| } | |||
| }; | |||
| @@ -1,5 +1,4 @@ | |||
| #include "Core.hpp" | |||
| #include "midi.hpp" | |||
| struct MIDI_CC : Module { | |||
| @@ -20,7 +19,7 @@ struct MIDI_CC : Module { | |||
| midi::InputQueue midiInput; | |||
| int8_t values[128]; | |||
| int learningId = -1; | |||
| int ccs[16] = {}; | |||
| int learnedCcs[16] = {}; | |||
| dsp::ExponentialFilter valueFilters[16]; | |||
| int8_t lastValues[16] = {}; | |||
| @@ -34,7 +33,7 @@ struct MIDI_CC : Module { | |||
| values[i] = 0; | |||
| } | |||
| for (int i = 0; i < 16; i++) { | |||
| ccs[i] = i; | |||
| learnedCcs[i] = i; | |||
| } | |||
| learningId = -1; | |||
| midiInput.reset(); | |||
| @@ -51,7 +50,7 @@ struct MIDI_CC : Module { | |||
| if (!outputs[CC_OUTPUT + i].active) | |||
| continue; | |||
| int cc = ccs[i]; | |||
| int cc = learnedCcs[i]; | |||
| float value = rescale(values[cc], 0, 127, 0.f, 10.f); | |||
| valueFilters[i].lambda = lambda; | |||
| @@ -77,7 +76,7 @@ struct MIDI_CC : Module { | |||
| uint8_t cc = msg.getNote(); | |||
| // Learn | |||
| if (learningId >= 0 && values[cc] != msg.data2) { | |||
| ccs[learningId] = cc; | |||
| learnedCcs[learningId] = cc; | |||
| learningId = -1; | |||
| } | |||
| // Allow CC to be negative if the 8th bit is set. | |||
| @@ -93,7 +92,7 @@ struct MIDI_CC : Module { | |||
| json_t *ccsJ = json_array(); | |||
| for (int i = 0; i < 16; i++) { | |||
| json_array_append_new(ccsJ, json_integer(ccs[i])); | |||
| json_array_append_new(ccsJ, json_integer(learnedCcs[i])); | |||
| } | |||
| json_object_set_new(rootJ, "ccs", ccsJ); | |||
| @@ -114,7 +113,7 @@ struct MIDI_CC : Module { | |||
| for (int i = 0; i < 16; i++) { | |||
| json_t *ccJ = json_array_get(ccsJ, i); | |||
| if (ccJ) | |||
| ccs[i] = json_integer_value(ccJ); | |||
| learnedCcs[i] = json_integer_value(ccJ); | |||
| } | |||
| } | |||
| @@ -135,90 +134,6 @@ struct MIDI_CC : Module { | |||
| }; | |||
| struct MidiCcChoice : GridChoice { | |||
| MIDI_CC *module; | |||
| int id; | |||
| int focusCc; | |||
| MidiCcChoice() { | |||
| box.size.y = mm2px(6.666); | |||
| textOffset.y -= 4; | |||
| } | |||
| void setId(int id) override { | |||
| this->id = id; | |||
| } | |||
| void step() override { | |||
| if (!module) { | |||
| text = ""; | |||
| return; | |||
| } | |||
| if (module->learningId == id) { | |||
| if (0 <= focusCc) | |||
| text = string::f("%d", focusCc); | |||
| else | |||
| text = "LRN"; | |||
| color.a = 0.5; | |||
| } | |||
| else { | |||
| text = string::f("%d", module->ccs[id]); | |||
| color.a = 1.0; | |||
| if (app()->event->selectedWidget == this) | |||
| app()->event->selectedWidget = NULL; | |||
| } | |||
| } | |||
| void onSelect(const event::Select &e) override { | |||
| e.consume(this); | |||
| if (!module) | |||
| return; | |||
| module->learningId = id; | |||
| focusCc = -1; | |||
| } | |||
| void onDeselect(const event::Deselect &e) override { | |||
| if (!module) | |||
| return; | |||
| if (0 <= focusCc && focusCc < 128) { | |||
| module->ccs[id] = focusCc; | |||
| } | |||
| module->learningId = -1; | |||
| } | |||
| void onSelectText(const event::SelectText &e) override { | |||
| char c = e.codepoint; | |||
| if ('0' <= c && c <= '9') { | |||
| if (focusCc < 0) | |||
| focusCc = 0; | |||
| focusCc = focusCc * 10 + (c - '0'); | |||
| } | |||
| e.consume(this); | |||
| } | |||
| void onSelectKey(const event::SelectKey &e) override { | |||
| if (app()->event->selectedWidget == this) { | |||
| if (e.action == GLFW_PRESS && (e.key == GLFW_KEY_ENTER || e.key == GLFW_KEY_KP_ENTER)) { | |||
| event::Deselect eDeselect; | |||
| onDeselect(eDeselect); | |||
| app()->event->selectedWidget = NULL; | |||
| e.consume(this); | |||
| } | |||
| } | |||
| } | |||
| }; | |||
| struct MidiCcWidget : Grid16MidiWidget { | |||
| MIDI_CC *module; | |||
| GridChoice *createGridChoice() override { | |||
| MidiCcChoice *gridChoice = new MidiCcChoice; | |||
| gridChoice->module = module; | |||
| return gridChoice; | |||
| } | |||
| }; | |||
| struct MIDI_CCWidget : ModuleWidget { | |||
| MIDI_CCWidget(MIDI_CC *module) { | |||
| setModule(module); | |||
| @@ -246,12 +161,12 @@ struct MIDI_CCWidget : ModuleWidget { | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(27.09498, 108.14429)), module, MIDI_CC::CC_OUTPUT + 14)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.693932, 108.14429)), module, MIDI_CC::CC_OUTPUT + 15)); | |||
| MidiCcWidget *midiWidget = createWidget<MidiCcWidget>(mm2px(Vec(3.399621, 14.837339))); | |||
| midiWidget->module = module; | |||
| typedef Grid16MidiWidget<CcChoice<MIDI_CC>> TMidiWidget; | |||
| TMidiWidget *midiWidget = createWidget<TMidiWidget>(mm2px(Vec(3.399621, 14.837339))); | |||
| midiWidget->box.size = mm2px(Vec(44, 54.667)); | |||
| if (module) | |||
| midiWidget->midiIO = &module->midiInput; | |||
| midiWidget->createGridChoices(); | |||
| midiWidget->setModule(module); | |||
| addChild(midiWidget); | |||
| } | |||
| }; | |||
| @@ -1,5 +1,4 @@ | |||
| #include "Core.hpp" | |||
| #include "midi.hpp" | |||
| #include <algorithm> | |||
| @@ -1,6 +1,4 @@ | |||
| #include "Core.hpp" | |||
| #include "midi.hpp" | |||
| #include "event.hpp" | |||
| struct MIDI_Gate : Module { | |||
| @@ -25,7 +23,7 @@ struct MIDI_Gate : Module { | |||
| uint8_t velocities[16]; | |||
| int learningId = -1; | |||
| uint8_t learnedNotes[16] = {}; | |||
| bool velocity = false; | |||
| bool velocityMode = false; | |||
| MIDI_Gate() { | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| @@ -76,7 +74,7 @@ struct MIDI_Gate : Module { | |||
| for (int i = 0; i < 16; i++) { | |||
| if (gateTimes[i] > 0.f) { | |||
| outputs[TRIG_OUTPUT + i].setVoltage(velocity ? rescale(velocities[i], 0, 127, 0.f, 10.f) : 10.f); | |||
| outputs[TRIG_OUTPUT + i].setVoltage(velocityMode ? rescale(velocities[i], 0, 127, 0.f, 10.f) : 10.f); | |||
| // If the gate is off, wait 1 ms before turning the pulse off. | |||
| // This avoids drum controllers sending a pulse with 0 ms duration. | |||
| if (!gates[i]) { | |||
| @@ -119,8 +117,9 @@ struct MIDI_Gate : Module { | |||
| } | |||
| json_object_set_new(rootJ, "notes", notesJ); | |||
| json_object_set_new(rootJ, "velocity", json_boolean(velocityMode)); | |||
| json_object_set_new(rootJ, "midi", midiInput.toJson()); | |||
| json_object_set_new(rootJ, "velocity", json_boolean(velocity)); | |||
| return rootJ; | |||
| } | |||
| @@ -134,74 +133,13 @@ struct MIDI_Gate : Module { | |||
| } | |||
| } | |||
| json_t *midiJ = json_object_get(rootJ, "midi"); | |||
| if (midiJ) | |||
| midiInput.fromJson(midiJ); | |||
| json_t *velocityJ = json_object_get(rootJ, "velocity"); | |||
| if (velocityJ) | |||
| velocity = json_boolean_value(velocityJ); | |||
| } | |||
| }; | |||
| struct MidiTrigChoice : GridChoice { | |||
| MIDI_Gate *module; | |||
| int id; | |||
| MidiTrigChoice() { | |||
| box.size.y = mm2px(6.666); | |||
| textOffset.y -= 4; | |||
| textOffset.x -= 4; | |||
| } | |||
| void setId(int id) override { | |||
| this->id = id; | |||
| } | |||
| void step() override { | |||
| if (!module) | |||
| return; | |||
| if (module->learningId == id) { | |||
| text = "LRN"; | |||
| color.a = 0.5; | |||
| } | |||
| else { | |||
| uint8_t note = module->learnedNotes[id]; | |||
| static const char *noteNames[] = { | |||
| "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" | |||
| }; | |||
| int oct = note / 12 - 1; | |||
| int semi = note % 12; | |||
| text = string::f("%s%d", noteNames[semi], oct); | |||
| color.a = 1.0; | |||
| if (app()->event->selectedWidget == this) | |||
| app()->event->selectedWidget = NULL; | |||
| } | |||
| } | |||
| void onSelect(const event::Select &e) override { | |||
| e.consume(this); | |||
| if (!module) | |||
| return; | |||
| module->learningId = id; | |||
| } | |||
| velocityMode = json_boolean_value(velocityJ); | |||
| void onDeselect(const event::Deselect &e) override { | |||
| if (!module) | |||
| return; | |||
| module->learningId = -1; | |||
| } | |||
| }; | |||
| struct MidiTrigWidget : Grid16MidiWidget { | |||
| MIDI_Gate *module; | |||
| GridChoice *createGridChoice() override { | |||
| MidiTrigChoice *gridChoice = new MidiTrigChoice; | |||
| gridChoice->module = module; | |||
| return gridChoice; | |||
| json_t *midiJ = json_object_get(rootJ, "midi"); | |||
| if (midiJ) | |||
| midiInput.fromJson(midiJ); | |||
| } | |||
| }; | |||
| @@ -233,12 +171,12 @@ struct MIDI_GateWidget : ModuleWidget { | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(27.09498, 108.14429)), module, MIDI_Gate::TRIG_OUTPUT + 14)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.693932, 108.14429)), module, MIDI_Gate::TRIG_OUTPUT + 15)); | |||
| MidiTrigWidget *midiWidget = createWidget<MidiTrigWidget>(mm2px(Vec(3.399621, 14.837339))); | |||
| midiWidget->module = module; | |||
| typedef Grid16MidiWidget<NoteChoice<MIDI_Gate>> TMidiWidget; | |||
| TMidiWidget *midiWidget = createWidget<TMidiWidget>(mm2px(Vec(3.399621, 14.837339))); | |||
| midiWidget->box.size = mm2px(Vec(44, 54.667)); | |||
| if (module) | |||
| midiWidget->midiIO = &module->midiInput; | |||
| midiWidget->createGridChoices(); | |||
| midiWidget->setModule(module); | |||
| addChild(midiWidget); | |||
| } | |||
| @@ -248,12 +186,12 @@ struct MIDI_GateWidget : ModuleWidget { | |||
| struct VelocityItem : MenuItem { | |||
| MIDI_Gate *module; | |||
| void onAction(const event::Action &e) override { | |||
| module->velocity ^= true; | |||
| module->velocityMode ^= true; | |||
| } | |||
| }; | |||
| menu->addChild(new MenuEntry); | |||
| VelocityItem *velocityItem = createMenuItem<VelocityItem>("Velocity", CHECKMARK(module->velocity)); | |||
| VelocityItem *velocityItem = createMenuItem<VelocityItem>("Velocity", CHECKMARK(module->velocityMode)); | |||
| velocityItem->module = module; | |||
| menu->addChild(velocityItem); | |||
| } | |||