Need help getting my submenu to function like my main menu

Hi all! Ive been working on this for a week and just can't get things to work. I have a Teensy 4.1, encoder, and tft screen. The audio part works, I just need to get the submenu to work like the main menu does. I can scroll and edit values in the main menu. So far im only working on the first submenu. I can enter it, but I can't edit values or return to the main menu. The main menu has 5 functions to operate - draw value,
adjustValue, redrawValue, redrawMenuItem, and drawMenu. I have duplicated these for the submenu. I also have a handle encoder and handle button code. Any suggestions would be greatly appreciated!

#include "OpenAudio_ArduinoLibrary.h"
#include "AudioStream_F32.h"
#include "TD_filterIIR.h"
#include <Audio.h>
#include <ILI9341_t3n.h>
#include <Encoder.h>
#include <Bounce.h>
#include <EEPROM.h>
#include <SPI.h>
#include "ili9341_t3n_font_Arial.h"
#include "ili9341_t3n_font_ArialBold.h"
#include <effect_dynamics_F32.h>
#include <effect_platervbstereo.h>

AudioInputI2S_F32 input1;
AudioAnalyzePeak_F32 peak1;
AudioAnalyzePeak_F32 peak2;
filterIIR hp1;
filterIIR eq1;
filterIIR hp2;
filterIIR eq2;
AudioEffectDynamics_F32 dynamics1;
AudioEffectDynamics_F32 dynamics2;
AudioConvert_F32toI16 convert1;
AudioConvert_F32toI16 convert2;
AudioConvert_I16toF32 convert3;
AudioConvert_I16toF32 convert4;
AudioEffectPlateReverb reverb;
AudioMixer4_F32 volume;
AudioMixer4_F32 reverb_send;
AudioOutputI2S_F32 output1;



AudioConnection_F32 patchCord1(input1, 0, peak1, 0);
AudioConnection_F32 patchCord2(input1, 1, peak2, 0);
AudioConnection_F32 patchCord3(input1, 0, hp1, 0);
AudioConnection_F32 patchCord4(input1, 1, hp2, 0);
AudioConnection_F32 patchCord5(hp1, 0, eq1, 0);
AudioConnection_F32 patchCord6(hp2, 0, eq2, 0);
AudioConnection_F32 patchCord7(eq1, 0, dynamics1, 0);
AudioConnection_F32 patchCord8(eq2, 0, dynamics2, 0);
AudioConnection_F32 patchCord9(dynamics1, 0, volume, 0);
AudioConnection_F32 patchCord10(dynamics2, 0, volume, 1);
AudioConnection_F32 patchCord11(dynamics1, 0, reverb_send, 0);
AudioConnection_F32 patchCord12(dynamics2, 0, reverb_send, 1);
AudioConnection_F32 patchCord13(reverb_send, 0, convert1, 0);
AudioConnection_F32 patchCord14(reverb_send, 0, convert2, 0);
AudioConnection patchCord15(convert1, 0, reverb, 0);
AudioConnection patchCord16(convert2, 0, reverb, 1);
AudioConnection patchCord17(reverb, 0, convert3, 0);
AudioConnection patchCord18(reverb, 1, convert4, 0);
AudioConnection_F32 patchCord19(convert3, 0, volume, 2);
AudioConnection_F32 patchCord20(convert4, 0, volume, 3);
AudioConnection_F32 patchCord21(volume, 0, output1, 0);
AudioConnection_F32 patchCord22(volume, 0, output1, 1);
AudioControlSGTL5000 codec;


#define TFT_DC 9
#define TFT_CS 10
#define ENCODER_A_PIN 5
#define ENCODER_B_PIN 6
#define ENCODER_BTN_PIN A0
ILI9341_t3n tft = ILI9341_t3n(TFT_CS, TFT_DC);

Encoder encoder(ENCODER_A_PIN, ENCODER_B_PIN);
Bounce encoderButton = Bounce(ENCODER_BTN_PIN, 10);

// Main Menu items
const char* menuItems[] = { "Mic Volume", "Guitar Volume", "Mic Reverb", "Guitar Reverb",
                                "Mic EQ", "Guitar EQ", "Mic Compressor",
                                "Guitar Compressor", "Reverb Settings", "Input Levels" };
const int menuItemCount = sizeof(menuItems) / sizeof(menuItems[0]);

int currentSelectedItem = 0;

enum Mode {
    MAIN_MENU,
    SUB_MENU,
    EDIT_MODE,
    SUB_MENU_EDIT_MODE
};

Mode currentMode = MAIN_MENU;


struct Menu {
    const char** items;
    int itemCount;
    int currentItem;
    const char* title;
    bool isMain;
    int selected;
    void (*drawFunction)();
};

Menu mainMenu;
// Initialize the Mic EQ menu
const char* micEqMenuItems[] = {"Low Cut", "Low Freq", "Low Q", "Low Gain", "High Freq", "High Q", "High Gain", "Back"};

Menu micEqMenu = {
    micEqMenuItems,                       // Items
    sizeof(micEqMenuItems) / sizeof(char*), // Item count
    0,                                    // Initial selected item
    "Mic EQ",                             // Title
    false,                                // Not a main menu
    0,                                    // Initial selected item for submenu
    nullptr                               // Draw function (if any)
};




// EEPROM addresses for each setting
const int EEPROM_ADDR_MIC_VOLUME = 0;
const int EEPROM_ADDR_GUITAR_VOLUME = EEPROM_ADDR_MIC_VOLUME + sizeof(float);
const int EEPROM_ADDR_MIC_REVERB = EEPROM_ADDR_GUITAR_VOLUME + sizeof(float);
const int EEPROM_ADDR_GUITAR_REVERB = EEPROM_ADDR_MIC_REVERB + sizeof(float);

const int EEPROM_ADDR_MIC_IN_LVL = EEPROM_ADDR_GUITAR_REVERB + sizeof(float);
const int EEPROM_ADDR_GUITAR_IN_LVL = EEPROM_ADDR_MIC_IN_LVL + sizeof(float);
const int EEPROM_ADDR_OUTPUT_LVL = EEPROM_ADDR_GUITAR_IN_LVL + sizeof(float);

const int EEPROM_ADDR_HP1_LOWCUT = EEPROM_ADDR_OUTPUT_LVL + sizeof(float);
const int EEPROM_ADDR_HP2_LOWCUT = EEPROM_ADDR_HP1_LOWCUT + sizeof(float);

const int EEPROM_ADDR_EQ1_LOWF = EEPROM_ADDR_HP2_LOWCUT + sizeof(float);
const int EEPROM_ADDR_EQ1_LOWQ = EEPROM_ADDR_EQ1_LOWF + sizeof(float);
const int EEPROM_ADDR_EQ1_LOWGAIN = EEPROM_ADDR_EQ1_LOWQ + sizeof(float);

const int EEPROM_ADDR_EQ1_MEDF = EEPROM_ADDR_EQ1_LOWGAIN + sizeof(float);
const int EEPROM_ADDR_EQ1_MEDQ = EEPROM_ADDR_EQ1_MEDF + sizeof(float);
const int EEPROM_ADDR_EQ1_MEDGAIN = EEPROM_ADDR_EQ1_MEDQ + sizeof(float);

const int EEPROM_ADDR_EQ1_HIGHF = EEPROM_ADDR_EQ1_MEDGAIN + sizeof(float);
const int EEPROM_ADDR_EQ1_HIGHQ = EEPROM_ADDR_EQ1_HIGHF + sizeof(float);
const int EEPROM_ADDR_EQ1_HIGHGAIN = EEPROM_ADDR_EQ1_HIGHQ + sizeof(float);

const int EEPROM_ADDR_EQ2_LOWF = EEPROM_ADDR_EQ1_HIGHGAIN + sizeof(float);
const int EEPROM_ADDR_EQ2_LOWQ = EEPROM_ADDR_EQ2_LOWF + sizeof(float);
const int EEPROM_ADDR_EQ2_LOWGAIN = EEPROM_ADDR_EQ2_LOWQ + sizeof(float);

const int EEPROM_ADDR_EQ2_MEDF = EEPROM_ADDR_EQ2_LOWGAIN + sizeof(float);
const int EEPROM_ADDR_EQ2_MEDQ = EEPROM_ADDR_EQ2_MEDF + sizeof(float);
const int EEPROM_ADDR_EQ2_MEDGAIN = EEPROM_ADDR_EQ2_MEDQ + sizeof(float);

const int EEPROM_ADDR_EQ2_HIGHF = EEPROM_ADDR_EQ2_MEDGAIN + sizeof(float);
const int EEPROM_ADDR_EQ2_HIGHQ = EEPROM_ADDR_EQ2_HIGHF + sizeof(float);
const int EEPROM_ADDR_EQ2_HIGHGAIN = EEPROM_ADDR_EQ2_HIGHQ + sizeof(float);
const int EEPROM_ADDR_GATE_THRESHOLD1 = EEPROM_ADDR_EQ2_HIGHGAIN + sizeof(float);
const int EEPROM_ADDR_GATE_ATTACK1 = EEPROM_ADDR_GATE_THRESHOLD1 + sizeof(float);
const int EEPROM_ADDR_GATE_RELEASE1 = EEPROM_ADDR_GATE_ATTACK1 + sizeof(float);
const int EEPROM_ADDR_GATE_HYSTERESIS1 = EEPROM_ADDR_GATE_RELEASE1 + sizeof(float);
const int EEPROM_ADDR_GATE_ATTENUATION1 = EEPROM_ADDR_GATE_HYSTERESIS1 + sizeof(float);

const int EEPROM_ADDR_GATE_THRESHOLD2 = EEPROM_ADDR_GATE_ATTENUATION1 + sizeof(float);
const int EEPROM_ADDR_GATE_ATTACK2 = EEPROM_ADDR_GATE_THRESHOLD2 + sizeof(float);
const int EEPROM_ADDR_GATE_RELEASE2 = EEPROM_ADDR_GATE_ATTACK2 + sizeof(float);
const int EEPROM_ADDR_GATE_HYSTERESIS2 = EEPROM_ADDR_GATE_RELEASE2 + sizeof(float);
const int EEPROM_ADDR_GATE_ATTENUATION2 = EEPROM_ADDR_GATE_HYSTERESIS2 + sizeof(float);

