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.

373 lines
11KB

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