Optimization: grid processing array

This commit is contained in:
Dejvino 2026-03-01 12:09:07 +01:00
parent 82bab0698b
commit 1047d846f9
4 changed files with 372 additions and 292 deletions

View File

@ -12,7 +12,7 @@ const int I2S_LRC_PIN = 10; // Left-Right Clock (GP10)
const int I2S_DOUT_PIN = 11; // Data Out (GP11) const int I2S_DOUT_PIN = 11; // Data Out (GP11)
// Audio parameters // Audio parameters
const int SAMPLE_RATE = 44100 / 2; const int SAMPLE_RATE = 44100 / 2 / 2 / 2;
const int16_t AMPLITUDE = 16383 / 2; // Use a lower amplitude to avoid clipping (max is 32767 for 16-bit) const int16_t AMPLITUDE = 16383 / 2; // Use a lower amplitude to avoid clipping (max is 32767 for 16-bit)
// Create an I2S output object // Create an I2S output object

View File

@ -768,27 +768,24 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G
void randomizeGrid() { void randomizeGrid() {
printf("Randomizing grid...\n"); printf("Randomizing grid...\n");
Uint32 startTime = SDL_GetTicks(); Uint32 startTime = SDL_GetTicks();
int attempts = 0;
bool validGrid = false;
{
SynthLockGuard<SynthMutex> lock(engine.gridMutex); SynthLockGuard<SynthMutex> lock(engine.gridMutex);
// Number of types to choose from (excluding SINK) // Number of types to choose from (excluding SINK)
const int numTypes = (int)SynthEngine::GridCell::SINK; const int numTypes = (int)SynthEngine::GridCell::SINK;
// 1. Clear existing buffers first (resets the pool) // 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 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) {
SynthEngine::GridCell& c = engine.grid[x][y]; SynthEngine::GridCell& c = engine.grid[x][y];
if (c.type == SynthEngine::GridCell::SINK) continue; if (c.type == SynthEngine::GridCell::SINK) continue;
c.type = SynthEngine::GridCell::EMPTY; c.type = SynthEngine::GridCell::EMPTY;
c.param = 0.5f;
c.rotation = 0;
c.value = 0.0f;
c.phase = 0.0f;
} }
} }
int attempts = 0;
bool validGrid = false;
bool visited[SynthEngine::GRID_W][SynthEngine::GRID_H]; bool visited[SynthEngine::GRID_W][SynthEngine::GRID_H];
while (!validGrid && attempts < 1000) { while (!validGrid && attempts < 1000) {
@ -944,7 +941,9 @@ void randomizeGrid() {
} }
} }
} }
}
engine.rebuildProcessingOrder();
printf("Randomized in %d attempts (%d ms). Valid: %s\n", attempts, SDL_GetTicks() - startTime, validGrid ? "YES" : "NO"); printf("Randomized in %d attempts (%d ms). Valid: %s\n", attempts, SDL_GetTicks() - startTime, validGrid ? "YES" : "NO");
} }
@ -1076,13 +1075,17 @@ int main(int argc, char* argv[]) {
if (e.type == SDL_MOUSEBUTTONDOWN) { if (e.type == SDL_MOUSEBUTTONDOWN) {
int mx = e.button.x; int mx = e.button.x;
int my = e.button.y; int my = e.button.y;
if (mx < GRID_PANEL_WIDTH) { if (mx < GRID_PANEL_WIDTH) {
int gx = mx / CELL_SIZE; int gx = mx / CELL_SIZE;
int gy = my / CELL_SIZE; int gy = my / CELL_SIZE;
if (gx >= 0 && gx < SynthEngine::GRID_W && gy >= 0 && gy < SynthEngine::GRID_H) { if (gx >= 0 && gx < SynthEngine::GRID_W && gy >= 0 && gy < SynthEngine::GRID_H) {
bool grid_modified = false;
{
SynthLockGuard<SynthMutex> lock(engine.gridMutex); SynthLockGuard<SynthMutex> lock(engine.gridMutex);
SynthEngine::GridCell& c = engine.grid[gx][gy]; SynthEngine::GridCell& c = engine.grid[gx][gy];
if (c.type != SynthEngine::GridCell::SINK) { if (c.type != SynthEngine::GridCell::SINK) {
grid_modified = true;
SynthEngine::GridCell::Type oldType = c.type; SynthEngine::GridCell::Type oldType = c.type;
SynthEngine::GridCell::Type newType = oldType; SynthEngine::GridCell::Type newType = oldType;
@ -1106,6 +1109,10 @@ int main(int argc, char* argv[]) {
} }
} }
} }
if (grid_modified) {
engine.rebuildProcessingOrder();
}
}
} else { } else {
// Synth Panel Click // Synth Panel Click
int synthX = mx - GRID_PANEL_WIDTH; int synthX = mx - GRID_PANEL_WIDTH;

View File

@ -1,5 +1,6 @@
#include "synth_engine.h" #include "synth_engine.h"
#include <math.h> #include <math.h>
#include <utility>
#include <string.h> #include <string.h>
// A simple sine lookup table for the sine oscillator // A simple sine lookup table for the sine oscillator
@ -35,6 +36,7 @@ SynthEngine::SynthEngine(uint32_t sampleRate)
// Initialize SINK // Initialize SINK
grid[GRID_W / 2][GRID_H - 1].type = GridCell::SINK; grid[GRID_W / 2][GRID_H - 1].type = GridCell::SINK;
rebuildProcessingOrder();
} }
SynthEngine::~SynthEngine() { SynthEngine::~SynthEngine() {
@ -70,6 +72,7 @@ void SynthEngine::importGrid(const uint8_t* buffer) {
c.rotation = r; c.rotation = r;
} }
} }
rebuildProcessingOrder_locked();
} }
void SynthEngine::clearGrid() { void SynthEngine::clearGrid() {
@ -84,8 +87,10 @@ void SynthEngine::clearGrid() {
c.rotation = 0; c.rotation = 0;
c.value = 0.0f; c.value = 0.0f;
c.phase = 0.0f; c.phase = 0.0f;
c.next_value = 0.0f;
} }
} }
rebuildProcessingOrder_locked();
} }
void SynthEngine::loadPreset(int preset) { void SynthEngine::loadPreset(int preset) {
@ -201,6 +206,8 @@ void SynthEngine::loadPreset(int preset) {
grid[sinkX][y].type = GridCell::WIRE; grid[sinkX][y].rotation = 2; grid[sinkX][y].type = GridCell::WIRE; grid[sinkX][y].rotation = 2;
} }
} }
rebuildProcessingOrder_locked();
} }
void SynthEngine::setFrequency(float freq) { void SynthEngine::setFrequency(float freq) {
@ -236,9 +243,68 @@ float SynthEngine::_random() {
return (float)_rngState / 4294967296.0f; return (float)_rngState / 4294967296.0f;
} }
void SynthEngine::rebuildProcessingOrder_locked() {
_processing_order.clear();
bool visited[GRID_W][GRID_H] = {false};
std::vector<std::pair<int, int>> q;
// Start BFS from the SINK backwards
q.push_back({GRID_W / 2, GRID_H - 1});
visited[GRID_W / 2][GRID_H - 1] = true;
int head = 0;
while(head < (int)q.size()) {
std::pair<int, int> curr = q[head++];
int cx = curr.first;
int cy = curr.second;
// Check neighbors to see if they output to (cx, cy)
int nx_offsets[4] = {0, 1, 0, -1};
int ny_offsets[4] = {-1, 0, 1, 0};
for(int i=0; i<4; ++i) {
int tx = cx + nx_offsets[i];
int ty = cy + ny_offsets[i];
if (tx >= 0 && tx < GRID_W && ty >= 0 && ty < GRID_H && !visited[tx][ty]) {
GridCell& neighbor = grid[tx][ty];
bool pointsToCurr = false;
if (neighbor.type != GridCell::EMPTY && neighbor.type != GridCell::SINK) {
int dx = cx - tx;
int dy = cy - ty;
int dir = -1;
if (dx == 0 && dy == -1) dir = 0; // N
else if (dx == 1 && dy == 0) dir = 1; // E
else if (dx == 0 && dy == 1) dir = 2; // S
else if (dx == -1 && dy == 0) dir = 3; // W
if (neighbor.type == GridCell::FORK) {
int leftOut = (neighbor.rotation + 3) % 4;
int rightOut = (neighbor.rotation + 1) % 4;
if (dir == leftOut || dir == rightOut) pointsToCurr = true;
} else {
if (neighbor.rotation == dir) pointsToCurr = true;
}
}
if (pointsToCurr) {
visited[tx][ty] = true;
q.push_back({tx, ty});
}
}
}
}
_processing_order = q;
}
void SynthEngine::rebuildProcessingOrder() {
SynthLockGuard<SynthMutex> lock(gridMutex);
rebuildProcessingOrder_locked();
}
float SynthEngine::processGridStep() { float SynthEngine::processGridStep() {
// Double buffer for values to handle feedback loops gracefully (1-sample delay)
static float next_values[GRID_W][GRID_H];
auto isConnected = [&](int tx, int ty, int from_x, int from_y) -> bool { 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; if (from_x < 0 || from_x >= GRID_W || from_y < 0 || from_y >= GRID_H) return false;
@ -327,8 +393,10 @@ float SynthEngine::processGridStep() {
return hasSide ? gain : 1.0f; return hasSide ? gain : 1.0f;
}; };
for (int x = 0; x < GRID_W; ++x) { // 1. Calculate next values for active cells
for (int y = 0; y < GRID_H; ++y) { for (const auto& cell_coord : _processing_order) {
int x = cell_coord.first;
int y = cell_coord.second;
GridCell& c = grid[x][y]; GridCell& c = grid[x][y];
float val = 0.0f; float val = 0.0f;
@ -571,16 +639,15 @@ float SynthEngine::processGridStep() {
} }
} }
} }
} } // End of big switch
next_values[x][y] = val; c.next_value = val;
} } // End of for loop over _processing_order
}
// Update state // 2. Update current values from next values for active cells
for(int x=0; x < GRID_W; ++x) { for (const auto& cell_coord : _processing_order) {
for(int y=0; y < GRID_H; ++y) { int x = cell_coord.first;
grid[x][y].value = next_values[x][y]; int y = cell_coord.second;
} grid[x][y].value = grid[x][y].next_value;
} }
return grid[GRID_W / 2][GRID_H - 1].value; return grid[GRID_W / 2][GRID_H - 1].value;