const int EEPROM_ADDR_COMP_THRESHOLD1 = EEPROM_ADDR_GATE_ATTENUATION2 + sizeof(float);
const int EEPROM_ADDR_COMP_ATTACK1 = EEPROM_ADDR_COMP_THRESHOLD1 + sizeof(float);
const int EEPROM_ADDR_COMP_RELEASE1 = EEPROM_ADDR_COMP_ATTACK1 + sizeof(float);
const int EEPROM_ADDR_COMP_RATIO1 = EEPROM_ADDR_COMP_RELEASE1 + sizeof(float);
const int EEPROM_ADDR_COMP_KNEE_WIDTH1 = EEPROM_ADDR_COMP_RATIO1 + sizeof(float);

const int EEPROM_ADDR_COMP_THRESHOLD2 = EEPROM_ADDR_COMP_KNEE_WIDTH1 + sizeof(float);
const int EEPROM_ADDR_COMP_ATTACK2 = EEPROM_ADDR_COMP_THRESHOLD2 + sizeof(float);
const int EEPROM_ADDR_COMP_RELEASE2 = EEPROM_ADDR_COMP_ATTACK2 + sizeof(float);
const int EEPROM_ADDR_COMP_RATIO2 = EEPROM_ADDR_COMP_RELEASE2 + sizeof(float);
const int EEPROM_ADDR_COMP_KNEE_WIDTH2 = EEPROM_ADDR_COMP_RATIO2 + sizeof(float);

const int EEPROM_ADDR_LIM_THRESHOLD1 = EEPROM_ADDR_COMP_KNEE_WIDTH2 + sizeof(float);
const int EEPROM_ADDR_LIM_ATTACK1 = EEPROM_ADDR_LIM_THRESHOLD1 + sizeof(float);
const int EEPROM_ADDR_LIM_RELEASE1 = EEPROM_ADDR_LIM_ATTACK1 + sizeof(float);

const int EEPROM_ADDR_LIM_THRESHOLD2 = EEPROM_ADDR_LIM_RELEASE1 + sizeof(float);
const int EEPROM_ADDR_LIM_ATTACK2 = EEPROM_ADDR_LIM_THRESHOLD2 + sizeof(float);
const int EEPROM_ADDR_LIM_RELEASE2 = EEPROM_ADDR_LIM_ATTACK2 + sizeof(float);

const int EEPROM_ADDR_MAKEUP_GAIN1 = EEPROM_ADDR_LIM_RELEASE2 + sizeof(float);
const int EEPROM_ADDR_MAKEUP_GAIN2 = EEPROM_ADDR_MAKEUP_GAIN1 + sizeof(float);

const int EEPROM_ADDR_REVERB_SIZE = EEPROM_ADDR_MAKEUP_GAIN2 + sizeof(float);
const int EEPROM_ADDR_REVERB_LOWPASS = EEPROM_ADDR_REVERB_SIZE + sizeof(float);
const int EEPROM_ADDR_REVERB_LODAMP = EEPROM_ADDR_REVERB_LOWPASS + sizeof(float);
const int EEPROM_ADDR_REVERB_HIDAMP = EEPROM_ADDR_REVERB_LODAMP + sizeof(float);
const int EEPROM_ADDR_REVERB_DIFFUSION = EEPROM_ADDR_REVERB_HIDAMP + sizeof(float);
const int EEPROM_ADDR_REVERB_MIC_SEND = EEPROM_ADDR_REVERB_DIFFUSION + sizeof(float);
const int EEPROM_ADDR_REVERB_GUITAR_SEND = EEPROM_ADDR_REVERB_MIC_SEND + sizeof(float);


// Variables for editable values
float micVolume = 0.5;
float guitarVolume = 0.5;
float micReverb = 0.5;
float guitarReverb = 0.5;


float micInLvl = 5;     // 5: 1.33v p-p(default), 0: 3.12v, 15: 0.24v
float guitarInLvl = 5;  // 5: 1.33v p-p(default), 0: 3.12v, 15: 0.24v
float outPutLvl = 29;   //29: 1.29v p-p(default), 13: 3.16v, 31: 1.16v


float hp1_lowcut = 80;
float hp2_lowcut = 80;

float eq1_lowf = 250;
float eq1_lowq = 1.0;
float eq1_lowgain = 0.0;

float eq1_medf = 2500;
float eq1_medq = 1.0;
float eq1_medgain = 0.0;

float eq1_highf = 8000;
float eq1_highq = 1.0;
float eq1_highgain = 0.0;


float eq2_lowf = 250;
float eq2_lowq = 3.0;
float eq2_lowgain = 0.0;

float eq2_medf = 2500;
float eq2_medq = 1.0;
float eq2_medgain = 0.0;

float eq2_highf = 8000;
float eq2_highq = 1.0;
float eq2_highgain = 0.0;

float gateThreshold1 = -50.0f;
float gateAttack1 = 0.01f;
float gateRelease1 = 0.1f;
float gateHysteresis1 = 6.0f;
float gateAttenuation1 = -12.0f;

float gateThreshold2 = -50.0f;
float gateAttack2 = 0.01f;
float gateRelease2 = 0.1f;
float gateHysteresis2 = 6.0f;
float gateAttenuation2 = -12.0f;

float compThreshold1 = -10.0f;
float compAttack1 = 0.1f;
float compRelease1 = 0.2f;
float compRatio1 = 4.0f;
float compKneeWidth1 = 6.0f;

float compThreshold2 = -10.0f;
float compAttack2 = 0.1f;
float compRelease2 = 0.2f;
float compRatio2 = 4.0f;
float compKneeWidth2 = 6.0f;

float limThreshold1 = -0.1f;
float limAttack1 = 0.01f;
float limRelease1 = 0.1f;

float limThreshold2 = -0.1f;
float limAttack2 = 0.01f;
float limRelease2 = 0.1f;

float makeupGain1 = 6.0f;
float makeupGain2 = 6.0f;

float reverbSize = 0.7f;
float reverbLowpass = 1.0f;
float reverbLodamp = 0.4f;
float reverbHidamp = 1.0f;
float reverbDiffusion = 1.0f;
float reverbMicSend = 0.5f;
float reverbGuitarSend = 0.5f;


void saveSettingsToEEPROM() {
  // Save each setting
  EEPROM.put(EEPROM_ADDR_MIC_VOLUME, micVolume);
  EEPROM.put(EEPROM_ADDR_GUITAR_VOLUME, guitarVolume);
  EEPROM.put(EEPROM_ADDR_MIC_REVERB, micReverb);
  EEPROM.put(EEPROM_ADDR_GUITAR_REVERB, guitarReverb);

  EEPROM.put(EEPROM_ADDR_MIC_IN_LVL, micInLvl);
  EEPROM.put(EEPROM_ADDR_GUITAR_IN_LVL, guitarInLvl);
  EEPROM.put(EEPROM_ADDR_OUTPUT_LVL, outPutLvl);

  EEPROM.put(EEPROM_ADDR_HP1_LOWCUT, hp1_lowcut);
  EEPROM.put(EEPROM_ADDR_HP2_LOWCUT, hp2_lowcut);

  EEPROM.put(EEPROM_ADDR_EQ1_LOWF, eq1_lowf);
  EEPROM.put(EEPROM_ADDR_EQ1_LOWQ, eq1_lowq);
  EEPROM.put(EEPROM_ADDR_EQ1_LOWGAIN, eq1_lowgain);
  EEPROM.put(EEPROM_ADDR_EQ1_MEDF, eq1_medf);
  EEPROM.put(EEPROM_ADDR_EQ1_MEDQ, eq1_medq);
  EEPROM.put(EEPROM_ADDR_EQ1_MEDGAIN, eq1_medgain);
  EEPROM.put(EEPROM_ADDR_EQ1_HIGHF, eq1_highf);
  EEPROM.put(EEPROM_ADDR_EQ1_HIGHQ, eq1_highq);
  EEPROM.put(EEPROM_ADDR_EQ1_HIGHGAIN, eq1_highgain);

  EEPROM.put(EEPROM_ADDR_EQ2_LOWF, eq2_lowf);
  EEPROM.put(EEPROM_ADDR_EQ2_LOWQ, eq2_lowq);
  EEPROM.put(EEPROM_ADDR_EQ2_LOWGAIN, eq2_lowgain);
  EEPROM.put(EEPROM_ADDR_EQ2_MEDF, eq2_medf);
  EEPROM.put(EEPROM_ADDR_EQ2_MEDQ, eq2_medq);
  EEPROM.put(EEPROM_ADDR_EQ2_MEDGAIN, eq2_medgain);
  EEPROM.put(EEPROM_ADDR_EQ2_HIGHF, eq2_highf);
  EEPROM.put(EEPROM_ADDR_EQ2_HIGHQ, eq2_highq);
  EEPROM.put(EEPROM_ADDR_EQ2_HIGHGAIN, eq2_highgain);

  EEPROM.put(EEPROM_ADDR_GATE_THRESHOLD1, gateThreshold1);
  EEPROM.put(EEPROM_ADDR_GATE_ATTACK1, gateAttack1);
  EEPROM.put(EEPROM_ADDR_GATE_RELEASE1, gateRelease1);
  EEPROM.put(EEPROM_ADDR_GATE_HYSTERESIS1, gateHysteresis1);
  EEPROM.put(EEPROM_ADDR_GATE_ATTENUATION1, gateAttenuation1);

  EEPROM.put(EEPROM_ADDR_GATE_THRESHOLD2, gateThreshold2);
  EEPROM.put(EEPROM_ADDR_GATE_ATTACK2, gateAttack2);
  EEPROM.put(EEPROM_ADDR_GATE_RELEASE2, gateRelease2);
  EEPROM.put(EEPROM_ADDR_GATE_HYSTERESIS2, gateHysteresis2);
  EEPROM.put(EEPROM_ADDR_GATE_ATTENUATION2, gateAttenuation2);

  EEPROM.put(EEPROM_ADDR_COMP_THRESHOLD1, compThreshold1);
  EEPROM.put(EEPROM_ADDR_COMP_ATTACK1, compAttack1);
  EEPROM.put(EEPROM_ADDR_COMP_RELEASE1, compRelease1);
  EEPROM.put(EEPROM_ADDR_COMP_RATIO1, compRatio1);
  EEPROM.put(EEPROM_ADDR_COMP_KNEE_WIDTH1, compKneeWidth1);

  EEPROM.put(EEPROM_ADDR_COMP_THRESHOLD2, compThreshold2);
  EEPROM.put(EEPROM_ADDR_COMP_ATTACK2, compAttack2);
  EEPROM.put(EEPROM_ADDR_COMP_RELEASE2, compRelease2);
  EEPROM.put(EEPROM_ADDR_COMP_RATIO2, compRatio2);
  EEPROM.put(EEPROM_ADDR_COMP_KNEE_WIDTH2, compKneeWidth2);

  EEPROM.put(EEPROM_ADDR_LIM_THRESHOLD1, limThreshold1);
  EEPROM.put(EEPROM_ADDR_LIM_ATTACK1, limAttack1);
  EEPROM.put(EEPROM_ADDR_LIM_RELEASE1, limRelease1);

  EEPROM.put(EEPROM_ADDR_LIM_THRESHOLD2, limThreshold2);
  EEPROM.put(EEPROM_ADDR_LIM_ATTACK2, limAttack2);
  EEPROM.put(EEPROM_ADDR_LIM_RELEASE2, limRelease2);

  EEPROM.put(EEPROM_ADDR_MAKEUP_GAIN1, makeupGain1);
  EEPROM.put(EEPROM_ADDR_MAKEUP_GAIN2, makeupGain2);

  EEPROM.put(EEPROM_ADDR_REVERB_SIZE, reverbSize);
  EEPROM.put(EEPROM_ADDR_REVERB_LOWPASS, reverbLowpass);
  EEPROM.put(EEPROM_ADDR_REVERB_LODAMP, reverbLodamp);
  EEPROM.put(EEPROM_ADDR_REVERB_HIDAMP, reverbHidamp);
  EEPROM.put(EEPROM_ADDR_REVERB_DIFFUSION, reverbDiffusion);
}

