|
- #include <algorithm>
-
- #include "plugin.hpp"
-
-
- namespace rack {
- namespace core {
-
-
- struct MIDI_CV : Module {
- enum ParamIds {
- NUM_PARAMS
- };
- enum InputIds {
- NUM_INPUTS
- };
- enum OutputIds {
- PITCH_OUTPUT,
- GATE_OUTPUT,
- VELOCITY_OUTPUT,
- AFTERTOUCH_OUTPUT,
- PW_OUTPUT,
- MOD_OUTPUT,
- RETRIGGER_OUTPUT,
- CLOCK_OUTPUT,
- CLOCK_DIV_OUTPUT,
- START_OUTPUT,
- STOP_OUTPUT,
- CONTINUE_OUTPUT,
- NUM_OUTPUTS
- };
- enum LightIds {
- NUM_LIGHTS
- };
-
- midi::InputQueue midiInput;
- dsp::MidiParser<16> midiParser;
-
- MIDI_CV() {
- config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
- configOutput(PITCH_OUTPUT, "1V/octave pitch");
- configOutput(GATE_OUTPUT, "Gate");
- configOutput(VELOCITY_OUTPUT, "Velocity");
- configOutput(AFTERTOUCH_OUTPUT, "Aftertouch");
- configOutput(PW_OUTPUT, "Pitch wheel");
- configOutput(MOD_OUTPUT, "Mod wheel");
- configOutput(RETRIGGER_OUTPUT, "Retrigger");
- configOutput(CLOCK_OUTPUT, "Clock");
- configOutput(CLOCK_DIV_OUTPUT, "Clock divider");
- configOutput(START_OUTPUT, "Start trigger");
- configOutput(STOP_OUTPUT, "Stop trigger");
- configOutput(CONTINUE_OUTPUT, "Continue trigger");
- }
-
- void onReset() override {
- midiParser.reset();
- midiInput.reset();
- }
-
- void process(const ProcessArgs& args) override {
- midi::Message msg;
- while (midiInput.tryPop(&msg, args.frame)) {
- midiParser.processMessage(msg);
- }
-
- midiParser.processFilters(args.sampleTime);
-
- // Set note outputs
- outputs[PITCH_OUTPUT].setChannels(midiParser.channels);
- outputs[GATE_OUTPUT].setChannels(midiParser.channels);
- outputs[VELOCITY_OUTPUT].setChannels(midiParser.channels);
- outputs[AFTERTOUCH_OUTPUT].setChannels(midiParser.channels);
- outputs[RETRIGGER_OUTPUT].setChannels(midiParser.channels);
- for (uint8_t c = 0; c < midiParser.channels; c++) {
- outputs[PITCH_OUTPUT].setVoltage(midiParser.getPitchVoltage(c), c);
- outputs[GATE_OUTPUT].setVoltage(midiParser.gates[c] ? 10.f : 0.f, c);
- outputs[VELOCITY_OUTPUT].setVoltage(midiParser.velocities[c] / 127.f * 10.f, c);
- outputs[AFTERTOUCH_OUTPUT].setVoltage(midiParser.aftertouches[c] / 127.f * 10.f, c);
- outputs[RETRIGGER_OUTPUT].setVoltage(midiParser.retriggerPulses[c].isHigh() ? 10.f : 0.f, c);
- }
-
- // Pitch and mod wheel outputs
- uint8_t wheelChannels = midiParser.getWheelChannels();
- outputs[PW_OUTPUT].setChannels(wheelChannels);
- outputs[MOD_OUTPUT].setChannels(wheelChannels);
- for (uint8_t c = 0; c < wheelChannels; c++) {
- outputs[PW_OUTPUT].setVoltage(midiParser.getPw(c) * 5.f, c);
- outputs[MOD_OUTPUT].setVoltage(midiParser.getMod(c) * 10.f, c);
- }
-
- // Set clock and transport outputs
- outputs[CLOCK_OUTPUT].setVoltage(midiParser.clockPulse.isHigh() ? 10.f : 0.f);
- outputs[CLOCK_DIV_OUTPUT].setVoltage(midiParser.clockDividerPulse.isHigh() ? 10.f : 0.f);
- outputs[START_OUTPUT].setVoltage(midiParser.startPulse.isHigh() ? 10.f : 0.f);
- outputs[STOP_OUTPUT].setVoltage(midiParser.stopPulse.isHigh() ? 10.f : 0.f);
- outputs[CONTINUE_OUTPUT].setVoltage(midiParser.continuePulse.isHigh() ? 10.f : 0.f);
-
- midiParser.processPulses(args.sampleTime);
- }
-
- json_t* dataToJson() override {
- json_t* rootJ = midiParser.toJson();
- json_object_set_new(rootJ, "midi", midiInput.toJson());
- return rootJ;
- }
-
- void dataFromJson(json_t* rootJ) override {
- // For backwards compatibility, set to 0 if undefined in JSON.
- midiParser.pwRange = 0;
-
- midiParser.fromJson(rootJ);
-
- json_t* midiJ = json_object_get(rootJ, "midi");
- if (midiJ)
- midiInput.fromJson(midiJ);
- }
- };
-
-
- struct MIDI_CVWidget : ModuleWidget {
- MIDI_CVWidget(MIDI_CV* module) {
- setModule(module);
- setPanel(createPanel(asset::system("res/Core/MIDI_CV.svg"), asset::system("res/Core/MIDI_CV-dark.svg")));
-
- addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, 0)));
- addChild(createWidget<ThemedScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
- addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
- addChild(createWidget<ThemedScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
-
- addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(7.905, 64.347)), module, MIDI_CV::PITCH_OUTPUT));
- addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(20.248, 64.347)), module, MIDI_CV::GATE_OUTPUT));
- addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(32.591, 64.347)), module, MIDI_CV::VELOCITY_OUTPUT));
- addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(7.905, 80.603)), module, MIDI_CV::AFTERTOUCH_OUTPUT));
- addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(20.248, 80.603)), module, MIDI_CV::PW_OUTPUT));
- addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(32.591, 80.603)), module, MIDI_CV::MOD_OUTPUT));
- addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(7.905, 96.859)), module, MIDI_CV::CLOCK_OUTPUT));
- addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(20.248, 96.707)), module, MIDI_CV::CLOCK_DIV_OUTPUT));
- addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(32.591, 96.859)), module, MIDI_CV::RETRIGGER_OUTPUT));
- addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(7.905, 113.115)), module, MIDI_CV::START_OUTPUT));
- addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(20.248, 113.115)), module, MIDI_CV::STOP_OUTPUT));
- addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(32.591, 112.975)), module, MIDI_CV::CONTINUE_OUTPUT));
-
- MidiDisplay* display = createWidget<MidiDisplay>(mm2px(Vec(0.0, 13.048)));
- display->box.size = mm2px(Vec(40.64, 29.012));
- display->setMidiPort(module ? &module->midiInput : NULL);
- addChild(display);
-
- // MidiButton example
- // MidiButton* midiButton = createWidget<MidiButton_MIDI_DIN>(Vec(0, 0));
- // midiButton->setMidiPort(module ? &module->midiInput : NULL);
- // addChild(midiButton);
- }
-
- void appendContextMenu(Menu* menu) override {
- MIDI_CV* module = dynamic_cast<MIDI_CV*>(this->module);
-
- menu->addChild(new MenuSeparator);
-
- menu->addChild(createSubmenuItem("Polyphony channels", string::f("%d", module->midiParser.channels), [=](Menu* menu) {
- for (int c = 1; c <= 16; c++) {
- std::string channelsLabel = (c == 1) ? "Monophonic" : string::f("%d", c);
- menu->addChild(createCheckMenuItem(channelsLabel, "",
- [=]() {return module->midiParser.channels == c;},
- [=]() {module->midiParser.setChannels(c);}
- ));
- }
- }));
-
- menu->addChild(createIndexSubmenuItem("Monophonic priority", {
- "Last",
- "First",
- "Lowest",
- "Highest",
- }, [=]() {
- return module->midiParser.monoMode;
- }, [=](size_t monoMode) {
- module->midiParser.setMonoMode(decltype(module->midiParser)::MonoMode(monoMode));
- }));
-
- menu->addChild(createBoolPtrMenuItem("Release retrigger", "", &module->midiParser.retriggerOnResume));
-
- menu->addChild(createIndexSubmenuItem("Polyphony mode", {
- "Rotate",
- "Reuse",
- "Reset",
- "MPE",
- }, [=]() {
- return module->midiParser.polyMode;
- }, [=](size_t polyMode) {
- module->midiParser.setPolyMode(decltype(module->midiParser)::PolyMode(polyMode));
- }));
-
- 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};
- auto getPwRangeLabel = [](float pwRange) -> std::string {
- if (pwRange == 0)
- return "Off";
- else if (std::fmod(pwRange, 12) == 0.f)
- return string::f("%g octave%s", pwRange / 12, pwRange / 12 == 1 ? "" : "s");
- else
- return string::f("%g semitone%s", pwRange, pwRange == 1 ? "" : "s");
- };
- menu->addChild(createSubmenuItem("Pitch bend range", getPwRangeLabel(module->midiParser.pwRange), [=](Menu* menu) {
- for (size_t i = 0; i < pwRanges.size(); i++) {
- menu->addChild(createCheckMenuItem(getPwRangeLabel(pwRanges[i]), "",
- [=]() {return module->midiParser.pwRange == pwRanges[i];},
- [=]() {module->midiParser.pwRange = pwRanges[i];}
- ));
- }
- }));
-
- menu->addChild(createBoolPtrMenuItem("Smooth pitch/mod wheel", "", &module->midiParser.smooth));
-
- static const std::vector<uint32_t> clockDivisions = {24 * 4, 24 * 2, 24, 24 / 2, 24 / 4, 24 / 8, 2, 1};
- static const std::vector<std::string> clockDivisionLabels = {"Whole", "Half", "Quarter", "8th", "16th", "32nd", "12 PPQN", "24 PPQN"};
- size_t clockDivisionIndex = std::find(clockDivisions.begin(), clockDivisions.end(), module->midiParser.clockDivision) - clockDivisions.begin();
- std::string clockDivisionLabel = (clockDivisionIndex < clockDivisionLabels.size()) ? clockDivisionLabels[clockDivisionIndex] : "";
- menu->addChild(createSubmenuItem("CLK/N divider", clockDivisionLabel, [=](Menu* menu) {
- for (size_t i = 0; i < clockDivisions.size(); i++) {
- menu->addChild(createCheckMenuItem(clockDivisionLabels[i], "",
- [=]() {return module->midiParser.clockDivision == clockDivisions[i];},
- [=]() {module->midiParser.clockDivision = clockDivisions[i];}
- ));
- }
- }));
-
- menu->addChild(new MenuSeparator);
-
- menu->addChild(createMenuItem("Panic", "",
- [=]() {module->midiParser.panic();}
- ));
-
- // Example of using appendMidiMenu()
- // menu->addChild(new MenuSeparator);
- // appendMidiMenu(menu, &module->midiInput);
- }
- };
-
-
- // Use legacy slug for compatibility
- Model* modelMIDI_CV = createModel<MIDI_CV, MIDI_CVWidget>("MIDIToCVInterface");
-
-
- } // namespace core
- } // namespace rack
|