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.

284 lines
8.7KB

  1. #include "plugin.hpp"
  2. namespace rack {
  3. namespace core {
  4. struct MIDICC_CV : 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. /** [cc][channel] */
  20. int8_t ccValues[128][16];
  21. /** When LSB is enabled for CC 0-31, the MSB is stored here until the LSB is received.
  22. [cc][channel]
  23. */
  24. int8_t msbValues[32][16];
  25. int learningId;
  26. int8_t learnedCcs[16];
  27. /** [cell][channel] */
  28. dsp::ExponentialFilter valueFilters[16][16];
  29. bool smooth;
  30. bool mpeMode;
  31. bool lsbMode;
  32. MIDICC_CV() {
  33. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  34. for (int id = 0; id < 16; id++)
  35. configOutput(CC_OUTPUT + id, string::f("Cell %d", id + 1));
  36. for (int id = 0; id < 16; id++) {
  37. for (int c = 0; c < 16; c++) {
  38. valueFilters[id][c].setTau(1 / 30.f);
  39. }
  40. }
  41. onReset();
  42. }
  43. void onReset() override {
  44. for (uint8_t cc = 0; cc < 128; cc++) {
  45. for (int c = 0; c < 16; c++) {
  46. ccValues[cc][c] = 0;
  47. }
  48. }
  49. for (uint8_t cc = 0; cc < 32; cc++) {
  50. for (int c = 0; c < 16; c++) {
  51. msbValues[cc][c] = 0;
  52. }
  53. }
  54. learningId = -1;
  55. for (int id = 0; id < 16; id++) {
  56. learnedCcs[id] = id + 1;
  57. }
  58. midiInput.reset();
  59. smooth = true;
  60. mpeMode = false;
  61. lsbMode = false;
  62. }
  63. void process(const ProcessArgs& args) override {
  64. midi::Message msg;
  65. while (midiInput.tryPop(&msg, args.frame)) {
  66. processMessage(msg);
  67. }
  68. int channels = mpeMode ? 16 : 1;
  69. for (int id = 0; id < 16; id++) {
  70. if (!outputs[CC_OUTPUT + id].isConnected())
  71. continue;
  72. outputs[CC_OUTPUT + id].setChannels(channels);
  73. int8_t cc = learnedCcs[id];
  74. if (cc < 0) {
  75. outputs[CC_OUTPUT + id].clearVoltages();
  76. continue;
  77. }
  78. for (int c = 0; c < channels; c++) {
  79. int16_t cellValue = int16_t(ccValues[cc][c]) * 128;
  80. if (lsbMode && cc < 32)
  81. cellValue += ccValues[cc + 32][c];
  82. // Maximum value for 14-bit CC should be MSB=127 LSB=0, not MSB=127 LSB=127, because this is the maximum value that 7-bit controllers can send.
  83. float value = float(cellValue) / (128 * 127);
  84. // Support negative values because the gamepad MIDI driver generates nonstandard 8-bit CC values.
  85. value = clamp(value, -1.f, 1.f);
  86. // Detect behavior from MIDI buttons.
  87. if (smooth && std::fabs(valueFilters[id][c].out - value) < 1.f) {
  88. // Smooth value with filter
  89. valueFilters[id][c].process(args.sampleTime, value);
  90. }
  91. else {
  92. // Jump value
  93. valueFilters[id][c].out = value;
  94. }
  95. outputs[CC_OUTPUT + id].setVoltage(valueFilters[id][c].out * 10.f, c);
  96. }
  97. }
  98. }
  99. void processMessage(const midi::Message& msg) {
  100. switch (msg.getStatus()) {
  101. // cc
  102. case 0xb: {
  103. processCC(msg);
  104. } break;
  105. default: break;
  106. }
  107. }
  108. void processCC(const midi::Message& msg) {
  109. uint8_t c = mpeMode ? msg.getChannel() : 0;
  110. uint8_t cc = msg.getNote();
  111. if (msg.bytes.size() < 2)
  112. return;
  113. // Allow CC to be negative if the 8th bit is set.
  114. // The gamepad driver abuses this, for example.
  115. // Cast uint8_t to int8_t
  116. int8_t value = msg.bytes[2];
  117. // Learn
  118. if (learningId >= 0 && ccValues[cc][c] != value) {
  119. setLearnedCc(learningId, cc);
  120. learningId = -1;
  121. }
  122. if (lsbMode && cc < 32) {
  123. // Don't set MSB yet. Wait for LSB to be received.
  124. msbValues[cc][c] = value;
  125. }
  126. else if (lsbMode && 32 <= cc && cc < 64) {
  127. // Apply MSB when LSB is received
  128. ccValues[cc - 32][c] = msbValues[cc - 32][c];
  129. ccValues[cc][c] = value;
  130. }
  131. else {
  132. ccValues[cc][c] = value;
  133. }
  134. }
  135. void setLearnedCc(int id, int8_t cc) {
  136. // Unset IDs of similar CCs
  137. if (cc >= 0) {
  138. for (int id = 0; id < 16; id++) {
  139. if (learnedCcs[id] == cc)
  140. learnedCcs[id] = -1;
  141. }
  142. }
  143. learnedCcs[id] = cc;
  144. }
  145. json_t* dataToJson() override {
  146. json_t* rootJ = json_object();
  147. json_t* ccsJ = json_array();
  148. for (int i = 0; i < 16; i++) {
  149. json_array_append_new(ccsJ, json_integer(learnedCcs[i]));
  150. }
  151. json_object_set_new(rootJ, "ccs", ccsJ);
  152. // Remember values so users don't have to touch MIDI controller knobs when restarting Rack
  153. json_t* valuesJ = json_array();
  154. for (int i = 0; i < 128; i++) {
  155. // Note: Only save channel 0. Since MPE mode won't be commonly used, it's pointless to save all 16 channels.
  156. json_array_append_new(valuesJ, json_integer(ccValues[i][0]));
  157. }
  158. json_object_set_new(rootJ, "values", valuesJ);
  159. json_object_set_new(rootJ, "midi", midiInput.toJson());
  160. json_object_set_new(rootJ, "smooth", json_boolean(smooth));
  161. json_object_set_new(rootJ, "mpeMode", json_boolean(mpeMode));
  162. json_object_set_new(rootJ, "lsbMode", json_boolean(lsbMode));
  163. return rootJ;
  164. }
  165. void dataFromJson(json_t* rootJ) override {
  166. json_t* ccsJ = json_object_get(rootJ, "ccs");
  167. if (ccsJ) {
  168. for (int i = 0; i < 16; i++) {
  169. json_t* ccJ = json_array_get(ccsJ, i);
  170. if (ccJ)
  171. setLearnedCc(i, json_integer_value(ccJ));
  172. }
  173. }
  174. json_t* valuesJ = json_object_get(rootJ, "values");
  175. if (valuesJ) {
  176. for (int i = 0; i < 128; i++) {
  177. json_t* valueJ = json_array_get(valuesJ, i);
  178. if (valueJ) {
  179. ccValues[i][0] = json_integer_value(valueJ);
  180. }
  181. }
  182. }
  183. json_t* midiJ = json_object_get(rootJ, "midi");
  184. if (midiJ)
  185. midiInput.fromJson(midiJ);
  186. json_t* smoothJ = json_object_get(rootJ, "smooth");
  187. if (smoothJ)
  188. smooth = json_boolean_value(smoothJ);
  189. json_t* mpeModeJ = json_object_get(rootJ, "mpeMode");
  190. if (mpeModeJ)
  191. mpeMode = json_boolean_value(mpeModeJ);
  192. json_t* lsbEnabledJ = json_object_get(rootJ, "lsbMode");
  193. if (lsbEnabledJ)
  194. lsbMode = json_boolean_value(lsbEnabledJ);
  195. }
  196. };
  197. struct MIDICC_CVWidget : ModuleWidget {
  198. MIDICC_CVWidget(MIDICC_CV* module) {
  199. setModule(module);
  200. setPanel(createPanel(asset::system("res/Core/MIDICC_CV.svg"), asset::system("res/Core/MIDICC_CV-dark.svg")));
  201. addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, 0)));
  202. addChild(createWidget<ThemedScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  203. addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  204. addChild(createWidget<ThemedScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  205. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(8.189, 78.431)), module, MIDICC_CV::CC_OUTPUT + 0));
  206. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(19.739, 78.431)), module, MIDICC_CV::CC_OUTPUT + 1));
  207. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(31.289, 78.431)), module, MIDICC_CV::CC_OUTPUT + 2));
  208. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(42.838, 78.431)), module, MIDICC_CV::CC_OUTPUT + 3));
  209. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(8.189, 89.946)), module, MIDICC_CV::CC_OUTPUT + 4));
  210. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(19.739, 89.946)), module, MIDICC_CV::CC_OUTPUT + 5));
  211. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(31.289, 89.946)), module, MIDICC_CV::CC_OUTPUT + 6));
  212. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(42.838, 89.946)), module, MIDICC_CV::CC_OUTPUT + 7));
  213. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(8.189, 101.466)), module, MIDICC_CV::CC_OUTPUT + 8));
  214. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(19.739, 101.466)), module, MIDICC_CV::CC_OUTPUT + 9));
  215. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(31.289, 101.466)), module, MIDICC_CV::CC_OUTPUT + 10));
  216. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(42.838, 101.466)), module, MIDICC_CV::CC_OUTPUT + 11));
  217. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(8.19, 112.998)), module, MIDICC_CV::CC_OUTPUT + 12));
  218. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(19.739, 112.984)), module, MIDICC_CV::CC_OUTPUT + 13));
  219. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(31.289, 112.984)), module, MIDICC_CV::CC_OUTPUT + 14));
  220. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(42.839, 112.984)), module, MIDICC_CV::CC_OUTPUT + 15));
  221. typedef Grid16MidiDisplay<CcChoice<MIDICC_CV>> TMidiDisplay;
  222. TMidiDisplay* display = createWidget<TMidiDisplay>(mm2px(Vec(0.0, 13.039)));
  223. display->box.size = mm2px(Vec(50.8, 55.88));
  224. display->setMidiPort(module ? &module->midiInput : NULL);
  225. display->setModule(module);
  226. addChild(display);
  227. }
  228. void appendContextMenu(Menu* menu) override {
  229. MIDICC_CV* module = dynamic_cast<MIDICC_CV*>(this->module);
  230. menu->addChild(new MenuSeparator);
  231. menu->addChild(createBoolPtrMenuItem("Smooth CC", "", &module->smooth));
  232. menu->addChild(createBoolPtrMenuItem("MPE mode", "", &module->mpeMode));
  233. menu->addChild(createBoolPtrMenuItem("14-bit CC 0-31 / 32-63", "", &module->lsbMode));
  234. }
  235. };
  236. // Use legacy slug for compatibility
  237. Model* modelMIDICC_CV = createModel<MIDICC_CV, MIDICC_CVWidget>("MIDICCToCVInterface");
  238. } // namespace core
  239. } // namespace rack