From 17afa9882b34e491bfe06b02a9fabb52413dad9f Mon Sep 17 00:00:00 2001 From: Adam Wonak Date: Sun, 29 Jun 2025 20:33:00 -0700 Subject: [PATCH] implement cv mod for Euclidean. Note, this is implemented as an override, not a sum mod. --- examples/Gravity/Gravity.ino | 14 +++++-- examples/Gravity/channel.h | 50 +++++++++++------------ examples/Gravity/display.h | 10 ++++- examples/Gravity/euclidean.h | 77 +++++++++++------------------------- 4 files changed, 64 insertions(+), 87 deletions(-) diff --git a/examples/Gravity/Gravity.ino b/examples/Gravity/Gravity.ino index 03edfb9..80dc75f 100644 --- a/examples/Gravity/Gravity.ino +++ b/examples/Gravity/Gravity.ino @@ -58,10 +58,16 @@ void loop() { gravity.Process(); // Read CVs and call the update function for each channel. - int cv1 = gravity.cv1.Read(); - int cv2 = gravity.cv2.Read(); - for (int i = 0; i < Gravity::OUTPUT_COUNT; i++) { - app.channel[i].applyCvMod(cv1, cv2); + if (!app.editing_param) { + int cv1 = gravity.cv1.Read(); + int cv2 = gravity.cv2.Read(); + for (int i = 0; i < Gravity::OUTPUT_COUNT; i++) { + // Only apply CV to the channel when the current channel has cv + // mod configured. + if (app.channel[i].isCvModActive()) { + app.channel[i].applyCvMod(cv1, cv2); + } + } } // Check for dirty state eligible to be saved. diff --git a/examples/Gravity/channel.h b/examples/Gravity/channel.h index d0b5d28..af99eb2 100644 --- a/examples/Gravity/channel.h +++ b/examples/Gravity/channel.h @@ -20,6 +20,8 @@ enum CvDestination { CV_DEST_DUTY, CV_DEST_OFFSET, CV_DEST_SWING, + CV_DEST_EUC_STEPS, + CV_DEST_EUC_HITS, CV_DEST_LAST, }; @@ -80,11 +82,10 @@ class Channel { bool isCvModActive() const { return cv_source != CV_NONE && cv_destination != CV_DEST_NONE; } // Euclidean - void setSteps(byte val) { pattern.SetSteps(val); } - void setHits(byte val) { pattern.SetHits(val); } - - byte getSteps() { pattern.GetSteps(); } - byte getHits() { pattern.GetHits(); } + void setSteps(int val) { pattern.SetSteps(val); } + void setHits(int val) { pattern.SetHits(val); } + byte getSteps() { return pattern.GetSteps(); } + byte getHits() { return pattern.GetHits(); } /** * @brief Processes a clock tick and determines if the output should be high or low. @@ -111,19 +112,14 @@ class Channel { // Step check if (current_tick_offset % mod_pulses == 0) { bool hit = cvmod_probability >= random(0, 100); - if (pattern.IsActive()) { - // Euclidean rhythm check - switch (pattern.NextStep()) { - case Pattern::REST: // Rest when active or fall back to probability - hit = pattern.IsActive() ? false : hit; - break; - case Pattern::HIT: // Hit if probability is true - hit &= true; - break; - case Pattern::PADDING: // Padding returns only when active, always rest) - hit = false; - break; - } + // Euclidean rhythm check + switch (pattern.NextStep()) { + case Pattern::REST: // Rest when active or fall back to probability + hit = false; + break; + case Pattern::HIT: // Hit if probability is true + hit &= true; + break; } if (hit) { output.High(); @@ -139,16 +135,6 @@ class Channel { } void applyCvMod(int cv1_value, int cv2_value) { - if (!isCvModActive()) { - // If CV is off, ensure cv modded values match the base values. - cvmod_clock_mod_index = base_clock_mod_index; - cvmod_probability = base_probability; - cvmod_duty_cycle = base_duty_cycle; - cvmod_offset = base_offset; - cvmod_swing = base_swing; - return; - } - // Use the CV value for current selected cv source. int value = (cv_source == CV_1) ? cv1_value : cv2_value; @@ -179,6 +165,14 @@ class Channel { (cv_destination == CV_DEST_SWING) ? constrain(base_swing + map(value, -512, 512, -25, 25), 50, 95) : base_swing; + + if (cv_destination == CV_DEST_EUC_STEPS) { + pattern.SetSteps(map(value, -512, 512, 0, MAX_PATTERN_LEN)); + } + + if (cv_destination == CV_DEST_EUC_HITS) { + pattern.SetHits(map(value, -512, 512, 0, pattern.GetSteps())); + } } private: diff --git a/examples/Gravity/display.h b/examples/Gravity/display.h index 73769b9..c1dc144 100644 --- a/examples/Gravity/display.h +++ b/examples/Gravity/display.h @@ -313,6 +313,12 @@ void DisplayChannelPage() { case CV_DEST_SWING: subText = F("SWING"); break; + case CV_DEST_EUC_STEPS: + subText = F("EUCLID STEPS"); + break; + case CV_DEST_EUC_HITS: + subText = F("EUCLID HITS"); + break; } break; } @@ -323,8 +329,8 @@ void DisplayChannelPage() { // Draw Channel Page menu items String menu_items[PARAM_CH_LAST] = { - F("MOD"), F("PROBABILITY"), F("DUTY"), F("OFFSET"), F("SWING"), F("EUC STEPS"), - F("EUC HITS"), F("CV SOURCE"), F("CV DEST")}; + F("MOD"), F("PROBABILITY"), F("DUTY"), F("OFFSET"), F("SWING"), F("EUCLID STEPS"), + F("EUCLID HITS"), F("CV SOURCE"), F("CV DEST")}; drawMenuItems(menu_items, PARAM_CH_LAST); } diff --git a/examples/Gravity/euclidean.h b/examples/Gravity/euclidean.h index 647e3eb..57e5cb6 100644 --- a/examples/Gravity/euclidean.h +++ b/examples/Gravity/euclidean.h @@ -8,7 +8,7 @@ struct PatternState { uint8_t hits; }; -const PatternState DEFAULT_PATTERN = {16, 4}; +const PatternState DEFAULT_PATTERN = {1, 1}; class Pattern { public: @@ -16,93 +16,64 @@ class Pattern { ~Pattern() {} enum Step { - HIT, REST, - PADDING, + HIT, }; void Init(PatternState state) { - steps_ = constrain(state.steps, 0, MAX_PATTERN_LEN); + steps_ = constrain(state.steps, 1, MAX_PATTERN_LEN); hits_ = constrain(state.hits, 1, steps_); updatePattern(); } PatternState GetState() { return {steps_, hits_}; } - // Get the current step value and advance the euclidean rhythm step index - // to the next step in the pattern. - Step NextStep() { - byte padding_ = 0; - if (steps_ == 0) return REST; - - Step value = GetCurrentStep(current_step_); - current_step_ = - (current_step_ < steps_ + padding_ - 1) ? current_step_ + 1 : 0; - return value; - } - Step GetCurrentStep(byte i) { return pattern_[i]; } - void SetSteps(byte steps) { - steps_ = constrain(steps, 0, MAX_PATTERN_LEN); + void SetSteps(int steps) { + steps_ = constrain(steps, 1, MAX_PATTERN_LEN); hits_ = min(hits_, steps_); updatePattern(); } - void SetHits(byte hits) { - hits_ = constrain(hits, 0, steps_); + void SetHits(int hits) { + hits_ = constrain(hits, 1, steps_); + updatePattern(); } - // void ChangeOffset(byte val) { - // offset_ = constrain(offset_ + val, 0, (steps_ + padding_)); - // updatePattern(); - // } + void Reset() { step_index_ = 0; } - // void ChangePadding(byte val) { - // if (val == 1 && padding_ + steps_ < MAX_PATTERN_LEN) { - // padding_++; - // updatePattern(); - // } else if (val == -1 && padding_ > 0) { - // padding_--; - // offset_ = min(offset_, (padding_ + steps_) - 1); - // updatePattern(); - // } - // } + uint8_t GetSteps() { return steps_; } + uint8_t GetHits() { return hits_; } + uint8_t GetStepIndex() { return step_index_; } - void Reset() { current_step_ = 0; } - bool IsActive() { return steps_ != 0 && hits_ != 0; } + // Get the current step value and advance the euclidean rhythm step index + // to the next step in the pattern. + Step NextStep() { + if (steps_ == 0) return REST; - inline uint8_t GetSteps() { return steps_; } - inline uint8_t GetHits() { return hits_; } - inline uint8_t GetStepIndex() { return current_step_; } + Step value = GetCurrentStep(step_index_); + step_index_ = (step_index_ < steps_ - 1) ? step_index_ + 1 : 0; + return value; + } private: uint8_t steps_ = 0; uint8_t hits_ = 0; - volatile uint8_t current_step_ = 0; + volatile uint8_t step_index_ = 0; Step pattern_[MAX_PATTERN_LEN]; // Update the euclidean rhythm pattern when attributes change. void updatePattern() { - // Fill current pattern with "padding" steps, then overwrite with hits - // and rests. - for (int i = 0; i < MAX_PATTERN_LEN; i++) { - pattern_[i] = PADDING; - } - - // Populate the euclidean rhythm pattern according to the current - // instance variables. byte bucket = 0; - byte offset_ = 0; // temp disable - byte padding_ = 0; // temp disable - pattern_[offset_] = (hits_ > 0) ? HIT : REST; + pattern_[0] = HIT; for (int i = 1; i < steps_; i++) { bucket += hits_; if (bucket >= steps_) { bucket -= steps_; - pattern_[(i + offset_) % (steps_ + padding_)] = HIT; + pattern_[i] = HIT; } else { - pattern_[(i + offset_) % (steps_ + padding_)] = REST; + pattern_[i] = REST; } } }