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.

300 lines
8.8KB

  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(GATE_OUTPUTS, 16),
  13. NUM_OUTPUTS
  14. };
  15. enum LightIds {
  16. NUM_LIGHTS
  17. };
  18. midi::InputQueue midiInput;
  19. /** True when a cell's gate is held. [cell][channel] */
  20. bool gates[16][16];
  21. /** Last velocity value of cell. [cell][channel] */
  22. uint8_t velocities[16][16];
  23. /** Triggered when a cell's note is played. [cell][channel] */
  24. dsp::PulseGenerator trigPulses[16][16];
  25. /** Cell ID in learn mode, or -1 if none. */
  26. int learningId;
  27. /** [cell] */
  28. int8_t learnedNotes[16];
  29. // Settings
  30. enum VelocityMode {
  31. FIXED_MODE,
  32. VELOCITY_MODE,
  33. AFTERTOUCH_MODE,
  34. };
  35. VelocityMode velocityMode;
  36. bool mpeMode;
  37. bool trigMode;
  38. MIDI_Gate() {
  39. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  40. for (int i = 0; i < 16; i++)
  41. configOutput(GATE_OUTPUTS + i, string::f("Gate %d", i + 1));
  42. onReset();
  43. }
  44. void onReset() override {
  45. for (int y = 0; y < 4; y++) {
  46. for (int x = 0; x < 4; x++) {
  47. learnedNotes[4 * y + x] = 36 + 4 * (3 - y) + x;
  48. }
  49. }
  50. learningId = -1;
  51. midiInput.reset();
  52. velocityMode = FIXED_MODE;
  53. mpeMode = false;
  54. trigMode = false;
  55. panic();
  56. }
  57. void panic() {
  58. for (int i = 0; i < 16; i++) {
  59. for (int c = 0; c < 16; c++) {
  60. gates[i][c] = false;
  61. velocities[i][c] = 127;
  62. trigPulses[i][c].reset();
  63. }
  64. }
  65. }
  66. void process(const ProcessArgs& args) override {
  67. midi::Message msg;
  68. while (midiInput.tryPop(&msg, args.frame)) {
  69. processMessage(msg);
  70. }
  71. int channels = mpeMode ? 16 : 1;
  72. for (int i = 0; i < 16; i++) {
  73. for (int c = 0; c < channels; c++) {
  74. bool pulse = trigPulses[i][c].process(args.sampleTime);
  75. bool gate = pulse || (!trigMode && gates[i][c]);
  76. float velocity = gate ? 1.f : 0.f;
  77. if (velocityMode != FIXED_MODE)
  78. velocity *= velocities[i][c] / 127.f;
  79. outputs[GATE_OUTPUTS + i].setVoltage(velocity * 10.f, c);
  80. }
  81. outputs[GATE_OUTPUTS + i].setChannels(channels);
  82. }
  83. }
  84. void processMessage(const midi::Message& msg) {
  85. switch (msg.getStatus()) {
  86. // note off
  87. case 0x8: {
  88. releaseNote(msg.getChannel(), msg.getNote());
  89. } break;
  90. // note on
  91. case 0x9: {
  92. uint8_t velocity = msg.getValue();
  93. if (velocity > 0) {
  94. pressNote(msg.getChannel(), msg.getNote(), velocity);
  95. }
  96. else {
  97. // Note-on event with velocity 0 is an alternative for note-off event.
  98. releaseNote(msg.getChannel(), msg.getNote());
  99. }
  100. } break;
  101. // polyphonic pressure/aftertouch
  102. case 0xa: {
  103. if (velocityMode == AFTERTOUCH_MODE) {
  104. uint8_t c = mpeMode ? msg.getChannel() : 0;
  105. uint8_t note = msg.getNote();
  106. uint8_t velocity = msg.getValue();
  107. for (int i = 0; i < 16; i++) {
  108. if (learnedNotes[i] == note) {
  109. velocities[i][c] = velocity;
  110. }
  111. }
  112. }
  113. } break;
  114. // channel pressure/aftertouch
  115. case 0xd: {
  116. if (velocityMode == AFTERTOUCH_MODE) {
  117. uint8_t c = mpeMode ? msg.getChannel() : 0;
  118. uint8_t velocity = msg.getNote();
  119. for (int i = 0; i < 16; i++) {
  120. velocities[i][c] = velocity;
  121. }
  122. }
  123. } break;
  124. default: break;
  125. }
  126. }
  127. void pressNote(uint8_t channel, uint8_t note, uint8_t vel) {
  128. int c = mpeMode ? channel : 0;
  129. // Learn
  130. if (learningId >= 0) {
  131. setLearnedNote(learningId, note);
  132. learningId = -1;
  133. }
  134. // Find id
  135. for (int i = 0; i < 16; i++) {
  136. if (learnedNotes[i] == note) {
  137. gates[i][c] = true;
  138. if (velocityMode == VELOCITY_MODE)
  139. velocities[i][c] = vel;
  140. trigPulses[i][c].trigger(1e-3f);
  141. }
  142. }
  143. }
  144. void releaseNote(uint8_t channel, uint8_t note) {
  145. int c = mpeMode ? channel : 0;
  146. // Find id
  147. for (int i = 0; i < 16; i++) {
  148. if (learnedNotes[i] == note) {
  149. gates[i][c] = false;
  150. }
  151. }
  152. }
  153. void setLearnedNote(int id, int8_t note) {
  154. // Unset IDs of similar note
  155. if (note >= 0) {
  156. for (int id = 0; id < 16; id++) {
  157. if (learnedNotes[id] == note)
  158. learnedNotes[id] = -1;
  159. }
  160. }
  161. learnedNotes[id] = note;
  162. }
  163. json_t* dataToJson() override {
  164. json_t* rootJ = json_object();
  165. json_t* notesJ = json_array();
  166. for (int i = 0; i < 16; i++) {
  167. json_array_append_new(notesJ, json_integer(learnedNotes[i]));
  168. }
  169. json_object_set_new(rootJ, "notes", notesJ);
  170. json_object_set_new(rootJ, "velocity", json_integer(velocityMode));
  171. json_object_set_new(rootJ, "mpeMode", json_boolean(mpeMode));
  172. json_object_set_new(rootJ, "trigMode", json_boolean(trigMode));
  173. json_object_set_new(rootJ, "midi", midiInput.toJson());
  174. return rootJ;
  175. }
  176. void dataFromJson(json_t* rootJ) override {
  177. json_t* notesJ = json_object_get(rootJ, "notes");
  178. if (notesJ) {
  179. for (int i = 0; i < 16; i++) {
  180. json_t* noteJ = json_array_get(notesJ, i);
  181. if (noteJ)
  182. setLearnedNote(i, json_integer_value(noteJ));
  183. }
  184. }
  185. json_t* velocityJ = json_object_get(rootJ, "velocity");
  186. // Boolean in Rack <=v2.6.4
  187. if (json_is_true(velocityJ))
  188. velocityMode = VELOCITY_MODE;
  189. else if (json_is_integer(velocityJ))
  190. velocityMode = VelocityMode(json_integer_value(velocityJ));
  191. json_t* mpeModeJ = json_object_get(rootJ, "mpeMode");
  192. if (mpeModeJ)
  193. mpeMode = json_boolean_value(mpeModeJ);
  194. json_t* trigModeJ = json_object_get(rootJ, "trigMode");
  195. if (trigModeJ)
  196. trigMode = json_boolean_value(trigModeJ);
  197. json_t* midiJ = json_object_get(rootJ, "midi");
  198. if (midiJ)
  199. midiInput.fromJson(midiJ);
  200. }
  201. };
  202. struct MIDI_GateWidget : ModuleWidget {
  203. MIDI_GateWidget(MIDI_Gate* module) {
  204. setModule(module);
  205. setPanel(createPanel(asset::system("res/Core/MIDI_Gate.svg"), asset::system("res/Core/MIDI_Gate-dark.svg")));
  206. addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, 0)));
  207. addChild(createWidget<ThemedScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  208. addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  209. addChild(createWidget<ThemedScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  210. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(8.189, 78.431)), module, MIDI_Gate::GATE_OUTPUTS + 0));
  211. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(19.739, 78.431)), module, MIDI_Gate::GATE_OUTPUTS + 1));
  212. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(31.289, 78.431)), module, MIDI_Gate::GATE_OUTPUTS + 2));
  213. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(42.838, 78.431)), module, MIDI_Gate::GATE_OUTPUTS + 3));
  214. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(8.189, 89.946)), module, MIDI_Gate::GATE_OUTPUTS + 4));
  215. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(19.739, 89.946)), module, MIDI_Gate::GATE_OUTPUTS + 5));
  216. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(31.289, 89.946)), module, MIDI_Gate::GATE_OUTPUTS + 6));
  217. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(42.838, 89.946)), module, MIDI_Gate::GATE_OUTPUTS + 7));
  218. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(8.189, 101.466)), module, MIDI_Gate::GATE_OUTPUTS + 8));
  219. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(19.739, 101.466)), module, MIDI_Gate::GATE_OUTPUTS + 9));
  220. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(31.289, 101.466)), module, MIDI_Gate::GATE_OUTPUTS + 10));
  221. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(42.838, 101.466)), module, MIDI_Gate::GATE_OUTPUTS + 11));
  222. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(8.189, 112.998)), module, MIDI_Gate::GATE_OUTPUTS + 12));
  223. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(19.739, 112.984)), module, MIDI_Gate::GATE_OUTPUTS + 13));
  224. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(31.289, 112.984)), module, MIDI_Gate::GATE_OUTPUTS + 14));
  225. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(42.838, 112.984)), module, MIDI_Gate::GATE_OUTPUTS + 15));
  226. typedef Grid16MidiDisplay<NoteChoice<MIDI_Gate>> TMidiDisplay;
  227. TMidiDisplay* display = createWidget<TMidiDisplay>(mm2px(Vec(0.0, 13.039)));
  228. display->box.size = mm2px(Vec(50.8, 55.88));
  229. display->setMidiPort(module ? &module->midiInput : NULL);
  230. display->setModule(module);
  231. addChild(display);
  232. }
  233. void appendContextMenu(Menu* menu) override {
  234. MIDI_Gate* module = dynamic_cast<MIDI_Gate*>(this->module);
  235. menu->addChild(new MenuSeparator);
  236. menu->addChild(createIndexPtrSubmenuItem("Gate amplitude", {
  237. "10V",
  238. "Velocity",
  239. "Aftertouch",
  240. }, &module->velocityMode));
  241. menu->addChild(createBoolPtrMenuItem("MPE mode", "", &module->mpeMode));
  242. menu->addChild(createBoolPtrMenuItem("Trigger mode", "", &module->trigMode));
  243. menu->addChild(new MenuSeparator);
  244. menu->addChild(createMenuItem("Panic", "", [=]() {
  245. module->panic();
  246. }));
  247. }
  248. };
  249. // Use legacy slug for compatibility
  250. Model* modelMIDI_Gate = createModel<MIDI_Gate, MIDI_GateWidget>("MIDITriggerToCVInterface");
  251. } // namespace core
  252. } // namespace rack