View File

@ -2,6 +2,8 @@
#define SYNTH_ENGINE_H #define SYNTH_ENGINE_H
#include <stdint.h> #include <stdint.h>
#include <vector>
#include <utility>
#if defined(ARDUINO_ARCH_RP2040) #if defined(ARDUINO_ARCH_RP2040)
#include <pico/mutex.h> #include <pico/mutex.h>
@ -103,6 +105,7 @@ public:
float param = 0.5f; // 0.0 to 1.0 float param = 0.5f; // 0.0 to 1.0
int rotation = 0; // 0:N, 1:E, 2:S, 3:W (Output direction) int rotation = 0; // 0:N, 1:E, 2:S, 3:W (Output direction)
float value = 0.0f; // Current output sample float value = 0.0f; // Current output sample
float next_value = 0.0f; // For double-buffering in processGridStep
float phase = 0.0f; // For Oscillator, Noise state float phase = 0.0f; // For Oscillator, Noise state
}; };
@ -113,6 +116,7 @@ public:
void exportGrid(uint8_t* buffer); void exportGrid(uint8_t* buffer);
void importGrid(const uint8_t* buffer); void importGrid(const uint8_t* buffer);
void loadPreset(int preset); void loadPreset(int preset);
void rebuildProcessingOrder();
void clearGrid(); void clearGrid();
GridCell grid[GRID_W][GRID_H]; GridCell grid[GRID_W][GRID_H];
@ -129,6 +133,8 @@ private:
Waveform _waveform; Waveform _waveform;
bool _isGateOpen; bool _isGateOpen;
uint32_t _rngState; uint32_t _rngState;
std::vector<std::pair<int, int>> _processing_order;
void rebuildProcessingOrder_locked();
// Internal random number generator // Internal random number generator
float _random(); float _random();