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.

231 lines
6.6KB

  1. #include "plugin.hpp"
  2. namespace rack {
  3. namespace core {
  4. struct MIDI_Gate : Module {
  5. enum ParamIds {
  6. NUM_PARAMS
  7. };
  8. enum InputIds {
  9. NUM_INPUTS
  10. };
  11. enum OutputIds {
  12. ENUMS(TRIG_OUTPUT, 16),
  13. NUM_OUTPUTS
  14. };
  15. enum LightIds {
  16. NUM_LIGHTS
  17. };
  18. midi::InputQueue midiInput;
  19. bool gates[16];
  20. float gateTimes[16];
  21. uint8_t velocities[16];
  22. int learningId = -1;
  23. uint8_t learnedNotes[16] = {};
  24. bool velocityMode = false;
  25. MIDI_Gate() {
  26. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  27. onReset();
  28. }
  29. void onReset() override {
  30. for (int y = 0; y < 4; y++) {
  31. for (int x = 0; x < 4; x++) {
  32. learnedNotes[4 * y + x] = 36 + 4 * (3 - y) + x;
  33. }
  34. }
  35. learningId = -1;
  36. panic();
  37. midiInput.reset();
  38. }
  39. void panic() {
  40. for (int i = 0; i < 16; i++) {
  41. gates[i] = false;
  42. gateTimes[i] = 0.f;
  43. }
  44. }
  45. void process(const ProcessArgs& args) override {
  46. midi::Message msg;
  47. while (midiInput.shift(&msg)) {
  48. processMessage(msg);
  49. }
  50. for (int i = 0; i < 16; i++) {
  51. if (gateTimes[i] > 0.f) {
  52. outputs[TRIG_OUTPUT + i].setVoltage(velocityMode ? rescale(velocities[i], 0, 127, 0.f, 10.f) : 10.f);
  53. // If the gate is off, wait 1 ms before turning the pulse off.
  54. // This avoids drum controllers sending a pulse with 0 ms duration.
  55. if (!gates[i]) {
  56. gateTimes[i] -= args.sampleTime;
  57. }
  58. }
  59. else {
  60. outputs[TRIG_OUTPUT + i].setVoltage(0.f);
  61. }
  62. }
  63. }
  64. void processMessage(midi::Message msg) {
  65. switch (msg.getStatus()) {
  66. // note off
  67. case 0x8: {
  68. releaseNote(msg.getNote());
  69. } break;
  70. // note on
  71. case 0x9: {
  72. if (msg.getValue() > 0) {
  73. pressNote(msg.getNote(), msg.getValue());
  74. }
  75. else {
  76. // Many stupid keyboards send a "note on" command with 0 velocity to mean "note release"
  77. releaseNote(msg.getNote());
  78. }
  79. } break;
  80. default: break;
  81. }
  82. }
  83. void pressNote(uint8_t note, uint8_t vel) {
  84. // Learn
  85. if (learningId >= 0) {
  86. learnedNotes[learningId] = note;
  87. learningId = -1;
  88. }
  89. // Find id
  90. for (int i = 0; i < 16; i++) {
  91. if (learnedNotes[i] == note) {
  92. gates[i] = true;
  93. gateTimes[i] = 1e-3f;
  94. velocities[i] = vel;
  95. }
  96. }
  97. }
  98. void releaseNote(uint8_t note) {
  99. // Find id
  100. for (int i = 0; i < 16; i++) {
  101. if (learnedNotes[i] == note) {
  102. gates[i] = false;
  103. }
  104. }
  105. }
  106. json_t* dataToJson() override {
  107. json_t* rootJ = json_object();
  108. json_t* notesJ = json_array();
  109. for (int i = 0; i < 16; i++) {
  110. json_t* noteJ = json_integer(learnedNotes[i]);
  111. json_array_append_new(notesJ, noteJ);
  112. }
  113. json_object_set_new(rootJ, "notes", notesJ);
  114. json_object_set_new(rootJ, "velocity", json_boolean(velocityMode));
  115. json_object_set_new(rootJ, "midi", midiInput.toJson());
  116. return rootJ;
  117. }
  118. void dataFromJson(json_t* rootJ) override {
  119. json_t* notesJ = json_object_get(rootJ, "notes");
  120. if (notesJ) {
  121. for (int i = 0; i < 16; i++) {
  122. json_t* noteJ = json_array_get(notesJ, i);
  123. if (noteJ)
  124. learnedNotes[i] = json_integer_value(noteJ);
  125. }
  126. }
  127. json_t* velocityJ = json_object_get(rootJ, "velocity");
  128. if (velocityJ)
  129. velocityMode = json_boolean_value(velocityJ);
  130. json_t* midiJ = json_object_get(rootJ, "midi");
  131. if (midiJ)
  132. midiInput.fromJson(midiJ);
  133. }
  134. };
  135. struct MIDI_GateVelocityItem : MenuItem {
  136. MIDI_Gate* module;
  137. void onAction(const event::Action& e) override {
  138. module->velocityMode ^= true;
  139. }
  140. };
  141. struct MIDI_GatePanicItem : MenuItem {
  142. MIDI_Gate* module;
  143. void onAction(const event::Action& e) override {
  144. module->panic();
  145. }
  146. };
  147. struct MIDI_GateWidget : ModuleWidget {
  148. MIDI_GateWidget(MIDI_Gate* module) {
  149. setModule(module);
  150. setPanel(APP->window->loadSvg(asset::system("res/Core/MIDI-Gate.svg")));
  151. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
  152. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  153. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  154. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  155. addOutput(createOutput<PJ301MPort>(mm2px(Vec(3.894335, 73.344704)), module, MIDI_Gate::TRIG_OUTPUT + 0));
  156. addOutput(createOutput<PJ301MPort>(mm2px(Vec(15.494659, 73.344704)), module, MIDI_Gate::TRIG_OUTPUT + 1));
  157. addOutput(createOutput<PJ301MPort>(mm2px(Vec(27.094982, 73.344704)), module, MIDI_Gate::TRIG_OUTPUT + 2));
  158. addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.693932, 73.344704)), module, MIDI_Gate::TRIG_OUTPUT + 3));
  159. addOutput(createOutput<PJ301MPort>(mm2px(Vec(3.8943355, 84.945023)), module, MIDI_Gate::TRIG_OUTPUT + 4));
  160. addOutput(createOutput<PJ301MPort>(mm2px(Vec(15.49466, 84.945023)), module, MIDI_Gate::TRIG_OUTPUT + 5));
  161. addOutput(createOutput<PJ301MPort>(mm2px(Vec(27.094982, 84.945023)), module, MIDI_Gate::TRIG_OUTPUT + 6));
  162. addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.693932, 84.945023)), module, MIDI_Gate::TRIG_OUTPUT + 7));
  163. addOutput(createOutput<PJ301MPort>(mm2px(Vec(3.8943343, 96.543976)), module, MIDI_Gate::TRIG_OUTPUT + 8));
  164. addOutput(createOutput<PJ301MPort>(mm2px(Vec(15.494659, 96.543976)), module, MIDI_Gate::TRIG_OUTPUT + 9));
  165. addOutput(createOutput<PJ301MPort>(mm2px(Vec(27.09498, 96.543976)), module, MIDI_Gate::TRIG_OUTPUT + 10));
  166. addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.693932, 96.543976)), module, MIDI_Gate::TRIG_OUTPUT + 11));
  167. addOutput(createOutput<PJ301MPort>(mm2px(Vec(3.894335, 108.14429)), module, MIDI_Gate::TRIG_OUTPUT + 12));
  168. addOutput(createOutput<PJ301MPort>(mm2px(Vec(15.49466, 108.14429)), module, MIDI_Gate::TRIG_OUTPUT + 13));
  169. addOutput(createOutput<PJ301MPort>(mm2px(Vec(27.09498, 108.14429)), module, MIDI_Gate::TRIG_OUTPUT + 14));
  170. addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.693932, 108.14429)), module, MIDI_Gate::TRIG_OUTPUT + 15));
  171. typedef Grid16MidiWidget<NoteChoice<MIDI_Gate>> TMidiWidget;
  172. TMidiWidget* midiWidget = createWidget<TMidiWidget>(mm2px(Vec(3.399621, 14.837339)));
  173. midiWidget->box.size = mm2px(Vec(44, 54.667));
  174. midiWidget->setMidiPort(module ? &module->midiInput : NULL);
  175. midiWidget->setModule(module);
  176. addChild(midiWidget);
  177. }
  178. void appendContextMenu(Menu* menu) override {
  179. MIDI_Gate* module = dynamic_cast<MIDI_Gate*>(this->module);
  180. menu->addChild(new MenuEntry);
  181. MIDI_GateVelocityItem* velocityItem = createMenuItem<MIDI_GateVelocityItem>("Velocity mode", CHECKMARK(module->velocityMode));
  182. velocityItem->module = module;
  183. menu->addChild(velocityItem);
  184. MIDI_GatePanicItem* panicItem = new MIDI_GatePanicItem;
  185. panicItem->text = "Panic";
  186. panicItem->module = module;
  187. menu->addChild(panicItem);
  188. }
  189. };
  190. // Use legacy slug for compatibility
  191. Model* modelMIDI_Gate = createModel<MIDI_Gate, MIDI_GateWidget>("MIDITriggerToCVInterface");
  192. } // namespace core
  193. } // namespace rack