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.

259 lines
7.9KB

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