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.

181 lines
5.6KB

  1. #include "plugin.hpp"
  2. struct MIDI_CC : Module {
  3. enum ParamIds {
  4. NUM_PARAMS
  5. };
  6. enum InputIds {
  7. NUM_INPUTS
  8. };
  9. enum OutputIds {
  10. ENUMS(CC_OUTPUT, 16),
  11. NUM_OUTPUTS
  12. };
  13. enum LightIds {
  14. NUM_LIGHTS
  15. };
  16. midi::InputQueue midiInput;
  17. int8_t values[128];
  18. int learningId;
  19. int learnedCcs[16];
  20. dsp::ExponentialFilter valueFilters[16];
  21. int8_t lastValues[16] = {};
  22. MIDI_CC() {
  23. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  24. for (int i = 0; i < 16; i++) {
  25. valueFilters[i].lambda = 1 / 0.01f;
  26. }
  27. onReset();
  28. }
  29. void onReset() override {
  30. for (int i = 0; i < 128; i++) {
  31. values[i] = 0;
  32. }
  33. for (int i = 0; i < 16; i++) {
  34. learnedCcs[i] = i;
  35. }
  36. learningId = -1;
  37. midiInput.reset();
  38. }
  39. void process(const ProcessContext &ctx) override {
  40. midi::Message msg;
  41. while (midiInput.shift(&msg)) {
  42. processMessage(msg);
  43. }
  44. for (int i = 0; i < 16; i++) {
  45. if (!outputs[CC_OUTPUT + i].isConnected())
  46. continue;
  47. int cc = learnedCcs[i];
  48. float value = rescale(values[cc], 0, 127, 0.f, 10.f);
  49. // Detect behavior from MIDI buttons.
  50. if ((lastValues[i] == 0 && values[cc] == 127) || (lastValues[i] == 127 && values[cc] == 0)) {
  51. // Jump value
  52. valueFilters[i].out = value;
  53. }
  54. else {
  55. // Smooth value with filter
  56. valueFilters[i].process(ctx.sampleTime, value);
  57. }
  58. lastValues[i] = values[cc];
  59. outputs[CC_OUTPUT + i].setVoltage(valueFilters[i].out);
  60. }
  61. }
  62. void processMessage(midi::Message msg) {
  63. switch (msg.getStatus()) {
  64. // cc
  65. case 0xb: {
  66. processCC(msg);
  67. } break;
  68. default: break;
  69. }
  70. }
  71. void processCC(midi::Message msg) {
  72. uint8_t cc = msg.getNote();
  73. // Learn
  74. if (learningId >= 0 && values[cc] != msg.data2) {
  75. learnedCcs[learningId] = cc;
  76. learningId = -1;
  77. }
  78. // Allow CC to be negative if the 8th bit is set.
  79. // The gamepad driver abuses this, for example.
  80. values[cc] = clamp(msg.data2, -127, 127);
  81. }
  82. json_t *dataToJson() override {
  83. json_t *rootJ = json_object();
  84. json_t *ccsJ = json_array();
  85. for (int i = 0; i < 16; i++) {
  86. json_array_append_new(ccsJ, json_integer(learnedCcs[i]));
  87. }
  88. json_object_set_new(rootJ, "ccs", ccsJ);
  89. // Remember values so users don't have to touch MIDI controller knobs when restarting Rack
  90. json_t *valuesJ = json_array();
  91. for (int i = 0; i < 128; i++) {
  92. json_array_append_new(valuesJ, json_integer(values[i]));
  93. }
  94. json_object_set_new(rootJ, "values", valuesJ);
  95. json_object_set_new(rootJ, "midi", midiInput.toJson());
  96. return rootJ;
  97. }
  98. void dataFromJson(json_t *rootJ) override {
  99. json_t *ccsJ = json_object_get(rootJ, "ccs");
  100. if (ccsJ) {
  101. for (int i = 0; i < 16; i++) {
  102. json_t *ccJ = json_array_get(ccsJ, i);
  103. if (ccJ)
  104. learnedCcs[i] = json_integer_value(ccJ);
  105. }
  106. }
  107. json_t *valuesJ = json_object_get(rootJ, "values");
  108. if (valuesJ) {
  109. for (int i = 0; i < 128; i++) {
  110. json_t *valueJ = json_array_get(valuesJ, i);
  111. if (valueJ) {
  112. values[i] = json_integer_value(valueJ);
  113. }
  114. }
  115. }
  116. json_t *midiJ = json_object_get(rootJ, "midi");
  117. if (midiJ)
  118. midiInput.fromJson(midiJ);
  119. }
  120. };
  121. struct MIDI_CCWidget : ModuleWidget {
  122. MIDI_CCWidget(MIDI_CC *module) {
  123. setModule(module);
  124. setPanel(APP->window->loadSvg(asset::system("res/Core/MIDI-CC.svg")));
  125. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
  126. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  127. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  128. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  129. addOutput(createOutput<PJ301MPort>(mm2px(Vec(3.894335, 73.344704)), module, MIDI_CC::CC_OUTPUT + 0));
  130. addOutput(createOutput<PJ301MPort>(mm2px(Vec(15.494659, 73.344704)), module, MIDI_CC::CC_OUTPUT + 1));
  131. addOutput(createOutput<PJ301MPort>(mm2px(Vec(27.094982, 73.344704)), module, MIDI_CC::CC_OUTPUT + 2));
  132. addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.693932, 73.344704)), module, MIDI_CC::CC_OUTPUT + 3));
  133. addOutput(createOutput<PJ301MPort>(mm2px(Vec(3.8943355, 84.945023)), module, MIDI_CC::CC_OUTPUT + 4));
  134. addOutput(createOutput<PJ301MPort>(mm2px(Vec(15.49466, 84.945023)), module, MIDI_CC::CC_OUTPUT + 5));
  135. addOutput(createOutput<PJ301MPort>(mm2px(Vec(27.094982, 84.945023)), module, MIDI_CC::CC_OUTPUT + 6));
  136. addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.693932, 84.945023)), module, MIDI_CC::CC_OUTPUT + 7));
  137. addOutput(createOutput<PJ301MPort>(mm2px(Vec(3.8943343, 96.543976)), module, MIDI_CC::CC_OUTPUT + 8));
  138. addOutput(createOutput<PJ301MPort>(mm2px(Vec(15.494659, 96.543976)), module, MIDI_CC::CC_OUTPUT + 9));
  139. addOutput(createOutput<PJ301MPort>(mm2px(Vec(27.09498, 96.543976)), module, MIDI_CC::CC_OUTPUT + 10));
  140. addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.693932, 96.543976)), module, MIDI_CC::CC_OUTPUT + 11));
  141. addOutput(createOutput<PJ301MPort>(mm2px(Vec(3.894335, 108.14429)), module, MIDI_CC::CC_OUTPUT + 12));
  142. addOutput(createOutput<PJ301MPort>(mm2px(Vec(15.49466, 108.14429)), module, MIDI_CC::CC_OUTPUT + 13));
  143. addOutput(createOutput<PJ301MPort>(mm2px(Vec(27.09498, 108.14429)), module, MIDI_CC::CC_OUTPUT + 14));
  144. addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.693932, 108.14429)), module, MIDI_CC::CC_OUTPUT + 15));
  145. typedef Grid16MidiWidget<CcChoice<MIDI_CC>> TMidiWidget;
  146. TMidiWidget *midiWidget = createWidget<TMidiWidget>(mm2px(Vec(3.399621, 14.837339)));
  147. midiWidget->box.size = mm2px(Vec(44, 54.667));
  148. midiWidget->setMidiPort(module ? &module->midiInput : NULL);
  149. midiWidget->setModule(module);
  150. addChild(midiWidget);
  151. }
  152. };
  153. // Use legacy slug for compatibility
  154. Model *modelMIDI_CC = createModel<MIDI_CC, MIDI_CCWidget>("MIDICCToCVInterface");