Menu with button support to switch wave tables
This commit is contained in:
parent
59b48c1ab3
commit
34d15a7f1c
@ -45,14 +45,38 @@ void loopAudio() {
|
|||||||
if (now - lastNoteChangeTime > 500) {
|
if (now - lastNoteChangeTime > 500) {
|
||||||
lastNoteChangeTime = now;
|
lastNoteChangeTime = now;
|
||||||
int noteIndex = random(0, SCALES[currentScaleIndex].numNotes);
|
int noteIndex = random(0, SCALES[currentScaleIndex].numNotes);
|
||||||
currentFrequency = SCALES[currentScaleIndex].frequencies[noteIndex];
|
|
||||||
|
// Calculate frequency based on key, scale, and octave
|
||||||
|
const float baseFrequency = 261.63f; // C4
|
||||||
|
float keyFrequency = baseFrequency * pow(2.0f, currentKeyIndex / 12.0f);
|
||||||
|
int semitoneOffset = SCALES[currentScaleIndex].semitones[noteIndex];
|
||||||
|
currentFrequency = keyFrequency * pow(2.0f, semitoneOffset / 12.0f);
|
||||||
|
|
||||||
Serial.println("Playing note: " + String(currentFrequency) + " Hz");
|
Serial.println("Playing note: " + String(currentFrequency) + " Hz");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate the sine wave sample
|
// Generate the sine wave sample
|
||||||
|
int16_t sample;
|
||||||
double phaseIncrement = 2.0 * M_PI * currentFrequency / SAMPLE_RATE;
|
double phaseIncrement = 2.0 * M_PI * currentFrequency / SAMPLE_RATE;
|
||||||
phase = fmod(phase + phaseIncrement, 2.0 * M_PI);
|
phase = fmod(phase + phaseIncrement, 2.0 * M_PI);
|
||||||
int16_t sample = static_cast<int16_t>(AMPLITUDE * sin(phase));
|
|
||||||
|
switch (currentWavetableIndex) {
|
||||||
|
case 0: // Sine
|
||||||
|
sample = static_cast<int16_t>(AMPLITUDE * sin(phase));
|
||||||
|
break;
|
||||||
|
case 1: // Square
|
||||||
|
sample = (phase < M_PI) ? AMPLITUDE : -AMPLITUDE;
|
||||||
|
break;
|
||||||
|
case 2: // Saw
|
||||||
|
sample = static_cast<int16_t>(AMPLITUDE * (1.0 - (phase / M_PI)));
|
||||||
|
break;
|
||||||
|
case 3: // Triangle
|
||||||
|
sample = static_cast<int16_t>(AMPLITUDE * (2.0 * fabs(phase / M_PI - 1.0) - 1.0));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sample = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Write the same sample to both left and right channels (mono audio).
|
// Write the same sample to both left and right channels (mono audio).
|
||||||
// This call is blocking and will wait until there is space in the DMA buffer.
|
// This call is blocking and will wait until there is space in the DMA buffer.
|
||||||
|
|||||||
@ -4,11 +4,29 @@ volatile unsigned long lastLoop0Time = 0;
|
|||||||
volatile unsigned long lastLoop1Time = 0;
|
volatile unsigned long lastLoop1Time = 0;
|
||||||
volatile bool watchdogActive = false;
|
volatile bool watchdogActive = false;
|
||||||
|
|
||||||
|
UIState currentState = UI_MENU;
|
||||||
|
volatile int menuSelection = 0;
|
||||||
|
|
||||||
|
const MenuItem MENU_ITEMS[] = {
|
||||||
|
{ "Scale Type", UI_EDIT_SCALE_TYPE },
|
||||||
|
{ "Scale Key", UI_EDIT_SCALE_KEY },
|
||||||
|
{ "Wavetable", UI_EDIT_WAVETABLE }
|
||||||
|
};
|
||||||
|
const int NUM_MENU_ITEMS = sizeof(MENU_ITEMS) / sizeof(MENU_ITEMS[0]);
|
||||||
|
|
||||||
const Scale SCALES[] = {
|
const Scale SCALES[] = {
|
||||||
{ "C Major", { 261.63, 293.66, 329.63, 349.23, 392.00, 440.00, 493.88, 523.25 }, 8 },
|
{ "Major", { 0, 2, 4, 5, 7, 9, 11, 12 }, 8 },
|
||||||
{ "C Minor", { 261.63, 293.66, 311.13, 349.23, 392.00, 415.30, 466.16, 523.25 }, 8 },
|
{ "Minor", { 0, 2, 3, 5, 7, 8, 10, 12 }, 8 },
|
||||||
{ "Pentatonic", { 261.63, 293.66, 329.63, 392.00, 440.00, 523.25, 587.33, 659.25 }, 8 },
|
{ "Pentatonic", { 0, 2, 4, 7, 9, 12, 14, 16 }, 8 },
|
||||||
{ "Blues", { 261.63, 311.13, 349.23, 369.99, 392.00, 466.16, 523.25, 587.33 }, 8 }
|
{ "Blues", { 0, 3, 5, 6, 7, 10, 12, 14 }, 8 }
|
||||||
};
|
};
|
||||||
const int NUM_SCALES = sizeof(SCALES) / sizeof(SCALES[0]);
|
const int NUM_SCALES = sizeof(SCALES) / sizeof(SCALES[0]);
|
||||||
volatile int currentScaleIndex = 0;
|
volatile int currentScaleIndex = 0;
|
||||||
|
|
||||||
|
const char* KEY_NAMES[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
|
||||||
|
const int NUM_KEYS = sizeof(KEY_NAMES) / sizeof(KEY_NAMES[0]);
|
||||||
|
volatile int currentKeyIndex = 0; // C
|
||||||
|
|
||||||
|
const char* WAVETABLE_NAMES[] = {"Sine", "Square", "Saw", "Triangle"};
|
||||||
|
const int NUM_WAVETABLES = sizeof(WAVETABLE_NAMES) / sizeof(WAVETABLE_NAMES[0]);
|
||||||
|
volatile int currentWavetableIndex = 0; // Sine
|
||||||
@ -7,14 +7,39 @@ extern volatile unsigned long lastLoop0Time;
|
|||||||
extern volatile unsigned long lastLoop1Time;
|
extern volatile unsigned long lastLoop1Time;
|
||||||
extern volatile bool watchdogActive;
|
extern volatile bool watchdogActive;
|
||||||
|
|
||||||
|
enum UIState {
|
||||||
|
UI_MENU,
|
||||||
|
UI_EDIT_SCALE_TYPE,
|
||||||
|
UI_EDIT_SCALE_KEY,
|
||||||
|
UI_EDIT_WAVETABLE
|
||||||
|
};
|
||||||
|
|
||||||
struct Scale {
|
struct Scale {
|
||||||
const char* name;
|
const char* name;
|
||||||
const float frequencies[8];
|
const int semitones[8];
|
||||||
int numNotes;
|
int numNotes;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct MenuItem {
|
||||||
|
const char* label;
|
||||||
|
UIState editState;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern UIState currentState;
|
||||||
|
extern const MenuItem MENU_ITEMS[];
|
||||||
|
extern const int NUM_MENU_ITEMS;
|
||||||
|
extern volatile int menuSelection;
|
||||||
|
|
||||||
extern const Scale SCALES[];
|
extern const Scale SCALES[];
|
||||||
extern const int NUM_SCALES;
|
extern const int NUM_SCALES;
|
||||||
extern volatile int currentScaleIndex;
|
extern volatile int currentScaleIndex;
|
||||||
|
|
||||||
|
extern const char* KEY_NAMES[];
|
||||||
|
extern const int NUM_KEYS;
|
||||||
|
extern volatile int currentKeyIndex;
|
||||||
|
|
||||||
|
extern const char* WAVETABLE_NAMES[];
|
||||||
|
extern const int NUM_WAVETABLES;
|
||||||
|
extern volatile int currentWavetableIndex;
|
||||||
|
|
||||||
#endif // SHAREDSTATE_H
|
#endif // SHAREDSTATE_H
|
||||||
127
UIThread.cpp
127
UIThread.cpp
@ -17,6 +17,7 @@
|
|||||||
// Encoder Pins
|
// Encoder Pins
|
||||||
#define PIN_ENC_CLK 12
|
#define PIN_ENC_CLK 12
|
||||||
#define PIN_ENC_DT 13
|
#define PIN_ENC_DT 13
|
||||||
|
#define PIN_ENC_SW 14
|
||||||
|
|
||||||
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
|
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
|
||||||
|
|
||||||
@ -24,6 +25,15 @@ volatile int8_t encoderDelta = 0;
|
|||||||
static uint8_t prevNextCode = 0;
|
static uint8_t prevNextCode = 0;
|
||||||
static uint16_t store = 0;
|
static uint16_t store = 0;
|
||||||
|
|
||||||
|
// Button state
|
||||||
|
static int lastButtonReading = HIGH;
|
||||||
|
static int currentButtonState = HIGH;
|
||||||
|
static unsigned long lastDebounceTime = 0;
|
||||||
|
static bool buttonClick = false;
|
||||||
|
|
||||||
|
void handleInput();
|
||||||
|
void drawUI();
|
||||||
|
|
||||||
// --- ENCODER INTERRUPT ---
|
// --- ENCODER INTERRUPT ---
|
||||||
// Robust Rotary Encoder reading
|
// Robust Rotary Encoder reading
|
||||||
void readEncoder() {
|
void readEncoder() {
|
||||||
@ -50,6 +60,7 @@ void setupUI() {
|
|||||||
|
|
||||||
pinMode(PIN_ENC_CLK, INPUT_PULLUP);
|
pinMode(PIN_ENC_CLK, INPUT_PULLUP);
|
||||||
pinMode(PIN_ENC_DT, INPUT_PULLUP);
|
pinMode(PIN_ENC_DT, INPUT_PULLUP);
|
||||||
|
pinMode(PIN_ENC_SW, INPUT_PULLUP);
|
||||||
attachInterrupt(digitalPinToInterrupt(PIN_ENC_CLK), readEncoder, CHANGE);
|
attachInterrupt(digitalPinToInterrupt(PIN_ENC_CLK), readEncoder, CHANGE);
|
||||||
attachInterrupt(digitalPinToInterrupt(PIN_ENC_DT), readEncoder, CHANGE);
|
attachInterrupt(digitalPinToInterrupt(PIN_ENC_DT), readEncoder, CHANGE);
|
||||||
|
|
||||||
@ -59,10 +70,6 @@ void setupUI() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
display.clearDisplay();
|
display.clearDisplay();
|
||||||
display.setTextSize(1);
|
|
||||||
display.setTextColor(SSD1306_WHITE);
|
|
||||||
display.setCursor(0, 0);
|
|
||||||
display.println(F("Lorem ipsum dolor sit amet, consectetur adipiscing elit."));
|
|
||||||
display.display();
|
display.display();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,27 +82,107 @@ void handleInput() {
|
|||||||
interrupts();
|
interrupts();
|
||||||
|
|
||||||
if (rotation != 0) {
|
if (rotation != 0) {
|
||||||
|
switch (currentState) {
|
||||||
|
case UI_MENU:
|
||||||
|
menuSelection += rotation;
|
||||||
|
while (menuSelection < 0) menuSelection += NUM_MENU_ITEMS;
|
||||||
|
menuSelection %= NUM_MENU_ITEMS;
|
||||||
|
break;
|
||||||
|
case UI_EDIT_SCALE_TYPE:
|
||||||
currentScaleIndex += rotation;
|
currentScaleIndex += rotation;
|
||||||
while (currentScaleIndex < 0) currentScaleIndex += NUM_SCALES;
|
while (currentScaleIndex < 0) currentScaleIndex += NUM_SCALES;
|
||||||
while (currentScaleIndex >= NUM_SCALES) currentScaleIndex -= NUM_SCALES;
|
currentScaleIndex %= NUM_SCALES;
|
||||||
|
break;
|
||||||
|
case UI_EDIT_SCALE_KEY:
|
||||||
|
currentKeyIndex += rotation;
|
||||||
|
while (currentKeyIndex < 0) currentKeyIndex += NUM_KEYS;
|
||||||
|
currentKeyIndex %= NUM_KEYS;
|
||||||
|
break;
|
||||||
|
case UI_EDIT_WAVETABLE:
|
||||||
|
currentWavetableIndex += rotation;
|
||||||
|
while (currentWavetableIndex < 0) currentWavetableIndex += NUM_WAVETABLES;
|
||||||
|
currentWavetableIndex %= NUM_WAVETABLES;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle Button Click
|
||||||
|
int reading = digitalRead(PIN_ENC_SW);
|
||||||
|
if (reading != lastButtonReading) {
|
||||||
|
lastDebounceTime = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((millis() - lastDebounceTime) > 50) {
|
||||||
|
if (reading != currentButtonState) {
|
||||||
|
currentButtonState = reading;
|
||||||
|
if (currentButtonState == LOW) {
|
||||||
|
buttonClick = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastButtonReading = reading;
|
||||||
|
|
||||||
|
if (buttonClick) {
|
||||||
|
buttonClick = false;
|
||||||
|
if (currentState == UI_MENU) {
|
||||||
|
currentState = MENU_ITEMS[menuSelection].editState;
|
||||||
|
} else {
|
||||||
|
currentState = UI_MENU;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawUI() {
|
||||||
|
display.clearDisplay();
|
||||||
|
display.setTextSize(1);
|
||||||
|
display.setTextColor(SSD1306_WHITE);
|
||||||
|
display.setCursor(0, 0);
|
||||||
|
|
||||||
|
if (currentState == UI_MENU) {
|
||||||
|
for (int i = 0; i < NUM_MENU_ITEMS; i++) {
|
||||||
|
if (i == menuSelection) {
|
||||||
|
display.fillRect(0, i * 10, SCREEN_WIDTH, 10, SSD1306_WHITE);
|
||||||
|
display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
|
||||||
|
} else {
|
||||||
|
display.setTextColor(SSD1306_WHITE);
|
||||||
|
}
|
||||||
|
display.setCursor(2, i * 10 + 1);
|
||||||
|
display.print(MENU_ITEMS[i].label);
|
||||||
|
display.print(": ");
|
||||||
|
|
||||||
|
// Display current value
|
||||||
|
switch (MENU_ITEMS[i].editState) {
|
||||||
|
case UI_EDIT_SCALE_TYPE: display.print(SCALES[currentScaleIndex].name); break;
|
||||||
|
case UI_EDIT_SCALE_KEY: display.print(KEY_NAMES[currentKeyIndex]); break;
|
||||||
|
case UI_EDIT_WAVETABLE: display.print(WAVETABLE_NAMES[currentWavetableIndex]); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// In an edit screen
|
||||||
|
const char* title = MENU_ITEMS[menuSelection].label;
|
||||||
|
const char* value = "";
|
||||||
|
switch (currentState) {
|
||||||
|
case UI_EDIT_SCALE_TYPE: value = SCALES[currentScaleIndex].name; break;
|
||||||
|
case UI_EDIT_SCALE_KEY: value = KEY_NAMES[currentKeyIndex]; break;
|
||||||
|
case UI_EDIT_WAVETABLE: value = WAVETABLE_NAMES[currentWavetableIndex]; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
display.println(title);
|
||||||
|
display.drawLine(0, 10, SCREEN_WIDTH, 10, SSD1306_WHITE);
|
||||||
|
display.setCursor(10, 25);
|
||||||
|
display.setTextSize(2);
|
||||||
|
display.print(value);
|
||||||
|
display.setTextSize(1);
|
||||||
|
display.setCursor(0, 50);
|
||||||
|
display.println(F("(Press to confirm)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
display.display();
|
||||||
|
}
|
||||||
|
|
||||||
void loopUI() {
|
void loopUI() {
|
||||||
// sleep to avoid thread starvation
|
|
||||||
delay(10);
|
|
||||||
|
|
||||||
handleInput();
|
handleInput();
|
||||||
// The loop on core 0 is responsible for updating the UI.
|
drawUI();
|
||||||
static unsigned long lastUpdate = 0;
|
delay(20); // Prevent excessive screen refresh
|
||||||
if (millis() - lastUpdate < 100) return;
|
|
||||||
lastUpdate = millis();
|
|
||||||
|
|
||||||
display.clearDisplay();
|
|
||||||
display.setCursor(0, 0);
|
|
||||||
display.setTextSize(1);
|
|
||||||
display.println(F("Current Scale:"));
|
|
||||||
display.setTextSize(2);
|
|
||||||
display.println(SCALES[currentScaleIndex].name);
|
|
||||||
display.display();
|
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user