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.

1933 lines
82KB

  1. //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  2. //// KlokSpid is a 8 HP module, initially designed to divide/multiply an external clock frequency (clock ////
  3. //// modulator), but it can work as standalone (BPM-based) clock generator, too! ////
  4. //// - 2 input jacks: ////
  5. //// - external clock (CLK), to work as divider/multiplier (standalone clock generator if not patched). ////
  6. //// - multipurpose CV-RATIO/TRIG. jack: ////
  7. //// - when running as clock multiplier/divider: CV-controllable ratio (full range /64 to x64). ////
  8. //// - when running as BPM-clock generator: trigger input (BPM start/stop, or BPM reset). ////
  9. //// - 4 output jacks: gates (default +5V/0V Square waveform). Other gates (%) and 1ms/2ms/5ms triggers ////
  10. //// (fixed duration pulses) are possible, via SETUP. ////
  11. //// ////
  12. //// As standalone (BPM-based) clock generator only: ////
  13. //// - any jack may have its custom ratio (via SETUP) - default is x1, for all jacks. ////
  14. //// - when set as "Custom" for first time, proposed default ratios are, per jack: /4, x1, x2, and x4. ////
  15. //// - jack #4 can be set (via SETUP) to send LFO-based waveform (instead of square/pulse) @ x1 only. ////
  16. //////////////////////////////////////////////////////////////////////////////////////////////////////////////
  17. #include "Ohmer.hpp"
  18. #include <dsp/digital.hpp>
  19. #include <string>
  20. namespace rack_plugin_Ohmer {
  21. // Dedicated LFO (based on LFO-1 stuff from Fundamental, but simplified as required).
  22. // It will be used - if enabled via SETUP - to output specific waveform to jack #4. Disabled by default.
  23. // LFO can be enabled to jack #4 but only if this jack is set at default ratio x1.
  24. struct LFO {
  25. float phase = 0.0f;
  26. float pw = 0.5f;
  27. float freq = 1.0f;
  28. bool offset = false;
  29. bool invert = false;
  30. LFO() {}
  31. void step(float dt) {
  32. float deltaPhase = fminf(freq * dt, 0.5f);
  33. phase += deltaPhase;
  34. if (phase >= 1.0f)
  35. phase -= 1.0f;
  36. }
  37. float sin() {
  38. if (offset)
  39. return 1.0f - cosf(2*M_PI * phase) * (invert ? -1.0f : 1.0f);
  40. else return sinf(2.0f*M_PI * phase) * (invert ? -1.0f : 1.0f);
  41. }
  42. float tri(float x) {
  43. return 4.0f * fabsf(x - roundf(x));
  44. }
  45. float tri() {
  46. if (offset)
  47. return tri(invert ? phase - 0.5f : phase);
  48. else return -1.0f + tri(invert ? phase - 0.25f : phase - 0.75f);
  49. }
  50. float saw(float x) {
  51. return 2.0f * (x - roundf(x));
  52. }
  53. float saw() {
  54. if (offset)
  55. return invert ? 2.0f * (1.0f - phase) : 2.0f * phase;
  56. else return saw(phase) * (invert ? -1.0f : 1.0f);
  57. }
  58. };
  59. // KlokSpid module architecture.
  60. struct KlokSpidModule : Module {
  61. enum ParamIds {
  62. PARAM_ENCODER,
  63. PARAM_BUTTON,
  64. NUM_PARAMS
  65. };
  66. enum InputIds {
  67. INPUT_CLOCK,
  68. INPUT_CV_TRIG,
  69. NUM_INPUTS
  70. };
  71. enum OutputIds {
  72. OUTPUT_1,
  73. OUTPUT_2,
  74. OUTPUT_3,
  75. OUTPUT_4,
  76. NUM_OUTPUTS
  77. };
  78. enum LightIds {
  79. LED_CLK,
  80. LED_CV_TRIG,
  81. LED_CVMODE,
  82. LED_TRIGMODE,
  83. LED_SYNC_GREEN,
  84. LED_SYNC_RED,
  85. NUM_LIGHTS
  86. };
  87. // Pointer to encoder (handled as knob).
  88. Knob *klokspdEncoder;
  89. // Optional LFO for jack #4.
  90. LFO LFOjack4;
  91. // Module interface definitions, such parameters (encoder, button), input ports, output ports and LEDs.
  92. KlokSpidModule() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
  93. onReset();
  94. onRandomize();
  95. }
  96. void onReset() override {
  97. if (!avoidOnResetReentry) {
  98. encoderPrevious = 0;
  99. onFirstInitCounter = (long)(engineGetSampleRate() / 4.0f);
  100. currentStep = 0;
  101. avoidOnResetReentry = true;
  102. }
  103. }
  104. // Inhibit "Randomize" from context-menu / Ctrl+R / Command+R keyboard shortcut over module.
  105. void onRandomize() override {
  106. }
  107. //// GENERAL PURPOSE VARIABLES/FLAGS/TABLES.
  108. int onFirstInitCounter = 0;
  109. bool avoidOnResetReentry = false; // When true, this avoid OnReset() reentry.
  110. //// CLOCK MODULATOR RATIOS.
  111. // Real clock ratios (global) list/array. Preset ratios while KlokSpid module runs as clock modulator (can be selected via encoder exclusively).
  112. float list_fRatio[31] = {64.0f, 32.0f, 24.0f, 16.0f, 15.0f, 12.0f, 10.0f, 9.0f, 8.0f, 7.0f, 6.0f, 5.0f, 4.0f, 3.0f, 2.0f, 1.0f, 0.5f, 1.0f/3.0f, 0.25f, 0.2f, 1.0f/6.0f, 1.0f/7.0f, 0.125f, 1.0f/9.0f, 0.1f, 1.0f/12.0f, 1.0f/15.0f, 0.0625f, 1.0f/24.0f, 0.03125f, 0.015625f};
  113. //// MODEL (GUI THEME).
  114. // Current selected KlokSpid model (GUI theme).
  115. int Theme = 0;
  116. // DMD-font color (default is "Classic" beige model).
  117. NVGcolor DMDtextColor = nvgRGB(0x08, 0x08, 0x08);
  118. //// Main DMD and small displays (near output jacks).
  119. char dmdMessage1[24] = "";
  120. char dmdMessage2[24] = "";
  121. int xOffsetValue = 0; // Horizontal offset on DMD to display for second line.
  122. char dispOut1[4] = "";
  123. int xdispOutOffset1 = 0;
  124. char dispOut2[4] = "";
  125. int xdispOutOffset2 = 0;
  126. char dispOut3[4] = "";
  127. int xdispOutOffset3 = 0;
  128. char dispOut4[4] = "";
  129. int xdispOutOffset4 = 0;
  130. // Strings for running modes.
  131. const std::string runningMode[3] = {"Clk Generator", "Clk Modulator", "Clk CV-Ratio"};
  132. //// STEP-RELATED (REALTIME) COUNTERS/GAPS.
  133. // Step related variables: used to determine the frequency of source signal, and when KlokSpid must sends relevant pulses to output(s).
  134. long long int currentStep = 0;
  135. long long int previousStep = 0;
  136. long long int expectedStep = 0;
  137. long stepGap = 0;
  138. long stepGapPrevious = 0;
  139. long long int nextPulseStep[NUM_OUTPUTS] = {0, 0, 0, 0};
  140. // Current jacks states, voltages on input jacks, and button state.
  141. bool activeCLK = false;
  142. bool activeCLKPrevious = true;
  143. bool activeCV = false;
  144. bool activeCVPrevious = true;
  145. float voltageOnCV = 0.0f;
  146. bool buttonPressed = false;
  147. // Encoder (registered position to be used on next step for relative move).
  148. int encoderCurrent = 0;
  149. int encoderPrevious = 0; // Encoder "absolute" (saved to jSon)...
  150. int encoderDelta = 0; // 0 if not moved, -1 if counter-clockwise (decrement), 1 if clockwise (increment).
  151. // Ratio (clock modulator).
  152. int svRatio = 15; // saved value.
  153. int rateRatioByEncoder = 15; // Assuming encoder is, by default "centered" to "x1" (= 15).
  154. // Clock modulator modes.
  155. enum ClkModModeIds {
  156. X1, // work at x1.
  157. DIV, // divider mode.
  158. MULT // muliplier mode.
  159. };
  160. // Clock modulator mode, assuming default is X1.
  161. int clkModulatorMode = X1;
  162. //// SCHMITT TRIGGERS.
  163. // Schmitt trigger to check thresholds on CLK input connector.
  164. SchmittTrigger CLKInputPort;
  165. // Schmitt trigger to handle BPM start/stop state (only when KlokSpid is acting as clock generator) via button.
  166. SchmittTrigger runButton;
  167. // Schmitt trigger to handle the start/stop toggle button (also used for SETUP to confirm menu/parameter) - via CV/TRIG input port (if configured as "Start/Stop").
  168. SchmittTrigger runTriggerPort;
  169. //// RATIO-BY-CV VARIABLES/FLAGS.
  170. // Incoming CV may be bipolar (true) or unipolar (false).
  171. bool bipolarCV = true;
  172. // Is CV used to modulate ratio?
  173. bool isRatioCVmod = false;
  174. // Real ratio, given by current CV voltage.
  175. float rateRatioCV = 0.0f;
  176. // Real ratio, given by current CV voltage, integer is required only for display into DMD (to avoid "decimals" cosmetic issues, at the right side of DMD!).
  177. int rateRatioCVi = 0;
  178. //// BPM-RELATED VARIABLES (STANDALONE CLOCK GENERATOR).
  179. // Default BPM (when KlokSpid is acting as clock generator). Default is 120 BPM (centered knob).
  180. int svBPM = 120; // saved value.
  181. int BPM = 120;
  182. // Previous registed BPM (when KlokSpid is acting as clock generator), from previous step.
  183. int previousBPM = 120;
  184. // Custom jacks ratios (per output jack). By default false, all are X1 (original setting for KlokSpid). True means each jack can receive an optional ratio.
  185. bool defOutRatios = true;
  186. int outputRatio[4] = {9, 12, 13, 15};
  187. int outputRatioInUse[4] = {12, 12, 12, 12};
  188. float list_outRatiof[25] = {64.0f, 32.0f, 24.0f, 16.0f, 12.0f, 9.0f, 8.0f, 6.0f, 5.0f, 4.0f, 3.0f, 2.0f, 1.0f, 0.5f, 1.0f/3.0f, 0.25f, 0.2f, 1.0f/6.0f, 0.125f, 1.0f/9.0f, 1.0f/12.0f, 0.0625f, 1.0f/24.0f, 0.03125f, 0.015625f};
  189. // Indicates if "CV-RATIO/TRIG." input port (used as trigger, standalone BPM-clock mode only) is a transport trigger (true = toggle start/stop) or reset (false, default) useful for "re-sync" between modules.
  190. bool transportTrig = false;
  191. // Standalone clock generator mode only: indicates if BPM is running or stopped.
  192. bool isBPMRunning = true;
  193. bool runBPMOnInit = true;
  194. //// SETUP-RELATED VARIABLES/TABLES.
  195. // Enumeration of SETUP menu entries.
  196. enum setupMenuEntries {
  197. SETUP_WELCOME_MESSAGE, // SETUP menu entry for #0 is always dedicated to welcome message ("*- SETUP -*") displayed on DMD.
  198. SETUP_CVPOLARITY, // SETUP menu entry for CV polarity (bipolar, or unipolar).
  199. SETUP_DURATION, // SETUP menu entry for pulse duration (fixed and gate-based parameters).
  200. SETUP_OUTVOLTAGE, // SETUP menu entry for output voltage.
  201. SETUP_OUTSRATIOS, // SETUP menu entry for custom ratio concerning all jacks (all at x1, or custom).
  202. SETUP_OUT1RATIO, // SETUP menu entry for custom ratio concerning output jack #1.
  203. SETUP_OUT2RATIO, // SETUP menu entry for custom ratio concerning output jack #2.
  204. SETUP_OUT3RATIO, // SETUP menu entry for custom ratio concerning output jack #3.
  205. SETUP_OUT4RATIO, // SETUP menu entry for custom ratio concerning output jack #4.
  206. SETUP_OUT4LFO, // SETUP menu entry for LFO to output jack #4.
  207. SETUP_OUT4LFOPOLARITY, // SETUP menu entry for LFO polarity to output jack #4 (bipolar, or unipolar).
  208. SETUP_CVTRIG, // SETUP menu entry describing how CV/TRIG input port is working (as start/stop toggle, or as "RST" input).
  209. SETUP_EXIT, // Lastest menu entry is always used to exit SETUP (options are "Save/Exit", "Canc/Exit", "Review" or "Factory").
  210. NUM_SETUP_ENTRIES // This position indicates how many entries the KlokSpid's SETUP menu have.
  211. };
  212. // Strings for SETUP entries.
  213. const std::string setupMenuName[NUM_SETUP_ENTRIES] = {"*-SETUP-*", "CV Polarity", "Pulse Durat.", "Out. Voltage", "Outp. Ratios", "Out. 1 Ratio", "Out. 2 Ratio", "Out. 3 Ratio", "Out. 4 Ratio", "Out. 4 LFO", "LFO Polarity", "TRIG. Jack", "Exit SETUP"};
  214. // Strings for SETUP possible parameters.
  215. std::string setupParamName[NUM_SETUP_ENTRIES][25];
  216. // Related horizontal offsets (second line of DMD).
  217. int setupParamXOffset[NUM_SETUP_ENTRIES][25];
  218. // This flag indicates if KlokSpid module is currently running SETUP, or not.
  219. bool isSetupRunning = false;
  220. // This flag indicates if KlokSpid module is entering SETUP (2 seconds delay), or not.
  221. bool isEnteringSetup = false;
  222. // This flag indicates if KlokSpid module is exiting SETUP (2 seconds delay), or not.
  223. bool isExitingSetup = false;
  224. // This flag is designed to avoid continuous SETUP entries/exits while button is continously held.
  225. bool allowedButtonHeld = false;
  226. // Item index (edited parameter number).
  227. int setup_ParamIdx = 0;
  228. // Current edited value for selected parameter.
  229. int setup_CurrentValue = 0;
  230. // Table containing number of possible values for each parameter.
  231. int setup_NumValue[NUM_SETUP_ENTRIES] = {0, 2, 9, 4, 2, 25, 25, 25, 25, 7, 2, 2, 4};
  232. // Default factory values for each parameter.
  233. int setup_Factory[NUM_SETUP_ENTRIES] = {0, 0, 5, 0, 0, 9, 12, 13, 15, 0, 0, 1, 1};
  234. // Table containing current values for all parameters.
  235. int setup_Current[NUM_SETUP_ENTRIES] = {0, 0, 5, 0, 0, 9, 12, 13, 15, 0, 0, 1, 1};
  236. // Table containing edited parameters during SETUP (will be filled when entering SETUP).
  237. int setup_Edited[NUM_SETUP_ENTRIES] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1};
  238. // Table containing backup edited parameters during SETUP (will be filled when entering SETUP).
  239. int setup_Backup[NUM_SETUP_ENTRIES] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1};
  240. // Counter (as "delay") used to enter and (optionally) to saved/exit SETUP quickly on long press.
  241. long setupCounter = 0;
  242. //// PULSE TO OUTPUT RELATED VARIABLES AND PULSE GENERATORS.
  243. // Enumeration of possible pulse durations: fixed 1 ms, fixed 2 ms, fixed 5 ms, Gate 1/4, Gate 1/3, Square, Gate 2/3, Gate 3/4, Gate 95%.
  244. enum PulseDurations {
  245. FIXED1MS, // Fixed 1 ms.
  246. FIXED2MS, // Fixed 2 ms.
  247. FIXED5MS, // Fixed 5 ms.
  248. GATE25, // Gate 1/4 (25%).
  249. GATE33, // Gate 1/3 (33%).
  250. SQUARE, // Square waveform.
  251. GATE66, // Gate 2/3 (66%).
  252. GATE75, // Gate 3/4 (75%).
  253. GATE95, // Gate 95%.
  254. };
  255. // Pulse counter for divider mode (set at max divider value, minus 1).
  256. int pulseDivCounter[NUM_OUTPUTS] = {63, 63, 63, 63};
  257. // Pulse counter for multiplier mode, to avoid continuous pulse when no more receiving (set at max divider value, minus 1). Kind of "timeout".
  258. int pulseMultCounter[NUM_OUTPUTS] = {0, 0, 0, 0};
  259. // Pulse generators, to send "pulses" to output jacks (one pulse generator per output jack).
  260. PulseGenerator sendPulse[NUM_OUTPUTS];
  261. // These flags are related to pulse generators (current pulse state).
  262. bool sendingOutput[NUM_OUTPUTS] = {false, false, false, false};
  263. // This flag indicates if sending pulse (one per output jack) is allowed (true) or not (false).
  264. bool canPulse[NUM_OUTPUTS] = {false, false, false, false};
  265. // Current pulse duration (time in second). Default is fixed 1 ms at start. Operational can be changed via SETUP.
  266. float pulseDuration[NUM_OUTPUTS] = {0.001f, 0.001f, 0.001f, 0.001f};
  267. // Extension of "pulseDuration" value (for square and gate modes), set as square wave (50 %) by default, can be changed via SETUP.
  268. int pulseDurationExt = SQUARE;
  269. // Voltage for outputs (pulses/gates), default is +5V, can be changed to +10V, +12V (+11.7V) or +2V instead, via SETUP.
  270. float outVoltage = 5.0f;
  271. // Special LFO output on jack #4.
  272. int jack4LFO = 0;
  273. bool jack4LFObipolar = true;
  274. bool resetPhase = true;
  275. //////////////////////////////////////
  276. //// CLK LED AFTERGLOW. ////
  277. //////////////////////////////////////
  278. // Counter used for red CLK LED afterglow (used together with "ledClkAfterglow" boolean flag).
  279. long ledClkDelay = 0; // long is required for highest engine samplerates!
  280. // This flag controls CLK (red) LED afterglow (active or not).
  281. bool ledClkAfterglow = false;
  282. //////////////////////////////////////
  283. //// SYNC STATUS. ////
  284. //////////////////////////////////////
  285. // Assuming clock generator isn't synchronized (sync'd) with source clock on initialization.
  286. bool isSync = false;
  287. //////////////////////////////////////
  288. //// FUNCTIONS & METHODS (VOIDS). ////
  289. //////////////////////////////////////
  290. void step() override;
  291. // Convert a string to char pointer (char*).
  292. char* stringToPchar(std::string str) {
  293. char *cstr = new char[str.length() + 1];
  294. strcpy(cstr, str.c_str());
  295. return cstr;
  296. delete [] cstr;
  297. }
  298. void updateDisplayJack(int jackID) {
  299. if (activeCLK) {
  300. // Clock modulator mode. For now, all ports are at x1.
  301. xdispOutOffset1 = 5;
  302. strcpy(dispOut1, stringToPchar("x1"));
  303. xdispOutOffset2 = 5;
  304. strcpy(dispOut2, stringToPchar("x1"));
  305. xdispOutOffset3 = 5;
  306. strcpy(dispOut3, stringToPchar("x1"));
  307. xdispOutOffset4 = 5;
  308. strcpy(dispOut4, stringToPchar("x1"));
  309. }
  310. else {
  311. // Clock generator mode.
  312. switch (jackID) {
  313. case 0:
  314. xdispOutOffset1 = 0;
  315. if ((outputRatioInUse[0] > 4) && (outputRatioInUse[0] < 12))
  316. xdispOutOffset1 = 4;
  317. else if ((outputRatioInUse[0] > 11) && (outputRatioInUse[0] < 20))
  318. xdispOutOffset1 = 5;
  319. else if (outputRatioInUse[0] > 19)
  320. xdispOutOffset2 = 1;
  321. strcpy(dispOut1, stringToPchar(setupParamName[SETUP_OUT1RATIO][outputRatioInUse[0]]));
  322. break;
  323. case 1:
  324. xdispOutOffset2 = 0;
  325. if ((outputRatioInUse[1] > 4) && (outputRatioInUse[1] < 12))
  326. xdispOutOffset2 = 4;
  327. else if ((outputRatioInUse[1] > 11) && (outputRatioInUse[1] < 20))
  328. xdispOutOffset2 = 5;
  329. else if (outputRatioInUse[1] > 19)
  330. xdispOutOffset2 = 1;
  331. strcpy(dispOut2, stringToPchar(setupParamName[SETUP_OUT2RATIO][outputRatioInUse[1]]));
  332. break;
  333. case 2:
  334. xdispOutOffset3 = 0;
  335. if ((outputRatioInUse[2] > 4) && (outputRatioInUse[2] < 12))
  336. xdispOutOffset3 = 4;
  337. else if ((outputRatioInUse[2] > 11) && (outputRatioInUse[2] < 20))
  338. xdispOutOffset3 = 5;
  339. else if (outputRatioInUse[2] > 19)
  340. xdispOutOffset3 = 1;
  341. strcpy(dispOut3, stringToPchar(setupParamName[SETUP_OUT3RATIO][outputRatioInUse[2]]));
  342. break;
  343. case 3:
  344. if (outputRatioInUse[3] == 12) {
  345. if (jack4LFO != 0) {
  346. xdispOutOffset4 = 0;
  347. switch (jack4LFO) {
  348. case 1:
  349. case 2:
  350. strcpy(dispOut4, stringToPchar("SIN"));
  351. break;
  352. case 3:
  353. case 4:
  354. strcpy(dispOut4, stringToPchar("TRI"));
  355. break;
  356. case 5:
  357. case 6:
  358. strcpy(dispOut4, stringToPchar("SAW"));
  359. }
  360. }
  361. else {
  362. xdispOutOffset4 = 5;
  363. strcpy(dispOut4, stringToPchar("x1"));
  364. }
  365. }
  366. else {
  367. xdispOutOffset4 = 0;
  368. if ((outputRatioInUse[3] > 4) && (outputRatioInUse[3] < 12))
  369. xdispOutOffset4 = 4;
  370. else if ((outputRatioInUse[3] > 11) && (outputRatioInUse[3] < 20))
  371. xdispOutOffset4 = 5;
  372. else if (outputRatioInUse[3] > 19)
  373. xdispOutOffset4 = 1;
  374. strcpy(dispOut4, stringToPchar(setupParamName[SETUP_OUT4RATIO][outputRatioInUse[3]]));
  375. }
  376. break;
  377. }
  378. }
  379. }
  380. // Set the DMD, regarding current mode (0 = BPM generator, 1 = clock modulator by encoder).
  381. void updateDMDtoRunningMode(int currMode) {
  382. // Update small displays for each output jacks.
  383. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  384. updateDisplayJack(i);
  385. switch (currMode) {
  386. case 0:
  387. // BPM clock generator.
  388. // Update main DMD.
  389. if (!isSetupRunning) {
  390. strcpy(dmdMessage1, stringToPchar(runningMode[0]));
  391. if (BPM < 10)
  392. xOffsetValue = 19;
  393. else if (BPM < 100)
  394. xOffsetValue = 13;
  395. else xOffsetValue = 7;
  396. strcpy(dmdMessage2, stringToPchar(std::to_string(BPM) + " BPM"));
  397. }
  398. break;
  399. case 1:
  400. // Clock modulator.
  401. if (inputs[INPUT_CV_TRIG].active) {
  402. // Ratio is modulated by CV.
  403. voltageOnCV = inputs[INPUT_CV_TRIG].value;
  404. if (bipolarCV)
  405. rateRatioCV = round(clamp(static_cast<float>(voltageOnCV), -5.0f, 5.0f) * 12.6f); // By bipolar voltage (-5V/+5V).
  406. else rateRatioCV = round((clamp(static_cast<float>(voltageOnCV), 0.0f, 10.0f) - 5.0f) * 12.6f); // By unipolar voltage (0V/+10V).
  407. // Required to display ratio without artifacts!
  408. rateRatioCVi = static_cast<int>(rateRatioCV);
  409. if (round(rateRatioCV) == 0.0f) {
  410. clkModulatorMode = X1;
  411. rateRatioCV = 1.0f; // Real ratio becomes... 1.0f because it's multiplied by 1.
  412. }
  413. else if (round(rateRatioCV) > 0.0f) {
  414. clkModulatorMode = MULT;
  415. rateRatioCV = round(rateRatioCV + 1.0f);
  416. }
  417. else {
  418. clkModulatorMode = DIV;
  419. rateRatioCV = 1.0f / round(1.0f - rateRatioCV);
  420. }
  421. if (!isSetupRunning) {
  422. // Clock modulator (free ratio by CV).
  423. strcpy(dmdMessage1, stringToPchar(runningMode[2]));
  424. xOffsetValue = 2;
  425. std::string sSign = "x";
  426. if (rateRatioCVi >= 0) {
  427. rateRatioCVi = rateRatioCVi + 1;
  428. }
  429. else {
  430. rateRatioCVi = 1 - rateRatioCVi;
  431. sSign = "/";
  432. }
  433. strcpy(dmdMessage2, stringToPchar("Rate: " + sSign + std::to_string(rateRatioCVi)));
  434. }
  435. }
  436. else {
  437. // Clock modulator (preset ratio by encoder).
  438. // Ratio is selected from encoder.
  439. if (!isSetupRunning) {
  440. // Related multiplier/divider mode.
  441. clkModulatorMode = DIV;
  442. if (rateRatioByEncoder == 15)
  443. clkModulatorMode = X1;
  444. else if (rateRatioByEncoder > 15)
  445. clkModulatorMode = MULT;
  446. static const int list_iRatio[31] = {64, 32, 24, 16, 15, 12, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 15, 16, 24, 32, 64};
  447. strcpy(dmdMessage1, stringToPchar(runningMode[1]));
  448. xOffsetValue = 2;
  449. std::string sSign = "x";
  450. if (rateRatioByEncoder < 15)
  451. sSign = "/";
  452. strcpy(dmdMessage2, stringToPchar("Rate: " + sSign + std::to_string(list_iRatio[rateRatioByEncoder])));
  453. }
  454. }
  455. }
  456. }
  457. // This custom function applies current settings (useful after SETUP operation, also "on-the-fly" altered parameter during Setup - useful to experiment).
  458. void UpdateKlokSpidSettings(bool allowJsonUpdate) {
  459. // SETUP parameter SETUP_CVPOLARITY: CV polarity (bipolar or unipolar CV-Ratio).
  460. bipolarCV = (setup_Current[SETUP_CVPOLARITY] == 0); // json persistence (only if SETUP isn't running).
  461. if (allowJsonUpdate)
  462. this->bipolarCV = (setup_Current[SETUP_CVPOLARITY] == 0); // json persistence (only if SETUP isn't running).
  463. // SETUP parameter SETUP_DURATION: possible pulse durations (1 ms, 2 ms, 5 ms, Gate 1/4, Gate 1/3, Square, Gate 2/3, Gate 3/4, Gate 95%). Keept for compatibility with v0.5.2 .vcv patches!
  464. switch (setup_Current[SETUP_DURATION]) {
  465. case FIXED1MS:
  466. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  467. pulseDuration[i] = 0.001f;
  468. break;
  469. case FIXED2MS:
  470. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  471. pulseDuration[i] = 0.002f;
  472. break;
  473. case FIXED5MS:
  474. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  475. pulseDuration[i] = 0.005f;
  476. }
  477. // Extension for pulse duration parameter (it's a kind of "descriptor" for non-fixed durations).
  478. pulseDurationExt = setup_Current[SETUP_DURATION];
  479. if (allowJsonUpdate)
  480. this->pulseDurationExt = setup_Current[SETUP_DURATION]; // json persistence (only if SETUP isn't running).
  481. // SETUP parameter SETUP_OUTVOLTAGE: output voltage: +2V, +5V, +10V or +12V (+11.7V).
  482. switch (setup_Current[SETUP_OUTVOLTAGE]) {
  483. case 0:
  484. outVoltage = 5.0f;
  485. if (allowJsonUpdate)
  486. this->outVoltage = 5.0f; // First setting is +5V, also factory (default) setting. json persistence (only if SETUP isn't running).
  487. break;
  488. case 1:
  489. outVoltage = 10.0f;
  490. if (allowJsonUpdate)
  491. this->outVoltage = 10.0f; // Second setting is +10V. json persistence (only if SETUP isn't running).
  492. break;
  493. case 2:
  494. outVoltage = 11.7f;
  495. if (allowJsonUpdate)
  496. this->outVoltage = 11.7f; // Third setting is +12V (real +11.7 V). json persistence (only if SETUP isn't running).
  497. break;
  498. case 3:
  499. outVoltage = 2.0f;
  500. if (allowJsonUpdate)
  501. this->outVoltage = 2.0f; // Last setting (introduced from v0.5.5/v0.6.0.4-beta): +2V. json persistence (only if SETUP isn't running).
  502. }
  503. // SETUP parameter SETUP_OUTSRATIOS: all output jacks at default x1, or custom (useful to bypass all 4 jack rations during SETUP, if let at default).
  504. defOutRatios = (setup_Current[SETUP_OUTSRATIOS] == 0); // json persistence (only if SETUP isn't running).
  505. if (allowJsonUpdate)
  506. this->defOutRatios = (setup_Current[SETUP_OUTSRATIOS] == 0); // json persistence (only if SETUP isn't running).
  507. // SETUP parameter SETUP_OUT1RATIO: optional BPM rate applied on output jack #1.
  508. outputRatio[0] = setup_Current[SETUP_OUT1RATIO];
  509. if (allowJsonUpdate)
  510. this->outputRatio[0] = setup_Current[SETUP_OUT1RATIO]; // json persistence (only if SETUP isn't running).
  511. if (defOutRatios)
  512. outputRatioInUse[0] = 12;
  513. else outputRatioInUse[0] = outputRatio[0];
  514. // SETUP parameter SETUP_OUT2RATIO: optional BPM rate applied on output jack #2.
  515. outputRatio[1] = setup_Current[SETUP_OUT2RATIO];
  516. if (allowJsonUpdate)
  517. this->outputRatio[1] = setup_Current[SETUP_OUT2RATIO]; // json persistence (only if SETUP isn't running).
  518. if (defOutRatios)
  519. outputRatioInUse[1] = 12;
  520. else outputRatioInUse[1] = outputRatio[1];
  521. // SETUP parameter SETUP_OUT3RATIO: optional BPM rate applied on output jack #3.
  522. outputRatio[2] = setup_Current[SETUP_OUT3RATIO];
  523. if (allowJsonUpdate)
  524. this->outputRatio[2] = setup_Current[SETUP_OUT3RATIO]; // json persistence (only if SETUP isn't running).
  525. if (defOutRatios)
  526. outputRatioInUse[2] = 12;
  527. else outputRatioInUse[2] = outputRatio[2];
  528. // SETUP parameter SETUP_OUT4RATIO: optional BPM rate applied on output jack #4.
  529. outputRatio[3] = setup_Current[SETUP_OUT4RATIO];
  530. if (allowJsonUpdate)
  531. this->outputRatio[3] = setup_Current[SETUP_OUT4RATIO]; // json persistence (only if SETUP isn't running).
  532. if (defOutRatios)
  533. outputRatioInUse[3] = 12;
  534. else outputRatioInUse[3] = outputRatio[3];
  535. // SETUP parameter SETUP_OUT4LFO: optional LFO on output jack #4: Disabled, Sine, Triangle, Saw, Inverse Sine, Inverse Triangle, Inverse Saw.
  536. // Introduced from v0.6.1, but remaining to do.
  537. jack4LFO = setup_Current[SETUP_OUT4LFO];
  538. if (allowJsonUpdate)
  539. this->jack4LFO = setup_Current[SETUP_OUT4LFO]; // json persistence (only if SETUP isn't running).
  540. // SETUP parameter SETUP_OUT4LFOPOLARITY: LFO polarity (bipolar or unipolar).
  541. jack4LFObipolar = (setup_Current[SETUP_OUT4LFOPOLARITY] == 0); // json persistence (only if SETUP isn't running).
  542. if (allowJsonUpdate)
  543. this->jack4LFObipolar = (setup_Current[SETUP_OUT4LFOPOLARITY] == 0); // json persistence (only if SETUP isn't running).
  544. // SETUP parameter SETUP_CVTRIG: CV-RATIO/TRIG. input port behavior (standalone clock generator only, this port is TRIG.).
  545. // - "true" is meaning the TRIG. input port acts as "start/stop toggle".
  546. // - "false" is meaning the TRIG. input port acts as "BPM reset" (useful to "re-sync" BPM from an external/reference source clock, for example).
  547. transportTrig = (setup_Current[SETUP_CVTRIG] == 0);
  548. if (allowJsonUpdate)
  549. this->transportTrig = (setup_Current[SETUP_CVTRIG] == 0); // json persistence (only if SETUP isn't running).
  550. // Update small displays for each output jacks.
  551. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  552. updateDisplayJack(i);
  553. }
  554. // This custom function returns pulse duration (ms), regardling number of samples (long int) and pulsation duration parameter (SETUP).
  555. float GetPulsingTime(long int stepGap, float rate) {
  556. float pTime = 0.001; // As default pulse duration is set to 1ms (also can be set to "fixed 1ms" via SETUP).
  557. if (stepGap == 0) {
  558. // No reference duration (number of samples is zero).
  559. switch (setup_Current[SETUP_DURATION]) {
  560. case FIXED2MS:
  561. pTime = 0.002f; // Fixed 2 ms pulse.
  562. break;
  563. case FIXED5MS:
  564. pTime = 0.005f; // Fixed 5 ms pulse.
  565. }
  566. }
  567. else {
  568. // Reference duration in number of samples (when known stepGap). Variable-length pulse duration can be defined.
  569. switch (setup_Current[SETUP_DURATION]) {
  570. case FIXED2MS:
  571. pTime = 0.002f; // Fixed 2 ms pulse.
  572. break;
  573. case FIXED5MS:
  574. pTime = 0.005f; // Fixed 5 ms pulse.
  575. break;
  576. case GATE25:
  577. pTime = rate * 0.25f * (stepGap / engineGetSampleRate()); // Gate 1/4 (25%)
  578. break;
  579. case GATE33:
  580. pTime = rate * (1.0f / 3.0f) * (stepGap / engineGetSampleRate()); // Gate 1/3 (33%)
  581. break;
  582. case SQUARE:
  583. pTime = rate * 0.5f * (stepGap / engineGetSampleRate()); // Square wave (50%)
  584. break;
  585. case GATE66:
  586. pTime = rate * (2.0f / 3.0f) * (stepGap / engineGetSampleRate()); // Gate 2/3 (66%)
  587. break;
  588. case GATE75:
  589. pTime = rate * 0.75f * (stepGap / engineGetSampleRate()); // Gate 3/4 (75%)
  590. break;
  591. case GATE95:
  592. pTime = rate * 0.95f * (stepGap / engineGetSampleRate()); // Gate 95%
  593. }
  594. }
  595. return pTime;
  596. }
  597. // Persistence for extra datas via json functions (in particular parameters defined via KlokSpid's SETUP, also BPM state).
  598. // These extra datas are saved to .vcv file (including "autosave.vcv") also are "transfered" when you duplicate the module.
  599. json_t *toJson() override {
  600. json_t *rootJ = json_object();
  601. json_object_set_new(rootJ, "Theme", json_integer(Theme));
  602. json_object_set_new(rootJ, "bipolarCV", json_boolean(bipolarCV));
  603. json_object_set_new(rootJ, "pulseDurationExt", json_integer(pulseDurationExt));
  604. json_object_set_new(rootJ, "outVoltage", json_real(outVoltage));
  605. json_object_set_new(rootJ, "defOutRatios", json_boolean(defOutRatios)); // When true, all jacks are x1. Otherwise (false) any jack may have its specific ratio.
  606. json_object_set_new(rootJ, "out1Ratio", json_integer(outputRatio[0]));
  607. json_object_set_new(rootJ, "out2Ratio", json_integer(outputRatio[1]));
  608. json_object_set_new(rootJ, "out3Ratio", json_integer(outputRatio[2]));
  609. json_object_set_new(rootJ, "out4Ratio", json_integer(outputRatio[3]));
  610. json_object_set_new(rootJ, "jack4LFO", json_integer(jack4LFO));
  611. json_object_set_new(rootJ, "jack4LFObipolar", json_boolean(jack4LFObipolar));
  612. json_object_set_new(rootJ, "transportTrig", json_boolean(transportTrig)); // CV-RATIO/TRIG. port may be used as BPM "start/stop" toggle or as BPM-reset. BPM-reset is default factory (false).
  613. json_object_set_new(rootJ, "Ratio", json_integer(rateRatioByEncoder)); // Ratio set by encoder.
  614. json_object_set_new(rootJ, "BPM", json_integer(BPM)); // BPM set by encoder.
  615. json_object_set_new(rootJ, "runBPMOnInit", json_boolean(runBPMOnInit)); // State of BPM pulsing or stopped.
  616. return rootJ;
  617. }
  618. void fromJson(json_t *rootJ) override {
  619. // Retrieving module theme/variation (when loading .vcv and cloning module).
  620. json_t *ThemeJ = json_object_get(rootJ, "Theme");
  621. if (ThemeJ)
  622. Theme = json_integer_value(ThemeJ);
  623. // Retrieving bipolar or unipolar mode (for CV when running as clock multiplier/divider).
  624. json_t *bipolarCVJ = json_object_get(rootJ, "bipolarCV");
  625. if (bipolarCVJ)
  626. bipolarCV = json_is_true(bipolarCVJ);
  627. // Retrieving pulse duration "mode" data. Introducted since v0.5.3. Thanks Yoann for this idea!
  628. json_t *pulseDurationExtJ = json_object_get(rootJ, "pulseDurationExt");
  629. if (pulseDurationExtJ)
  630. pulseDurationExt = json_integer_value(pulseDurationExtJ);
  631. // Retrieving output voltage data (real/float value).
  632. json_t *outVoltageJ = json_object_get(rootJ, "outVoltage");
  633. if (outVoltageJ)
  634. outVoltage = json_real_value(outVoltageJ);
  635. // Retrieving if ratio par jack is disabled (all at x1), or enabled (each having its ratio).
  636. json_t *defOutRatiosJ = json_object_get(rootJ, "defOutRatios");
  637. if (defOutRatiosJ)
  638. defOutRatios = json_is_true(defOutRatiosJ);
  639. // Retrieving ratio for output jack #1 (when loading .vcv and cloning module).
  640. json_t *jack1BPMRateJ = json_object_get(rootJ, "out1Ratio");
  641. if (jack1BPMRateJ)
  642. outputRatio[0] = json_integer_value(jack1BPMRateJ);
  643. // Retrieving ratio for output jack #2 (when loading .vcv and cloning module).
  644. json_t *jack2BPMRateJ = json_object_get(rootJ, "out2Ratio");
  645. if (jack2BPMRateJ)
  646. outputRatio[1] = json_integer_value(jack2BPMRateJ);
  647. // Retrieving ratio for output jack #3 (when loading .vcv and cloning module).
  648. json_t *jack3BPMRateJ = json_object_get(rootJ, "out3Ratio");
  649. if (jack3BPMRateJ)
  650. outputRatio[2] = json_integer_value(jack3BPMRateJ);
  651. // Retrieving ratio for output jack #4 (when loading .vcv and cloning module).
  652. json_t *jack4BPMRateJ = json_object_get(rootJ, "out4Ratio");
  653. if (jack4BPMRateJ)
  654. outputRatio[3] = json_integer_value(jack4BPMRateJ);
  655. // Retrieving output jack #4 LFO mode (when loading .vcv and cloning module).
  656. json_t *jack4LFOJ = json_object_get(rootJ, "jack4LFO");
  657. if (jack4LFOJ)
  658. jack4LFO = json_integer_value(jack4LFOJ);
  659. // Retrieving bipolar or unipolar for jack #4 LFO.
  660. json_t *jack4LFObipolarJ = json_object_get(rootJ, "jack4LFObipolar");
  661. if (jack4LFObipolarJ)
  662. jack4LFObipolar = json_is_true(jack4LFObipolarJ);
  663. // Retrieving usage of TRIG. input port: start/stop toggle (true) or BPM-reset (false).
  664. json_t *transportTrigJ = json_object_get(rootJ, "transportTrig");
  665. if (transportTrigJ)
  666. transportTrig = json_is_true(transportTrigJ);
  667. // Retrieving ratio (clock modulator) set by encoder (when loading .vcv and cloning module).
  668. json_t *svRatioJ = json_object_get(rootJ, "Ratio");
  669. if (svRatioJ)
  670. svRatio = json_integer_value(svRatioJ);
  671. // Retrieving BPM (when loading .vcv and cloning module).
  672. json_t *svBPMJ = json_object_get(rootJ, "BPM");
  673. if (svBPMJ)
  674. svBPM = json_integer_value(svBPMJ);
  675. // Retrieving last saved BPM-clocking state (it was running or stopped).
  676. json_t *runBPMOnInitJ = json_object_get(rootJ, "runBPMOnInit");
  677. if (runBPMOnInitJ)
  678. runBPMOnInit = json_is_true(runBPMOnInitJ);
  679. }
  680. };
  681. void KlokSpidModule::step() {
  682. // step() function is the right place for DSP processing!
  683. // Depending current KlokSpid model (theme), set the relevant DMD-text color.
  684. DMDtextColor = tblDMDtextColor[Theme];
  685. // Bypass early steps, due to encoder/knob behaviors on init (from 0.0f... to json saved value!).
  686. if (onFirstInitCounter > 0) {
  687. currentStep++;
  688. // Small displays near output jacks.
  689. xdispOutOffset1 = 0;
  690. strcpy(dispOut1, "");
  691. xdispOutOffset2 = 0;
  692. strcpy(dispOut2, "");
  693. xdispOutOffset3 = 0;
  694. strcpy(dispOut3, "");
  695. xdispOutOffset4 = 0;
  696. strcpy(dispOut4, "");
  697. // Flashing calibration message on DMD.
  698. if ((currentStep % 8000) > 4000)
  699. strcpy(dmdMessage1, "Calibrating...");
  700. else strcpy(dmdMessage1, "");
  701. xOffsetValue = 2;
  702. strcpy(dmdMessage2, "");
  703. // Encoder calibration.
  704. encoderCurrent = (int)roundf(10.0f * params[PARAM_ENCODER].value);
  705. if (encoderPrevious != encoderCurrent) {
  706. onFirstInitCounter = onFirstInitCounter + (int)(engineGetSampleRate() / 512.0f);
  707. encoderPrevious = encoderCurrent;
  708. }
  709. else onFirstInitCounter--;
  710. // Last step for initialization.
  711. if (onFirstInitCounter == 1) {
  712. // This is the lastest step of initialization.
  713. // Filling table containing current SETUP parameters.
  714. // SETUP parameter SETUP_CVPOLARITY: bipolar or unipolar CV.
  715. setup_Current[SETUP_CVPOLARITY] = bipolarCV ? 0 : 1;
  716. // SETUP parameter SETUP_DURATION: Pulse duration (extended, to keep compatibility with previous v0.5.2).
  717. // Parameter #2: possible pulse durations (fixed 1 ms, 2 ms or 5 ms durations, Gate 1/4, Gate 1/3, Square, Gate 2/3, Gate 3/4, Gate 95%).
  718. setup_Current[SETUP_DURATION] = pulseDurationExt;
  719. switch (pulseDurationExt) {
  720. case FIXED1MS:
  721. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  722. pulseDuration[i] = 0.001f;
  723. break;
  724. case FIXED2MS:
  725. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  726. pulseDuration[i] = 0.002f;
  727. break;
  728. case FIXED5MS:
  729. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  730. pulseDuration[i] = 0.005f;
  731. break;
  732. default:
  733. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  734. pulseDuration[i] = 0.001f; // It's a default value, but gates are defined in realtime (later).
  735. }
  736. // If output voltage is above +11V, assuming +11.7V.
  737. if (round(outVoltage * 10) > 110)
  738. outVoltage = 11.7f;
  739. // Assuming +5V is default output voltage.
  740. setup_Current[SETUP_OUTVOLTAGE] = 0; // +5V.
  741. // SETUP parameter SETUP_OUTVOLTAGE: Output voltage.
  742. if (round(outVoltage * 10) == 20)
  743. setup_Current[SETUP_OUTVOLTAGE] = 3; // +2V. Lastest value (instead of "inserted" at first, to preserve compatibility!).
  744. else if (round(outVoltage * 10) == 100)
  745. setup_Current[SETUP_OUTVOLTAGE] = 1; // +10V.
  746. else if (round(outVoltage * 10) == 117)
  747. setup_Current[SETUP_OUTVOLTAGE] = 2; // +11.7V (indicated +12V in module's SETUP).
  748. // SETUP parameter SETUP_OUTSRATIOS: enabled or disabled custom ratios (for all output jacks).
  749. setup_Current[SETUP_OUTSRATIOS] = defOutRatios ? 0 : 1;
  750. // SETUP parameter SETUP_OUT1RATIO to SETUP_OUT4RATIO: optional BPM rate applied on output jacks #1~#4.
  751. for (int i = 0; i < NUM_OUTPUTS; i++) {
  752. setup_Current[SETUP_OUT1RATIO + i] = outputRatio[i];
  753. if (defOutRatios)
  754. outputRatioInUse[i] = 12;
  755. else outputRatioInUse[i] = outputRatio[i];
  756. }
  757. // SETUP parameter SETUP_OUT4LFO: optional LFO on output jack #4.
  758. setup_Current[SETUP_OUT4LFO] = jack4LFO;
  759. // SETUP parameter SETUP_OUT4LFOPOLARITY: bipolar or unipolar LFO.
  760. setup_Current[SETUP_OUT4LFOPOLARITY] = jack4LFObipolar ? 0 : 1;
  761. // SETUP parameter SETUP_CVTRIG: CV/TRIG port, as trigger input when running as standalone clock generator (only).
  762. setup_Current[SETUP_CVTRIG] = transportTrig ? 0 : 1;
  763. // Parameter's value is, by default 1 for default "Save/Exit".
  764. setup_Current[SETUP_EXIT] = 1;
  765. // Is standalone clock is running at init, or not (previous state).
  766. isBPMRunning = this->runBPMOnInit;
  767. // Strings construction for SETUP.
  768. // SETUP_WELCOME_MESSAGE (unique option).
  769. setupParamName[SETUP_WELCOME_MESSAGE][0] = "Press Btn!";
  770. setupParamXOffset[SETUP_WELCOME_MESSAGE][0] = -1;
  771. for (int i = 1; i < 22; i++) {
  772. setupParamName[SETUP_WELCOME_MESSAGE][i] = ""; // Unused (useless) strings are set to empty.
  773. setupParamXOffset[SETUP_WELCOME_MESSAGE][i] = 0; // Useless x-offsets to 0.
  774. }
  775. // SETUP_CVPOLARITY: having 2 possible parameters.
  776. setupParamName[SETUP_CVPOLARITY][0] = "Bipolar";
  777. setupParamXOffset[SETUP_CVPOLARITY][0] = 2;
  778. setupParamName[SETUP_CVPOLARITY][1] = "Unipolar";
  779. setupParamXOffset[SETUP_CVPOLARITY][1] = 2;
  780. for (int i = 2; i < 22; i++) {
  781. setupParamName[SETUP_CVPOLARITY][i] = ""; // Unused (useless) strings are set to empty.
  782. setupParamXOffset[SETUP_CVPOLARITY][i] = 0; // Useless x-offsets to 0.
  783. }
  784. // SETUP_DURATION: having 9 possible parameters.
  785. setupParamName[SETUP_DURATION][FIXED1MS] = "Fixed 1ms";
  786. setupParamXOffset[SETUP_DURATION][FIXED1MS] = 1;
  787. setupParamName[SETUP_DURATION][FIXED2MS] = "Fixed 2ms";
  788. setupParamXOffset[SETUP_DURATION][FIXED2MS] = 1;
  789. setupParamName[SETUP_DURATION][FIXED5MS] = "Fixed 5ms";
  790. setupParamXOffset[SETUP_DURATION][FIXED5MS] = 1;
  791. setupParamName[SETUP_DURATION][GATE25] = "Gate 25%";
  792. setupParamXOffset[SETUP_DURATION][GATE25] = 2;
  793. setupParamName[SETUP_DURATION][GATE33] = "Gate 33%";
  794. setupParamXOffset[SETUP_DURATION][GATE33] = 2;
  795. setupParamName[SETUP_DURATION][SQUARE] = "Square W.";
  796. setupParamXOffset[SETUP_DURATION][SQUARE] = 2;
  797. setupParamName[SETUP_DURATION][GATE66] = "Gate 66%";
  798. setupParamXOffset[SETUP_DURATION][GATE66] = 2;
  799. setupParamName[SETUP_DURATION][GATE75] = "Gate 75%";
  800. setupParamXOffset[SETUP_DURATION][GATE75] = 2;
  801. setupParamName[SETUP_DURATION][GATE95] = "Gate 95%";
  802. setupParamXOffset[SETUP_DURATION][GATE95] = 2;
  803. // SETUP_OUTVOLTAGE: having 4 possible parameters.
  804. setupParamName[SETUP_OUTVOLTAGE][0] = "+5V";
  805. setupParamXOffset[SETUP_OUTVOLTAGE][0] = 2;
  806. setupParamName[SETUP_OUTVOLTAGE][1] = "+10V";
  807. setupParamXOffset[SETUP_OUTVOLTAGE][1] = 2;
  808. setupParamName[SETUP_OUTVOLTAGE][2] = "+11.7V";
  809. setupParamXOffset[SETUP_OUTVOLTAGE][2] = 2;
  810. setupParamName[SETUP_OUTVOLTAGE][3] = "+2V";
  811. setupParamXOffset[SETUP_OUTVOLTAGE][3] = 2;
  812. for (int i = 4; i < 22; i++) {
  813. setupParamName[SETUP_OUTVOLTAGE][i] = ""; // Unused (useless) strings are set to empty.
  814. setupParamXOffset[SETUP_OUTVOLTAGE][i] = 0; // Useless x-offsets to 0.
  815. }
  816. // SETUP_OUTSRATIOS: having 2 possible parameters.
  817. setupParamName[SETUP_OUTSRATIOS][0] = "All @ x1";
  818. setupParamXOffset[SETUP_OUTSRATIOS][0] = 2;
  819. setupParamName[SETUP_OUTSRATIOS][1] = "Custom";
  820. setupParamXOffset[SETUP_OUTSRATIOS][1] = 2;
  821. for (int i = 2; i < 22; i++) {
  822. setupParamName[SETUP_OUTSRATIOS][i] = ""; // Unused (useless) strings are set to empty.
  823. setupParamXOffset[SETUP_OUTSRATIOS][i] = 0; // Useless x-offsets to 0.
  824. }
  825. // SETUP_OUT1RATIO to SETUP_OUT4RATIO: each ratio for any output jack have 25 possible parameters.
  826. for (int i = 0; i < 4; i++) {
  827. setupParamName[SETUP_OUT1RATIO + i][0] = "/64";
  828. setupParamXOffset[SETUP_OUT1RATIO + i][0] = 32;
  829. setupParamName[SETUP_OUT1RATIO + i][1] = "/32";
  830. setupParamXOffset[SETUP_OUT1RATIO + i][1] = 32;
  831. setupParamName[SETUP_OUT1RATIO + i][2] = "/24";
  832. setupParamXOffset[SETUP_OUT1RATIO + i][2] = 32;
  833. setupParamName[SETUP_OUT1RATIO + i][3] = "/16";
  834. setupParamXOffset[SETUP_OUT1RATIO + i][3] = 32;
  835. setupParamName[SETUP_OUT1RATIO + i][4] = "/12";
  836. setupParamXOffset[SETUP_OUT1RATIO + i][4] = 32;
  837. setupParamName[SETUP_OUT1RATIO + i][5] = "/9";
  838. setupParamXOffset[SETUP_OUT1RATIO + i][5] = 38;
  839. setupParamName[SETUP_OUT1RATIO + i][6] = "/8";
  840. setupParamXOffset[SETUP_OUT1RATIO + i][6] = 38;
  841. setupParamName[SETUP_OUT1RATIO + i][7] = "/6";
  842. setupParamXOffset[SETUP_OUT1RATIO + i][7] = 38;
  843. setupParamName[SETUP_OUT1RATIO + i][8] = "/5";
  844. setupParamXOffset[SETUP_OUT1RATIO + i][8] = 38;
  845. setupParamName[SETUP_OUT1RATIO + i][9] = "/4";
  846. setupParamXOffset[SETUP_OUT1RATIO + i][9] = 38;
  847. setupParamName[SETUP_OUT1RATIO + i][10] = "/3";
  848. setupParamXOffset[SETUP_OUT1RATIO + i][10] = 38;
  849. setupParamName[SETUP_OUT1RATIO + i][11] = "/2";
  850. setupParamXOffset[SETUP_OUT1RATIO + i][11] = 38;
  851. setupParamName[SETUP_OUT1RATIO + i][12] = "x1";
  852. setupParamXOffset[SETUP_OUT1RATIO + i][12] = 38;
  853. setupParamName[SETUP_OUT1RATIO + i][13] = "x2";
  854. setupParamXOffset[SETUP_OUT1RATIO + i][13] = 38;
  855. setupParamName[SETUP_OUT1RATIO + i][14] = "x3";
  856. setupParamXOffset[SETUP_OUT1RATIO + i][14] = 38;
  857. setupParamName[SETUP_OUT1RATIO + i][15] = "x4";
  858. setupParamXOffset[SETUP_OUT1RATIO + i][15] = 38;
  859. setupParamName[SETUP_OUT1RATIO + i][16] = "x5";
  860. setupParamXOffset[SETUP_OUT1RATIO + i][16] = 38;
  861. setupParamName[SETUP_OUT1RATIO + i][17] = "x6";
  862. setupParamXOffset[SETUP_OUT1RATIO + i][17] = 38;
  863. setupParamName[SETUP_OUT1RATIO + i][18] = "x8";
  864. setupParamXOffset[SETUP_OUT1RATIO + i][18] = 38;
  865. setupParamName[SETUP_OUT1RATIO + i][19] = "x9";
  866. setupParamXOffset[SETUP_OUT1RATIO + i][19] = 38;
  867. setupParamName[SETUP_OUT1RATIO + i][20] = "x12";
  868. setupParamXOffset[SETUP_OUT1RATIO + i][20] = 32;
  869. setupParamName[SETUP_OUT1RATIO + i][21] = "x16";
  870. setupParamXOffset[SETUP_OUT1RATIO + i][21] = 32;
  871. setupParamName[SETUP_OUT1RATIO + i][22] = "x24";
  872. setupParamXOffset[SETUP_OUT1RATIO + i][22] = 32;
  873. setupParamName[SETUP_OUT1RATIO + i][23] = "x32";
  874. setupParamXOffset[SETUP_OUT1RATIO + i][23] = 32;
  875. setupParamName[SETUP_OUT1RATIO + i][24] = "x64";
  876. setupParamXOffset[SETUP_OUT1RATIO + i][24] = 32;
  877. }
  878. // SETUP_OUT4LFO: having 7 possible parameters.
  879. setupParamName[SETUP_OUT4LFO][0] = "Disabled";
  880. setupParamXOffset[SETUP_OUT4LFO][0] = 2;
  881. setupParamName[SETUP_OUT4LFO][1] = "Sine";
  882. setupParamXOffset[SETUP_OUT4LFO][1] = 2;
  883. setupParamName[SETUP_OUT4LFO][2] = "Inv. Sine";
  884. setupParamXOffset[SETUP_OUT4LFO][2] = 2;
  885. setupParamName[SETUP_OUT4LFO][3] = "Triangle";
  886. setupParamXOffset[SETUP_OUT4LFO][3] = 2;
  887. setupParamName[SETUP_OUT4LFO][4] = "Inv. Tri.";
  888. setupParamXOffset[SETUP_OUT4LFO][4] = 2;
  889. setupParamName[SETUP_OUT4LFO][5] = "Sawtooth";
  890. setupParamXOffset[SETUP_OUT4LFO][5] = 2;
  891. setupParamName[SETUP_OUT4LFO][6] = "Inv. Saw.";
  892. setupParamXOffset[SETUP_OUT4LFO][6] = 2;
  893. for (int i = 7; i < 22; i++) {
  894. setupParamName[SETUP_OUT4LFO][i] = ""; // Unused (useless) strings are set to empty.
  895. setupParamXOffset[SETUP_OUT4LFO][i] = 0; // Useless x-offsets to 0.
  896. }
  897. // SETUP_OUT4LFOPOLARITY: having 2 possible parameters.
  898. setupParamName[SETUP_OUT4LFOPOLARITY][0] = "Bipolar";
  899. setupParamXOffset[SETUP_OUT4LFOPOLARITY][0] = 2;
  900. setupParamName[SETUP_OUT4LFOPOLARITY][1] = "Unipolar";
  901. setupParamXOffset[SETUP_OUT4LFOPOLARITY][1] = 2;
  902. for (int i = 2; i < 22; i++) {
  903. setupParamName[SETUP_OUT4LFOPOLARITY][i] = ""; // Unused (useless) strings are set to empty.
  904. setupParamXOffset[SETUP_OUT4LFOPOLARITY][i] = 0; // Useless x-offsets to 0.
  905. }
  906. // SETUP_CVTRIG: having 2 possible parameters.
  907. setupParamName[SETUP_CVTRIG][0] = "Play/Stop";
  908. setupParamXOffset[SETUP_CVTRIG][0] = 2;
  909. setupParamName[SETUP_CVTRIG][1] = "Reset In.";
  910. setupParamXOffset[SETUP_CVTRIG][1] = 2;
  911. for (int i = 2; i < 22; i++) {
  912. setupParamName[SETUP_CVTRIG][i] = ""; // Unused (useless) strings are set to empty.
  913. setupParamXOffset[SETUP_CVTRIG][i] = 0; // Useless x-offsets to 0.
  914. }
  915. // SETUP_EXIT: having 4 possible parameters.
  916. setupParamName[SETUP_EXIT][0] = "Canc./Exit";
  917. setupParamXOffset[SETUP_EXIT][0] = -1;
  918. setupParamName[SETUP_EXIT][1] = "Save/Exit";
  919. setupParamXOffset[SETUP_EXIT][1] = 1;
  920. setupParamName[SETUP_EXIT][2] = "Review...";
  921. setupParamXOffset[SETUP_EXIT][2] = 2;
  922. setupParamName[SETUP_EXIT][3] = "Factory";
  923. setupParamXOffset[SETUP_EXIT][3] = 2;
  924. for (int i = 4; i < 22; i++) {
  925. setupParamName[SETUP_EXIT][i] = ""; // Unused (useless) strings are set to empty.
  926. setupParamXOffset[SETUP_EXIT][i] = 0; // Useless x-offsets to 0.
  927. }
  928. activeCLK = inputs[INPUT_CLOCK].active;
  929. activeCLKPrevious = activeCLK;
  930. activeCV = inputs[INPUT_CV_TRIG].active;
  931. activeCVPrevious = activeCV;
  932. // Reinit encoder reading.
  933. encoderCurrent = (int)roundf(10.0f * params[PARAM_ENCODER].value);
  934. encoderPrevious = encoderCurrent;
  935. encoderDelta = 0; // Default assuming encoder isn't moved.
  936. //
  937. currentStep = 0;
  938. rateRatioByEncoder = this->svRatio;
  939. BPM = this->svBPM;
  940. if (activeCLK)
  941. updateDMDtoRunningMode(1);
  942. else updateDMDtoRunningMode(0);
  943. onFirstInitCounter = 0;
  944. }
  945. return;
  946. }
  947. // Current state of CLK port. Active means connected/wired.
  948. activeCLK = inputs[INPUT_CLOCK].active;
  949. // Current state and voltage (CV/TRIG port). Active means connected/wired.
  950. activeCV = inputs[INPUT_CV_TRIG].active;
  951. // Encoder behavior (moved or not).
  952. encoderCurrent = (int)roundf(10.0f * params[PARAM_ENCODER].value);
  953. encoderDelta = 0; // Default assuming encoder isn't moved.
  954. if (abs(encoderCurrent - encoderPrevious) <= 2) {
  955. if (encoderCurrent < encoderPrevious)
  956. encoderDelta = -1; // Counter-clockwise ==> decrement.
  957. else if (encoderCurrent > encoderPrevious)
  958. encoderDelta = 1; // Clockwise => increment.
  959. }
  960. // Save current encoder position to become previous (for next check).
  961. encoderPrevious = encoderCurrent;
  962. if (activeCLK != activeCLKPrevious) {
  963. // Is state was changed (added or removed a patch cable to/away CLK port)?
  964. // New state will become "previous" state.
  965. activeCLKPrevious = activeCLK;
  966. // Reset all steps counter and "gaps", not synchronized.
  967. currentStep = 0;
  968. previousStep = 0;
  969. expectedStep = 0;
  970. stepGap = 0;
  971. stepGapPrevious = 0;
  972. isSync = false;
  973. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++) {
  974. canPulse[i] = false;
  975. nextPulseStep[i] = 0;
  976. }
  977. if (!activeCLK)
  978. updateDMDtoRunningMode(0);
  979. else {
  980. activeCV = inputs[INPUT_CV_TRIG].active;
  981. updateDMDtoRunningMode(1);
  982. }
  983. }
  984. if (activeCV != activeCVPrevious) {
  985. // Is state was changed (added or removed a patch cable to/away CLK port)?
  986. // New state will become "previous" state.
  987. activeCVPrevious = activeCV;
  988. if (activeCLK)
  989. updateDMDtoRunningMode(1);
  990. }
  991. // Considering CV (if applicable e.g. wired!).
  992. voltageOnCV = 0.0f;
  993. isRatioCVmod = false;
  994. rateRatioCV = 0.0f;
  995. if (activeCV) {
  996. voltageOnCV = inputs[INPUT_CV_TRIG].value;
  997. if (activeCLK) {
  998. // Considering CV-RATIO signal to modulate ratio (doesn't matter if SETUP is running, or not).
  999. isRatioCVmod = true;
  1000. if (bipolarCV)
  1001. rateRatioCV = round(clamp(static_cast<float>(voltageOnCV), -5.0f, 5.0f) * 12.6f); // By bipolar voltage (-5V/+5V).
  1002. else rateRatioCV = round((clamp(static_cast<float>(voltageOnCV), 0.0f, 10.0f) - 5.0f) * 12.6f); // By unipolar voltage (0V/+10V).
  1003. // Update DMD.
  1004. updateDMDtoRunningMode(1);
  1005. }
  1006. else {
  1007. // BPM is set by encoded (except while SETUP is running).
  1008. if (!isSetupRunning) {
  1009. if (encoderDelta != 0) {
  1010. BPM = BPM + encoderDelta; // May be increased or decreased.
  1011. if (BPM < 1)
  1012. BPM = 1; // Minimum 1 BPM.
  1013. else if (BPM > 960)
  1014. BPM = 960; // Maximum 960 BPM.
  1015. this->svBPM = BPM;
  1016. // Reset encoder move detection.
  1017. encoderDelta = 0;
  1018. // Update DMD.
  1019. updateDMDtoRunningMode(0);
  1020. }
  1021. }
  1022. }
  1023. }
  1024. else {
  1025. if (!isSetupRunning) {
  1026. if (activeCLK) {
  1027. // Preset ratios are controlled by encoder.
  1028. if (encoderDelta != 0) {
  1029. rateRatioByEncoder = rateRatioByEncoder + encoderDelta;
  1030. if (rateRatioByEncoder < 0)
  1031. rateRatioByEncoder = 0; // Limiting to 0 (/64).
  1032. else if (rateRatioByEncoder > 30)
  1033. rateRatioByEncoder = 30; // Limiting to 30 (X64).
  1034. this->svRatio = rateRatioByEncoder;
  1035. // Related multiplier/divider mode.
  1036. clkModulatorMode = DIV;
  1037. if (rateRatioByEncoder == 15)
  1038. clkModulatorMode = X1;
  1039. else if (rateRatioByEncoder > 15)
  1040. clkModulatorMode = MULT;
  1041. // Reset encoder move detection.
  1042. encoderDelta = 0;
  1043. // Update DMD.
  1044. updateDMDtoRunningMode(1);
  1045. }
  1046. }
  1047. else {
  1048. // BPM is set by encoded (except while SETUP is running).
  1049. if (!isSetupRunning) {
  1050. if (encoderDelta != 0) {
  1051. BPM = BPM + encoderDelta; // May be increased or decreased.
  1052. if (BPM < 1)
  1053. BPM = 1; // Minimum BPM is 1.
  1054. else if (BPM > 960)
  1055. BPM = 960; // Maximum 960 BPM.
  1056. this->svBPM = BPM;
  1057. // Reset encoder move detection.
  1058. encoderDelta = 0;
  1059. // Update DMD.
  1060. updateDMDtoRunningMode(0);
  1061. }
  1062. }
  1063. }
  1064. }
  1065. }
  1066. // Button state.
  1067. buttonPressed = runButton.process(params[PARAM_BUTTON].value);
  1068. // KlokSpid is working as multiplier/divider module (when CLK input port is connected - aka "active").
  1069. if (activeCLK) {
  1070. // Increment step number.
  1071. currentStep++;
  1072. // Using Schmitt trigger (SchmittTrigger is provided by dsp/digital.hpp) to detect thresholds from CLK input connector. Calibration: +1.7V (rising edge), low +0.2V (falling edge).
  1073. if (CLKInputPort.process(rescale(inputs[INPUT_CLOCK].value, 0.2f, 1.7f, 0.0f, 1.0f))) {
  1074. // CLK input is receiving a compliant trigger voltage (rising edge): lit and "afterglow" CLK (red) LED.
  1075. ledClkDelay = 0;
  1076. ledClkAfterglow = true;
  1077. if (previousStep == 0) {
  1078. // No "history", it's the first pulse received on CLK input after a frequency change. Not synchronized.
  1079. expectedStep = 0;
  1080. stepGap = 0;
  1081. stepGapPrevious = 0;
  1082. // stepGap at 0: the pulse duration will be 1 ms (default), or 2 ms or 5 ms (depending SETUP). Variable pulses can't be used as long as frequency remains unknown.
  1083. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  1084. if (isRatioCVmod)
  1085. pulseDuration[i] = GetPulsingTime(0, 1.0f / rateRatioCV); // Ratio is CV-controlled.
  1086. else pulseDuration[i] = GetPulsingTime(0, list_fRatio[rateRatioByEncoder]); // Ratio is controlled by encoder.
  1087. // Not synchronized.
  1088. isSync = false;
  1089. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++) {
  1090. canPulse[i] = (clkModulatorMode != MULT); // MULT needs second pulse to establish source frequency.
  1091. pulseDivCounter[i] = 0; // Used for DIV mode exclusively!
  1092. pulseMultCounter[i] = 0; // Used for MULT mode exclusively!
  1093. }
  1094. previousStep = currentStep;
  1095. }
  1096. else {
  1097. // It's the second pulse received on CLK input after a frequency change.
  1098. stepGapPrevious = stepGap;
  1099. stepGap = currentStep - previousStep;
  1100. expectedStep = currentStep + stepGap;
  1101. // The frequency is known, we can determine the pulse duration (defined by SETUP).
  1102. // The pulse duration also depends of clocking ratio, such "X1", multiplied or divided, and its ratio.
  1103. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  1104. if (isRatioCVmod)
  1105. pulseDuration[i] = GetPulsingTime(stepGap, 1.0f / rateRatioCV); // Ratio is CV-controlled.
  1106. else pulseDuration[i] = GetPulsingTime(stepGap, list_fRatio[rateRatioByEncoder]); // Ratio is controlled by encoder.
  1107. isSync = true;
  1108. if (stepGap > stepGapPrevious)
  1109. isSync = ((stepGap - stepGapPrevious) < 2);
  1110. else if (stepGap < stepGapPrevious)
  1111. isSync = ((stepGapPrevious - stepGap) < 2);
  1112. if (isSync) {
  1113. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  1114. canPulse[i] = (clkModulatorMode != DIV);
  1115. }
  1116. else {
  1117. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  1118. canPulse[i] = (clkModulatorMode == X1);
  1119. }
  1120. previousStep = currentStep;
  1121. }
  1122. switch (clkModulatorMode) {
  1123. case X1:
  1124. // Ratio is x1, following source clock, the easiest scenario! (always sync'd).
  1125. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  1126. canPulse[i] = true;
  1127. break;
  1128. case DIV:
  1129. // Divider mode scenario.
  1130. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++) {
  1131. if (pulseDivCounter[i] == 0) {
  1132. if (isRatioCVmod)
  1133. pulseDivCounter[i] = int(1.0f / rateRatioCV) - 1; // Ratio is CV-controlled.
  1134. else pulseDivCounter[i] = int(list_fRatio[rateRatioByEncoder] - 1); // Ratio is controlled by knob.
  1135. canPulse[i] = true;
  1136. }
  1137. else {
  1138. pulseDivCounter[i]--;
  1139. canPulse[i] = false;
  1140. }
  1141. }
  1142. break;
  1143. case MULT:
  1144. // Multiplier mode scenario: pulsing only when source frequency is established.
  1145. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++) {
  1146. if (isSync) {
  1147. // Next step for pulsing in multiplier mode.
  1148. if (isRatioCVmod) {
  1149. // Ratio is CV-controlled.
  1150. nextPulseStep[i] = currentStep + round(stepGap / rateRatioCV);
  1151. pulseMultCounter[i] = int(rateRatioCV) - 1;
  1152. }
  1153. else {
  1154. // Ratio is controlled by knob.
  1155. nextPulseStep[i] = currentStep + round(stepGap * list_fRatio[rateRatioByEncoder]);
  1156. pulseMultCounter[i] = round(1.0f / list_fRatio[rateRatioByEncoder]) - 1;
  1157. }
  1158. canPulse[i] = true;
  1159. }
  1160. }
  1161. }
  1162. }
  1163. else {
  1164. // At this point, it's not a rising edge!
  1165. // When running as multiplier, may pulse here too during low voltages on CLK input!
  1166. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++) {
  1167. if (isSync && (nextPulseStep[i] == currentStep) && (clkModulatorMode == MULT)) {
  1168. if (isRatioCVmod)
  1169. nextPulseStep[i] = currentStep + round(stepGap / rateRatioCV); // Ratio is CV-controlled.
  1170. else nextPulseStep[i] = currentStep + round(stepGap * list_fRatio[rateRatioByEncoder]); // Ratio is controlled by knob.
  1171. // This block is to avoid continuous pulsing if no more receiving incoming signal.
  1172. if (pulseMultCounter[i] > 0) {
  1173. pulseMultCounter[i]--;
  1174. canPulse[i] = true;
  1175. }
  1176. else {
  1177. canPulse[i] = false;
  1178. isSync = false;
  1179. }
  1180. }
  1181. }
  1182. }
  1183. }
  1184. else {
  1185. // CLK input port isn't connected (not active): KlokSpid is working as clock generator.
  1186. ledClkAfterglow = false;
  1187. if (previousBPM == BPM) {
  1188. // CV-RATIO/TRIG. input port is used as TRIG. to reset clock generator or to toggle BPM-clocking, while voltage is +1.7 V (or above) - rising edge.
  1189. if (activeCV) {
  1190. if (runTriggerPort.process(rescale(voltageOnCV, 0.2f, 1.7f, 0.0f, 1.0f))) {
  1191. // On +1.7 V trigger (rising edge), the clock generator state if toggled (started or stopped).
  1192. if (transportTrig) {
  1193. // CV-RATIO/TRIG. input port (TRIG.) is configured as "play/stop toggle".
  1194. isBPMRunning = !isBPMRunning;
  1195. // BPM state persistence (json).
  1196. this->runBPMOnInit = isBPMRunning;
  1197. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  1198. nextPulseStep[i] = 0;
  1199. currentStep = 0;
  1200. // Reset phase for LFO jack #4.
  1201. resetPhase = true;
  1202. }
  1203. else {
  1204. // CV-RATIO/TRIG. input port (TRIG.) is configured as RESET input (default factory): assuming it's an incoming reset signal!
  1205. currentStep = 0;
  1206. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++)
  1207. nextPulseStep[i] = 0;
  1208. // Reset phase for LFO jack #4.
  1209. resetPhase = true;
  1210. }
  1211. }
  1212. }
  1213. // Incrementing step counter...
  1214. currentStep++;
  1215. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++) {
  1216. if (isBPMRunning) {
  1217. if (currentStep >= nextPulseStep[i])
  1218. canPulse[i] = true;
  1219. if (canPulse[i]) {
  1220. // Setting pulse...
  1221. // Define the step for next pulse. Time reference is given by (current) engine samplerate setting.
  1222. nextPulseStep[i] = currentStep + round(60.0f * engineGetSampleRate() * list_outRatiof[outputRatioInUse[i]] / BPM);
  1223. // Define the pulse duration (fixed or variable-length).
  1224. pulseDuration[i] = GetPulsingTime(engineGetSampleRate(), 60.0f / BPM * list_outRatiof[outputRatioInUse[i]]);
  1225. if (i == OUTPUT_4)
  1226. resetPhase = true;
  1227. }
  1228. }
  1229. else {
  1230. // BPM clock is stopped.
  1231. canPulse[i] = false;
  1232. nextPulseStep[i] = 0;
  1233. currentStep = 0;
  1234. // Reset phase for LFO jack #4.
  1235. resetPhase = true;
  1236. }
  1237. }
  1238. }
  1239. else {
  1240. // Update DMD (number of BPM).
  1241. updateDMDtoRunningMode(0);
  1242. // Altered BPM: reset phase for LFO jack #4.
  1243. resetPhase = true;
  1244. }
  1245. previousBPM = BPM;
  1246. }
  1247. // Using pulse generator to output to all ports.
  1248. for (int i = OUTPUT_1; i < NUM_OUTPUTS; i++) {
  1249. if (canPulse[i]) {
  1250. if (i == OUTPUT_4) {
  1251. if (resetPhase) {
  1252. LFOjack4.phase = 0.0f;
  1253. resetPhase = false;
  1254. }
  1255. }
  1256. // Sending pulse, using pulse generator.
  1257. sendPulse[i].triggerDuration = pulseDuration[i];
  1258. sendPulse[i].trigger(pulseDuration[i]);
  1259. canPulse[i] = false;
  1260. }
  1261. sendingOutput[i] = sendPulse[i].process(engineGetSampleTime());
  1262. if (i < OUTPUT_4)
  1263. outputs[i].value = sendingOutput[i] ? outVoltage : 0.0f;
  1264. else {
  1265. // Jack #4 specific (LFO feature to output jack #4, but: clock generator mode only, and if jack ratio is set at "x1" only).
  1266. if ((!activeCLK) && (jack4LFO != 0) && (outputRatioInUse[OUTPUT_4] == 12)) {
  1267. LFOjack4.invert = ((jack4LFO % 2) == 0);
  1268. LFOjack4.freq = (float)BPM / 60.0f;
  1269. LFOjack4.step(engineGetSampleTime());
  1270. if (jack4LFObipolar)
  1271. LFOjack4.offset = 0.0f;
  1272. else LFOjack4.offset = outVoltage / 2.0f;
  1273. switch (jack4LFO) {
  1274. case 1:
  1275. case 2:
  1276. // LFO for jack #4 is a sine-based waveform.
  1277. outputs[OUTPUT_4].value = isBPMRunning ? outVoltage / 2.0f * LFOjack4.sin() : 0.0f;
  1278. break;
  1279. case 3:
  1280. case 4:
  1281. // LFO for jack #4 is a triangle-based waveform.
  1282. outputs[OUTPUT_4].value = isBPMRunning ? outVoltage / 2.0f * LFOjack4.tri() : 0.0f;
  1283. break;
  1284. case 5:
  1285. case 6:
  1286. // LFO for jack #4 is a sawtooth-based waveform.
  1287. outputs[OUTPUT_4].value = isBPMRunning ? outVoltage / 2.0f * LFOjack4.saw() : 0.0f;
  1288. }
  1289. }
  1290. else outputs[OUTPUT_4].value = sendingOutput[OUTPUT_4] ? outVoltage : 0.0f;
  1291. }
  1292. }
  1293. // Afterglow for CLK (red) LED.
  1294. if (ledClkAfterglow) {
  1295. if (inputs[INPUT_CLOCK].value < 1.7f) {
  1296. ledClkDelay++;
  1297. if (ledClkDelay > round(engineGetSampleRate() / 16)) {
  1298. ledClkAfterglow = false;
  1299. ledClkDelay = 0;
  1300. }
  1301. }
  1302. }
  1303. // Handling the button (it's a momentary button, handled by a dedicated Schmitt trigger).
  1304. // - Short presses toggles BPM clock start/stops (when released).
  1305. // - Long press to enter SETUP.
  1306. // - When SETUP is running, press to advance to next parameter.
  1307. if (buttonPressed) {
  1308. if (!isSetupRunning && !isEnteringSetup) {
  1309. // Try to enter SETUP... starting delay counter for 2 seconds.
  1310. isEnteringSetup = true;
  1311. setupCounter = 0;
  1312. allowedButtonHeld = true; // Allow to keep button held.
  1313. }
  1314. else if (isSetupRunning && !isExitingSetup) {
  1315. // Try to quick save/exit SETUP... starting delay counter for 2 seconds.
  1316. isExitingSetup = true;
  1317. setupCounter = 0;
  1318. // Necessary to avoid continuous entry/exit SETUP while button is held.
  1319. allowedButtonHeld = true; // Allow to keep button held.
  1320. }
  1321. else allowedButtonHeld = false; // Button must be released.
  1322. } // Don't add "else" clause from here, otherwise be sure it doesn't work!
  1323. if (buttonPressed && isSetupRunning) {
  1324. // SETUP is running: when (shortly) pressed, advance to next parameter.
  1325. // Storing previous edited parameter into "edited" table prior to advance to next SETUP parameter.
  1326. setup_Edited[setup_ParamIdx] = setup_CurrentValue;
  1327. // Advance to next SETUP parameter (conditional).
  1328. if ((setup_ParamIdx == SETUP_OUTSRATIOS) && (setup_Edited[SETUP_OUTSRATIOS] == 0))
  1329. setup_ParamIdx = SETUP_OUT4LFO; // Bypass all four jack ratios, and go directly to jack #4 LFO.
  1330. else if ((setup_ParamIdx == SETUP_OUT4RATIO) && (setup_Edited[SETUP_OUTSRATIOS] == 1) && (setup_Edited[SETUP_OUT4RATIO] != 12))
  1331. setup_ParamIdx = SETUP_CVTRIG; // In case custom output jack ratios, jack #4 ratio isn't x1, bypass jack #4 LFO & polarity, and go directly to CV/TRIG.
  1332. else if ((setup_ParamIdx == SETUP_OUT4LFO) && (setup_Edited[SETUP_OUT4LFO] == 0))
  1333. setup_ParamIdx = SETUP_CVTRIG; // Bypass LFO polarity entry is LFO mode is disabled, go directly to CV/TRIG.
  1334. else setup_ParamIdx++; // Advance to next, in all other cases.
  1335. // These variables are used to cycle display (parameter name, then its value). DEPRECATED!
  1336. setupCounter = 0;
  1337. if (setup_ParamIdx > SETUP_EXIT) {
  1338. // Exiting SETUP. From here, all required actions on exit SETUP (such save, cancel changes, reset to default factory etc), except "Review" option!
  1339. switch (setup_Edited[SETUP_EXIT]) {
  1340. case 0:
  1341. // Cancel/Exit: all changes from SETUP are ignored (changes are cancelled).
  1342. // all previous (backuped) will be restored (any change is ignored).
  1343. for (int i = 1; i < SETUP_EXIT; i++)
  1344. setup_Current[i] = setup_Backup[i];
  1345. // Restored pre-SETUP settings, so it's useless to save them as "json" persistent.
  1346. UpdateKlokSpidSettings(false);
  1347. break;
  1348. case 1:
  1349. // Save/Exit: all parameters from SETUP will be saved.
  1350. for (int i = 1; i < SETUP_EXIT; i++)
  1351. setup_Current[i] = setup_Edited[i];
  1352. // Using new settings (because edited are saved), so it's mandatory to save them as "json" persistent datas.
  1353. UpdateKlokSpidSettings(true);
  1354. break;
  1355. case 2:
  1356. // Review: return to first parameter (don't exit "SETUP" in this choice is selected).
  1357. setup_ParamIdx = 1;
  1358. setup_CurrentValue = setup_Edited[1]; // Bypass the welcome message and edit first parameter.
  1359. strcpy(dmdMessage1, stringToPchar(setupMenuName[1]));
  1360. xOffsetValue = setupParamXOffset[1][setup_CurrentValue];
  1361. strcpy(dmdMessage2, stringToPchar(setupParamName[1][setup_CurrentValue]));
  1362. break;
  1363. case 3:
  1364. // Factory: restore all factory default parameters.
  1365. for (int i = 1; i < SETUP_EXIT; i++)
  1366. setup_Current[i] = setup_Factory[i];
  1367. // Using new settings (because restored as default factory), like "Save/Exit", it's mandatory to save them as "json" persistent datas.
  1368. UpdateKlokSpidSettings(true);
  1369. break;
  1370. }
  1371. // Exit SETUP, except if "Review" was selected.
  1372. if (setup_Edited[SETUP_EXIT] != 2) {
  1373. // Exit SETUP (except if "Review" was selected).
  1374. setupCounter = 0;
  1375. // Clearing flag because now exit SETUP.
  1376. isSetupRunning = false;
  1377. // Update DMD to current running mode.
  1378. if (activeCLK)
  1379. updateDMDtoRunningMode(1); // Ratio (clock modulator mode).
  1380. else updateDMDtoRunningMode(0); // Number of BPM.
  1381. }
  1382. }
  1383. else if (setup_ParamIdx == SETUP_EXIT) {
  1384. // Last default proposed parameter will be "Save and Exit" (SETUP).
  1385. setup_CurrentValue = 1;
  1386. // Update DMD.
  1387. strcpy(dmdMessage1, stringToPchar(setupMenuName[setup_ParamIdx]));
  1388. xOffsetValue = setupParamXOffset[setup_ParamIdx][setup_CurrentValue];
  1389. strcpy(dmdMessage2, stringToPchar(setupParamName[setup_ParamIdx][setup_CurrentValue]));
  1390. }
  1391. else {
  1392. // Set currently displayed (on DMD) value as current (edited) parameter.
  1393. setup_CurrentValue = setup_Edited[setup_ParamIdx];
  1394. // Update DMD.
  1395. strcpy(dmdMessage1, stringToPchar(setupMenuName[setup_ParamIdx]));
  1396. xOffsetValue = setupParamXOffset[setup_ParamIdx][setup_CurrentValue];
  1397. strcpy(dmdMessage2, stringToPchar(setupParamName[setup_ParamIdx][setup_CurrentValue]));
  1398. }
  1399. }
  1400. if (runButton.isHigh()) {
  1401. // Button is held, don't matter if SETUP is running or not.
  1402. // Always increment the counter.
  1403. setupCounter++;
  1404. if (isSetupRunning && isExitingSetup) {
  1405. if (setupCounter >= 2 * engineGetSampleRate()) {
  1406. // Button was held during 2 seconds (while SETUP is running): now KlokSpid module exit SETUP (doing auto "Save/Exit").
  1407. //
  1408. isExitingSetup = false;
  1409. setupCounter = 0;
  1410. allowedButtonHeld = false; // Button must be released (retrigger is required).
  1411. for (int i=0; i<SETUP_EXIT; i++)
  1412. setup_Current[i] = setup_Edited[i];
  1413. // Quick SETUP-exit, doing automatic "Save/exit", by this way using new settings (because edited are saved), so it's mandatory to save them as "json" persistent datas.
  1414. UpdateKlokSpidSettings(true);
  1415. // Clearing flag because now exit SETUP.
  1416. isSetupRunning = false;
  1417. // Update DMD regadling current running mode.
  1418. if (activeCLK)
  1419. updateDMDtoRunningMode(1); // Ratio (clock modulator mode).
  1420. else updateDMDtoRunningMode(0); // Number of BPM.
  1421. }
  1422. }
  1423. else {
  1424. if (isEnteringSetup && (setupCounter >= 2 * engineGetSampleRate())) {
  1425. // Button was finally held during 2 seconds: now KlokSpid module runs its SETUP. Initializing some variables/arrays/flags first.
  1426. //
  1427. isEnteringSetup = false;
  1428. setupCounter = 0;
  1429. // Button must be released (retrigger is required).
  1430. allowedButtonHeld = false;
  1431. // Menu entry #0 is used to display "- SETUP -" as welcome message (don't have parameters, so be sure "parameter" is set to 0).
  1432. setup_Current[SETUP_WELCOME_MESSAGE] = 0;
  1433. // Copy current parameters (since initialization or previous SETUP) to "edited" parameters before entering SETUP.
  1434. // Also use a "backup" table in case of "Cancel/Exit" choice.
  1435. for (int i = 0; i < SETUP_EXIT; i++) {
  1436. setup_Backup[i] = setup_Current[i];
  1437. setup_Edited[i] = setup_Current[i];
  1438. }
  1439. // Lastest menu entry is used to exit SETUP menu, by default with save.
  1440. setup_Edited[SETUP_EXIT] = 1;
  1441. // Select first parameter will ne displayed. In fact, the welcome message "- SETUP -" on DMD when entered SETUP.
  1442. setup_ParamIdx = 0;
  1443. // Update DMD.
  1444. strcpy(dmdMessage1, stringToPchar(setupMenuName[SETUP_WELCOME_MESSAGE]));
  1445. xOffsetValue = setupParamXOffset[SETUP_WELCOME_MESSAGE][0];
  1446. strcpy(dmdMessage2, stringToPchar(setupParamName[SETUP_WELCOME_MESSAGE][0]));
  1447. // This flag indicates SETUP is running.
  1448. isSetupRunning = true;
  1449. }
  1450. }
  1451. }
  1452. else {
  1453. if (isEnteringSetup) {
  1454. // Abort entering SETUP.
  1455. isEnteringSetup = false;
  1456. // Button works as BPM start/stop toggle: inverting state.
  1457. isBPMRunning = !isBPMRunning;
  1458. // Persistence for current BPM-state (toJson).
  1459. this->runBPMOnInit = isBPMRunning;
  1460. }
  1461. else if (isSetupRunning && isExitingSetup) {
  1462. // Abort quick exit SETUP.
  1463. isExitingSetup = false;
  1464. }
  1465. setupCounter = 0;
  1466. allowedButtonHeld = false; // button must be "retriggered", to avoid continuous entry/exit SETUP while held.
  1467. }
  1468. // KlokSpid module's SETUP.
  1469. if (isSetupRunning) {
  1470. // SETUP is running.
  1471. if (encoderDelta > 0) {
  1472. // Incremented encoder (rotated clockwise).
  1473. setup_CurrentValue++;
  1474. if (setup_CurrentValue >= setup_NumValue[setup_ParamIdx])
  1475. setup_CurrentValue = 0; // End of values list: return to first value.
  1476. // Update DMD.
  1477. strcpy(dmdMessage1, stringToPchar(setupMenuName[setup_ParamIdx]));
  1478. xOffsetValue = setupParamXOffset[setup_ParamIdx][setup_CurrentValue];
  1479. strcpy(dmdMessage2, stringToPchar(setupParamName[setup_ParamIdx][setup_CurrentValue]));
  1480. // Update current parameter "in realtime".
  1481. setup_Current[setup_ParamIdx] = setup_CurrentValue;
  1482. // Update parameters, but without "jSon" persistence while SETUP is running!
  1483. UpdateKlokSpidSettings(false);
  1484. // Reset encoder move detection.
  1485. encoderDelta = 0;
  1486. }
  1487. else if (encoderDelta < 0) {
  1488. // Decremented encoder (rotated counter-clockwise).
  1489. if (setup_NumValue[setup_ParamIdx] != 0) {
  1490. if (setup_CurrentValue == 0)
  1491. setup_CurrentValue = setup_NumValue[setup_ParamIdx] - 1; // Return to last possible value.
  1492. else setup_CurrentValue--; //Previous value.
  1493. }
  1494. // Update DMD.
  1495. strcpy(dmdMessage1, stringToPchar(setupMenuName[setup_ParamIdx]));
  1496. xOffsetValue = setupParamXOffset[setup_ParamIdx][setup_CurrentValue];
  1497. strcpy(dmdMessage2, stringToPchar(setupParamName[setup_ParamIdx][setup_CurrentValue]));
  1498. // Update current parameter "in realtime".
  1499. setup_Current[setup_ParamIdx] = setup_CurrentValue;
  1500. // Update parameters, but without "jSon" persistence while SETUP is running!
  1501. UpdateKlokSpidSettings(false);
  1502. // Reset encoder move detection.
  1503. encoderDelta = 0;
  1504. }
  1505. }
  1506. // Handling LEDs on KlokSpid module (at the end of step).
  1507. lights[LED_SYNC_GREEN].value = ((activeCLK && (isSync || (clkModulatorMode == X1))) || (!activeCLK && isBPMRunning)) ? 1.0 : 0.0; // Unique "SYNC" LED: will be lit green color when sync'd / BPM is running.
  1508. lights[LED_SYNC_RED].value = ((activeCLK && (isSync || (clkModulatorMode == X1))) || (!activeCLK && isBPMRunning)) ? 0.0 : 1.0; // Unique "SYNC" LED: will be lit red color (opposite cases).
  1509. lights[LED_CLK].value = (isSetupRunning || ledClkAfterglow) ? 1.0 : 0.0;
  1510. lights[LED_CV_TRIG].value = (isSetupRunning || activeCV) ? 1.0 : 0.0; // TODO -- MUST BE ENHANCED!
  1511. lights[LED_CVMODE].value = (isSetupRunning || activeCLK) ? 1.0 : 0.0;
  1512. lights[LED_TRIGMODE].value = (isSetupRunning || !activeCLK) ? 1.0 : 0.0;
  1513. } // End of KlokSpid module's step() function.
  1514. // Dot-matrix display (DMD) and small displays (near output jack) handler.
  1515. struct KlokSpidDMD : TransparentWidget {
  1516. KlokSpidModule *module;
  1517. std::shared_ptr<Font> font;
  1518. KlokSpidDMD() {
  1519. font = Font::load(assetPlugin(plugin, "res/fonts/LEDCounter7.ttf"));
  1520. }
  1521. void updateDMD1(NVGcontext *vg, Vec pos, NVGcolor DMDtextColor, char* dmdMessage1) {
  1522. nvgFontSize(vg, 16);
  1523. nvgFontFaceId(vg, font->handle);
  1524. nvgTextLetterSpacing(vg, -2);
  1525. nvgFillColor(vg, nvgTransRGBA(DMDtextColor, 0xff));
  1526. nvgText(vg, pos.x, pos.y, dmdMessage1, NULL);
  1527. }
  1528. void updateDMD2(NVGcontext *vg, Vec pos, NVGcolor DMDtextColor, int xOffsetValue, char* dmdMessage2) {
  1529. nvgFontSize(vg, 20);
  1530. nvgFontFaceId(vg, font->handle);
  1531. nvgTextLetterSpacing(vg, -1);
  1532. nvgFillColor(vg, nvgTransRGBA(DMDtextColor, 0xff));
  1533. nvgText(vg, pos.x + xOffsetValue, pos.y, dmdMessage2, NULL);
  1534. }
  1535. void updateDispOut1(NVGcontext *vg, Vec pos, NVGcolor DMDtextColor, int xdispOutOffset1, char* dispOut1) {
  1536. nvgFontSize(vg, 14);
  1537. nvgFontFaceId(vg, font->handle);
  1538. nvgTextLetterSpacing(vg, -1);
  1539. nvgFillColor(vg, nvgTransRGBA(DMDtextColor, 0xff));
  1540. nvgText(vg, pos.x + xdispOutOffset1, pos.y, dispOut1, NULL);
  1541. }
  1542. void updateDispOut2(NVGcontext *vg, Vec pos, NVGcolor DMDtextColor, int xdispOutOffset2, char* dispOut2) {
  1543. nvgFontSize(vg, 14);
  1544. nvgFontFaceId(vg, font->handle);
  1545. nvgTextLetterSpacing(vg, -1);
  1546. nvgFillColor(vg, nvgTransRGBA(DMDtextColor, 0xff));
  1547. nvgText(vg, pos.x + xdispOutOffset2, pos.y, dispOut2, NULL);
  1548. }
  1549. void updateDispOut3(NVGcontext *vg, Vec pos, NVGcolor DMDtextColor, int xdispOutOffset3, char* dispOut3) {
  1550. nvgFontSize(vg, 14);
  1551. nvgFontFaceId(vg, font->handle);
  1552. nvgTextLetterSpacing(vg, -1);
  1553. nvgFillColor(vg, nvgTransRGBA(DMDtextColor, 0xff));
  1554. nvgText(vg, pos.x + xdispOutOffset3, pos.y, dispOut3, NULL);
  1555. }
  1556. void updateDispOut4(NVGcontext *vg, Vec pos, NVGcolor DMDtextColor, int xdispOutOffset4, char* dispOut4) {
  1557. nvgFontSize(vg, 14);
  1558. nvgFontFaceId(vg, font->handle);
  1559. nvgTextLetterSpacing(vg, -1);
  1560. nvgFillColor(vg, nvgTransRGBA(DMDtextColor, 0xff));
  1561. nvgText(vg, pos.x + xdispOutOffset4, pos.y, dispOut4, NULL);
  1562. }
  1563. void draw(NVGcontext *vg) override {
  1564. // Main DMD.
  1565. updateDMD1(vg, Vec(14, box.size.y - 174), module->DMDtextColor, module->dmdMessage1);
  1566. updateDMD2(vg, Vec(12, box.size.y - 152), module->DMDtextColor, module->xOffsetValue, module->dmdMessage2);
  1567. // Display between output jacks.
  1568. updateDispOut1(vg, Vec(35, box.size.y + 61), module->DMDtextColor, module->xdispOutOffset1, module->dispOut1);
  1569. updateDispOut2(vg, Vec(62.5, box.size.y + 61), module->DMDtextColor, module->xdispOutOffset2, module->dispOut2);
  1570. updateDispOut3(vg, Vec(35, box.size.y + 75), module->DMDtextColor, module->xdispOutOffset3, module->dispOut3);
  1571. updateDispOut4(vg, Vec(62.5, box.size.y + 75), module->DMDtextColor, module->xdispOutOffset4, module->dispOut4);
  1572. }
  1573. };
  1574. struct KlokSpidWidget : ModuleWidget {
  1575. // Themed plates.
  1576. SVGPanel *panelKlokSpidClassic;
  1577. SVGPanel *panelKlokSpidStageRepro;
  1578. SVGPanel *panelKlokSpidAbsoluteNight;
  1579. SVGPanel *panelKlokSpidDarkSignature;
  1580. SVGPanel *panelKlokSpidDeepBlueSignature;
  1581. SVGPanel *panelCarbonSignature;
  1582. // Silver Torx screws.
  1583. SVGScrew *topLeftScrewSilver;
  1584. SVGScrew *topRightScrewSilver;
  1585. SVGScrew *bottomLeftScrewSilver;
  1586. SVGScrew *bottomRightScrewSilver;
  1587. // Gold Torx screws.
  1588. SVGScrew *topLeftScrewGold;
  1589. SVGScrew *topRightScrewGold;
  1590. SVGScrew *bottomLeftScrewGold;
  1591. SVGScrew *bottomRightScrewGold;
  1592. // Silver button.
  1593. SVGSwitch *buttonSilver;
  1594. // Gold button.
  1595. SVGSwitch *buttonGold;
  1596. //
  1597. KlokSpidWidget(KlokSpidModule *module);
  1598. void step() override;
  1599. // Action for "Initialize", from context-menu, is (for now) bypassed.
  1600. void reset() override {
  1601. return;
  1602. };
  1603. // Action for "Randomize", from context-menu, is (for now) bypassed.
  1604. void randomize() override {
  1605. return;
  1606. };
  1607. Menu* createContextMenu() override;
  1608. };
  1609. KlokSpidWidget::KlokSpidWidget(KlokSpidModule *module) : ModuleWidget(module) {
  1610. box.size = Vec(8 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT);
  1611. // Model: Classic (beige plate, always default GUI when added from modules menu).
  1612. panelKlokSpidClassic = new SVGPanel();
  1613. panelKlokSpidClassic->box.size = box.size;
  1614. panelKlokSpidClassic->setBackground(SVG::load(assetPlugin(plugin, "res/KlokSpid_Classic.svg")));
  1615. addChild(panelKlokSpidClassic);
  1616. // Model: Stage Repro.
  1617. panelKlokSpidStageRepro = new SVGPanel();
  1618. panelKlokSpidStageRepro->box.size = box.size;
  1619. panelKlokSpidStageRepro->setBackground(SVG::load(assetPlugin(plugin, "res/KlokSpid_Stage_Repro.svg")));
  1620. addChild(panelKlokSpidStageRepro);
  1621. // Model: Absolute Night.
  1622. panelKlokSpidAbsoluteNight = new SVGPanel();
  1623. panelKlokSpidAbsoluteNight->box.size = box.size;
  1624. panelKlokSpidAbsoluteNight->setBackground(SVG::load(assetPlugin(plugin, "res/KlokSpid_Absolute_Night.svg")));
  1625. addChild(panelKlokSpidAbsoluteNight);
  1626. // Model: Dark "Signature".
  1627. panelKlokSpidDarkSignature = new SVGPanel();
  1628. panelKlokSpidDarkSignature->box.size = box.size;
  1629. panelKlokSpidDarkSignature->setBackground(SVG::load(assetPlugin(plugin, "res/KlokSpid_Dark_Signature.svg")));
  1630. addChild(panelKlokSpidDarkSignature);
  1631. // Model: Deepblue "Signature".
  1632. panelKlokSpidDeepBlueSignature = new SVGPanel();
  1633. panelKlokSpidDeepBlueSignature->box.size = box.size;
  1634. panelKlokSpidDeepBlueSignature->setBackground(SVG::load(assetPlugin(plugin, "res/KlokSpid_Deepblue_Signature.svg")));
  1635. addChild(panelKlokSpidDeepBlueSignature);
  1636. // Model: Carbon "Signature".
  1637. panelCarbonSignature = new SVGPanel();
  1638. panelCarbonSignature->box.size = box.size;
  1639. panelCarbonSignature->setBackground(SVG::load(assetPlugin(plugin, "res/KlokSpid_Carbon_Signature.svg")));
  1640. addChild(panelCarbonSignature);
  1641. // Always four screws for 8 HP module, may are silver or gold, depending model.
  1642. // Top-left silver Torx screw.
  1643. topLeftScrewSilver = Widget::create<Torx_Silver>(Vec(RACK_GRID_WIDTH, 0));
  1644. addChild(topLeftScrewSilver);
  1645. // Top-right silver Torx screw.
  1646. topRightScrewSilver = Widget::create<Torx_Silver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0));
  1647. addChild(topRightScrewSilver);
  1648. // Bottom-left silver Torx screw.
  1649. bottomLeftScrewSilver = Widget::create<Torx_Silver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH));
  1650. addChild(bottomLeftScrewSilver);
  1651. // Bottom-right silver Torx screw.
  1652. bottomRightScrewSilver = Widget::create<Torx_Silver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH));
  1653. addChild(bottomRightScrewSilver);
  1654. // Top-left gold Torx screw.
  1655. topLeftScrewGold = Widget::create<Torx_Gold>(Vec(RACK_GRID_WIDTH, 0));
  1656. addChild(topLeftScrewGold);
  1657. // Top-right gold Torx screw.
  1658. topRightScrewGold = Widget::create<Torx_Gold>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0));
  1659. addChild(topRightScrewGold);
  1660. // Bottom-left gold Torx screw.
  1661. bottomLeftScrewGold = Widget::create<Torx_Gold>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH));
  1662. addChild(bottomLeftScrewGold);
  1663. // Bottom-right gold Torx screw.
  1664. bottomRightScrewGold = Widget::create<Torx_Gold>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH));
  1665. addChild(bottomRightScrewGold);
  1666. // DMD display.
  1667. {
  1668. KlokSpidDMD *display = new KlokSpidDMD();
  1669. display->module = module;
  1670. display->box.pos = Vec(0, 0);
  1671. display->box.size = Vec(box.size.x, 234);
  1672. addChild(display);
  1673. }
  1674. // Ratio/BPM/SETUP encoder.
  1675. //addParam(ParamWidget::create<KS_Encoder>(Vec(20, 106), module, KlokSpidModule::PARAM_ENCODER, -INFINITY, +INFINITY, 0.0));
  1676. module->klokspdEncoder = dynamic_cast<Knob*>(Knob::create<KS_Encoder>(Vec(20, 106), module, KlokSpidModule::PARAM_ENCODER, -INFINITY, +INFINITY, 0.0));
  1677. addParam(module->klokspdEncoder);
  1678. // Push button (silver), used to toggle START/STOP, also used to enter SETUP, and to advance to next parameter in SETUP.
  1679. buttonSilver = ParamWidget::create<KS_ButtonSilver>(Vec(94, 178), module, KlokSpidModule::PARAM_BUTTON , 0.0, 1.0, 0.0);
  1680. addParam(buttonSilver);
  1681. // Push button (gold), used to toggle START/STOP, also used to enter SETUP, and to advance to next parameter in SETUP.
  1682. buttonGold = ParamWidget::create<KS_ButtonGold>(Vec(94, 178), module, KlokSpidModule::PARAM_BUTTON , 0.0, 1.0, 0.0);
  1683. addParam(buttonGold);
  1684. // Input ports (golden jacks).
  1685. addInput(Port::create<PJ301M_In>(Vec(24, 215), Port::INPUT, module, KlokSpidModule::INPUT_CLOCK));
  1686. addInput(Port::create<PJ301M_In>(Vec(72, 215), Port::INPUT, module, KlokSpidModule::INPUT_CV_TRIG));
  1687. // Output ports (golden jacks).
  1688. addOutput(Port::create<PJ301M_Out>(Vec(10, 261), Port::OUTPUT, module, KlokSpidModule::OUTPUT_1));
  1689. addOutput(Port::create<PJ301M_Out>(Vec(86, 261), Port::OUTPUT, module, KlokSpidModule::OUTPUT_2));
  1690. addOutput(Port::create<PJ301M_Out>(Vec(10, 310), Port::OUTPUT, module, KlokSpidModule::OUTPUT_3));
  1691. addOutput(Port::create<PJ301M_Out>(Vec(86, 310), Port::OUTPUT, module, KlokSpidModule::OUTPUT_4));
  1692. // LEDs (red for CLK input, yellow for CV-RATIO/TRIG input).
  1693. addChild(ModuleLightWidget::create<MediumLight<RedLight>>(Vec(31.5, 242), module, KlokSpidModule::LED_CLK));
  1694. addChild(ModuleLightWidget::create<MediumLight<KlokSpidOrangeLight>>(Vec(79.5, 242), module, KlokSpidModule::LED_CV_TRIG));
  1695. //addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(56, 242), module, KlokSpidModule::LED_OUTPUT));
  1696. addChild(ModuleLightWidget::create<MediumLight<GreenRedLight>>(Vec(7, 96), module, KlokSpidModule::LED_SYNC_GREEN)); // Unified SYNC LED (green/red).
  1697. // Small-sized orange LEDs near CV-RATIO/TRIG input port (when lit, each LED indicates the port role).
  1698. addChild(ModuleLightWidget::create<SmallLight<KlokSpidOrangeLight>>(Vec(67.5, 206), module, KlokSpidModule::LED_CVMODE));
  1699. addChild(ModuleLightWidget::create<SmallLight<KlokSpidOrangeLight>>(Vec(95, 206), module, KlokSpidModule::LED_TRIGMODE));
  1700. }
  1701. //// Make one visible theme at once!
  1702. void KlokSpidWidget::step() {
  1703. KlokSpidModule *klokspidmodule = dynamic_cast<KlokSpidModule*>(module);
  1704. assert(klokspidmodule);
  1705. // "Signature"-line modules are using gold parts (instead of silver).
  1706. bool isSignatureLine = (klokspidmodule->Theme > 2);
  1707. // Themed module plate, selected via context-menu, is visible (all others are obviously, hidden).
  1708. panelKlokSpidClassic->visible = (klokspidmodule->Theme == 0);
  1709. panelKlokSpidStageRepro->visible = (klokspidmodule->Theme == 1);
  1710. panelKlokSpidAbsoluteNight->visible = (klokspidmodule->Theme == 2);
  1711. panelKlokSpidDarkSignature->visible = (klokspidmodule->Theme == 3);
  1712. panelKlokSpidDeepBlueSignature->visible = (klokspidmodule->Theme == 4);
  1713. panelCarbonSignature->visible = (klokspidmodule->Theme == 5);
  1714. // Silver Torx screws are visible only for non-"Signature" modules.
  1715. topLeftScrewSilver->visible = !isSignatureLine;
  1716. topRightScrewSilver->visible = !isSignatureLine;
  1717. bottomLeftScrewSilver->visible = !isSignatureLine;
  1718. bottomRightScrewSilver->visible = !isSignatureLine;
  1719. // Gold Torx screws are visible only for "Signature" modules.
  1720. topLeftScrewGold->visible = isSignatureLine;
  1721. topRightScrewGold->visible = isSignatureLine;
  1722. bottomLeftScrewGold->visible = isSignatureLine;
  1723. bottomRightScrewGold->visible = isSignatureLine;
  1724. // Silver or gold button is visible at once (opposite is, obvisouly, hidden).
  1725. buttonSilver->visible = !isSignatureLine;
  1726. buttonGold->visible = isSignatureLine;
  1727. // Resume original step() method.
  1728. ModuleWidget::step();
  1729. }
  1730. //// CONTEXT-MENU (RIGHT-CLICK ON MODULE).
  1731. // Classic (default beige) module.
  1732. struct klokspidClassicMenu : MenuItem {
  1733. KlokSpidModule *klokspidmodule;
  1734. void onAction(EventAction &e) override {
  1735. klokspidmodule->Theme = 0;
  1736. }
  1737. void step() override {
  1738. rightText = (klokspidmodule->Theme == 0) ? "✔" : "";
  1739. MenuItem::step();
  1740. }
  1741. };
  1742. // Stage Repro module.
  1743. struct klokspidStageReproMenu : MenuItem {
  1744. KlokSpidModule *klokspidmodule;
  1745. void onAction(EventAction &e) override {
  1746. klokspidmodule->Theme = 1;
  1747. }
  1748. void step() override {
  1749. rightText = (klokspidmodule->Theme == 1) ? "✔" : "";
  1750. MenuItem::step();
  1751. }
  1752. };
  1753. // Absolute Night module.
  1754. struct klokspidAbsoluteNightMenu : MenuItem {
  1755. KlokSpidModule *klokspidmodule;
  1756. void onAction(EventAction &e) override {
  1757. klokspidmodule->Theme = 2;
  1758. }
  1759. void step() override {
  1760. rightText = (klokspidmodule->Theme == 2) ? "✔" : "";
  1761. MenuItem::step();
  1762. }
  1763. };
  1764. // Dark "Signature" module.
  1765. struct klokspidDarkSignatureMenu : MenuItem {
  1766. KlokSpidModule *klokspidmodule;
  1767. void onAction(EventAction &e) override {
  1768. klokspidmodule->Theme = 3;
  1769. }
  1770. void step() override {
  1771. rightText = (klokspidmodule->Theme == 3) ? "✔" : "";
  1772. MenuItem::step();
  1773. }
  1774. };
  1775. // Deepblue "Signature" module.
  1776. struct klokspidDeepblueSignatureMenu : MenuItem {
  1777. KlokSpidModule *klokspidmodule;
  1778. void onAction(EventAction &e) override {
  1779. klokspidmodule->Theme = 4;
  1780. }
  1781. void step() override {
  1782. rightText = (klokspidmodule->Theme == 4) ? "✔" : "";
  1783. MenuItem::step();
  1784. }
  1785. };
  1786. // Carbon "Signature" module.
  1787. struct klokspidCarbonSignatureMenu : MenuItem {
  1788. KlokSpidModule *klokspidmodule;
  1789. void onAction(EventAction &e) override {
  1790. klokspidmodule->Theme = 5;
  1791. }
  1792. void step() override {
  1793. rightText = (klokspidmodule->Theme == 5) ? "✔" : "";
  1794. MenuItem::step();
  1795. }
  1796. };
  1797. // CONTEXT-MENU CONSTRUCTION.
  1798. Menu* KlokSpidWidget::createContextMenu() {
  1799. Menu* menu = ModuleWidget::createContextMenu();
  1800. KlokSpidModule *klokspidmodule = dynamic_cast<KlokSpidModule*>(module);
  1801. assert(klokspidmodule);
  1802. menu->addChild(construct<MenuEntry>());
  1803. menu->addChild(construct<MenuLabel>(&MenuLabel::text, "Model:"));
  1804. menu->addChild(construct<klokspidClassicMenu>(&klokspidClassicMenu::text, "Classic (default)", &klokspidClassicMenu::klokspidmodule, klokspidmodule));
  1805. menu->addChild(construct<klokspidStageReproMenu>(&klokspidStageReproMenu::text, "Stage Repro", &klokspidStageReproMenu::klokspidmodule, klokspidmodule));
  1806. menu->addChild(construct<klokspidAbsoluteNightMenu>(&klokspidAbsoluteNightMenu::text, "Absolute Night", &klokspidAbsoluteNightMenu::klokspidmodule, klokspidmodule));
  1807. menu->addChild(construct<klokspidDarkSignatureMenu>(&klokspidDarkSignatureMenu::text, "Dark \"Signature\"", &klokspidDarkSignatureMenu::klokspidmodule, klokspidmodule));
  1808. menu->addChild(construct<klokspidDeepblueSignatureMenu>(&klokspidDeepblueSignatureMenu::text, "Deepblue \"Signature\"", &klokspidDeepblueSignatureMenu::klokspidmodule, klokspidmodule));
  1809. menu->addChild(construct<klokspidCarbonSignatureMenu>(&klokspidCarbonSignatureMenu::text, "Carbon \"Signature\"", &klokspidCarbonSignatureMenu::klokspidmodule, klokspidmodule));
  1810. return menu;
  1811. }
  1812. } // namespace rack_plugin_Ohmer
  1813. using namespace rack_plugin_Ohmer;
  1814. RACK_PLUGIN_MODEL_INIT(Ohmer, KlokSpid) {
  1815. Model *modelKlokSpid = Model::create<KlokSpidModule, KlokSpidWidget>("Ohmer Modules", "KlokSpid", "KlokSpid", CLOCK_TAG, CLOCK_MODULATOR_TAG); // CLOCK_MODULATOR_TAG introduced in 0.6 API.
  1816. return modelKlokSpid;
  1817. }