void loadSettingsFromEEPROM() {
  EEPROM.get(EEPROM_ADDR_MIC_VOLUME, micVolume);
  EEPROM.get(EEPROM_ADDR_GUITAR_VOLUME, guitarVolume);
  EEPROM.get(EEPROM_ADDR_MIC_REVERB, micReverb);
  EEPROM.get(EEPROM_ADDR_GUITAR_REVERB, guitarReverb);
  EEPROM.get(EEPROM_ADDR_MIC_IN_LVL, micInLvl);
  EEPROM.get(EEPROM_ADDR_GUITAR_IN_LVL, guitarInLvl);
  EEPROM.get(EEPROM_ADDR_OUTPUT_LVL, outPutLvl);
  EEPROM.get(EEPROM_ADDR_HP1_LOWCUT, hp1_lowcut);
  EEPROM.get(EEPROM_ADDR_HP2_LOWCUT, hp2_lowcut);
  EEPROM.get(EEPROM_ADDR_EQ1_LOWF, eq1_lowf);
  EEPROM.get(EEPROM_ADDR_EQ1_LOWQ, eq1_lowq);
  EEPROM.get(EEPROM_ADDR_EQ1_LOWGAIN, eq1_lowgain);
  EEPROM.get(EEPROM_ADDR_EQ1_MEDF, eq1_medf);
  EEPROM.get(EEPROM_ADDR_EQ1_MEDQ, eq1_medq);
  EEPROM.get(EEPROM_ADDR_EQ1_MEDGAIN, eq1_medgain);
  EEPROM.get(EEPROM_ADDR_EQ1_HIGHF, eq1_highf);
  EEPROM.get(EEPROM_ADDR_EQ1_HIGHQ, eq1_highq);
  EEPROM.get(EEPROM_ADDR_EQ1_HIGHGAIN, eq1_highgain);
  EEPROM.get(EEPROM_ADDR_EQ2_LOWF, eq2_lowf);
  EEPROM.get(EEPROM_ADDR_EQ2_LOWQ, eq2_lowq);
  EEPROM.get(EEPROM_ADDR_EQ2_LOWGAIN, eq2_lowgain);
  EEPROM.get(EEPROM_ADDR_EQ2_MEDF, eq2_medf);
  EEPROM.get(EEPROM_ADDR_EQ2_MEDQ, eq2_medq);
  EEPROM.get(EEPROM_ADDR_EQ2_MEDGAIN, eq2_medgain);
  EEPROM.get(EEPROM_ADDR_EQ2_HIGHF, eq2_highf);
  EEPROM.get(EEPROM_ADDR_EQ2_HIGHQ, eq2_highq);
  EEPROM.get(EEPROM_ADDR_EQ2_HIGHGAIN, eq2_highgain);
  EEPROM.get(EEPROM_ADDR_GATE_THRESHOLD1, gateThreshold1);
  EEPROM.get(EEPROM_ADDR_GATE_ATTACK1, gateAttack1);
  EEPROM.get(EEPROM_ADDR_GATE_RELEASE1, gateRelease1);
  EEPROM.get(EEPROM_ADDR_GATE_HYSTERESIS1, gateHysteresis1);
  EEPROM.get(EEPROM_ADDR_GATE_ATTENUATION1, gateAttenuation1);

  EEPROM.get(EEPROM_ADDR_COMP_THRESHOLD1, compThreshold1);
  EEPROM.get(EEPROM_ADDR_COMP_ATTACK1, compAttack1);
  EEPROM.get(EEPROM_ADDR_COMP_RELEASE1, compRelease1);
  EEPROM.get(EEPROM_ADDR_COMP_RATIO1, compRatio1);
  EEPROM.get(EEPROM_ADDR_COMP_KNEE_WIDTH1, compKneeWidth1);

  EEPROM.get(EEPROM_ADDR_LIM_THRESHOLD1, limThreshold1);
  EEPROM.get(EEPROM_ADDR_LIM_ATTACK1, limAttack1);
  EEPROM.get(EEPROM_ADDR_LIM_RELEASE1, limRelease1);

  EEPROM.get(EEPROM_ADDR_MAKEUP_GAIN1, makeupGain1);

  EEPROM.get(EEPROM_ADDR_GATE_THRESHOLD2, gateThreshold2);
  EEPROM.get(EEPROM_ADDR_GATE_ATTACK2, gateAttack2);
  EEPROM.get(EEPROM_ADDR_GATE_RELEASE2, gateRelease2);
  EEPROM.get(EEPROM_ADDR_GATE_HYSTERESIS2, gateHysteresis2);
  EEPROM.get(EEPROM_ADDR_GATE_ATTENUATION2, gateAttenuation2);

  EEPROM.get(EEPROM_ADDR_COMP_THRESHOLD2, compThreshold2);
  EEPROM.get(EEPROM_ADDR_COMP_ATTACK2, compAttack2);
  EEPROM.get(EEPROM_ADDR_COMP_RELEASE2, compRelease2);
  EEPROM.get(EEPROM_ADDR_COMP_RATIO2, compRatio2);
  EEPROM.get(EEPROM_ADDR_COMP_KNEE_WIDTH2, compKneeWidth2);

  EEPROM.get(EEPROM_ADDR_LIM_THRESHOLD2, limThreshold2);
  EEPROM.get(EEPROM_ADDR_LIM_ATTACK2, limAttack2);
  EEPROM.get(EEPROM_ADDR_LIM_RELEASE2, limRelease2);

  EEPROM.get(EEPROM_ADDR_MAKEUP_GAIN2, makeupGain2);

  EEPROM.get(EEPROM_ADDR_REVERB_SIZE, reverbSize);
  EEPROM.get(EEPROM_ADDR_REVERB_LOWPASS, reverbLowpass);
  EEPROM.get(EEPROM_ADDR_REVERB_LODAMP, reverbLodamp);
  EEPROM.get(EEPROM_ADDR_REVERB_HIDAMP, reverbHidamp);
  EEPROM.get(EEPROM_ADDR_REVERB_DIFFUSION, reverbDiffusion);
  EEPROM.get(EEPROM_ADDR_REVERB_MIC_SEND, reverbMicSend);
  EEPROM.get(EEPROM_ADDR_REVERB_GUITAR_SEND, reverbGuitarSend);
}

