From e664f6c17b0eff241ad604c89e20e265953a7f17 Mon Sep 17 00:00:00 2001 From: Adam Wonak Date: Fri, 4 Jul 2025 10:19:39 -0700 Subject: [PATCH] refactor save state to allow persisting a transient state prior to explicitly saving to a slot. --- examples/Gravity/Gravity.ino | 4 +- examples/Gravity/display.h | 13 +++- examples/Gravity/save_state.cpp | 105 ++++++++++++++++---------------- examples/Gravity/save_state.h | 9 ++- 4 files changed, 68 insertions(+), 63 deletions(-) diff --git a/examples/Gravity/Gravity.ino b/examples/Gravity/Gravity.ino index c990c99..d0d21d8 100644 --- a/examples/Gravity/Gravity.ino +++ b/examples/Gravity/Gravity.ino @@ -179,13 +179,13 @@ void HandleEncoderPressed() { } if (app.selected_param == PARAM_MAIN_SAVE_DATA) { if (app.selected_sub_param < MAX_SAVE_SLOTS) { - app.selected_save_slot = app.selected_sub_param - 1; + app.selected_save_slot = app.selected_sub_param; stateManager.saveData(app); } } if (app.selected_param == PARAM_MAIN_LOAD_DATA) { if (app.selected_sub_param < MAX_SAVE_SLOTS) { - app.selected_save_slot = app.selected_sub_param - 1; + app.selected_save_slot = app.selected_sub_param; stateManager.loadData(app, app.selected_save_slot); InitGravity(app); } diff --git a/examples/Gravity/display.h b/examples/Gravity/display.h index 65200ba..d6ddc16 100644 --- a/examples/Gravity/display.h +++ b/examples/Gravity/display.h @@ -153,6 +153,10 @@ void drawMenuItems(String menu_items[], int menu_size) { } } +// Visual indicators for main section of screen. +inline void solidTick() { gravity.display.drawBox(56, 4, 4, 4); } +inline void hollowTick() { gravity.display.drawBox(56, 4, 4, 4); } + // Display an indicator when swing percentage matches a musical note. void swingDivisionMark() { auto& ch = GetSelectedChannel(); @@ -160,13 +164,12 @@ void swingDivisionMark() { case 58: // 1/32nd case 66: // 1/16th case 75: // 1/8th - gravity.display.drawBox(56, 4, 4, 4); + solidTick(); break; case 54: // 1/32nd tripplet case 62: // 1/16th tripplet case 71: // 1/8th tripplet - gravity.display.drawBox(56, 4, 4, 4); - gravity.display.drawBox(57, 5, 2, 2); + hollowTick(); break; } } @@ -246,6 +249,10 @@ void DisplayMainPage() { mainText = F("x"); subText = F("BACK TO MAIN"); } else { + // Indicate currently active slot. + if (app.selected_sub_param == app.selected_save_slot) { + solidTick(); + } mainText = displaySaveSlot(app.selected_sub_param); subText = (app.selected_param == PARAM_MAIN_SAVE_DATA) ? F("SAVE TO SLOT") diff --git a/examples/Gravity/save_state.cpp b/examples/Gravity/save_state.cpp index bf61730..8ac0960 100644 --- a/examples/Gravity/save_state.cpp +++ b/examples/Gravity/save_state.cpp @@ -11,15 +11,17 @@ StateManager::StateManager() : _isDirty(false), _lastChangeTime(0) {} bool StateManager::initialize(AppState& app) { if (_isDataValid()) { - Metadata load_meta; - EEPROM.get(0, load_meta); - app.selected_save_slot = load_meta.active_slot; - - // Load data from the last active slot. - return loadData(app, app.selected_save_slot); + // Load data from the transient slot. + return loadData(app, MAX_SAVE_SLOTS); } else { - // TODO: save default state to all save slots. + // EEPROM does not contain save data for this firmware & version. + // Initialize eeprom and save default patter to all save slots. reset(app); + _saveMetadata(); + for (int i = 0; i <= MAX_SAVE_SLOTS; i++) { + app.selected_save_slot = i; + _saveState(app, i); + } return false; } } @@ -27,35 +29,7 @@ bool StateManager::initialize(AppState& app) { bool StateManager::loadData(AppState& app, byte slot_index) { if (slot_index >= MAX_SAVE_SLOTS) return false; - static EepromData load_data; - int address = EEPROM_DATA_START_ADDR + (slot_index * sizeof(EepromData)); - EEPROM.get(address, load_data); - - // TODO: Validate loaded data. - - // Restore app state from loaded data. - app.tempo = load_data.tempo; - app.encoder_reversed = load_data.encoder_reversed; - app.selected_param = load_data.selected_param; - 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.selected_save_slot = slot_index; - - for (int i = 0; i < Gravity::OUTPUT_COUNT; i++) { - auto& ch = app.channel[i]; - const auto& saved_ch_state = load_data.channel_data[i]; - - ch.setClockMod(saved_ch_state.base_clock_mod_index); - ch.setProbability(saved_ch_state.base_probability); - ch.setDutyCycle(saved_ch_state.base_duty_cycle); - ch.setOffset(saved_ch_state.base_offset); - ch.setSwing(saved_ch_state.base_swing); - ch.setSteps(saved_ch_state.base_euc_steps); - ch.setHits(saved_ch_state.base_euc_hits); - ch.setCv1Dest(static_cast(saved_ch_state.cv1_dest)); - ch.setCv2Dest(static_cast(saved_ch_state.cv2_dest)); - } + _loadState(app, slot_index); return true; } @@ -63,19 +37,19 @@ bool StateManager::loadData(AppState& app, byte slot_index) { void StateManager::saveData(const AppState& app) { if (app.selected_save_slot >= MAX_SAVE_SLOTS) return; - _save(app); + _saveState(app, app.selected_save_slot); _isDirty = false; } void StateManager::update(const AppState& app) { if (_isDirty && (millis() - _lastChangeTime > SAVE_DELAY_MS)) { - _save(app); + // MAX_SAVE_SLOTS slot is reserved for transient state. + _saveState(app, MAX_SAVE_SLOTS); _isDirty = false; } } void StateManager::reset(AppState& app) { - noInterrupts(); app.tempo = Clock::DEFAULT_TEMPO; app.encoder_reversed = false; app.selected_param = 0; @@ -88,9 +62,6 @@ void StateManager::reset(AppState& app) { app.channel[i].Init(); } - // TODO: Should this overwrite save slot 0? - interrupts(); - _isDirty = false; } @@ -107,16 +78,10 @@ bool StateManager::_isDataValid() { return name_match && version_match; } -void StateManager::_save(const AppState& app) { - noInterrupts(); - _saveState(app); - _saveMetadata(app.selected_save_slot); - interrupts(); -} - -void StateManager::_saveState(const AppState& app) { +void StateManager::_saveState(const AppState& app, byte slot_index) { if (app.selected_save_slot >= MAX_SAVE_SLOTS) return; + noInterrupts(); static EepromData save_data; save_data.tempo = app.tempo; @@ -141,14 +106,48 @@ void StateManager::_saveState(const AppState& app) { save_ch.cv2_dest = static_cast(ch.getCv2Dest()); } - int address = EEPROM_DATA_START_ADDR + (app.selected_save_slot * sizeof(EepromData)); + int address = EEPROM_DATA_START_ADDR + (slot_index * sizeof(EepromData)); EEPROM.put(address, save_data); + interrupts(); } -void StateManager::_saveMetadata(byte active_slot) { +void StateManager::_loadState(AppState& app, byte slot_index) { + noInterrupts(); + static EepromData load_data; + int address = EEPROM_DATA_START_ADDR + (slot_index * sizeof(EepromData)); + EEPROM.get(address, load_data); + + // Restore app state from loaded data. + app.tempo = load_data.tempo; + app.encoder_reversed = load_data.encoder_reversed; + app.selected_param = load_data.selected_param; + 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.selected_save_slot = slot_index; + + for (int i = 0; i < Gravity::OUTPUT_COUNT; i++) { + auto& ch = app.channel[i]; + const auto& saved_ch_state = load_data.channel_data[i]; + + ch.setClockMod(saved_ch_state.base_clock_mod_index); + ch.setProbability(saved_ch_state.base_probability); + ch.setDutyCycle(saved_ch_state.base_duty_cycle); + ch.setOffset(saved_ch_state.base_offset); + ch.setSwing(saved_ch_state.base_swing); + ch.setSteps(saved_ch_state.base_euc_steps); + ch.setHits(saved_ch_state.base_euc_hits); + ch.setCv1Dest(static_cast(saved_ch_state.cv1_dest)); + ch.setCv2Dest(static_cast(saved_ch_state.cv2_dest)); + } + interrupts(); +} + +void StateManager::_saveMetadata() { + noInterrupts(); Metadata current_meta; strcpy(current_meta.sketch_name, SKETCH_NAME); current_meta.version = SKETCH_VERSION; - current_meta.active_slot = active_slot; EEPROM.put(0, current_meta); + interrupts(); } diff --git a/examples/Gravity/save_state.h b/examples/Gravity/save_state.h index b77e6be..96415b3 100644 --- a/examples/Gravity/save_state.h +++ b/examples/Gravity/save_state.h @@ -12,7 +12,7 @@ const char SKETCH_NAME[] = "Gravity"; const byte SKETCH_VERSION = 7; // Number of available save slots. -const byte MAX_SAVE_SLOTS = 8; +const byte MAX_SAVE_SLOTS = 10; // Define the minimum amount of time between EEPROM writes. static const unsigned long SAVE_DELAY_MS = 2000; @@ -41,7 +41,6 @@ class StateManager { // This struct holds the data that identifies the firmware version. struct Metadata { byte version; - byte active_slot; char sketch_name[16]; }; struct ChannelState { @@ -68,9 +67,9 @@ class StateManager { }; private: bool _isDataValid(); - void _save(const AppState& app); - void _saveState(const AppState& app); - void _saveMetadata(byte active_slot); + void _saveMetadata(); + void _saveState(const AppState& app, byte slot_index); + void _loadState(AppState& app, byte slot_index); bool _isDirty; unsigned long _lastChangeTime;