From 09a2d83c49604c5926bef81a12a6a5e7598f6e57 Mon Sep 17 00:00:00 2001 From: Dejvino Date: Thu, 26 Feb 2026 22:34:36 +0100 Subject: [PATCH] Refactoring --- AudioThread.cpp | 133 +++++++++++++++++++ AudioThread.h | 7 + README.md | 35 +++-- RP2040_NoiceSynth.ino | 294 +++++------------------------------------- SharedState.cpp | 19 +++ SharedState.h | 59 +++++++++ UIThread.cpp | 90 +++++++++++++ UIThread.h | 7 + 8 files changed, 374 insertions(+), 270 deletions(-) create mode 100644 AudioThread.cpp create mode 100644 AudioThread.h create mode 100644 SharedState.cpp create mode 100644 SharedState.h create mode 100644 UIThread.cpp create mode 100644 UIThread.h diff --git a/AudioThread.cpp b/AudioThread.cpp new file mode 100644 index 0000000..4e6b1c2 --- /dev/null +++ b/AudioThread.cpp @@ -0,0 +1,133 @@ +#include "AudioThread.h" +#include "SharedState.h" + +// --- MIDI --- +// Create a MIDI object listening on Serial1 (GP0/GP1) +MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI); + +// --- Forward Declarations --- +void fill_audio_buffer(int16_t* buffer, size_t buffer_size); +void handleNoteOn(byte channel, byte note, byte velocity); +void handleNoteOff(byte channel, byte note, byte velocity); + +#ifdef TEST_OUT +// C Natural Minor Scale notes (C3 to C5) for testing +const byte c_minor_scale_notes[] = { + 48, 50, 51, 53, 55, 56, 58, // C3 octave + 60, 62, 63, 65, 67, 68, 70, // C4 octave + 72 // C5 +}; +#endif + +void setupAudio() { +#ifndef TEST_OUT + // --- Initialize MIDI --- + // The optocoupler circuit inverts the signal, so we must enable inverse logic. + Serial1.setRX(MIDI_RX_PIN); + Serial1.setTX(0); // Not using TX + Serial1.begin(31250); + Serial1.setRXInverse(true); + + MIDI.setHandleNoteOn(handleNoteOn); + MIDI.setHandleNoteOff(handleNoteOff); + MIDI.begin(MIDI_CHANNEL_OMNI); + Serial.println("MIDI Initialized."); +#else + Serial.println("TEST_OUT mode enabled. Playing random C minor notes."); + // Seed random from noise on the volume pot ADC pin + randomSeed(analogRead(VOL_POT_PIN)); +#endif + + // --- Initialize I2S Audio --- + i2s.setBCLK(I2S_BCLK_PIN); + i2s.setLRCK(I2S_LRCK_PIN); + i2s.setDATA(I2S_DATA_PIN); + + // Set the audio callback function + i2s.setBufferCallback(fill_audio_buffer); + + if (!i2s.begin(I2S_STEREO, SAMPLE_RATE, BITS_PER_SAMPLE)) { + Serial.println("Failed to initialize I2S!"); + while (1); // Stop forever + } + Serial.println("I2S Initialized."); +} + +void loopAudio() { +#ifdef TEST_OUT + static uint32_t last_note_event = 0; + static bool is_playing_test_note = false; + const uint16_t note_duration = 250; // ms + const uint16_t note_gap = 50; // ms + + // Check if it's time to turn off the current note + if (is_playing_test_note && (millis() - last_note_event > note_duration)) { + handleNoteOff(1, 0, 0); // Turn note off + is_playing_test_note = false; + last_note_event = millis(); // Reset timer for the gap + } + + // Check if it's time to play a new note + if (!is_playing_test_note && (millis() - last_note_event > note_gap)) { + // Pick a random note from the scale + int note_index = random(sizeof(c_minor_scale_notes) / sizeof(c_minor_scale_notes[0])); + byte midi_note = c_minor_scale_notes[note_index]; + + // Call NoteOn to set frequency and state + handleNoteOn(1, midi_note, 127); // Channel and velocity don't matter here + is_playing_test_note = true; + last_note_event = millis(); // Reset timer for the duration + } +#else + // Listen for incoming MIDI messages + MIDI.read(); +#endif +} + +// --- Audio Generation Callback --- +// This function is called by the I2S library on the second core (by default) +// to fill the audio buffer. It must be fast and should not do any allocations. +void fill_audio_buffer(int16_t* buffer, size_t buffer_size) { + if (!g_note_on || g_note_frequency <= 0.0) { + // If no note is playing, fill the buffer with silence. + memset(buffer, 0, buffer_size * sizeof(int16_t)); + return; + } + + // Calculate how much to increment the phase for each sample to get the desired frequency. + float phase_increment = (2.0 * PI * g_note_frequency) / SAMPLE_RATE; + + // The maximum amplitude for a 16-bit signed integer. + const int16_t max_amplitude = 32767; + + for (size_t i = 0; i < buffer_size; i += 2) { // Process in stereo pairs + // Generate a sawtooth wave sample (-1.0 to 1.0) + float sample = (g_phase / PI) - 1.0; + + // Increment and wrap the phase + g_phase += phase_increment; + if (g_phase >= 2.0 * PI) { + g_phase -= 2.0 * PI; + } + + // Apply volume and scale to 16-bit integer range + int16_t final_sample = static_cast(sample * max_amplitude * g_volume); + + // Write the same sample to both left and right channels + buffer[i] = final_sample; // Left channel + buffer[i + 1] = final_sample; // Right channel + } +} + +// --- MIDI Callback Functions --- +void handleNoteOn(byte channel, byte note, byte velocity) { + // Convert MIDI note number to frequency + g_note_frequency = 440.0 * pow(2.0, (note - 69.0) / 12.0); + g_note_on = true; + g_phase = 0.0; // Reset phase for a clean attack +} + +void handleNoteOff(byte channel, byte note, byte velocity) { + g_note_on = false; + g_note_frequency = 0.0; +} \ No newline at end of file diff --git a/AudioThread.h b/AudioThread.h new file mode 100644 index 0000000..3c44b14 --- /dev/null +++ b/AudioThread.h @@ -0,0 +1,7 @@ +#ifndef AUDIO_THREAD_H +#define AUDIO_THREAD_H + +void setupAudio(); +void loopAudio(); + +#endif \ No newline at end of file diff --git a/README.md b/README.md index 97d08d1..dd99e02 100644 --- a/README.md +++ b/README.md @@ -24,23 +24,41 @@ This guide provides the blueprint for building your own. | **Power** | 3.7V LiPo Battery and a 5V booster/charger board (e.g., Adafruit PowerBoost). | | **MIDI Circuit Parts** | 6N138 or 6N137 Optocoupler, 1N4148 Diode, various resistors (see diagram). | -## Powering Your Synth (Battery Operation) +## Powering Your Synth -To make the synth truly portable, we'll use a LiPo battery and a booster board. This is critical for providing stable power to all components. +You can power the synth either from a portable LiPo battery or directly from USB. + +### Battery Operation (Portable) + +To make the synth truly portable, use a LiPo battery and a 5V booster/charger board. This provides stable power to all components. > **LIPO BATTERY WARNING**: LiPo batteries are powerful but require careful handling. > * **Never** use a LiPo battery without a dedicated protection and charging circuit like the PowerBoost. > * Do not puncture, bend, or short-circuit a LiPo battery. -### Wiring for Power: - 1. Connect the **LiPo battery** to the JST connector on the **PowerBoost board**. 2. Connect the **PowerBoost's 5V output** to the **Pico's VBUS pin (Pin 40)**. This powers the Pico. 3. Connect the **PowerBoost's 5V output** to the **VCC/VIN pin of your I2S Audio Module**. -4. Connect the **PowerBoost's GND** to one of the **Pico's GND pins**. -5. Connect this **common ground** to the **GND pins of all other components** (OLED, I2S Module, Encoder, Potentiometer, MIDI circuit). +4. Connect the **PowerBoost's GND** to one of the **Pico's GND pins**. This creates a common ground. -This setup ensures everything shares a common ground and that the power-hungry components get the stable 5V they need, while the Pico's onboard regulator provides 3.3V for the lower-power parts. +### USB Operation + +If you don't need battery power, you can run the synth from a USB source (like a computer or wall adapter). The wiring is simpler. + +There are two ways to power the components: + +**Option 1 (Simplest): Power everything from 3.3V** +The PCM5102A DAC chip runs natively on 3.3V. This is the easiest and safest wiring method for USB operation. +1. Power the Pico by connecting its **micro-USB port** to a USB power source. +2. Connect the Pico's **3V3(OUT) pin (Pin 36)** to the VCC/VIN pins of **all** components: the I2S Module, OLED, Rotary Encoder, Potentiometer, and MIDI circuit. +3. Ensure all components share a **common ground** with one of the Pico's GND pins. + +**Option 2: Power I2S Module from 5V (with protection)** +If you prefer to give the audio module a separate 5V supply from USB. For good measure, adding a Schottky diode (e.g., 1N5817) is recommended to protect against any potential reverse voltage. +1. Power the Pico via its **micro-USB port**. +2. Connect the Pico's **VBUS pin (Pin 40)** to the **anode (+)** of a Schottky diode. Connect the **cathode (-)** of the diode to the **VCC/VIN pin of your I2S Audio Module**. +3. Connect the Pico's **3V3(OUT) pin (Pin 36)** to power the OLED, Encoder, Potentiometer, and MIDI circuit. +4. Ensure all components share a **common ground**. ## Wiring & Connections @@ -49,11 +67,12 @@ Establish a **common ground** by connecting the ground from your PowerBoost boar | Component | Pico Pin | Description | | ------------------ | ------------------ | ---------------------------------------------- | | **I2S Audio Module** | | | -| VCC | 5V from PowerBoost | Power for the amplifier | +| VCC | 3.3V or 5V Source (see Power section) | Power for the amplifier | | GND | Common GND | Ground | | DIN (Data) | GP11 (Pin 15) | I2S Data Out | | BCLK (Bit Clock) | GP9 (Pin 12) | I2S Bit Clock | | LRCK (Word Clock) | GP10 (Pin 14) | I2S Left/Right Clock | +| SCK (System Clock) | GND | **PCM5102 Only**: Connect to GND for internal PLL | | **SSD1306 OLED** | | | | VCC | 3V3 (OUT) (Pin 36) | 3.3V Power | | GND | Common GND | Ground | diff --git a/RP2040_NoiceSynth.ino b/RP2040_NoiceSynth.ino index 25a34a3..433c88e 100644 --- a/RP2040_NoiceSynth.ino +++ b/RP2040_NoiceSynth.ino @@ -1,275 +1,45 @@ -/** - * NoiceSynth - A Compact RP2040 Synthesizer - * - * This sketch provides the foundational code for a portable MIDI synthesizer - * based on the Raspberry Pi Pico (RP2040). - * - * Features implemented: - * - I2S audio output for a simple sawtooth oscillator. - * - MIDI input handling (Note On/Off) via TRS jack to control the oscillator. - * - OLED display for visual feedback (current note, volume). - * - Analog volume control via a potentiometer. - * - Basic rotary encoder reading (framework for future parameter control). - * - * Libraries Required (Install via Arduino Library Manager): - * - Adafruit GFX Library - * - Adafruit SSD1306 - * - MIDI Library (by Forty Seven Effects) - * - * The I2S library by Earle F. Philhower, III is included with the RP2040 board core. - */ - -#include -#include -#include -#include -#include - -// --- Pin Definitions (as per README.md) --- -// I2S Audio -#define I2S_BCLK_PIN 9 -#define I2S_LRCK_PIN 10 -#define I2S_DATA_PIN 11 - -// I2C OLED Display -#define OLED_SDA_PIN 4 -#define OLED_SCL_PIN 5 - -// Controls -#define ENCODER_CLK_PIN 12 -#define ENCODER_DT_PIN 13 -#define ENCODER_SW_PIN 14 -#define VOL_POT_PIN 26 // ADC0 - -// MIDI Input -#define MIDI_RX_PIN 1 // UART0 RX - -// --- Constants --- -#define SCREEN_WIDTH 128 -#define SCREEN_HEIGHT 64 -#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) - -#define SAMPLE_RATE 44100 -#define BITS_PER_SAMPLE 16 - -// --- Global Objects --- -I2S i2s; -Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); - -// Create a MIDI object listening on Serial1 (GP0/GP1) -MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI); - -// --- Synthesizer State Variables --- -volatile float g_note_frequency = 0.0; -volatile bool g_note_on = false; -volatile float g_volume = 0.5; - -#ifdef TEST_OUT -// C Natural Minor Scale notes (C3 to C5) for testing -const byte c_minor_scale_notes[] = { - 48, 50, 51, 53, 55, 56, 58, // C3 octave - 60, 62, 63, 65, 67, 68, 70, // C4 octave - 72 // C5 -}; -#endif - -// Oscillator phase -float g_phase = 0.0; - -// Rotary encoder state -int g_encoder_value = 0; -int g_last_clk_state; - -// --- Audio Generation Callback --- -// This function is called by the I2S library on the second core (by default) -// to fill the audio buffer. It must be fast and should not do any allocations. -void fill_audio_buffer(int16_t* buffer, size_t buffer_size) { - if (!g_note_on || g_note_frequency <= 0.0) { - // If no note is playing, fill the buffer with silence. - for (size_t i = 0; i < buffer_size; i++) { - buffer[i] = 0; - } - return; - } - - // Calculate how much to increment the phase for each sample to get the desired frequency. - float phase_increment = (2.0 * PI * g_note_frequency) / SAMPLE_RATE; - - // The maximum amplitude for a 16-bit signed integer. - const int16_t max_amplitude = 32767; - - for (size_t i = 0; i < buffer_size; i += 2) { // Process in stereo pairs - // Generate a sawtooth wave sample (-1.0 to 1.0) - float sample = (g_phase / PI) - 1.0; - - // Increment and wrap the phase - g_phase += phase_increment; - if (g_phase >= 2.0 * PI) { - g_phase -= 2.0 * PI; - } - - // Apply volume and scale to 16-bit integer range - int16_t final_sample = static_cast(sample * max_amplitude * g_volume); - - // Write the same sample to both left and right channels - buffer[i] = final_sample; // Left channel - buffer[i + 1] = final_sample; // Right channel - } -} - -// --- MIDI Callback Functions --- -void handleNoteOn(byte channel, byte note, byte velocity) { - // Convert MIDI note number to frequency - g_note_frequency = 440.0 * pow(2.0, (note - 69.0) / 12.0); - g_note_on = true; - g_phase = 0.0; // Reset phase for a clean attack -} - -void handleNoteOff(byte channel, byte note, byte velocity) { - g_note_on = false; - g_note_frequency = 0.0; -} - -// --- Helper Functions --- -void updateDisplay() { - display.clearDisplay(); - display.setCursor(0, 0); - - display.println(F(" NoiceSynth")); - display.println(F("--------------------")); - - if (g_note_on) { - display.print(F("Note Freq: ")); - display.print(g_note_frequency, 2); - display.println(F(" Hz")); - } else { - display.println(F("Note: Off")); - } - - display.print(F("Volume: ")); - display.print(static_cast(g_volume * 100)); - display.println(F("%")); - - display.print(F("Encoder: ")); - display.println(g_encoder_value); - - display.display(); -} - -void readEncoder() { - int new_clk_state = digitalRead(ENCODER_CLK_PIN); - // Check for a change on the CLK pin (a "tick" of the encoder) - if (new_clk_state != g_last_clk_state && new_clk_state == LOW) { - // Read the DT pin to determine direction - if (digitalRead(ENCODER_DT_PIN) == LOW) { - g_encoder_value++; // Clockwise - } else { - g_encoder_value--; // Counter-clockwise - } - } - g_last_clk_state = new_clk_state; -} - +#include "SharedState.h" +#include "UIThread.h" +#include "AudioThread.h" void setup() { Serial.begin(115200); + delay(2000); // Wait for serial monitor + Serial.println(F("Starting NoiceSynth...")); - // --- Initialize I2C and Display --- - Wire.setSDA(OLED_SDA_PIN); - Wire.setSCL(OLED_SCL_PIN); - Wire.begin(); + setupUI(); + setupAudio(); - if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x64 - Serial.println(F("SSD1306 allocation failed")); - for (;;); // Don't proceed, loop forever + Serial.println(F("Started.")); + + // Enable Watchdog + lastLoop0Time = millis(); + lastLoop1Time = millis(); + watchdogActive = true; +} + +void loop1() { + if (!watchdogActive) { + delay(100); + return; } - display.clearDisplay(); - display.setTextColor(SSD1306_WHITE); - display.setTextSize(1); - display.println("NoiceSynth Booting..."); - display.display(); - delay(1000); - - // --- Initialize Controls --- - pinMode(ENCODER_CLK_PIN, INPUT_PULLUP); - pinMode(ENCODER_DT_PIN, INPUT_PULLUP); - pinMode(ENCODER_SW_PIN, INPUT_PULLUP); - g_last_clk_state = digitalRead(ENCODER_CLK_PIN); - -#ifndef TEST_OUT - // --- Initialize MIDI --- - // The optocoupler circuit inverts the signal, so we must enable inverse logic. - Serial1.setRX(MIDI_RX_PIN); - Serial1.setTX(0); // Not using TX - Serial1.begin(31250); - Serial1.setRXInverse(true); - - MIDI.setHandleNoteOn(handleNoteOn); - MIDI.setHandleNoteOff(handleNoteOff); - MIDI.begin(MIDI_CHANNEL_OMNI); - Serial.println("MIDI Initialized."); -#else - Serial.println("TEST_OUT mode enabled. Playing random C minor notes."); - // Seed random from noise on the volume pot ADC pin - randomSeed(analogRead(VOL_POT_PIN)); -#endif - // --- Initialize I2S Audio --- - i2s.setBCLK(I2S_BCLK_PIN); - i2s.setLRCK(I2S_LRCK_PIN); - i2s.setDATA(I2S_DATA_PIN); - - // Set the audio callback function - i2s.setBufferCallback(fill_audio_buffer); - - if (!i2s.begin(I2S_STEREO, SAMPLE_RATE, BITS_PER_SAMPLE)) { - Serial.println("Failed to initialize I2S!"); - while (1); // Stop forever + unsigned long now = millis(); + lastLoop1Time = now; + if (watchdogActive && (now - lastLoop0Time > 1000)) { + Serial.println(F("Core 0 Freeze detected! Rebooting...")); + rp2040.reboot(); } - Serial.println("I2S Initialized."); + + loopAudio(); } void loop() { -#ifdef TEST_OUT - static uint32_t last_note_event = 0; - static bool is_playing_test_note = false; - const uint16_t note_duration = 250; // ms - const uint16_t note_gap = 50; // ms - - // Check if it's time to turn off the current note - if (is_playing_test_note && (millis() - last_note_event > note_duration)) { - handleNoteOff(1, 0, 0); // Turn note off - is_playing_test_note = false; - last_note_event = millis(); // Reset timer for the gap + unsigned long now = millis(); + lastLoop0Time = now; + if (watchdogActive && (now - lastLoop1Time > 1000)) { + Serial.println(F("Core 1 Freeze detected! Rebooting...")); + rp2040.reboot(); } - // Check if it's time to play a new note - if (!is_playing_test_note && (millis() - last_note_event > note_gap)) { - // Pick a random note from the scale - int note_index = random(sizeof(c_minor_scale_notes) / sizeof(c_minor_scale_notes[0])); - byte midi_note = c_minor_scale_notes[note_index]; - - // Call NoteOn to set frequency and state - handleNoteOn(1, midi_note, 127); // Channel and velocity don't matter here - is_playing_test_note = true; - last_note_event = millis(); // Reset timer for the duration - } -#else - // Listen for incoming MIDI messages - MIDI.read(); -#endif - - // Read the volume potentiometer (Pico ADC is 12-bit, 0-4095) - // We use a bit of filtering by averaging with the old value to reduce noise. - float new_volume = analogRead(VOL_POT_PIN) / 4095.0f; - g_volume = (g_volume * 0.95) + (new_volume * 0.05); - - // Read the rotary encoder - readEncoder(); - - // Update the display periodically - static uint32_t last_display_update = 0; - if (millis() - last_display_update > 100) { // Update 10 times/sec - last_display_update = millis(); - updateDisplay(); - } + loopUI(); } \ No newline at end of file diff --git a/SharedState.cpp b/SharedState.cpp new file mode 100644 index 0000000..5d524cf --- /dev/null +++ b/SharedState.cpp @@ -0,0 +1,19 @@ +#include "SharedState.h" + +// --- Global Objects --- +I2S i2s; +Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); + +// --- Watchdog --- +volatile unsigned long lastLoop0Time = 0; +volatile unsigned long lastLoop1Time = 0; +volatile bool watchdogActive = false; + +// --- Synthesizer State --- +volatile float g_note_frequency = 0.0; +volatile bool g_note_on = false; +volatile float g_volume = 0.5; +float g_phase = 0.0; + +// --- Control State --- +int g_encoder_value = 0; \ No newline at end of file diff --git a/SharedState.h b/SharedState.h new file mode 100644 index 0000000..ac159c8 --- /dev/null +++ b/SharedState.h @@ -0,0 +1,59 @@ +#ifndef SHARED_STATE_H +#define SHARED_STATE_H + +#include +#include +#include +#include +#include + +// --- Pin Definitions --- +// I2S Audio +#define I2S_BCLK_PIN 9 +#define I2S_LRCK_PIN 10 +#define I2S_DATA_PIN 11 + +// I2C OLED Display +#define OLED_SDA_PIN 4 +#define OLED_SCL_PIN 5 + +// Controls +#define ENCODER_CLK_PIN 12 +#define ENCODER_DT_PIN 13 +#define ENCODER_SW_PIN 14 +#define VOL_POT_PIN 26 // ADC0 + +// MIDI Input +#define MIDI_RX_PIN 1 // UART0 RX + +// --- Constants --- +#define SCREEN_WIDTH 128 +#define SCREEN_HEIGHT 64 +#define OLED_RESET -1 + +#define SAMPLE_RATE 44100 +#define BITS_PER_SAMPLE 16 + +// --- Global Objects --- +extern I2S i2s; +extern Adafruit_SSD1306 display; +extern midi::MidiInterface MIDI; + +// --- Watchdog --- +extern volatile unsigned long lastLoop0Time; +extern volatile unsigned long lastLoop1Time; +extern volatile bool watchdogActive; + +// --- Synthesizer State --- +extern volatile float g_note_frequency; +extern volatile bool g_note_on; +extern volatile float g_volume; +extern float g_phase; + +// --- Control State --- +extern int g_encoder_value; + +// --- Test Mode --- +// #define TEST_OUT + +#endif \ No newline at end of file diff --git a/UIThread.cpp b/UIThread.cpp new file mode 100644 index 0000000..0b38347 --- /dev/null +++ b/UIThread.cpp @@ -0,0 +1,90 @@ +#include "UIThread.h" +#include "SharedState.h" +#include + +// --- Local State --- +static int g_last_clk_state; + +// --- Forward Declarations --- +static void readEncoder(); +static void updateDisplay(); + +void setupUI() { + // --- Initialize I2C and Display --- + Wire.setSDA(OLED_SDA_PIN); + Wire.setSCL(OLED_SCL_PIN); + Wire.begin(); + + if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x64 + Serial.println(F("SSD1306 allocation failed")); + for (;;); // Don't proceed, loop forever + } + display.clearDisplay(); + display.setTextColor(SSD1306_WHITE); + display.setTextSize(1); + display.println("NoiceSynth Booting..."); + display.display(); + delay(1000); + + // --- Initialize Controls --- + pinMode(ENCODER_CLK_PIN, INPUT_PULLUP); + pinMode(ENCODER_DT_PIN, INPUT_PULLUP); + pinMode(ENCODER_SW_PIN, INPUT_PULLUP); + g_last_clk_state = digitalRead(ENCODER_CLK_PIN); +} + +void loopUI() { + // Read the volume potentiometer (Pico ADC is 12-bit, 0-4095) + // We use a bit of filtering by averaging with the old value to reduce noise. + float new_volume = analogRead(VOL_POT_PIN) / 4095.0f; + g_volume = (g_volume * 0.95) + (new_volume * 0.05); + + // Read the rotary encoder + readEncoder(); + + // Update the display periodically + static uint32_t last_display_update = 0; + if (millis() - last_display_update > 100) { // Update 10 times/sec + last_display_update = millis(); + updateDisplay(); + } +} + +static void updateDisplay() { + display.clearDisplay(); + display.setCursor(0, 0); + + display.println(F(" NoiceSynth")); + display.println(F("--------------------")); + + if (g_note_on) { + display.print(F("Note Freq: ")); + display.print(g_note_frequency, 2); + display.println(F(" Hz")); + } else { + display.println(F("Note: Off")); + } + + display.print(F("Volume: ")); + display.print(static_cast(g_volume * 100)); + display.println(F("%")); + + display.print(F("Encoder: ")); + display.println(g_encoder_value); + + display.display(); +} + +static void readEncoder() { + int new_clk_state = digitalRead(ENCODER_CLK_PIN); + // Check for a change on the CLK pin (a "tick" of the encoder) + if (new_clk_state != g_last_clk_state && new_clk_state == LOW) { + // Read the DT pin to determine direction + if (digitalRead(ENCODER_DT_PIN) == LOW) { + g_encoder_value++; // Clockwise + } else { + g_encoder_value--; // Counter-clockwise + } + } + g_last_clk_state = new_clk_state; +} \ No newline at end of file diff --git a/UIThread.h b/UIThread.h new file mode 100644 index 0000000..4bb7efa --- /dev/null +++ b/UIThread.h @@ -0,0 +1,7 @@ +#ifndef UI_THREAD_H +#define UI_THREAD_H + +void setupUI(); +void loopUI(); + +#endif \ No newline at end of file