void setup() {

  AudioMemory(6);
  AudioMemory_F32(12);


  codec.enable();
  codec.inputSelect(AUDIO_INPUT_LINEIN);
  codec.adcHighPassFilterDisable();
  codec.lineInLevel(micInLvl, guitarInLvl);  // 5: 1.33v p-p(default), 0: 3.12v, 15: 0.24v
  codec.volume(0.6);                         //headphone volume 0 - 1.0
  codec.lineOutLevel(outPutLvl);             //29: 1.29v p-p(default), 13: 3.16v, 31: 1.16v

loadSettingsFromEEPROM();

  tft.begin();
  tft.setRotation(3); // Adjust as per your display's orientation
  drawMenuBar("Main Menu");
  
   Menu mainMenu = {
        menuItems, // The array of main menu item strings
        menuItemCount, // The count of main menu items
        0, // Initial selected item index
        "Main Menu", // Title of the menu
        true, // Indicates it's the main menu
           0,
        nullptr
    };

    drawMenu(mainMenu);


  volume.gain(0, micVolume);
  volume.gain(1, guitarVolume);
  volume.gain(2, micReverb);
  volume.gain(3, guitarReverb);

  reverb_send.gain(0, reverbMicSend);
  reverb_send.gain(1, reverbGuitarSend);


  // Apply the loaded settings to the filters
  hp1.setBiQuadEq(0, 'H', hp1_lowcut, 1.1, 0);
  hp1.setBiQuadEq(1, 'H', hp1_lowcut, 1.1, 0);
  hp2.setBiQuadEq(0, 'H', hp2_lowcut, 1.1, 0);
  hp2.setBiQuadEq(1, 'H', hp2_lowcut, 1.1, 0);


  eq1.setBiQuadEq(0, 'P', eq1_lowf, eq1_lowq, eq1_lowgain);
  eq1.setBiQuadEq(1, 'P', eq2_medf, eq2_medq, eq2_medgain);
  eq1.setBiQuadEq(2, 'P', eq2_highf, eq2_highq, eq2_highgain);

  eq2.setBiQuadEq(0, 'P', eq1_lowf, eq1_lowq, eq1_lowgain);
  eq2.setBiQuadEq(1, 'P', eq2_medf, eq2_medq, eq2_medgain);
  eq2.setBiQuadEq(2, 'P', eq2_highf, eq2_highq, eq2_highgain);


  // Gate (threshold = -50.0f, attack = 0.01f, release = 0.1f, hysterisis = 6.0f, attenuation = -12.0f);
  dynamics1.gate(gateThreshold1, gateAttack1, gateRelease1, gateHysteresis1, gateAttenuation1);

  // Compressor (threshold = -10.0f, attack = 0.1f, release = 0.2f, ratio = 4.0f, kneeWidth = 6.0f);
  dynamics1.compression(compThreshold1, compAttack1, compRelease1, compRatio1, compKneeWidth1);

  // Limiter (threshold = -0.1f, attack = 0.01f, release = 0.1f);
  dynamics1.limit(limThreshold1, limAttack1, limRelease1);

  // Auto Makeup Gain (headroom = 6.0f);
  dynamics1.autoMakeupGain(makeupGain1);


  // Gate (threshold = -50.0f, attack = 0.01f, release = 0.1f, hysterisis = 6.0f, attenuation = -12.0f);
  dynamics2.gate(gateThreshold2, gateAttack2, gateRelease2, gateHysteresis2, gateAttenuation2);

  // Compressor  (threshold = -10.0f, attack = 0.1f, release = 0.2f, ratio = 4.0f, kneeWidth = 6.0f);
  dynamics2.compression(compThreshold2, compAttack2, compRelease2, compRatio2, compKneeWidth2);

  // Limiter (threshold = -0.1f, attack = 0.01f, release = 0.1f);
  dynamics2.limit(limThreshold2, limAttack2, limRelease2);

  // Auto Makeup Gain (headroom = 6.0f);
  dynamics2.autoMakeupGain(makeupGain2);


  reverb.size(reverbSize);            // max reverb length
  reverb.lowpass(reverbLowpass);      // sets the reverb master lowpass filter
  reverb.lodamp(reverbLodamp);        // amount of low end loss in the reverb tail
  reverb.hidamp(reverbHidamp);        // amount of treble loss in the reverb tail
  reverb.diffusion(reverbDiffusion);  // 1.0 is the detault setting, lower it to create more "echoey" reverb

}

void loop() {
   handleEncoder();
   handleButton();

}

void handleEncoder() {
    static int lastEncoderPosition = 0;
    int currentEncoderPosition = encoder.read() / 4; // Adjust the divisor based on your encoder's sensitivity

    if (currentEncoderPosition != lastEncoderPosition) {
        if (currentMode == MAIN_MENU) {
            int previousSelectedItem = currentSelectedItem;
            currentSelectedItem += (currentEncoderPosition - lastEncoderPosition);

            if (currentSelectedItem < 0) {
                currentSelectedItem = 0;
            } else if (currentSelectedItem >= menuItemCount) {
                currentSelectedItem = menuItemCount - 1;
            }

            // Redraw only the previously and currently selected items
            if (previousSelectedItem != currentSelectedItem) {
                redrawMenuItem(previousSelectedItem, false);
                redrawMenuItem(currentSelectedItem, true);
            }
        } else if (currentMode == EDIT_MODE) {
            // Logic to adjust the value of the current selected item
            adjustValue(currentEncoderPosition - lastEncoderPosition);
        }
          if (currentMode == SUB_MENU) {
    int previousSubMenuItem = micEqMenu.currentItem;
    int encoderChange = currentEncoderPosition - lastEncoderPosition;

    if (encoderChange != 0) {
        micEqMenu.currentItem += encoderChange;
        micEqMenu.currentItem = constrain(micEqMenu.currentItem, 0, micEqMenu.itemCount - 1);

        // Redraw only the changed items
        if (previousSubMenuItem != micEqMenu.currentItem) {
            redrawMicEqItem(previousSubMenuItem, false);
            redrawMicEqItem(micEqMenu.currentItem, true);
        }
    }
}

        lastEncoderPosition = currentEncoderPosition;
    }
    
}

void handleButton() {
    encoderButton.update();
    if (encoderButton.fallingEdge()) {
        if (currentMode == MAIN_MENU && currentSelectedItem < 4) {
            currentMode = EDIT_MODE;
            // Immediately redraw the selected menu item with the value in blue
            drawValue(currentSelectedItem, true); // true indicates edit mode
        } 
        else if (currentSelectedItem == 4) { // Mic EQ submenu
                currentMode = SUB_MENU;
                micEqMenu.currentItem = 0; // Reset submenu selection
                drawMicEqMenu(); // Draw Mic EQ submenu
            } 
            else if (currentMode == EDIT_MODE) {
            currentMode = MAIN_MENU;
            // Save to EEPROM
            saveSettingsToEEPROM();
            // Redraw the selected menu item with the value in white
            drawValue(currentSelectedItem, false); // false indicates normal mode
        }
        else if (currentMode == SUB_MENU) {
            if (micEqMenu.currentItem == micEqMenu.itemCount - 1) { // "Back" option
                currentMode = MAIN_MENU;
                drawMenu(mainMenu);
            } else {
                currentMode = SUB_MENU_EDIT_MODE;
                drawMicEqValue(micEqMenu.currentItem, true);
            }
        } else if (currentMode == SUB_MENU_EDIT_MODE) {
            currentMode = SUB_MENU;
            // Add logic to save edited values
            drawMicEqMenu();
        }
    }
}




void drawValue(int itemIndex, bool inEditMode) {
    // Get the x, y position for the value
    int x = 200; // Adjust the X position as needed
    int y = 35 + 21 * itemIndex;

    // Clear the area for the value
    tft.fillRect(x, y, 50, 15, ILI9341_BLACK); // Adjust the size as needed

    // Set the text color based on the mode
    tft.setTextColor(inEditMode ? ILI9341_BLUE : ILI9341_WHITE);

    // Display the value
    tft.setCursor(x, y);
    switch (itemIndex) {
        case 0:
            tft.println(micVolume, 2);
            break;
        case 1:
            tft.println(guitarVolume, 2);
            break;
        case 2:
            tft.println(micReverb, 2);
            break;
        case 3:
            tft.println(guitarReverb, 2);
            break;
    }
}


void adjustValue(int encoderChange) {
    // Ensure we are editing an adjustable item
    if (currentSelectedItem < 0 || currentSelectedItem >= 4) return;

    // Adjust the value based on encoder change
    float* value;
    switch (currentSelectedItem) {
        case 0: value = &micVolume; break;
        case 1: value = &guitarVolume; break;
        case 2: value = &micReverb; break;
        case 3: value = &guitarReverb; break;
    }

    *value += encoderChange * 0.01; // Adjust the step size as necessary
    *value = constrain(*value, 0.0, 1.0); // Constrain the value between 0 and 1

    // Update the display to show the new value
    redrawValue(currentSelectedItem);
}

void redrawValue(int itemIndex) {
    tft.fillRect(200, 35 + 21 * itemIndex, 50, 15, ILI9341_BLACK); // Adjust size as needed
    tft.setTextColor(ILI9341_BLUE); // Color for edit mode
    tft.setCursor(200, 35 + 21 * itemIndex); // Adjust position as needed
    switch (itemIndex) {
        case 0: tft.println(micVolume, 2); break;
        case 1: tft.println(guitarVolume, 2); break;
        case 2: tft.println(micReverb, 2); break;
        case 3: tft.println(guitarReverb, 2); break;
    }
}


void redrawMenuItem(int itemIndex, bool highlight) {
    tft.setFont(Arial_13);
    tft.setCursor(10, 35 + 21 * itemIndex);

    if (highlight) {
        tft.setTextColor(ILI9341_BLUE);
    } else {
        tft.setTextColor(ILI9341_WHITE);
    }

    // Draw the separator line after the item
    tft.drawFastHLine(0, 52 + 21 * itemIndex, tft.width(), 0x18E3);


    tft.print(menuItems[itemIndex]);
    // Update value display if necessary
    if (itemIndex < 4) {
        tft.fillRect(200, 35 + 21 * itemIndex, 50, 15, ILI9341_BLACK);
        tft.setTextColor(ILI9341_WHITE);
        tft.setCursor(200, 35 + 21 * itemIndex);
        switch (itemIndex) {
            case 0:
                tft.println(micVolume, 2);
                break;
            case 1:
                tft.println(guitarVolume, 2);
                break;
            case 2:
                tft.println(micReverb, 2);
                break;
            case 3:
                tft.println(guitarReverb, 2);
                break;
        }
    }
}



void drawMenuBar(const char* title) {
  tft.fillScreen(ILI9341_BLACK);
  tft.setFont(Arial_16_Bold);
  tft.setTextColor(ILI9341_WHITE);
  tft.setTextSize(4);
  tft.setCursor(10, 5);
  tft.println(title);
  tft.drawFastHLine(0, 27, tft.width(), ILI9341_WHITE);
tft.drawFastHLine(0, 28, tft.width(), 0x7BEF);
tft.drawFastHLine(0, 29, tft.width(), 0x39E7);
tft.drawFastHLine(0, 30, tft.width(), ILI9341_WHITE); // Draw a line under the menu bar
}


void drawMenu(Menu menu) {
    drawMenuBar(menu.title); // Assume drawMenuBar() is adjusted to take a string title
    tft.setFont(Arial_13);
    for (int i = 0; i < menu.itemCount; ++i) {
        tft.setCursor(10, 35 + 21 * i);
        tft.setTextColor(i == menu.currentItem ? ILI9341_BLUE : ILI9341_WHITE);
        tft.print(menu.items[i]);

        // Draw separator
        tft.drawFastHLine(0, 52 + 21 * i, tft.width(), 0x18E3);

        // Special handling for items with values (first 4 items in your main menu)
        if (menu.isMain && i < 4) {
            tft.fillRect(200, 35 + 21 * i, 50, 15, ILI9341_BLACK);
            tft.setTextColor(ILI9341_WHITE);
            tft.setCursor(200, 35 + 21 * i);
            // Display the corresponding value based on the menu item
            switch (i) {
                case 0: tft.println(micVolume, 2); break;
                case 1: tft.println(guitarVolume, 2); break;
                case 2: tft.println(micReverb, 2); break;
                case 3: tft.println(guitarReverb, 2); break;
            }
        }
    }
}



