Introduce StateManager class for persisting state to EEPROM.

This commit is contained in:
2025-06-14 21:08:04 -07:00
parent 07ed4b3d9a
commit 696229cfe1
4 changed files with 174 additions and 11 deletions

View File

@ -1,5 +1,5 @@
/** /**
* @file clock_mod.ino * @file Gravity.ino
* @author Adam Wonak (https://github.com/awonak/) * @author Adam Wonak (https://github.com/awonak/)
* @brief Demo firmware for Sitka Instruments Gravity. * @brief Demo firmware for Sitka Instruments Gravity.
* @version 0.1 * @version 0.1
@ -18,20 +18,14 @@
*/ */
#include <gravity.h> #include <gravity.h>
#include "save_state.h"
#include "app_state.h"
#include "channel.h" #include "channel.h"
// Firmware state variables.
struct AppState {
bool refresh_screen = true;
bool editing_param = false;
int selected_param = 0;
byte selected_channel = 0; // 0=tempo, 1-6=output channel
Source selected_source = SOURCE_INTERNAL;
Channel channel[OUTPUT_COUNT];
};
AppState app; AppState app;
StateManager stateManager;
enum ParamsMainPage { enum ParamsMainPage {
PARAM_MAIN_TEMPO, PARAM_MAIN_TEMPO,
PARAM_MAIN_SOURCE, PARAM_MAIN_SOURCE,
@ -114,6 +108,13 @@ void setup() {
// Start Gravity. // Start Gravity.
gravity.Init(); gravity.Init();
// Initialize the state manager. This will load settings from EEPROM
stateManager.initialize(app);
// Apply the loaded state to the hardware
gravity.clock.SetTempo(app.tempo);
gravity.clock.SetSource(app.selected_source);
// Clock handlers. // Clock handlers.
gravity.clock.AttachIntHandler(HandleIntClockTick); gravity.clock.AttachIntHandler(HandleIntClockTick);
gravity.clock.AttachExtHandler(HandleExtClockTick); gravity.clock.AttachExtHandler(HandleExtClockTick);
@ -192,6 +193,7 @@ void HandleShiftPressed() {
void HandleEncoderPressed() { void HandleEncoderPressed() {
app.editing_param = !app.editing_param; app.editing_param = !app.editing_param;
stateManager.save(app);
app.refresh_screen = true; app.refresh_screen = true;
} }
@ -208,6 +210,7 @@ void HandleRotate(Direction dir, int val) {
editChannelParameter(val); editChannelParameter(val);
} }
} }
stateManager.save(app);
app.refresh_screen = true; app.refresh_screen = true;
} }
@ -218,6 +221,7 @@ void HandlePressedRotate(Direction dir, int val) {
app.selected_channel--; app.selected_channel--;
} }
app.selected_param = 0; app.selected_param = 0;
stateManager.save(app);
app.refresh_screen = true; app.refresh_screen = true;
} }
@ -228,6 +232,7 @@ void editMainParameter(int val) {
break; break;
} }
gravity.clock.SetTempo(gravity.clock.Tempo() + val); gravity.clock.SetTempo(gravity.clock.Tempo() + val);
app.tempo = gravity.clock.Tempo();
break; break;
case PARAM_MAIN_SOURCE: { case PARAM_MAIN_SOURCE: {
@ -357,6 +362,7 @@ void DisplayMainPage() {
subText = "MIDI"; subText = "MIDI";
break; break;
} }
break;
} }
drawCenteredText(mainText, MAIN_TEXT_Y, LARGE_FONT); drawCenteredText(mainText, MAIN_TEXT_Y, LARGE_FONT);

View File

@ -0,0 +1,18 @@
#ifndef APP_STATE_H
#define APP_STATE_H
#include <gravity.h>
#include "channel.h"
// Global state for settings and app behavior.
struct AppState {
int tempo = 120;
bool refresh_screen = true;
bool editing_param = false;
int selected_param = 0;
byte selected_channel = 0; // 0=tempo, 1-6=output channel
Source selected_source = SOURCE_INTERNAL;
Channel channel[OUTPUT_COUNT];
};
#endif // APP_STATE_H

View File

