| @@ -35,7 +35,10 @@ struct MIDI_CV : Module { | |||
| midi::InputQueue midiInput; | |||
| /** Number of semitones to bend up/down by pitch wheel */ | |||
| float pwRange; | |||
| bool smooth; | |||
| int clockDivision; | |||
| int channels; | |||
| enum PolyMode { | |||
| ROTATE_MODE, | |||
| @@ -47,7 +50,6 @@ struct MIDI_CV : Module { | |||
| PolyMode polyMode; | |||
| uint32_t clock = 0; | |||
| int clockDivision; | |||
| bool pedal; | |||
| // Indexed by channel | |||
| @@ -102,6 +104,7 @@ struct MIDI_CV : Module { | |||
| smooth = true; | |||
| channels = 1; | |||
| polyMode = ROTATE_MODE; | |||
| pwRange = 2; | |||
| clockDivision = 24; | |||
| panic(); | |||
| midiInput.reset(); | |||
| @@ -130,30 +133,19 @@ struct MIDI_CV : Module { | |||
| processMessage(msg); | |||
| } | |||
| outputs[PITCH_OUTPUT].setChannels(channels); | |||
| outputs[GATE_OUTPUT].setChannels(channels); | |||
| outputs[VELOCITY_OUTPUT].setChannels(channels); | |||
| outputs[AFTERTOUCH_OUTPUT].setChannels(channels); | |||
| outputs[RETRIGGER_OUTPUT].setChannels(channels); | |||
| for (int c = 0; c < channels; c++) { | |||
| outputs[PITCH_OUTPUT].setVoltage((notes[c] - 60.f) / 12.f, c); | |||
| outputs[GATE_OUTPUT].setVoltage(gates[c] ? 10.f : 0.f, c); | |||
| outputs[VELOCITY_OUTPUT].setVoltage(rescale(velocities[c], 0, 127, 0.f, 10.f), c); | |||
| outputs[AFTERTOUCH_OUTPUT].setVoltage(rescale(aftertouches[c], 0, 127, 0.f, 10.f), c); | |||
| outputs[RETRIGGER_OUTPUT].setVoltage(retriggerPulses[c].process(args.sampleTime) ? 10.f : 0.f, c); | |||
| } | |||
| // Set pitch and mod wheel | |||
| // Set pitch wheel and mod wheel | |||
| int wheelChannels = (polyMode == MPE_MODE) ? 16 : 1; | |||
| float pwValues[16] = {}; | |||
| outputs[PW_OUTPUT].setChannels(wheelChannels); | |||
| outputs[MOD_OUTPUT].setChannels(wheelChannels); | |||
| for (int c = 0; c < wheelChannels; c++) { | |||
| float pw = ((int) pws[c] - 8192) / 8191.f; | |||
| float pw = (int16_t(pws[c]) - 8192) / 8191.f; | |||
| pw = clamp(pw, -1.f, 1.f); | |||
| if (smooth) | |||
| pw = pwFilters[c].process(args.sampleTime, pw); | |||
| else | |||
| pwFilters[c].out = pw; | |||
| pwValues[c] = pw; | |||
| outputs[PW_OUTPUT].setVoltage(pw * 5.f); | |||
| float mod = mods[c] / 127.f; | |||
| @@ -165,6 +157,23 @@ struct MIDI_CV : Module { | |||
| outputs[MOD_OUTPUT].setVoltage(mod * 10.f); | |||
| } | |||
| // Set note outputs | |||
| outputs[PITCH_OUTPUT].setChannels(channels); | |||
| outputs[GATE_OUTPUT].setChannels(channels); | |||
| outputs[VELOCITY_OUTPUT].setChannels(channels); | |||
| outputs[AFTERTOUCH_OUTPUT].setChannels(channels); | |||
| outputs[RETRIGGER_OUTPUT].setChannels(channels); | |||
| for (int c = 0; c < channels; c++) { | |||
| float pw = pwValues[(polyMode == MPE_MODE) ? c : 0]; | |||
| float pitch = (notes[c] - 60.f + pw * pwRange) / 12.f; | |||
| outputs[PITCH_OUTPUT].setVoltage(pitch, c); | |||
| outputs[GATE_OUTPUT].setVoltage(gates[c] ? 10.f : 0.f, c); | |||
| outputs[VELOCITY_OUTPUT].setVoltage(rescale(velocities[c], 0, 127, 0.f, 10.f), c); | |||
| outputs[AFTERTOUCH_OUTPUT].setVoltage(rescale(aftertouches[c], 0, 127, 0.f, 10.f), c); | |||
| outputs[RETRIGGER_OUTPUT].setVoltage(retriggerPulses[c].process(args.sampleTime) ? 10.f : 0.f, c); | |||
| } | |||
| // Set clock and transport outputs | |||
| outputs[CLOCK_OUTPUT].setVoltage(clockPulse.process(args.sampleTime) ? 10.f : 0.f); | |||
| outputs[CLOCK_DIV_OUTPUT].setVoltage(clockDividerPulse.process(args.sampleTime) ? 10.f : 0.f); | |||
| outputs[START_OUTPUT].setVoltage(startPulse.process(args.sampleTime) ? 10.f : 0.f); | |||
| @@ -426,6 +435,7 @@ struct MIDI_CV : Module { | |||
| json_t* dataToJson() override { | |||
| json_t* rootJ = json_object(); | |||
| json_object_set_new(rootJ, "pwRange", json_real(pwRange)); | |||
| json_object_set_new(rootJ, "smooth", json_boolean(smooth)); | |||
| json_object_set_new(rootJ, "channels", json_integer(channels)); | |||
| json_object_set_new(rootJ, "polyMode", json_integer(polyMode)); | |||
| @@ -440,6 +450,13 @@ struct MIDI_CV : Module { | |||
| } | |||
| void dataFromJson(json_t* rootJ) override { | |||
| json_t* pwRangeJ = json_object_get(rootJ, "pwRange"); | |||
| if (pwRangeJ) | |||
| pwRange = json_number_value(pwRangeJ); | |||
| // For backwards compatibility, set to 0 if undefined in JSON. | |||
| else | |||
| pwRange = 0; | |||
| json_t* smoothJ = json_object_get(rootJ, "smooth"); | |||
| if (smoothJ) | |||
| smooth = json_boolean_value(smoothJ); | |||
| @@ -510,6 +527,16 @@ struct MIDI_CVWidget : ModuleWidget { | |||
| menu->addChild(new MenuSeparator); | |||
| static const std::vector<float> pwRanges = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 24, 36, 48}; | |||
| menu->addChild(createSubmenuItem("Pitch bend range", string::f("%g", module->pwRange), [=](Menu* menu) { | |||
| for (size_t i = 0; i < pwRanges.size(); i++) { | |||
| menu->addChild(createCheckMenuItem(string::f("%g", pwRanges[i]), "", | |||
| [=]() {return module->pwRange == pwRanges[i];}, | |||
| [=]() {module->pwRange = pwRanges[i];} | |||
| )); | |||
| } | |||
| })); | |||
| menu->addChild(createBoolPtrMenuItem("Smooth pitch/mod wheel", "", &module->smooth)); | |||
| static const std::vector<int> clockDivisions = {24 * 4, 24 * 2, 24, 24 / 2, 24 / 4, 24 / 8, 2, 1}; | |||