void drawMicEqMenu() {
    drawMenuBar(micEqMenu.title);
    tft.setFont(Arial_13);
    
    for (int i = 0; i < micEqMenu.itemCount; ++i) {
        tft.setCursor(10, 35 + 21 * i);
        tft.setTextColor(i == micEqMenu.currentItem ? ILI9341_BLUE : ILI9341_WHITE);
        tft.print(micEqMenu.items[i]);

        // Draw separator lines
        tft.drawFastHLine(0, 52 + 21 * i, tft.width(), 0x18E3);

        // Check if this item has an associated value to display
        if (i < (micEqMenu.itemCount - 1)) { // Assuming last item is "Back", which doesn't have a value
            int x = 200; // X position for value display
            int y = 35 + 21 * i; // Y position for value display

            // Display the value for each submenu item
            tft.fillRect(x, y, 50, 15, ILI9341_BLACK); // Clear area
            tft.setTextColor(ILI9341_WHITE); // Text color
            tft.setCursor(x, y);
            switch (i) {
                case 0: tft.println(hp1_lowcut, 2); break;
                case 1: tft.println(eq1_lowf, 2); break;
                case 2: tft.println(eq1_lowq, 2); break;
                case 3: tft.println(eq1_lowgain, 2); break;
                case 4: tft.println(eq1_highf, 2); break;
                case 5: tft.println(eq1_highq, 2); break;
                case 6: tft.println(eq1_highgain, 2); break;
                
            }
        }
    }
}



//Mic Eq Menu

void drawMicEqValue(int itemIndex, bool inEditMode) {
    // Get the x, y position for the value
    int x = 200; // Adjust the X position as needed
    int y = 35 + 21 * itemIndex;

    // Clear the area for the value
    tft.fillRect(x, y, 50, 15, ILI9341_BLACK); // Adjust the size as needed

    // Set the text color based on the mode
    tft.setTextColor(inEditMode ? ILI9341_BLUE : ILI9341_WHITE);

    // Display the value
    tft.setCursor(x, y);
    switch (itemIndex) {
        case 0: tft.println(hp1_lowcut, 2); break;
        case 1: tft.println(eq1_lowf, 2); break;
        case 2: tft.println(eq1_lowq, 2); break;
        case 3: tft.println(eq1_lowgain, 2); break;
        case 4: tft.println(eq1_highf, 2); break;
        case 5: tft.println(eq1_highq, 2); break;
        case 6: tft.println(eq1_highgain, 2); break;
       
    }
}

void adjustMicEqValue(int encoderChange) {
    // Ensure we are editing an adjustable item
    if (currentSelectedItem < 0 || currentSelectedItem >= 4) return;

    // Adjust the value based on encoder change
    float* value;
    switch (currentSelectedItem) {
        case 0: value = &hp1_lowcut; break;
        case 1: value = &eq1_lowf; break;
        case 2: value = &eq1_lowq; break;
        case 3: value = &eq1_lowgain; break;
        case 4: value = &eq1_highf; break;
        case 5: value = &eq1_highq; break;
        case 6: value = &eq1_highgain; break;
    }

    *value += encoderChange * 0.01; // Adjust the step size as necessary
    *value = constrain(*value, 0.0, 1.0); // Constrain the value between 0 and 1

    // Update the display to show the new value
    redrawMicEqValue(currentSelectedItem);

}

void redrawMicEqItem(int itemIndex, bool highlight) {
    tft.setFont(Arial_13);
    tft.setCursor(10, 35 + 21 * itemIndex);

    if (highlight) {
        tft.setTextColor(ILI9341_BLUE);
    } else {
        tft.setTextColor(ILI9341_WHITE);
    }

    // Draw the separator line after the item
    tft.drawFastHLine(0, 52 + 21 * itemIndex, tft.width(), 0x18E3);


    tft.print(micEqMenu.items[itemIndex]);
    // Update value display if necessary
    if (itemIndex < 6) {
        tft.fillRect(200, 35 + 21 * itemIndex, 50, 15, ILI9341_BLACK);
        tft.setTextColor(ILI9341_WHITE);
        tft.setCursor(200, 35 + 21 * itemIndex);
        switch (itemIndex) {
        case 0: tft.println(hp1_lowcut, 2); break;
        case 1: tft.println(eq1_lowf, 2); break;
        case 2: tft.println(eq1_lowq, 2); break;
        case 3: tft.println(eq1_lowgain, 2); break;
        case 4: tft.println(eq1_highf, 2); break;
        case 5: tft.println(eq1_highq, 2); break;
        case 6: tft.println(eq1_highgain, 2); break;
            
        }
    }
}

void redrawMicEqValue(int itemIndex) {
    tft.fillRect(200, 35 + 21 * itemIndex, 50, 15, ILI9341_BLACK); // Adjust size as needed
    tft.setTextColor(ILI9341_BLUE); // Color for edit mode
    tft.setCursor(200, 35 + 21 * itemIndex); // Adjust position as needed
    switch (itemIndex) {
        case 0: tft.println(hp1_lowcut, 2); break;
        case 1: tft.println(eq1_lowf, 2); break;
        case 2: tft.println(eq1_lowq, 2); break;
        case 3: tft.println(eq1_lowgain, 2); break;
        case 4: tft.println(eq1_highf, 2); break;
        case 5: tft.println(eq1_highq, 2); break;
        case 6: tft.println(eq1_highgain, 2); break;
    }
}

Save your EEPROM life, change all the .put to .update

Save yourself a lot of work and repeated code.
Read about struct {}s, array[ ]s, and also ‘recursion’ to siimplify nested menu operations.

it's not a good advice.

Not only EEPROM.put() uses EEPROM.update() to perform the write, so does not rewrite the value if it didn't change but EEPROM.update() is only meant for a byte and the variables @jvphotog uses are of larger types.


@jvphotog
I would suggest you focus on getting the menu / submenu working in a general way outside your app and then inject that into your code.

2 Likes

Haha alright well I replaced the puts with update and then switched it back to put!

I started over with the menu and encoder code. So far I can only navigate the main menu, clicking into a submenu doesnt redraw the screen yet. But is this organization better than what I had?
I know the library im using can do double buffering but im not sure how to get that to work yet. I deleted the eeprom code so its easier to read* Thanks for the help!

#include "OpenAudio_ArduinoLibrary.h"
#include "AudioStream_F32.h"
#include "TD_filterIIR.h"
#include <Audio.h>
#include <ILI9341_t3n.h>
#include <Encoder.h>
#include <Bounce.h>
#include <EEPROM.h>
#include <SPI.h>
#include "ili9341_t3n_font_Arial.h"
#include "ili9341_t3n_font_ArialBold.h"
#include <effect_dynamics_F32.h>
#include <effect_platervbstereo.h>

AudioInputI2S_F32 input1;
AudioAnalyzePeak_F32 peak1;
AudioAnalyzePeak_F32 peak2;
filterIIR hp1;
filterIIR eq1;
filterIIR hp2;
filterIIR eq2;
AudioEffectDynamics_F32 dynamics1;
AudioEffectDynamics_F32 dynamics2;
AudioConvert_F32toI16 convert1;
AudioConvert_F32toI16 convert2;
AudioConvert_I16toF32 convert3;
AudioConvert_I16toF32 convert4;
AudioEffectPlateReverb reverb;
AudioMixer4_F32 volume;
AudioMixer4_F32 reverb_send;
AudioOutputI2S_F32 output1;



AudioConnection_F32 patchCord1(input1, 0, peak1, 0);
AudioConnection_F32 patchCord2(input1, 1, peak2, 0);
AudioConnection_F32 patchCord3(input1, 0, hp1, 0);
AudioConnection_F32 patchCord4(input1, 1, hp2, 0);
AudioConnection_F32 patchCord5(hp1, 0, eq1, 0);
AudioConnection_F32 patchCord6(hp2, 0, eq2, 0);
AudioConnection_F32 patchCord7(eq1, 0, dynamics1, 0);
AudioConnection_F32 patchCord8(eq2, 0, dynamics2, 0);
AudioConnection_F32 patchCord9(dynamics1, 0, volume, 0);
AudioConnection_F32 patchCord10(dynamics2, 0, volume, 1);
AudioConnection_F32 patchCord11(dynamics1, 0, reverb_send, 0);
AudioConnection_F32 patchCord12(dynamics2, 0, reverb_send, 1);
AudioConnection_F32 patchCord13(reverb_send, 0, convert1, 0);
AudioConnection_F32 patchCord14(reverb_send, 0, convert2, 0);
AudioConnection patchCord15(convert1, 0, reverb, 0);
AudioConnection patchCord16(convert2, 0, reverb, 1);
AudioConnection patchCord17(reverb, 0, convert3, 0);
AudioConnection patchCord18(reverb, 1, convert4, 0);
AudioConnection_F32 patchCord19(convert3, 0, volume, 2);
AudioConnection_F32 patchCord20(convert4, 0, volume, 3);
AudioConnection_F32 patchCord21(volume, 0, output1, 0);
AudioConnection_F32 patchCord22(volume, 0, output1, 1);
AudioControlSGTL5000 codec;


#define TFT_DC 9
#define TFT_CS 10
#define ENCODER_A_PIN 5
#define ENCODER_B_PIN 6
#define ENCODER_BTN_PIN A0
ILI9341_t3n tft = ILI9341_t3n(TFT_CS, TFT_DC);

Encoder encoder(ENCODER_A_PIN, ENCODER_B_PIN);
Bounce encoderButton = Bounce(ENCODER_BTN_PIN, 10);


struct Menu; 

struct MenuItem {
    const char* name;
    void (*action)();
    Menu* submenu; 
};


struct Menu {
    MenuItem* items;
    int itemCount;
};

void actionMicVolume();
void actionGuitarVolume();
void actionMicReverb();
void actionGuitarReverb();


void actionLowCut();
void actionLowFreq();

// Define the submenu items for the Mic EQ menu
MenuItem micEqMenuItems[] = {
    {"Low Cut", actionLowCut, nullptr},
    {"Low Freq", actionLowFreq, nullptr},
    
    {"Back", nullptr, nullptr} // 'Back' item to return to the main menu
};

