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.

229 lines
6.8KB

  1. #include "Core.hpp"
  2. #include "midi.hpp"
  3. struct MIDITriggerToCVInterface : Module {
  4. enum ParamIds {
  5. NUM_PARAMS
  6. };
  7. enum InputIds {
  8. NUM_INPUTS
  9. };
  10. enum OutputIds {
  11. ENUMS(TRIG_OUTPUT, 16),
  12. NUM_OUTPUTS
  13. };
  14. enum LightIds {
  15. NUM_LIGHTS
  16. };
  17. MidiInputQueue midiInput;
  18. bool gates[16];
  19. float gateTimes[16];
  20. int learningId = -1;
  21. uint8_t learnedNotes[16] = {};
  22. MIDITriggerToCVInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
  23. onReset();
  24. }
  25. void onReset() override {
  26. for (int i = 0; i < 16; i++) {
  27. gates[i] = false;
  28. gateTimes[i] = 0.f;
  29. learnedNotes[i] = i + 36;
  30. }
  31. learningId = -1;
  32. }
  33. void pressNote(uint8_t note) {
  34. // Learn
  35. if (learningId >= 0) {
  36. learnedNotes[learningId] = note;
  37. learningId = -1;
  38. }
  39. // Find id
  40. for (int i = 0; i < 16; i++) {
  41. if (learnedNotes[i] == note) {
  42. gates[i] = true;
  43. gateTimes[i] = 1e-3f;
  44. }
  45. }
  46. }
  47. void releaseNote(uint8_t note) {
  48. // Find id
  49. for (int i = 0; i < 16; i++) {
  50. if (learnedNotes[i] == note) {
  51. gates[i] = false;
  52. }
  53. }
  54. }
  55. void step() override {
  56. MidiMessage msg;
  57. while (midiInput.shift(&msg)) {
  58. processMessage(msg);
  59. }
  60. float deltaTime = engineGetSampleTime();
  61. for (int i = 0; i < 16; i++) {
  62. if (gateTimes[i] > 0.f) {
  63. outputs[TRIG_OUTPUT + i].value = 10.f;
  64. // If the gate is off, wait 1 ms before turning the pulse off.
  65. // This avoids drum controllers sending a pulse with 0 ms duration.
  66. if (!gates[i]) {
  67. gateTimes[i] -= deltaTime;
  68. }
  69. }
  70. else {
  71. outputs[TRIG_OUTPUT + i].value = 0.f;
  72. }
  73. }
  74. }
  75. void processMessage(MidiMessage msg) {
  76. switch (msg.status()) {
  77. // note off
  78. case 0x8: {
  79. releaseNote(msg.note());
  80. } break;
  81. // note on
  82. case 0x9: {
  83. if (msg.value() > 0) {
  84. pressNote(msg.note());
  85. }
  86. else {
  87. releaseNote(msg.note());
  88. }
  89. } break;
  90. default: break;
  91. }
  92. }
  93. json_t *toJson() override {
  94. json_t *rootJ = json_object();
  95. json_t *notesJ = json_array();
  96. for (int i = 0; i < 16; i++) {
  97. json_t *noteJ = json_integer(learnedNotes[i]);
  98. json_array_append_new(notesJ, noteJ);
  99. }
  100. json_object_set_new(rootJ, "notes", notesJ);
  101. json_object_set_new(rootJ, "midi", midiInput.toJson());
  102. return rootJ;
  103. }
  104. void fromJson(json_t *rootJ) override {
  105. json_t *notesJ = json_object_get(rootJ, "notes");
  106. if (notesJ) {
  107. for (int i = 0; i < 16; i++) {
  108. json_t *noteJ = json_array_get(notesJ, i);
  109. if (noteJ)
  110. learnedNotes[i] = json_integer_value(noteJ) & 0x7f;
  111. }
  112. }
  113. json_t *midiJ = json_object_get(rootJ, "midi");
  114. if (midiJ)
  115. midiInput.fromJson(midiJ);
  116. }
  117. };
  118. struct MidiTrigChoice : GridChoice {
  119. MIDITriggerToCVInterface *module;
  120. int id;
  121. MidiTrigChoice() {
  122. box.size.y = mm2px(6.666);
  123. textOffset.y -= 4;
  124. textOffset.x -= 4;
  125. }
  126. void setId(int id) override {
  127. this->id = id;
  128. }
  129. void step() override {
  130. if (module->learningId == id) {
  131. text = "LRN";
  132. color.a = 0.5;
  133. }
  134. else {
  135. uint8_t note = module->learnedNotes[id];
  136. static const char *noteNames[] = {
  137. "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"
  138. };
  139. int oct = note / 12 - 1;
  140. int semi = note % 12;
  141. text = stringf("%s%d", noteNames[semi], oct);
  142. color.a = 1.0;
  143. if (gFocusedWidget == this)
  144. gFocusedWidget = NULL;
  145. }
  146. }
  147. void onFocus(EventFocus &e) override {
  148. e.consumed = true;
  149. module->learningId = id;
  150. }
  151. void onDefocus(EventDefocus &e) override {
  152. module->learningId = -1;
  153. }
  154. };
  155. struct MidiTrigWidget : Grid16MidiWidget {
  156. MIDITriggerToCVInterface *module;
  157. GridChoice *createGridChoice() override {
  158. MidiTrigChoice *gridChoice = new MidiTrigChoice();
  159. gridChoice->module = module;
  160. return gridChoice;
  161. }
  162. };
  163. struct MIDITriggerToCVInterfaceWidget : ModuleWidget {
  164. MIDITriggerToCVInterfaceWidget(MIDITriggerToCVInterface *module) : ModuleWidget(module) {
  165. setPanel(SVG::load(assetGlobal("res/Core/MIDITriggerToCVInterface.svg")));
  166. addChild(Widget::create<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
  167. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  168. addChild(Widget::create<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  169. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  170. addOutput(Port::create<PJ301MPort>(mm2px(Vec(3.894335, 73.344704)), Port::OUTPUT, module, MIDITriggerToCVInterface::TRIG_OUTPUT + 0));
  171. addOutput(Port::create<PJ301MPort>(mm2px(Vec(15.494659, 73.344704)), Port::OUTPUT, module, MIDITriggerToCVInterface::TRIG_OUTPUT + 1));
  172. addOutput(Port::create<PJ301MPort>(mm2px(Vec(27.094982, 73.344704)), Port::OUTPUT, module, MIDITriggerToCVInterface::TRIG_OUTPUT + 2));
  173. addOutput(Port::create<PJ301MPort>(mm2px(Vec(38.693932, 73.344704)), Port::OUTPUT, module, MIDITriggerToCVInterface::TRIG_OUTPUT + 3));
  174. addOutput(Port::create<PJ301MPort>(mm2px(Vec(3.8943355, 84.945023)), Port::OUTPUT, module, MIDITriggerToCVInterface::TRIG_OUTPUT + 4));
  175. addOutput(Port::create<PJ301MPort>(mm2px(Vec(15.49466, 84.945023)), Port::OUTPUT, module, MIDITriggerToCVInterface::TRIG_OUTPUT + 5));
  176. addOutput(Port::create<PJ301MPort>(mm2px(Vec(27.094982, 84.945023)), Port::OUTPUT, module, MIDITriggerToCVInterface::TRIG_OUTPUT + 6));
  177. addOutput(Port::create<PJ301MPort>(mm2px(Vec(38.693932, 84.945023)), Port::OUTPUT, module, MIDITriggerToCVInterface::TRIG_OUTPUT + 7));
  178. addOutput(Port::create<PJ301MPort>(mm2px(Vec(3.8943343, 96.543976)), Port::OUTPUT, module, MIDITriggerToCVInterface::TRIG_OUTPUT + 8));
  179. addOutput(Port::create<PJ301MPort>(mm2px(Vec(15.494659, 96.543976)), Port::OUTPUT, module, MIDITriggerToCVInterface::TRIG_OUTPUT + 9));
  180. addOutput(Port::create<PJ301MPort>(mm2px(Vec(27.09498, 96.543976)), Port::OUTPUT, module, MIDITriggerToCVInterface::TRIG_OUTPUT + 10));
  181. addOutput(Port::create<PJ301MPort>(mm2px(Vec(38.693932, 96.543976)), Port::OUTPUT, module, MIDITriggerToCVInterface::TRIG_OUTPUT + 11));
  182. addOutput(Port::create<PJ301MPort>(mm2px(Vec(3.894335, 108.14429)), Port::OUTPUT, module, MIDITriggerToCVInterface::TRIG_OUTPUT + 12));
  183. addOutput(Port::create<PJ301MPort>(mm2px(Vec(15.49466, 108.14429)), Port::OUTPUT, module, MIDITriggerToCVInterface::TRIG_OUTPUT + 13));
  184. addOutput(Port::create<PJ301MPort>(mm2px(Vec(27.09498, 108.14429)), Port::OUTPUT, module, MIDITriggerToCVInterface::TRIG_OUTPUT + 14));
  185. addOutput(Port::create<PJ301MPort>(mm2px(Vec(38.693932, 108.14429)), Port::OUTPUT, module, MIDITriggerToCVInterface::TRIG_OUTPUT + 15));
  186. MidiTrigWidget *midiWidget = Widget::create<MidiTrigWidget>(mm2px(Vec(3.399621, 14.837339)));
  187. midiWidget->module = module;
  188. midiWidget->box.size = mm2px(Vec(44, 54.667));
  189. midiWidget->midiIO = &module->midiInput;
  190. midiWidget->createGridChoices();
  191. addChild(midiWidget);
  192. }
  193. };
  194. Model *modelMIDITriggerToCVInterface = Model::create<MIDITriggerToCVInterface, MIDITriggerToCVInterfaceWidget>("Core", "MIDITriggerToCVInterface", "MIDI-TRIG", MIDI_TAG, EXTERNAL_TAG);