display fixes and updates.
Use selected_sub_param as intermediate value for save/load.
This commit is contained in:
@ -172,34 +172,36 @@ void HandleEncoderPressed() {
|
||||
// Check if leaving editing mode should apply a selection.
|
||||
if (app.editing_param) {
|
||||
if (app.selected_channel == 0) { // main page
|
||||
// TODO: rewrite as switch
|
||||
if (app.selected_param == PARAM_MAIN_ENCODER_DIR) {
|
||||
bool reversed = app.selected_sub_param == 1;
|
||||
gravity.encoder.SetReverseDirection(reversed);
|
||||
}
|
||||
else if (app.selected_param == PARAM_MAIN_SAVE_DATA) {
|
||||
if (app.selected_sub_param != 0) {
|
||||
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;
|
||||
stateManager.saveData(app, app.selected_save_slot);
|
||||
stateManager.saveData(app);
|
||||
}
|
||||
}
|
||||
else if (app.selected_param == PARAM_MAIN_LOAD_DATA) {
|
||||
if (app.selected_sub_param != 0) {
|
||||
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;
|
||||
stateManager.loadData(app, app.selected_save_slot);
|
||||
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
|
||||
stateManager.reset(app);
|
||||
InitGravity(app);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Only mark dirty when leaving editing mode.
|
||||
// Only mark dirty and reset selected_sub_param when leaving editing mode.
|
||||
stateManager.markDirty();
|
||||
}
|
||||
app.selected_sub_param = 0;
|
||||
}
|
||||
|
||||
app.editing_param = !app.editing_param;
|
||||
app.refresh_screen = true;
|
||||
}
|
||||
@ -236,7 +238,6 @@ void editMainParameter(int val) {
|
||||
gravity.clock.SetTempo(gravity.clock.Tempo() + val);
|
||||
app.tempo = gravity.clock.Tempo();
|
||||
break;
|
||||
|
||||
case PARAM_MAIN_SOURCE: {
|
||||
byte source = static_cast<int>(app.selected_source);
|
||||
updateSelection(source, val, Clock::SOURCE_LAST);
|
||||
@ -244,7 +245,7 @@ void editMainParameter(int val) {
|
||||
gravity.clock.SetSource(app.selected_source);
|
||||
break;
|
||||
}
|
||||
case PARAM_MAIN_PULSE:
|
||||
case PARAM_MAIN_PULSE: {
|
||||
byte pulse = static_cast<int>(app.selected_pulse);
|
||||
updateSelection(pulse, val, Clock::PULSE_LAST);
|
||||
app.selected_pulse = static_cast<Clock::Pulse>(pulse);
|
||||
@ -252,6 +253,7 @@ void editMainParameter(int val) {
|
||||
gravity.pulse.Low();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PARAM_MAIN_ENCODER_DIR:
|
||||
updateSelection(app.selected_sub_param, val, 2);
|
||||
break;
|
||||
|
||||
@ -12,9 +12,9 @@ struct AppState {
|
||||
bool refresh_screen = true;
|
||||
bool editing_param = false;
|
||||
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_shuffle = 0;
|
||||
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;
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "app_state.h"
|
||||
#include "save_state.h"
|
||||
|
||||
//
|
||||
// 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
|
||||
|
||||
void DisplayMainPage() {
|
||||
@ -230,27 +240,25 @@ void DisplayMainPage() {
|
||||
subText = app.selected_sub_param == 0 ? F("DEFAULT") : F("REVERSED");
|
||||
break;
|
||||
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:
|
||||
mainText = "LOAD";
|
||||
if (app.selected_sub_param == 0) {
|
||||
subText = F("BACK");
|
||||
if (app.selected_sub_param == MAX_SAVE_SLOTS) {
|
||||
mainText = F("x");
|
||||
subText = F("BACK TO MAIN");
|
||||
} else {
|
||||
subText = F("FROM SLOT ");
|
||||
subText += String(app.selected_sub_param + 1);
|
||||
mainText = displaySaveSlot(app.selected_sub_param);
|
||||
subText = (app.selected_param == PARAM_MAIN_SAVE_DATA)
|
||||
? F("SAVE TO SLOT")
|
||||
: F("LOAD FROM SLOT");
|
||||
}
|
||||
break;
|
||||
case PARAM_MAIN_RESET_STATE:
|
||||
if (app.selected_sub_param == 0) {
|
||||
mainText = F("RST");
|
||||
subText = app.selected_sub_param == 0 ? F("BACK") : F("RESET ALL");
|
||||
break;
|
||||
subText = F("RESET ALL");
|
||||
} else {
|
||||
mainText = F("x");
|
||||
subText = F("BACK TO MAIN");
|
||||
}
|
||||
}
|
||||
|
||||
drawCenteredText(mainText.c_str(), MAIN_TEXT_Y, LARGE_FONT);
|
||||
|
||||
@ -18,6 +18,7 @@ bool StateManager::initialize(AppState& app) {
|
||||
// Load data from the last active slot.
|
||||
return loadData(app, app.selected_save_slot);
|
||||
} else {
|
||||
// TODO: save default state to all save slots.
|
||||
reset(app);
|
||||
return false;
|
||||
}
|
||||
@ -49,7 +50,7 @@ bool StateManager::loadData(AppState& app, byte slot_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_shuffle);
|
||||
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<CvDestination>(saved_ch_state.cv1_dest));
|
||||
@ -59,21 +60,22 @@ bool StateManager::loadData(AppState& app, byte slot_index) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void StateManager::saveData(const AppState& app, byte slot_index) {
|
||||
if (slot_index >= MAX_SAVE_SLOTS) return;
|
||||
void StateManager::saveData(const AppState& app) {
|
||||
if (app.selected_save_slot >= MAX_SAVE_SLOTS) return;
|
||||
|
||||
_save(app, slot_index);
|
||||
_save(app);
|
||||
_isDirty = false;
|
||||
}
|
||||
|
||||
void StateManager::update(const AppState& app) {
|
||||
if (_isDirty && (millis() - _lastChangeTime > SAVE_DELAY_MS)) {
|
||||
_save(app, app.selected_save_slot);
|
||||
_save(app);
|
||||
_isDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
void StateManager::reset(AppState& app) {
|
||||
noInterrupts();
|
||||
app.tempo = Clock::DEFAULT_TEMPO;
|
||||
app.encoder_reversed = false;
|
||||
app.selected_param = 0;
|
||||
@ -87,6 +89,7 @@ void StateManager::reset(AppState& app) {
|
||||
}
|
||||
|
||||
// TODO: Should this overwrite save slot 0?
|
||||
interrupts();
|
||||
|
||||
_isDirty = false;
|
||||
}
|
||||
@ -104,15 +107,15 @@ bool StateManager::_isDataValid() {
|
||||
return name_match && version_match;
|
||||
}
|
||||
|
||||
void StateManager::_save(const AppState& app, byte slot_index) {
|
||||
void StateManager::_save(const AppState& app) {
|
||||
noInterrupts();
|
||||
_saveState(app, slot_index);
|
||||
_saveMetadata(slot_index);
|
||||
_saveState(app);
|
||||
_saveMetadata(app.selected_save_slot);
|
||||
interrupts();
|
||||
}
|
||||
|
||||
void StateManager::_saveState(const AppState& app, byte slot_index) {
|
||||
if (slot_index >= MAX_SAVE_SLOTS) return;
|
||||
void StateManager::_saveState(const AppState& app) {
|
||||
if (app.selected_save_slot >= MAX_SAVE_SLOTS) return;
|
||||
|
||||
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_source = static_cast<byte>(app.selected_source);
|
||||
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++) {
|
||||
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_duty_cycle = ch.getDutyCycle(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_hits = ch.getHits(false);
|
||||
save_ch.cv1_dest = static_cast<byte>(ch.getCv1Dest());
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@ -9,10 +9,10 @@ struct AppState;
|
||||
|
||||
// Define the constants for the current firmware.
|
||||
const char SKETCH_NAME[] = "Gravity";
|
||||
const byte SKETCH_VERSION = 6;
|
||||
const byte SKETCH_VERSION = 7;
|
||||
|
||||
// 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.
|
||||
static const unsigned long SAVE_DELAY_MS = 2000;
|
||||
@ -28,8 +28,8 @@ class StateManager {
|
||||
bool initialize(AppState& app);
|
||||
// Load data from specified slot.
|
||||
bool loadData(AppState& app, byte slot_index);
|
||||
// Save data to specified slot.
|
||||
void saveData(const AppState& app, byte slot_index);
|
||||
// Save data to slot defined by app.
|
||||
void saveData(const AppState& app);
|
||||
// Reset AppState instance back to default values.
|
||||
void reset(AppState& app);
|
||||
// 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_duty_cycle;
|
||||
byte base_offset;
|
||||
byte base_shuffle;
|
||||
byte base_swing;
|
||||
byte base_euc_steps;
|
||||
byte base_euc_hits;
|
||||
byte cv1_dest; // Cast the CvDestination enum as a byte for storage
|
||||
@ -63,12 +63,13 @@ class StateManager {
|
||||
byte selected_channel;
|
||||
byte selected_source;
|
||||
byte selected_pulse;
|
||||
byte selected_save_slot;
|
||||
ChannelState channel_data[Gravity::OUTPUT_COUNT];
|
||||
};
|
||||
private:
|
||||
bool _isDataValid();
|
||||
void _save(const AppState& app, byte slot_index);
|
||||
void _saveState(const AppState& app, byte slot_index);
|
||||
void _save(const AppState& app);
|
||||
void _saveState(const AppState& app);
|
||||
void _saveMetadata(byte active_slot);
|
||||
|
||||
bool _isDirty;
|
||||
|
||||
Reference in New Issue
Block a user