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.

189 lines
5.6KB

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