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.

1116 lines
45KB

  1. ///////////////////////////////////////////////////////////////////////////////////////////////////////
  2. ////// RKD is a 4 HP module, a 8-output clock modulator (dividers), with table rotations. //////
  3. ///////////////////////////////////////////////////////////////////////////////////////////////////////
  4. ////// Inspired from existing Eurorack hardware RCD module, by 4ms Company. //////
  5. ////// Done with restricted 4ms Company permission (thank you 4ms). //////
  6. ////// This module uses its own algorithm (no original part of firmware code was used). //////
  7. ////// 4ms Company name, logo, RCD, RCDBO, Rotating Clock Divider & RCD Breakout as TRADEMARKED! //////
  8. ///////////////////////////////////////////////////////////////////////////////////////////////////////
  9. #include "Ohmer.hpp"
  10. #include <dsp/digital.hpp>
  11. namespace rack_plugin_Ohmer {
  12. struct RKD : Module {
  13. enum ParamIds {
  14. JUMPER_COUNTINGDOWN,
  15. JUMPER_GATE,
  16. JUMPER_MAXDIVRANGE16,
  17. JUMPER_MAXDIVRANGE32,
  18. JUMPER_SPREAD,
  19. JUMPER_AUTORESET,
  20. NUM_PARAMS
  21. };
  22. enum InputIds {
  23. ROTATE_INPUT,
  24. RESET_INPUT,
  25. CLK_INPUT,
  26. NUM_INPUTS
  27. };
  28. enum OutputIds {
  29. OUTPUT_1,
  30. OUTPUT_2,
  31. OUTPUT_3,
  32. OUTPUT_4,
  33. OUTPUT_5,
  34. OUTPUT_6,
  35. OUTPUT_7,
  36. OUTPUT_8,
  37. NUM_OUTPUTS
  38. };
  39. enum LightIds {
  40. LED_OUT_1,
  41. LED_OUT_2,
  42. LED_OUT_3,
  43. LED_OUT_4,
  44. LED_OUT_5,
  45. LED_OUT_6,
  46. LED_OUT_7,
  47. LED_OUT_8,
  48. LED_CLK,
  49. LED_RESET_RED,
  50. LED_RESET_ORANGE,
  51. LED_RESET_BLUE,
  52. NUM_LIGHTS
  53. };
  54. RKD() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {}
  55. // This flag indicates if jumpers (PCB) is visible, or not (only RKD module).
  56. bool bViewPCB = false;
  57. // This flag is set when module is running (CLK jack is wired).
  58. bool bCLKisActive = false;
  59. // Schmitt trigger, for RESET input port.
  60. SchmittTrigger RESET_Port;
  61. // Schmitt trigger, for CLK input port.
  62. SchmittTrigger CLK_Port;
  63. // This flag, when true, indicates the CLK rising edge at the current step.
  64. bool bIsRisingEdge = false;
  65. // Next incoming rising edge will be the first rising edge. Required to handle gate modes together with counting up or down.
  66. bool bIsEarlyRisingEdge = true;
  67. // This flag, when true, indicates the CLK falling edge at the current step.
  68. bool bIsFallingEdge = false;
  69. // This flag, when true, indicates the CLK is high (voltage equal or higher +2V).
  70. bool bCLKisHigh = false;
  71. // Assumed timeout at start.
  72. bool bCLKTimeOut = true;
  73. // Default jumpers/switches setting (false = Off, true = On).
  74. bool jmprCountingDown = false; // Factory is Off: Counting Up.
  75. bool jmprCountingDownPrevious = false;
  76. bool jmprGate = false; // Factory is Off: Trig.
  77. bool jmprGatePrevious = false;
  78. bool jmprMaxDivRange16 = true; // Factory is On (combined with Max-Div-Range 32, also On by default): Max Div amount = 8.
  79. bool jmprMaxDivRange16Previous = true;
  80. bool jmprMaxDivRange32 = true; // Factory is On (combined with Max-Div-Range 16, also On by default): Max Div amount = 8.
  81. bool jmprMaxDivRange32Previous = true;
  82. bool jmprSpread = false; // Factory is Off: Spread Off.
  83. bool jmprSpreadPrevious = false;
  84. bool jmprAutoReset = false; // Factory is Off = Auto-Reset Off.
  85. // Table set (0: Manufacturer, 1: Prime numbers, 2: Perfect squares, 3: Fibonacci sequence, 4: Triplet & 16ths).
  86. int tableSet = 0; // This variable is persistent (json).
  87. int tableSetPrev = 0; // Used to change detection across consecutive steps.
  88. // RKD default dividers table.
  89. int tblDividersR0[NUM_OUTPUTS] = {1, 2, 3, 4, 5, 6, 7, 8}; // default dividers (R+0) when using factory jumpers/switches setting.
  90. // Prime numbers base table.
  91. int tblPrimes[18] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61};
  92. // Perfect squares base table.
  93. int tblSquares[8] = {1, 4, 9, 16, 25, 36, 49, 64};
  94. // Fibonacci sequence base table.
  95. int tblFibonacci[9] = {1, 2, 3, 5, 8, 13, 21, 34, 55};
  96. // Triplet & 16ths table.
  97. int tblTripletSixteenths[8] = {1, 2, 3, 4, 8, 16, 32, 64};
  98. // Current (active) dividers table.
  99. int tblActiveDividers[NUM_OUTPUTS] = {1, 2, 3, 4, 5, 6, 7, 8};
  100. // Rotation dividers table (as prepared table).
  101. // Future dividers table, when rotation is required. Last value of this array will be used as "temp backup/restore", during rotation.
  102. int tblDividersRt[NUM_OUTPUTS + 1] = {1, 2, 3, 4, 5, 6, 7, 8, 0};
  103. // When set (armed), indicates table have been changed (eg after jumper/switch change/context-menu table).
  104. bool bTableChange = true;
  105. // When set (armed), prepare the rotation (set new dividers table).
  106. bool bDoRotation = false;
  107. // When set (armed), doing rotation on next rising-edge (coming on CLK input port).
  108. bool bDoRotationOnRisingEdge = false;
  109. // Displayed dividers into segment-LED displays (assuming defaults are "--" because the CLK isn't patched).
  110. char dispDiv1[3] = "--";
  111. char dispDiv2[3] = "--";
  112. char dispDiv3[3] = "--";
  113. char dispDiv4[3] = "--";
  114. char dispDiv5[3] = "--";
  115. char dispDiv6[3] = "--";
  116. char dispDiv7[3] = "--";
  117. char dispDiv8[3] = "--";
  118. // Maximum divide amount, default is 8 (for manufacter table).
  119. int maxDivAmount = 8;
  120. // ROTATE (CV) voltage.
  121. float cvRotate = 0.0f;
  122. int cvRotateTblIndex = 0;
  123. int cvRotateTblIndexPrevious = 0;
  124. // RESET voltage (trigger input port).
  125. float cvReset = 0.0f;
  126. bool bResetOnJack = false;
  127. bool bRegisteredResetOnJack = false;
  128. // Step-based (sample) counters.
  129. long long int currentStep = 0;
  130. long long int previousStep = 0;
  131. long long int expectedStep = 0;
  132. // Source (CLK) frequency flag (set when source frequency is known).
  133. bool bCLKFreqKnown = false;
  134. // Dividers counters (one per output jack).
  135. int divCounters[NUM_OUTPUTS] = {0, 0, 0, 0, 0, 0, 0, 0};
  136. // Global Auto-Reset sequence counter.
  137. int divCountersAutoReset = 0;
  138. // This flag is set on "Auto-Reset" event.
  139. bool bIsAutoReset = false;
  140. // This flag allow/inhibit Auto-Reset - temporary (Auto-Reset will not fired after a timeout/reset, or a reset done via RESET jack).
  141. bool bAllowAutoReset = false;
  142. // This flag is used only for blue RESET (Auto-Reset) LED (too avoid too long flashing LED).
  143. bool bAutoResetLEDfired = false;
  144. // True if output jack is fired (pulsing).
  145. bool bJackIsFired[NUM_OUTPUTS] = {false, false, false, false, false, false, false, false};
  146. // RESET LED afterglow (0: end of afterglow/unlit LED, other positive values indicate how many steps the LED is lit.
  147. int ledResetAfterglow = 0;
  148. //
  149. // Methods (void functions).
  150. // DSP method.
  151. void step() override;
  152. // While using "Initialize" from context-menu, or by using Ctrl+I/Command+I shortcut over module.
  153. void reset() override {
  154. // While using "Initialize" from context-menu (or by using Ctrl+I/Command+I over module).
  155. this->jmprCountingDown = false;
  156. jmprCountingDownPrevious = false;
  157. this->jmprGate = false;
  158. jmprGatePrevious = false;
  159. this->jmprMaxDivRange16 = true;
  160. jmprMaxDivRange16Previous = true;
  161. this->jmprMaxDivRange32 = true;
  162. jmprMaxDivRange32Previous = true;
  163. this->jmprSpread = false;
  164. jmprSpreadPrevious = false;
  165. this->jmprAutoReset = false;
  166. this->tableSet = 0;
  167. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  168. tblDividersR0[i] = i + 1; // Default dividers for all output ports (manufacturer table).
  169. // Default factory maximum divide amount is 8.
  170. maxDivAmount = 8;
  171. // Set module in timeout (sleeping) mode, to reset some variables/flags/counters...
  172. ModuleTimeOut();
  173. }
  174. void ModuleTimeOut() {
  175. // Reset Schmitt trigger used by RESET input jack.
  176. RESET_Port.reset();
  177. // Defining trigger thresholds for RESET input jack (rescale).
  178. //RESET_Port.setThresholds(0.2f, 3.5f);
  179. bResetOnJack = false;
  180. bRegisteredResetOnJack = false;
  181. // Reset Schmitt trigger used by CLK input jack.
  182. CLK_Port.reset();
  183. // Defining thresholds for CLK input jack (rescale).
  184. //CLK_Port.setThresholds(0.2f, 3.5f);
  185. // CLK is low (not wired = no signal = false).
  186. bCLKisHigh = false;
  187. // Reset ROTATE indexes.
  188. cvRotateTblIndex = 0;
  189. cvRotateTblIndexPrevious = 0;
  190. // Table rotation is on Initialize. For now we're using standard "R+0" base table.
  191. bDoRotation = true;
  192. bDoRotationOnRisingEdge = false;
  193. //
  194. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++) {
  195. divCounters[i] = 0; // Reset all dividers counters to 0 (for all output jacks).
  196. pulseOutputJack(i, false); // Be sure this jack isn't pulsing.
  197. }
  198. // Reset "Auto-Reset" counter and related flags.
  199. divCountersAutoReset = 0;
  200. bIsAutoReset = false;
  201. bAllowAutoReset = false;
  202. bAutoResetLEDfired = false;
  203. // Unlit CLK LED.
  204. lights[LED_CLK].value = 0.0f;
  205. // Source (CLK) frequency is reset (because CLK signal is lost/absent).
  206. bCLKFreqKnown = false;
  207. // Reset step-based counters.
  208. currentStep = 0;
  209. previousStep = 0;
  210. expectedStep = 0;
  211. // Early rising edge flag. When set, this meaning the next rising edge will be considered as early (first) rising edge. Required for gate modes!
  212. bIsEarlyRisingEdge = true;
  213. // Set time out flag (this will lit RESET red LED).
  214. bCLKTimeOut = true;
  215. }
  216. // Pulse manager.
  217. void pulseOutputJack(int givenOutputJack, bool bJackPulseState) {
  218. outputs[givenOutputJack].value = bJackPulseState ? 5.0f : 0.0f;
  219. lights[givenOutputJack].value = bJackPulseState ? 1.0f : 0.0f;
  220. bJackIsFired[givenOutputJack] = bJackPulseState;
  221. }
  222. // Persistence for extra datas via json functions (in particular setting defined via jumpers/switches, and table set).
  223. // These extra datas are saved into .vcv files (including "autosave.vcv").
  224. // Also these extra datas are "transfered" as soon as you duplicate (clone) module on the rack.
  225. json_t *toJson() override {
  226. json_t *rootJ = json_object();
  227. json_object_set_new(rootJ, "visiblePCB", json_boolean(bViewPCB)); // Is PCB is visible, or not.
  228. json_object_set_new(rootJ, "jmprCountingDown", json_boolean(jmprCountingDown)); // "Counting" jumper/switch.
  229. json_object_set_new(rootJ, "jmprGate", json_boolean(jmprGate)); // "Trig./Gate" jumper/switch.
  230. json_object_set_new(rootJ, "jmprMaxDivRange16", json_boolean(jmprMaxDivRange16)); // "Max-Div-Range 16" jumper/switch.
  231. json_object_set_new(rootJ, "jmprMaxDivRange32", json_boolean(jmprMaxDivRange32)); // "Max-Div-Range 32" jumper/switch.
  232. json_object_set_new(rootJ, "jmprSpread", json_boolean(jmprSpread)); // "Spread" jumper/switch.
  233. json_object_set_new(rootJ, "jmprAutoReset", json_boolean(jmprAutoReset)); // "Auto-Reset" jumper/switch.
  234. json_object_set_new(rootJ, "tableSet", json_integer(tableSet)); // Table set (0: Manufacturer, 1: Prime numbers, 2: Perfect squares, 3: Fibonacci sequence).
  235. return rootJ;
  236. }
  237. // Retrieving "json" persistent settings.
  238. void fromJson(json_t *rootJ) override {
  239. json_t *bViewPCBJ = json_object_get(rootJ, "visiblePCB");
  240. if (bViewPCBJ)
  241. bViewPCB = json_is_true(bViewPCBJ);
  242. json_t *jmprCountingDownJ = json_object_get(rootJ, "jmprCountingDown");
  243. if (jmprCountingDownJ)
  244. jmprCountingDown = json_is_true(jmprCountingDownJ);
  245. json_t *jmprGateJ = json_object_get(rootJ, "jmprGate");
  246. if (jmprGateJ)
  247. jmprGate = json_is_true(jmprGateJ);
  248. json_t *jmprMaxDivRange16J = json_object_get(rootJ, "jmprMaxDivRange16");
  249. if (jmprMaxDivRange16J)
  250. jmprMaxDivRange16 = json_is_true(jmprMaxDivRange16J);
  251. json_t *jmprMaxDivRange32J = json_object_get(rootJ, "jmprMaxDivRange32");
  252. if (jmprMaxDivRange32J)
  253. jmprMaxDivRange32 = json_is_true(jmprMaxDivRange32J);
  254. json_t *jmprSpreadJ = json_object_get(rootJ, "jmprSpread");
  255. if (jmprSpreadJ)
  256. jmprSpread = json_is_true(jmprSpreadJ);
  257. json_t *jmprAutoResetJ = json_object_get(rootJ, "jmprAutoReset");
  258. if (jmprAutoResetJ)
  259. jmprAutoReset = json_is_true(jmprAutoResetJ);
  260. json_t *tableSetJ = json_object_get(rootJ, "tableSet");
  261. if (tableSetJ)
  262. tableSet = json_integer_value(tableSetJ);
  263. }
  264. }; // End of module (object) definition.
  265. void RKD::step() {
  266. // DSP processing.
  267. // Reading jumpers/switches setting.
  268. jmprGate = (params[JUMPER_GATE].value == 1.0);
  269. jmprGatePrevious = jmprGate;
  270. jmprCountingDown = (params[JUMPER_COUNTINGDOWN].value == 1.0);
  271. // Gate mode only: if "Counting" is changed on the fly, invert firing status for each output jack.
  272. if ((jmprGate) && (jmprCountingDownPrevious != jmprCountingDown))
  273. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  274. bJackIsFired[i] = !bJackIsFired[i];
  275. jmprCountingDownPrevious = jmprCountingDown;
  276. jmprMaxDivRange16 = (params[JUMPER_MAXDIVRANGE16].value == 1.0);
  277. bTableChange = bTableChange || (jmprMaxDivRange16 != jmprMaxDivRange16Previous);
  278. jmprMaxDivRange16Previous = jmprMaxDivRange16;
  279. jmprMaxDivRange32 = (params[JUMPER_MAXDIVRANGE32].value == 1.0);
  280. bTableChange = bTableChange || (jmprMaxDivRange32 != jmprMaxDivRange32Previous);
  281. jmprMaxDivRange32Previous = jmprMaxDivRange32;
  282. jmprSpread = (params[JUMPER_SPREAD].value == 1.0);
  283. if (tableSet == 0)
  284. bTableChange = bTableChange || (jmprSpread != jmprSpreadPrevious); // Spread concerns manufacturer table only. Have no effect on other tables.
  285. jmprSpreadPrevious = jmprSpread;
  286. jmprAutoReset = (params[JUMPER_AUTORESET].value == 1.0);
  287. // Checking if table set was changed via context-menu.
  288. if (!bTableChange)
  289. bTableChange = (tableSetPrev != tableSet);
  290. // Is table change?
  291. if (bTableChange) {
  292. // Yep! assuming table have been changed (either by jumpers/switches setting, or table set via module's context-menu).
  293. if (tableSet == 0) {
  294. // Define new "Mav Div" amount, regardling current "Max-Div-Range 16", "Max-Div-Range 32" and "Spread" jumpers/switches setting.
  295. if (jmprMaxDivRange16 && jmprMaxDivRange32 && jmprSpread)
  296. maxDivAmount = 16; // Max-Div-Range 16 = On, Max-Div-Range 32 = On: Max Div = 8, but Spread On --> Max Div 16.
  297. else if (jmprMaxDivRange16 && jmprMaxDivRange32 && !jmprSpread)
  298. maxDivAmount = 8; // Max-Div-Range 16 = On, Max-Div-Range 32 = On, Spread Off: Max Div = 8 (it's the default factory).
  299. else if (jmprMaxDivRange16 && !jmprMaxDivRange32)
  300. maxDivAmount = 16; // Max-Div-Range 16 = On, Max-Div-Range 32 = Off: Max Div = 16.
  301. else if (!jmprMaxDivRange16 && jmprMaxDivRange32)
  302. maxDivAmount = 32; // Max-Div-Range 16 = Off, Max-Div-Range 32 = On: Max Div = 32.
  303. else maxDivAmount = 64; // Last possible remaining case is... Max-Div-Range 16 = Off, Max-Div-Range 32 = Off: Max Div = 64.
  304. }
  305. else maxDivAmount = 64; // Max Div = 64 for all "extra" tables.
  306. switch (tableSet) {
  307. case 0:
  308. // Now we're defining "future" table, but based on "Manufacturer" table.
  309. if (jmprMaxDivRange16 && jmprMaxDivRange32 && jmprSpread) {
  310. // Max-Div-Range 16 = On, Max-Div-Range 32 = On, Spread = On.
  311. // Special case of "musical" divisions (triplets, 16ths).
  312. tblDividersR0[OUTPUT_1] = 1;
  313. tblDividersR0[OUTPUT_2] = 2;
  314. tblDividersR0[OUTPUT_3] = 3;
  315. tblDividersR0[OUTPUT_4] = 4;
  316. tblDividersR0[OUTPUT_5] = 6;
  317. tblDividersR0[OUTPUT_6] = 8;
  318. tblDividersR0[OUTPUT_7] = 12;
  319. tblDividersR0[OUTPUT_8] = 16;
  320. }
  321. else if (jmprMaxDivRange16 && jmprMaxDivRange32 && !jmprSpread) {
  322. // Max-Div-Range 16 = On, Max-Div-Range 32 = On, Spread = Off (factory setting).
  323. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  324. tblDividersR0[i] = i + 1;
  325. }
  326. else if (jmprMaxDivRange16 && !jmprMaxDivRange32 && jmprSpread) {
  327. // Max-Div-Range 16 is On, Max-Div-Range 32 is Off, Spread is On.
  328. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  329. tblDividersR0[i] = 2 * i + 2;
  330. }
  331. else if (jmprMaxDivRange16 && !jmprMaxDivRange32 && !jmprSpread) {
  332. // Max-Div-Range 16 is On, Max-Div-Range 32 is Off, Spread is Off.
  333. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  334. tblDividersR0[i] = i + 9;
  335. }
  336. else if (!jmprMaxDivRange16 && jmprMaxDivRange32 && jmprSpread) {
  337. // Max-Div-Range 16 is Off, Max-Div-Range 32 is On, Spread is On.
  338. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  339. tblDividersR0[i] = 4 * i + 4;
  340. }
  341. else if (!jmprMaxDivRange16 && jmprMaxDivRange32 && !jmprSpread) {
  342. // Max-Div-Range 16 is Off, Max-Div-Range 32 is On, Spread is Off.
  343. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  344. tblDividersR0[i] = i + 17;
  345. }
  346. else if (!jmprMaxDivRange16 && !jmprMaxDivRange32 && jmprSpread) {
  347. // Max-Div-Range 16 is Off, Max-Div-Range 32 is Off, Spread is On.
  348. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  349. tblDividersR0[i] = 8 * i + 8;
  350. }
  351. else if (!jmprMaxDivRange16 && !jmprMaxDivRange32 && !jmprSpread) {
  352. // Last possibility: Max-Div-Range 16 is Off, Max-Div-Range 32 is Off, Spread is Off.
  353. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  354. tblDividersR0[i] = i + 33;
  355. }
  356. // Now we're defining "future" table.
  357. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  358. tblDividersRt[i] = tblDividersR0[i];
  359. break;
  360. case 1:
  361. // Now we're defining "future" table, but based on prime numbers.
  362. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  363. tblDividersRt[i] = tblPrimes[i];
  364. break;
  365. case 2:
  366. // Now we're defining "future" table, but based on perfect squares.
  367. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  368. tblDividersRt[i] = tblSquares[i];
  369. break;
  370. case 3:
  371. // Now we're defining "future" table, but based on Fibonacci sequence.
  372. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  373. tblDividersRt[i] = tblFibonacci[i];
  374. break;
  375. case 4:
  376. // Now we're defining "future" table, but based on triplet & 16ths.
  377. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  378. tblDividersRt[i] = tblTripletSixteenths[i];
  379. }
  380. // Clearing flag about table change "preparation".
  381. bTableChange = false;
  382. // Arming table rotation (required after table change).
  383. bDoRotation = true;
  384. bDoRotationOnRisingEdge = false;
  385. }
  386. tableSetPrev = tableSet;
  387. // CV ROTATE analysis: is module receive ROTATE voltage?
  388. if (inputs[ROTATE_INPUT].active) {
  389. // CV ROTATE voltage must be between 0V to +5V (inclusive) - otherwise, voltage is clipped.
  390. cvRotate = clamp(inputs[ROTATE_INPUT].value, 0.0f, 5.0f);
  391. }
  392. else cvRotate = 0.0f; // Assuming 0V while ROTATE input port isn't wired.
  393. // "cvRotateTblIndex" is a kind of index to dividers table.
  394. switch (tableSet) {
  395. case 0:
  396. // Manufacturer table is also based on "Max-Div" amount (jumpers J3-J4, or Max Div switches setting on BRK panel).
  397. cvRotateTblIndex = int(cvRotate / 5.0f * (float)(maxDivAmount));
  398. if (cvRotateTblIndex >= maxDivAmount)
  399. cvRotateTblIndex = maxDivAmount - 1; // Possible number of table rotations is based on Max Div amount!
  400. break;
  401. case 1:
  402. // Prime is based on 18 possible values, meaning 11 possible "sliding windows" to get access to 8 (consecutive) prime numbers (one per output jack).
  403. cvRotateTblIndex = int(cvRotate / 5.0f * 11.0f);
  404. if (cvRotateTblIndex >= 11)
  405. cvRotateTblIndex = 10;
  406. break;
  407. case 2:
  408. case 4:
  409. // "Perfect squares" and "Triplet & 16ths" are based on 8 possible values (one per output jack).
  410. cvRotateTblIndex = int(cvRotate / 5.0f * 8.0f);
  411. if (cvRotateTblIndex >= 8)
  412. cvRotateTblIndex = 7;
  413. break;
  414. case 3:
  415. // Fibonacci sequence is based on 8 possible values, 1 possible rotation (first), then 10 possible translations.
  416. cvRotateTblIndex = int(cvRotate / 5.0f * 11.0f);
  417. if (cvRotateTblIndex >= 11)
  418. cvRotateTblIndex = 10;
  419. break;
  420. }
  421. // If table index have changed (or rotation was previously set), rotation is required.
  422. bDoRotation = bDoRotation || (cvRotateTblIndexPrevious != cvRotateTblIndex);
  423. // Is table rotation required?
  424. if (bDoRotation) {
  425. // Clear "preparation" flag.
  426. bDoRotation = false;
  427. // Table rotation is required. Set (arm) another/next flag, by this way, real rotation will occur on next CLK rising-edge.
  428. bDoRotationOnRisingEdge = true;
  429. // Depending table set (Manufacturer, Prime numbers, Perfect squares, or Fibonacci sequence).
  430. switch (tableSet) {
  431. case 0:
  432. // Manufacturer table.
  433. if (jmprMaxDivRange16 && jmprMaxDivRange32 && jmprSpread) {
  434. // Particular case when Max-Divide-Amount is 8 by jumpers/switches --AND-- Spread is On...
  435. // In this special case, all ports will output standard "musical" divisions of 16ths & triplets.
  436. // We're using "cell moves" (like a shorting routine will do).
  437. // Firstly, duplicating base "R+0" table...
  438. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  439. tblDividersRt[i] = tblDividersR0[i];
  440. // ...then doing "j" moves (from bottom to top).
  441. for (int j = 0; j < cvRotateTblIndex; j++) {
  442. // Saving OUTPUT 1.
  443. tblDividersRt[8] = tblDividersRt[OUTPUT_1];
  444. tblDividersRt[OUTPUT_1] = tblDividersRt[OUTPUT_2];
  445. tblDividersRt[OUTPUT_2] = tblDividersRt[OUTPUT_3];
  446. tblDividersRt[OUTPUT_3] = tblDividersRt[OUTPUT_4];
  447. tblDividersRt[OUTPUT_4] = tblDividersRt[OUTPUT_5];
  448. tblDividersRt[OUTPUT_5] = tblDividersRt[OUTPUT_6];
  449. tblDividersRt[OUTPUT_6] = tblDividersRt[OUTPUT_7];
  450. tblDividersRt[OUTPUT_7] = tblDividersRt[OUTPUT_8];
  451. // Previously saved OUTPUT 1 is restored to... OUTPUT 8!
  452. tblDividersRt[OUTPUT_8] = tblDividersRt[8];
  453. }
  454. }
  455. else {
  456. // All other cases are standard shifting.
  457. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++) {
  458. // Duplicating "R+0" reference table, then adding number of rotation(s).
  459. tblDividersRt[i] = tblDividersR0[i] + cvRotateTblIndex;
  460. if (tblDividersRt[i] > maxDivAmount) {
  461. // Applying "modulo" if necessary!
  462. tblDividersRt[i] = (tblDividersRt[i] % maxDivAmount);
  463. }
  464. }
  465. }
  466. break;
  467. case 1:
  468. // Prime numbers-based table.
  469. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  470. tblDividersRt[i] = tblPrimes[i + cvRotateTblIndex];
  471. break;
  472. case 2:
  473. case 4:
  474. // "Perfect squares" or "Triplet & 16ths" based table.
  475. // Firstly, duplicating perfect squares table...
  476. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  477. if (tableSet == 2)
  478. tblDividersRt[i] = tblSquares[i];
  479. else tblDividersRt[i] = tblTripletSixteenths[i];
  480. // ...then doing "j" moves (from bottom to top).
  481. for (int j = 0; j < cvRotateTblIndex; j++) {
  482. // Saving OUTPUT 1.
  483. tblDividersRt[8] = tblDividersRt[OUTPUT_1];
  484. tblDividersRt[OUTPUT_1] = tblDividersRt[OUTPUT_2];
  485. tblDividersRt[OUTPUT_2] = tblDividersRt[OUTPUT_3];
  486. tblDividersRt[OUTPUT_3] = tblDividersRt[OUTPUT_4];
  487. tblDividersRt[OUTPUT_4] = tblDividersRt[OUTPUT_5];
  488. tblDividersRt[OUTPUT_5] = tblDividersRt[OUTPUT_6];
  489. tblDividersRt[OUTPUT_6] = tblDividersRt[OUTPUT_7];
  490. tblDividersRt[OUTPUT_7] = tblDividersRt[OUTPUT_8];
  491. // Previously saved OUTPUT 1 is restored to... OUTPUT 8!
  492. tblDividersRt[OUTPUT_8] = tblDividersRt[8];
  493. }
  494. break;
  495. case 3:
  496. // Fibonacci-based table.
  497. // Firstly, duplicating Fibonacci table.
  498. if (cvRotateTblIndex < 2) {
  499. // No rotation use 1, 2, 3, 5, 8, 13, 21, 34.
  500. // First rotation uses 2, 3, 5, 8, 13, 21, 34, 55.
  501. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  502. tblDividersRt[i] = tblFibonacci[i + cvRotateTblIndex];
  503. }
  504. else {
  505. // Next rotations are based on 2, 3, 5, 8, 13, 21, 34, 55, then applying +R shift.
  506. // On second and later rotation, then applying "+R" on all ports.
  507. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  508. tblDividersRt[i] = tblFibonacci[i + 1] + cvRotateTblIndex - 1;
  509. }
  510. }
  511. }
  512. // By default assuming this step isn't a CLK rising edge.
  513. bIsRisingEdge = false;
  514. // By default assuming this step isn't a CLK falling edge.
  515. bIsFallingEdge = false;
  516. // Module is running (enabled) as long as its CLK input jack is wired.
  517. bCLKisActive = inputs[CLK_INPUT].active;
  518. if (!bCLKisActive) {
  519. // Module in timeout (idle) mode.
  520. ModuleTimeOut();
  521. // CLK isn't connected: display "--" in all segment-LED displays (using -1).
  522. strcpy(dispDiv1, "--");
  523. strcpy(dispDiv2, "--");
  524. strcpy(dispDiv3, "--");
  525. strcpy(dispDiv4, "--");
  526. strcpy(dispDiv5, "--");
  527. strcpy(dispDiv6, "--");
  528. strcpy(dispDiv7, "--");
  529. strcpy(dispDiv8, "--");
  530. }
  531. else {
  532. // CLK input port is wired.
  533. // Update segment-LEDs to display the eight dividers alongside their jacks.
  534. // Based on "future" table!
  535. snprintf(dispDiv1, sizeof(dispDiv1), "%2i", tblDividersRt[OUTPUT_1]);
  536. snprintf(dispDiv2, sizeof(dispDiv2), "%2i", tblDividersRt[OUTPUT_2]);
  537. snprintf(dispDiv3, sizeof(dispDiv3), "%2i", tblDividersRt[OUTPUT_3]);
  538. snprintf(dispDiv4, sizeof(dispDiv4), "%2i", tblDividersRt[OUTPUT_4]);
  539. snprintf(dispDiv5, sizeof(dispDiv5), "%2i", tblDividersRt[OUTPUT_5]);
  540. snprintf(dispDiv6, sizeof(dispDiv6), "%2i", tblDividersRt[OUTPUT_6]);
  541. snprintf(dispDiv7, sizeof(dispDiv7), "%2i", tblDividersRt[OUTPUT_7]);
  542. snprintf(dispDiv8, sizeof(dispDiv8), "%2i", tblDividersRt[OUTPUT_8]);
  543. // Increment step number.
  544. currentStep++;
  545. // Using Schmitt trigger (SchmittTrigger is provided by dsp/digital.hpp) to detect triggers on CLK input jack.
  546. if (CLK_Port.process(rescale(inputs[CLK_INPUT].value, 0.2f, 3.5f, 0.0f, 1.0f))) {
  547. // It's a rising edge.
  548. bIsRisingEdge = true;
  549. // Disarm timeout flag.
  550. bCLKTimeOut = false;
  551. // CLK input is receiving a compliant trigger voltage (trigger on rising edge).
  552. // If rotation was requested, it becomes effective on received rising edge. Set the new current dividers table.
  553. if (bDoRotationOnRisingEdge) {
  554. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  555. tblActiveDividers[i] = tblDividersRt[i];
  556. // Define adaptative "Max Div" amount, but only for "Primes numbers" table!
  557. // "Max Div" can be 32 or 64, depending highest divider.
  558. if (tableSet == 1) {
  559. if (tblActiveDividers[7] > 32)
  560. maxDivAmount = 64;
  561. else maxDivAmount = 32;
  562. }
  563. // Rotation was done.
  564. bDoRotationOnRisingEdge = false;
  565. }
  566. // Frequency of source CLK.
  567. if (previousStep == 0) {
  568. // Source CLK frequency is unknown.
  569. bCLKFreqKnown = false;
  570. expectedStep = 0;
  571. }
  572. else {
  573. // But perhaps this rising edge comes too early (source frequency is increased).
  574. if (bCLKFreqKnown) {
  575. if (currentStep != expectedStep) {
  576. bCLKFreqKnown = false;
  577. expectedStep = 0;
  578. currentStep = 1;
  579. }
  580. else {
  581. // Source CLK frequency is stable.
  582. bCLKFreqKnown = true;
  583. expectedStep = currentStep - previousStep + currentStep;
  584. }
  585. }
  586. else {
  587. // Source CLK frequency was unknow at previous rising edge, but for now it can be established on this (next) rising edge.
  588. bCLKFreqKnown = true;
  589. expectedStep = currentStep - previousStep + currentStep;
  590. }
  591. }
  592. // Of course, on rising edgen the CLK signal is high!
  593. bCLKisHigh = true;
  594. // ...and this current step (on rising edge) becomes... previous step, for next rising edge detection!
  595. previousStep = currentStep;
  596. }
  597. else {
  598. // At this point it's not a rising edge (maybe incoming signal is already at high state, or low, or a falling edge).
  599. // Is it a falling edge?
  600. if (bCLKisHigh && (clamp(inputs[CLK_INPUT].value, 0.0f, 15.0f) < 0.2f)) {
  601. // At previous step it was high, but now is low, meaning this step is a falling edge.
  602. bCLKisHigh = false; // Below 2V, disarm the flag to stop counting.
  603. // It's a falling edge.
  604. bIsFallingEdge = true;
  605. }
  606. // Also, be sure the CLK source frequency wasn't lower (slower signal).
  607. if (expectedStep != 0) {
  608. if (currentStep >= expectedStep) {
  609. // CLK frequency is lower (slower), or... no more signal (kind of "timeout").
  610. if (bCLKFreqKnown) {
  611. // If the frequency was previously known, we give an extra delay prior timeout.
  612. expectedStep = currentStep - previousStep + currentStep; // Give an extra delay prior timeout.
  613. bCLKFreqKnown = false;
  614. }
  615. else ModuleTimeOut(); // Timeout: module becomes "idle".
  616. }
  617. }
  618. }
  619. }
  620. // "CLK" white LED state.
  621. lights[LED_CLK].value = bCLKisHigh ? 1.0f : 0.0f;
  622. // Using Schmitt trigger (SchmittTrigger is provided by dsp/digital.hpp) to register incoming trigger signal on RESET jack (anytime).
  623. if (!bRegisteredResetOnJack)
  624. bRegisteredResetOnJack = RESET_Port.process(rescale(inputs[RESET_INPUT].value, 0.2f, 3.5f, 0.0f, 1.0f));
  625. // Registered RESET on jack becomes effective on next incoming rising edge.
  626. if (bIsRisingEdge) {
  627. // Clearing "Auto-Reset" flag.
  628. bIsAutoReset = false;
  629. // Is RESET jack was triggered?
  630. bResetOnJack = bRegisteredResetOnJack;
  631. bRegisteredResetOnJack = false;
  632. // Global dividers counters reset for all output jacks, due to received pulse on "RESET" jack.
  633. if (bResetOnJack) {
  634. // Reset Schmitt trigger used by RESET input jack.
  635. RESET_Port.reset();
  636. // This will "force" disabling RESET LED afterglow, in order to lit orange LED.
  637. ledResetAfterglow = 0;
  638. // Reset dividers counters.
  639. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  640. divCounters[i] = 0;
  641. // Temporary inhibit Auto-Reset.
  642. bAllowAutoReset = false;
  643. // Restart Auto-Reset sequence (counter).
  644. divCountersAutoReset = 0;
  645. }
  646. }
  647. else bResetOnJack = false;
  648. // Determine initial pulsing state, only on early rising edge!
  649. if (bIsEarlyRisingEdge)
  650. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  651. bJackIsFired[i] = !jmprCountingDown;
  652. // Auto-reset and pulsing management (for each output jack).
  653. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++) {
  654. if (bIsRisingEdge) {
  655. // On rising edge.
  656. // Is the "Auto-Reset", for this current jack, must be applied first, or not?
  657. // "Auto-Reset" may occurs only if "Auto-Reset" jumper/switch is On, not temporary disabled, and Auto-Reset counter is 0, and for certain dividers.
  658. if ((jmprAutoReset) && (bAllowAutoReset) && (divCountersAutoReset == 0) && (((2 * maxDivAmount) % tblActiveDividers[i]) != 0)) {
  659. divCounters[i] = 0;
  660. bIsAutoReset = true;
  661. bAutoResetLEDfired = true;
  662. }
  663. // Pulse generators.
  664. if (jmprGate) {
  665. // Gate modes.
  666. if ((tblActiveDividers[i] % 2) == 0) {
  667. // On all even dividers...
  668. if (divCounters[i] % (tblActiveDividers[i] / 2) == 0)
  669. pulseOutputJack(i, !bJackIsFired[i]); // Invert state of pulse.
  670. }
  671. else {
  672. // On all odd dividers...
  673. // /1 in (gate modes) must be considered differently, in fact like... trigger mode! (TBC).
  674. if (tblActiveDividers[i] == 1)
  675. pulseOutputJack(i, true); // Degraded" pulse while source frequency isn't stable.
  676. else if ((divCounters[i] % tblActiveDividers[i]) == 0)
  677. pulseOutputJack(i, !bJackIsFired[i]); // Invert state of pulse.
  678. }
  679. }
  680. else {
  681. // Trigger modes (default).
  682. if (jmprCountingDown)
  683. pulseOutputJack(i, (divCounters[i] % tblActiveDividers[i]) == 0); // Counting down mode.
  684. else pulseOutputJack(i, ((divCounters[i] + 1) % tblActiveDividers[i]) == 0); // Counting up mode (default).
  685. }
  686. // Advances next divider counter for this jack.
  687. // Increment divider counter...
  688. divCounters[i]++;
  689. // ...and restart to 0 when division value (for current jack) is reached (or over).
  690. if (divCounters[i] >= tblActiveDividers[i])
  691. divCounters[i] = 0;
  692. }
  693. else if (bIsFallingEdge) {
  694. // On falling edge (only).
  695. if (jmprGate) {
  696. // Gate mode.
  697. if ((tblActiveDividers[i] % 2) == 1) {
  698. if (((divCounters[i] + ((tblActiveDividers[i] - 1) / 2)) % tblActiveDividers[i]) == 0)
  699. pulseOutputJack(i, !bJackIsFired[i]); // Invert state of pulse.
  700. }
  701. }
  702. else pulseOutputJack(i, false); // Trigger mode: always stop pulsing on falling edge (for any divider).
  703. }
  704. }
  705. // Advance "Auto-Reset" counter (global, on rising edges only).
  706. if (bIsRisingEdge) {
  707. // Increment "Auto-Reset" sequence counter...
  708. divCountersAutoReset++;
  709. // ...and restart to 0 when "2 x Max-Div" is reached.
  710. if ((divCountersAutoReset % (2 * maxDivAmount)) == 0)
  711. divCountersAutoReset = 0;
  712. // Now next rising edge aren't first rising edge.
  713. bIsEarlyRisingEdge = false;
  714. // Allow next Auto-Reset events.
  715. bAllowAutoReset = true;
  716. }
  717. else bResetOnJack = false;
  718. // "RESET" small red LED management (having afterglow).
  719. if (ledResetAfterglow > 0) {
  720. ledResetAfterglow--;
  721. }
  722. else {
  723. if ((bIsAutoReset && bAutoResetLEDfired) || bResetOnJack || bCLKTimeOut) {
  724. if (bCLKTimeOut) {
  725. // Highest priority LED.
  726. // Setup counter for red LED afterglow.
  727. ledResetAfterglow = round(engineGetSampleRate() / 4);
  728. // Lit "RESET" LED (red) on CLK timeout.
  729. lights[LED_RESET_RED].value = 1.0f;
  730. lights[LED_RESET_ORANGE].value = 0.0f;
  731. lights[LED_RESET_BLUE].value = 0.0f;
  732. }
  733. else if (bResetOnJack) {
  734. // Setup counter for orange LED afterglow.
  735. ledResetAfterglow = round(engineGetSampleRate() / 6);
  736. // Lit "RESET" LED (orange) on RESET via jack.
  737. lights[LED_RESET_RED].value = 0.0f;
  738. lights[LED_RESET_ORANGE].value = 1.0f;
  739. lights[LED_RESET_BLUE].value = 0.0f;
  740. }
  741. else {
  742. // Setup counter for blue LED afterglow.
  743. ledResetAfterglow = round(engineGetSampleRate() / 8);
  744. // Lit "RESET" LED (blue) on "Auto-Reset" event.
  745. lights[LED_RESET_RED].value = 0.0f;
  746. lights[LED_RESET_ORANGE].value = 0.0f;
  747. lights[LED_RESET_BLUE].value = 1.0f;
  748. bAutoResetLEDfired = false;
  749. }
  750. }
  751. else {
  752. // Unlit "RESET" LEDs.
  753. lights[LED_RESET_RED].value = 0.0f;
  754. lights[LED_RESET_ORANGE].value = 0.0f;
  755. lights[LED_RESET_BLUE].value = 0.0f;
  756. }
  757. }
  758. // Update current rotation index to become "previous". This will be useful to detect possible "table rotation" on next step.
  759. cvRotateTblIndexPrevious = cvRotateTblIndex;
  760. } // End of DSP processing...
  761. // Segment-LED displays.
  762. struct RKD_Displays : TransparentWidget {
  763. RKD *module;
  764. std::shared_ptr<Font> font;
  765. RKD_Displays() {
  766. font = Font::load(assetPlugin(plugin, "res/fonts/digital-readout.medium.ttf"));
  767. }
  768. void updateD1(NVGcontext *vg, Vec pos, char* dispDiv1) {
  769. nvgFontSize(vg, 14);
  770. nvgFontFaceId(vg, font->handle);
  771. nvgTextLetterSpacing(vg, -1);
  772. nvgFillColor(vg, nvgTransRGBA(nvgRGB(0x6c, 0xff, 0xff), 0xff));
  773. nvgText(vg, pos.x + 16, pos.y + 29.5, dispDiv1, NULL);
  774. }
  775. void updateD2(NVGcontext *vg, Vec pos, char* dispDiv2) {
  776. nvgFontSize(vg, 14);
  777. nvgFontFaceId(vg, font->handle);
  778. nvgTextLetterSpacing(vg, -1);
  779. nvgFillColor(vg, nvgTransRGBA(nvgRGB(0x6c, 0xff, 0xff), 0xff));
  780. nvgText(vg, pos.x + 16, pos.y + 59.5, dispDiv2, NULL);
  781. }
  782. void updateD3(NVGcontext *vg, Vec pos, char* dispDiv3) {
  783. nvgFontSize(vg, 14);
  784. nvgFontFaceId(vg, font->handle);
  785. nvgTextLetterSpacing(vg, -1);
  786. nvgFillColor(vg, nvgTransRGBA(nvgRGB(0x6c, 0xff, 0xff), 0xff));
  787. nvgText(vg, pos.x + 16, pos.y + 89.5, dispDiv3, NULL);
  788. }
  789. void updateD4(NVGcontext *vg, Vec pos, char* dispDiv4) {
  790. nvgFontSize(vg, 14);
  791. nvgFontFaceId(vg, font->handle);
  792. nvgTextLetterSpacing(vg, -1);
  793. nvgFillColor(vg, nvgTransRGBA(nvgRGB(0x6c, 0xff, 0xff), 0xff));
  794. nvgText(vg, pos.x + 16, pos.y + 119.5, dispDiv4, NULL);
  795. }
  796. void updateD5(NVGcontext *vg, Vec pos, char* dispDiv5) {
  797. nvgFontSize(vg, 14);
  798. nvgFontFaceId(vg, font->handle);
  799. nvgTextLetterSpacing(vg, -1);
  800. nvgFillColor(vg, nvgTransRGBA(nvgRGB(0x6c, 0xff, 0xff), 0xff));
  801. nvgText(vg, pos.x + 16, pos.y + 149.5, dispDiv5, NULL);
  802. }
  803. void updateD6(NVGcontext *vg, Vec pos, char* dispDiv6) {
  804. nvgFontSize(vg, 14);
  805. nvgFontFaceId(vg, font->handle);
  806. nvgTextLetterSpacing(vg, -1);
  807. nvgFillColor(vg, nvgTransRGBA(nvgRGB(0x6c, 0xff, 0xff), 0xff));
  808. nvgText(vg, pos.x + 16, pos.y + 179.5, dispDiv6, NULL);
  809. }
  810. void updateD7(NVGcontext *vg, Vec pos, char* dispDiv7) {
  811. nvgFontSize(vg, 14);
  812. nvgFontFaceId(vg, font->handle);
  813. nvgTextLetterSpacing(vg, -1);
  814. nvgFillColor(vg, nvgTransRGBA(nvgRGB(0x6c, 0xff, 0xff), 0xff));
  815. nvgText(vg, pos.x + 16, pos.y + 209.5, dispDiv7, NULL);
  816. }
  817. void updateD8(NVGcontext *vg, Vec pos, char* dispDiv8) {
  818. nvgFontSize(vg, 14);
  819. nvgFontFaceId(vg, font->handle);
  820. nvgTextLetterSpacing(vg, -1);
  821. nvgFillColor(vg, nvgTransRGBA(nvgRGB(0x6c, 0xff, 0xff), 0xff));
  822. nvgText(vg, pos.x + 16, pos.y + 239.5, dispDiv8, NULL);
  823. }
  824. void draw(NVGcontext *vg) override {
  825. updateD1(vg, Vec(0, box.size.y - 150), module->dispDiv1);
  826. updateD2(vg, Vec(0, box.size.y - 150), module->dispDiv2);
  827. updateD3(vg, Vec(0, box.size.y - 150), module->dispDiv3);
  828. updateD4(vg, Vec(0, box.size.y - 150), module->dispDiv4);
  829. updateD5(vg, Vec(0, box.size.y - 150), module->dispDiv5);
  830. updateD6(vg, Vec(0, box.size.y - 150), module->dispDiv6);
  831. updateD7(vg, Vec(0, box.size.y - 150), module->dispDiv7);
  832. updateD8(vg, Vec(0, box.size.y - 150), module->dispDiv8);
  833. }
  834. };
  835. struct RKDWidget : ModuleWidget {
  836. // Panels (RKD production, PCB/jumpers access).
  837. SVGPanel *panelRKD;
  838. SVGPanel *panelPCB;
  839. // Silver Torx screws.
  840. Widget *topScrewSilver;
  841. Widget *bottomScrewSilver;
  842. // Jumper shunts.
  843. ParamWidget *jumperCountingDown;
  844. ParamWidget *jumperGate;
  845. ParamWidget *jumperMaxDivRange16;
  846. ParamWidget *jumperMaxDivRange32;
  847. ParamWidget *jumperSpread;
  848. ParamWidget *jumperAutoReset;
  849. //
  850. RKDWidget(RKD *module);
  851. void step() override;
  852. // Action for "Randomize", from context-menu, is (for now) bypassed.
  853. void randomize() override {
  854. };
  855. // RKD module uses a custom context-menu, to access to jumpers (setting), and to different table sets.
  856. Menu* createContextMenu() override;
  857. };
  858. RKDWidget::RKDWidget(RKD *module) : ModuleWidget(module) {
  859. box.size = Vec(4 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT);
  860. // RKD module (installed in rack).
  861. panelRKD = new SVGPanel();
  862. panelRKD->box.size = box.size;
  863. panelRKD->setBackground(SVG::load(assetPlugin(plugin, "res/RKD.svg")));
  864. addChild(panelRKD);
  865. // RKD module (viewing PCB to access/change jumpers).
  866. panelPCB = new SVGPanel();
  867. panelPCB->box.size = box.size;
  868. panelPCB->setBackground(SVG::load(assetPlugin(plugin, "res/RKD_PCB.svg")));
  869. addChild(panelPCB);
  870. // Like original RKD module, we're using only two screws.
  871. // Top screw (removed while PCB is shown).
  872. topScrewSilver = Widget::create<Torx_Silver>(Vec(RACK_GRID_WIDTH, 0));
  873. addChild(topScrewSilver);
  874. // Bottom screw (removed while PCB is shown).
  875. bottomScrewSilver = Widget::create<Torx_Silver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH));
  876. addChild(bottomScrewSilver);
  877. // Segment-LED displays.
  878. {
  879. RKD_Displays *display = new RKD_Displays();
  880. display->module = module;
  881. display->box.pos = Vec(0, 0);
  882. display->box.size = Vec(box.size.x, 234);
  883. addChild(display);
  884. }
  885. // Input jack: ROTATE.
  886. addInput(Port::create<CL1362_In_RR>(Vec(2.4, 35), Port::INPUT, module, RKD::ROTATE_INPUT));
  887. // Input jack: RESET.
  888. addInput(Port::create<CL1362_In_RR>(Vec(32.2, 35), Port::INPUT, module, RKD::RESET_INPUT));
  889. // Input jack: CLK.
  890. addInput(Port::create<CL1362_In>(Vec(30.8, 66), Port::INPUT, module, RKD::CLK_INPUT));
  891. // Output jack: 1+R.
  892. addOutput(Port::create<CL1362_Out>(Vec(30.8, 96), Port::OUTPUT, module, RKD::OUTPUT_1));
  893. // Output jack: 2+R.
  894. addOutput(Port::create<CL1362_Out>(Vec(30.8, 126), Port::OUTPUT, module, RKD::OUTPUT_2));
  895. // Output jack: 3+R.
  896. addOutput(Port::create<CL1362_Out>(Vec(30.8, 156), Port::OUTPUT, module, RKD::OUTPUT_3));
  897. // Output jack: 4+R.
  898. addOutput(Port::create<CL1362_Out>(Vec(30.8, 186), Port::OUTPUT, module, RKD::OUTPUT_4));
  899. // Output jack: 5+R.
  900. addOutput(Port::create<CL1362_Out>(Vec(30.8, 216), Port::OUTPUT, module, RKD::OUTPUT_5));
  901. // Output jack: 6+R.
  902. addOutput(Port::create<CL1362_Out>(Vec(30.8, 246), Port::OUTPUT, module, RKD::OUTPUT_6));
  903. // Output jack: 7+R.
  904. addOutput(Port::create<CL1362_Out>(Vec(30.8, 276), Port::OUTPUT, module, RKD::OUTPUT_7));
  905. // Output jack: 8+R.
  906. addOutput(Port::create<CL1362_Out>(Vec(30.8, 306), Port::OUTPUT, module, RKD::OUTPUT_8));
  907. // White LED for CLK.
  908. addChild(ModuleLightWidget::create<MediumLight<RKDWhiteLight>>(Vec(3.7, 73.4), module, RKD::LED_CLK));
  909. // Red LED for output 1+R.
  910. addChild(ModuleLightWidget::create<MediumLight<RedLight>>(Vec(3.7, 103.4), module, RKD::LED_OUT_1));
  911. // Orange LED for output 2+R.
  912. addChild(ModuleLightWidget::create<MediumLight<RKDOrangeLight>>(Vec(3.7, 133.4), module, RKD::LED_OUT_2));
  913. // Yellow LED for output 3+R.
  914. addChild(ModuleLightWidget::create<MediumLight<YellowLight>>(Vec(3.7, 163.4), module, RKD::LED_OUT_3));
  915. // Green LED for output 4+R.
  916. addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(3.7, 193.4), module, RKD::LED_OUT_4));
  917. // Green LED for output 5+R.
  918. addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(3.7, 223.4), module, RKD::LED_OUT_5));
  919. // Blue (cyan) LED for output 6+R.
  920. addChild(ModuleLightWidget::create<MediumLight<BlueLight>>(Vec(3.7, 253.4), module, RKD::LED_OUT_6));
  921. // Purple LED for output 7+R.
  922. addChild(ModuleLightWidget::create<MediumLight<RKDPurpleLight>>(Vec(3.7, 283.4), module, RKD::LED_OUT_7));
  923. // White LED for output 8+R.
  924. addChild(ModuleLightWidget::create<MediumLight<RKDWhiteLight>>(Vec(3.7, 313.4), module, RKD::LED_OUT_8));
  925. // Small red LED near RESET input jack (tri-colored: red, orange or blue).
  926. addChild(ModuleLightWidget::create<SmallLight<RedOrangeBlueLight>>(Vec(50.8, 33.2), module, RKD::LED_RESET_RED));
  927. // Jumper "Counting Up/Down". By default Off (counting up).
  928. jumperCountingDown = ParamWidget::create<RKD_Jumper>(Vec(10.6, 349.8), module, RKD::JUMPER_COUNTINGDOWN, 0.0, 1.0, 0.0);
  929. addParam(jumperCountingDown);
  930. // Jumper "Trig/Gate". By default Off (trigger).
  931. jumperGate = ParamWidget::create<RKD_Jumper>(Vec(18.3, 349.8), module, RKD::JUMPER_GATE, 0.0, 1.0, 0.0);
  932. addParam(jumperGate);
  933. // Jumper "Max-Div-Range 16". By default On. Working together with "Max-Div-Range 32".
  934. jumperMaxDivRange16 = ParamWidget::create<RKD_Jumper>(Vec(26.4, 349.8), module, RKD::JUMPER_MAXDIVRANGE16, 0.0, 1.0, 1.0);
  935. addParam(jumperMaxDivRange16);
  936. // Jumper "Max-Div-Range 32". By default On. Working together with "Max-Div-Range 16".
  937. jumperMaxDivRange32 = ParamWidget::create<RKD_Jumper>(Vec(34.4, 349.8), module, RKD::JUMPER_MAXDIVRANGE32, 0.0, 1.0, 1.0);
  938. addParam(jumperMaxDivRange32);
  939. // Jumper "Spread Off/On". By default Off (spread off).
  940. jumperSpread = ParamWidget::create<RKD_Jumper>(Vec(42.4, 349.8), module, RKD::JUMPER_SPREAD, 0.0, 1.0, 0.0);
  941. addParam(jumperSpread);
  942. // Jumper "Auto-Reset Off/On". By default Off (auto-reset is disabled).
  943. jumperAutoReset = ParamWidget::create<RKD_Jumper>(Vec(50.4, 349.8), module, RKD::JUMPER_AUTORESET, 0.0, 1.0, 0.0);
  944. addParam(jumperAutoReset);
  945. }
  946. void RKDWidget::step() {
  947. RKD *rkd = dynamic_cast<RKD*>(module);
  948. assert(rkd);
  949. // Top and bottom Silver Torx screws will are hidden while PCB is visible.
  950. topScrewSilver->visible = !rkd->bViewPCB;
  951. bottomScrewSilver->visible = !rkd->bViewPCB;
  952. // Jumper shunts are visible while... PCB is visible.
  953. jumperCountingDown->visible = rkd->bViewPCB;
  954. jumperGate->visible = rkd->bViewPCB;
  955. jumperMaxDivRange16->visible = rkd->bViewPCB;
  956. jumperMaxDivRange32->visible = rkd->bViewPCB;
  957. jumperSpread->visible = rkd->bViewPCB;
  958. jumperAutoReset->visible = rkd->bViewPCB;
  959. // Is regular RKD panel visible, or RKD with PCB/jumpers?
  960. panelRKD->visible = !rkd->bViewPCB;
  961. panelPCB->visible = rkd->bViewPCB;
  962. ModuleWidget::step();
  963. };
  964. // CONTEXT-MENU.
  965. // Context-menu entry routine: RKD module as production (regular) mode.
  966. struct ProdMenuItem : MenuItem {
  967. RKD *rkd;
  968. void onAction(EventAction &e) override {
  969. rkd->bViewPCB = false;
  970. }
  971. void step() override {
  972. rightText = (rkd->bViewPCB == false) ? "✔" : "";
  973. MenuItem::step();
  974. }
  975. };
  976. // Context-menu entry routine: RKD module as PCB/Jumper mode (module settings).
  977. struct PCBMenuItem : MenuItem {
  978. RKD *rkd;
  979. void onAction(EventAction &e) override {
  980. rkd->bViewPCB = true;
  981. }
  982. void step() override {
  983. rightText = (rkd->bViewPCB == true) ? "✔" : "";
  984. MenuItem::step();
  985. }
  986. };
  987. // Context-menu entry routine: select "RKD (factory)" table.
  988. struct RKDManufacturerItem : MenuItem {
  989. RKD *rkd;
  990. void onAction(EventAction &e) override {
  991. rkd->tableSet = 0;
  992. }
  993. void step() override {
  994. rightText = (rkd->tableSet == 0) ? "✔" : "";
  995. MenuItem::step();
  996. }
  997. };
  998. // Context-menu entry routine: select "Prime numbers" table.
  999. struct RKDPrimesItem : MenuItem {
  1000. RKD *rkd;
  1001. void onAction(EventAction &e) override {
  1002. rkd->tableSet = 1;
  1003. }
  1004. void step() override {
  1005. rightText = (rkd->tableSet == 1) ? "✔" : "";
  1006. MenuItem::step();
  1007. }
  1008. };
  1009. // Context-menu entry routine: select "Perfect squares" table.
  1010. struct RKDSquaresItem : MenuItem {
  1011. RKD *rkd;
  1012. void onAction(EventAction &e) override {
  1013. rkd->tableSet = 2;
  1014. }
  1015. void step() override {
  1016. rightText = (rkd->tableSet == 2) ? "✔" : "";
  1017. MenuItem::step();
  1018. }
  1019. };
  1020. // Context-menu entry routine: select "Fibonacci sequence" table.
  1021. struct RKDFibonacciItem : MenuItem {
  1022. RKD *rkd;
  1023. void onAction(EventAction &e) override {
  1024. rkd->tableSet = 3;
  1025. }
  1026. void step() override {
  1027. rightText = (rkd->tableSet == 3) ? "✔" : "";
  1028. MenuItem::step();
  1029. }
  1030. };
  1031. // Context-menu entry routine: select "Triplet & 16ths" table.
  1032. struct RKDTripletSixteenthsItem : MenuItem {
  1033. RKD *rkd;
  1034. void onAction(EventAction &e) override {
  1035. rkd->tableSet = 4;
  1036. }
  1037. void step() override {
  1038. rightText = (rkd->tableSet == 4) ? "✔" : "";
  1039. MenuItem::step();
  1040. }
  1041. };
  1042. // CONTEXT-MENU CONSTRUCTION.
  1043. Menu* RKDWidget::createContextMenu() {
  1044. Menu* menu = ModuleWidget::createContextMenu();
  1045. RKD *rkd = dynamic_cast<RKD*>(module);
  1046. assert(rkd);
  1047. menu->addChild(construct<MenuEntry>());
  1048. menu->addChild(construct<MenuLabel>(&MenuLabel::text, "RKD module state:"));
  1049. menu->addChild(construct<ProdMenuItem>(&ProdMenuItem::text, "Installed in rack (production)", &ProdMenuItem::rkd, rkd));
  1050. menu->addChild(construct<PCBMenuItem>(&PCBMenuItem::text, "View jumpers (located on PCB)", &PCBMenuItem::rkd, rkd));
  1051. menu->addChild(construct<MenuLabel>(&MenuLabel::text, ""));
  1052. menu->addChild(construct<MenuLabel>(&MenuLabel::text, "Dividers table:"));
  1053. menu->addChild(construct<RKDManufacturerItem>(&RKDManufacturerItem::text, "Manufacturer", &RKDManufacturerItem::rkd, rkd));
  1054. menu->addChild(construct<RKDPrimesItem>(&RKDPrimesItem::text, "Prime numbers", &RKDPrimesItem::rkd, rkd));
  1055. menu->addChild(construct<RKDSquaresItem>(&RKDSquaresItem::text, "Perfect squares", &RKDSquaresItem::rkd, rkd));
  1056. menu->addChild(construct<RKDFibonacciItem>(&RKDFibonacciItem::text, "Fibonacci sequence", &RKDFibonacciItem::rkd, rkd));
  1057. menu->addChild(construct<RKDTripletSixteenthsItem>(&RKDTripletSixteenthsItem::text, "Triplet & 16ths", &RKDTripletSixteenthsItem::rkd, rkd));
  1058. return menu;
  1059. }
  1060. } // namespace rack_plugin_Ohmer
  1061. using namespace rack_plugin_Ohmer;
  1062. RACK_PLUGIN_MODEL_INIT(Ohmer, RKD) {
  1063. Model *modelRKD = Model::create<RKD, RKDWidget>("Ohmer Modules", "RKD", "RKD (Rotate Klok Divider)", CLOCK_MODULATOR_TAG);
  1064. return modelRKD;
  1065. }