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.

281 lines
6.2KB

  1. #include <list>
  2. #include <algorithm>
  3. #include "rtmidi/RtMidi.h"
  4. #include "core.hpp"
  5. #include "MidiIO.hpp"
  6. #include "dsp/digital.hpp"
  7. /*
  8. * MIDIToCVInterface converts midi note on/off events, velocity , channel aftertouch, pitch wheel and mod wheel to
  9. * CV
  10. */
  11. struct MIDIToCVInterface : MidiIO, Module {
  12. enum ParamIds {
  13. RESET_PARAM,
  14. NUM_PARAMS
  15. };
  16. enum InputIds {
  17. NUM_INPUTS
  18. };
  19. enum OutputIds {
  20. PITCH_OUTPUT = 0,
  21. GATE_OUTPUT,
  22. VELOCITY_OUTPUT,
  23. MOD_OUTPUT,
  24. PITCHWHEEL_OUTPUT,
  25. CHANNEL_AFTERTOUCH_OUTPUT,
  26. NUM_OUTPUTS
  27. };
  28. std::list<int> notes;
  29. bool pedal = false;
  30. int note = 60; // C4, most modules should use 261.626 Hz
  31. int mod = 0;
  32. int vel = 0;
  33. int afterTouch = 0;
  34. int pitchWheel = 64;
  35. bool retrigger = false;
  36. bool retriggered = false;
  37. SchmittTrigger resetTrigger;
  38. float resetLight = 0.0;
  39. MIDIToCVInterface() : MidiIO(), Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {
  40. }
  41. ~MIDIToCVInterface() {
  42. };
  43. void step();
  44. void pressNote(int note);
  45. void releaseNote(int note);
  46. void processMidi(std::vector<unsigned char> msg);
  47. json_t *toJson() {
  48. json_t *rootJ = json_object();
  49. addBaseJson(rootJ);
  50. return rootJ;
  51. }
  52. void fromJson(json_t *rootJ) {
  53. baseFromJson(rootJ);
  54. }
  55. void reset() {
  56. resetMidi();
  57. }
  58. void resetMidi();
  59. };
  60. void MIDIToCVInterface::resetMidi() {
  61. mod = 0;
  62. pitchWheel = 64;
  63. afterTouch = 0;
  64. vel = 0;
  65. resetLight = 1.0;
  66. outputs[GATE_OUTPUT].value = 0.0;
  67. notes.clear();
  68. }
  69. void MIDIToCVInterface::step() {
  70. static float sampleRate = engineGetSampleRate();
  71. if (isPortOpen()) {
  72. std::vector<unsigned char> message;
  73. // midiIn->getMessage returns empty vector if there are no messages in the queue
  74. getMessage(&message);
  75. while (message.size() > 0) {
  76. processMidi(message);
  77. getMessage(&message);
  78. }
  79. }
  80. outputs[PITCH_OUTPUT].value = ((note - 60)) / 12.0;
  81. bool gate = pedal || !notes.empty();
  82. if (retrigger && retriggered) {
  83. gate = false;
  84. retriggered = false;
  85. }
  86. if (resetTrigger.process(params[RESET_PARAM].value)) {
  87. resetMidi();
  88. return;
  89. }
  90. if (resetLight > 0) {
  91. resetLight -= resetLight / 0.55 / sampleRate; // fade out light
  92. }
  93. outputs[GATE_OUTPUT].value = gate ? 10.0 : 0.0;
  94. outputs[MOD_OUTPUT].value = mod / 127.0 * 10.0;
  95. outputs[PITCHWHEEL_OUTPUT].value = (pitchWheel - 64) / 64.0 * 10.0;
  96. outputs[CHANNEL_AFTERTOUCH_OUTPUT].value = afterTouch / 127.0 * 10.0;
  97. outputs[VELOCITY_OUTPUT].value = vel / 127.0 * 10.0;
  98. }
  99. void MIDIToCVInterface::pressNote(int note) {
  100. // Remove existing similar note
  101. auto it = std::find(notes.begin(), notes.end(), note);
  102. if (it != notes.end())
  103. notes.erase(it);
  104. // Push note
  105. notes.push_back(note);
  106. this->note = note;
  107. retriggered = true;
  108. }
  109. void MIDIToCVInterface::releaseNote(int note) {
  110. // Remove the note
  111. auto it = std::find(notes.begin(), notes.end(), note);
  112. if (it != notes.end())
  113. notes.erase(it);
  114. if (pedal) {
  115. // Don't release if pedal is held
  116. } else if (!notes.empty()) {
  117. // Play previous note
  118. auto it2 = notes.end();
  119. it2--;
  120. this->note = *it2;
  121. retriggered = true;
  122. }
  123. }
  124. void MIDIToCVInterface::processMidi(std::vector<unsigned char> msg) {
  125. int channel = msg[0] & 0xf;
  126. int status = (msg[0] >> 4) & 0xf;
  127. int data1 = msg[1];
  128. int data2 = msg[2];
  129. //fprintf(stderr, "channel %d status %d data1 %d data2 %d\n", channel, status, data1,data2);
  130. // Filter channels
  131. if (this->channel >= 0 && this->channel != channel)
  132. return;
  133. switch (status) {
  134. // note off
  135. case 0x8: {
  136. releaseNote(data1);
  137. }
  138. break;
  139. case 0x9: // note on
  140. if (data2 > 0) {
  141. pressNote(data1);
  142. this->vel = data2;
  143. } else {
  144. // For some reason, some keyboards send a "note on" event with a velocity of 0 to signal that the key has been released.
  145. releaseNote(data1);
  146. }
  147. break;
  148. case 0xb: // cc
  149. switch (data1) {
  150. case 0x01: // mod
  151. this->mod = data2;
  152. break;
  153. case 0x40: // sustain
  154. pedal = (data2 >= 64);
  155. releaseNote(-1);
  156. break;
  157. }
  158. break;
  159. case 0xe: // pitch wheel
  160. this->pitchWheel = data2;
  161. break;
  162. case 0xd: // channel aftertouch
  163. this->afterTouch = data1;
  164. break;
  165. }
  166. }
  167. MidiToCVWidget::MidiToCVWidget() {
  168. MIDIToCVInterface *module = new MIDIToCVInterface();
  169. setModule(module);
  170. box.size = Vec(15 * 9, 380);
  171. {
  172. Panel *panel = new LightPanel();
  173. panel->box.size = box.size;
  174. addChild(panel);
  175. }
  176. float margin = 5;
  177. float labelHeight = 15;
  178. float yPos = margin;
  179. float yGap = 35;
  180. addChild(createScrew<ScrewSilver>(Vec(15, 0)));
  181. addChild(createScrew<ScrewSilver>(Vec(box.size.x - 30, 0)));
  182. addChild(createScrew<ScrewSilver>(Vec(15, 365)));
  183. addChild(createScrew<ScrewSilver>(Vec(box.size.x - 30, 365)));
  184. {
  185. Label *label = new Label();
  186. label->box.pos = Vec(box.size.x - margin - 7 * 15, margin);
  187. label->text = "MIDI to CV";
  188. addChild(label);
  189. yPos = labelHeight * 2;
  190. }
  191. addParam(createParam<LEDButton>(Vec(7 * 15, labelHeight), module, MIDIToCVInterface::RESET_PARAM, 0.0, 1.0, 0.0));
  192. addChild(createValueLight<SmallLight<RedValueLight>>(Vec(7 * 15 + 5, labelHeight + 5), &module->resetLight));
  193. {
  194. Label *label = new Label();
  195. label->box.pos = Vec(margin, yPos);
  196. label->text = "MIDI Interface";
  197. addChild(label);
  198. yPos += labelHeight + margin;
  199. MidiChoice *midiChoice = new MidiChoice();
  200. midiChoice->midiModule = dynamic_cast<MidiIO *>(module);
  201. midiChoice->box.pos = Vec(margin, yPos);
  202. midiChoice->box.size.x = box.size.x - 10;
  203. addChild(midiChoice);
  204. yPos += midiChoice->box.size.y + margin;
  205. }
  206. {
  207. Label *label = new Label();
  208. label->box.pos = Vec(margin, yPos);
  209. label->text = "Channel";
  210. addChild(label);
  211. yPos += labelHeight + margin;
  212. ChannelChoice *channelChoice = new ChannelChoice();
  213. channelChoice->midiModule = dynamic_cast<MidiIO *>(module);
  214. channelChoice->box.pos = Vec(margin, yPos);
  215. channelChoice->box.size.x = box.size.x - 10;
  216. addChild(channelChoice);
  217. yPos += channelChoice->box.size.y + margin + 15;
  218. }
  219. std::string labels[MIDIToCVInterface::NUM_OUTPUTS] = {"1V/oct", "Gate", "Velocity", "Mod Wheel", "Pitch Wheel",
  220. "Aftertouch"};
  221. for (int i = 0; i < MIDIToCVInterface::NUM_OUTPUTS; i++) {
  222. Label *label = new Label();
  223. label->box.pos = Vec(margin, yPos);
  224. label->text = labels[i];
  225. addChild(label);
  226. addOutput(createOutput<PJ3410Port>(Vec(15 * 6, yPos - 5), module, i));
  227. yPos += yGap + margin;
  228. }
  229. }
  230. void MidiToCVWidget::step() {
  231. ModuleWidget::step();
  232. }