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.

199 lines
6.2KB

  1. #include "plugin.hpp"
  2. namespace rack {
  3. namespace core {
  4. struct MidiOutput : dsp::MidiGenerator<PORT_MAX_CHANNELS>, midi::Output {
  5. void onMessage(const midi::Message& message) override {
  6. // DEBUG("MIDI: %ld %s", message.getFrame(), message.toString().c_str());
  7. Output::sendMessage(message);
  8. }
  9. void reset() {
  10. Output::reset();
  11. MidiGenerator::reset();
  12. }
  13. };
  14. struct CV_MIDI : Module {
  15. enum ParamIds {
  16. NUM_PARAMS
  17. };
  18. enum InputIds {
  19. PITCH_INPUT,
  20. GATE_INPUT,
  21. VEL_INPUT,
  22. AFT_INPUT,
  23. PW_INPUT,
  24. MW_INPUT,
  25. CLK_INPUT,
  26. VOL_INPUT,
  27. PAN_INPUT,
  28. START_INPUT,
  29. STOP_INPUT,
  30. CONTINUE_INPUT,
  31. NUM_INPUTS
  32. };
  33. enum OutputIds {
  34. NUM_OUTPUTS
  35. };
  36. enum LightIds {
  37. NUM_LIGHTS
  38. };
  39. MidiOutput midiOutput;
  40. dsp::Timer rateLimiterTimer;
  41. CV_MIDI() {
  42. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  43. configInput(PITCH_INPUT, "1V/octave pitch");
  44. configInput(GATE_INPUT, "Gate");
  45. configInput(VEL_INPUT, "Velocity");
  46. configInput(AFT_INPUT, "Aftertouch");
  47. configInput(PW_INPUT, "Pitch wheel");
  48. configInput(MW_INPUT, "Mod wheel");
  49. configInput(CLK_INPUT, "Clock");
  50. configInput(VOL_INPUT, "Volume");
  51. configInput(PAN_INPUT, "Pan");
  52. configInput(START_INPUT, "Start trigger");
  53. configInput(STOP_INPUT, "Stop trigger");
  54. configInput(CONTINUE_INPUT, "Continue trigger");
  55. onReset();
  56. }
  57. void onReset() override {
  58. midiOutput.reset();
  59. }
  60. void process(const ProcessArgs& args) override {
  61. // MIDI baud rate is 31250 b/s, or 3125 B/s.
  62. // CC messages are 3 bytes, so we can send a maximum of 1041 CC messages per second.
  63. // Since multiple CCs can be generated, play it safe and limit the CC rate to 200 Hz.
  64. const float rateLimiterPeriod = 1 / 200.f;
  65. bool rateLimiterTriggered = (rateLimiterTimer.process(args.sampleTime) >= rateLimiterPeriod);
  66. if (rateLimiterTriggered)
  67. rateLimiterTimer.time -= rateLimiterPeriod;
  68. midiOutput.setFrame(args.frame);
  69. uint8_t channels = inputs[PITCH_INPUT].getChannels();
  70. midiOutput.setChannels(channels);
  71. for (int c = 0; c < channels; c++) {
  72. int vel = (int) std::round(inputs[VEL_INPUT].getNormalPolyVoltage(10.f * 100 / 127, c) / 10.f * 127);
  73. vel = clamp(vel, 0, 127);
  74. midiOutput.setVelocity(vel, c);
  75. int note = (int) std::round(inputs[PITCH_INPUT].getVoltage(c) * 12.f + 60.f);
  76. note = clamp(note, 0, 127);
  77. bool gate = inputs[GATE_INPUT].getPolyVoltage(c) >= 1.f;
  78. midiOutput.setNoteGate(note, gate, c);
  79. int aft = (int) std::round(inputs[AFT_INPUT].getPolyVoltage(c) / 10.f * 127);
  80. aft = clamp(aft, 0, 127);
  81. midiOutput.setKeyPressure(aft, c);
  82. }
  83. if (rateLimiterTriggered) {
  84. int pw = (int) std::round((inputs[PW_INPUT].getVoltage() + 5.f) / 10.f * 0x4000);
  85. pw = clamp(pw, 0, 0x3fff);
  86. midiOutput.setPitchWheel(pw);
  87. int mw = (int) std::round(inputs[MW_INPUT].getVoltage() / 10.f * 127);
  88. mw = clamp(mw, 0, 127);
  89. midiOutput.setModWheel(mw);
  90. int vol = (int) std::round(inputs[VOL_INPUT].getNormalVoltage(10.f) / 10.f * 127);
  91. vol = clamp(vol, 0, 127);
  92. midiOutput.setVolume(vol);
  93. int pan = (int) std::round((inputs[PAN_INPUT].getVoltage() + 5.f) / 10.f * 127);
  94. pan = clamp(pan, 0, 127);
  95. midiOutput.setPan(pan);
  96. }
  97. bool clk = inputs[CLK_INPUT].getVoltage() >= 1.f;
  98. midiOutput.setClock(clk);
  99. bool start = inputs[START_INPUT].getVoltage() >= 1.f;
  100. midiOutput.setStart(start);
  101. bool stop = inputs[STOP_INPUT].getVoltage() >= 1.f;
  102. midiOutput.setStop(stop);
  103. bool cont = inputs[CONTINUE_INPUT].getVoltage() >= 1.f;
  104. midiOutput.setContinue(cont);
  105. }
  106. json_t* dataToJson() override {
  107. json_t* rootJ = json_object();
  108. json_object_set_new(rootJ, "midi", midiOutput.toJson());
  109. return rootJ;
  110. }
  111. void dataFromJson(json_t* rootJ) override {
  112. json_t* midiJ = json_object_get(rootJ, "midi");
  113. if (midiJ)
  114. midiOutput.fromJson(midiJ);
  115. }
  116. };
  117. struct CV_MIDIPanicItem : MenuItem {
  118. CV_MIDI* module;
  119. void onAction(const ActionEvent& e) override {
  120. module->midiOutput.panic();
  121. }
  122. };
  123. struct CV_MIDIWidget : ModuleWidget {
  124. CV_MIDIWidget(CV_MIDI* module) {
  125. setModule(module);
  126. setPanel(createPanel(asset::system("res/Core/CV_MIDI.svg"), asset::system("res/Core/CV_MIDI-dark.svg")));
  127. addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, 0)));
  128. addChild(createWidget<ThemedScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  129. addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  130. addChild(createWidget<ThemedScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  131. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(7.906, 64.347)), module, CV_MIDI::PITCH_INPUT));
  132. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(20.249, 64.347)), module, CV_MIDI::GATE_INPUT));
  133. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(32.591, 64.347)), module, CV_MIDI::VEL_INPUT));
  134. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(7.906, 80.603)), module, CV_MIDI::AFT_INPUT));
  135. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(20.249, 80.603)), module, CV_MIDI::PW_INPUT));
  136. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(32.591, 80.603)), module, CV_MIDI::MW_INPUT));
  137. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(7.906, 96.859)), module, CV_MIDI::CLK_INPUT));
  138. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(20.249, 96.707)), module, CV_MIDI::VOL_INPUT));
  139. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(32.591, 96.859)), module, CV_MIDI::PAN_INPUT));
  140. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(7.906, 113.115)), module, CV_MIDI::START_INPUT));
  141. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(20.249, 113.115)), module, CV_MIDI::STOP_INPUT));
  142. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(32.591, 112.975)), module, CV_MIDI::CONTINUE_INPUT));
  143. MidiDisplay* display = createWidget<MidiDisplay>(mm2px(Vec(0.0, 13.039)));
  144. display->box.size = mm2px(Vec(40.64, 29.021));
  145. display->setMidiPort(module ? &module->midiOutput : NULL);
  146. addChild(display);
  147. }
  148. void appendContextMenu(Menu* menu) override {
  149. CV_MIDI* module = dynamic_cast<CV_MIDI*>(this->module);
  150. menu->addChild(new MenuSeparator);
  151. menu->addChild(createMenuItem("Panic", "",
  152. [=]() {module->midiOutput.panic();}
  153. ));
  154. }
  155. };
  156. Model* modelCV_MIDI = createModel<CV_MIDI, CV_MIDIWidget>("CV-MIDI");
  157. } // namespace core
  158. } // namespace rack