From bd08ac435218304b59ca7dd98c0d04b261253f9e Mon Sep 17 00:00:00 2001 From: Adam Wonak Date: Sat, 21 Feb 2026 10:19:09 -0800 Subject: [PATCH] Add clock run/reset --- firmware/Euclidean/Euclidean.ino | 31 +++++++ firmware/Euclidean/app_state.h | 32 +++---- firmware/Euclidean/display.h | 35 +++++++- firmware/Euclidean/save_state.cpp | 8 +- firmware/Euclidean/save_state.h | 2 + firmware/Gravity/Gravity.ino | 31 +++++++ firmware/Gravity/app_state.h | 32 +++---- firmware/Gravity/display.h | 35 +++++++- firmware/Gravity/save_state.cpp | 8 +- firmware/Gravity/save_state.h | 2 + src/analog_input.h | 128 +++++++++++++++------------ src/digital_output.h | 142 +++++++++++++++--------------- 12 files changed, 324 insertions(+), 162 deletions(-) diff --git a/firmware/Euclidean/Euclidean.ino b/firmware/Euclidean/Euclidean.ino index c0b221b..02ad5d3 100644 --- a/firmware/Euclidean/Euclidean.ino +++ b/firmware/Euclidean/Euclidean.ino @@ -107,6 +107,28 @@ void loop() { } } + // Clock Run + if (app.cv_run == 1 || app.cv_run == 2) { + auto &cv = app.cv_run == 1 ? gravity.cv1 : gravity.cv2; + int val = cv.Read(); + if (val > AnalogInput::GATE_THRESHOLD && gravity.clock.IsPaused()) { + gravity.clock.Start(); + app.refresh_screen = true; + } else if (val < AnalogInput::GATE_THRESHOLD && !gravity.clock.IsPaused()) { + gravity.clock.Stop(); + ResetOutputs(); + app.refresh_screen = true; + } + } + + // Clock Reset + if ((app.cv_reset == 1 && + gravity.cv1.IsRisingEdge(AnalogInput::GATE_THRESHOLD)) || + (app.cv_reset == 2 && + gravity.cv2.IsRisingEdge(AnalogInput::GATE_THRESHOLD))) { + gravity.clock.Reset(); + } + // Check for dirty state eligible to be saved. stateManager.update(app); @@ -285,6 +307,14 @@ void editMainParameter(int val) { gravity.clock.SetTempo(gravity.clock.Tempo() + val); app.tempo = gravity.clock.Tempo(); break; + case PARAM_MAIN_RUN: + updateSelection(app.selected_sub_param, val, 3); + app.cv_run = app.selected_sub_param; + break; + case PARAM_MAIN_RESET: + updateSelection(app.selected_sub_param, val, 3); + app.cv_reset = app.selected_sub_param; + break; case PARAM_MAIN_SOURCE: { byte source = static_cast(app.selected_source); updateSelection(source, val, Clock::SOURCE_LAST); @@ -301,6 +331,7 @@ void editMainParameter(int val) { } break; } + // These changes are applied upon encoder button press. case PARAM_MAIN_ENCODER_DIR: updateSelection(app.selected_sub_param, val, 2); break; diff --git a/firmware/Euclidean/app_state.h b/firmware/Euclidean/app_state.h index 90712df..7327bfd 100644 --- a/firmware/Euclidean/app_state.h +++ b/firmware/Euclidean/app_state.h @@ -18,24 +18,26 @@ // Global state for settings and app behavior. struct AppState { - int tempo = Clock::DEFAULT_TEMPO; - Channel channel[Gravity::OUTPUT_COUNT]; - byte selected_param = 0; - byte selected_sub_param = 0; // Temporary value for editing params. - byte selected_channel = 0; // 0=tempo, 1-6=output channel - byte selected_swing = 0; - byte selected_save_slot = 0; // The currently active save slot. - Clock::Source selected_source = Clock::SOURCE_INTERNAL; - Clock::Pulse selected_pulse = Clock::PULSE_PPQN_24; - bool editing_param = false; - bool encoder_reversed = false; - bool refresh_screen = true; + int tempo = Clock::DEFAULT_TEMPO; + Channel channel[Gravity::OUTPUT_COUNT]; + byte selected_param = 0; + byte selected_sub_param = 0; // Temporary value for editing params. + byte selected_channel = 0; // 0=tempo, 1-6=output channel + byte selected_swing = 0; + byte selected_save_slot = 0; // The currently active save slot. + Clock::Source selected_source = Clock::SOURCE_INTERNAL; + Clock::Pulse selected_pulse = Clock::PULSE_PPQN_24; + byte cv_run = 0; + byte cv_reset = 0; + bool editing_param = false; + bool encoder_reversed = false; + bool refresh_screen = true; }; extern AppState app; -static Channel& GetSelectedChannel() { - return app.channel[app.selected_channel - 1]; +static Channel &GetSelectedChannel() { + return app.channel[app.selected_channel - 1]; } -#endif // APP_STATE_H \ No newline at end of file +#endif // APP_STATE_H \ No newline at end of file diff --git a/firmware/Euclidean/display.h b/firmware/Euclidean/display.h index e679f4a..9a8666b 100644 --- a/firmware/Euclidean/display.h +++ b/firmware/Euclidean/display.h @@ -127,6 +127,8 @@ constexpr uint8_t CHANNEL_BOX_HEIGHT = 14; // Menu items for editing global parameters. enum ParamsMainPage : uint8_t { PARAM_MAIN_TEMPO, + PARAM_MAIN_RUN, + PARAM_MAIN_RESET, PARAM_MAIN_SOURCE, PARAM_MAIN_PULSE, PARAM_MAIN_ENCODER_DIR, @@ -253,6 +255,34 @@ void DisplayMainPage() { } subText = F("BPM"); break; + case PARAM_MAIN_RUN: + mainText = F("RUN"); + switch (app.cv_run) { + case 0: + subText = F("NONE"); + break; + case 1: + subText = F("CV1 GATE"); + break; + case 2: + subText = F("CV2 GATE"); + break; + } + break; + case PARAM_MAIN_RESET: + mainText = F("RST"); + switch (app.cv_reset) { + case 0: + subText = F("NONE"); + break; + case 1: + subText = F("CV1 TRIG"); + break; + case 2: + subText = F("CV2 TRIG"); + break; + } + break; case PARAM_MAIN_SOURCE: mainText = F("EXT"); switch (app.selected_source) { @@ -339,8 +369,9 @@ void DisplayMainPage() { // Draw Main Page menu items String menu_items[PARAM_MAIN_LAST] = { - F("TEMPO"), F("SOURCE"), F("PULSE OUT"), F("ENCODER DIR"), - F("SAVE"), F("LOAD"), F("RESET"), F("ERASE")}; + F("TEMPO"), F("CLK RUN"), F("CLK RESET"), F("SOURCE"), + F("PULSE OUT"), F("ENCODER DIR"), F("SAVE"), F("LOAD"), + F("RESET"), F("ERASE")}; drawMenuItems(menu_items, PARAM_MAIN_LAST); } diff --git a/firmware/Euclidean/save_state.cpp b/firmware/Euclidean/save_state.cpp index 7aa5c9d..b5bb936 100644 --- a/firmware/Euclidean/save_state.cpp +++ b/firmware/Euclidean/save_state.cpp @@ -18,7 +18,7 @@ // Define the constants for the current firmware. const char StateManager::SKETCH_NAME[] = "ALT EUCLIDEAN"; const char StateManager::SEMANTIC_VERSION[] = - "V2.0.0BETA2"; // NOTE: This should match the version in the + "V2.0.0BETA3"; // NOTE: This should match the version in the // library.properties file. // Number of available save slots. @@ -94,6 +94,8 @@ void StateManager::reset(AppState &app) { app.selected_channel = default_app.selected_channel; app.selected_source = default_app.selected_source; app.selected_pulse = default_app.selected_pulse; + app.cv_run = default_app.cv_run; + app.cv_reset = default_app.cv_reset; for (int i = 0; i < Gravity::OUTPUT_COUNT; i++) { app.channel[i].Init(); @@ -148,6 +150,8 @@ void StateManager::_saveState(const AppState &app, byte slot_index) { save_data.selected_channel = app.selected_channel; save_data.selected_source = static_cast(app.selected_source); save_data.selected_pulse = static_cast(app.selected_pulse); + save_data.cv_run = app.cv_run; + save_data.cv_reset = app.cv_reset; // TODO: break this out into a separate function. Save State should be // broken out into global / per-channel save methods. When saving via @@ -184,6 +188,8 @@ void StateManager::_loadState(AppState &app, byte slot_index) { app.selected_channel = load_data.selected_channel; app.selected_source = static_cast(load_data.selected_source); app.selected_pulse = static_cast(load_data.selected_pulse); + app.cv_run = load_data.cv_run; + app.cv_reset = load_data.cv_reset; for (int i = 0; i < Gravity::OUTPUT_COUNT; i++) { auto &ch = app.channel[i]; diff --git a/firmware/Euclidean/save_state.h b/firmware/Euclidean/save_state.h index 0750a26..7a27487 100644 --- a/firmware/Euclidean/save_state.h +++ b/firmware/Euclidean/save_state.h @@ -72,6 +72,8 @@ public: byte selected_channel; byte selected_source; byte selected_pulse; + byte cv_run; + byte cv_reset; ChannelState channel_data[Gravity::OUTPUT_COUNT]; }; diff --git a/firmware/Gravity/Gravity.ino b/firmware/Gravity/Gravity.ino index 4a08a04..33f7ea6 100644 --- a/firmware/Gravity/Gravity.ino +++ b/firmware/Gravity/Gravity.ino @@ -107,6 +107,28 @@ void loop() { } } + // Clock Run + if (app.cv_run == 1 || app.cv_run == 2) { + auto &cv = app.cv_run == 1 ? gravity.cv1 : gravity.cv2; + int val = cv.Read(); + if (val > AnalogInput::GATE_THRESHOLD && gravity.clock.IsPaused()) { + gravity.clock.Start(); + app.refresh_screen = true; + } else if (val < AnalogInput::GATE_THRESHOLD && !gravity.clock.IsPaused()) { + gravity.clock.Stop(); + ResetOutputs(); + app.refresh_screen = true; + } + } + + // Clock Reset + if ((app.cv_reset == 1 && + gravity.cv1.IsRisingEdge(AnalogInput::GATE_THRESHOLD)) || + (app.cv_reset == 2 && + gravity.cv2.IsRisingEdge(AnalogInput::GATE_THRESHOLD))) { + gravity.clock.Reset(); + } + // Check for dirty state eligible to be saved. stateManager.update(app); @@ -285,6 +307,14 @@ void editMainParameter(int val) { gravity.clock.SetTempo(gravity.clock.Tempo() + val); app.tempo = gravity.clock.Tempo(); break; + case PARAM_MAIN_RUN: + updateSelection(app.selected_sub_param, val, 3); + app.cv_run = app.selected_sub_param; + break; + case PARAM_MAIN_RESET: + updateSelection(app.selected_sub_param, val, 3); + app.cv_reset = app.selected_sub_param; + break; case PARAM_MAIN_SOURCE: { byte source = static_cast(app.selected_source); updateSelection(source, val, Clock::SOURCE_LAST); @@ -301,6 +331,7 @@ void editMainParameter(int val) { } break; } + // These changes are applied upon encoder button press. case PARAM_MAIN_ENCODER_DIR: updateSelection(app.selected_sub_param, val, 2); break; diff --git a/firmware/Gravity/app_state.h b/firmware/Gravity/app_state.h index 90712df..f559c0e 100644 --- a/firmware/Gravity/app_state.h +++ b/firmware/Gravity/app_state.h @@ -18,24 +18,26 @@ // Global state for settings and app behavior. struct AppState { - int tempo = Clock::DEFAULT_TEMPO; - Channel channel[Gravity::OUTPUT_COUNT]; - byte selected_param = 0; - byte selected_sub_param = 0; // Temporary value for editing params. - byte selected_channel = 0; // 0=tempo, 1-6=output channel - byte selected_swing = 0; - byte selected_save_slot = 0; // The currently active save slot. - Clock::Source selected_source = Clock::SOURCE_INTERNAL; - Clock::Pulse selected_pulse = Clock::PULSE_PPQN_24; - bool editing_param = false; - bool encoder_reversed = false; - bool refresh_screen = true; + int tempo = Clock::DEFAULT_TEMPO; + Channel channel[Gravity::OUTPUT_COUNT]; + byte selected_param = 0; + byte selected_sub_param = 0; // Temporary value for editing params. + byte selected_channel = 0; // 0=tempo, 1-6=output channel + byte selected_swing = 0; + byte selected_save_slot = 0; // The currently active save slot. + byte cv_run = 0; + byte cv_reset = 0; + Clock::Source selected_source = Clock::SOURCE_INTERNAL; + Clock::Pulse selected_pulse = Clock::PULSE_PPQN_24; + bool editing_param = false; + bool encoder_reversed = false; + bool refresh_screen = true; }; extern AppState app; -static Channel& GetSelectedChannel() { - return app.channel[app.selected_channel - 1]; +static Channel &GetSelectedChannel() { + return app.channel[app.selected_channel - 1]; } -#endif // APP_STATE_H \ No newline at end of file +#endif // APP_STATE_H \ No newline at end of file diff --git a/firmware/Gravity/display.h b/firmware/Gravity/display.h index 03b8517..975478a 100644 --- a/firmware/Gravity/display.h +++ b/firmware/Gravity/display.h @@ -128,6 +128,8 @@ constexpr uint8_t CHANNEL_BOX_HEIGHT = 14; enum ParamsMainPage : uint8_t { PARAM_MAIN_TEMPO, PARAM_MAIN_SOURCE, + PARAM_MAIN_RUN, + PARAM_MAIN_RESET, PARAM_MAIN_PULSE, PARAM_MAIN_ENCODER_DIR, PARAM_MAIN_SAVE_DATA, @@ -296,6 +298,34 @@ void DisplayMainPage() { break; } break; + case PARAM_MAIN_RUN: + mainText = F("RUN"); + switch (app.cv_run) { + case 0: + subText = F("NONE"); + break; + case 1: + subText = F("CV 1"); + break; + case 2: + subText = F("CV 2"); + break; + } + break; + case PARAM_MAIN_RESET: + mainText = F("RST"); + switch (app.cv_reset) { + case 0: + subText = F("NONE"); + break; + case 1: + subText = F("CV 1"); + break; + case 2: + subText = F("CV 2"); + break; + } + break; case PARAM_MAIN_PULSE: mainText = F("OUT"); switch (app.selected_pulse) { @@ -358,8 +388,9 @@ void DisplayMainPage() { // Draw Main Page menu items String menu_items[PARAM_MAIN_LAST] = { - F("TEMPO"), F("SOURCE"), F("PULSE OUT"), F("ENCODER DIR"), - F("SAVE"), F("LOAD"), F("RESET"), F("ERASE")}; + F("TEMPO"), F("SOURCE"), F("CLK RUN"), + F("CLK RESET"), F("PULSE OUT"), F("ENCODER DIR"), + F("SAVE"), F("LOAD"), F("ERASE")}; drawMenuItems(menu_items, PARAM_MAIN_LAST); } diff --git a/firmware/Gravity/save_state.cpp b/firmware/Gravity/save_state.cpp index 57a4b00..65781c2 100644 --- a/firmware/Gravity/save_state.cpp +++ b/firmware/Gravity/save_state.cpp @@ -18,7 +18,7 @@ // Define the constants for the current firmware. const char StateManager::SKETCH_NAME[] = "ALT GRAVITY"; const char StateManager::SEMANTIC_VERSION[] = - "V2.0.0BETA2"; // NOTE: This should match the version in the + "V2.0.0BETA4"; // NOTE: This should match the version in the // library.properties file. // Number of available save slots. @@ -94,6 +94,8 @@ void StateManager::reset(AppState &app) { app.selected_channel = default_app.selected_channel; app.selected_source = default_app.selected_source; app.selected_pulse = default_app.selected_pulse; + app.cv_run = default_app.cv_run; + app.cv_reset = default_app.cv_reset; for (int i = 0; i < Gravity::OUTPUT_COUNT; i++) { app.channel[i].Init(); @@ -148,6 +150,8 @@ void StateManager::_saveState(const AppState &app, byte slot_index) { save_data.selected_channel = app.selected_channel; save_data.selected_source = static_cast(app.selected_source); save_data.selected_pulse = static_cast(app.selected_pulse); + save_data.cv_run = app.cv_run; + save_data.cv_reset = app.cv_reset; // TODO: break this out into a separate function. Save State should be // broken out into global / per-channel save methods. When saving via @@ -185,6 +189,8 @@ void StateManager::_loadState(AppState &app, byte slot_index) { app.selected_channel = load_data.selected_channel; app.selected_source = static_cast(load_data.selected_source); app.selected_pulse = static_cast(load_data.selected_pulse); + app.cv_run = load_data.cv_run; + app.cv_reset = load_data.cv_reset; for (int i = 0; i < Gravity::OUTPUT_COUNT; i++) { auto &ch = app.channel[i]; diff --git a/firmware/Gravity/save_state.h b/firmware/Gravity/save_state.h index 5ff1dfe..7bdf85e 100644 --- a/firmware/Gravity/save_state.h +++ b/firmware/Gravity/save_state.h @@ -73,6 +73,8 @@ public: byte selected_channel; byte selected_source; byte selected_pulse; + byte cv_run; + byte cv_reset; ChannelState channel_data[Gravity::OUTPUT_COUNT]; }; diff --git a/src/analog_input.h b/src/analog_input.h index 496899b..bc88039 100644 --- a/src/analog_input.h +++ b/src/analog_input.h @@ -11,78 +11,94 @@ #ifndef ANALOG_INPUT_H #define ANALOG_INPUT_H -const int MAX_INPUT = (1 << 10) - 1; // Max 10 bit analog read resolution. +const int MAX_INPUT = (1 << 10) - 1; // Max 10 bit analog read resolution. // estimated default calibration value const int CALIBRATED_LOW = -566; const int CALIBRATED_HIGH = 512; class AnalogInput { - public: - AnalogInput() {} - ~AnalogInput() {} +public: + static const int GATE_THRESHOLD = 0; - /** - * Initializes a analog input object. - * - * @param pin gpio pin for the analog input. - */ - void Init(uint8_t pin) { - pinMode(pin, INPUT); - pin_ = pin; - } + AnalogInput() {} + ~AnalogInput() {} - /** - * Read the value of the analog input and set instance state. - * - */ - void Process() { - old_read_ = read_; - int raw = analogRead(pin_); - read_ = map(raw, 0, MAX_INPUT, low_, high_); - read_ = constrain(read_ - offset_, -512, 512); - if (inverted_) read_ = -read_; - } + /** + * Initializes a analog input object. + * + * @param pin gpio pin for the analog input. + */ + void Init(uint8_t pin) { + pinMode(pin, INPUT); + pin_ = pin; + } - // Set calibration values. + /** + * Read the value of the analog input and set instance state. + * + */ + void Process() { + old_read_ = read_; + int raw = analogRead(pin_); + read_ = map(raw, 0, MAX_INPUT, low_, high_); + read_ = constrain(read_ - offset_, -512, 512); + if (inverted_) + read_ = -read_; + } - void AdjustCalibrationLow(int amount) { low_ += amount; } + // Set calibration values. - void AdjustCalibrationHigh(int amount) { high_ += amount; } + void AdjustCalibrationLow(int amount) { low_ += amount; } - void SetOffset(float percent) { offset_ = -(percent)*512; } + void AdjustCalibrationHigh(int amount) { high_ += amount; } - void SetAttenuation(float percent) { - low_ = abs(percent) * CALIBRATED_LOW; - high_ = abs(percent) * CALIBRATED_HIGH; - inverted_ = percent < 0; - } + void SetOffset(float percent) { offset_ = -(percent) * 512; } - /** - * Get the current value of the analog input within a range of +/-512. - * - * @return read value within a range of +/-512. - * - */ - inline int16_t Read() { return read_; } + void SetAttenuation(float percent) { + low_ = abs(percent) * CALIBRATED_LOW; + high_ = abs(percent) * CALIBRATED_HIGH; + inverted_ = percent < 0; + } - /** - * Return the analog read value as voltage. - * - * @return A float representing the voltage (-5.0 to +5.0). - * - */ - inline float Voltage() { return ((read_ / 512.0) * 5.0); } + /** + * Get the current value of the analog input within a range of +/-512. + * + * @return read value within a range of +/-512. + * + */ + inline int16_t Read() { return read_; } - private: - uint8_t pin_; - int16_t read_; - uint16_t old_read_; - // calibration values. - int offset_ = 0; - int low_ = CALIBRATED_LOW; - int high_ = CALIBRATED_HIGH; - bool inverted_ = false; + /** + * Return the analog read value as voltage. + * + * @return A float representing the voltage (-5.0 to +5.0). + * + */ + inline float Voltage() { return ((read_ / 512.0) * 5.0); } + + /** + * Checks for a rising edge transition across a threshold. + * + * @param threshold The value that the input must cross. + * @return True if the value just crossed the threshold from below, false + * otherwise. + */ + inline bool IsRisingEdge(int16_t threshold) const { + bool was_high = old_read_ > threshold; + bool is_high = read_ > threshold; + return is_high && !was_high; + } + +private: + uint8_t pin_; + int16_t read_; + uint16_t old_read_; + // calibration values. + int offset_ = 0; + int low_ = CALIBRATED_LOW; + int high_ = CALIBRATED_HIGH; + bool inverted_ = false; }; #endif diff --git a/src/digital_output.h b/src/digital_output.h index 9c4cfc8..5a8d58d 100644 --- a/src/digital_output.h +++ b/src/digital_output.h @@ -4,7 +4,7 @@ * @brief Class for interacting with trigger / gate outputs. * @version 0.1 * @date 2025-04-17 - * + * * @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com * */ @@ -16,79 +16,81 @@ const byte DEFAULT_TRIGGER_DURATION_MS = 5; class DigitalOutput { - public: - /** - * Initializes an CV Output paired object. - * - * @param cv_pin gpio pin for the cv output - */ - void Init(uint8_t cv_pin) { - pinMode(cv_pin, OUTPUT); // Gate/Trigger Output - cv_pin_ = cv_pin; - trigger_duration_ = DEFAULT_TRIGGER_DURATION_MS; +public: + /** + * Initializes an CV Output paired object. + * + * @param cv_pin gpio pin for the cv output + */ + void Init(uint8_t cv_pin) { + pinMode(cv_pin, OUTPUT); // Gate/Trigger Output + cv_pin_ = cv_pin; + trigger_duration_ = DEFAULT_TRIGGER_DURATION_MS; + } + + /** + * Set the trigger duration in miliseconds. + * + * @param duration_ms trigger duration in miliseconds + */ + void SetTriggerDuration(uint8_t duration_ms) { + trigger_duration_ = duration_ms; + } + + /** + * Turn the CV and LED on or off according to the input state. + * + * @param state Arduino digital HIGH or LOW values. + */ + inline void Update(uint8_t state) { + if (state == HIGH) + High(); // Rising + if (state == LOW) + Low(); // Falling + } + + // Sets the cv output HIGH to about 5v. + inline void High() { update(HIGH); } + + // Sets the cv output LOW to 0v. + inline void Low() { update(LOW); } + + /** + * Begin a Trigger period for this output. + */ + inline void Trigger() { + update(HIGH); + last_triggered_ = millis(); + } + + /** + * Return a bool representing the on/off state of the output. + */ + inline void Process() { + // If trigger is HIGH and the trigger duration time has elapsed, set the + // output low. + if (on_ && (millis() - last_triggered_) >= trigger_duration_) { + update(LOW); } + } - /** - * Set the trigger duration in miliseconds. - * - * @param duration_ms trigger duration in miliseconds - */ - void SetTriggerDuration(uint8_t duration_ms) { - trigger_duration_ = duration_ms; - } + /** + * Return a bool representing the on/off state of the output. + * + * @return true if current cv state is high, false if current cv state is low + */ + inline bool On() { return on_; } - /** - * Turn the CV and LED on or off according to the input state. - * - * @param state Arduino digital HIGH or LOW values. - */ - inline void Update(uint8_t state) { - if (state == HIGH) High(); // Rising - if (state == LOW) Low(); // Falling - } +private: + unsigned long last_triggered_; + uint8_t trigger_duration_; + uint8_t cv_pin_; + bool on_; - // Sets the cv output HIGH to about 5v. - inline void High() { update(HIGH); } - - // Sets the cv output LOW to 0v. - inline void Low() { update(LOW); } - - /** - * Begin a Trigger period for this output. - */ - inline void Trigger() { - update(HIGH); - last_triggered_ = millis(); - } - - /** - * Return a bool representing the on/off state of the output. - */ - inline void Process() { - // If trigger is HIGH and the trigger duration time has elapsed, set the output low. - if (on_ && (millis() - last_triggered_) >= trigger_duration_) { - update(LOW); - } - } - - /** - * Return a bool representing the on/off state of the output. - * - * @return true if current cv state is high, false if current cv state is low - */ - inline bool On() { return on_; } - - private: - unsigned long last_triggered_; - uint8_t trigger_duration_; - uint8_t cv_pin_; - uint8_t led_pin_; - bool on_; - - void update(uint8_t state) { - digitalWrite(cv_pin_, state); - on_ = state == HIGH; - } + void update(uint8_t state) { + digitalWrite(cv_pin_, state); + on_ = state == HIGH; + } }; #endif