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.

407 lines
14KB

  1. #include <list>
  2. #include <algorithm>
  3. #include "dsp/digital.hpp"
  4. #include "moDllz.hpp"
  5. #include "midi.hpp"
  6. #include "dsp/filter.hpp"
  7. /*
  8. * MIDIdualCV converts upper/lower midi note on/off events, velocity , channel aftertouch, pitch wheel, mod wheel breath cc and expression to CV
  9. */
  10. namespace rack_plugin_moDllz {
  11. struct MidiNoteData {
  12. uint8_t velocity = 0;
  13. uint8_t aftertouch = 0;
  14. };
  15. struct MIDIdualCV : Module {
  16. enum ParamIds {
  17. RESETMIDI_PARAM,
  18. LWRRETRGGMODE_PARAM,
  19. UPRRETRGGMODE_PARAM,
  20. PBDUALPOLARITY_PARAM,
  21. SUSTAINHOLD_PARAM,
  22. NUM_PARAMS
  23. };
  24. enum InputIds {
  25. NUM_INPUTS
  26. };
  27. enum OutputIds {
  28. PITCH_OUTPUT_Lwr,
  29. PITCH_OUTPUT_Upr,
  30. VELOCITY_OUTPUT_Lwr,
  31. VELOCITY_OUTPUT_Upr,
  32. RETRIGGATE_OUTPUT_Lwr,
  33. RETRIGGATE_OUTPUT_Upr,
  34. GATE_OUTPUT,
  35. PBEND_OUTPUT,
  36. MOD_OUTPUT,
  37. EXPRESSION_OUTPUT,
  38. BREATH_OUTPUT,
  39. SUSTAIN_OUTPUT,
  40. PRESSURE_OUTPUT,
  41. NUM_OUTPUTS
  42. };
  43. enum LightIds {
  44. RESETMIDI_LIGHT,
  45. NUM_LIGHTS
  46. };
  47. MidiInputQueue midiInput;
  48. uint8_t mod = 0;
  49. ExponentialFilter modFilter;
  50. uint8_t breath = 0;
  51. ExponentialFilter breathFilter;
  52. uint8_t expression = 0;
  53. ExponentialFilter exprFilter;
  54. uint16_t pitch = 8192;
  55. ExponentialFilter pitchFilter;
  56. uint8_t sustain = 0;
  57. ExponentialFilter sustainFilter;
  58. uint8_t pressure = 0;
  59. ExponentialFilter pressureFilter;
  60. MidiNoteData noteData[128];
  61. struct noteLive{
  62. int note = 0;
  63. uint8_t vel = 0;
  64. bool trig = false;
  65. };
  66. noteLive lowerNote;
  67. noteLive upperNote;
  68. bool anynoteGate = false;
  69. bool sustpedal = false;
  70. PulseGenerator gatePulse;
  71. SchmittTrigger resetMidiTrigger;
  72. MIDIdualCV() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {}
  73. bool noteupdated = false;
  74. /////////////////// //// //// //// /////////////////////
  75. ///////////////// /////////////// ///////// //////////// ////// ////////////////////
  76. ///////////////// //////// ///////// /////// //////////////////////
  77. /////////////////////// /////// ///////// //////////// ////////////////////////////
  78. ////////////// ///////// ///////// ///// ////////////////////////////
  79. void step() override {
  80. MidiMessage msg;
  81. while (midiInput.shift(&msg)) {
  82. processMessage(msg);
  83. }
  84. ///reset triggers...
  85. lowerNote.trig = false;
  86. upperNote.trig = false;
  87. ///////////////////////
  88. if (noteupdated){
  89. anynoteGate = false;
  90. noteupdated = false;
  91. ///LOWER///
  92. for (int i = 0; i < 128; i++){
  93. if (noteData[i].velocity > 0){
  94. anynoteGate = true;
  95. /////// trigger !!
  96. gatePulse.trigger(1e-3);
  97. /////////
  98. if (i < lowerNote.note) lowerNote.trig = true;
  99. lowerNote.note = i;
  100. lowerNote.vel = noteData[i].velocity;
  101. break;
  102. }
  103. }
  104. if (anynoteGate){
  105. ///UPPER///
  106. for (int i = 127; i > -1; i--){
  107. if (noteData[i].velocity > 0){
  108. if (i > upperNote.note) upperNote.trig = true;
  109. upperNote.note = i;
  110. upperNote.vel = noteData[i].velocity;
  111. break;
  112. }
  113. }
  114. }else{
  115. lowerNote.note = 128;
  116. upperNote.note = -1;
  117. }
  118. if (lowerNote.note < 128)
  119. outputs[PITCH_OUTPUT_Lwr].value = static_cast<float>(lowerNote.note - 60) / 12.0f;
  120. if (upperNote.note > -1)
  121. outputs[PITCH_OUTPUT_Upr].value = static_cast<float>(upperNote.note - 60) / 12.0f;
  122. if (lowerNote.vel > 0)
  123. outputs[VELOCITY_OUTPUT_Lwr].value = static_cast<float>(lowerNote.vel) / 127.0f * 10.0f;
  124. if (upperNote.vel > 0)
  125. outputs[VELOCITY_OUTPUT_Upr].value = static_cast<float>(upperNote.vel) / 127.0 * 10.0;
  126. }
  127. bool retriggLwr = lowerNote.trig || params[LWRRETRGGMODE_PARAM].value > 0.5;
  128. bool retriggUpr = upperNote.trig || params[UPRRETRGGMODE_PARAM].value > 0.5;
  129. bool pulse = gatePulse.process(1.0 / engineGetSampleRate());
  130. bool gateout = anynoteGate || sustpedal;
  131. outputs[RETRIGGATE_OUTPUT_Lwr].value = gateout && !(retriggLwr && pulse)? 10.0f : 0.0f ;
  132. outputs[RETRIGGATE_OUTPUT_Upr].value = gateout && !(retriggUpr && pulse)? 10.0f : 0.0f ;
  133. outputs[GATE_OUTPUT].value = gateout ? 10.0f : 0.0f ;
  134. pitchFilter.lambda = 100.f * engineGetSampleTime();
  135. if (params[PBDUALPOLARITY_PARAM].value < 0.5f)
  136. outputs[PBEND_OUTPUT].value = pitchFilter.process(rescale(pitch, 0, 16384, -5.f, 5.f));
  137. else
  138. outputs[PBEND_OUTPUT].value = pitchFilter.process(rescale(pitch, 0, 16384, 0.f, 10.f));
  139. modFilter.lambda = 100.f * engineGetSampleTime();
  140. outputs[MOD_OUTPUT].value = modFilter.process(rescale(mod, 0, 127, 0.f, 10.f));
  141. breathFilter.lambda = 100.f * engineGetSampleTime();
  142. outputs[BREATH_OUTPUT].value = breathFilter.process(rescale(breath, 0, 127, 0.f, 10.f));
  143. exprFilter.lambda = 100.f * engineGetSampleTime();
  144. outputs[EXPRESSION_OUTPUT].value = exprFilter.process(rescale(expression, 0, 127, 0.f, 10.f));
  145. sustainFilter.lambda = 100.f * engineGetSampleTime();
  146. outputs[SUSTAIN_OUTPUT].value = sustainFilter.process(rescale(sustain, 0, 127, 0.f, 10.f));
  147. pressureFilter.lambda = 100.f * engineGetSampleTime();
  148. outputs[PRESSURE_OUTPUT].value = pressureFilter.process(rescale(pressure, 0, 127, 0.f, 10.f));
  149. ///// RESET MIDI LIGHT
  150. if (resetMidiTrigger.process(params[RESETMIDI_PARAM].value)) {
  151. lights[RESETMIDI_LIGHT].value= 1.0f;
  152. MidiPanic();
  153. return;
  154. }
  155. if (lights[RESETMIDI_LIGHT].value > 0.0001f){
  156. lights[RESETMIDI_LIGHT].value -= 0.0001f ; // fade out light
  157. }
  158. }
  159. /////////////////////// * * * ///////////////////////////////////////////////// * * *
  160. // * * * E N D O F S T E P * * *
  161. /////////////////////// * * * ///////////////////////////////////////////////// * * *
  162. void processMessage(MidiMessage msg) {
  163. // debug("MIDI: %01x %01x %02x %02x", msg.status(), msg.channel(), msg.data1, msg.data2);
  164. if ((midiInput.channel < 0) || (midiInput.channel == msg.channel())){
  165. switch (msg.status()) {
  166. case 0x8: { // note off
  167. uint8_t note = msg.data1 & 0x7f;
  168. noteData[note].velocity = 0;
  169. noteData[note].aftertouch = 0;
  170. noteupdated = true;
  171. }
  172. break;
  173. case 0x9: { // note on
  174. uint8_t note = msg.data1 & 0x7f;
  175. noteData[note].velocity = msg.data2;
  176. noteData[note].aftertouch = 0;
  177. noteupdated = true;
  178. }
  179. break;
  180. case 0xb: // cc
  181. processCC(msg);
  182. break;
  183. case 0xe: // pitch wheel
  184. pitch = msg.data2 * 128 + msg.data1;
  185. break;
  186. case 0xd: // channel aftertouch
  187. pressure = msg.data1;
  188. break;
  189. // case 0xf: ///realtime clock etc
  190. // processSystem(msg);
  191. // break;
  192. default: break;
  193. }
  194. }
  195. }
  196. void processCC(MidiMessage msg) {
  197. switch (msg.data1) {
  198. case 0x01: // mod
  199. mod = msg.data2;
  200. break;
  201. case 0x02: // breath
  202. breath = msg.data2;
  203. break;
  204. case 0x0B: // Expression
  205. expression = msg.data2;
  206. break;
  207. case 0x40: { // sustain
  208. sustain = msg.data2;
  209. if ((params[SUSTAINHOLD_PARAM].value > 0.5) && anynoteGate) sustpedal = (msg.data2 >= 64);
  210. else sustpedal = false;
  211. }
  212. break;
  213. default: break;
  214. }
  215. }
  216. //void processSystem(MidiMessage msg) {
  217. // switch (msg.channel()) {
  218. // case 0x8: {
  219. // debug("timing clock");
  220. // } break;
  221. // case 0xa: {
  222. // debug("start");
  223. // } break;
  224. // case 0xb: {
  225. // debug("continue");
  226. // } break;
  227. // case 0xc: {
  228. // debug("stop");
  229. // } break;
  230. // default: break;
  231. // }
  232. //}
  233. void MidiPanic() {
  234. pitch = 8192;
  235. outputs[PBEND_OUTPUT].value = 0.0f;
  236. mod = 0;
  237. outputs[MOD_OUTPUT].value = 0.0f;
  238. breath = 0;
  239. outputs[BREATH_OUTPUT].value = 0.0f;
  240. expression = 0;
  241. outputs[EXPRESSION_OUTPUT].value = 0.0f;
  242. pressure = 0;
  243. outputs[PRESSURE_OUTPUT].value = 0.0f;
  244. sustain = 0;
  245. outputs[SUSTAIN_OUTPUT].value = 0.0f;
  246. sustpedal = false;
  247. }
  248. json_t *toJson() override {
  249. json_t *rootJ = json_object();
  250. json_object_set_new(rootJ, "midi", midiInput.toJson());
  251. return rootJ;
  252. }
  253. void fromJson(json_t *rootJ) override {
  254. json_t *midiJ = json_object_get(rootJ, "midi");
  255. midiInput.fromJson(midiJ);
  256. }
  257. };
  258. struct overMidiDisplay : TransparentWidget {
  259. void draw(NVGcontext* vg)
  260. {
  261. NVGcolor backgroundColor = nvgRGBA(0x80, 0x80, 0x80, 0x24);
  262. nvgBeginPath(vg);
  263. nvgRoundedRect(vg, 0, 0, 113, 48, 5.0f);
  264. nvgFillColor(vg, backgroundColor);
  265. nvgFill(vg);
  266. nvgBeginPath(vg);
  267. NVGcolor linecolor = nvgRGB(0x50, 0x50, 0x50);
  268. nvgRect(vg, 61, 0, 1, 16);
  269. nvgFillColor(vg, linecolor);
  270. nvgFill(vg);
  271. }
  272. };
  273. struct MIDIdualCVWidget : ModuleWidget {
  274. MIDIdualCVWidget(MIDIdualCV *module): ModuleWidget(module){
  275. setPanel(SVG::load(assetPlugin(plugin, "res/MIDIdualCV.svg")));
  276. //Screws
  277. addChild(Widget::create<ScrewBlack>(Vec(0, 0)));
  278. addChild(Widget::create<ScrewBlack>(Vec(box.size.x - 15, 0)));
  279. addChild(Widget::create<ScrewBlack>(Vec(0, 365)));
  280. addChild(Widget::create<ScrewBlack>(Vec(box.size.x - 15, 365)));
  281. ///MIDI
  282. float yPos = 21.0f;
  283. float xPos = 11.0f;
  284. MidiWidget *midiWidget = Widget::create<MidiWidget>(Vec(xPos,yPos));
  285. midiWidget->box.size = Vec(113,48);
  286. midiWidget->midiIO = &module->midiInput;
  287. midiWidget->driverChoice->box.size = Vec(56,16);
  288. midiWidget->deviceChoice->box.size = Vec(113,16);
  289. midiWidget->channelChoice->box.size = Vec(113,16);
  290. midiWidget->driverChoice->box.pos = Vec(0, 0);
  291. midiWidget->deviceChoice->box.pos = Vec(0, 16);
  292. midiWidget->channelChoice->box.pos = Vec(0, 32);
  293. midiWidget->driverSeparator->box.pos = Vec(0, 16);
  294. midiWidget->deviceSeparator->box.pos = Vec(0, 32);
  295. midiWidget->driverChoice->font = Font::load(mFONT_FILE);
  296. midiWidget->deviceChoice->font = Font::load(mFONT_FILE);
  297. midiWidget->channelChoice->font = Font::load(mFONT_FILE);
  298. midiWidget->driverChoice->textOffset = Vec(4,12);
  299. midiWidget->deviceChoice->textOffset = Vec(4,12);
  300. midiWidget->channelChoice->textOffset = Vec(4,12);
  301. midiWidget->driverChoice->color = nvgRGB(0xdd, 0xdd, 0xdd);
  302. midiWidget->deviceChoice->color = nvgRGB(0xdd, 0xdd, 0xdd);
  303. midiWidget->channelChoice->color = nvgRGB(0xdd, 0xdd, 0xdd);
  304. addChild(midiWidget);
  305. overMidiDisplay *overmididisplay = Widget::create<overMidiDisplay>(Vec(xPos,yPos));
  306. addChild(overmididisplay);
  307. // //reset button
  308. xPos = 77;
  309. yPos = 22;
  310. addParam(ParamWidget::create<moDllzMidiPanic>(Vec(xPos, yPos), module, MIDIdualCV::RESETMIDI_PARAM, 0.0f, 1.0f, 0.0f));
  311. addChild(ModuleLightWidget::create<SmallLight<RedLight>>(Vec(xPos+35.f, yPos+4.f), module, MIDIdualCV::RESETMIDI_LIGHT));
  312. yPos = 94.0f;
  313. //Lower-Upper Outputs
  314. for (int i = 0; i < 3; i++)
  315. {
  316. addOutput(Port::create<moDllzPort>(Vec(20, yPos), Port::OUTPUT, module, i * 2));
  317. addOutput(Port::create<moDllzPort>(Vec(93, yPos), Port::OUTPUT, module, i * 2 + 1));
  318. yPos += 24;
  319. }
  320. //Retrig Switches
  321. addParam(ParamWidget::create<moDllzSwitch>(Vec(21, 168), module, MIDIdualCV::LWRRETRGGMODE_PARAM, 0.0, 1.0, 0.0));
  322. addParam(ParamWidget::create<moDllzSwitch>(Vec(105, 168), module, MIDIdualCV::UPRRETRGGMODE_PARAM, 0.0, 1.0, 0.0));
  323. yPos = 200;
  324. xPos = 72;
  325. //Common Outputs
  326. for (int i = 6; i < MIDIdualCV::NUM_OUTPUTS; i++)
  327. {
  328. addOutput(Port::create<moDllzPort>(Vec(xPos, yPos), Port::OUTPUT, module, i));
  329. yPos += 23;
  330. }
  331. ///PitchWheel +- or +
  332. addParam(ParamWidget::create<moDllzSwitch>(Vec(105, 225), module, MIDIdualCV::PBDUALPOLARITY_PARAM, 0.0, 1.0, 0.0));
  333. ///Sustain hold notes
  334. addParam(ParamWidget::create<moDllzSwitchLed>(Vec(105, 318), module, MIDIdualCV::SUSTAINHOLD_PARAM, 0.0, 1.0, 1.0));
  335. }
  336. };
  337. } // namespace rack_plugin_moDllz
  338. using namespace rack_plugin_moDllz;
  339. RACK_PLUGIN_MODEL_INIT(moDllz, MIDIdualCV) {
  340. Model *modelMIDIdualCV = Model::create<MIDIdualCV, MIDIdualCVWidget>("moDllz", "MIDIdualCV", "MIDI to dual CV interface", MIDI_TAG, DUAL_TAG, EXTERNAL_TAG);
  341. return modelMIDIdualCV;
  342. }