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.

387 lines
12KB

  1. // A Knobulator.
  2. //
  3. // Read the documentation at
  4. // https://github.com/apbianco/SerialRacker/blob/master/README.md
  5. #include "SerialRacker.hpp"
  6. #include "dsp/digital.hpp"
  7. #include "dsp/filter.hpp"
  8. namespace rack_plugin_SerialRacker {
  9. struct MidiMultiplexer : Module {
  10. // This parametrizes the module:
  11. // - The witdth of the input:
  12. //
  13. // <-- kNinput -->
  14. // (o) (o) (o) (o)
  15. //
  16. static const int kNInput = 4;
  17. // - The size of the output: (each output is made of line of
  18. // width kNinput)
  19. //
  20. // <-- kNinput -->
  21. // (o) (o) (o) (o) |
  22. // (o) (o) (o) (o) | kNOutput
  23. // (o) (o) (o) (o) |
  24. // (o) (o) (o) (o) |
  25. static const int kNOut = 4;
  26. enum ParamIds {
  27. CHANNELS_PARAM,
  28. PREV_INPUT_BUTTON,
  29. NEXT_INPUT_BUTTON,
  30. NUM_PARAMS
  31. };
  32. enum InputIds {
  33. NEXT_INPUT,
  34. ENUMS(IN_INPUT, kNInput),
  35. NUM_INPUTS
  36. };
  37. enum OutputIds {
  38. ENUMS(OUT_OUTPUT, kNOut * kNInput),
  39. NUM_OUTPUTS
  40. };
  41. enum LightIds {
  42. ENUMS(CHANNEL_LIGHT, kNOut),
  43. NUM_LIGHTS
  44. };
  45. // The clock input to move to the next output row and the button to
  46. // move to the next and previous channel.
  47. SchmittTrigger clockTrigger;
  48. SchmittTrigger nextTrigger;
  49. SchmittTrigger prevTrigger;
  50. // Limiter to help produce the final output
  51. SlewLimiter channelFilter[kNInput][kNOut];
  52. // Text fields: the name given to an output row and a text field to
  53. // display CV values.
  54. TextField *row_label = nullptr;
  55. TextField *cv_values = nullptr;
  56. // The current output row.
  57. int channel = 0;
  58. // True when json has finished loaded. This is used to handle the
  59. // refresh of values right after they have been loaded.
  60. bool json_loaded = false;
  61. // Sampled outut values - these are issues on the outputs each time
  62. // the module runs.
  63. float sampled_values[kNInput][kNOut] = {
  64. {0.0f, 0.0f, 0.0f, 0.0f},
  65. {0.0f, 0.0f, 0.0f, 0.0f},
  66. {0.0f, 0.0f, 0.0f, 0.0f},
  67. {0.0f, 0.0f, 0.0f, 0.0f}};
  68. // Sampled current knob values. They are used to determine whether
  69. // knob values have just changed (and which one changed.)
  70. float knob_values[4] = {-1.0f, -1.0f, -1.0f, -1.0f};
  71. // The CV values to display
  72. float cv_values_to_display[kNInput] = {-1.0f, -1.0f, -1.0f, -1.0f};
  73. // The row labels to display.
  74. std::string rowDisplay[kNOut] = {SET_ROW_NAME(1), SET_ROW_NAME(2),
  75. SET_ROW_NAME(3), SET_ROW_NAME(4)};
  76. MidiMultiplexer() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
  77. for (int i = 0; i < kNInput; ++i) {
  78. for (int j = 0; j < kNOut; ++j) {
  79. channelFilter[i][j].rise = 0.01f;
  80. channelFilter[i][j].fall = 0.01f;
  81. }
  82. }
  83. // Read the initial knob values
  84. for (int i = 0; i < kNInput; ++i) {
  85. knob_values[i] = inputs[IN_INPUT + i].value;
  86. cv_values_to_display[i] = knob_values[i];
  87. }
  88. }
  89. void step() override {
  90. // Determine current channel and whether the channel has changed.
  91. int new_channel = channel;
  92. int max_number_channels = kNOut - (int) params[CHANNELS_PARAM].value;
  93. if (clockTrigger.process(inputs[NEXT_INPUT].value / 2.f)) {
  94. new_channel++;
  95. }
  96. if (nextTrigger.process(params[NEXT_INPUT_BUTTON].value)) {
  97. new_channel++;
  98. }
  99. if (prevTrigger.process(params[PREV_INPUT_BUTTON].value)) {
  100. new_channel--;
  101. }
  102. // The maximum number of channels can be dynamically adjusted. We
  103. // recompute the limits now.
  104. if (new_channel < 0) {
  105. new_channel = max_number_channels - 1;
  106. }
  107. new_channel %= max_number_channels;
  108. bool channel_changed = (new_channel != channel ? true : false);
  109. channel = new_channel;
  110. // We're determining whether a knob has changed: we compare its
  111. // actual value with the value we had for it before. When we've
  112. // loaded json, we just have knob values taken from the sampled
  113. // values array which would have been loaded.
  114. bool knob_changed_values[kNInput] = {false, false, false, false};
  115. // This remembers whether, overall, a knob value changed.
  116. bool any_knob_changed = false;
  117. for (int i = 0; i < kNInput; ++i) {
  118. if (inputs[IN_INPUT + i].value != knob_values[i]) {
  119. knob_changed_values[i] = true;
  120. any_knob_changed = true;
  121. knob_values[i] = inputs[IN_INPUT + i].value;
  122. }
  123. }
  124. // Run the channel filters first
  125. for (int i = 0; i < kNInput; ++i) {
  126. for (int j = 0; j < kNOut; ++j) {
  127. channelFilter[i][j].process(channel == j ? 1.0f : 0.0f);
  128. }
  129. }
  130. // Set all outputs
  131. for (int i = 0; i < kNInput; ++i) {
  132. // We're going to process one column of input/output at a time,
  133. // corresponding to the input at hand:
  134. //
  135. // .---- i
  136. // V
  137. // (o) (o) (o)
  138. //
  139. // (o) |<---- j
  140. // (o) |
  141. // (o)* |<---- channel
  142. // (o) |
  143. //
  144. // Compute the output for each row of output and decide whether
  145. // we update or capture the output
  146. for (int j = 0; j < kNOut; ++j) {
  147. int output_index = OUT_OUTPUT + (j * kNInput) + i;
  148. // When we're processing the channel
  149. if (j == channel) {
  150. // If the current knob value changed, just use the output from the
  151. // knob and set the sampled value to be that value.
  152. if (knob_changed_values[i]) {
  153. // Compute the output and store it directly as a sampled
  154. // value.
  155. sampled_values[i][j] = (channelFilter[i][j].out *
  156. inputs[IN_INPUT + i].value);
  157. }
  158. // All output from the current channel are copied as values
  159. // to be displayed.
  160. cv_values_to_display[i] = sampled_values[i][j];
  161. }
  162. // An now just output the sampled value. This is valid for all
  163. // rows, regardless of whether it's the active one or not.
  164. outputs[output_index].value = sampled_values[i][j];
  165. }
  166. }
  167. // Visual updates:
  168. //
  169. // Set the lights
  170. for (int i = 0; i < kNOut; ++i) {
  171. lights[CHANNEL_LIGHT + i].setBrightness(channelFilter[channel][i].out);
  172. }
  173. // Set the values. We limit the output to 9.99 to avoid having the
  174. // output take two lines. We do that only when we've registered a
  175. // change: json loaded, a knob value changed or a channel changed.
  176. if (any_knob_changed || json_loaded || channel_changed) {
  177. char cv_values_string[1024];
  178. for (int i = 0; i < kNInput; ++i) {
  179. if (cv_values_to_display[i] > 9.99) {
  180. cv_values_to_display[i] = 9.99;
  181. }
  182. }
  183. sprintf(cv_values_string, "%4.2f %4.2f %4.2f %4.2f",
  184. cv_values_to_display[0], cv_values_to_display[1],
  185. cv_values_to_display[2], cv_values_to_display[3]);
  186. if (cv_values) {
  187. cv_values->text.assign(cv_values_string);
  188. }
  189. info("update");
  190. }
  191. // Set the label of the currently selected channel. We do that
  192. // only when we've registered a change: json loaded or the channel
  193. // has changed.
  194. if (row_label) {
  195. if (channel_changed || json_loaded) {
  196. row_label->text = rowDisplay[channel];
  197. } else {
  198. rowDisplay[channel] = row_label->text;
  199. }
  200. }
  201. // After the first update iteration, we can declare that JSON has
  202. // been loaded.
  203. json_loaded = false;
  204. }
  205. // Saving and loading the module configuration to a file. We define:
  206. //
  207. // sampled_values: An array of sampled values
  208. // output_row_labels: The name assigned (by the user) to the output row
  209. // channel_value: Which output row is currently active.
  210. json_t *toJson() override {
  211. json_t *root = json_object();
  212. // Save the sampled values
  213. json_t *values = json_array();
  214. for (int i = 0; i < kNInput; ++i) {
  215. for (int j = 0; j < kNOut; ++j) {
  216. json_t *value = json_real(sampled_values[i][j]);
  217. json_array_append_new(values, value);
  218. }
  219. }
  220. json_object_set_new(root, "sampled_values", values);
  221. // Save the output labels
  222. json_t *labels = json_array();
  223. for (int i = 0; i < kNOut; ++i) {
  224. json_t *label = json_string(rowDisplay[i].c_str());
  225. json_array_append_new(labels, label);
  226. }
  227. json_object_set_new(root, "output_row_labels", labels);
  228. // Save the current channel
  229. json_object_set_new(root, "channel_value", json_integer(channel));
  230. return root;
  231. }
  232. void fromJson(json_t *root) override {
  233. json_t *values = json_object_get(root, "sampled_values");
  234. if (values == nullptr) {
  235. return;
  236. }
  237. for (int i = 0; i < kNInput; ++i) {
  238. for (int j = 0; j < kNOut; ++j) {
  239. json_t *value = json_array_get(values, i * kNInput + j);
  240. if (value) {
  241. sampled_values[i][j] = json_real_value(value);
  242. }
  243. }
  244. }
  245. json_t *labels = json_object_get(root, "output_row_labels");
  246. for (int i = 0; i < kNOut; ++i) {
  247. json_t *label = json_array_get(labels, i);
  248. if (label) {
  249. rowDisplay[i] = json_string_value(label);
  250. }
  251. }
  252. json_t *channel_value = json_object_get(root, "channel_value");
  253. if (channel_value) {
  254. channel = json_integer_value(channel_value);
  255. }
  256. json_loaded = true;
  257. }
  258. };
  259. // Helper routine to display items just reading their X/Y positions in
  260. // InkScape.
  261. Vec AVec(float x, float y) {
  262. return Vec(x, 128.499 - y);
  263. }
  264. Vec PortVec(float x, float y) {
  265. return AVec(x, y + 8.467);
  266. }
  267. struct MidiMultiplexerWidget : ModuleWidget {
  268. MidiMultiplexerWidget(MidiMultiplexer *module);
  269. };
  270. MidiMultiplexerWidget::MidiMultiplexerWidget(MidiMultiplexer *module) :
  271. ModuleWidget(module) {
  272. typedef MidiMultiplexer TMidiMultiplexer;
  273. setPanel(SVG::load(assetPlugin(plugin, "res/MidiMultiplexer.svg")));
  274. // Place the screws.
  275. addChild(Widget::create<ScrewSilver>(
  276. Vec(RACK_GRID_WIDTH, 0)));
  277. addChild(Widget::create<ScrewSilver>(
  278. Vec(2 * MidiMultiplexer::kNOut * RACK_GRID_WIDTH, 0)));
  279. addChild(Widget::create<ScrewSilver>(
  280. Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  281. addChild(Widget::create<ScrewSilver>(
  282. Vec(2 * MidiMultiplexer::kNOut * RACK_GRID_WIDTH,
  283. RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  284. // First line: Input jacks
  285. for (int i = 0; i < MidiMultiplexer::kNOut; ++i) {
  286. addInput(Port::create<PJ301MPort>(
  287. mm2px(PortVec(5.121 + 10.682 * i, 100.365)), Port::INPUT, module,
  288. TMidiMultiplexer::IN_INPUT + i));
  289. }
  290. // Next line: advance to next/previous and its associated buttons,
  291. // the jack that allows to change to advance to the next row and the
  292. // switch selecting how deep the bank goes
  293. addParam(ParamWidget::create<CKD6>(
  294. mm2px(AVec(5.655-1, 84.118+8.408)), module,
  295. TMidiMultiplexer::PREV_INPUT_BUTTON, 0.0f, 1.0f, 0.0f));
  296. addInput(Port::create<PJ301MPort>(
  297. mm2px(PortVec(15.803, 83.522)), Port::INPUT, module,
  298. TMidiMultiplexer::NEXT_INPUT));
  299. addParam(ParamWidget::create<CKD6>(
  300. mm2px(AVec(26.923-1, 84.118+8.408)), module,
  301. TMidiMultiplexer::NEXT_INPUT_BUTTON, 0.0f, 1.0f, 0.0f));
  302. addParam(ParamWidget::create<CKSSThree>(
  303. mm2px(AVec(39.885, 82.663+10.054)), module,
  304. TMidiMultiplexer::CHANNELS_PARAM, 0.0f, 2.0f, 0.0f));
  305. // The output label field
  306. TextField *field = Widget::create<LedDisplayTextField>(
  307. mm2px(AVec(2.864, 71.041 + 10)));
  308. field->box.size = mm2px(Vec(45.02, 10));
  309. field->multiline = false;
  310. module->row_label = field;
  311. field->text = SET_ROW_NAME(1);
  312. addChild(field);
  313. // The CV values label field
  314. TextField *cv_values = Widget::create<LedDisplayTextField>(
  315. mm2px(AVec(2.864, 58.341 + 10)));
  316. cv_values->box.size = mm2px(Vec(45.02, 10));
  317. cv_values->multiline = false;
  318. module->cv_values = cv_values;
  319. cv_values->text = "0.00 0.00 0.00 0.00";
  320. addChild(cv_values);
  321. // The output ports
  322. for (int j = 0; j < 4; ++j) {
  323. for (int i = 0; i < MidiMultiplexer::kNOut; ++i) {
  324. addOutput(Port::create<PJ301MPort>(
  325. mm2px(PortVec(5.121 + 10 * i, 42.333 - (9.245 * j))),
  326. Port::OUTPUT, module, TMidiMultiplexer::OUT_OUTPUT + (4 * j) + i));
  327. }
  328. }
  329. // The LEDs
  330. const float led_x = (6.121 + 10 * (MidiMultiplexer::kNOut - 1) + 8.467);
  331. for (int i = 0; i < MidiMultiplexer::kNOut; ++i) {
  332. addChild(ModuleLightWidget::create<SmallLight<GreenLight>>(
  333. mm2px(PortVec(led_x, 42.333 - (9.245 * i))), module,
  334. TMidiMultiplexer::CHANNEL_LIGHT + i));
  335. }
  336. }
  337. } // namespace rack_plugin_SerialRacker
  338. using namespace rack_plugin_SerialRacker;
  339. RACK_PLUGIN_MODEL_INIT(SerialRacker, MidiMultiplexer) {
  340. Model *modelMidiMultiplexer = Model::create<MidiMultiplexer,
  341. MidiMultiplexerWidget>(
  342. "SerialRacker", "MidiMultiplexer", "Midi Multiplexer", UTILITY_TAG);
  343. return modelMidiMultiplexer;
  344. }