| @@ -302,179 +302,234 @@ struct Davies1900hLargeRedKnob : Davies1900hKnob { | |||
| struct Rogan : app::SvgKnob { | |||
| widget::SvgWidget* bg; | |||
| widget::SvgWidget* fg; | |||
| Rogan() { | |||
| minAngle = -0.83 * M_PI; | |||
| maxAngle = 0.83 * M_PI; | |||
| bg = new widget::SvgWidget; | |||
| fb->addChildBelow(bg, tw); | |||
| fg = new widget::SvgWidget; | |||
| fb->addChildAbove(fg, tw); | |||
| } | |||
| }; | |||
| struct Rogan6PSWhite : Rogan { | |||
| Rogan6PSWhite() { | |||
| setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan6PSWhite.svg"))); | |||
| bg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-bg.svg"))); | |||
| fg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-fg.svg"))); | |||
| } | |||
| }; | |||
| struct Rogan5PSGray : Rogan { | |||
| Rogan5PSGray() { | |||
| setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan5PSGray.svg"))); | |||
| bg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-bg.svg"))); | |||
| fg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-fg.svg"))); | |||
| } | |||
| }; | |||
| struct Rogan3PSBlue : Rogan { | |||
| Rogan3PSBlue() { | |||
| setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSBlue.svg"))); | |||
| bg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-bg.svg"))); | |||
| fg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-fg.svg"))); | |||
| } | |||
| }; | |||
| struct Rogan3PSRed : Rogan { | |||
| Rogan3PSRed() { | |||
| setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSRed.svg"))); | |||
| bg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-bg.svg"))); | |||
| fg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-fg.svg"))); | |||
| } | |||
| }; | |||
| struct Rogan3PSGreen : Rogan { | |||
| Rogan3PSGreen() { | |||
| setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSGreen.svg"))); | |||
| bg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-bg.svg"))); | |||
| fg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-fg.svg"))); | |||
| } | |||
| }; | |||
| struct Rogan3PSWhite : Rogan { | |||
| Rogan3PSWhite() { | |||
| setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite.svg"))); | |||
| widget::SvgWidget* bg = new widget::SvgWidget; | |||
| bg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-bg.svg"))); | |||
| fb->addChildBelow(bg, tw); | |||
| widget::SvgWidget* fg = new widget::SvgWidget; | |||
| fg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-fg.svg"))); | |||
| fb->addChildAbove(fg, tw); | |||
| } | |||
| }; | |||
| struct Rogan3PBlue : Rogan { | |||
| Rogan3PBlue() { | |||
| setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PBlue.svg"))); | |||
| bg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-bg.svg"))); | |||
| fg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-fg.svg"))); | |||
| } | |||
| }; | |||
| struct Rogan3PRed : Rogan { | |||
| Rogan3PRed() { | |||
| setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PRed.svg"))); | |||
| bg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-bg.svg"))); | |||
| fg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-fg.svg"))); | |||
| } | |||
| }; | |||
| struct Rogan3PGreen : Rogan { | |||
| Rogan3PGreen() { | |||
| setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PGreen.svg"))); | |||
| bg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-bg.svg"))); | |||
| fg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-fg.svg"))); | |||
| } | |||
| }; | |||
| struct Rogan3PWhite : Rogan { | |||
| Rogan3PWhite() { | |||
| setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PWhite.svg"))); | |||
| bg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-bg.svg"))); | |||
| fg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-fg.svg"))); | |||
| } | |||
| }; | |||
| struct Rogan2SGray : Rogan { | |||
| Rogan2SGray() { | |||
| setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan2SGray.svg"))); | |||
| bg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-bg.svg"))); | |||
| fg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-fg.svg"))); | |||
| } | |||
| }; | |||
| struct Rogan2PSBlue : Rogan { | |||
| Rogan2PSBlue() { | |||
| setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan2PSBlue.svg"))); | |||
| bg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-bg.svg"))); | |||
| fg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-fg.svg"))); | |||
| } | |||
| }; | |||
| struct Rogan2PSRed : Rogan { | |||
| Rogan2PSRed() { | |||
| setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan2PSRed.svg"))); | |||
| bg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-bg.svg"))); | |||
| fg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-fg.svg"))); | |||
| } | |||
| }; | |||
| struct Rogan2PSGreen : Rogan { | |||
| Rogan2PSGreen() { | |||
| setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan2PSGreen.svg"))); | |||
| bg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-bg.svg"))); | |||
| fg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-fg.svg"))); | |||
| } | |||
| }; | |||
| struct Rogan2PSWhite : Rogan { | |||
| Rogan2PSWhite() { | |||
| setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan2PSWhite.svg"))); | |||
| bg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-bg.svg"))); | |||
| fg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-fg.svg"))); | |||
| } | |||
| }; | |||
| struct Rogan2PBlue : Rogan { | |||
| Rogan2PBlue() { | |||
| setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan2PBlue.svg"))); | |||
| bg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-bg.svg"))); | |||
| fg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-fg.svg"))); | |||
| } | |||
| }; | |||
| struct Rogan2PRed : Rogan { | |||
| Rogan2PRed() { | |||
| setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan2PRed.svg"))); | |||
| bg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-bg.svg"))); | |||
| fg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-fg.svg"))); | |||
| } | |||
| }; | |||
| struct Rogan2PGreen : Rogan { | |||
| Rogan2PGreen() { | |||
| setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan2PGreen.svg"))); | |||
| bg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-bg.svg"))); | |||
| fg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-fg.svg"))); | |||
| } | |||
| }; | |||
| struct Rogan2PWhite : Rogan { | |||
| Rogan2PWhite() { | |||
| setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan2PWhite.svg"))); | |||
| bg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-bg.svg"))); | |||
| fg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-fg.svg"))); | |||
| } | |||
| }; | |||
| struct Rogan1PSBlue : Rogan { | |||
| Rogan1PSBlue() { | |||
| setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan1PSBlue.svg"))); | |||
| bg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-bg.svg"))); | |||
| fg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-fg.svg"))); | |||
| } | |||
| }; | |||
| struct Rogan1PSRed : Rogan { | |||
| Rogan1PSRed() { | |||
| setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan1PSRed.svg"))); | |||
| bg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-bg.svg"))); | |||
| fg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-fg.svg"))); | |||
| } | |||
| }; | |||
| struct Rogan1PSGreen : Rogan { | |||
| Rogan1PSGreen() { | |||
| setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan1PSGreen.svg"))); | |||
| bg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-bg.svg"))); | |||
| fg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-fg.svg"))); | |||
| } | |||
| }; | |||
| struct Rogan1PSWhite : Rogan { | |||
| Rogan1PSWhite() { | |||
| setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan1PSWhite.svg"))); | |||
| bg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-bg.svg"))); | |||
| fg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-fg.svg"))); | |||
| } | |||
| }; | |||
| struct Rogan1PBlue : Rogan { | |||
| Rogan1PBlue() { | |||
| setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan1PBlue.svg"))); | |||
| bg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-bg.svg"))); | |||
| fg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-fg.svg"))); | |||
| } | |||
| }; | |||
| struct Rogan1PRed : Rogan { | |||
| Rogan1PRed() { | |||
| setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan1PRed.svg"))); | |||
| bg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-bg.svg"))); | |||
| fg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-fg.svg"))); | |||
| } | |||
| }; | |||
| struct Rogan1PGreen : Rogan { | |||
| Rogan1PGreen() { | |||
| setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan1PGreen.svg"))); | |||
| bg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-bg.svg"))); | |||
| fg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-fg.svg"))); | |||
| } | |||
| }; | |||
| struct Rogan1PWhite : Rogan { | |||
| Rogan1PWhite() { | |||
| setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan1PWhite.svg"))); | |||
| bg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-bg.svg"))); | |||
| fg->setSvg(Svg::load(asset::system("res/ComponentLibrary/Rogan3PSWhite-fg.svg"))); | |||
| } | |||
| }; | |||
| @@ -21,6 +21,7 @@ struct MIDI_CC : Module { | |||
| }; | |||
| midi::InputQueue midiInput; | |||
| /** [cc][channel] */ | |||
| int8_t ccValues[128][16]; | |||
| /** When LSB is enabled for CC 0-31, the MSB is stored here until the LSB is received. | |||
| @@ -33,7 +34,7 @@ struct MIDI_CC : Module { | |||
| dsp::ExponentialFilter valueFilters[16][16]; | |||
| bool smooth; | |||
| bool mpeMode; | |||
| bool lsbEnabled; | |||
| bool lsbMode; | |||
| MIDI_CC() { | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| @@ -66,7 +67,7 @@ struct MIDI_CC : Module { | |||
| midiInput.reset(); | |||
| smooth = true; | |||
| mpeMode = false; | |||
| lsbEnabled = false; | |||
| lsbMode = false; | |||
| } | |||
| void process(const ProcessArgs& args) override { | |||
| @@ -80,6 +81,7 @@ struct MIDI_CC : Module { | |||
| } | |||
| int channels = mpeMode ? 16 : 1; | |||
| for (int i = 0; i < 16; i++) { | |||
| if (!outputs[CC_OUTPUT + i].isConnected()) | |||
| continue; | |||
| @@ -89,7 +91,7 @@ struct MIDI_CC : Module { | |||
| for (int c = 0; c < channels; c++) { | |||
| int16_t cellValue = int16_t(ccValues[cc][c]) * 128; | |||
| if (lsbEnabled && cc < 32) | |||
| if (lsbMode && cc < 32) | |||
| cellValue += ccValues[cc + 32][c]; | |||
| // Maximum value for 14-bit CC should be MSB=127 LSB=0, not MSB=127 LSB=127, because this is the maximum value that 7-bit controllers can send. | |||
| float value = float(cellValue) / (128 * 127); | |||
| @@ -135,11 +137,11 @@ struct MIDI_CC : Module { | |||
| learningId = -1; | |||
| } | |||
| if (lsbEnabled && cc < 32) { | |||
| if (lsbMode && cc < 32) { | |||
| // Don't set MSB yet. Wait for LSB to be received. | |||
| msbValues[cc][c] = value; | |||
| } | |||
| else if (lsbEnabled && 32 <= cc && cc < 64) { | |||
| else if (lsbMode && 32 <= cc && cc < 64) { | |||
| // Apply MSB when LSB is received | |||
| ccValues[cc - 32][c] = msbValues[cc - 32][c]; | |||
| ccValues[cc][c] = value; | |||
| @@ -170,7 +172,7 @@ struct MIDI_CC : Module { | |||
| json_object_set_new(rootJ, "smooth", json_boolean(smooth)); | |||
| json_object_set_new(rootJ, "mpeMode", json_boolean(mpeMode)); | |||
| json_object_set_new(rootJ, "lsbEnabled", json_boolean(lsbEnabled)); | |||
| json_object_set_new(rootJ, "lsbMode", json_boolean(lsbMode)); | |||
| return rootJ; | |||
| } | |||
| @@ -206,9 +208,9 @@ struct MIDI_CC : Module { | |||
| if (mpeModeJ) | |||
| mpeMode = json_boolean_value(mpeModeJ); | |||
| json_t* lsbEnabledJ = json_object_get(rootJ, "lsbEnabled"); | |||
| json_t* lsbEnabledJ = json_object_get(rootJ, "lsbMode"); | |||
| if (lsbEnabledJ) | |||
| lsbEnabled = json_boolean_value(lsbEnabledJ); | |||
| lsbMode = json_boolean_value(lsbEnabledJ); | |||
| } | |||
| }; | |||
| @@ -259,7 +261,6 @@ struct MIDI_CCWidget : ModuleWidget { | |||
| module->smooth ^= true; | |||
| } | |||
| }; | |||
| SmoothItem* smoothItem = new SmoothItem; | |||
| smoothItem->text = "Smooth CC"; | |||
| smoothItem->rightText = CHECKMARK(module->smooth); | |||
| @@ -272,25 +273,23 @@ struct MIDI_CCWidget : ModuleWidget { | |||
| module->mpeMode ^= true; | |||
| } | |||
| }; | |||
| MpeModeItem* mpeModeItem = new MpeModeItem; | |||
| mpeModeItem->text = "MPE mode"; | |||
| mpeModeItem->rightText = CHECKMARK(module->mpeMode); | |||
| mpeModeItem->module = module; | |||
| menu->addChild(mpeModeItem); | |||
| struct LSBItem : MenuItem { | |||
| struct LsbModeItem : MenuItem { | |||
| MIDI_CC* module; | |||
| void onAction(const ActionEvent& e) override { | |||
| module->lsbEnabled ^= true; | |||
| module->lsbMode ^= true; | |||
| } | |||
| }; | |||
| LSBItem* highResolutionItem = new LSBItem; | |||
| highResolutionItem->text = "CC 0-31 controls are 14-bit"; | |||
| highResolutionItem->rightText = CHECKMARK(module->lsbEnabled); | |||
| highResolutionItem->module = module; | |||
| menu->addChild(highResolutionItem); | |||
| LsbModeItem* lsbItem = new LsbModeItem; | |||
| lsbItem->text = "CC 0-31 controls are 14-bit"; | |||
| lsbItem->rightText = CHECKMARK(module->lsbMode); | |||
| lsbItem->module = module; | |||
| menu->addChild(lsbItem); | |||
| } | |||
| }; | |||
| @@ -15,11 +15,11 @@ struct MIDI_CV : Module { | |||
| NUM_INPUTS | |||
| }; | |||
| enum OutputIds { | |||
| CV_OUTPUT, | |||
| PITCH_OUTPUT, | |||
| GATE_OUTPUT, | |||
| VELOCITY_OUTPUT, | |||
| AFTERTOUCH_OUTPUT, | |||
| PITCH_OUTPUT, | |||
| PW_OUTPUT, | |||
| MOD_OUTPUT, | |||
| RETRIGGER_OUTPUT, | |||
| CLOCK_OUTPUT, | |||
| @@ -59,10 +59,14 @@ struct MIDI_CV : Module { | |||
| int rotateIndex; | |||
| // 16 channels for MPE. When MPE is disabled, only the first channel is used. | |||
| uint16_t pitches[16]; | |||
| /** Pitch wheel. | |||
| When MPE is disabled, only the first channel is used. | |||
| [channel] | |||
| */ | |||
| uint16_t pws[16]; | |||
| /** [channel] */ | |||
| uint8_t mods[16]; | |||
| dsp::ExponentialFilter pitchFilters[16]; | |||
| dsp::ExponentialFilter pwFilters[16]; | |||
| dsp::ExponentialFilter modFilters[16]; | |||
| dsp::PulseGenerator clockPulse; | |||
| @@ -74,11 +78,11 @@ struct MIDI_CV : Module { | |||
| MIDI_CV() { | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| configOutput(CV_OUTPUT, "V/oct"); | |||
| configOutput(PITCH_OUTPUT, "Pitch (1V/oct)"); | |||
| configOutput(GATE_OUTPUT, "Gate"); | |||
| configOutput(VELOCITY_OUTPUT, "Velocity"); | |||
| configOutput(AFTERTOUCH_OUTPUT, "Aftertouch"); | |||
| configOutput(PITCH_OUTPUT, "Pitch wheel"); | |||
| configOutput(PW_OUTPUT, "Pitch wheel"); | |||
| configOutput(MOD_OUTPUT, "Mod wheel"); | |||
| configOutput(RETRIGGER_OUTPUT, "Retrigger"); | |||
| configOutput(CLOCK_OUTPUT, "Clock"); | |||
| @@ -88,7 +92,7 @@ struct MIDI_CV : Module { | |||
| configOutput(CONTINUE_OUTPUT, "Continue"); | |||
| heldNotes.reserve(128); | |||
| for (int c = 0; c < 16; c++) { | |||
| pitchFilters[c].setTau(1 / 30.f); | |||
| pwFilters[c].setTau(1 / 30.f); | |||
| modFilters[c].setTau(1 / 30.f); | |||
| } | |||
| onReset(); | |||
| @@ -110,9 +114,9 @@ struct MIDI_CV : Module { | |||
| gates[c] = false; | |||
| velocities[c] = 0; | |||
| aftertouches[c] = 0; | |||
| pitches[c] = 8192; | |||
| pws[c] = 8192; | |||
| mods[c] = 0; | |||
| pitchFilters[c].reset(); | |||
| pwFilters[c].reset(); | |||
| modFilters[c].reset(); | |||
| } | |||
| pedal = false; | |||
| @@ -130,13 +134,13 @@ struct MIDI_CV : Module { | |||
| midiInput.queue.pop(); | |||
| } | |||
| outputs[CV_OUTPUT].setChannels(channels); | |||
| 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[CV_OUTPUT].setVoltage((notes[c] - 60.f) / 12.f, 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); | |||
| @@ -144,37 +148,25 @@ struct MIDI_CV : Module { | |||
| } | |||
| // Set pitch and mod wheel | |||
| auto updatePitch = [&](int c) { | |||
| float pitch = ((int) pitches[c] - 8192) / 8191.f; | |||
| pitch = clamp(pitch, -1.f, 1.f); | |||
| int wheelChannels = (polyMode == MPE_MODE) ? 16 : 1; | |||
| 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; | |||
| pw = clamp(pw, -1.f, 1.f); | |||
| if (smooth) | |||
| pitch = pitchFilters[c].process(args.sampleTime, pitch); | |||
| pw = pwFilters[c].process(args.sampleTime, pw); | |||
| else | |||
| pitchFilters[c].out = pitch; | |||
| outputs[PITCH_OUTPUT].setVoltage(pitchFilters[c].out * 5.f); | |||
| }; | |||
| auto updateMod = [&](int c) { | |||
| pwFilters[c].out = pw; | |||
| outputs[PW_OUTPUT].setVoltage(pw * 5.f); | |||
| float mod = mods[c] / 127.f; | |||
| mod = clamp(mod, 0.f, 1.f); | |||
| if (smooth) | |||
| modFilters[c].process(args.sampleTime, mod); | |||
| mod = modFilters[c].process(args.sampleTime, mod); | |||
| else | |||
| modFilters[c].out = mod; | |||
| outputs[MOD_OUTPUT].setVoltage(modFilters[c].out * 10.f); | |||
| }; | |||
| if (polyMode == MPE_MODE) { | |||
| for (int c = 0; c < channels; c++) { | |||
| updatePitch(c); | |||
| outputs[PITCH_OUTPUT].setChannels(1); | |||
| updateMod(c); | |||
| outputs[MOD_OUTPUT].setChannels(1); | |||
| } | |||
| } | |||
| else { | |||
| updatePitch(0); | |||
| outputs[PITCH_OUTPUT].setChannels(1); | |||
| updateMod(0); | |||
| outputs[MOD_OUTPUT].setChannels(1); | |||
| outputs[MOD_OUTPUT].setVoltage(mod * 10.f); | |||
| } | |||
| outputs[CLOCK_OUTPUT].setVoltage(clockPulse.process(args.sampleTime) ? 10.f : 0.f); | |||
| @@ -233,7 +225,7 @@ struct MIDI_CV : Module { | |||
| // pitch wheel | |||
| case 0xe: { | |||
| int c = (polyMode == MPE_MODE) ? msg.getChannel() : 0; | |||
| pitches[c] = ((uint16_t) msg.getValue() << 7) | msg.getNote(); | |||
| pws[c] = ((uint16_t) msg.getValue() << 7) | msg.getNote(); | |||
| } break; | |||
| case 0xf: { | |||
| processSystem(msg); | |||
| @@ -440,7 +432,7 @@ struct MIDI_CV : Module { | |||
| json_object_set_new(rootJ, "clockDivision", json_integer(clockDivision)); | |||
| // Saving/restoring pitch and mod doesn't make much sense for MPE. | |||
| if (polyMode != MPE_MODE) { | |||
| json_object_set_new(rootJ, "lastPitch", json_integer(pitches[0])); | |||
| json_object_set_new(rootJ, "lastPitch", json_integer(pws[0])); | |||
| json_object_set_new(rootJ, "lastMod", json_integer(mods[0])); | |||
| } | |||
| json_object_set_new(rootJ, "midi", midiInput.toJson()); | |||
| @@ -466,7 +458,7 @@ struct MIDI_CV : Module { | |||
| json_t* lastPitchJ = json_object_get(rootJ, "lastPitch"); | |||
| if (lastPitchJ) | |||
| pitches[0] = json_integer_value(lastPitchJ); | |||
| pws[0] = json_integer_value(lastPitchJ); | |||
| json_t* lastModJ = json_object_get(rootJ, "lastMod"); | |||
| if (lastModJ) | |||
| @@ -489,11 +481,11 @@ struct MIDI_CVWidget : ModuleWidget { | |||
| addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||
| addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(4.61505, 60.1445)), module, MIDI_CV::CV_OUTPUT)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(4.61505, 60.1445)), module, MIDI_CV::PITCH_OUTPUT)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(16.214, 60.1445)), module, MIDI_CV::GATE_OUTPUT)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(27.8143, 60.1445)), module, MIDI_CV::VELOCITY_OUTPUT)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(4.61505, 76.1449)), module, MIDI_CV::AFTERTOUCH_OUTPUT)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(16.214, 76.1449)), module, MIDI_CV::PITCH_OUTPUT)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(16.214, 76.1449)), module, MIDI_CV::PW_OUTPUT)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(27.8143, 76.1449)), module, MIDI_CV::MOD_OUTPUT)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(4.61505, 92.1439)), module, MIDI_CV::CLOCK_OUTPUT)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(16.214, 92.1439)), module, MIDI_CV::CLOCK_DIV_OUTPUT)); | |||
| @@ -519,7 +511,6 @@ struct MIDI_CVWidget : ModuleWidget { | |||
| module->smooth ^= true; | |||
| } | |||
| }; | |||
| SmoothItem* smoothItem = new SmoothItem; | |||
| smoothItem->text = "Smooth pitch/mod wheel"; | |||
| smoothItem->rightText = CHECKMARK(module->smooth); | |||
| @@ -533,7 +524,6 @@ struct MIDI_CVWidget : ModuleWidget { | |||
| module->clockDivision = clockDivision; | |||
| } | |||
| }; | |||
| struct ClockDivisionItem : MenuItem { | |||
| MIDI_CV* module; | |||
| Menu* createChildMenu() override { | |||
| @@ -551,7 +541,6 @@ struct MIDI_CVWidget : ModuleWidget { | |||
| return menu; | |||
| } | |||
| }; | |||
| ClockDivisionItem* clockDivisionItem = new ClockDivisionItem; | |||
| clockDivisionItem->text = "CLK/N divider"; | |||
| clockDivisionItem->rightText = RIGHT_ARROW; | |||
| @@ -565,7 +554,6 @@ struct MIDI_CVWidget : ModuleWidget { | |||
| module->setChannels(channels); | |||
| } | |||
| }; | |||
| struct ChannelItem : MenuItem { | |||
| MIDI_CV* module; | |||
| Menu* createChildMenu() override { | |||
| @@ -584,7 +572,6 @@ struct MIDI_CVWidget : ModuleWidget { | |||
| return menu; | |||
| } | |||
| }; | |||
| ChannelItem* channelItem = new ChannelItem; | |||
| channelItem->text = "Polyphony channels"; | |||
| channelItem->rightText = string::f("%d", module->channels) + " " + RIGHT_ARROW; | |||
| @@ -598,7 +585,6 @@ struct MIDI_CVWidget : ModuleWidget { | |||
| module->setPolyMode(polyMode); | |||
| } | |||
| }; | |||
| struct PolyModeItem : MenuItem { | |||
| MIDI_CV* module; | |||
| Menu* createChildMenu() override { | |||
| @@ -621,7 +607,6 @@ struct MIDI_CVWidget : ModuleWidget { | |||
| return menu; | |||
| } | |||
| }; | |||
| PolyModeItem* polyModeItem = new PolyModeItem; | |||
| polyModeItem->text = "Polyphony mode"; | |||
| polyModeItem->rightText = RIGHT_ARROW; | |||
| @@ -634,7 +619,6 @@ struct MIDI_CVWidget : ModuleWidget { | |||
| module->panic(); | |||
| } | |||
| }; | |||
| PanicItem* panicItem = new PanicItem; | |||
| panicItem->text = "Panic"; | |||
| panicItem->module = module; | |||
| @@ -13,7 +13,7 @@ struct MIDI_Gate : Module { | |||
| NUM_INPUTS | |||
| }; | |||
| enum OutputIds { | |||
| ENUMS(TRIG_OUTPUT, 16), | |||
| ENUMS(GATE_OUTPUTS, 16), | |||
| NUM_OUTPUTS | |||
| }; | |||
| enum LightIds { | |||
| @@ -22,21 +22,23 @@ struct MIDI_Gate : Module { | |||
| midi::InputQueue midiInput; | |||
| /** [cell][c] */ | |||
| /** [cell][channel] */ | |||
| bool gates[16][16]; | |||
| /** [cell][c] */ | |||
| /** [cell][channel] */ | |||
| float gateTimes[16][16]; | |||
| /** [cell][c] */ | |||
| /** [cell][channel] */ | |||
| uint8_t velocities[16][16]; | |||
| int learningId = -1; | |||
| uint8_t learnedNotes[16] = {}; | |||
| bool velocityMode = false; | |||
| bool mpeMode = false; | |||
| /** Cell ID in learn mode, or -1 if none. */ | |||
| int learningId; | |||
| /** [cell] */ | |||
| uint8_t learnedNotes[16]; | |||
| bool velocityMode; | |||
| bool mpeMode; | |||
| MIDI_Gate() { | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| for (int i = 0; i < 16; i++) | |||
| configOutput(TRIG_OUTPUT + i, string::f("Cell %d", i + 1)); | |||
| configOutput(GATE_OUTPUTS + i, string::f("Gate %d", i + 1)); | |||
| onReset(); | |||
| } | |||
| @@ -76,16 +78,16 @@ struct MIDI_Gate : Module { | |||
| int channels = mpeMode ? 16 : 1; | |||
| for (int i = 0; i < 16; i++) { | |||
| outputs[TRIG_OUTPUT + i].setChannels(channels); | |||
| outputs[GATE_OUTPUTS + i].setChannels(channels); | |||
| for (int c = 0; c < channels; c++) { | |||
| // Make sure all pulses last longer than 1ms | |||
| if (gates[i][c] || gateTimes[i][c] > 0.f) { | |||
| float velocity = velocityMode ? (velocities[i][c] / 127.f) : 1.f; | |||
| outputs[TRIG_OUTPUT + i].setVoltage(velocity * 10.f, c); | |||
| outputs[GATE_OUTPUTS + i].setVoltage(velocity * 10.f, c); | |||
| gateTimes[i][c] -= args.sampleTime; | |||
| } | |||
| else { | |||
| outputs[TRIG_OUTPUT + i].setVoltage(0.f, c); | |||
| outputs[GATE_OUTPUTS + i].setVoltage(0.f, c); | |||
| } | |||
| } | |||
| } | |||
| @@ -181,30 +183,6 @@ struct MIDI_Gate : Module { | |||
| }; | |||
| struct MIDI_GateVelocityItem : MenuItem { | |||
| MIDI_Gate* module; | |||
| void onAction(const ActionEvent& e) override { | |||
| module->velocityMode ^= true; | |||
| } | |||
| }; | |||
| struct MIDI_GateMpeModeItem : MenuItem { | |||
| MIDI_Gate* module; | |||
| void onAction(const ActionEvent& e) override { | |||
| module->mpeMode ^= true; | |||
| } | |||
| }; | |||
| struct MIDI_GatePanicItem : MenuItem { | |||
| MIDI_Gate* module; | |||
| void onAction(const ActionEvent& e) override { | |||
| module->panic(); | |||
| } | |||
| }; | |||
| struct MIDI_GateWidget : ModuleWidget { | |||
| MIDI_GateWidget(MIDI_Gate* module) { | |||
| setModule(module); | |||
| @@ -215,22 +193,22 @@ struct MIDI_GateWidget : ModuleWidget { | |||
| addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||
| addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(3.894335, 73.344704)), module, MIDI_Gate::TRIG_OUTPUT + 0)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(15.494659, 73.344704)), module, MIDI_Gate::TRIG_OUTPUT + 1)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(27.094982, 73.344704)), module, MIDI_Gate::TRIG_OUTPUT + 2)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.693932, 73.344704)), module, MIDI_Gate::TRIG_OUTPUT + 3)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(3.8943355, 84.945023)), module, MIDI_Gate::TRIG_OUTPUT + 4)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(15.49466, 84.945023)), module, MIDI_Gate::TRIG_OUTPUT + 5)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(27.094982, 84.945023)), module, MIDI_Gate::TRIG_OUTPUT + 6)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.693932, 84.945023)), module, MIDI_Gate::TRIG_OUTPUT + 7)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(3.8943343, 96.543976)), module, MIDI_Gate::TRIG_OUTPUT + 8)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(15.494659, 96.543976)), module, MIDI_Gate::TRIG_OUTPUT + 9)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(27.09498, 96.543976)), module, MIDI_Gate::TRIG_OUTPUT + 10)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.693932, 96.543976)), module, MIDI_Gate::TRIG_OUTPUT + 11)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(3.894335, 108.14429)), module, MIDI_Gate::TRIG_OUTPUT + 12)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(15.49466, 108.14429)), module, MIDI_Gate::TRIG_OUTPUT + 13)); | |||
| 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)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(3.894335, 73.344704)), module, MIDI_Gate::GATE_OUTPUTS + 0)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(15.494659, 73.344704)), module, MIDI_Gate::GATE_OUTPUTS + 1)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(27.094982, 73.344704)), module, MIDI_Gate::GATE_OUTPUTS + 2)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.693932, 73.344704)), module, MIDI_Gate::GATE_OUTPUTS + 3)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(3.8943355, 84.945023)), module, MIDI_Gate::GATE_OUTPUTS + 4)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(15.49466, 84.945023)), module, MIDI_Gate::GATE_OUTPUTS + 5)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(27.094982, 84.945023)), module, MIDI_Gate::GATE_OUTPUTS + 6)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.693932, 84.945023)), module, MIDI_Gate::GATE_OUTPUTS + 7)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(3.8943343, 96.543976)), module, MIDI_Gate::GATE_OUTPUTS + 8)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(15.494659, 96.543976)), module, MIDI_Gate::GATE_OUTPUTS + 9)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(27.09498, 96.543976)), module, MIDI_Gate::GATE_OUTPUTS + 10)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.693932, 96.543976)), module, MIDI_Gate::GATE_OUTPUTS + 11)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(3.894335, 108.14429)), module, MIDI_Gate::GATE_OUTPUTS + 12)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(15.49466, 108.14429)), module, MIDI_Gate::GATE_OUTPUTS + 13)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(27.09498, 108.14429)), module, MIDI_Gate::GATE_OUTPUTS + 14)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.693932, 108.14429)), module, MIDI_Gate::GATE_OUTPUTS + 15)); | |||
| typedef Grid16MidiWidget<NoteChoice<MIDI_Gate>> TMidiWidget; | |||
| TMidiWidget* midiWidget = createWidget<TMidiWidget>(mm2px(Vec(3.399621, 14.837339))); | |||
| @@ -243,18 +221,36 @@ struct MIDI_GateWidget : ModuleWidget { | |||
| void appendContextMenu(Menu* menu) override { | |||
| MIDI_Gate* module = dynamic_cast<MIDI_Gate*>(this->module); | |||
| struct VelocityItem : MenuItem { | |||
| MIDI_Gate* module; | |||
| void onAction(const ActionEvent& e) override { | |||
| module->velocityMode ^= true; | |||
| } | |||
| }; | |||
| menu->addChild(new MenuSeparator); | |||
| MIDI_GateVelocityItem* velocityItem = createMenuItem<MIDI_GateVelocityItem>("Velocity mode", CHECKMARK(module->velocityMode)); | |||
| VelocityItem* velocityItem = createMenuItem<VelocityItem>("Velocity mode", CHECKMARK(module->velocityMode)); | |||
| velocityItem->module = module; | |||
| menu->addChild(velocityItem); | |||
| MIDI_GateMpeModeItem* mpeModeItem = new MIDI_GateMpeModeItem; | |||
| struct MpeModeItem : MenuItem { | |||
| MIDI_Gate* module; | |||
| void onAction(const ActionEvent& e) override { | |||
| module->mpeMode ^= true; | |||
| } | |||
| }; | |||
| MpeModeItem* mpeModeItem = new MpeModeItem; | |||
| mpeModeItem->text = "MPE mode"; | |||
| mpeModeItem->rightText = CHECKMARK(module->mpeMode); | |||
| mpeModeItem->module = module; | |||
| menu->addChild(mpeModeItem); | |||
| MIDI_GatePanicItem* panicItem = new MIDI_GatePanicItem; | |||
| struct PanicItem : MenuItem { | |||
| MIDI_Gate* module; | |||
| void onAction(const ActionEvent& e) override { | |||
| module->panic(); | |||
| } | |||
| }; | |||
| PanicItem* panicItem = new PanicItem; | |||
| panicItem->text = "Panic"; | |||
| panicItem->module = module; | |||
| menu->addChild(panicItem); | |||