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.

196 lines
5.9KB

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