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.

374 lines
12KB

  1. #include "Core.hpp"
  2. #include "midi.hpp"
  3. #include <algorithm>
  4. struct QuadMIDIToCVInterface : Module {
  5. enum ParamIds {
  6. NUM_PARAMS
  7. };
  8. enum InputIds {
  9. NUM_INPUTS
  10. };
  11. enum OutputIds {
  12. ENUMS(CV_OUTPUT, 4),
  13. ENUMS(GATE_OUTPUT, 4),
  14. ENUMS(VELOCITY_OUTPUT, 4),
  15. ENUMS(AFTERTOUCH_OUTPUT, 4),
  16. NUM_OUTPUTS
  17. };
  18. enum LightIds {
  19. NUM_LIGHTS
  20. };
  21. MidiInputQueue midiInput;
  22. enum PolyMode {
  23. ROTATE_MODE,
  24. /* Added REUSE option that reuses a channel when receiving the same note.
  25. Good when using sustain pedal so it doesn't "stack" unisons ... not sure this is the best name but it is descriptive...*/
  26. REUSE_MODE,
  27. RESET_MODE,
  28. REASSIGN_MODE,
  29. UNISON_MODE,
  30. NUM_MODES
  31. };
  32. PolyMode polyMode = ROTATE_MODE;
  33. struct NoteData {
  34. uint8_t velocity = 0;
  35. uint8_t aftertouch = 0;
  36. };
  37. NoteData noteData[128];
  38. // cachedNotes : UNISON_MODE and REASSIGN_MODE cache all played notes. The other polyModes cache stealed notes (after 4th one).
  39. std::vector<uint8_t> cachedNotes;
  40. uint8_t notes[4];
  41. bool gates[4];
  42. // gates set to TRUE by pedal and current gate. FALSE by pedal.
  43. bool pedalgates[4];
  44. bool pedal;
  45. int rotateIndex;
  46. int stealIndex;
  47. // retrigger for stolen notes (when gates already open)
  48. PulseGenerator reTrigger[4];
  49. QuadMIDIToCVInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS), cachedNotes(128) {
  50. onReset();
  51. }
  52. json_t *toJson() override {
  53. json_t *rootJ = json_object();
  54. json_object_set_new(rootJ, "midi", midiInput.toJson());
  55. json_object_set_new(rootJ, "polyMode", json_integer(polyMode));
  56. return rootJ;
  57. }
  58. void fromJson(json_t *rootJ) override {
  59. json_t *midiJ = json_object_get(rootJ, "midi");
  60. if (midiJ)
  61. midiInput.fromJson(midiJ);
  62. json_t *polyModeJ = json_object_get(rootJ, "polyMode");
  63. if (polyModeJ)
  64. polyMode = (PolyMode) json_integer_value(polyModeJ);
  65. }
  66. void onReset() override {
  67. for (int i = 0; i < 4; i++) {
  68. notes[i] = 60;
  69. gates[i] = false;
  70. pedalgates[i] = false;
  71. }
  72. pedal = false;
  73. rotateIndex = -1;
  74. cachedNotes.clear();
  75. }
  76. int getPolyIndex (int nowIndex) {
  77. for (int i = 0; i < 4; i++) {
  78. nowIndex ++;
  79. if (nowIndex > 3)
  80. nowIndex = 0;
  81. if (!(gates[nowIndex] || pedalgates[nowIndex])) {
  82. stealIndex = nowIndex;
  83. return nowIndex;
  84. }
  85. }
  86. // All taken = steal (stealIndex always rotate)
  87. stealIndex ++;
  88. if (stealIndex > 3)
  89. stealIndex = 0;
  90. if ((polyMode < REASSIGN_MODE) && (gates[stealIndex]))
  91. cachedNotes.push_back(notes[stealIndex]);
  92. return stealIndex;
  93. }
  94. void pressNote(uint8_t note) {
  95. // Set notes and gates
  96. switch (polyMode) {
  97. case ROTATE_MODE: {
  98. rotateIndex = getPolyIndex(rotateIndex);
  99. } break;
  100. case REUSE_MODE: {
  101. bool reuse = false;
  102. for (int i = 0; i < 4; i++) {
  103. if (notes[i] == note) {
  104. rotateIndex = i;
  105. reuse = true;
  106. break;
  107. }
  108. }
  109. if (!reuse)
  110. rotateIndex = getPolyIndex(rotateIndex);
  111. } break;
  112. case RESET_MODE: {
  113. rotateIndex = getPolyIndex(-1);
  114. } break;
  115. case REASSIGN_MODE: {
  116. cachedNotes.push_back(note);
  117. rotateIndex = getPolyIndex(-1);
  118. } break;
  119. case UNISON_MODE: {
  120. cachedNotes.push_back(note);
  121. for (int i = 0; i < 4; i++) {
  122. notes[i] = note;
  123. gates[i] = true;
  124. pedalgates[i] = pedal;
  125. //...it could be just "legato" for Unison mode without this...
  126. reTrigger[i].trigger(1e-3);
  127. }
  128. return;
  129. } break;
  130. default: break;
  131. }
  132. // Set notes and gates
  133. if (gates[rotateIndex] || pedalgates[rotateIndex])
  134. reTrigger[rotateIndex].trigger(1e-3);
  135. notes[rotateIndex] = note;
  136. gates[rotateIndex] = true;
  137. pedalgates[rotateIndex] = pedal;
  138. }
  139. void releaseNote(uint8_t note) {
  140. // Remove the note
  141. auto it = std::find(cachedNotes.begin(), cachedNotes.end(), note);
  142. if (it != cachedNotes.end())
  143. cachedNotes.erase(it);
  144. switch (polyMode) {
  145. case REASSIGN_MODE: {
  146. int held = static_cast<int>(cachedNotes.size());
  147. if (held > 4)
  148. held = 4;
  149. for (int i = 0; i < held; i++) {
  150. if (!pedalgates[i])
  151. notes[i] = cachedNotes.at(i);
  152. pedalgates[i] = pedal;
  153. }
  154. for (int i = held; i < 4; i++) {
  155. gates[i] = false;
  156. }
  157. } break;
  158. case UNISON_MODE: {
  159. if (!cachedNotes.empty()) {
  160. uint8_t backnote = cachedNotes.back();
  161. for (int i = 0; i < 4; i++) {
  162. notes[i] = backnote;
  163. gates[i] = true;
  164. }
  165. }
  166. else {
  167. for (int i = 0; i < 4; i++) {
  168. gates[i] = false;
  169. }
  170. }
  171. } break;
  172. // default ROTATE_MODE REUSE_MODE RESET_MODE
  173. default: {
  174. for (int i = 0; i < 4; i++) {
  175. if (notes[i] == note) {
  176. if (pedalgates[i]){
  177. gates[i] = false;
  178. }
  179. else if (!cachedNotes.empty()) {
  180. notes[i] = cachedNotes.back();
  181. cachedNotes.pop_back();
  182. }
  183. else {
  184. gates[i] = false;
  185. }
  186. }
  187. }
  188. } break;
  189. }
  190. }
  191. void pressPedal() {
  192. pedal = true;
  193. for (int i = 0; i < 4; i++) {
  194. pedalgates[i] = gates[i];
  195. }
  196. }
  197. void releasePedal() {
  198. pedal = false;
  199. /* When pedal is off: Recover notes for still-pressed keys (if any),
  200. ...after they were already being "cycled" out by pedal-sustained notes
  201. */
  202. for (int i = 0; i < 4; i++) {
  203. pedalgates[i] = false;
  204. if (!cachedNotes.empty()) {
  205. if (polyMode < REASSIGN_MODE) {
  206. notes[i] = cachedNotes.back();
  207. cachedNotes.pop_back();
  208. gates[i] = true;
  209. }
  210. }
  211. }
  212. if (polyMode == REASSIGN_MODE) {
  213. int held = static_cast<int>(cachedNotes.size());
  214. if (held > 4)
  215. held = 4;
  216. for (int i = 0; i < held; i++) {
  217. notes[i] = cachedNotes.at(i);
  218. gates[i] = true;
  219. }
  220. for (int i = held; i < 4; i++) {
  221. gates[i] = false;
  222. }
  223. }
  224. }
  225. void step() override {
  226. MidiMessage msg;
  227. while (midiInput.shift(&msg)) {
  228. processMessage(msg);
  229. }
  230. for (int i = 0; i < 4; i++) {
  231. uint8_t lastNote = notes[i];
  232. uint8_t lastGate = ((gates[i] || pedalgates[i]) && (!(reTrigger[i].process(engineGetSampleTime()))));
  233. outputs[CV_OUTPUT + i].value = (lastNote - 60) / 12.f;
  234. outputs[GATE_OUTPUT + i].value = lastGate ? 10.f : 0.f;
  235. outputs[VELOCITY_OUTPUT + i].value = rescale(noteData[lastNote].velocity, 0, 127, 0.f, 10.f);
  236. outputs[AFTERTOUCH_OUTPUT + i].value = rescale(noteData[lastNote].aftertouch, 0, 127, 0.f, 10.f);
  237. }
  238. }
  239. void processMessage(MidiMessage msg) {
  240. // filter MIDI channel
  241. if ((midiInput.channel > -1) && (midiInput.channel != msg.channel()))
  242. return;
  243. switch (msg.status()) {
  244. // note off
  245. case 0x8: {
  246. releaseNote(msg.note());
  247. } break;
  248. // note on
  249. case 0x9: {
  250. if (msg.value() > 0) {
  251. noteData[msg.note()].velocity = msg.value();
  252. pressNote(msg.note());
  253. }
  254. else {
  255. releaseNote(msg.note());
  256. }
  257. } break;
  258. // channel aftertouch
  259. case 0xa: {
  260. noteData[msg.note()].aftertouch = msg.value();
  261. } break;
  262. // cc
  263. case 0xb: {
  264. processCC(msg);
  265. } break;
  266. default: break;
  267. }
  268. }
  269. void processCC(MidiMessage msg) {
  270. switch (msg.note()) {
  271. // sustain
  272. case 0x40: {
  273. if (msg.value() >= 64)
  274. pressPedal();
  275. else
  276. releasePedal();
  277. } break;
  278. default: break;
  279. }
  280. }
  281. };
  282. struct QuadMIDIToCVInterfaceWidget : ModuleWidget {
  283. QuadMIDIToCVInterfaceWidget(QuadMIDIToCVInterface *module) : ModuleWidget(module) {
  284. setPanel(SVG::load(assetGlobal("res/Core/QuadMIDIToCVInterface.svg")));
  285. addChild(Widget::create<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
  286. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  287. addChild(Widget::create<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  288. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  289. addOutput(Port::create<PJ301MPort>(mm2px(Vec(3.894335, 60.144478)), Port::OUTPUT, module, QuadMIDIToCVInterface::CV_OUTPUT + 0));
  290. addOutput(Port::create<PJ301MPort>(mm2px(Vec(15.494659, 60.144478)), Port::OUTPUT, module, QuadMIDIToCVInterface::GATE_OUTPUT + 0));
  291. addOutput(Port::create<PJ301MPort>(mm2px(Vec(27.094986, 60.144478)), Port::OUTPUT, module, QuadMIDIToCVInterface::VELOCITY_OUTPUT + 0));
  292. addOutput(Port::create<PJ301MPort>(mm2px(Vec(38.693935, 60.144478)), Port::OUTPUT, module, QuadMIDIToCVInterface::AFTERTOUCH_OUTPUT + 0));
  293. addOutput(Port::create<PJ301MPort>(mm2px(Vec(3.894335, 76.144882)), Port::OUTPUT, module, QuadMIDIToCVInterface::CV_OUTPUT + 1));
  294. addOutput(Port::create<PJ301MPort>(mm2px(Vec(15.494659, 76.144882)), Port::OUTPUT, module, QuadMIDIToCVInterface::GATE_OUTPUT + 1));
  295. addOutput(Port::create<PJ301MPort>(mm2px(Vec(27.094986, 76.144882)), Port::OUTPUT, module, QuadMIDIToCVInterface::VELOCITY_OUTPUT + 1));
  296. addOutput(Port::create<PJ301MPort>(mm2px(Vec(38.693935, 76.144882)), Port::OUTPUT, module, QuadMIDIToCVInterface::AFTERTOUCH_OUTPUT + 1));
  297. addOutput(Port::create<PJ301MPort>(mm2px(Vec(3.894335, 92.143906)), Port::OUTPUT, module, QuadMIDIToCVInterface::CV_OUTPUT + 2));
  298. addOutput(Port::create<PJ301MPort>(mm2px(Vec(15.494659, 92.143906)), Port::OUTPUT, module, QuadMIDIToCVInterface::GATE_OUTPUT + 2));
  299. addOutput(Port::create<PJ301MPort>(mm2px(Vec(27.094986, 92.143906)), Port::OUTPUT, module, QuadMIDIToCVInterface::VELOCITY_OUTPUT + 2));
  300. addOutput(Port::create<PJ301MPort>(mm2px(Vec(38.693935, 92.143906)), Port::OUTPUT, module, QuadMIDIToCVInterface::AFTERTOUCH_OUTPUT + 2));
  301. addOutput(Port::create<PJ301MPort>(mm2px(Vec(3.894335, 108.1443)), Port::OUTPUT, module, QuadMIDIToCVInterface::CV_OUTPUT + 3));
  302. addOutput(Port::create<PJ301MPort>(mm2px(Vec(15.494659, 108.1443)), Port::OUTPUT, module, QuadMIDIToCVInterface::GATE_OUTPUT + 3));
  303. addOutput(Port::create<PJ301MPort>(mm2px(Vec(27.094986, 108.1443)), Port::OUTPUT, module, QuadMIDIToCVInterface::VELOCITY_OUTPUT + 3));
  304. addOutput(Port::create<PJ301MPort>(mm2px(Vec(38.693935, 108.1443)), Port::OUTPUT, module, QuadMIDIToCVInterface::AFTERTOUCH_OUTPUT + 3));
  305. MidiWidget *midiWidget = Widget::create<MidiWidget>(mm2px(Vec(3.4009969, 14.837336)));
  306. midiWidget->box.size = mm2px(Vec(44, 28));
  307. midiWidget->midiIO = &module->midiInput;
  308. addChild(midiWidget);
  309. }
  310. void appendContextMenu(Menu *menu) override {
  311. QuadMIDIToCVInterface *module = dynamic_cast<QuadMIDIToCVInterface*>(this->module);
  312. struct PolyphonyItem : MenuItem {
  313. QuadMIDIToCVInterface *module;
  314. QuadMIDIToCVInterface::PolyMode polyMode;
  315. void onAction(EventAction &e) override {
  316. module->polyMode = polyMode;
  317. }
  318. };
  319. menu->addChild(MenuEntry::create());
  320. menu->addChild(MenuLabel::create("Polyphony mode"));
  321. std::vector<std::string> polyModeNames = {"Rotate", "Reset", "Reassign", "Unison"};
  322. for (int i = 0; i < QuadMIDIToCVInterface::NUM_MODES; i++) {
  323. PolyphonyItem *item = MenuItem::create<PolyphonyItem>(polyModeNames[i], CHECKMARK(module->polyMode == i));
  324. item->module = module;
  325. item->polyMode = (QuadMIDIToCVInterface::PolyMode) i;
  326. menu->addChild(item);
  327. }
  328. }
  329. };
  330. Model *modelQuadMIDIToCVInterface = Model::create<QuadMIDIToCVInterface, QuadMIDIToCVInterfaceWidget>("Core", "QuadMIDIToCVInterface", "MIDI-4", MIDI_TAG, EXTERNAL_TAG, QUAD_TAG);