Buffering of audio

This commit is contained in:
Dejvino 2026-03-01 11:41:08 +01:00
parent ad0fb039fc
commit 82bab0698b
4 changed files with 71 additions and 35 deletions

View File

@ -26,6 +26,12 @@ double phase = 0.0;
unsigned long lastNoteChangeTime = 0; unsigned long lastNoteChangeTime = 0;
// --- // ---
// Ring Buffer
int16_t audioBuffer[AUDIO_BUFFER_SIZE];
int audioHead = 0;
int audioTail = 0;
bool audioBuffering = true;
void setupAudio() { void setupAudio() {
// Configure I2S pins // Configure I2S pins
i2s.setBCLK(I2S_BCLK_PIN); i2s.setBCLK(I2S_BCLK_PIN);
@ -43,8 +49,10 @@ void setupAudio() {
// Initialize the portable synth engine // Initialize the portable synth engine
globalSynth = new SynthEngine(SAMPLE_RATE); globalSynth = new SynthEngine(SAMPLE_RATE);
if (globalSynth) {
globalSynth->loadPreset(2); globalSynth->loadPreset(2);
} }
}
void loopAudio() { void loopAudio() {
unsigned long now = millis(); unsigned long now = millis();
@ -71,38 +79,43 @@ void loopAudio() {
} }
} }
const int BATCH_SIZE = 16; // Produce samples in a cyclic buffer
int nextHead = (audioHead + 1) % AUDIO_BUFFER_SIZE;
// Ensure we don't generate samples faster than the I2S can consume them. if (nextHead != audioTail) {
// We write 2 samples (Left + Right) for every 1 synth sample. if (audioHead == audioTail) {
if (i2s.availableForWrite() < BATCH_SIZE * 2) { audioBuffering = true;
return;
} }
int16_t sample = 0;
// Process a small batch of samples
static int16_t samples[BATCH_SIZE];
// Generate sound samples
if (globalSynth) { if (globalSynth) {
// using synth engine globalSynth->process(&sample, 1);
globalSynth->process(samples, BATCH_SIZE);
} else { } else {
// using fallback sawtooth
for (int i = 0; i < BATCH_SIZE; ++i) {
if (currentFrequency > 0) { if (currentFrequency > 0) {
//samples[i] = (int16_t)(sin(phase) * AMPLITUDE);
phase += 2.0 * M_PI * currentFrequency / SAMPLE_RATE; phase += 2.0 * M_PI * currentFrequency / SAMPLE_RATE;
if (phase >= 2.0 * M_PI) phase -= 2.0 * M_PI; if (phase >= 2.0 * M_PI) phase -= 2.0 * M_PI;
samples[i] = phase * 0.1f * AMPLITUDE; sample = phase * 0.1f * AMPLITUDE;
} else { } else {
samples[i] = 0; sample = 0;
} }
} }
audioBuffer[audioHead] = sample;
audioHead = nextHead;
} else {
audioBuffering = false;
} }
// write out stereo samples // Consume samples from this buffer whenever there is capacity in i2s
for (int i = 0; i < BATCH_SIZE; ++i) { while (!audioBuffering && audioHead != audioTail) {
i2s.write(samples[i]); if (i2s.availableForWrite() < 2) {
i2s.write(samples[i]); 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;
} }

View File

@ -2,6 +2,8 @@
#include "SharedState.h" #include "SharedState.h"
#include "synth_engine.h" #include "synth_engine.h"
volatile int audioBufferUsage = 0;
volatile unsigned long lastLoop0Time = 0; volatile unsigned long lastLoop0Time = 0;
volatile unsigned long lastLoop1Time = 0; volatile unsigned long lastLoop1Time = 0;
volatile bool watchdogActive = false; volatile bool watchdogActive = false;

View File

@ -3,6 +3,9 @@
#include <Arduino.h> #include <Arduino.h>
#define AUDIO_BUFFER_SIZE 512
extern volatile int audioBufferUsage;
extern volatile unsigned long lastLoop0Time; extern volatile unsigned long lastLoop0Time;
extern volatile unsigned long lastLoop1Time; extern volatile unsigned long lastLoop1Time;
extern volatile bool watchdogActive; extern volatile bool watchdogActive;

View File

@ -182,7 +182,7 @@ void handleInput() {
void drawUI() { void drawUI() {
display.clearDisplay(); display.clearDisplay();
if (globalSynth) { {
// Copy grid state to local buffer to minimize lock time // Copy grid state to local buffer to minimize lock time
struct MiniCell { struct MiniCell {
uint8_t type; uint8_t type;
@ -191,7 +191,7 @@ void drawUI() {
}; };
MiniCell gridCopy[SynthEngine::GRID_W][SynthEngine::GRID_H]; MiniCell gridCopy[SynthEngine::GRID_W][SynthEngine::GRID_H];
{ if (globalSynth) {
SynthLockGuard<SynthMutex> lock(globalSynth->gridMutex); SynthLockGuard<SynthMutex> lock(globalSynth->gridMutex);
for(int x=0; x<SynthEngine::GRID_W; ++x) { for(int x=0; x<SynthEngine::GRID_W; ++x) {
for(int y=0; y<SynthEngine::GRID_H; ++y) { for(int y=0; y<SynthEngine::GRID_H; ++y) {
@ -202,9 +202,9 @@ void drawUI() {
} }
} }
int cellW = 10; int cellW = 8;
int cellH = 5; int cellH = 5;
int marginX = (SCREEN_WIDTH - (SynthEngine::GRID_W * cellW)) / 2; int marginX = 2;
int marginY = (SCREEN_HEIGHT - (SynthEngine::GRID_H * cellH)) / 2; int marginY = (SCREEN_HEIGHT - (SynthEngine::GRID_H * cellH)) / 2;
for(int x=0; x<SynthEngine::GRID_W; ++x) { for(int x=0; x<SynthEngine::GRID_W; ++x) {
@ -245,6 +245,24 @@ void drawUI() {
} }
} }
} }
// Draw Buffer Stats
int barX = 110;
int barY = 10;
int barW = 8;
int barH = 30;
display.drawRect(barX, barY, barW, barH, SSD1306_WHITE);
int usage = audioBufferUsage;
int fillH = (usage * (barH - 2)) / AUDIO_BUFFER_SIZE;
if (fillH > 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(); display.display();