display fixes and updates.

Use selected_sub_param as intermediate value for save/load.
This commit is contained in:
2025-07-04 08:17:55 -07:00
parent 047704a8bf
commit 3cc8d7f0d7
5 changed files with 71 additions and 56 deletions

View File

@ -172,34 +172,36 @@ void HandleEncoderPressed() {
// Check if leaving editing mode should apply a selection. // Check if leaving editing mode should apply a selection.
if (app.editing_param) { if (app.editing_param) {
if (app.selected_channel == 0) { // main page if (app.selected_channel == 0) { // main page
// TODO: rewrite as switch
if (app.selected_param == PARAM_MAIN_ENCODER_DIR) { if (app.selected_param == PARAM_MAIN_ENCODER_DIR) {
bool reversed = app.selected_sub_param == 1; bool reversed = app.selected_sub_param == 1;
gravity.encoder.SetReverseDirection(reversed); gravity.encoder.SetReverseDirection(reversed);
} }
else if (app.selected_param == PARAM_MAIN_SAVE_DATA) { if (app.selected_param == PARAM_MAIN_SAVE_DATA) {
if (app.selected_sub_param != 0) { 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 - 1;
stateManager.saveData(app, app.selected_save_slot); stateManager.saveData(app);
} }
} }
else if (app.selected_param == PARAM_MAIN_LOAD_DATA) { if (app.selected_param == PARAM_MAIN_LOAD_DATA) {
if (app.selected_sub_param != 0) { 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 - 1;
stateManager.loadData(app, app.selected_save_slot); stateManager.loadData(app, app.selected_save_slot);
InitGravity(app); InitGravity(app);
} }
} }
else if (app.selected_param == PARAM_MAIN_RESET_STATE) { if (app.selected_param == PARAM_MAIN_RESET_STATE) {
if (app.selected_sub_param == 0) { // Reset if (app.selected_sub_param == 0) { // Reset
stateManager.reset(app); stateManager.reset(app);
InitGravity(app); InitGravity(app);
} }
} }
} }
// Only mark dirty when leaving editing mode. // Only mark dirty and reset selected_sub_param when leaving editing mode.
stateManager.markDirty(); stateManager.markDirty();
}
app.selected_sub_param = 0; app.selected_sub_param = 0;
}
app.editing_param = !app.editing_param; app.editing_param = !app.editing_param;
app.refresh_screen = true; app.refresh_screen = true;
} }
@ -236,7 +238,6 @@ void editMainParameter(int val) {
gravity.clock.SetTempo(gravity.clock.Tempo() + val); gravity.clock.SetTempo(gravity.clock.Tempo() + val);
app.tempo = gravity.clock.Tempo(); app.tempo = gravity.clock.Tempo();
break; break;
case PARAM_MAIN_SOURCE: { case PARAM_MAIN_SOURCE: {
byte source = static_cast<int>(app.selected_source); byte source = static_cast<int>(app.selected_source);
updateSelection(source, val, Clock::SOURCE_LAST); updateSelection(source, val, Clock::SOURCE_LAST);
@ -244,7 +245,7 @@ void editMainParameter(int val) {
gravity.clock.SetSource(app.selected_source); gravity.clock.SetSource(app.selected_source);
break; break;
} }
case PARAM_MAIN_PULSE: case PARAM_MAIN_PULSE: {
byte pulse = static_cast<int>(app.selected_pulse); byte pulse = static_cast<int>(app.selected_pulse);
updateSelection(pulse, val, Clock::PULSE_LAST); updateSelection(pulse, val, Clock::PULSE_LAST);
app.selected_pulse = static_cast<Clock::Pulse>(pulse); app.selected_pulse = static_cast<Clock::Pulse>(pulse);
@ -252,6 +253,7 @@ void editMainParameter(int val) {
gravity.pulse.Low(); gravity.pulse.Low();
} }
break; break;
}
case PARAM_MAIN_ENCODER_DIR: case PARAM_MAIN_ENCODER_DIR:
updateSelection(app.selected_sub_param, val, 2); updateSelection(app.selected_sub_param, val, 2);
break; break;

View File

@ -12,9 +12,9 @@ struct AppState {
bool refresh_screen = true; bool refresh_screen = true;
bool editing_param = false; bool editing_param = false;
byte selected_param = 0; byte selected_param = 0;
byte selected_sub_param = 0; byte selected_sub_param = 0; // Temporary value for editing params.
byte selected_channel = 0; // 0=tempo, 1-6=output channel byte selected_channel = 0; // 0=tempo, 1-6=output channel
byte selected_shuffle = 0; byte selected_swing = 0;
byte selected_save_slot = 0; // The currently active save slot. byte selected_save_slot = 0; // The currently active save slot.
Clock::Source selected_source = Clock::SOURCE_INTERNAL; Clock::Source selected_source = Clock::SOURCE_INTERNAL;
Clock::Pulse selected_pulse = Clock::PULSE_PPQN_24; Clock::Pulse selected_pulse = Clock::PULSE_PPQN_24;

View File

@ -4,6 +4,7 @@
#include <Arduino.h> #include <Arduino.h>
#include "app_state.h" #include "app_state.h"
#include "save_state.h"
// //
// UI Display functions for drawing the UI to the OLED display. // UI Display functions for drawing the UI to the OLED display.
@ -169,6 +170,15 @@ void swingDivisionMark() {
} }
} }
// Human friendly display value for save slot.
String displaySaveSlot(int slot) {
if (slot >= 0 && slot <= MAX_SAVE_SLOTS / 2) {
return String("A") + String(slot + 1);
} else if (slot >= MAX_SAVE_SLOTS / 2 && slot <= MAX_SAVE_SLOTS) {
return String("B") + String(slot - (MAX_SAVE_SLOTS / 2));
}
}
// Main display functions // Main display functions
void DisplayMainPage() { void DisplayMainPage() {
@ -230,27 +240,25 @@ void DisplayMainPage() {
subText = app.selected_sub_param == 0 ? F("DEFAULT") : F("REVERSED"); subText = app.selected_sub_param == 0 ? F("DEFAULT") : F("REVERSED");
break; break;
case PARAM_MAIN_SAVE_DATA: case PARAM_MAIN_SAVE_DATA:
mainText = "SAVE";
if (app.selected_sub_param == 0) {
subText = F("BACK");
} else {
subText = F("TO SLOT ");
subText += String(app.selected_sub_param + 1);
}
break;
case PARAM_MAIN_LOAD_DATA: case PARAM_MAIN_LOAD_DATA:
mainText = "LOAD"; if (app.selected_sub_param == MAX_SAVE_SLOTS) {
if (app.selected_sub_param == 0) { mainText = F("x");
subText = F("BACK"); subText = F("BACK TO MAIN");
} else { } else {
subText = F("FROM SLOT "); mainText = displaySaveSlot(app.selected_sub_param);
subText += String(app.selected_sub_param + 1); subText = (app.selected_param == PARAM_MAIN_SAVE_DATA)
? F("SAVE TO SLOT")
: F("LOAD FROM SLOT");
} }
break; break;
case PARAM_MAIN_RESET_STATE: case PARAM_MAIN_RESET_STATE:
if (app.selected_sub_param == 0) {
mainText = F("RST"); mainText = F("RST");
subText = app.selected_sub_param == 0 ? F("BACK") : F("RESET ALL"); subText = F("RESET ALL");
break; } else {
mainText = F("x");
subText = F("BACK TO MAIN");
}
} }
drawCenteredText(mainText.c_str(), MAIN_TEXT_Y, LARGE_FONT); drawCenteredText(mainText.c_str(), MAIN_TEXT_Y, LARGE_FONT);

View File

@ -18,6 +18,7 @@ bool StateManager::initialize(AppState& app) {
// Load data from the last active slot. // Load data from the last active slot.
return loadData(app, app.selected_save_slot); return loadData(app, app.selected_save_slot);
} else { } else {
// TODO: save default state to all save slots.
reset(app); reset(app);
return false; return false;
} }
@ -49,7 +50,7 @@ bool StateManager::loadData(AppState& app, byte slot_index) {
ch.setProbability(saved_ch_state.base_probability); ch.setProbability(saved_ch_state.base_probability);
ch.setDutyCycle(saved_ch_state.base_duty_cycle); ch.setDutyCycle(saved_ch_state.base_duty_cycle);
ch.setOffset(saved_ch_state.base_offset); ch.setOffset(saved_ch_state.base_offset);
ch.setSwing(saved_ch_state.base_shuffle); ch.setSwing(saved_ch_state.base_swing);
ch.setSteps(saved_ch_state.base_euc_steps); ch.setSteps(saved_ch_state.base_euc_steps);
ch.setHits(saved_ch_state.base_euc_hits); ch.setHits(saved_ch_state.base_euc_hits);
ch.setCv1Dest(static_cast<CvDestination>(saved_ch_state.cv1_dest)); ch.setCv1Dest(static_cast<CvDestination>(saved_ch_state.cv1_dest));
@ -59,21 +60,22 @@ bool StateManager::loadData(AppState& app, byte slot_index) {
return true; return true;
} }
void StateManager::saveData(const AppState& app, byte slot_index) { void StateManager::saveData(const AppState& app) {
if (slot_index >= MAX_SAVE_SLOTS) return; if (app.selected_save_slot >= MAX_SAVE_SLOTS) return;
_save(app, slot_index); _save(app);
_isDirty = false; _isDirty = false;
} }
void StateManager::update(const AppState& app) { void StateManager::update(const AppState& app) {
if (_isDirty && (millis() - _lastChangeTime > SAVE_DELAY_MS)) { if (_isDirty && (millis() - _lastChangeTime > SAVE_DELAY_MS)) {
_save(app, app.selected_save_slot); _save(app);
_isDirty = false; _isDirty = false;
} }
} }
void StateManager::reset(AppState& app) { void StateManager::reset(AppState& app) {
noInterrupts();
app.tempo = Clock::DEFAULT_TEMPO; app.tempo = Clock::DEFAULT_TEMPO;
app.encoder_reversed = false; app.encoder_reversed = false;
app.selected_param = 0; app.selected_param = 0;
@ -87,6 +89,7 @@ void StateManager::reset(AppState& app) {
} }
// TODO: Should this overwrite save slot 0? // TODO: Should this overwrite save slot 0?
interrupts();
_isDirty = false; _isDirty = false;
} }
@ -104,15 +107,15 @@ bool StateManager::_isDataValid() {
return name_match && version_match; return name_match && version_match;
} }
void StateManager::_save(const AppState& app, byte slot_index) { void StateManager::_save(const AppState& app) {
noInterrupts(); noInterrupts();
_saveState(app, slot_index); _saveState(app);
_saveMetadata(slot_index); _saveMetadata(app.selected_save_slot);
interrupts(); interrupts();
} }
void StateManager::_saveState(const AppState& app, byte slot_index) { void StateManager::_saveState(const AppState& app) {
if (slot_index >= MAX_SAVE_SLOTS) return; if (app.selected_save_slot >= MAX_SAVE_SLOTS) return;
static EepromData save_data; static EepromData save_data;
@ -122,6 +125,7 @@ void StateManager::_saveState(const AppState& app, byte slot_index) {
save_data.selected_channel = app.selected_channel; save_data.selected_channel = app.selected_channel;
save_data.selected_source = static_cast<byte>(app.selected_source); save_data.selected_source = static_cast<byte>(app.selected_source);
save_data.selected_pulse = static_cast<byte>(app.selected_pulse); save_data.selected_pulse = static_cast<byte>(app.selected_pulse);
save_data.selected_save_slot = app.selected_save_slot;
for (int i = 0; i < Gravity::OUTPUT_COUNT; i++) { for (int i = 0; i < Gravity::OUTPUT_COUNT; i++) {
const auto& ch = app.channel[i]; const auto& ch = app.channel[i];
@ -130,14 +134,14 @@ void StateManager::_saveState(const AppState& app, byte slot_index) {
save_ch.base_probability = ch.getProbability(false); save_ch.base_probability = ch.getProbability(false);
save_ch.base_duty_cycle = ch.getDutyCycle(false); save_ch.base_duty_cycle = ch.getDutyCycle(false);
save_ch.base_offset = ch.getOffset(false); save_ch.base_offset = ch.getOffset(false);
save_ch.base_shuffle = ch.getSwing(false); save_ch.base_swing = ch.getSwing(false);
save_ch.base_euc_steps = ch.getSteps(false); save_ch.base_euc_steps = ch.getSteps(false);
save_ch.base_euc_hits = ch.getHits(false); save_ch.base_euc_hits = ch.getHits(false);
save_ch.cv1_dest = static_cast<byte>(ch.getCv1Dest()); save_ch.cv1_dest = static_cast<byte>(ch.getCv1Dest());
save_ch.cv2_dest = static_cast<byte>(ch.getCv2Dest()); save_ch.cv2_dest = static_cast<byte>(ch.getCv2Dest());
} }
int address = EEPROM_DATA_START_ADDR + (slot_index * sizeof(EepromData)); int address = EEPROM_DATA_START_ADDR + (app.selected_save_slot * sizeof(EepromData));
EEPROM.put(address, save_data); EEPROM.put(address, save_data);
} }

View File

@ -9,10 +9,10 @@ struct AppState;
// Define the constants for the current firmware. // Define the constants for the current firmware.
const char SKETCH_NAME[] = "Gravity"; const char SKETCH_NAME[] = "Gravity";
const byte SKETCH_VERSION = 6; const byte SKETCH_VERSION = 7;
// Number of available save slots. // Number of available save slots.
const byte MAX_SAVE_SLOTS = 16; const byte MAX_SAVE_SLOTS = 8;
// Define the minimum amount of time between EEPROM writes. // Define the minimum amount of time between EEPROM writes.
static const unsigned long SAVE_DELAY_MS = 2000; static const unsigned long SAVE_DELAY_MS = 2000;
@ -28,8 +28,8 @@ class StateManager {
bool initialize(AppState& app); bool initialize(AppState& app);
// Load data from specified slot. // Load data from specified slot.
bool loadData(AppState& app, byte slot_index); bool loadData(AppState& app, byte slot_index);
// Save data to specified slot. // Save data to slot defined by app.
void saveData(const AppState& app, byte slot_index); void saveData(const AppState& app);
// Reset AppState instance back to default values. // Reset AppState instance back to default values.
void reset(AppState& app); void reset(AppState& app);
// Call from main loop, check if state has changed and needs to be saved. // Call from main loop, check if state has changed and needs to be saved.
@ -49,7 +49,7 @@ class StateManager {
byte base_probability; byte base_probability;
byte base_duty_cycle; byte base_duty_cycle;
byte base_offset; byte base_offset;
byte base_shuffle; byte base_swing;
byte base_euc_steps; byte base_euc_steps;
byte base_euc_hits; byte base_euc_hits;
byte cv1_dest; // Cast the CvDestination enum as a byte for storage byte cv1_dest; // Cast the CvDestination enum as a byte for storage
@ -63,12 +63,13 @@ class StateManager {
byte selected_channel; byte selected_channel;
byte selected_source; byte selected_source;
byte selected_pulse; byte selected_pulse;
byte selected_save_slot;
ChannelState channel_data[Gravity::OUTPUT_COUNT]; ChannelState channel_data[Gravity::OUTPUT_COUNT];
}; };
private: private:
bool _isDataValid(); bool _isDataValid();
void _save(const AppState& app, byte slot_index); void _save(const AppState& app);
void _saveState(const AppState& app, byte slot_index); void _saveState(const AppState& app);
void _saveMetadata(byte active_slot); void _saveMetadata(byte active_slot);
bool _isDirty; bool _isDirty;