Optimizations 1

This commit is contained in:
Dejvino 2026-03-01 10:42:04 +01:00
parent aaeaa9986e
commit ad0fb039fc
5 changed files with 246 additions and 222 deletions

View File

@ -12,8 +12,8 @@ const int I2S_LRC_PIN = 10; // Left-Right Clock (GP10)
const int I2S_DOUT_PIN = 11; // Data Out (GP11)
// Audio parameters
const int SAMPLE_RATE = 44100;
const int16_t AMPLITUDE = 16383; // Use a lower amplitude to avoid clipping (max is 32767 for 16-bit)
const int SAMPLE_RATE = 44100 / 2;
const int16_t AMPLITUDE = 16383 / 2; // Use a lower amplitude to avoid clipping (max is 32767 for 16-bit)
// Create an I2S output object
I2S i2s(OUTPUT);
@ -43,6 +43,7 @@ void setupAudio() {
// Initialize the portable synth engine
globalSynth = new SynthEngine(SAMPLE_RATE);
globalSynth->loadPreset(2);
}
void loopAudio() {
@ -51,27 +52,56 @@ void loopAudio() {
// Every 500ms, pick a new random note to play
if (now - lastNoteChangeTime > 500) {
lastNoteChangeTime = now;
int noteIndex = random(0, SCALES[currentScaleIndex].numNotes);
int noteIndex = random(0, SCALES[currentScaleIndex].numNotes + 2);
// 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);
bool rest = noteIndex >= SCALES[currentScaleIndex].numNotes;
if (!rest) {
// 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);
} else {
currentFrequency = 0;
}
if (globalSynth) {
globalSynth->setFrequency(currentFrequency);
globalSynth->setGate(true); // Trigger envelope
globalSynth->setFrequency(currentFrequency > 0 ? currentFrequency : 440.0f);
globalSynth->setGate(!rest); // Trigger envelope
}
Serial.println("Playing note: " + String(currentFrequency) + " Hz");
}
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
int16_t samples[32];
if (globalSynth) globalSynth->process(samples, 32);
else memset(samples, 0, sizeof(samples));
static int16_t samples[BATCH_SIZE];
for (int i = 0; i < 32; ++i) {
// Generate sound samples
if (globalSynth) {
// using synth engine
globalSynth->process(samples, BATCH_SIZE);
} 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;
}
}
}
// write out stereo samples
for (int i = 0; i < BATCH_SIZE; ++i) {
i2s.write(samples[i]);
i2s.write(samples[i]);
}

View File

