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.

330 lines
9.2KB

  1. #include "Core.hpp"
  2. #include "midi.hpp"
  3. #include "dsp/digital.hpp"
  4. #include "dsp/filter.hpp"
  5. #include <algorithm>
  6. struct MIDIToCVInterface : Module {
  7. enum ParamIds {
  8. NUM_PARAMS
  9. };
  10. enum InputIds {
  11. NUM_INPUTS
  12. };
  13. enum OutputIds {
  14. CV_OUTPUT,
  15. GATE_OUTPUT,
  16. VELOCITY_OUTPUT,
  17. AFTERTOUCH_OUTPUT,
  18. PITCH_OUTPUT,
  19. MOD_OUTPUT,
  20. RETRIGGER_OUTPUT,
  21. CLOCK_1_OUTPUT,
  22. CLOCK_2_OUTPUT,
  23. START_OUTPUT,
  24. STOP_OUTPUT,
  25. CONTINUE_OUTPUT,
  26. NUM_OUTPUTS
  27. };
  28. enum LightIds {
  29. NUM_LIGHTS
  30. };
  31. MidiInputQueue midiInput;
  32. uint8_t mod = 0;
  33. ExponentialFilter modFilter;
  34. uint16_t pitch = 8192;
  35. ExponentialFilter pitchFilter;
  36. PulseGenerator retriggerPulse;
  37. PulseGenerator clockPulses[2];
  38. PulseGenerator startPulse;
  39. PulseGenerator stopPulse;
  40. PulseGenerator continuePulse;
  41. int clock = 0;
  42. int divisions[2];
  43. struct NoteData {
  44. uint8_t velocity = 0;
  45. uint8_t aftertouch = 0;
  46. };
  47. NoteData noteData[128];
  48. std::vector<uint8_t> heldNotes;
  49. uint8_t lastNote;
  50. bool pedal;
  51. bool gate;
  52. MIDIToCVInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS), heldNotes(128) {
  53. onReset();
  54. }
  55. json_t *toJson() override {
  56. json_t *rootJ = json_object();
  57. json_t *divisionsJ = json_array();
  58. for (int i = 0; i < 2; i++) {
  59. json_t *divisionJ = json_integer(divisions[i]);
  60. json_array_append_new(divisionsJ, divisionJ);
  61. }
  62. json_object_set_new(rootJ, "divisions", divisionsJ);
  63. json_object_set_new(rootJ, "midi", midiInput.toJson());
  64. return rootJ;
  65. }
  66. void fromJson(json_t *rootJ) override {
  67. json_t *divisionsJ = json_object_get(rootJ, "divisions");
  68. if (divisionsJ) {
  69. for (int i = 0; i < 2; i++) {
  70. json_t *divisionJ = json_array_get(divisionsJ, i);
  71. if (divisionJ)
  72. divisions[i] = json_integer_value(divisionJ);
  73. }
  74. }
  75. json_t *midiJ = json_object_get(rootJ, "midi");
  76. if (midiJ)
  77. midiInput.fromJson(midiJ);
  78. }
  79. void onReset() override {
  80. heldNotes.clear();
  81. lastNote = 60;
  82. pedal = false;
  83. gate = false;
  84. clock = 0;
  85. divisions[0] = 24;
  86. divisions[1] = 6;
  87. }
  88. void pressNote(uint8_t note) {
  89. // Remove existing similar note
  90. auto it = std::find(heldNotes.begin(), heldNotes.end(), note);
  91. if (it != heldNotes.end())
  92. heldNotes.erase(it);
  93. // Push note
  94. heldNotes.push_back(note);
  95. lastNote = note;
  96. gate = true;
  97. retriggerPulse.trigger(1e-3);
  98. }
  99. void releaseNote(uint8_t note) {
  100. // Remove the note
  101. auto it = std::find(heldNotes.begin(), heldNotes.end(), note);
  102. if (it != heldNotes.end())
  103. heldNotes.erase(it);
  104. // Hold note if pedal is pressed
  105. if (pedal)
  106. return;
  107. // Set last note
  108. if (!heldNotes.empty()) {
  109. lastNote = heldNotes[heldNotes.size() - 1];
  110. gate = true;
  111. }
  112. else {
  113. gate = false;
  114. }
  115. }
  116. void pressPedal() {
  117. pedal = true;
  118. }
  119. void releasePedal() {
  120. pedal = false;
  121. releaseNote(255);
  122. }
  123. void step() override {
  124. MidiMessage msg;
  125. while (midiInput.shift(&msg)) {
  126. processMessage(msg);
  127. }
  128. float deltaTime = engineGetSampleTime();
  129. outputs[CV_OUTPUT].value = (lastNote - 60) / 12.f;
  130. outputs[GATE_OUTPUT].value = gate ? 10.f : 0.f;
  131. outputs[VELOCITY_OUTPUT].value = rescale(noteData[lastNote].velocity, 0, 127, 0.f, 10.f);
  132. outputs[AFTERTOUCH_OUTPUT].value = rescale(noteData[lastNote].aftertouch, 0, 127, 0.f, 10.f);
  133. pitchFilter.lambda = 100.f * deltaTime;
  134. outputs[PITCH_OUTPUT].value = pitchFilter.process(rescale(pitch, 0, 16384, -5.f, 5.f));
  135. modFilter.lambda = 100.f * deltaTime;
  136. outputs[MOD_OUTPUT].value = modFilter.process(rescale(mod, 0, 127, 0.f, 10.f));
  137. outputs[RETRIGGER_OUTPUT].value = retriggerPulse.process(deltaTime) ? 10.f : 0.f;
  138. outputs[CLOCK_1_OUTPUT].value = clockPulses[0].process(deltaTime) ? 10.f : 0.f;
  139. outputs[CLOCK_2_OUTPUT].value = clockPulses[1].process(deltaTime) ? 10.f : 0.f;
  140. outputs[START_OUTPUT].value = startPulse.process(deltaTime) ? 10.f : 0.f;
  141. outputs[STOP_OUTPUT].value = stopPulse.process(deltaTime) ? 10.f : 0.f;
  142. outputs[CONTINUE_OUTPUT].value = continuePulse.process(deltaTime) ? 10.f : 0.f;
  143. }
  144. void processMessage(MidiMessage msg) {
  145. // DEBUG("MIDI: %01x %01x %02x %02x", msg.status(), msg.channel(), msg.note(), msg.value());
  146. switch (msg.status()) {
  147. // note off
  148. case 0x8: {
  149. releaseNote(msg.note());
  150. } break;
  151. // note on
  152. case 0x9: {
  153. if (msg.value() > 0) {
  154. noteData[msg.note()].velocity = msg.value();
  155. pressNote(msg.note());
  156. }
  157. else {
  158. // For some reason, some keyboards send a "note on" event with a velocity of 0 to signal that the key has been released.
  159. releaseNote(msg.note());
  160. }
  161. } break;
  162. // channel aftertouch
  163. case 0xa: {
  164. uint8_t note = msg.note();
  165. noteData[note].aftertouch = msg.value();
  166. } break;
  167. // cc
  168. case 0xb: {
  169. processCC(msg);
  170. } break;
  171. // pitch wheel
  172. case 0xe: {
  173. pitch = msg.value() * 128 + msg.note();
  174. } break;
  175. case 0xf: {
  176. processSystem(msg);
  177. } break;
  178. default: break;
  179. }
  180. }
  181. void processCC(MidiMessage msg) {
  182. switch (msg.note()) {
  183. // mod
  184. case 0x01: {
  185. mod = msg.value();
  186. } break;
  187. // sustain
  188. case 0x40: {
  189. if (msg.value() >= 64)
  190. pressPedal();
  191. else
  192. releasePedal();
  193. } break;
  194. default: break;
  195. }
  196. }
  197. void processSystem(MidiMessage msg) {
  198. switch (msg.channel()) {
  199. // Timing
  200. case 0x8: {
  201. if (clock % divisions[0] == 0) {
  202. clockPulses[0].trigger(1e-3);
  203. }
  204. if (clock % divisions[1] == 0) {
  205. clockPulses[1].trigger(1e-3);
  206. }
  207. if (++clock >= (24*16*16)) {
  208. // Avoid overflowing the integer
  209. clock = 0;
  210. }
  211. } break;
  212. // Start
  213. case 0xa: {
  214. startPulse.trigger(1e-3);
  215. clock = 0;
  216. } break;
  217. // Continue
  218. case 0xb: {
  219. continuePulse.trigger(1e-3);
  220. } break;
  221. // Stop
  222. case 0xc: {
  223. stopPulse.trigger(1e-3);
  224. // Reset timing
  225. clock = 0;
  226. } break;
  227. default: break;
  228. }
  229. }
  230. };
  231. struct MIDIToCVInterfaceWidget : ModuleWidget {
  232. MIDIToCVInterfaceWidget(MIDIToCVInterface *module) : ModuleWidget(module) {
  233. setPanel(SVG::load(asset::global("res/Core/MIDIToCVInterface.svg")));
  234. addChild(Widget::create<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
  235. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  236. addChild(Widget::create<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  237. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  238. addOutput(Port::create<PJ301MPort>(mm2px(Vec(4.61505, 60.1445)), Port::OUTPUT, module, MIDIToCVInterface::CV_OUTPUT));
  239. addOutput(Port::create<PJ301MPort>(mm2px(Vec(16.214, 60.1445)), Port::OUTPUT, module, MIDIToCVInterface::GATE_OUTPUT));
  240. addOutput(Port::create<PJ301MPort>(mm2px(Vec(27.8143, 60.1445)), Port::OUTPUT, module, MIDIToCVInterface::VELOCITY_OUTPUT));
  241. addOutput(Port::create<PJ301MPort>(mm2px(Vec(4.61505, 76.1449)), Port::OUTPUT, module, MIDIToCVInterface::AFTERTOUCH_OUTPUT));
  242. addOutput(Port::create<PJ301MPort>(mm2px(Vec(16.214, 76.1449)), Port::OUTPUT, module, MIDIToCVInterface::PITCH_OUTPUT));
  243. addOutput(Port::create<PJ301MPort>(mm2px(Vec(27.8143, 76.1449)), Port::OUTPUT, module, MIDIToCVInterface::MOD_OUTPUT));
  244. addOutput(Port::create<PJ301MPort>(mm2px(Vec(4.61505, 92.1439)), Port::OUTPUT, module, MIDIToCVInterface::RETRIGGER_OUTPUT));
  245. addOutput(Port::create<PJ301MPort>(mm2px(Vec(16.214, 92.1439)), Port::OUTPUT, module, MIDIToCVInterface::CLOCK_1_OUTPUT));
  246. addOutput(Port::create<PJ301MPort>(mm2px(Vec(27.8143, 92.1439)), Port::OUTPUT, module, MIDIToCVInterface::CLOCK_2_OUTPUT));
  247. addOutput(Port::create<PJ301MPort>(mm2px(Vec(4.61505, 108.144)), Port::OUTPUT, module, MIDIToCVInterface::START_OUTPUT));
  248. addOutput(Port::create<PJ301MPort>(mm2px(Vec(16.214, 108.144)), Port::OUTPUT, module, MIDIToCVInterface::STOP_OUTPUT));
  249. addOutput(Port::create<PJ301MPort>(mm2px(Vec(27.8143, 108.144)), Port::OUTPUT, module, MIDIToCVInterface::CONTINUE_OUTPUT));
  250. MidiWidget *midiWidget = Widget::create<MidiWidget>(mm2px(Vec(3.41891, 14.8373)));
  251. midiWidget->box.size = mm2px(Vec(33.840, 28));
  252. midiWidget->midiIO = &module->midiInput;
  253. addChild(midiWidget);
  254. }
  255. void appendContextMenu(Menu *menu) override {
  256. MIDIToCVInterface *module = dynamic_cast<MIDIToCVInterface*>(this->module);
  257. struct ClockDivisionItem : MenuItem {
  258. MIDIToCVInterface *module;
  259. int index;
  260. int division;
  261. void onAction(EventAction &e) override {
  262. module->divisions[index] = division;
  263. }
  264. };
  265. struct ClockItem : MenuItem {
  266. MIDIToCVInterface *module;
  267. int index;
  268. Menu *createChildMenu() override {
  269. Menu *menu = new Menu();
  270. std::vector<int> divisions = {24*4, 24*2, 24, 24/2, 24/4, 24/8, 2, 1};
  271. std::vector<std::string> divisionNames = {"Whole", "Half", "Quarter", "8th", "16th", "32nd", "12 PPQN", "24 PPQN"};
  272. for (size_t i = 0; i < divisions.size(); i++) {
  273. ClockDivisionItem *item = MenuItem::create<ClockDivisionItem>(divisionNames[i], CHECKMARK(module->divisions[index] == divisions[i]));
  274. item->module = module;
  275. item->index = index;
  276. item->division = divisions[i];
  277. menu->addChild(item);
  278. }
  279. return menu;
  280. }
  281. };
  282. menu->addChild(construct<MenuLabel>());
  283. for (int i = 0; i < 2; i++) {
  284. ClockItem *item = MenuItem::create<ClockItem>(string::stringf("CLK %d rate", i + 1));
  285. item->module = module;
  286. item->index = i;
  287. menu->addChild(item);
  288. }
  289. }
  290. };
  291. Model *modelMIDIToCVInterface = Model::create<MIDIToCVInterface, MIDIToCVInterfaceWidget>("Core", "MIDIToCVInterface", "MIDI-1", MIDI_TAG, EXTERNAL_TAG);