You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

247 lines
9.1KB

  1. #include <algorithm>
  2. #include "plugin.hpp"
  3. namespace rack {
  4. namespace core {
  5. struct MIDI_CV : Module {
  6. enum ParamIds {
  7. NUM_PARAMS
  8. };
  9. enum InputIds {
  10. NUM_INPUTS
  11. };
  12. enum OutputIds {
  13. PITCH_OUTPUT,
  14. GATE_OUTPUT,
  15. VELOCITY_OUTPUT,
  16. AFTERTOUCH_OUTPUT,
  17. PW_OUTPUT,
  18. MOD_OUTPUT,
  19. RETRIGGER_OUTPUT,
  20. CLOCK_OUTPUT,
  21. CLOCK_DIV_OUTPUT,
  22. START_OUTPUT,
  23. STOP_OUTPUT,
  24. CONTINUE_OUTPUT,
  25. NUM_OUTPUTS
  26. };
  27. enum LightIds {
  28. NUM_LIGHTS
  29. };
  30. midi::InputQueue midiInput;
  31. dsp::MidiParser<16> midiParser;
  32. MIDI_CV() {
  33. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  34. configOutput(PITCH_OUTPUT, "1V/octave pitch");
  35. configOutput(GATE_OUTPUT, "Gate");
  36. configOutput(VELOCITY_OUTPUT, "Velocity");
  37. configOutput(AFTERTOUCH_OUTPUT, "Aftertouch");
  38. configOutput(PW_OUTPUT, "Pitch wheel");
  39. configOutput(MOD_OUTPUT, "Mod wheel");
  40. configOutput(RETRIGGER_OUTPUT, "Retrigger");
  41. configOutput(CLOCK_OUTPUT, "Clock");
  42. configOutput(CLOCK_DIV_OUTPUT, "Clock divider");
  43. configOutput(START_OUTPUT, "Start trigger");
  44. configOutput(STOP_OUTPUT, "Stop trigger");
  45. configOutput(CONTINUE_OUTPUT, "Continue trigger");
  46. }
  47. void onReset() override {
  48. midiParser.reset();
  49. midiInput.reset();
  50. }
  51. void process(const ProcessArgs& args) override {
  52. midi::Message msg;
  53. while (midiInput.tryPop(&msg, args.frame)) {
  54. midiParser.processMessage(msg);
  55. }
  56. midiParser.processFilters(args.sampleTime);
  57. // Set note outputs
  58. outputs[PITCH_OUTPUT].setChannels(midiParser.channels);
  59. outputs[GATE_OUTPUT].setChannels(midiParser.channels);
  60. outputs[VELOCITY_OUTPUT].setChannels(midiParser.channels);
  61. outputs[AFTERTOUCH_OUTPUT].setChannels(midiParser.channels);
  62. outputs[RETRIGGER_OUTPUT].setChannels(midiParser.channels);
  63. for (uint8_t c = 0; c < midiParser.channels; c++) {
  64. outputs[PITCH_OUTPUT].setVoltage(midiParser.getPitchVoltage(c), c);
  65. outputs[GATE_OUTPUT].setVoltage(midiParser.gates[c] ? 10.f : 0.f, c);
  66. outputs[VELOCITY_OUTPUT].setVoltage(midiParser.velocities[c] / 127.f * 10.f, c);
  67. outputs[AFTERTOUCH_OUTPUT].setVoltage(midiParser.aftertouches[c] / 127.f * 10.f, c);
  68. outputs[RETRIGGER_OUTPUT].setVoltage(midiParser.retriggerPulses[c].isHigh() ? 10.f : 0.f, c);
  69. }
  70. // Pitch and mod wheel outputs
  71. uint8_t wheelChannels = midiParser.getWheelChannels();
  72. outputs[PW_OUTPUT].setChannels(wheelChannels);
  73. outputs[MOD_OUTPUT].setChannels(wheelChannels);
  74. for (uint8_t c = 0; c < wheelChannels; c++) {
  75. outputs[PW_OUTPUT].setVoltage(midiParser.getPw(c) * 5.f, c);
  76. outputs[MOD_OUTPUT].setVoltage(midiParser.getMod(c) * 10.f, c);
  77. }
  78. // Set clock and transport outputs
  79. outputs[CLOCK_OUTPUT].setVoltage(midiParser.clockPulse.isHigh() ? 10.f : 0.f);
  80. outputs[CLOCK_DIV_OUTPUT].setVoltage(midiParser.clockDividerPulse.isHigh() ? 10.f : 0.f);
  81. outputs[START_OUTPUT].setVoltage(midiParser.startPulse.isHigh() ? 10.f : 0.f);
  82. outputs[STOP_OUTPUT].setVoltage(midiParser.stopPulse.isHigh() ? 10.f : 0.f);
  83. outputs[CONTINUE_OUTPUT].setVoltage(midiParser.continuePulse.isHigh() ? 10.f : 0.f);
  84. midiParser.processPulses(args.sampleTime);
  85. }
  86. json_t* dataToJson() override {
  87. json_t* rootJ = midiParser.toJson();
  88. json_object_set_new(rootJ, "midi", midiInput.toJson());
  89. return rootJ;
  90. }
  91. void dataFromJson(json_t* rootJ) override {
  92. // For backwards compatibility, set to 0 if undefined in JSON.
  93. midiParser.pwRange = 0;
  94. midiParser.fromJson(rootJ);
  95. json_t* midiJ = json_object_get(rootJ, "midi");
  96. if (midiJ)
  97. midiInput.fromJson(midiJ);
  98. }
  99. };
  100. struct MIDI_CVWidget : ModuleWidget {
  101. MIDI_CVWidget(MIDI_CV* module) {
  102. setModule(module);
  103. setPanel(createPanel(asset::system("res/Core/MIDI_CV.svg"), asset::system("res/Core/MIDI_CV-dark.svg")));
  104. addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, 0)));
  105. addChild(createWidget<ThemedScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  106. addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  107. addChild(createWidget<ThemedScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  108. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(7.905, 64.347)), module, MIDI_CV::PITCH_OUTPUT));
  109. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(20.248, 64.347)), module, MIDI_CV::GATE_OUTPUT));
  110. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(32.591, 64.347)), module, MIDI_CV::VELOCITY_OUTPUT));
  111. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(7.905, 80.603)), module, MIDI_CV::AFTERTOUCH_OUTPUT));
  112. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(20.248, 80.603)), module, MIDI_CV::PW_OUTPUT));
  113. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(32.591, 80.603)), module, MIDI_CV::MOD_OUTPUT));
  114. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(7.905, 96.859)), module, MIDI_CV::CLOCK_OUTPUT));
  115. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(20.248, 96.707)), module, MIDI_CV::CLOCK_DIV_OUTPUT));
  116. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(32.591, 96.859)), module, MIDI_CV::RETRIGGER_OUTPUT));
  117. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(7.905, 113.115)), module, MIDI_CV::START_OUTPUT));
  118. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(20.248, 113.115)), module, MIDI_CV::STOP_OUTPUT));
  119. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(32.591, 112.975)), module, MIDI_CV::CONTINUE_OUTPUT));
  120. MidiDisplay* display = createWidget<MidiDisplay>(mm2px(Vec(0.0, 13.048)));
  121. display->box.size = mm2px(Vec(40.64, 29.012));
  122. display->setMidiPort(module ? &module->midiInput : NULL);
  123. addChild(display);
  124. // MidiButton example
  125. // MidiButton* midiButton = createWidget<MidiButton_MIDI_DIN>(Vec(0, 0));
  126. // midiButton->setMidiPort(module ? &module->midiInput : NULL);
  127. // addChild(midiButton);
  128. }
  129. void appendContextMenu(Menu* menu) override {
  130. MIDI_CV* module = dynamic_cast<MIDI_CV*>(this->module);
  131. menu->addChild(new MenuSeparator);
  132. menu->addChild(createSubmenuItem("Polyphony channels", string::f("%d", module->midiParser.channels), [=](Menu* menu) {
  133. for (int c = 1; c <= 16; c++) {
  134. std::string channelsLabel = (c == 1) ? "Monophonic" : string::f("%d", c);
  135. menu->addChild(createCheckMenuItem(channelsLabel, "",
  136. [=]() {return module->midiParser.channels == c;},
  137. [=]() {module->midiParser.setChannels(c);}
  138. ));
  139. }
  140. }));
  141. menu->addChild(createIndexSubmenuItem("Monophonic priority", {
  142. "Last",
  143. "First",
  144. "Lowest",
  145. "Highest",
  146. }, [=]() {
  147. return module->midiParser.monoMode;
  148. }, [=](size_t monoMode) {
  149. module->midiParser.setMonoMode(decltype(module->midiParser)::MonoMode(monoMode));
  150. }));
  151. menu->addChild(createBoolPtrMenuItem("Release retrigger", "", &module->midiParser.retriggerOnResume));
  152. menu->addChild(createIndexSubmenuItem("Polyphony mode", {
  153. "Rotate",
  154. "Reuse",
  155. "Reset",
  156. "MPE",
  157. }, [=]() {
  158. return module->midiParser.polyMode;
  159. }, [=](size_t polyMode) {
  160. module->midiParser.setPolyMode(decltype(module->midiParser)::PolyMode(polyMode));
  161. }));
  162. menu->addChild(new MenuSeparator);
  163. static const std::vector<float> pwRanges = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 24, 36, 48};
  164. auto getPwRangeLabel = [](float pwRange) -> std::string {
  165. if (pwRange == 0)
  166. return "Off";
  167. else if (std::fmod(pwRange, 12) == 0.f)
  168. return string::f("%g octave%s", pwRange / 12, pwRange / 12 == 1 ? "" : "s");
  169. else
  170. return string::f("%g semitone%s", pwRange, pwRange == 1 ? "" : "s");
  171. };
  172. menu->addChild(createSubmenuItem("Pitch bend range", getPwRangeLabel(module->midiParser.pwRange), [=](Menu* menu) {
  173. for (size_t i = 0; i < pwRanges.size(); i++) {
  174. menu->addChild(createCheckMenuItem(getPwRangeLabel(pwRanges[i]), "",
  175. [=]() {return module->midiParser.pwRange == pwRanges[i];},
  176. [=]() {module->midiParser.pwRange = pwRanges[i];}
  177. ));
  178. }
  179. }));
  180. menu->addChild(createBoolPtrMenuItem("Smooth pitch/mod wheel", "", &module->midiParser.smooth));
  181. static const std::vector<uint32_t> clockDivisions = {24 * 4, 24 * 2, 24, 24 / 2, 24 / 4, 24 / 8, 2, 1};
  182. static const std::vector<std::string> clockDivisionLabels = {"Whole", "Half", "Quarter", "8th", "16th", "32nd", "12 PPQN", "24 PPQN"};
  183. size_t clockDivisionIndex = std::find(clockDivisions.begin(), clockDivisions.end(), module->midiParser.clockDivision) - clockDivisions.begin();
  184. std::string clockDivisionLabel = (clockDivisionIndex < clockDivisionLabels.size()) ? clockDivisionLabels[clockDivisionIndex] : "";
  185. menu->addChild(createSubmenuItem("CLK/N divider", clockDivisionLabel, [=](Menu* menu) {
  186. for (size_t i = 0; i < clockDivisions.size(); i++) {
  187. menu->addChild(createCheckMenuItem(clockDivisionLabels[i], "",
  188. [=]() {return module->midiParser.clockDivision == clockDivisions[i];},
  189. [=]() {module->midiParser.clockDivision = clockDivisions[i];}
  190. ));
  191. }
  192. }));
  193. menu->addChild(new MenuSeparator);
  194. menu->addChild(createMenuItem("Panic", "",
  195. [=]() {module->midiParser.panic();}
  196. ));
  197. // Example of using appendMidiMenu()
  198. // menu->addChild(new MenuSeparator);
  199. // appendMidiMenu(menu, &module->midiInput);
  200. }
  201. };
  202. // Use legacy slug for compatibility
  203. Model* modelMIDI_CV = createModel<MIDI_CV, MIDI_CVWidget>("MIDIToCVInterface");
  204. } // namespace core
  205. } // namespace rack