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.

223 lines
6.5KB

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