diff --git a/synth_engine.cpp b/synth_engine.cpp index a262333..2e0c5ea 100644 --- a/synth_engine.cpp +++ b/synth_engine.cpp @@ -4,20 +4,48 @@ #include // A simple sine lookup table for the sine oscillator -const int SINE_TABLE_SIZE = 256; -static int16_t sine_table[SINE_TABLE_SIZE]; -static bool sine_table_filled = false; +const int WAVE_TABLE_SIZE = 256; +const int NUM_WAVEFORMS = 8; +static int16_t wave_tables[NUM_WAVEFORMS][WAVE_TABLE_SIZE]; +static bool wave_tables_filled = false; /** - * @brief Fills the global sine table. Called once on startup. + * @brief Fills the global wave tables. Called once on startup. */ -void fill_sine_table() { - if (sine_table_filled) return; - for (int i = 0; i < SINE_TABLE_SIZE; ++i) { - // M_PI is not standard C++, but it's common. If it fails, use 3.1415926535... - sine_table[i] = static_cast(sin(2.0 * M_PI * i / SINE_TABLE_SIZE) * 32767.0); +void fill_wave_tables() { + if (wave_tables_filled) return; + for (int i = 0; i < WAVE_TABLE_SIZE; ++i) { + double phase = (double)i / (double)WAVE_TABLE_SIZE; + double pi2 = 2.0 * M_PI; + + // 0: Sine + wave_tables[0][i] = (int16_t)(sin(pi2 * phase) * 32767.0); + + // 1: Sawtooth (Rising) + wave_tables[1][i] = (int16_t)((2.0 * phase - 1.0) * 32767.0); + + // 2: Square + wave_tables[2][i] = (int16_t)((phase < 0.5 ? 1.0 : -1.0) * 32767.0); + + // 3: Triangle + double tri = (phase < 0.5) ? (4.0 * phase - 1.0) : (3.0 - 4.0 * phase); + wave_tables[3][i] = (int16_t)(tri * 32767.0); + + // 4: Ramp (Falling Saw) + wave_tables[4][i] = (int16_t)((1.0 - 2.0 * phase) * 32767.0); + + // 5: Pulse 25% + wave_tables[5][i] = (int16_t)((phase < 0.25 ? 1.0 : -1.0) * 32767.0); + + // 6: Distorted Sine + double d = sin(pi2 * phase) + 0.3 * sin(2.0 * pi2 * phase); + wave_tables[6][i] = (int16_t)((d / 1.3) * 32767.0); + + // 7: Organ + double o = 0.6 * sin(pi2 * phase) + 0.2 * sin(2.0 * pi2 * phase) + 0.1 * sin(4.0 * pi2 * phase); + wave_tables[7][i] = (int16_t)((o / 0.9) * 32767.0); } - sine_table_filled = true; + wave_tables_filled = true; } SynthEngine::SynthEngine(uint32_t sampleRate) @@ -31,7 +59,7 @@ SynthEngine::SynthEngine(uint32_t sampleRate) _freqToPhaseInc(0.0f), _rngState(12345) { - fill_sine_table(); + fill_wave_tables(); // Initialize with a default frequency setFrequency(440.0f); @@ -295,6 +323,7 @@ void SynthEngine::rebuildProcessingOrder_locked() { // Start BFS from the SINK backwards q.push_back({GRID_W / 2, GRID_H - 1}); visited[GRID_W / 2][GRID_H - 1] = true; + _processing_order.push_back({GRID_W / 2, GRID_H - 1}); int head = 0; while(head < (int)q.size()) { @@ -335,12 +364,13 @@ void SynthEngine::rebuildProcessingOrder_locked() { if (pointsToCurr) { visited[tx][ty] = true; q.push_back({tx, ty}); + if (grid[tx][ty].type != GridCell::WIRE) { + _processing_order.push_back({tx, ty}); + } } } } } - - _processing_order = q; } void SynthEngine::rebuildProcessingOrder() { @@ -352,45 +382,45 @@ void SynthEngine::updateGraph() { rebuildProcessingOrder_locked(); } -int32_t SynthEngine::processGridStep() { - - 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; - GridCell& n = grid[from_x][from_y]; +bool SynthEngine::isConnected(int tx, int ty, int from_x, int from_y) { + if (from_x < 0 || from_x >= GRID_W || from_y < 0 || from_y >= GRID_H) return false; + GridCell& n = grid[from_x][from_y]; + + bool connects = false; + if (n.type == GridCell::WIRE || n.type == GridCell::FIXED_OSCILLATOR || n.type == GridCell::INPUT_OSCILLATOR || n.type == GridCell::WAVETABLE || n.type == GridCell::NOISE || n.type == GridCell::LFO || n.type == GridCell::GATE_INPUT || n.type == GridCell::ADSR_ATTACK || n.type == GridCell::ADSR_DECAY || n.type == GridCell::ADSR_SUSTAIN || n.type == GridCell::ADSR_RELEASE || n.type == GridCell::LPF || n.type == GridCell::HPF || n.type == GridCell::VCA || n.type == GridCell::BITCRUSHER || n.type == GridCell::DISTORTION || n.type == GridCell::RECTIFIER || n.type == GridCell::PITCH_SHIFTER || n.type == GridCell::GLITCH || n.type == GridCell::OPERATOR || n.type == GridCell::DELAY || n.type == GridCell::REVERB) { + // Check rotation + // 0:N (y-1), 1:E (x+1), 2:S (y+1), 3:W (x-1) + if (n.rotation == 0 && from_y - 1 == ty && from_x == tx) connects = true; + if (n.rotation == 1 && from_x + 1 == tx && from_y == ty) connects = true; + if (n.rotation == 2 && from_y + 1 == ty && from_x == tx) connects = true; + if (n.rotation == 3 && from_x - 1 == tx && from_y == ty) connects = true; + } else if (n.type == GridCell::FORK) { + // Fork outputs to Left (rot+3) and Right (rot+1) relative to its rotation + // n.rotation is "Forward" + int dx = tx - from_x; + int dy = ty - from_y; + int dir = -1; + if (dx == 0 && dy == -1) dir = 0; // N + if (dx == 1 && dy == 0) dir = 1; // E + if (dx == 0 && dy == 1) dir = 2; // S + if (dx == -1 && dy == 0) dir = 3; // W - bool connects = false; - if (n.type == GridCell::WIRE || n.type == GridCell::FIXED_OSCILLATOR || n.type == GridCell::INPUT_OSCILLATOR || n.type == GridCell::WAVETABLE || n.type == GridCell::NOISE || n.type == GridCell::LFO || n.type == GridCell::GATE_INPUT || n.type == GridCell::ADSR_ATTACK || n.type == GridCell::ADSR_DECAY || n.type == GridCell::ADSR_SUSTAIN || n.type == GridCell::ADSR_RELEASE || n.type == GridCell::LPF || n.type == GridCell::HPF || n.type == GridCell::VCA || n.type == GridCell::BITCRUSHER || n.type == GridCell::DISTORTION || n.type == GridCell::RECTIFIER || n.type == GridCell::PITCH_SHIFTER || n.type == GridCell::GLITCH || n.type == GridCell::OPERATOR || n.type == GridCell::DELAY || n.type == GridCell::REVERB) { - // Check rotation - // 0:N (y-1), 1:E (x+1), 2:S (y+1), 3:W (x-1) - if (n.rotation == 0 && from_y - 1 == ty && from_x == tx) connects = true; - if (n.rotation == 1 && from_x + 1 == tx && from_y == ty) connects = true; - if (n.rotation == 2 && from_y + 1 == ty && from_x == tx) connects = true; - if (n.rotation == 3 && from_x - 1 == tx && from_y == ty) connects = true; - } else if (n.type == GridCell::FORK) { - // Fork outputs to Left (rot+3) and Right (rot+1) relative to its rotation - // n.rotation is "Forward" - int dx = tx - from_x; - int dy = ty - from_y; - int dir = -1; - if (dx == 0 && dy == -1) dir = 0; // N - if (dx == 1 && dy == 0) dir = 1; // E - if (dx == 0 && dy == 1) dir = 2; // S - if (dx == -1 && dy == 0) dir = 3; // W - - int leftOut = (n.rotation + 3) % 4; - int rightOut = (n.rotation + 1) % 4; - - if (dir == leftOut || dir == rightOut) connects = true; - } - return connects; - }; + int leftOut = (n.rotation + 3) % 4; + int rightOut = (n.rotation + 1) % 4; + + if (dir == leftOut || dir == rightOut) connects = true; + } + return connects; +} - // Helper to get input from a neighbor - auto getInput = [&](int tx, int ty, int from_x, int from_y) -> int32_t { - if (!isConnected(tx, ty, from_x, from_y)) return 0; - GridCell& n = grid[from_x][from_y]; +int32_t SynthEngine::getInput(int tx, int ty, int from_x, int from_y, int depth) { + if (depth > 16) return 0; // Prevent infinite loops + if (!isConnected(tx, ty, from_x, from_y)) return 0; + GridCell& n = grid[from_x][from_y]; - if (n.type == GridCell::FORK) { + if (n.type == GridCell::WIRE) { + return getSummedInput(from_x, from_y, n, depth + 1); + } else if (n.type == GridCell::FORK) { int dx = tx - from_x; int dy = ty - from_y; int dir = -1; @@ -404,21 +434,22 @@ int32_t SynthEngine::processGridStep() { if (dir == leftOut) return (n.value * (FP_ONE - n.param)) >> (FP_SHIFT - 1); if (dir == rightOut) return (n.value * n.param) >> (FP_SHIFT - 1); - } - - return n.value; - }; + } + + return n.value; +} - // Helper to sum inputs excluding the output direction - auto getSummedInput = [&](int x, int y, GridCell& c) -> int32_t { - int32_t sum = 0; - int outDir = c.rotation; // 0:N, 1:E, 2:S, 3:W - if (outDir != 0) sum += getInput(x, y, x, y-1); - if (outDir != 1) sum += getInput(x, y, x+1, y); - if (outDir != 2) sum += getInput(x, y, x, y+1); - if (outDir != 3) sum += getInput(x, y, x-1, y); - return sum; - }; +int32_t SynthEngine::getSummedInput(int x, int y, GridCell& c, int depth) { + int32_t sum = 0; + int outDir = c.rotation; // 0:N, 1:E, 2:S, 3:W + if (outDir != 0) sum += getInput(x, y, x, y-1, depth); + if (outDir != 1) sum += getInput(x, y, x+1, y, depth); + if (outDir != 2) sum += getInput(x, y, x, y+1, depth); + if (outDir != 3) sum += getInput(x, y, x-1, y, depth); + return sum; +} + +int32_t SynthEngine::processGridStep() { auto getInputFromTheBack = [&](int x, int y, GridCell& c) -> int32_t { int inDir = (c.rotation + 2) % 4; @@ -462,7 +493,7 @@ int32_t SynthEngine::processGridStep() { uint32_t inc = freq * 97391; c.phase_accumulator += inc; // Top 8 bits of 32-bit accumulator form the 256-entry table index - val = sine_table[c.phase_accumulator >> 24]; + val = wave_tables[0][c.phase_accumulator >> 24]; val = (val * getSideInputGain(x, y, c)) >> FP_SHIFT; } else if (c.type == GridCell::INPUT_OSCILLATOR) { int32_t mod = getInputFromTheBack(x, y, c); @@ -478,7 +509,7 @@ int32_t SynthEngine::processGridStep() { inc += (int32_t)(((int64_t)mod * 500 * 97391) >> FP_SHIFT); c.phase_accumulator += inc; - val = sine_table[c.phase_accumulator >> 24]; + val = wave_tables[0][c.phase_accumulator >> 24]; val = (val * getSideInputGain(x, y, c)) >> FP_SHIFT; } else if (c.type == GridCell::WAVETABLE) { int32_t mod = getInputFromTheBack(x, y, c); @@ -488,19 +519,8 @@ int32_t SynthEngine::processGridStep() { c.phase_accumulator += inc; int wave_select = (c.param * 8) >> FP_SHIFT; - bool phase_upper = (c.phase_accumulator & 0x80000000); - - switch(wave_select) { - case 0: val = sine_table[c.phase_accumulator >> 24]; break; - case 1: val = (int32_t)((c.phase_accumulator >> 16) & 0xFFFF) - 32768; break; // Saw - case 2: val = phase_upper ? -32767 : 32767; break; // Square - case 3: val = sine_table[c.phase_accumulator >> 24]; break; // Triangle (fallback) - case 4: val = 32767 - (int32_t)((c.phase_accumulator >> 16) & 0xFFFF); break; // Ramp - case 5: val = ((c.phase_accumulator >> 30) == 0) ? 32767 : -32767; break; // Pulse 25% - default: - val = sine_table[c.phase_accumulator >> 24]; - break; - } + if (wave_select > 7) wave_select = 7; + val = wave_tables[wave_select][c.phase_accumulator >> 24]; val = (val * getSideInputGain(x, y, c)) >> FP_SHIFT; } else if (c.type == GridCell::NOISE) { int32_t mod = getInputFromTheBack(x, y, c); @@ -538,7 +558,7 @@ int32_t SynthEngine::processGridStep() { uint32_t inc = freq_x10 * 9739; c.phase_accumulator += inc; // Output full range -1.0 to 1.0 - val = sine_table[c.phase_accumulator >> 24]; + val = wave_tables[0][c.phase_accumulator >> 24]; } else if (c.type == GridCell::FORK) { // Sum inputs from "Back" (Input direction) val = getInputFromTheBack(x, y, c); @@ -573,7 +593,7 @@ int32_t SynthEngine::processGridStep() { val = (in * c.param) >> FP_SHIFT; } else if (c.type == GridCell::WIRE) { // Sum inputs from all neighbors that point to me - val = getSummedInput(x, y, c); + val = getSummedInput(x, y, c, 0); } else if (c.type == GridCell::LPF) { // Input from Back int32_t in = getInputFromTheBack(x, y, c); @@ -599,7 +619,7 @@ int32_t SynthEngine::processGridStep() { int32_t in = getInputFromTheBack(x, y, c); // Mod from other directions (sum) - int32_t mod = getSummedInput(x, y, c); + int32_t mod = getSummedInput(x, y, c, 0); mod -= in; // Remove signal input from mod sum (it was included in getInput calls) // Gain = Param + Mod diff --git a/synth_engine.h b/synth_engine.h index 109d05f..8d0fdd3 100644 --- a/synth_engine.h +++ b/synth_engine.h @@ -146,6 +146,10 @@ private: std::vector> _processing_order; void rebuildProcessingOrder_locked(); + bool isConnected(int tx, int ty, int from_x, int from_y); + int32_t getInput(int tx, int ty, int from_x, int from_y, int depth = 0); + int32_t getSummedInput(int x, int y, GridCell& c, int depth = 0); + // Internal random number generator int32_t _random(); };