// Define the Mic EQ menu
Menu micEqMenu = {
    micEqMenuItems,
    sizeof(micEqMenuItems) / sizeof(MenuItem)
};

MenuItem guitarEqItems[] = {
    {"Low Cut", actionLowCut, nullptr},
    {"Low Freq", actionLowFreq, nullptr},
    
    {"Back", nullptr, nullptr} // 'Back' item to return to the main menu
};

Menu guitarEqMenu = {
    guitarEqItems,
    sizeof(guitarEqItems) / sizeof(MenuItem)
};

MenuItem micCompressorItems[] = {
    {"Low Cut", actionLowCut, nullptr},
    {"Low Freq", actionLowFreq, nullptr},
    
    {"Back", nullptr, nullptr} // 'Back' item to return to the main menu
};

Menu micCompressorMenu = {
    micCompressorItems,
    sizeof(micCompressorItems) / sizeof(MenuItem)
};

MenuItem guitarCompressorItems[] = {
    {"Low Cut", actionLowCut, nullptr},
    {"Low Freq", actionLowFreq, nullptr},
    
    {"Back", nullptr, nullptr} // 'Back' item to return to the main menu
};

Menu guitarCompressorMenu = {
    guitarCompressorItems,
    sizeof(guitarCompressorItems) / sizeof(MenuItem)
};

MenuItem reverbMenuItems[] = {
    {"Low Cut", actionLowCut, nullptr},
    {"Low Freq", actionLowFreq, nullptr},
    
    {"Back", nullptr, nullptr} // 'Back' item to return to the main menu
};

Menu reverbMenu = {
    reverbMenuItems,
    sizeof(reverbMenuItems) / sizeof(MenuItem)
};

MenuItem inputLevelsItems[] = {
    {"Low Cut", actionLowCut, nullptr},
    {"Low Freq", actionLowFreq, nullptr},
    
    {"Back", nullptr, nullptr} // 'Back' item to return to the main menu
};

Menu inputLevelsMenu = {
    inputLevelsItems,
    sizeof(inputLevelsItems) / sizeof(MenuItem)
};

// Define the main menu items
MenuItem mainMenuItems[] = {
    {"Mic Volume", actionMicVolume, nullptr},
    {"Guitar Volume", actionGuitarVolume, nullptr},
    {"Mic Reverb", actionMicReverb, nullptr},
    {"Guitar Reverb", actionGuitarReverb, nullptr},
    {"Mic EQ", nullptr, &micEqMenu}, // Link to the Mic EQ submenu
    {"Guitar EQ", nullptr, &guitarEqMenu},
    {"Mic Compressor", nullptr, &micCompressorMenu},
    {"Guitar Compressor", nullptr, &guitarCompressorMenu},
    {"Reverb Settings", nullptr, &reverbMenu},
    {"Input Levels", nullptr, &inputLevelsMenu}
    
};

// Define the main menu
Menu mainMenu = {
    mainMenuItems,
    sizeof(mainMenuItems) / sizeof(MenuItem)
};

Menu* currentMenu = &mainMenu; // Start with the main menu
int selectedItemIndex = 0;

void displayMenu(Menu* menu, int selectedIndex);
// Function prototypes for actions (to be implemented later)

void actionMicVolume() {
    // Placeholder for Low Cut action
}
void actionGuitarVolume() {
    // Placeholder for Low Cut action
}
void actionMicReverb() {
    // Placeholder for Low Cut action
}
void actionGuitarReverb() {
    // Placeholder for Low Cut action
}
void actionLowCut() {
    // Placeholder for Low Cut action
}
void actionLowFreq() {
    // Placeholder for Low Frequency action
}

bool displayNeedsUpdate = true;
int previousSelectedItemIndex = 0; // Global variable to track previous menu item index

// Variables for editable values
float micVolume = 0.5;
float guitarVolume = 0.5;
float micReverb = 0.5;
float guitarReverb = 0.5;


float micInLvl = 5;     // 5: 1.33v p-p(default), 0: 3.12v, 15: 0.24v
float guitarInLvl = 5;  // 5: 1.33v p-p(default), 0: 3.12v, 15: 0.24v
float outPutLvl = 29;   //29: 1.29v p-p(default), 13: 3.16v, 31: 1.16v


float hp1_lowcut = 80;
float hp2_lowcut = 80;

float eq1_lowf = 250;
float eq1_lowq = 1.0;
float eq1_lowgain = 0.0;

float eq1_medf = 2500;
float eq1_medq = 1.0;
float eq1_medgain = 0.0;

float eq1_highf = 8000;
float eq1_highq = 1.0;
float eq1_highgain = 0.0;


float eq2_lowf = 250;
float eq2_lowq = 3.0;
float eq2_lowgain = 0.0;

float eq2_medf = 2500;
float eq2_medq = 1.0;
float eq2_medgain = 0.0;

float eq2_highf = 8000;
float eq2_highq = 1.0;
float eq2_highgain = 0.0;

float gateThreshold1 = -50.0f;
float gateAttack1 = 0.01f;
float gateRelease1 = 0.1f;
float gateHysteresis1 = 6.0f;
float gateAttenuation1 = -12.0f;

float gateThreshold2 = -50.0f;
float gateAttack2 = 0.01f;
float gateRelease2 = 0.1f;
float gateHysteresis2 = 6.0f;
float gateAttenuation2 = -12.0f;

float compThreshold1 = -10.0f;
float compAttack1 = 0.1f;
float compRelease1 = 0.2f;
float compRatio1 = 4.0f;
float compKneeWidth1 = 6.0f;

float compThreshold2 = -10.0f;
float compAttack2 = 0.1f;
float compRelease2 = 0.2f;
float compRatio2 = 4.0f;
float compKneeWidth2 = 6.0f;

float limThreshold1 = -0.1f;
float limAttack1 = 0.01f;
float limRelease1 = 0.1f;

float limThreshold2 = -0.1f;
float limAttack2 = 0.01f;
float limRelease2 = 0.1f;

float makeupGain1 = 6.0f;
float makeupGain2 = 6.0f;

float reverbSize = 0.7f;
float reverbLowpass = 1.0f;
float reverbLodamp = 0.4f;
float reverbHidamp = 1.0f;
float reverbDiffusion = 1.0f;
float reverbMicSend = 0.5f;
float reverbGuitarSend = 0.5f;



void setup() {
Serial.begin(9600);
  AudioMemory(6);
  AudioMemory_F32(12);


  codec.enable();
  codec.inputSelect(AUDIO_INPUT_LINEIN);
  codec.adcHighPassFilterDisable();
  codec.lineInLevel(micInLvl, guitarInLvl);  // 5: 1.33v p-p(default), 0: 3.12v, 15: 0.24v
  codec.volume(0.6);                         //headphone volume 0 - 1.0
  codec.lineOutLevel(outPutLvl);             //29: 1.29v p-p(default), 13: 3.16v, 31: 1.16v

loadSettingsFromEEPROM();

  tft.begin();
  tft.setRotation(3); // Adjust as per your display's orientation
  tft.fillScreen(ILI9341_BLACK);
  previousSelectedItemIndex = -1;
  displayMenu(currentMenu, selectedItemIndex);


  volume.gain(0, micVolume);
  volume.gain(1, guitarVolume);
  volume.gain(2, micReverb);
  volume.gain(3, guitarReverb);

  reverb_send.gain(0, reverbMicSend);
  reverb_send.gain(1, reverbGuitarSend);


  // Apply the loaded settings to the filters
  hp1.setBiQuadEq(0, 'H', hp1_lowcut, 1.1, 0);
  hp1.setBiQuadEq(1, 'H', hp1_lowcut, 1.1, 0);
  hp2.setBiQuadEq(0, 'H', hp2_lowcut, 1.1, 0);
  hp2.setBiQuadEq(1, 'H', hp2_lowcut, 1.1, 0);


  eq1.setBiQuadEq(0, 'P', eq1_lowf, eq1_lowq, eq1_lowgain);
  eq1.setBiQuadEq(1, 'P', eq2_medf, eq2_medq, eq2_medgain);
  eq1.setBiQuadEq(2, 'P', eq2_highf, eq2_highq, eq2_highgain);

  eq2.setBiQuadEq(0, 'P', eq1_lowf, eq1_lowq, eq1_lowgain);
  eq2.setBiQuadEq(1, 'P', eq2_medf, eq2_medq, eq2_medgain);
  eq2.setBiQuadEq(2, 'P', eq2_highf, eq2_highq, eq2_highgain);


  // Gate (threshold = -50.0f, attack = 0.01f, release = 0.1f, hysterisis = 6.0f, attenuation = -12.0f);
  dynamics1.gate(gateThreshold1, gateAttack1, gateRelease1, gateHysteresis1, gateAttenuation1);

  // Compressor (threshold = -10.0f, attack = 0.1f, release = 0.2f, ratio = 4.0f, kneeWidth = 6.0f);
  dynamics1.compression(compThreshold1, compAttack1, compRelease1, compRatio1, compKneeWidth1);

  // Limiter (threshold = -0.1f, attack = 0.01f, release = 0.1f);
  dynamics1.limit(limThreshold1, limAttack1, limRelease1);

  // Auto Makeup Gain (headroom = 6.0f);
  dynamics1.autoMakeupGain(makeupGain1);


  // Gate (threshold = -50.0f, attack = 0.01f, release = 0.1f, hysterisis = 6.0f, attenuation = -12.0f);
  dynamics2.gate(gateThreshold2, gateAttack2, gateRelease2, gateHysteresis2, gateAttenuation2);

  // Compressor  (threshold = -10.0f, attack = 0.1f, release = 0.2f, ratio = 4.0f, kneeWidth = 6.0f);
  dynamics2.compression(compThreshold2, compAttack2, compRelease2, compRatio2, compKneeWidth2);

  // Limiter (threshold = -0.1f, attack = 0.01f, release = 0.1f);
  dynamics2.limit(limThreshold2, limAttack2, limRelease2);

  // Auto Makeup Gain (headroom = 6.0f);
  dynamics2.autoMakeupGain(makeupGain2);


  reverb.size(reverbSize);            // max reverb length
  reverb.lowpass(reverbLowpass);      // sets the reverb master lowpass filter
  reverb.lodamp(reverbLodamp);        // amount of low end loss in the reverb tail
  reverb.hidamp(reverbHidamp);        // amount of treble loss in the reverb tail
  reverb.diffusion(reverbDiffusion);  // 1.0 is the detault setting, lower it to create more "echoey" reverb
}