@ -0,0 +1,85 @@
// File: save_state.cpp
#include <EEPROM.h>
#include "save_state.h"
#include "app_state.h" // Includes AppState and Channel definitions
bool StateManager::initialize(AppState& app) {
if (isDataValid()) {
EepromData loadedState;
EEPROM.get(sizeof(Metadata), loadedState);
// Restore main app state
app.selected_param = loadedState.selected_param;
app.selected_channel = loadedState.selected_channel;
app.selected_source = static_cast<Source>(loadedState.selected_source);
// --- NEW: Loop through and restore each channel's state ---
for (int i = 0; i < OUTPUT_COUNT; i++) {
auto& ch = app.channel[i]; // Get a reference to the channel object
const auto& saved_ch_state = loadedState.channel_data[i]; // Get a const reference to the saved data
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.setCvSource(static_cast<CvSource>(saved_ch_state.cv_source));
ch.setCvDestination(static_cast<CvDestination>(saved_ch_state.cv_destination));
}
return true;
} else {
writeMetadata();
save(app); // Save the initial default state
return false;
}
}
void StateManager::save(const AppState& app) {
EepromData stateToSave;
// Populate main app state
stateToSave.selected_param = app.selected_param;
stateToSave.selected_channel = app.selected_channel;
stateToSave.selected_source = static_cast<byte>(app.selected_source);
// --- NEW: Loop through and populate each channel's state ---
for (int i = 0; i < OUTPUT_COUNT; i++) {
const auto& ch = app.channel[i]; // Get a const reference to the channel object
auto& saved_ch_state = stateToSave.channel_data[i]; // Get a reference to the struct we're saving to
// Use the getters with 'withCvMod = false' to get the base values
saved_ch_state.base_clock_mod_index = ch.getClockModIndex(false);
saved_ch_state.base_probability = ch.getProbability(false);
saved_ch_state.base_duty_cycle = ch.getDutyCycle(false);
saved_ch_state.base_offset = ch.getOffset(false);
saved_ch_state.cv_source = static_cast<byte>(ch.getCvSource());
saved_ch_state.cv_destination = static_cast<byte>(ch.getCvDestination());
}
// Write the entire state struct to EEPROM
EEPROM.put(sizeof(Metadata), stateToSave);
}
void StateManager::reset(AppState& app) {
AppState defaultState;
app = defaultState;
writeMetadata();
save(app);
}
// isDataValid() and writeMetadata() remain unchanged
bool StateManager::isDataValid() {
Metadata storedMeta;
EEPROM.get(0, storedMeta);
bool nameMatch = (strcmp(storedMeta.sketchName, CURRENT_SKETCH_NAME) == 0);
bool versionMatch = (storedMeta.version == CURRENT_SKETCH_VERSION);
return nameMatch && versionMatch;
}
void StateManager::writeMetadata() {
Metadata currentMeta;
strcpy(currentMeta.sketchName, CURRENT_SKETCH_NAME);
currentMeta.version = CURRENT_SKETCH_VERSION;
EEPROM.put(0, currentMeta);
}

View File

@ -0,0 +1,54 @@
// File: save_state.h
#ifndef SAVE_STATE_H
#define SAVE_STATE_H
#include <Arduino.h>
#include <gravity.h> // We need this for OUTPUT_COUNT
// Forward-declare AppState to avoid circular dependencies.
struct AppState;
// Forward-declare the Source enum as well.
enum Source;
// Define the constants for the current firmware.
const char CURRENT_SKETCH_NAME[] = "Gravity";
const float CURRENT_SKETCH_VERSION = 0.2f; // You could increment this to 0.2 if you want to force a reset
/**
* @brief Manages saving and loading of the application state to and from EEPROM.
*/
class StateManager {
public:
bool initialize(AppState& app);
void save(const AppState& app);
void reset(AppState& app);
private:
// This struct holds the data that identifies the firmware version.
struct Metadata {
char sketchName[16];
float version;
};
struct ChannelState {
byte base_clock_mod_index;
byte base_probability;
byte base_duty_cycle;
byte base_offset;
byte cv_source; // We'll store the CvSource enum as a byte
byte cv_destination; // We'll store the CvDestination enum as a byte
};
// This struct holds all the parameters we want to save.
struct EepromData {
int selected_param;
byte selected_channel;
byte selected_source;
ChannelState channel_data[OUTPUT_COUNT];
};
bool isDataValid();
void writeMetadata();
};
#endif // SAVE_STATE_H