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.

1090 lines
38KB

  1. //***********************************************************************************************
  2. //Chain-able clock module for VCV Rack by Marc Boulé
  3. //
  4. //Based on code from the Fundamental and Audible Instruments plugins by Andrew Belt and graphics
  5. // from the Component Library by Wes Milholen.
  6. //See ./LICENSE.txt for all licenses
  7. //See ./res/fonts/ for font licenses
  8. //
  9. //Module concept and design by Marc Boulé, Nigel Sixsmith and Xavier Belmont
  10. //
  11. //***********************************************************************************************
  12. #include "ImpromptuModular.hpp"
  13. namespace rack_plugin_ImpromptuModular {
  14. class Clock {
  15. // The -1.0 step is used as a reset state every double-period so that
  16. // lengths can be re-computed; it will stay at -1.0 when a clock is inactive.
  17. // a clock frame is defined as "length * iterations + syncWait", and
  18. // for master, syncWait does not apply and iterations = 1
  19. double step;// -1.0 when stopped, [0 to 2*period[ for clock steps (*2 is because of swing, so we do groups of 2 periods)
  20. double length;// double period
  21. double sampleTime;
  22. int iterations;// run this many double periods before going into sync if sub-clock
  23. Clock* syncSrc = nullptr; // only subclocks will have this set to master clock
  24. static constexpr double guard = 0.0005;// in seconds, region for sync to occur right before end of length of last iteration; sub clocks must be low during this period
  25. public:
  26. int isHigh(float swing, float pulseWidth) {
  27. // last 0.5ms (guard time) must be low so that sync mechanism will work properly (i.e. no missed pulses)
  28. // this will automatically be the case, since code below disallows any pulses or inter-pulse times less than 1ms
  29. int high = 0;
  30. if (step >= 0.0) {
  31. float swParam = swing;// swing is [-1 : 1]
  32. // all following values are in seconds
  33. float onems = 0.001f;
  34. float period = (float)length / 2.0f;
  35. float swing = (period - 2.0f * onems) * swParam;
  36. float p2min = onems;
  37. float p2max = period - onems - fabs(swing);
  38. if (p2max < p2min) {
  39. p2max = p2min;
  40. }
  41. //double p1 = 0.0;// implicit, no need
  42. double p2 = (double)((p2max - p2min) * pulseWidth + p2min);// pulseWidth is [0 : 1]
  43. double p3 = (double)(period + swing);
  44. double p4 = ((double)(period + swing)) + p2;
  45. if (step < p2)
  46. high = 1;
  47. else if ((step >= p3) && (step < p4))
  48. high = 2;
  49. }
  50. return high;
  51. }
  52. void setSync(Clock* clkGiven) {
  53. syncSrc = clkGiven;
  54. }
  55. inline void reset() {
  56. step = -1.0;
  57. }
  58. inline bool isReset() {
  59. return step == -1.0;
  60. }
  61. inline double getStep() {
  62. return step;
  63. }
  64. inline void setup(double lengthGiven, int iterationsGiven, double sampleTimeGiven) {
  65. length = lengthGiven;
  66. iterations = iterationsGiven;
  67. sampleTime = sampleTimeGiven;
  68. }
  69. inline void start() {
  70. step = 0.0;
  71. }
  72. void stepClock() {// here the clock was output on step "step", this function is called at end of module::step()
  73. if (step >= 0.0) {// if active clock
  74. step += sampleTime;
  75. if ( (syncSrc != nullptr) && (iterations == 1) && (step > (length - guard)) ) {// if in sync region
  76. if (syncSrc->isReset()) {
  77. reset();
  78. }// else nothing needs to be done, just wait and step stays the same
  79. }
  80. else {
  81. if (step >= length) {// reached end iteration
  82. iterations--;
  83. step -= length;
  84. if (iterations <= 0)
  85. reset();// frame done
  86. }
  87. }
  88. }
  89. }
  90. void applyNewLength(double lengthStretchFactor) {
  91. if (step != -1.0)
  92. step *= lengthStretchFactor;
  93. length *= lengthStretchFactor;
  94. }
  95. };
  96. //*****************************************************************************
  97. class ClockDelay {
  98. long stepCounter;
  99. int lastWriteValue;
  100. bool readState;
  101. long stepRise1;
  102. long stepFall1;
  103. long stepRise2;
  104. long stepFall2;
  105. public:
  106. void reset() {
  107. stepCounter = 0l;
  108. lastWriteValue = 0;
  109. readState = false;
  110. stepRise1 = 0l;
  111. stepFall1 = 0l;
  112. stepRise2 = 0l;
  113. stepFall2 = 0l;
  114. }
  115. void write(int value) {
  116. if (value == 1) {// first pulse is high
  117. if (lastWriteValue == 0) // if got rise 1
  118. stepRise1 = stepCounter;
  119. }
  120. else if (value == 2) {// second pulse is high
  121. if (lastWriteValue == 0) // if got rise 2
  122. stepRise2 = stepCounter;
  123. }
  124. else {// value = 0 (pulse is low)
  125. if (lastWriteValue == 1) // if got fall 1
  126. stepFall1 = stepCounter;
  127. else if (lastWriteValue == 2) // if got fall 2
  128. stepFall2 = stepCounter;
  129. }
  130. lastWriteValue = value;
  131. }
  132. bool read(long delaySamples) {
  133. long delayedStepCounter = stepCounter - delaySamples;
  134. if (delayedStepCounter == stepRise1 || delayedStepCounter == stepRise2)
  135. readState = true;
  136. else if (delayedStepCounter == stepFall1 || delayedStepCounter == stepFall2)
  137. readState = false;
  138. stepCounter++;
  139. if (stepCounter > 1e8) {// keep within long's bounds (could go higher or could allow negative)
  140. stepCounter -= 1e8;// 192000 samp/s * 2s * 64 * (3/4) = 18.4 Msamp
  141. stepRise1 -= 1e8;
  142. stepFall1 -= 1e8;
  143. stepRise2 -= 1e8;
  144. stepFall2 -= 1e8;
  145. }
  146. return readState;
  147. }
  148. };
  149. //*****************************************************************************
  150. struct Clocked : Module {
  151. enum ParamIds {
  152. ENUMS(RATIO_PARAMS, 4),// master is index 0
  153. ENUMS(SWING_PARAMS, 4),// master is index 0
  154. ENUMS(PW_PARAMS, 4),// master is index 0
  155. RESET_PARAM,
  156. RUN_PARAM,
  157. ENUMS(DELAY_PARAMS, 4),// index 0 is unused
  158. // -- 0.6.9 ^^
  159. BPMMODE_PARAM,
  160. NUM_PARAMS
  161. };
  162. enum InputIds {
  163. ENUMS(PW_INPUTS, 4),// master is index 0
  164. RESET_INPUT,
  165. RUN_INPUT,
  166. BPM_INPUT,
  167. ENUMS(SWING_INPUTS, 4),// master is index 0
  168. NUM_INPUTS
  169. };
  170. enum OutputIds {
  171. ENUMS(CLK_OUTPUTS, 4),// master is index 0
  172. RESET_OUTPUT,
  173. RUN_OUTPUT,
  174. BPM_OUTPUT,
  175. NUM_OUTPUTS
  176. };
  177. enum LightIds {
  178. RESET_LIGHT,
  179. RUN_LIGHT,
  180. ENUMS(CLK_LIGHTS, 4),// master is index 0 (not used)
  181. ENUMS(BPMSYNC_LIGHT, 2),// room for GreenRed
  182. NUM_LIGHTS
  183. };
  184. // Constants
  185. const float delayValues[8] = {0.0f, 0.0625f, 0.125f, 0.25f, 1.0f/3.0f, 0.5f , 2.0f/3.0f, 0.75f};
  186. const float ratioValues[34] = {1, 1.5, 2, 2.5, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 23, 24, 29, 31, 32, 37, 41, 43, 47, 48, 53, 59, 61, 64};
  187. static const int bpmMax = 300;
  188. static const int bpmMin = 30;
  189. static constexpr float masterLengthMax = 120.0f / bpmMin;// a length is a double period
  190. static constexpr float masterLengthMin = 120.0f / bpmMax;// a length is a double period
  191. static constexpr float delayInfoTime = 3.0f;// seconds
  192. static constexpr float swingInfoTime = 2.0f;// seconds
  193. // Need to save, with reset
  194. bool running;
  195. // Need to save, no reset
  196. int panelTheme;
  197. int expansion;
  198. bool displayDelayNoteMode;
  199. bool bpmDetectionMode;
  200. bool emitResetOnStopRun;
  201. int ppqn;
  202. // No need to save, with reset
  203. // none
  204. // No need to save, no reset
  205. bool scheduledReset;
  206. float swingVal[4];
  207. long swingInfo[4];// downward step counter when swing to be displayed, 0 when normal display
  208. int delayKnobIndexes[4];
  209. long delayInfo[4];// downward step counter when delay to be displayed, 0 when normal display
  210. int ratiosDoubled[4];
  211. int newRatiosDoubled[4];
  212. Clock clk[4];
  213. ClockDelay delay[4];
  214. float masterLength;// a length is a double period
  215. float resetLight;
  216. SchmittTrigger resetTrigger;
  217. SchmittTrigger runTrigger;
  218. PulseGenerator resetPulse;
  219. PulseGenerator runPulse;
  220. SchmittTrigger bpmDetectTrigger;
  221. int extPulseNumber;// 0 to ppqn - 1
  222. double extIntervalTime;
  223. double timeoutTime;
  224. long cantRunWarning;// 0 when no warning, positive downward step counter timer when warning
  225. long editingBpmMode;// 0 when no edit bpmMode, downward step counter timer when edit, negative upward when show can't edit ("--")
  226. int lightRefreshCounter;
  227. SchmittTrigger bpmModeTrigger;
  228. // called from the main thread (step() can not be called until all modules created)
  229. Clocked() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
  230. // Need to save, no reset
  231. panelTheme = 0;
  232. expansion = 0;
  233. displayDelayNoteMode = true;
  234. bpmDetectionMode = false;
  235. emitResetOnStopRun = false;
  236. ppqn = 4;
  237. // No need to save, no reset
  238. scheduledReset = false;
  239. for (int i = 0; i < 4; i++) {
  240. clk[i].setSync(i == 0 ? nullptr : &clk[0]);
  241. swingVal[i] = 0.0f;
  242. swingInfo[i] = 0l;
  243. delayKnobIndexes[i] = 0;
  244. delayInfo[i] = 0l;
  245. ratiosDoubled[i] = 0;
  246. newRatiosDoubled[i] = 0;
  247. clk[i].reset();
  248. delay[i].reset();
  249. }
  250. masterLength = 1.0f;// 120 BPM
  251. resetLight = 0.0f;
  252. resetTrigger.reset();
  253. runTrigger.reset();
  254. resetPulse.reset();
  255. runPulse.reset();
  256. bpmDetectTrigger.reset();
  257. extPulseNumber = -1;
  258. extIntervalTime = 0.0;
  259. timeoutTime = 2.0 / ppqn + 0.1;
  260. cantRunWarning = 0ul;
  261. bpmModeTrigger.reset();
  262. lightRefreshCounter = 0;
  263. onReset();
  264. }
  265. // widgets are not yet created when module is created
  266. // even if widgets not created yet, can use params[] and should handle 0.0f value since step may call
  267. // this before widget creation anyways
  268. // called from the main thread if by constructor, called by engine thread if right-click initialization
  269. // when called by constructor, module is created before the first step() is called
  270. void onReset() override {
  271. // Need to save, with reset
  272. running = false;
  273. // No need to save, with reset
  274. // none
  275. scheduledReset = true;
  276. }
  277. // widgets randomized before onRandomize() is called
  278. // called by engine thread if right-click randomize
  279. void onRandomize() override {
  280. // Need to save, with reset
  281. running = false;
  282. // No need to save, with reset
  283. // none
  284. scheduledReset = true;
  285. }
  286. // called by main thread
  287. json_t *toJson() override {
  288. json_t *rootJ = json_object();
  289. // Need to save (reset or not)
  290. // running
  291. json_object_set_new(rootJ, "running", json_boolean(running));
  292. // panelTheme
  293. json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme));
  294. // expansion
  295. json_object_set_new(rootJ, "expansion", json_integer(expansion));
  296. // displayDelayNoteMode
  297. json_object_set_new(rootJ, "displayDelayNoteMode", json_boolean(displayDelayNoteMode));
  298. // bpmDetectionMode
  299. json_object_set_new(rootJ, "bpmDetectionMode", json_boolean(bpmDetectionMode));
  300. // emitResetOnStopRun
  301. json_object_set_new(rootJ, "emitResetOnStopRun", json_boolean(emitResetOnStopRun));
  302. // ppqn
  303. json_object_set_new(rootJ, "ppqn", json_integer(ppqn));
  304. return rootJ;
  305. }
  306. // widgets have their fromJson() called before this fromJson() is called
  307. // called by main thread
  308. void fromJson(json_t *rootJ) override {
  309. // Need to save (reset or not)
  310. // running
  311. json_t *runningJ = json_object_get(rootJ, "running");
  312. if (runningJ)
  313. running = json_is_true(runningJ);
  314. // panelTheme
  315. json_t *panelThemeJ = json_object_get(rootJ, "panelTheme");
  316. if (panelThemeJ)
  317. panelTheme = json_integer_value(panelThemeJ);
  318. // expansion
  319. json_t *expansionJ = json_object_get(rootJ, "expansion");
  320. if (expansionJ)
  321. expansion = json_integer_value(expansionJ);
  322. // displayDelayNoteMode
  323. json_t *displayDelayNoteModeJ = json_object_get(rootJ, "displayDelayNoteMode");
  324. if (displayDelayNoteModeJ)
  325. displayDelayNoteMode = json_is_true(displayDelayNoteModeJ);
  326. // bpmDetectionMode
  327. json_t *bpmDetectionModeJ = json_object_get(rootJ, "bpmDetectionMode");
  328. if (bpmDetectionModeJ)
  329. bpmDetectionMode = json_is_true(bpmDetectionModeJ);
  330. // emitResetOnStopRun
  331. json_t *emitResetOnStopRunJ = json_object_get(rootJ, "emitResetOnStopRun");
  332. if (emitResetOnStopRunJ)
  333. emitResetOnStopRun = json_is_true(emitResetOnStopRunJ);
  334. // ppqn
  335. json_t *ppqnJ = json_object_get(rootJ, "ppqn");
  336. if (ppqnJ)
  337. ppqn = clamp(json_integer_value(ppqnJ), 4, 24);
  338. // No need to save, with reset
  339. // none
  340. scheduledReset = true;
  341. }
  342. int getRatioDoubled(int ratioKnobIndex) {
  343. // ratioKnobIndex is 0 for master BPM's ratio (1 is implicitly returned), and 1 to 3 for other ratio knobs
  344. // returns a positive ratio for mult, negative ratio for div (0 never returned)
  345. int ret = 1;
  346. if (ratioKnobIndex > 0) {
  347. bool isDivision = false;
  348. int i = (int) round( params[RATIO_PARAMS + ratioKnobIndex].value );// [ -(numRatios-1) ; (numRatios-1) ]
  349. if (i < 0) {
  350. i *= -1;
  351. isDivision = true;
  352. }
  353. if (i >= 34) {
  354. i = 34 - 1;
  355. }
  356. ret = (int) (ratioValues[i] * 2.0f + 0.5f);
  357. if (isDivision)
  358. ret = -1l * ret;
  359. }
  360. return ret;
  361. }
  362. void resetClocked() {
  363. for (int i = 0; i < 4; i++) {
  364. clk[i].reset();
  365. delay[i].reset();
  366. }
  367. extPulseNumber = -1;
  368. extIntervalTime = 0.0;
  369. timeoutTime = 2.0 / ppqn + 0.1;
  370. }
  371. // called by engine thread
  372. void onSampleRateChange() override {
  373. resetClocked();
  374. }
  375. // Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate()
  376. // called by engine thread
  377. void step() override {
  378. double sampleRate = (double)engineGetSampleRate();
  379. double sampleTime = 1.0 / sampleRate;// do this here since engineGetSampleRate() returns float
  380. // Scheduled reset (just the parts that do not have a place below in rest of function)
  381. if (scheduledReset) {
  382. resetClocked();
  383. resetLight = 0.0f;
  384. resetTrigger.reset();
  385. runTrigger.reset();
  386. resetPulse.reset();
  387. runPulse.reset();
  388. bpmDetectTrigger.reset();
  389. cantRunWarning = 0l;
  390. editingBpmMode = 0l;
  391. }
  392. // Run button
  393. if (runTrigger.process(params[RUN_PARAM].value + inputs[RUN_INPUT].value)) {
  394. if (!(bpmDetectionMode && inputs[BPM_INPUT].active) || running) {// toggle when not BPM detect, turn off only when BPM detect (allows turn off faster than timeout if don't want any trailing beats after stoppage). If allow manually start in bpmDetectionMode the clock will not know which pulse is the 1st of a ppqn set, so only allow stop
  395. running = !running;
  396. runPulse.trigger(0.001f);
  397. resetClocked();// reset on any change of run state (will not re-launch if not running, thus clock railed low)
  398. if (!running && emitResetOnStopRun) {
  399. //resetLight = 1.0f;
  400. resetPulse.trigger(0.001f);
  401. }
  402. }
  403. else
  404. cantRunWarning = (long) (0.7 * sampleRate / displayRefreshStepSkips);
  405. }
  406. // Reset (has to be near top because it sets steps to 0, and 0 not a real step (clock section will move to 1 before reaching outputs)
  407. if (resetTrigger.process(inputs[RESET_INPUT].value + params[RESET_PARAM].value)) {
  408. resetLight = 1.0f;
  409. resetPulse.trigger(0.001f);
  410. resetClocked();
  411. }
  412. // BPM mode
  413. if (bpmModeTrigger.process(params[BPMMODE_PARAM].value)) {
  414. if (inputs[BPM_INPUT].active) {
  415. if (editingBpmMode != 0ul) {// force active before allow change
  416. if (bpmDetectionMode == false) {
  417. bpmDetectionMode = true;
  418. ppqn = 4;
  419. }
  420. else {
  421. if (ppqn == 4)
  422. ppqn = 8;
  423. else if (ppqn == 8)
  424. ppqn = 12;
  425. else if (ppqn == 12)
  426. ppqn = 24;
  427. else
  428. bpmDetectionMode = false;
  429. }
  430. }
  431. editingBpmMode = (long) (3.0 * sampleRate / displayRefreshStepSkips);
  432. }
  433. else
  434. editingBpmMode = (long) (-1.5 * sampleRate / displayRefreshStepSkips);
  435. }
  436. // BPM input and knob
  437. float newMasterLength = masterLength;
  438. if (inputs[BPM_INPUT].active) {
  439. float bpmInValue = inputs[BPM_INPUT].value;
  440. bool trigBpmInValue = bpmDetectTrigger.process(bpmInValue);
  441. // BPM Detection method
  442. if (bpmDetectionMode) {
  443. if (scheduledReset)
  444. newMasterLength = 1.0f;// 120 BPM
  445. // rising edge detect
  446. if (trigBpmInValue) {
  447. if (!running) {
  448. // this must be the only way to start runnning when in bpmDetectionMode or else
  449. // when manually starting, the clock will not know which pulse is the 1st of a ppqn set
  450. //runPulse.trigger(0.001f); don't need this since slaves will detect the same thing
  451. running = true;
  452. runPulse.trigger(0.001f);
  453. resetClocked();
  454. }
  455. if (running) {
  456. extPulseNumber++;
  457. if (extPulseNumber >= ppqn * 2)// *2 because working with double_periods
  458. extPulseNumber = 0;
  459. if (extPulseNumber == 0)// if first pulse, start interval timer
  460. extIntervalTime = 0.0;
  461. else {
  462. // all other ppqn pulses except the first one. now we have an interval upon which to plan a strecth
  463. double timeLeft = extIntervalTime * (double)(ppqn * 2 - extPulseNumber) / ((double)extPulseNumber);
  464. newMasterLength = clk[0].getStep() + timeLeft;
  465. timeoutTime = extIntervalTime * ((double)(1 + extPulseNumber) / ((double)extPulseNumber)) + 0.1;
  466. }
  467. }
  468. }
  469. if (running) {
  470. extIntervalTime += sampleTime;
  471. if (extIntervalTime > timeoutTime) {
  472. running = false;
  473. runPulse.trigger(0.001f);
  474. resetClocked();
  475. if (emitResetOnStopRun) {
  476. //resetLight = 1.0f;
  477. resetPulse.trigger(0.001f);
  478. }
  479. }
  480. }
  481. }
  482. // BPM CV method
  483. else {
  484. newMasterLength = 1.0f / powf(2.0f, bpmInValue);// bpm = 120*2^V, 2T = 120/bpm = 120/(120*2^V) = 1/2^V
  485. // no need to round since this clocked's master's BPM knob is a snap knob thus already rounded, and with passthru approach, no cumul error
  486. }
  487. }
  488. else {
  489. newMasterLength = 120.0f / params[RATIO_PARAMS + 0].value;// already integer BPM since using snap knob
  490. }
  491. newMasterLength = clamp(newMasterLength, masterLengthMin, masterLengthMax);
  492. if (scheduledReset)
  493. masterLength = newMasterLength;
  494. if (newMasterLength != masterLength) {
  495. double lengthStretchFactor = ((double)newMasterLength) / ((double)masterLength);
  496. for (int i = 0; i < 4; i++) {
  497. clk[i].applyNewLength(lengthStretchFactor);
  498. }
  499. masterLength = newMasterLength;
  500. }
  501. // Ratio knobs changed (setup a sync)
  502. bool syncRatios[4] = {false, false, false, false};// 0 index unused
  503. for (int i = 1; i < 4; i++) {
  504. newRatiosDoubled[i] = getRatioDoubled(i);
  505. if (scheduledReset)
  506. ratiosDoubled[i] = newRatiosDoubled[i];
  507. if (newRatiosDoubled[i] != ratiosDoubled[i]) {
  508. syncRatios[i] = true;// 0 index not used, but loop must start at i = 0
  509. }
  510. }
  511. // Swing and delay changed (for swing and delay info), ignore CV inputs for the info process
  512. for (int i = 0; i < 4; i++) {
  513. float newSwingVal = params[SWING_PARAMS + i].value;
  514. if (scheduledReset) {
  515. swingInfo[i] = 0l;
  516. swingVal[i] = newSwingVal;
  517. }
  518. if (newSwingVal != swingVal[i]) {
  519. swingVal[i] = newSwingVal;
  520. swingInfo[i] = (long) (swingInfoTime * (float)sampleRate / displayRefreshStepSkips);// trigger swing info on channel i
  521. delayInfo[i] = 0l;// cancel delayed being displayed (if so)
  522. }
  523. if (i > 0) {
  524. int newDelayKnobIndex = clamp((int) round( params[DELAY_PARAMS + i].value ), 0, 8 - 1);
  525. if (scheduledReset) {
  526. delayInfo[i] = 0l;
  527. delayKnobIndexes[i] = newDelayKnobIndex;
  528. }
  529. if (newDelayKnobIndex != delayKnobIndexes[i]) {
  530. delayKnobIndexes[i] = newDelayKnobIndex;
  531. delayInfo[i] = (long) (delayInfoTime * (float)sampleRate / displayRefreshStepSkips);// trigger delay info on channel i
  532. swingInfo[i] = 0l;// cancel swing being displayed (if so)
  533. }
  534. }
  535. }
  536. //********** Clocks and Delays **********
  537. // Clocks
  538. if (running) {
  539. // See if clocks finished their prescribed number of iteratios of double periods (and syncWait for sub) or
  540. // were forced reset and if so, recalc and restart them
  541. // Master clock
  542. if (clk[0].isReset()) {
  543. // See if ratio knobs changed (or unitinialized)
  544. for (int i = 1; i < 4; i++) {
  545. if (syncRatios[i]) {// always false for master
  546. clk[i].reset();// force reset (thus refresh) of that sub-clock
  547. ratiosDoubled[i] = newRatiosDoubled[i];
  548. syncRatios[i] = false;
  549. }
  550. }
  551. clk[0].setup(masterLength, 1, sampleTime);// must call setup before start. length = double_period
  552. clk[0].start();
  553. }
  554. // Sub clocks
  555. for (int i = 1; i < 4; i++) {
  556. if (clk[i].isReset()) {
  557. double length;
  558. int iterations;
  559. int ratioDoubled = ratiosDoubled[i];
  560. if (ratioDoubled < 0) { // if div
  561. ratioDoubled *= -1;
  562. length = masterLength * ((double)ratioDoubled) / 2.0;
  563. iterations = 1l + (ratioDoubled % 2);
  564. clk[i].setup(length, iterations, sampleTime);
  565. }
  566. else {// mult
  567. length = (2.0f * masterLength) / ((double)ratioDoubled);
  568. iterations = ratioDoubled / (2l - (ratioDoubled % 2l));
  569. clk[i].setup(length, iterations, sampleTime);
  570. }
  571. clk[i].start();
  572. }
  573. }
  574. // Write clk outputs into delay buffer
  575. for (int i = 0; i < 4; i++) {
  576. float pulseWidth = params[PW_PARAMS + i].value;
  577. if (i < 3 && inputs[PW_INPUTS + i].active) {
  578. pulseWidth += (inputs[PW_INPUTS + i].value / 10.0f) - 0.5f;
  579. pulseWidth = clamp(pulseWidth, 0.0f, 1.0f);
  580. }
  581. float swingAmount = swingVal[i];
  582. if (i < 3 && inputs[SWING_INPUTS + i].active) {
  583. swingAmount += (inputs[SWING_INPUTS + i].value / 5.0f) - 1.0f;
  584. swingAmount = clamp(swingAmount, -1.0f, 1.0f);
  585. }
  586. delay[i].write(clk[i].isHigh(swingAmount, pulseWidth));
  587. }
  588. //********** Outputs and lights **********
  589. // Clock outputs
  590. for (int i = 0; i < 4; i++) {
  591. long delaySamples = 0l;
  592. if (i > 0) {
  593. float delayFraction = delayValues[delayKnobIndexes[i]];
  594. float ratioValue = ((float)ratiosDoubled[i]) / 2.0f;
  595. if (ratioValue < 0)
  596. ratioValue = 1.0f / (-1.0f * ratioValue);
  597. delaySamples = (long)(masterLength * delayFraction * sampleRate / (ratioValue * 2.0));
  598. }
  599. outputs[CLK_OUTPUTS + i].value = delay[i].read(delaySamples) ? 10.0f : 0.0f;
  600. }
  601. }
  602. else {
  603. for (int i = 0; i < 4; i++)
  604. outputs[CLK_OUTPUTS + i].value = 0.0f;
  605. }
  606. for (int i = 0; i < 4; i++)
  607. clk[i].stepClock();
  608. // Chaining outputs
  609. outputs[RESET_OUTPUT].value = (resetPulse.process((float)sampleTime) ? 10.0f : 0.0f);
  610. outputs[RUN_OUTPUT].value = (runPulse.process((float)sampleTime) ? 10.0f : 0.0f);
  611. outputs[BPM_OUTPUT].value = inputs[BPM_INPUT].active ? inputs[BPM_INPUT].value : log2f(1.0f / masterLength);
  612. lightRefreshCounter++;
  613. if (lightRefreshCounter > displayRefreshStepSkips) {
  614. lightRefreshCounter = 0;
  615. // Reset light
  616. lights[RESET_LIGHT].value = resetLight;
  617. resetLight -= (resetLight / lightLambda) * (float)sampleTime * displayRefreshStepSkips;
  618. // Run light
  619. lights[RUN_LIGHT].value = running ? 1.0f : 0.0f;
  620. // BPM light
  621. bool warningFlashState = true;
  622. if (cantRunWarning > 0l)
  623. warningFlashState = calcWarningFlash(cantRunWarning, (long) (0.7 * sampleRate / displayRefreshStepSkips));
  624. lights[BPMSYNC_LIGHT + 0].value = (bpmDetectionMode && warningFlashState && inputs[BPM_INPUT].active) ? 1.0f : 0.0f;
  625. if (editingBpmMode < 0l)
  626. lights[BPMSYNC_LIGHT + 1].value = 1.0f;
  627. else
  628. lights[BPMSYNC_LIGHT + 1].value = (bpmDetectionMode && warningFlashState && inputs[BPM_INPUT].active) ? (float)((ppqn - 4)*(ppqn - 4))/400.0f : 0.0f;
  629. // ratios synched lights
  630. for (int i = 1; i < 4; i++) {
  631. lights[CLK_LIGHTS + i].value = (syncRatios[i] && running) ? 1.0f: 0.0f;
  632. }
  633. // Incr/decr all counters related to step()
  634. for (int i = 0; i < 4; i++) {
  635. if (swingInfo[i] > 0)
  636. swingInfo[i]--;
  637. if (delayInfo[i] > 0)
  638. delayInfo[i]--;
  639. }
  640. if (cantRunWarning > 0l)
  641. cantRunWarning--;
  642. if (editingBpmMode != 0l) {
  643. if (editingBpmMode > 0l)
  644. editingBpmMode--;
  645. else
  646. editingBpmMode++;
  647. }
  648. }// lightRefreshCounter
  649. scheduledReset = false;
  650. }// step()
  651. };
  652. struct ClockedWidget : ModuleWidget {
  653. Clocked *module;
  654. DynamicSVGPanel *panel;
  655. int oldExpansion;
  656. int expWidth = 60;
  657. IMPort* expPorts[6];
  658. struct RatioDisplayWidget : TransparentWidget {
  659. Clocked *module;
  660. int knobIndex;
  661. std::shared_ptr<Font> font;
  662. char displayStr[4];
  663. const std::string delayLabelsClock[8] = {" 0", "/16", "1/8", "1/4", "1/3", "1/2", "2/3", "3/4"};
  664. const std::string delayLabelsNote[8] = {" 0", "/64", "/32", "/16", "/8t", "1/8", "/4t", "/8d"};
  665. RatioDisplayWidget() {
  666. font = Font::load(assetPlugin(plugin, "res/fonts/Segment14.ttf"));
  667. }
  668. void draw(NVGcontext *vg) override {
  669. NVGcolor textColor = prepareDisplay(vg, &box);
  670. nvgFontFaceId(vg, font->handle);
  671. //nvgTextLetterSpacing(vg, 2.5);
  672. Vec textPos = Vec(6, 24);
  673. nvgFillColor(vg, nvgTransRGBA(textColor, 16));
  674. nvgText(vg, textPos.x, textPos.y, "~~~", NULL);
  675. nvgFillColor(vg, textColor);
  676. if (module->swingInfo[knobIndex] > 0)
  677. {
  678. float swValue = module->swingVal[knobIndex];
  679. int swInt = (int)round(swValue * 99.0f);
  680. snprintf(displayStr, 4, " %2u", (unsigned) abs(swInt));
  681. if (swInt < 0)
  682. displayStr[0] = '-';
  683. if (swInt > 0)
  684. displayStr[0] = '+';
  685. }
  686. else if (module->delayInfo[knobIndex] > 0)
  687. {
  688. int delayKnobIndex = module->delayKnobIndexes[knobIndex];
  689. if (module->displayDelayNoteMode)
  690. snprintf(displayStr, 4, "%s", (delayLabelsNote[delayKnobIndex]).c_str());
  691. else
  692. snprintf(displayStr, 4, "%s", (delayLabelsClock[delayKnobIndex]).c_str());
  693. }
  694. else {
  695. if (knobIndex > 0) {// ratio to display
  696. bool isDivision = false;
  697. int ratioDoubled = module->newRatiosDoubled[knobIndex];
  698. if (ratioDoubled < 0) {
  699. ratioDoubled = -1 * ratioDoubled;
  700. isDivision = true;
  701. }
  702. if ( (ratioDoubled % 2) == 1 )
  703. snprintf(displayStr, 4, "%c,5", 0x30 + (char)(ratioDoubled / 2));
  704. else {
  705. snprintf(displayStr, 4, "X%2u", (unsigned)(ratioDoubled / 2));
  706. if (isDivision)
  707. displayStr[0] = '/';
  708. }
  709. }
  710. else {// BPM to display
  711. if (module->editingBpmMode != 0l) {
  712. if (module->editingBpmMode > 0l) {
  713. if (!module->bpmDetectionMode)
  714. snprintf(displayStr, 4, " CV");
  715. else
  716. snprintf(displayStr, 4, "P%2u", (unsigned) module->ppqn);
  717. }
  718. else
  719. snprintf(displayStr, 4, " --");
  720. }
  721. else
  722. snprintf(displayStr, 4, "%3u", (unsigned)((120.0f / module->masterLength) + 0.5f));
  723. }
  724. }
  725. displayStr[3] = 0;// more safety
  726. nvgText(vg, textPos.x, textPos.y, displayStr, NULL);
  727. }
  728. };
  729. struct PanelThemeItem : MenuItem {
  730. Clocked *module;
  731. int theme;
  732. void onAction(EventAction &e) override {
  733. module->panelTheme = theme;
  734. }
  735. void step() override {
  736. rightText = (module->panelTheme == theme) ? "✔" : "";
  737. }
  738. };
  739. struct ExpansionItem : MenuItem {
  740. Clocked *module;
  741. void onAction(EventAction &e) override {
  742. module->expansion = module->expansion == 1 ? 0 : 1;
  743. }
  744. };
  745. struct DelayDisplayNoteItem : MenuItem {
  746. Clocked *module;
  747. void onAction(EventAction &e) override {
  748. module->displayDelayNoteMode = !module->displayDelayNoteMode;
  749. }
  750. };
  751. struct EmitResetItem : MenuItem {
  752. Clocked *module;
  753. void onAction(EventAction &e) override {
  754. module->emitResetOnStopRun = !module->emitResetOnStopRun;
  755. }
  756. };
  757. Menu *createContextMenu() override {
  758. Menu *menu = ModuleWidget::createContextMenu();
  759. MenuLabel *spacerLabel = new MenuLabel();
  760. menu->addChild(spacerLabel);
  761. Clocked *module = dynamic_cast<Clocked*>(this->module);
  762. assert(module);
  763. MenuLabel *themeLabel = new MenuLabel();
  764. themeLabel->text = "Panel Theme";
  765. menu->addChild(themeLabel);
  766. PanelThemeItem *lightItem = new PanelThemeItem();
  767. lightItem->text = lightPanelID;// ImpromptuModular.hpp
  768. lightItem->module = module;
  769. lightItem->theme = 0;
  770. menu->addChild(lightItem);
  771. PanelThemeItem *darkItem = new PanelThemeItem();
  772. darkItem->text = darkPanelID;// ImpromptuModular.hpp
  773. darkItem->module = module;
  774. darkItem->theme = 1;
  775. menu->addChild(darkItem);
  776. menu->addChild(new MenuLabel());// empty line
  777. MenuLabel *settingsLabel = new MenuLabel();
  778. settingsLabel->text = "Settings";
  779. menu->addChild(settingsLabel);
  780. DelayDisplayNoteItem *ddnItem = MenuItem::create<DelayDisplayNoteItem>("Display Delay Values in Notes", CHECKMARK(module->displayDelayNoteMode));
  781. ddnItem->module = module;
  782. menu->addChild(ddnItem);
  783. EmitResetItem *erItem = MenuItem::create<EmitResetItem>("Emit Reset when Run is Turned Off", CHECKMARK(module->emitResetOnStopRun));
  784. erItem->module = module;
  785. menu->addChild(erItem);
  786. menu->addChild(new MenuLabel());// empty line
  787. MenuLabel *expansionLabel = new MenuLabel();
  788. expansionLabel->text = "Expansion module";
  789. menu->addChild(expansionLabel);
  790. ExpansionItem *expItem = MenuItem::create<ExpansionItem>(expansionMenuLabel, CHECKMARK(module->expansion != 0));
  791. expItem->module = module;
  792. menu->addChild(expItem);
  793. return menu;
  794. }
  795. void step() override {
  796. if(module->expansion != oldExpansion) {
  797. if (oldExpansion!= -1 && module->expansion == 0) {// if just removed expansion panel, disconnect wires to those jacks
  798. for (int i = 0; i < 6; i++)
  799. rack::global_ui->app.gRackWidget->wireContainer->removeAllWires(expPorts[i]);
  800. }
  801. oldExpansion = module->expansion;
  802. }
  803. box.size.x = panel->box.size.x - (1 - module->expansion) * expWidth;
  804. Widget::step();
  805. }
  806. ClockedWidget(Clocked *module) : ModuleWidget(module) {
  807. this->module = module;
  808. oldExpansion = -1;
  809. // Main panel from Inkscape
  810. panel = new DynamicSVGPanel();
  811. panel->mode = &module->panelTheme;
  812. panel->expWidth = &expWidth;
  813. panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/Clocked.svg")));
  814. panel->addPanel(SVG::load(assetPlugin(plugin, "res/dark/Clocked_dark.svg")));
  815. box.size = panel->box.size;
  816. box.size.x = box.size.x - (1 - module->expansion) * expWidth;
  817. addChild(panel);
  818. // Screws
  819. addChild(createDynamicScrew<IMScrew>(Vec(15, 0), &module->panelTheme));
  820. addChild(createDynamicScrew<IMScrew>(Vec(15, 365), &module->panelTheme));
  821. addChild(createDynamicScrew<IMScrew>(Vec(panel->box.size.x-30, 0), &module->panelTheme));
  822. addChild(createDynamicScrew<IMScrew>(Vec(panel->box.size.x-30, 365), &module->panelTheme));
  823. addChild(createDynamicScrew<IMScrew>(Vec(panel->box.size.x-30-expWidth, 0), &module->panelTheme));
  824. addChild(createDynamicScrew<IMScrew>(Vec(panel->box.size.x-30-expWidth, 365), &module->panelTheme));
  825. static const int rowRuler0 = 50;//reset,run inputs, master knob and bpm display
  826. static const int rowRuler1 = rowRuler0 + 55;// reset,run switches
  827. //
  828. static const int rowRuler2 = rowRuler1 + 55;// clock 1
  829. static const int rowSpacingClks = 50;
  830. static const int rowRuler5 = rowRuler2 + rowSpacingClks * 2 + 55;// reset,run outputs, pw inputs
  831. static const int colRulerL = 18;// reset input and button, ratio knobs
  832. // First two rows and last row
  833. static const int colRulerSpacingT = 47;
  834. static const int colRulerT1 = colRulerL + colRulerSpacingT;// run input and button
  835. static const int colRulerT2 = colRulerT1 + colRulerSpacingT;// in and pwMaster inputs
  836. static const int colRulerT3 = colRulerT2 + colRulerSpacingT + 5;// swingMaster knob
  837. static const int colRulerT4 = colRulerT3 + colRulerSpacingT;// pwMaster knob
  838. static const int colRulerT5 = colRulerT4 + colRulerSpacingT;// clkMaster output
  839. // Three clock rows
  840. static const int colRulerM0 = colRulerL + 5;// ratio knobs
  841. static const int colRulerM1 = colRulerL + 60;// ratio displays
  842. static const int colRulerM2 = colRulerT3;// swingX knobs
  843. static const int colRulerM3 = colRulerT4;// pwX knobs
  844. static const int colRulerM4 = colRulerT5;// clkX outputs
  845. RatioDisplayWidget *displayRatios[4];
  846. // Row 0
  847. // Reset input
  848. addInput(createDynamicPort<IMPort>(Vec(colRulerL, rowRuler0), Port::INPUT, module, Clocked::RESET_INPUT, &module->panelTheme));
  849. // Run input
  850. addInput(createDynamicPort<IMPort>(Vec(colRulerT1, rowRuler0), Port::INPUT, module, Clocked::RUN_INPUT, &module->panelTheme));
  851. // In input
  852. addInput(createDynamicPort<IMPort>(Vec(colRulerT2, rowRuler0), Port::INPUT, module, Clocked::BPM_INPUT, &module->panelTheme));
  853. // Master BPM knob
  854. addParam(createDynamicParam<IMBigSnapKnob>(Vec(colRulerT3 + 1 + offsetIMBigKnob, rowRuler0 + offsetIMBigKnob), module, Clocked::RATIO_PARAMS + 0, (float)(module->bpmMin), (float)(module->bpmMax), 120.0f, &module->panelTheme));// must be a snap knob, code in step() assumes that a rounded value is read from the knob (chaining considerations vs BPM detect)
  855. // BPM display
  856. displayRatios[0] = new RatioDisplayWidget();
  857. displayRatios[0]->box.pos = Vec(colRulerT4 + 11, rowRuler0 + vOffsetDisplay);
  858. displayRatios[0]->box.size = Vec(55, 30);// 3 characters
  859. displayRatios[0]->module = module;
  860. displayRatios[0]->knobIndex = 0;
  861. addChild(displayRatios[0]);
  862. // Row 1
  863. // Reset LED bezel and light
  864. addParam(ParamWidget::create<LEDBezel>(Vec(colRulerL + offsetLEDbezel, rowRuler1 + offsetLEDbezel), module, Clocked::RESET_PARAM, 0.0f, 1.0f, 0.0f));
  865. addChild(ModuleLightWidget::create<MuteLight<GreenLight>>(Vec(colRulerL + offsetLEDbezel + offsetLEDbezelLight, rowRuler1 + offsetLEDbezel + offsetLEDbezelLight), module, Clocked::RESET_LIGHT));
  866. // Run LED bezel and light
  867. addParam(ParamWidget::create<LEDBezel>(Vec(colRulerT1 + offsetLEDbezel, rowRuler1 + offsetLEDbezel), module, Clocked::RUN_PARAM, 0.0f, 1.0f, 0.0f));
  868. addChild(ModuleLightWidget::create<MuteLight<GreenLight>>(Vec(colRulerT1 + offsetLEDbezel + offsetLEDbezelLight, rowRuler1 + offsetLEDbezel + offsetLEDbezelLight), module, Clocked::RUN_LIGHT));
  869. // BPM mode and light
  870. addParam(ParamWidget::create<TL1105>(Vec(colRulerT2 + offsetTL1105, rowRuler1 + offsetTL1105), module, Clocked::BPMMODE_PARAM, 0.0f, 1.0f, 0.0f));
  871. addChild(ModuleLightWidget::create<SmallLight<GreenRedLight>>(Vec(colRulerM1 + 62, rowRuler1 + offsetMediumLight), module, Clocked::BPMSYNC_LIGHT));
  872. // Swing master knob
  873. addParam(createDynamicParam<IMSmallKnob>(Vec(colRulerT3 + offsetIMSmallKnob, rowRuler1 + offsetIMSmallKnob), module, Clocked::SWING_PARAMS + 0, -1.0f, 1.0f, 0.0f, &module->panelTheme));
  874. // PW master knob
  875. addParam(createDynamicParam<IMSmallKnob>(Vec(colRulerT4 + offsetIMSmallKnob, rowRuler1 + offsetIMSmallKnob), module, Clocked::PW_PARAMS + 0, 0.0f, 1.0f, 0.5f, &module->panelTheme));
  876. // Clock master out
  877. addOutput(createDynamicPort<IMPort>(Vec(colRulerT5, rowRuler1), Port::OUTPUT, module, Clocked::CLK_OUTPUTS + 0, &module->panelTheme));
  878. // Row 2-4 (sub clocks)
  879. for (int i = 0; i < 3; i++) {
  880. // Ratio1 knob
  881. addParam(createDynamicParam<IMBigSnapKnob>(Vec(colRulerM0 + offsetIMBigKnob, rowRuler2 + i * rowSpacingClks + offsetIMBigKnob), module, Clocked::RATIO_PARAMS + 1 + i, (34.0f - 1.0f)*-1.0f, 34.0f - 1.0f, 0.0f, &module->panelTheme));
  882. // Ratio display
  883. displayRatios[i + 1] = new RatioDisplayWidget();
  884. displayRatios[i + 1]->box.pos = Vec(colRulerM1, rowRuler2 + i * rowSpacingClks + vOffsetDisplay);
  885. displayRatios[i + 1]->box.size = Vec(55, 30);// 3 characters
  886. displayRatios[i + 1]->module = module;
  887. displayRatios[i + 1]->knobIndex = i + 1;
  888. addChild(displayRatios[i + 1]);
  889. // Sync light
  890. addChild(ModuleLightWidget::create<SmallLight<RedLight>>(Vec(colRulerM1 + 62, rowRuler2 + i * rowSpacingClks + 10), module, Clocked::CLK_LIGHTS + i + 1));
  891. // Swing knobs
  892. addParam(createDynamicParam<IMSmallKnob>(Vec(colRulerM2 + offsetIMSmallKnob, rowRuler2 + i * rowSpacingClks + offsetIMSmallKnob), module, Clocked::SWING_PARAMS + 1 + i, -1.0f, 1.0f, 0.0f, &module->panelTheme));
  893. // PW knobs
  894. addParam(createDynamicParam<IMSmallKnob>(Vec(colRulerM3 + offsetIMSmallKnob, rowRuler2 + i * rowSpacingClks + offsetIMSmallKnob), module, Clocked::PW_PARAMS + 1 + i, 0.0f, 1.0f, 0.5f, &module->panelTheme));
  895. // Delay knobs
  896. addParam(createDynamicParam<IMSmallSnapKnob>(Vec(colRulerM4 + offsetIMSmallKnob, rowRuler2 + i * rowSpacingClks + offsetIMSmallKnob), module, Clocked::DELAY_PARAMS + 1 + i , 0.0f, 8.0f - 1.0f, 0.0f, &module->panelTheme));
  897. }
  898. // Last row
  899. // Reset out
  900. addOutput(createDynamicPort<IMPort>(Vec(colRulerL, rowRuler5), Port::OUTPUT, module, Clocked::RESET_OUTPUT, &module->panelTheme));
  901. // Run out
  902. addOutput(createDynamicPort<IMPort>(Vec(colRulerT1, rowRuler5), Port::OUTPUT, module, Clocked::RUN_OUTPUT, &module->panelTheme));
  903. // Out out
  904. addOutput(createDynamicPort<IMPort>(Vec(colRulerT2, rowRuler5), Port::OUTPUT, module, Clocked::BPM_OUTPUT, &module->panelTheme));
  905. // Sub-clock outputs
  906. addOutput(createDynamicPort<IMPort>(Vec(colRulerT3, rowRuler5), Port::OUTPUT, module, Clocked::CLK_OUTPUTS + 1, &module->panelTheme));
  907. addOutput(createDynamicPort<IMPort>(Vec(colRulerT4, rowRuler5), Port::OUTPUT, module, Clocked::CLK_OUTPUTS + 2, &module->panelTheme));
  908. addOutput(createDynamicPort<IMPort>(Vec(colRulerT5, rowRuler5), Port::OUTPUT, module, Clocked::CLK_OUTPUTS + 3, &module->panelTheme));
  909. // Expansion module
  910. static const int rowRulerExpTop = 60;
  911. static const int rowSpacingExp = 50;
  912. static const int colRulerExp = 497 - 30 -150;// Clocked is (2+10)HP less than PS32
  913. addInput(expPorts[0] = createDynamicPort<IMPort>(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 0), Port::INPUT, module, Clocked::PW_INPUTS + 0, &module->panelTheme));
  914. addInput(expPorts[1] = createDynamicPort<IMPort>(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 1), Port::INPUT, module, Clocked::PW_INPUTS + 1, &module->panelTheme));
  915. addInput(expPorts[2] = createDynamicPort<IMPort>(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 2), Port::INPUT, module, Clocked::PW_INPUTS + 2, &module->panelTheme));
  916. addInput(expPorts[3] = createDynamicPort<IMPort>(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 3), Port::INPUT, module, Clocked::SWING_INPUTS + 0, &module->panelTheme));
  917. addInput(expPorts[4] = createDynamicPort<IMPort>(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 4), Port::INPUT, module, Clocked::SWING_INPUTS + 1, &module->panelTheme));
  918. addInput(expPorts[5] = createDynamicPort<IMPort>(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 5), Port::INPUT, module, Clocked::SWING_INPUTS + 2, &module->panelTheme));
  919. }
  920. };
  921. } // namespace rack_plugin_ImpromptuModular
  922. using namespace rack_plugin_ImpromptuModular;
  923. RACK_PLUGIN_MODEL_INIT(ImpromptuModular, Clocked) {
  924. Model *modelClocked = Model::create<Clocked, ClockedWidget>("Impromptu Modular", "Clocked", "CLK - Clocked", CLOCK_TAG);
  925. return modelClocked;
  926. }
  927. /*CHANGE LOG
  928. 0.6.10:
  929. add ppqn setting of 12
  930. move master PW to expansion panel and move BPM mode from right-click menu to main pannel button
  931. 0.6.9:
  932. new approach to BPM Detection (all slaves must enable Use BPM Detect if master does, and same ppqn)
  933. choice of 4, 8, 24 PPQN when using BPM detection
  934. add sub-clock ratio of 24 (existing patches making use of greater than 23 mult or div will need to adjust)
  935. add right click option for emit reset when run turned off
  936. 0.6.8:
  937. replace bit-ring-buffer delay engine with event-based delay engine
  938. add BPM pulse frequency vs CV level option in right click settings
  939. updated BPM CV levels (in, out) to new Rack standard for clock CVs
  940. 0.6.7:
  941. created
  942. */