void loop() {
    handleEncoderInput();
    handleButtonInput();
    if (displayNeedsUpdate) {
        displayMenu(currentMenu, selectedItemIndex);
        displayNeedsUpdate = false;
    }

}

void handleButtonInput() {
  encoderButton.update();
    if (encoderButton.fallingEdge()) { 
        Serial.println("Button Pressed"); // Debug statement
        MenuItem& selectedMenuItem = currentMenu->items[selectedItemIndex];
        if (selectedMenuItem.submenu != nullptr) {
            Serial.println("Navigating to Submenu"); // Debug statement
            currentMenu = selectedMenuItem.submenu; 
            selectedItemIndex = 0;
            displayNeedsUpdate = true; // Ensure display updates
        } else if (selectedMenuItem.action != nullptr) {
            selectedMenuItem.action(); 
        }
    }
}


void handleEncoderInput() {
    static int lastEncoderPosition = encoder.read();
    int currentEncoderPosition = encoder.read();
    int encoderDelta = (currentEncoderPosition - lastEncoderPosition) / 4; // Adjust for sensitivity

    if (encoderDelta != 0) {
        int itemCount = currentMenu->itemCount;
        selectedItemIndex = (selectedItemIndex + encoderDelta + itemCount) % itemCount; // Wrap-around logic

        lastEncoderPosition = currentEncoderPosition;
        displayNeedsUpdate = true; // Set flag to update display
    }
}

void displayMenu(Menu* menu, int selectedIndex) {
    if (previousSelectedItemIndex != selectedIndex || previousSelectedItemIndex == -1) {
        // Update only the necessary items
        int startIdx = (previousSelectedItemIndex == -1) ? 0 : min(selectedIndex, previousSelectedItemIndex);
        int endIdx = (previousSelectedItemIndex == -1) ? menu->itemCount - 1 : max(selectedIndex, previousSelectedItemIndex);

        for (int i = startIdx; i <= endIdx; ++i) {
            tft.setCursor(10, 35 + 21 * i);
            tft.setTextColor(i == selectedIndex ? ILI9341_BLUE : ILI9341_WHITE);
            tft.print(menu->items[i].name);
        }

        // Update the previousSelectedItemIndex
        previousSelectedItemIndex = selectedIndex;
    }
}






You still have all the audio stuff getting in the way of reading this from my iPhone…

menu/sub-menus never seems trivial
you're much better off starting small and more likely to get help

this test at the start of displayMenu() might not be good enough when you go to a submenu.

because you could be at the same selectedIndex (eg 0 for the first menu) as before when you clicked the submenu but still need the refresh since it's a different currentMenu now.

➜ probably need to also remember something like previousSelectedMenu and change the function to be

void displayMenu(Menu* menu, int selectedIndex) {
    if (previousSelectedMenu != menu || previousSelectedItemIndex != selectedIndex || previousSelectedItemIndex == -1) {
        // Update only the necessary items
        int startIdx = (previousSelectedItemIndex == -1) ? 0 : min(selectedIndex, previousSelectedItemIndex);
        int endIdx = (previousSelectedItemIndex == -1) ? menu->itemCount - 1 : max(selectedIndex, previousSelectedItemIndex);

        for (int i = startIdx; i <= endIdx; ++i) {
            tft.setCursor(10, 35 + 21 * i);
            tft.setTextColor(i == selectedIndex ? ILI9341_BLUE : ILI9341_WHITE);
            tft.print(menu->items[i].name);
        }

        // Update the previously Selected info
        previousSelectedItemIndex = selectedIndex;
        previousSelectedMenu = menu;
    }
}

