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.

333 lines
9.0KB

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