diff --git a/AudioThread.cpp b/AudioThread.cpp index 259b760..30e8f04 100644 --- a/AudioThread.cpp +++ b/AudioThread.cpp @@ -26,6 +26,12 @@ double phase = 0.0; unsigned long lastNoteChangeTime = 0; // --- +// Ring Buffer +int16_t audioBuffer[AUDIO_BUFFER_SIZE]; +int audioHead = 0; +int audioTail = 0; +bool audioBuffering = true; + void setupAudio() { // Configure I2S pins i2s.setBCLK(I2S_BCLK_PIN); @@ -43,7 +49,9 @@ void setupAudio() { // Initialize the portable synth engine globalSynth = new SynthEngine(SAMPLE_RATE); - globalSynth->loadPreset(2); + if (globalSynth) { + globalSynth->loadPreset(2); + } } void loopAudio() { @@ -71,38 +79,43 @@ void loopAudio() { } } - const int BATCH_SIZE = 16; - - // Ensure we don't generate samples faster than the I2S can consume them. - // We write 2 samples (Left + Right) for every 1 synth sample. - if (i2s.availableForWrite() < BATCH_SIZE * 2) { - return; - } - - // Process a small batch of samples - static int16_t samples[BATCH_SIZE]; - - // Generate sound samples - if (globalSynth) { - // using synth engine - globalSynth->process(samples, BATCH_SIZE); + // Produce samples in a cyclic buffer + int nextHead = (audioHead + 1) % AUDIO_BUFFER_SIZE; + if (nextHead != audioTail) { + if (audioHead == audioTail) { + audioBuffering = true; + } + int16_t sample = 0; + if (globalSynth) { + globalSynth->process(&sample, 1); + } else { + if (currentFrequency > 0) { + phase += 2.0 * M_PI * currentFrequency / SAMPLE_RATE; + if (phase >= 2.0 * M_PI) phase -= 2.0 * M_PI; + sample = phase * 0.1f * AMPLITUDE; + } else { + sample = 0; + } + } + audioBuffer[audioHead] = sample; + audioHead = nextHead; } else { - // using fallback sawtooth - for (int i = 0; i < BATCH_SIZE; ++i) { - if (currentFrequency > 0) { - //samples[i] = (int16_t)(sin(phase) * AMPLITUDE); - phase += 2.0 * M_PI * currentFrequency / SAMPLE_RATE; - if (phase >= 2.0 * M_PI) phase -= 2.0 * M_PI; - samples[i] = phase * 0.1f * AMPLITUDE; - } else { - samples[i] = 0; - } - } + audioBuffering = false; } - // write out stereo samples - for (int i = 0; i < BATCH_SIZE; ++i) { - i2s.write(samples[i]); - i2s.write(samples[i]); + // Consume samples from this buffer whenever there is capacity in i2s + while (!audioBuffering && audioHead != audioTail) { + if (i2s.availableForWrite() < 2) { + break; + } + int16_t s = audioBuffer[audioTail]; + i2s.write(s); + i2s.write(s); + audioTail = (audioTail + 1) % AUDIO_BUFFER_SIZE; } + + // Update usage stats + int usage = audioHead - audioTail; + if (usage < 0) usage += AUDIO_BUFFER_SIZE; + audioBufferUsage = usage; } \ No newline at end of file diff --git a/SharedState.cpp b/SharedState.cpp index 0194022..8acaec6 100644 --- a/SharedState.cpp +++ b/SharedState.cpp @@ -2,6 +2,8 @@ #include "SharedState.h" #include "synth_engine.h" +volatile int audioBufferUsage = 0; + volatile unsigned long lastLoop0Time = 0; volatile unsigned long lastLoop1Time = 0; volatile bool watchdogActive = false; diff --git a/SharedState.h b/SharedState.h index d60ad1e..fc4e2ce 100644 --- a/SharedState.h +++ b/SharedState.h @@ -3,6 +3,9 @@ #include +#define AUDIO_BUFFER_SIZE 512 +extern volatile int audioBufferUsage; + extern volatile unsigned long lastLoop0Time; extern volatile unsigned long lastLoop1Time; extern volatile bool watchdogActive; diff --git a/UIThread.cpp b/UIThread.cpp index e0c192b..24398fa 100644 --- a/UIThread.cpp +++ b/UIThread.cpp @@ -182,7 +182,7 @@ void handleInput() { void drawUI() { display.clearDisplay(); - if (globalSynth) { + { // Copy grid state to local buffer to minimize lock time struct MiniCell { uint8_t type; @@ -191,7 +191,7 @@ void drawUI() { }; MiniCell gridCopy[SynthEngine::GRID_W][SynthEngine::GRID_H]; - { + if (globalSynth) { SynthLockGuard lock(globalSynth->gridMutex); for(int x=0; x barH - 2) fillH = barH - 2; + if (fillH < 0) fillH = 0; + + display.fillRect(barX + 1, barY + (barH - 1) - fillH, barW - 2, fillH, SSD1306_WHITE); + + // display.setCursor(barX - 4, barY + barH + 4); + // display.print(usage); } display.display();