@ -181,48 +181,70 @@ void handleInput() {
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(": ");
if (globalSynth) {
// Copy grid state to local buffer to minimize lock time
struct MiniCell {
uint8_t type;
uint8_t rotation;
float value;
};
MiniCell gridCopy[SynthEngine::GRID_W][SynthEngine::GRID_H];
// 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;
{
SynthLockGuard<SynthMutex> lock(globalSynth->gridMutex);
for(int x=0; x<SynthEngine::GRID_W; ++x) {
for(int y=0; y<SynthEngine::GRID_H; ++y) {
gridCopy[x][y].type = (uint8_t)globalSynth->grid[x][y].type;
gridCopy[x][y].rotation = (uint8_t)globalSynth->grid[x][y].rotation;
gridCopy[x][y].value = globalSynth->grid[x][y].value;
}
}
}
} 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;
int cellW = 10;
int cellH = 5;
int marginX = (SCREEN_WIDTH - (SynthEngine::GRID_W * cellW)) / 2;
int marginY = (SCREEN_HEIGHT - (SynthEngine::GRID_H * cellH)) / 2;
for(int x=0; x<SynthEngine::GRID_W; ++x) {
for(int y=0; y<SynthEngine::GRID_H; ++y) {
int px = marginX + x * cellW;
int py = marginY + y * cellH;
int cx = px + cellW / 2;
int cy = py + cellH / 2;
uint8_t type = gridCopy[x][y].type;
uint8_t rot = gridCopy[x][y].rotation;
if (type == SynthEngine::GridCell::EMPTY) {
display.drawPixel(cx, cy, SSD1306_WHITE);
} else if (type == SynthEngine::GridCell::SINK) {
display.fillRect(px + 1, py + 1, cellW - 2, cellH - 2, SSD1306_WHITE);
} else {
// Draw direction line
int dx = 0, dy = 0;
switch(rot) {
case 0: dy = -2; break; // N
case 1: dx = 4; break; // E
case 2: dy = 2; break; // S
case 3: dx = -4; break; // W
}
display.drawLine(cx, cy, cx + dx, cy + dy, SSD1306_WHITE);
if (type == SynthEngine::GridCell::FORK) {
if (rot == 0 || rot == 2) display.drawLine(cx - 2, cy, cx + 2, cy, SSD1306_WHITE);
else display.drawLine(cx, cy - 2, cx, cy + 2, SSD1306_WHITE);
} else if (type >= SynthEngine::GridCell::FIXED_OSCILLATOR && type <= SynthEngine::GridCell::GATE_INPUT) {
// Sources: Filled rect
display.fillRect(cx - 1, cy - 1, 3, 3, SSD1306_WHITE);
} else if (type != SynthEngine::GridCell::WIRE) {
// Processors: Hollow rect
display.drawRect(cx - 1, cy - 1, 3, 3, SSD1306_WHITE);
}
}
}
}
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();
@ -232,6 +254,8 @@ void checkSerial() {
static int state = 0; // 0: Header, 1: Data
static int headerIdx = 0;
static const char* header = "NSGRID";
static int loadHeaderIdx = 0;
static const char* loadHeader = "NSLOAD";
static uint8_t buffer[SynthEngine::SERIALIZED_GRID_SIZE];
static int bufferIdx = 0;
@ -244,17 +268,39 @@ void checkSerial() {
state = 1;
bufferIdx = 0;
headerIdx = 0;
loadHeaderIdx = 0;
}
} else {
headerIdx = 0;
if (b == 'N') headerIdx = 1;
}
if (state == 0) {
if (b == loadHeader[loadHeaderIdx]) {
loadHeaderIdx++;
if (loadHeaderIdx == 6) {
if (globalSynth) {
uint8_t buf[SynthEngine::SERIALIZED_GRID_SIZE];
globalSynth->exportGrid(buf);
Serial.write("NSGRID", 6);
Serial.write(buf, sizeof(buf));
Serial.flush();
}
loadHeaderIdx = 0;
headerIdx = 0;
}
} else {
loadHeaderIdx = 0;
if (b == 'N') loadHeaderIdx = 1;
}
}
} else if (state == 1) {
buffer[bufferIdx++] = b;
if (bufferIdx == SynthEngine::SERIALIZED_GRID_SIZE) {
if (globalSynth) {
globalSynth->importGrid(buffer);
saveGridToEEPROM();
Serial.println(F("OK: Grid Received"));
}
state = 0;
bufferIdx = 0;

View File

@ -12,6 +12,11 @@
#include <stdio.h>
#if !defined(_WIN32)
#include <sys/select.h>
#include <unistd.h>
#endif
// --- Configuration ---
const uint32_t SAMPLE_RATE = 44100;
const uint32_t CHANNELS = 1; // Mono
@ -73,6 +78,61 @@ void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uin
vis_write_index.store(idx, std::memory_order_relaxed);
}
void checkSerialInput(FILE* serialPort) {
if (!serialPort) return;
#if !defined(_WIN32)
int fd = fileno(serialPort);
fd_set readfds;
struct timeval tv;
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
tv.tv_sec = 0;
tv.tv_usec = 0;
if (select(fd + 1, &readfds, NULL, NULL, &tv) <= 0) return;
uint8_t buf[256];
ssize_t n = read(fd, buf, sizeof(buf));
if (n <= 0) return;
#else
return;
#endif
printf("Grid import maybe?\n");
static int state = 0;
static int headerIdx = 0;
static const char* header = "NSGRID";
static uint8_t buffer[SynthEngine::SERIALIZED_GRID_SIZE];
static int bufferIdx = 0;
for (ssize_t i = 0; i < n; ++i) {
uint8_t b = buf[i];
if (state == 0) {
if (b == header[headerIdx]) {
headerIdx++;
if (headerIdx == 6) {
state = 1;
bufferIdx = 0;
headerIdx = 0;
printf("Grid import starting.\n");
}
} else {
headerIdx = 0;
if (b == 'N') headerIdx = 1;
}
} else if (state == 1) {
buffer[bufferIdx++] = b;
if (bufferIdx == SynthEngine::SERIALIZED_GRID_SIZE) {
engine.importGrid(buffer);
printf("Grid imported from serial.\n");
state = 0;
bufferIdx = 0;
}
}
}
}
// --- UI Drawing Helpers ---
void DrawCircle(SDL_Renderer * renderer, int32_t centreX, int32_t centreY, int32_t radius) {
@ -706,23 +766,24 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G
}
void randomizeGrid() {
printf("Randomizing grid...\n");
Uint32 startTime = SDL_GetTicks();
SynthLockGuard<SynthMutex> lock(engine.gridMutex);
// Number of types to choose from (excluding SINK)
const int numTypes = (int)SynthEngine::GridCell::SINK;
// 1. Clear existing buffers first
// 1. Clear existing buffers first (resets the pool)
// engine.clearGrid(); // Avoid deadlock by clearing manually
for (int x = 0; x < SynthEngine::GRID_W; ++x) {
for (int y = 0; y < SynthEngine::GRID_H; ++y) {
SynthEngine::GridCell& c = engine.grid[x][y];
if (c.buffer) {
delete[] c.buffer;
c.buffer = nullptr;
c.buffer_size = 0;
}
if (c.type != SynthEngine::GridCell::SINK) {
c.type = SynthEngine::GridCell::EMPTY;
}
if (c.type == SynthEngine::GridCell::SINK) continue;
c.type = SynthEngine::GridCell::EMPTY;
c.param = 0.5f;
c.rotation = 0;
c.value = 0.0f;
c.phase = 0.0f;
}
}
@ -731,6 +792,10 @@ void randomizeGrid() {
bool visited[SynthEngine::GRID_W][SynthEngine::GRID_H];
while (!validGrid && attempts < 1000) {
if (SDL_GetTicks() - startTime > 200) { // Safeguard: Timeout after 200ms
printf("Randomization timed out.\n");
break;
}
attempts++;
// 2. Randomize (without allocation)
@ -837,18 +902,6 @@ void randomizeGrid() {
}
}
// 5. Allocate buffers for DELAYs and REVERBs
for (int x = 0; x < SynthEngine::GRID_W; ++x) {
for (int y = 0; y < SynthEngine::GRID_H; ++y) {
SynthEngine::GridCell& c = engine.grid[x][y];
if (c.type == SynthEngine::GridCell::DELAY || c.type == SynthEngine::GridCell::REVERB || c.type == SynthEngine::GridCell::PITCH_SHIFTER) {
c.buffer_size = 2 * SAMPLE_RATE;
c.buffer = new float[c.buffer_size]();
c.write_idx = 0;
}
}
}
// 6. Run Simulation
engine.setGate(true);
float oldFreq = engine.getFrequency();
@ -874,16 +927,6 @@ void randomizeGrid() {
}
} else {
// Failed simulation, cleanup buffers
for (int x = 0; x < SynthEngine::GRID_W; ++x) {
for (int y = 0; y < SynthEngine::GRID_H; ++y) {
SynthEngine::GridCell& c = engine.grid[x][y];
if (c.buffer) {
delete[] c.buffer;
c.buffer = nullptr;
c.buffer_size = 0;
}
}
}
}
}
}
@ -902,7 +945,7 @@ void randomizeGrid() {
}
}
printf("Randomized in %d attempts. Valid: %s\n", attempts, validGrid ? "YES" : "NO");
printf("Randomized in %d attempts (%d ms). Valid: %s\n", attempts, SDL_GetTicks() - startTime, validGrid ? "YES" : "NO");
}
int main(int argc, char* argv[]) {
@ -943,7 +986,7 @@ int main(int argc, char* argv[]) {
ma_device_start(&device);
if (argc > 1) {
serialPort = fopen(argv[1], "wb");
serialPort = fopen(argv[1], "r+b");
if (serialPort) printf("Opened serial port: %s\n", argv[1]);
else printf("Failed to open serial port: %s\n", argv[1]);
}
@ -992,19 +1035,18 @@ int main(int argc, char* argv[]) {
SynthEngine::GridCell::BITCRUSHER,
SynthEngine::GridCell::DISTORTION,
SynthEngine::GridCell::RECTIFIER,
SynthEngine::GridCell::PITCH_SHIFTER,
SynthEngine::GridCell::GLITCH,
SynthEngine::GridCell::OPERATOR,
SynthEngine::GridCell::DELAY,
SynthEngine::GridCell::REVERB
SynthEngine::GridCell::OPERATOR
};
const int numCellTypes = sizeof(cellTypes) / sizeof(cellTypes[0]);
bool quit = false;
SDL_Event e;
bool exportButtonPressed = false;
bool importButtonPressed = false;
while (!quit) {
checkSerialInput(serialPort);
// --- Automated Melody Logic ---
if (auto_melody_enabled && SDL_GetTicks() > auto_melody_next_event_time) {
auto_melody_next_event_time = SDL_GetTicks() + 200 + (rand() % 150); // Note duration
@ -1060,19 +1102,7 @@ int main(int argc, char* argv[]) {
}
if (newType != oldType) {
// If old type was DELAY, free its buffer
if ((oldType == SynthEngine::GridCell::DELAY || oldType == SynthEngine::GridCell::REVERB || oldType == SynthEngine::GridCell::PITCH_SHIFTER) && c.buffer) {
delete[] c.buffer;
c.buffer = nullptr;
c.buffer_size = 0;
}
c.type = newType;
// If new type is DELAY, allocate its buffer
if (newType == SynthEngine::GridCell::DELAY || newType == SynthEngine::GridCell::REVERB || newType == SynthEngine::GridCell::PITCH_SHIFTER) {
c.buffer_size = 2 * SAMPLE_RATE; // Max 2 seconds delay
c.buffer = new float[c.buffer_size](); // Allocate and zero-initialize
c.write_idx = 0; // Reset write index
}
}
}
}
@ -1101,6 +1131,12 @@ int main(int argc, char* argv[]) {
my >= exportButtonRect.y && my <= exportButtonRect.y + exportButtonRect.h) {
exportButtonPressed = true;
}
SDL_Rect importButtonRect = {410, 435, 100, 30};
if (synthX >= importButtonRect.x && synthX <= importButtonRect.x + importButtonRect.w &&
my >= importButtonRect.y && my <= importButtonRect.y + importButtonRect.h) {
importButtonPressed = true;
}
}
} else if (e.type == SDL_MOUSEWHEEL) {
SDL_Keymod modState = SDL_GetModState();
@ -1192,13 +1228,39 @@ int main(int argc, char* argv[]) {
fwrite("NSGRID", 1, 6, serialPort);
fwrite(buf, 1, sizeof(buf), serialPort);
fflush(serialPort);
printf("Grid exported to serial.\n");
printf("Grid exported to serial. Waiting for response...\n");
char response[256];
if (fgets(response, sizeof(response), serialPort)) {
printf("Device response: %s", response);
} else {
printf("No response from device.\n");
}
} else {
printf("Serial port not open. Pass device path as argument (e.g. ./NoiceSynth /dev/ttyACM0)\n");
}
}
exportButtonPressed = false;
}
if (importButtonPressed) {
int mx = e.button.x;
int my = e.button.y;
int synthX = mx - GRID_PANEL_WIDTH;
SDL_Rect importButtonRect = {410, 435, 100, 30};
if (mx >= GRID_PANEL_WIDTH &&
synthX >= importButtonRect.x && synthX <= importButtonRect.x + importButtonRect.w &&
my >= importButtonRect.y && my <= importButtonRect.y + importButtonRect.h) {
if (serialPort) {
fwrite("NSLOAD", 1, 6, serialPort);
fflush(serialPort);
printf("Requested grid import from device...\n");
} else {
printf("Serial port not open.\n");
}
}
importButtonPressed = false;
}
} else if (e.type == SDL_KEYUP) {
if (!auto_melody_enabled && e.key.keysym.scancode == current_key_scancode) {
engine.setGate(false);
@ -1278,6 +1340,7 @@ int main(int argc, char* argv[]) {
drawToggle(renderer, 580, 450, 30, auto_melody_enabled);
drawButton(renderer, 300, 435, 100, 30, "EXPORT", exportButtonPressed);
drawButton(renderer, 410, 435, 100, 30, "IMPORT", importButtonPressed);
// --- Draw Grid Panel (Left) ---
SDL_Rect gridViewport = {0, 0, GRID_PANEL_WIDTH, WINDOW_HEIGHT};

View File

@ -1,5 +1,6 @@
#include "synth_engine.h"
#include <math.h>
#include <string.h>
// A simple sine lookup table for the sine oscillator
const int SINE_TABLE_SIZE = 256;
@ -37,14 +38,6 @@ SynthEngine::SynthEngine(uint32_t sampleRate)
}
SynthEngine::~SynthEngine() {
for (int x = 0; x < GRID_W; ++x) {
for (int y = 0; y < GRID_H; ++y) {
if (grid[x][y].buffer) {
delete[] grid[x][y].buffer;
grid[x][y].buffer = nullptr;
}
}
}
}
void SynthEngine::exportGrid(uint8_t* buffer) {
@ -62,6 +55,7 @@ void SynthEngine::exportGrid(uint8_t* buffer) {
void SynthEngine::importGrid(const uint8_t* buffer) {
SynthLockGuard<SynthMutex> lock(gridMutex);
size_t idx = 0;
for(int y=0; y<GRID_H; ++y) {
for(int x=0; x<GRID_W; ++x) {
@ -71,15 +65,7 @@ void SynthEngine::importGrid(const uint8_t* buffer) {
uint8_t r = buffer[idx++];
GridCell::Type newType = (GridCell::Type)t;
if (c.type != newType) {
if (c.buffer) { delete[] c.buffer; c.buffer = nullptr; c.buffer_size = 0; }
c.type = newType;
if (c.type == GridCell::DELAY || c.type == GridCell::REVERB || c.type == GridCell::PITCH_SHIFTER) {
c.buffer_size = 2 * _sampleRate;
c.buffer = new float[c.buffer_size]();
c.write_idx = 0;
}
}
c.type = newType;
c.param = (float)p / 255.0f;
c.rotation = r;
}
@ -93,11 +79,6 @@ void SynthEngine::clearGrid() {
GridCell& c = grid[x][y];
if (c.type == GridCell::SINK) continue;
if (c.buffer) {
delete[] c.buffer;
c.buffer = nullptr;
c.buffer_size = 0;
}
c.type = GridCell::EMPTY;
c.param = 0.5f;
c.rotation = 0;
@ -257,7 +238,7 @@ float SynthEngine::_random() {
float SynthEngine::processGridStep() {
// Double buffer for values to handle feedback loops gracefully (1-sample delay)
float next_values[GRID_W][GRID_H];
static float next_values[GRID_W][GRID_H];
auto isConnected = [&](int tx, int ty, int from_x, int from_y) -> bool {
if (from_x < 0 || from_x >= GRID_W || from_y < 0 || from_y >= GRID_H) return false;
@ -545,50 +526,6 @@ float SynthEngine::processGridStep() {
// Mix between original and rectified based on param
float rect = fabsf(in);
val = in * (1.0f - c.param) + rect * c.param;
} else if (c.type == GridCell::PITCH_SHIFTER) {
float in = getInputFromTheBack(x, y, c);
if (c.buffer && c.buffer_size > 0) {
c.buffer[c.write_idx] = in;
// Granular pitch shift
// Pitch ratio: 0.5 to 2.0
float pitchRatio = 0.5f + c.param * 1.5f;
// Delay rate change: 1.0 - pitchRatio
// If pitch=1, rate=0 (delay constant). If pitch=2, rate=-1 (delay decreases).
float rate = 1.0f - pitchRatio;
c.phase += rate;
// Wrap phase within window (e.g. 4096 samples)
float windowSize = 4096.0f;
if (c.phase >= windowSize) c.phase -= windowSize;
if (c.phase < 0.0f) c.phase += windowSize;
// Read from buffer
// Simple crossfade windowing would be better, but for now just a single tap with moving delay
// To reduce clicks, we really need 2 taps. Let's stick to single tap for simplicity in this grid context, or maybe just a vibrato if rate is LFO?
// Actually, let's implement the 2-tap crossfade for quality.
// Tap 1
float p1 = c.phase;
float p2 = c.phase + windowSize * 0.5f;
if (p2 >= windowSize) p2 -= windowSize;
// Window function (Triangle)
auto getWindow = [&](float p) -> float {
return 1.0f - fabsf(2.0f * (p / windowSize) - 1.0f);
};
// Read indices
int r1 = (int)c.write_idx - (int)p1;
if (r1 < 0) r1 += c.buffer_size;
int r2 = (int)c.write_idx - (int)p2;
if (r2 < 0) r2 += c.buffer_size;
val = c.buffer[r1] * getWindow(p1) + c.buffer[r2] * getWindow(p2);
c.write_idx = (c.write_idx + 1) % c.buffer_size;
} else {
val = 0.0f;
}
} else if (c.type == GridCell::GLITCH) {
float in = getInputFromTheBack(x, y, c);
// Param controls probability of glitch
@ -601,55 +538,6 @@ float SynthEngine::processGridStep() {
} else {
val = in;
}
} else if (c.type == GridCell::DELAY) {
// Input is from the "Back" (rot+2)
float input_val = getInputFromTheBack(x, y, c);
if (c.buffer && c.buffer_size > 0) {
// Write current input to buffer
c.buffer[c.write_idx] = input_val;
// Calculate read index based on parameter. Max delay is buffer_size.
uint32_t delay_samples = c.param * (c.buffer_size - 1);
// Using modulo for wraparound. Need to handle negative result from subtraction.
int read_idx = (int)c.write_idx - (int)delay_samples;
if (read_idx < 0) {
read_idx += c.buffer_size;
}
// Read delayed value for output
val = c.buffer[read_idx];
// Increment write index
c.write_idx = (c.write_idx + 1) % c.buffer_size;
} else {
val = 0.0f; // No buffer, no output
}
} else if (c.type == GridCell::REVERB) {
// Input is from the "Back" (rot+2)
float input_val = getInputFromTheBack(x, y, c);
if (c.buffer && c.buffer_size > 0) {
// Fixed delay for reverb effect (e.g. 50ms)
uint32_t delay_samples = (uint32_t)(0.05f * _sampleRate);
if (delay_samples >= c.buffer_size) delay_samples = c.buffer_size - 1;
int read_idx = (int)c.write_idx - (int)delay_samples;
if (read_idx < 0) read_idx += c.buffer_size;
float delayed = c.buffer[read_idx];
// Feedback controlled by param (0.0 to 0.95)
float feedback = c.param * 0.95f;
float newValue = input_val + delayed * feedback;
c.buffer[c.write_idx] = newValue;
val = newValue;
c.write_idx = (c.write_idx + 1) % c.buffer_size;
} else {
val = 0.0f;
}
} else if (c.type == GridCell::OPERATOR || c.type == GridCell::SINK) {
// Gather inputs
float inputs[4];

View File

@ -104,9 +104,6 @@ public:
int rotation = 0; // 0:N, 1:E, 2:S, 3:W (Output direction)
float value = 0.0f; // Current output sample
float phase = 0.0f; // For Oscillator, Noise state
float* buffer = nullptr; // For Delay
uint32_t buffer_size = 0; // For Delay
uint32_t write_idx = 0; // For Delay
};
static const int GRID_W = 12;