(I've not check that this is refreshing the display fully correctly)

I havent defined or implemented "previousSelectedMenu" yet but I have the menu working, scrolling, and going in and out of submenus now. However the screen us flashing pretty badly with every turn of the encoder. Im trying to use double buffering but that doesnt seem to help so Im doing something wrong.

#define TFT_DC 9
#define TFT_CS 10
#define ENCODER_A_PIN 5
#define ENCODER_B_PIN 6
#define ENCODER_BTN_PIN A0
#define CENTER ILI9341_t3n::CENTER
DMAMEM uint16_t fb1[320 * 240];
DMAMEM uint16_t fb2[320 * 240];
ILI9341_t3n tft = ILI9341_t3n(TFT_CS, TFT_DC);
bool screenNeedsUpdate = true;

Encoder encoder(ENCODER_A_PIN, ENCODER_B_PIN);
Bounce encoderButton = Bounce(ENCODER_BTN_PIN, 10);

struct Menu; 
Menu* menuHistory[10]; // Stack to keep track of menu history, assuming a depth of 10 for simplicity
int menuHistoryIndex = 0;
int scrollOffset = 0;
const int maxDisplayItems = 7; 


struct MenuItem {
    const char* name;
    void (*action)();
    Menu* submenu; 
};


struct Menu {
    MenuItem* items;
    int itemCount;
};

void actionMicVolume();
void actionGuitarVolume();
void actionMicReverb();
void actionGuitarReverb();


void actionLowCut();
void actionLowFreq();
void actionLowQ();
void actionLowGain();
void actionMedFreq();
void actionMedQ();
void actionMedGain();
void actionHighFreq();
void actionHighQ();
void actionHighGain();

// Define the submenu items for the Mic EQ menu
MenuItem micEqMenuItems[] = {
    {"Low Cut", actionLowCut, nullptr},
    {"Low Freq", actionLowFreq, nullptr},
    {"Low Q", actionLowQ, nullptr},
    {"Low Gain", actionLowGain, nullptr},
    {"Med Freq", actionMedFreq, nullptr},
    {"Med Q", actionMedQ, nullptr},
    {"Med Gain", actionMedGain, nullptr},
    {"High Freq", actionHighFreq, nullptr},
    {"High Q", actionHighQ, nullptr},
    {"High Gain", actionHighGain, nullptr},
    {"Back", nullptr, nullptr} // 'Back' item to return to the main menu
};

// Define the Mic EQ menu
Menu micEqMenu = {
    micEqMenuItems,
    sizeof(micEqMenuItems) / sizeof(MenuItem)
};

MenuItem guitarEqItems[] = {
    {"Low Cut", actionLowCut, nullptr},
    {"Low Freq", actionLowFreq, nullptr},
    
    {"Back", nullptr, nullptr} // 'Back' item to return to the main menu
};

Menu guitarEqMenu = {
    guitarEqItems,
    sizeof(guitarEqItems) / sizeof(MenuItem)
};

MenuItem micCompressorItems[] = {
    {"Low Cut", actionLowCut, nullptr},
    {"Low Freq", actionLowFreq, nullptr},
    
    {"Back", nullptr, nullptr} // 'Back' item to return to the main menu
};

Menu micCompressorMenu = {
    micCompressorItems,
    sizeof(micCompressorItems) / sizeof(MenuItem)
};

MenuItem guitarCompressorItems[] = {
    {"Low Cut", actionLowCut, nullptr},
    {"Low Freq", actionLowFreq, nullptr},
    
    {"Back", nullptr, nullptr} // 'Back' item to return to the main menu
};

Menu guitarCompressorMenu = {
    guitarCompressorItems,
    sizeof(guitarCompressorItems) / sizeof(MenuItem)
};

MenuItem reverbMenuItems[] = {
    {"Low Cut", actionLowCut, nullptr},
    {"Low Freq", actionLowFreq, nullptr},
    
    {"Back", nullptr, nullptr} // 'Back' item to return to the main menu
};

Menu reverbMenu = {
    reverbMenuItems,
    sizeof(reverbMenuItems) / sizeof(MenuItem)
};

MenuItem inputLevelsItems[] = {
    {"Low Cut", actionLowCut, nullptr},
    {"Low Freq", actionLowFreq, nullptr},
    
    {"Back", nullptr, nullptr} // 'Back' item to return to the main menu
};

Menu inputLevelsMenu = {
    inputLevelsItems,
    sizeof(inputLevelsItems) / sizeof(MenuItem)
};

// Define the main menu items
MenuItem mainMenuItems[] = {
    {"Mic Volume", actionMicVolume, nullptr},
    {"Guitar Volume", actionGuitarVolume, nullptr},
    {"Mic Reverb", actionMicReverb, nullptr},
    {"Guitar Reverb", actionGuitarReverb, nullptr},
    {"Mic EQ", nullptr, &micEqMenu}, // Link to the Mic EQ submenu
    {"Guitar EQ", nullptr, &guitarEqMenu},
    {"Mic Compressor", nullptr, &micCompressorMenu},
    {"Guitar Compressor", nullptr, &guitarCompressorMenu},
    {"Reverb Settings", nullptr, &reverbMenu},
    {"Input Levels", nullptr, &inputLevelsMenu}
    
};

// Define the main menu
Menu mainMenu = {
    mainMenuItems,
    sizeof(mainMenuItems) / sizeof(MenuItem)
};

Menu* currentMenu = &mainMenu; // Start with the main menu
int selectedItemIndex = 0;

void displayMenu(Menu* menu, int selectedIndex);
// Function prototypes for actions (to be implemented later)

void actionMicVolume() {
    // Placeholder for Low Cut action
}
void actionGuitarVolume() {
    // Placeholder for Low Cut action
}
void actionMicReverb() {
    // Placeholder for Low Cut action
}
void actionGuitarReverb() {
    // Placeholder for Low Cut action
}
void actionLowCut() {
    // Placeholder for Low Cut action
    // Add your code here
}

void actionLowFreq() {
    // Placeholder for Low Frequency action
    // Add your code here
}

void actionLowQ() {
    // Placeholder for Low Q action
    // Add your code here
}

void actionLowGain() {
    // Placeholder for Low Gain action
    // Add your code here
}

void actionMedFreq() {
    // Placeholder for Medium Frequency action
    // Add your code here
}

void actionMedQ() {
    // Placeholder for Medium Q action
    // Add your code here
}

void actionMedGain() {
    // Placeholder for Medium Gain action
    // Add your code here
}

void actionHighFreq() {
    // Placeholder for High Frequency action
    // Add your code here
}

void actionHighQ() {
    // Placeholder for High Q action
    // Add your code here
}

void actionHighGain() {
    // Placeholder for High Gain action
    // Add your code here
}


bool displayNeedsUpdate = true;
int previousSelectedItemIndex = 0; // Global variable to track previous menu item index


void setup() {

  tft.begin();
  tft.setRotation(3); // Adjust as per your display's orientation
  tft.fillScreen(ILI9341_BLACK);
  tft.setFrameBuffer(fb1);
  tft.useFrameBuffer(true);
  previousSelectedItemIndex = -1;
  displayMenu(currentMenu, selectedItemIndex);


  
}

void loop() {
    handleEncoderInput();
    handleButtonInput();
    if (displayNeedsUpdate) {
        displayMenu(currentMenu, selectedItemIndex);
        displayNeedsUpdate = false;
    }

}

void handleButtonInput() {
    encoderButton.update(); // Update the state of the button

    if (encoderButton.fallingEdge()) {
        MenuItem& selectedMenuItem = currentMenu->items[selectedItemIndex];
        if (strcmp(selectedMenuItem.name, "Back") == 0) {
            if (menuHistoryIndex > 0) {
                // Navigate back to the parent menu
                menuHistoryIndex--;
                currentMenu = menuHistory[menuHistoryIndex];
                selectedItemIndex = 0;
                tft.fillScreen(ILI9341_BLACK); // Clear the screen
                previousSelectedItemIndex = -1; // Force a full redraw
                displayMenu(currentMenu, selectedItemIndex); // Redraw the menu
            }
        } else if (selectedMenuItem.submenu != nullptr) {
            // Navigate to submenu and save current menu in history
            menuHistory[menuHistoryIndex++] = currentMenu;
            currentMenu = selectedMenuItem.submenu;
            selectedItemIndex = 0;
            tft.fillScreen(ILI9341_BLACK); // Clear the screen
            previousSelectedItemIndex = -1; // Force a full redraw
            displayMenu(currentMenu, selectedItemIndex); // Redraw the menu
        } else if (selectedMenuItem.action != nullptr) {
            selectedMenuItem.action(); // Execute the action
        }
    }
}


void handleEncoderInput() {
    static int lastEncoderPosition = encoder.read();
    int currentEncoderPosition = encoder.read();
    int encoderDelta = (currentEncoderPosition - lastEncoderPosition) / 4; // Adjust for sensitivity

    if (encoderDelta != 0) {
        int itemCount = currentMenu->itemCount;
        selectedItemIndex = (selectedItemIndex + encoderDelta + itemCount) % itemCount; // Wrap-around logic

        lastEncoderPosition = currentEncoderPosition;
        displayNeedsUpdate = true; // Set flag to update display
    }
}

void displayMenu(Menu* menu, int selectedIndex) {
    if (!screenNeedsUpdate && previousSelectedItemIndex == selectedIndex) {
        return;
    }

    // Ensure the selected index is within the bounds of the menu
    selectedIndex = max(0, min(selectedIndex, menu->itemCount - 1));

    // Adjust the scroll offset based on the selected index
    if (selectedIndex >= scrollOffset + maxDisplayItems) {
        scrollOffset = selectedIndex - maxDisplayItems + 1;
    } else if (selectedIndex < scrollOffset) {
        scrollOffset = selectedIndex;
    }

    // Switch between buffers
    tft.setFrameBuffer(selectedIndex % 2 == 0 ? fb1 : fb2); 
    tft.fillScreen(ILI9341_BLACK); // Clear buffer

    // Update the displayed menu items
    for (int i = 0; i < maxDisplayItems; ++i) {
        int itemIndex = i + scrollOffset;
        if (itemIndex < menu->itemCount) {
            // Redraw the item
            tft.setCursor(10, 35 + 21 * i);
            tft.setTextColor(itemIndex == selectedIndex ? ILI9341_BLUE : ILI9341_WHITE);
            tft.print(menu->items[itemIndex].name);
        }
    }

    // Update the display with the buffer content
    tft.updateScreenAsync();

    previousSelectedItemIndex = selectedIndex;
    screenNeedsUpdate = false;
}

if it's flashing it means that you are erasing and repainting the screen when it's not necessary

I assume that’s what’s going on but don’t know how to fix it. What’s the normal way of replacing text with new text so it doesn’t draw over itself? Thanks!

I got rid of the "fillscreen" and replaced them with "fillrect" and im still getting screen flashes with every turn of the encoder!

void loop() {
    handleEncoderInput();
    handleButtonInput();
    if (displayNeedsUpdate) {
        displayMenu(currentMenu, selectedItemIndex);
        displayNeedsUpdate = false;
    }

}



void handleButtonInput() {
    encoderButton.update(); // Update the state of the button

    if (encoderButton.fallingEdge()) {
        MenuItem& selectedMenuItem = currentMenu->items[selectedItemIndex];
        if (strcmp(selectedMenuItem.name, "Back") == 0) {
            if (menuHistoryIndex > 0) {
                // Navigate back to the parent menu
                menuHistoryIndex--;
                currentMenu = menuHistory[menuHistoryIndex];
                selectedItemIndex = 0;
                
                // Clear only the necessary area instead of the whole screen
                tft.fillRect(0, 0, tft.width(), tft.height(), ILI9341_BLACK);

                previousSelectedItemIndex = -1; // Force a full redraw
                displayMenu(currentMenu, selectedItemIndex); // Redraw the menu
            }
        } else if (selectedMenuItem.submenu != nullptr) {
            // Navigate to submenu and save current menu in history
            menuHistory[menuHistoryIndex++] = currentMenu;
            currentMenu = selectedMenuItem.submenu;
            selectedItemIndex = 0;
            
            // Clear only the necessary area instead of the whole screen
            tft.fillRect(0, 0, tft.width(), tft.height(), ILI9341_BLACK);

            previousSelectedItemIndex = -1; // Force a full redraw
            displayMenu(currentMenu, selectedItemIndex); // Redraw the menu
        } else if (selectedMenuItem.action != nullptr) {
            selectedMenuItem.action(); // Execute the action
        }
    }
}






void handleEncoderInput() {
    static int lastEncoderPosition = encoder.read();
    int currentEncoderPosition = encoder.read();
    int encoderDelta = (currentEncoderPosition - lastEncoderPosition) / 4; // Adjust for sensitivity

    if (encoderDelta != 0) {
        int itemCount = currentMenu->itemCount;
        selectedItemIndex = (selectedItemIndex + encoderDelta + itemCount) % itemCount; // Wrap-around logic

        lastEncoderPosition = currentEncoderPosition;
        displayNeedsUpdate = true; // Set flag to update display
    }
}

void displayMenu(Menu* menu, int selectedIndex) {
    // Ensure the selected index is within the bounds of the menu
    selectedIndex = max(0, min(selectedIndex, menu->itemCount - 1));

    // Adjust the scroll offset based on the selected index
    if (selectedIndex >= scrollOffset + maxDisplayItems) {
        scrollOffset = selectedIndex - maxDisplayItems + 1;
    } else if (selectedIndex < scrollOffset) {
        scrollOffset = selectedIndex;
    }

    // Update the displayed menu items
    for (int i = 0; i < maxDisplayItems; ++i) {
        int itemIndex = i + scrollOffset;
        if (itemIndex < menu->itemCount) {
            // Clear the line only if necessary
            tft.fillRect(10, 35 + 21 * i, tft.width() - 20, 20, ILI9341_BLACK);

            // Redraw the item
            tft.setCursor(10, 35 + 21 * i);
            tft.setTextColor(itemIndex == selectedIndex ? ILI9341_BLUE : ILI9341_WHITE);
            tft.print(menu->items[itemIndex].name);
        }
    }

    previousSelectedItemIndex = selectedIndex;
}

image

Maybe this?

what sets displayNeedsUpdate?

Print something to the serial monitor every time a screen updates happens and dig into the variables that make this possible.

Try building a minimal sketch with your menu functionality and without all the bells and whistles needed by your project. When the menu state machine is clean then you can move to your real project.

i see a global definition for mainMenu as well as a local definition within setup(). why not just define and initialize the global instance?

Because this is my first ever coding project and I don't know what Im doing! Ive copied and pasted and replaced and replaced, etc, and it quickly just turns into a mess and I just don't know how to make it efficient yet. This is an audio mixer projector for my mom, the audio part is done, ironically just making a menu system is by far the hardest part about the entire project!! I have a new respect for UI's!

that's the wrong way of learning anything...

put that project aside and

  • spend a few hours going through the basics of C++. You need to understand the language, functions, etc...
  • do basic tutorials. get a led to blink, detect a button press, play with the ultra sound sensor...
  • then explore the motors or whatever you like

all this with simple code. It should not be longer than a couple page of code.

Once this is understood, then you can put things together for your project and you'll be understanding what you are doing...

1 Like

it's hard to understand your menus. looks like a combination of data and logic which makes it difficult to follow, debug and modify. and it looks like there are many possibly variables that are configurable.

when i think of menu, i think of a table of things, usually including a string identifying the item, some sort of reference to the item and some identifier to indicate what to do when that menu item is selected.

top level menu items often refence sub-menu so the action is to select the sub-menu and the first sub-menu item. the action for a sub-menu item is to display that menu item.

there are typical 4 buttons: advance, select, accept, exit

but ultimately the menus allow options or variables to be changed. so the action allows an option to be toggled or a variable to inc/decremented, possibly scrolling thru each digit using generic routines

and finally there needs to be a button to leave the menu, which for you case should probably be to go back to the top-level menu

1 Like

i'm not sure that's the best advice in this case regarding menus. knowing the language doesn't necessarily explain various concepts that the language can be used for.

for example, C has pointers. but i took a course (Wirth book) on data structures that showed how pointers can be used to implement linked lists and trees.

efficient implementation of state machines, not just sequencers is another. lexical analysis and parsing are certainly not obvious.

Was some kind of boiler plate answer. From the look of the code it seems OP gets it. So quick tick in the box :wink:

(My view is that if you don’t understand the language then you’ll struggle and will be just heavily dependent on copy/paste from other codes or chatGPT without exactly understanding what you’re doing. So it’s a cornerstone).