diff --git a/AudioThread.cpp b/AudioThread.cpp index 73ca647..4cfc765 100644 --- a/AudioThread.cpp +++ b/AudioThread.cpp @@ -51,6 +51,7 @@ void setupAudio() { globalSynth = new SynthEngine(SAMPLE_RATE); if (globalSynth) { globalSynth->loadPreset(2); + globalSynth->setVolume(0.2f); } } diff --git a/simulator/main.cpp b/simulator/main.cpp index d45043d..c8eae31 100644 --- a/simulator/main.cpp +++ b/simulator/main.cpp @@ -465,10 +465,10 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G drawDirectionArrow(renderer, cx, cy, size, cell.rotation); // Param (Freq) char buf[16]; - snprintf(buf, 16, "%.0f", 10.0f + cell.param*990.0f); + snprintf(buf, 16, "%.0f", 10.0f + (cell.param / 32767.0f)*990.0f); SDL_SetRenderDrawColor(renderer, 0, 255, 255, 255); drawString(renderer, x + 5, y + size - 18, 10, buf); - drawParamBar(renderer, x, y, size, cell.param, 0, 255, 255); + drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 0, 255, 255); drawTypeLabel(renderer, x, y, 'O'); } else if (cell.type == SynthEngine::GridCell::INPUT_OSCILLATOR) { DrawCircle(renderer, cx, cy, r); @@ -482,10 +482,10 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G SDL_RenderDrawLine(renderer, cx, cy, cx+dx, cy+dy); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); // Param (Octave) - char buf[16]; snprintf(buf, 16, "O%d", 1 + (int)(cell.param * 4.99f)); + char buf[16]; snprintf(buf, 16, "O%d", 1 + (int)((cell.param / 32767.0f) * 4.99f)); SDL_SetRenderDrawColor(renderer, 255, 200, 0, 255); drawString(renderer, x + 5, y + size - 18, 10, buf); - drawParamBar(renderer, x, y, size, cell.param, 255, 200, 0); + drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 255, 200, 0); drawTypeLabel(renderer, x, y, 'I'); } else if (cell.type == SynthEngine::GridCell::NOISE) { // Draw static/noise pattern @@ -503,10 +503,10 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G // Param (Color) const char* colors[] = {"BRN", "PNK", "WHT", "YEL", "GRN"}; - int idx = (int)(cell.param * 4.99f); + int idx = (int)((cell.param / 32767.0f) * 4.99f); SDL_SetRenderDrawColor(renderer, 200, 200, 200, 255); drawString(renderer, x + 5, y + size - 18, 10, colors[idx]); - drawParamBar(renderer, x, y, size, cell.param, 200, 200, 200); + drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 200, 200, 200); drawTypeLabel(renderer, x, y, 'N'); } else if (cell.type == SynthEngine::GridCell::LFO) { DrawCircle(renderer, cx, cy, r); @@ -520,16 +520,16 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G drawDirectionArrow(renderer, cx, cy, size, cell.rotation); drawString(renderer, cx - 8, cy - 5, 12, "LFO"); // Param (Freq) - char buf[16]; snprintf(buf, 16, "%.1f", 0.1f + cell.param * 19.9f); + char buf[16]; snprintf(buf, 16, "%.1f", 0.1f + (cell.param / 32767.0f) * 19.9f); SDL_SetRenderDrawColor(renderer, 0, 255, 255, 255); drawString(renderer, x + 5, y + size - 18, 10, buf); - drawParamBar(renderer, x, y, size, cell.param, 0, 255, 255); + drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 0, 255, 255); drawTypeLabel(renderer, x, y, 'L'); } else if (cell.type == SynthEngine::GridCell::GATE_INPUT) { SDL_Rect box = {cx - r, cy - r, r*2, r*2}; SDL_RenderDrawRect(renderer, &box); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); - if (cell.value > 0.5f) SDL_RenderFillRect(renderer, &box); + if (cell.value > 16384) SDL_RenderFillRect(renderer, &box); drawString(renderer, cx - 8, cy - 5, 12, "G-IN"); drawTypeLabel(renderer, x, y, 'K'); } else if (cell.type == SynthEngine::GridCell::ADSR_ATTACK) { @@ -545,7 +545,7 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G SDL_RenderDrawLine(renderer, cx, cy, cx+odx, cy+ody); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); - drawParamBar(renderer, x, y, size, cell.param, 255, 255, 255); + drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 255, 255, 255); drawTypeLabel(renderer, x, y, 'A'); } else if (cell.type == SynthEngine::GridCell::ADSR_DECAY) { // Draw Ramp Down @@ -560,7 +560,7 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G SDL_RenderDrawLine(renderer, cx, cy, cx+odx, cy+ody); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); - drawParamBar(renderer, x, y, size, cell.param, 255, 255, 255); + drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 255, 255, 255); drawTypeLabel(renderer, x, y, 'D'); } else if (cell.type == SynthEngine::GridCell::ADSR_SUSTAIN) { // Draw Level @@ -575,7 +575,7 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G SDL_RenderDrawLine(renderer, cx, cy, cx+odx, cy+ody); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); - drawParamBar(renderer, x, y, size, cell.param, 255, 255, 255); + drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 255, 255, 255); drawTypeLabel(renderer, x, y, 'S'); } else if (cell.type == SynthEngine::GridCell::ADSR_RELEASE) { // Draw Ramp Down @@ -590,7 +590,7 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G SDL_RenderDrawLine(renderer, cx, cy, cx+odx, cy+ody); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); - drawParamBar(renderer, x, y, size, cell.param, 255, 255, 255); + drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 255, 255, 255); drawTypeLabel(renderer, x, y, 'E'); } else if (cell.type == SynthEngine::GridCell::LPF || cell.type == SynthEngine::GridCell::HPF) { // Box @@ -608,10 +608,10 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G SDL_RenderDrawLine(renderer, cx, cy, cx+odx, cy+ody); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); // Param - char buf[16]; snprintf(buf, 16, "%.2f", cell.param); + char buf[16]; snprintf(buf, 16, "%.2f", cell.param / 32767.0f); SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); drawString(renderer, x + 5, y + size - 18, 10, buf); - drawParamBar(renderer, x, y, size, cell.param, 0, 255, 0); + drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 0, 255, 0); drawTypeLabel(renderer, x, y, cell.type == SynthEngine::GridCell::LPF ? 'P' : 'H'); } else if (cell.type == SynthEngine::GridCell::VCA) { // Triangle shape for Amp @@ -629,10 +629,10 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G SDL_RenderDrawLine(renderer, cx, cy, cx+odx, cy+ody); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); // Param - char buf[16]; snprintf(buf, 16, "%.2f", cell.param); + char buf[16]; snprintf(buf, 16, "%.2f", cell.param / 32767.0f); SDL_SetRenderDrawColor(renderer, 255, 255, 0, 255); drawString(renderer, x + 5, y + size - 18, 10, buf); - drawParamBar(renderer, x, y, size, cell.param, 255, 255, 0); + drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 255, 255, 0); drawTypeLabel(renderer, x, y, 'A'); } else if (cell.type == SynthEngine::GridCell::BITCRUSHER) { SDL_Rect box = {cx - r, cy - r, r*2, r*2}; @@ -648,10 +648,10 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G SDL_RenderDrawLine(renderer, cx, cy, cx+odx, cy+ody); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); // Param - char buf[16]; snprintf(buf, 16, "%.2f", cell.param); + char buf[16]; snprintf(buf, 16, "%.2f", cell.param / 32767.0f); SDL_SetRenderDrawColor(renderer, 255, 0, 255, 255); drawString(renderer, x + 5, y + size - 18, 10, buf); - drawParamBar(renderer, x, y, size, cell.param, 255, 0, 255); + drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 255, 0, 255); drawTypeLabel(renderer, x, y, 'B'); } else if (cell.type == SynthEngine::GridCell::DISTORTION) { SDL_Rect box = {cx - r, cy - r, r*2, r*2}; @@ -666,10 +666,10 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G if(cell.rotation==0) ody=-r; else if(cell.rotation==1) odx=r; else if(cell.rotation==2) ody=r; else odx=-r; SDL_RenderDrawLine(renderer, cx, cy, cx+odx, cy+ody); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); - char buf[16]; snprintf(buf, 16, "%.2f", cell.param); + char buf[16]; snprintf(buf, 16, "%.2f", cell.param / 32767.0f); SDL_SetRenderDrawColor(renderer, 255, 100, 100, 255); drawString(renderer, x + 5, y + size - 18, 10, buf); - drawParamBar(renderer, x, y, size, cell.param, 255, 100, 100); + drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 255, 100, 100); drawTypeLabel(renderer, x, y, 'X'); } else if (cell.type == SynthEngine::GridCell::RECTIFIER) { SDL_Rect box = {cx - r, cy - r, r*2, r*2}; @@ -684,7 +684,7 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G if(cell.rotation==0) ody=-r; else if(cell.rotation==1) odx=r; else if(cell.rotation==2) ody=r; else odx=-r; SDL_RenderDrawLine(renderer, cx, cy, cx+odx, cy+ody); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); - drawParamBar(renderer, x, y, size, cell.param, 255, 150, 0); + drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 255, 150, 0); drawTypeLabel(renderer, x, y, '|'); } else if (cell.type == SynthEngine::GridCell::PITCH_SHIFTER) { drawString(renderer, cx - 8, cy - 5, 12, "PIT"); @@ -697,7 +697,7 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G if(cell.rotation==0) ody=-r; else if(cell.rotation==1) odx=r; else if(cell.rotation==2) ody=r; else odx=-r; SDL_RenderDrawLine(renderer, cx, cy, cx+odx, cy+ody); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); - drawParamBar(renderer, x, y, size, cell.param, 100, 255, 100); + drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 100, 255, 100); drawTypeLabel(renderer, x, y, '^'); } else if (cell.type == SynthEngine::GridCell::GLITCH) { drawString(renderer, cx - 8, cy - 5, 12, "GLT"); @@ -711,7 +711,7 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G SDL_RenderDrawLine(renderer, cx, cy, cx+odx, cy+ody); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); - drawParamBar(renderer, x, y, size, cell.param, 255, 0, 0); + drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 255, 0, 0); drawTypeLabel(renderer, x, y, 'G'); } else if (cell.type == SynthEngine::GridCell::FORK) { // Draw Y shape based on rotation @@ -734,10 +734,10 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G drawDirectionArrow(renderer, cx, cy, size, rDir); // Param (Balance) - char buf[16]; snprintf(buf, 16, "%.1f", cell.param); + char buf[16]; snprintf(buf, 16, "%.1f", cell.param / 32767.0f); SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); drawString(renderer, x + 5, y + size - 18, 10, buf); - drawParamBar(renderer, x, y, size, cell.param, 0, 255, 0); + drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 0, 255, 0); drawTypeLabel(renderer, x, y, 'Y'); } else if (cell.type == SynthEngine::GridCell::DELAY) { // Draw D @@ -756,11 +756,11 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G // Param (Delay time in ms) char buf[16]; - float delay_ms = cell.param * 2000.0f; // Max 2 seconds + float delay_ms = (cell.param / 32767.0f) * 2000.0f; // Max 2 seconds snprintf(buf, 16, "%.0fms", delay_ms); SDL_SetRenderDrawColor(renderer, 255, 128, 0, 255); drawString(renderer, x + 5, y + size - 18, 10, buf); - drawParamBar(renderer, x, y, size, cell.param, 255, 128, 0); + drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 255, 128, 0); drawTypeLabel(renderer, x, y, 'D'); } else if (cell.type == SynthEngine::GridCell::REVERB) { // Draw R @@ -777,10 +777,10 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G SDL_RenderDrawLine(renderer, cx, cy, cx+odx, cy+ody); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); // Param (Strength) - char buf[16]; snprintf(buf, 16, "%.2f", cell.param); + char buf[16]; snprintf(buf, 16, "%.2f", cell.param / 32767.0f); SDL_SetRenderDrawColor(renderer, 200, 100, 255, 255); drawString(renderer, x + 5, y + size - 18, 10, buf); - drawParamBar(renderer, x, y, size, cell.param, 200, 100, 255); + drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 200, 100, 255); drawTypeLabel(renderer, x, y, 'R'); } else if (cell.type == SynthEngine::GridCell::OPERATOR) { SDL_Rect opRect = {cx - r, cy - r, r*2, r*2}; @@ -795,7 +795,7 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G // Draw Op Symbol char opChar = '?'; - int opType = (int)(cell.param * 5.99f); + int opType = (int)((cell.param / 32767.0f) * 5.99f); if (opType == 0) opChar = '+'; else if (opType == 1) opChar = '*'; else if (opType == 2) opChar = '-'; @@ -803,7 +803,7 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G else if (opType == 4) opChar = '<'; else if (opType == 5) opChar = '>'; drawChar(renderer, cx - 15, cy - 15, 12, opChar); - drawParamBar(renderer, x, y, size, cell.param, 255, 255, 255); + drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 255, 255, 255); drawTypeLabel(renderer, x, y, 'M'); } else if (cell.type == SynthEngine::GridCell::WAVETABLE) { drawString(renderer, cx - 5, cy - 5, 12, "W"); @@ -816,12 +816,12 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G SDL_RenderDrawLine(renderer, cx, cy, cx+dx, cy+dy); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); // Param (Wave index) - int idx = (int)(cell.param * 7.99f); + int idx = (int)((cell.param / 32767.0f) * 7.99f); char buf[4]; snprintf(buf, 4, "%d", idx); SDL_SetRenderDrawColor(renderer, 128, 128, 255, 255); drawString(renderer, x + 5, y + size - 18, 10, buf); - drawParamBar(renderer, x, y, size, cell.param, 128, 128, 255); + drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 128, 128, 255); drawTypeLabel(renderer, x, y, 'W'); } } @@ -864,9 +864,9 @@ void randomizeGrid() { c.type = (SynthEngine::GridCell::Type)(rand() % numTypes); c.rotation = rand() % 4; - c.param = (float)rand() / (float)RAND_MAX; - c.value = 0.0f; - c.phase = 0.0f; + c.param = rand() % 32768; + c.value = 0; + c.phase = 0; } } @@ -954,7 +954,7 @@ void randomizeGrid() { for (int y = 0; y < SynthEngine::GRID_H; ++y) { if (!visited[x][y]) { engine.grid[x][y].type = SynthEngine::GridCell::EMPTY; - engine.grid[x][y].param = 0.5f; + engine.grid[x][y].param = 16384; engine.grid[x][y].rotation = 0; } } @@ -969,8 +969,8 @@ void randomizeGrid() { engine.setFrequency(440.0f); bool soundDetected = false; for(int i=0; i<1000; ++i) { - float val = engine.processGridStep(); - if (fabsf(val) > 0.001f) { + int32_t val = engine.processGridStep(); + if (abs(val) > 10) { soundDetected = true; break; } @@ -983,7 +983,7 @@ void randomizeGrid() { // Reset values to avoid initial pop for (int x = 0; x < SynthEngine::GRID_W; ++x) { for (int y = 0; y < SynthEngine::GRID_H; ++y) { - engine.grid[x][y].value = 0.0f; + engine.grid[x][y].value = 0; } } } else { @@ -999,7 +999,7 @@ void randomizeGrid() { SynthEngine::GridCell& c = engine.grid[x][y]; if (c.type != SynthEngine::GridCell::SINK) { c.type = SynthEngine::GridCell::EMPTY; - c.param = 0.5f; + c.param = 16384; c.rotation = 0; } } @@ -1207,7 +1207,7 @@ int main(int argc, char* argv[]) { c.rotation = (c.rotation + 1) % 4; } else if (e.button.button == SDL_BUTTON_MIDDLE) { newType = SynthEngine::GridCell::EMPTY; - c.param = 0.5f; + c.param = 16384; c.rotation = 0; } @@ -1291,7 +1291,7 @@ int main(int argc, char* argv[]) { if (mx < GRID_PANEL_WIDTH) { // Grid Scroll - float step = fineTune ? 0.01f : 0.05f; + int32_t step = fineTune ? 327 : 1638; // ~0.01 and ~0.05 int gx = mx / CELL_SIZE; int gy = my / CELL_SIZE; if (gx >= 0 && gx < SynthEngine::GRID_W && gy >= 0 && gy < SynthEngine::GRID_H) { @@ -1299,8 +1299,8 @@ int main(int argc, char* argv[]) { SynthEngine::GridCell& c = engine.grid[gx][gy]; if (e.wheel.y > 0) c.param += step; else c.param -= step; - if (c.param > 1.0f) c.param = 1.0f; - if (c.param < 0.0f) c.param = 0.0f; + if (c.param > 32767) c.param = 32767; + if (c.param < 0) c.param = 0; } } else { // Synth Scroll diff --git a/synth_engine.cpp b/synth_engine.cpp index 6b43f0b..a262333 100644 --- a/synth_engine.cpp +++ b/synth_engine.cpp @@ -63,7 +63,7 @@ size_t SynthEngine::exportGrid(uint8_t* buffer) { buffer[idx++] = (uint8_t)x; buffer[idx++] = (uint8_t)y; buffer[idx++] = (uint8_t)c.type; - buffer[idx++] = (uint8_t)(c.param * 255.0f); + buffer[idx++] = (uint8_t)((c.param * 255) >> FP_SHIFT); buffer[idx++] = (uint8_t)c.rotation; } } @@ -90,12 +90,12 @@ int SynthEngine::importGrid(const uint8_t* buffer, size_t size) { GridCell& c = grid[x][y]; if (c.type == GridCell::SINK) continue; c.type = GridCell::EMPTY; - c.param = 0.5f; + c.param = FP_HALF; c.rotation = 0; - c.value = 0.0f; - c.phase = 0.0f; + c.value = 0; + c.phase = 0; c.phase_accumulator = 0; - c.next_value = 0.0f; + c.next_value = 0; } } @@ -110,7 +110,7 @@ int SynthEngine::importGrid(const uint8_t* buffer, size_t size) { if (x < GRID_W && y < GRID_H) { GridCell& c = grid[x][y]; c.type = (GridCell::Type)t; - c.param = (float)p / 255.0f; + c.param = ((int32_t)p << FP_SHIFT) / 255; c.rotation = r; } } @@ -126,12 +126,12 @@ void SynthEngine::clearGrid() { if (c.type == GridCell::SINK) continue; c.type = GridCell::EMPTY; - c.param = 0.5f; + c.param = FP_HALF; c.rotation = 0; - c.value = 0.0f; - c.phase = 0.0f; + c.value = 0; + c.phase = 0; c.phase_accumulator = 0; - c.next_value = 0.0f; + c.next_value = 0; } } rebuildProcessingOrder_locked(); @@ -154,16 +154,16 @@ void SynthEngine::loadPreset(int preset) { grid[x][y+1].type = GridCell::WIRE; grid[x][y+1].rotation = 1; // E grid[x+1][y+1].type = GridCell::ADSR_ATTACK; grid[x+1][y+1].rotation = 1; // E - grid[x+1][y+1].param = att; + grid[x+1][y+1].param = (int32_t)(att * FP_ONE); grid[x+2][y+1].type = GridCell::ADSR_RELEASE; grid[x+2][y+1].rotation = 1; // E - grid[x+2][y+1].param = rel; + grid[x+2][y+1].param = (int32_t)(rel * FP_ONE); grid[x+3][y+1].type = GridCell::VCA; grid[x+3][y+1].rotation = 2; // S - grid[x+3][y+1].param = 0.0f; // Controlled by Env + grid[x+3][y+1].param = 0; // Controlled by Env grid[x+3][y].type = GridCell::INPUT_OSCILLATOR; grid[x+3][y].rotation = 2; // S - grid[x+3][y].param = (ratio > 1.0f) ? 0.5f : 0.0f; + grid[x+3][y].param = (ratio > 1.0f) ? FP_HALF : 0; }; int sinkY = GRID_H - 1; @@ -281,10 +281,10 @@ float SynthEngine::getFrequency() const { return (float)((double)_increment * (double)_sampleRate / 4294967296.0); } -float SynthEngine::_random() { +int32_t SynthEngine::_random() { // Simple Linear Congruential Generator _rngState = _rngState * 1664525 + 1013904223; - return (float)_rngState / 4294967296.0f; + return (int32_t)((_rngState >> 16) & 0xFFFF) - 32768; } void SynthEngine::rebuildProcessingOrder_locked() { @@ -352,7 +352,7 @@ void SynthEngine::updateGraph() { rebuildProcessingOrder_locked(); } -float SynthEngine::processGridStep() { +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; @@ -386,8 +386,8 @@ float SynthEngine::processGridStep() { }; // Helper to get input from a neighbor - auto getInput = [&](int tx, int ty, int from_x, int from_y) -> float { - if (!isConnected(tx, ty, from_x, from_y)) return 0.0f; + 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]; if (n.type == GridCell::FORK) { @@ -402,16 +402,16 @@ float SynthEngine::processGridStep() { int leftOut = (n.rotation + 3) % 4; int rightOut = (n.rotation + 1) % 4; - if (dir == leftOut) return n.value * (1.0f - n.param) * 2.0f; - if (dir == rightOut) return n.value * n.param * 2.0f; + 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; }; // Helper to sum inputs excluding the output direction - auto getSummedInput = [&](int x, int y, GridCell& c) -> float { - float sum = 0.0f; + 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); @@ -420,15 +420,15 @@ float SynthEngine::processGridStep() { return sum; }; - auto getInputFromTheBack = [&](int x, int y, GridCell& c) -> float { + auto getInputFromTheBack = [&](int x, int y, GridCell& c) -> int32_t { int inDir = (c.rotation + 2) % 4; int dx=0, dy=0; if(inDir==0) dy=-1; else if(inDir==1) dx=1; else if(inDir==2) dy=1; else dx=-1; return getInput(x, y, x+dx, y+dy); }; - auto getSideInputGain = [&](int x, int y, GridCell& c) -> float { - float gain = 0.0f; + auto getSideInputGain = [&](int x, int y, GridCell& c) -> int32_t { + int32_t gain = 0; bool hasSide = false; // Left (rot+3) int lDir = (c.rotation + 3) % 4; @@ -438,7 +438,7 @@ float SynthEngine::processGridStep() { int rDir = (c.rotation + 1) % 4; int rdx=0, rdy=0; if(rDir==0) rdy=-1; else if(rDir==1) rdx=1; else if(rDir==2) rdy=1; else rdx=-1; if (isConnected(x, y, x+rdx, y+rdy)) { hasSide = true; gain += getInput(x, y, x+rdx, y+rdy); } - return hasSide ? gain : 1.0f; + return hasSide ? gain : FP_ONE; }; // 1. Calculate next values for active cells @@ -446,82 +446,74 @@ float SynthEngine::processGridStep() { int x = cell_coord.first; int y = cell_coord.second; GridCell& c = grid[x][y]; - float val = 0.0f; + int32_t val = 0; if (c.type == GridCell::EMPTY) { - val = 0.0f; + val = 0; } else if (c.type == GridCell::FIXED_OSCILLATOR) { // Gather inputs for modulation - float mod = getInputFromTheBack(x, y, c); + int32_t mod = getInputFromTheBack(x, y, c); // Freq 10 to 1000 Hz. - float freq = 10.0f + c.param * 990.0f + (mod * 500.0f); // FM - if (freq < 1.0f) freq = 1.0f; + int32_t freq = 10 + ((c.param * 990) >> FP_SHIFT) + ((mod * 500) >> FP_SHIFT); + if (freq < 1) freq = 1; // Fixed point phase accumulation - uint32_t inc = (uint32_t)(freq * _freqToPhaseInc); + uint32_t inc = freq * 97391; c.phase_accumulator += inc; // Top 8 bits of 32-bit accumulator form the 256-entry table index - val = (float)sine_table[c.phase_accumulator >> 24] / 32768.0f; - val *= getSideInputGain(x, y, c); + val = sine_table[c.phase_accumulator >> 24]; + val = (val * getSideInputGain(x, y, c)) >> FP_SHIFT; } else if (c.type == GridCell::INPUT_OSCILLATOR) { - float mod = getInputFromTheBack(x, y, c); + int32_t mod = getInputFromTheBack(x, y, c); // Freq based on current note + octave param (1-5) - int octave = 1 + (int)(c.param * 4.99f); // Map 0.0-1.0 to 1-5 + int octave = 1 + ((c.param * 5) >> FP_SHIFT); // Map 0.0-1.0 to 1-5 // Use the engine's global increment directly to avoid float conversion round-trip uint32_t baseInc = _increment; uint32_t inc = baseInc << (octave - 1); // Apply FM (mod is float, convert to fixed point increment) - inc += (int32_t)(mod * 500.0f * _freqToPhaseInc); + inc += (int32_t)(((int64_t)mod * 500 * 97391) >> FP_SHIFT); c.phase_accumulator += inc; - val = (float)sine_table[c.phase_accumulator >> 24] / 32768.0f; - val *= getSideInputGain(x, y, c); + val = sine_table[c.phase_accumulator >> 24]; + val = (val * getSideInputGain(x, y, c)) >> FP_SHIFT; } else if (c.type == GridCell::WAVETABLE) { - float mod = getInputFromTheBack(x, y, c); + int32_t mod = getInputFromTheBack(x, y, c); // Track current note frequency + FM. Use direct increment for speed. - uint32_t inc = _increment + (int32_t)(mod * 500.0f * _freqToPhaseInc); + uint32_t inc = _increment + (int32_t)(((int64_t)mod * 500 * 97391) >> FP_SHIFT); c.phase_accumulator += inc; - // 0.0 to 1.0 representation for math-based waveforms - float phase_norm = (float)c.phase_accumulator / 4294967296.0f; - int wave_select = (int)(c.param * 7.99f); + int wave_select = (c.param * 8) >> FP_SHIFT; + bool phase_upper = (c.phase_accumulator & 0x80000000); switch(wave_select) { - case 0: val = (float)sine_table[c.phase_accumulator >> 24] / 32768.0f; break; - case 1: val = (phase_norm * 2.0f) - 1.0f; break; // Saw - case 2: val = (phase_norm < 0.5f) ? 1.0f : -1.0f; break; // Square - case 3: val = (phase_norm < 0.5f) ? (phase_norm * 4.0f - 1.0f) : (3.0f - phase_norm * 4.0f); break; // Triangle - case 4: val = 1.0f - (phase_norm * 2.0f); break; // Ramp - case 5: val = (phase_norm < 0.25f) ? 1.0f : -1.0f; break; // Pulse 25% - case 6: // Distorted Sine - val = sin(phase_norm * 2.0 * M_PI) + sin(phase_norm * 4.0 * M_PI) * 0.3f; - val /= 1.3f; // Normalize - break; - case 7: // Organ-like - val = sin(phase_norm * 2.0 * M_PI) * 0.6f + - sin(phase_norm * 4.0 * M_PI) * 0.2f + - sin(phase_norm * 8.0 * M_PI) * 0.1f; - val /= 0.9f; // Normalize + 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; } - val *= getSideInputGain(x, y, c); + val = (val * getSideInputGain(x, y, c)) >> FP_SHIFT; } else if (c.type == GridCell::NOISE) { - float mod = getInputFromTheBack(x, y, c); + int32_t mod = getInputFromTheBack(x, y, c); - float white = _random() * 2.0f - 1.0f; - int shade = (int)(c.param * 4.99f); + int32_t white = _random(); + int shade = (c.param * 5) >> FP_SHIFT; switch(shade) { case 0: // Brown (Leaky integrator) - c.phase = (c.phase + white * 0.1f) * 0.95f; - val = c.phase * 3.0f; // Gain up + c.phase = (c.phase + (white >> 3)) - (c.phase >> 4); + val = c.phase * 3; // Gain up break; case 1: // Pink (Approx: LPF) - c.phase = 0.5f * c.phase + 0.5f * white; + c.phase = (c.phase >> 1) + (white >> 1); val = c.phase; break; case 2: // White @@ -532,154 +524,146 @@ float SynthEngine::processGridStep() { c.phase = white; // Store last sample break; case 4: // Green (BPF approx) - c.phase = (c.phase + white) * 0.5f; // LPF + c.phase = (c.phase + white) >> 1; // LPF val = white - c.phase; // HPF result break; } // Apply Amplitude Modulation (AM) from input - val *= (1.0f + mod); - val *= getSideInputGain(x, y, c); + val = (val * (FP_ONE + mod)) >> FP_SHIFT; + val = (val * getSideInputGain(x, y, c)) >> FP_SHIFT; } else if (c.type == GridCell::LFO) { // Low Frequency Oscillator (0.1 Hz to 20 Hz) - float freq = 0.1f + c.param * 19.9f; - uint32_t inc = (uint32_t)(freq * _freqToPhaseInc); + int32_t freq_x10 = 1 + ((c.param * 199) >> FP_SHIFT); + uint32_t inc = freq_x10 * 9739; c.phase_accumulator += inc; // Output full range -1.0 to 1.0 - val = (float)sine_table[c.phase_accumulator >> 24] / 32768.0f; + val = sine_table[c.phase_accumulator >> 24]; } else if (c.type == GridCell::FORK) { // Sum inputs from "Back" (Input direction) val = getInputFromTheBack(x, y, c); } else if (c.type == GridCell::GATE_INPUT) { // Outputs 1.0 when gate is open (key pressed), 0.0 otherwise - val = _isGateOpen ? 1.0f : 0.0f; + val = _isGateOpen ? FP_MAX : 0; } else if (c.type == GridCell::ADSR_ATTACK) { // Slew Limiter (Up only) - float in = getInputFromTheBack(x, y, c); - float rate = 1.0f / (0.001f + c.param * 2.0f * _sampleRate); // 0.001s to 2s - if (in > c.value) { - c.value += rate; - if (c.value > in) c.value = in; + int32_t in = getInputFromTheBack(x, y, c); + int32_t rate = (1 << 20) / (1 + (c.param >> 4)); + if (in > (c.phase >> 9)) { + c.phase += rate; + if ((c.phase >> 9) > in) c.phase = in << 9; } else { - c.value = in; + c.phase = in << 9; } - val = c.value; + val = c.phase >> 9; } else if (c.type == GridCell::ADSR_DECAY || c.type == GridCell::ADSR_RELEASE) { // Slew Limiter (Down only) - float in = getInputFromTheBack(x, y, c); - float rate = 1.0f / (0.001f + c.param * 2.0f * _sampleRate); - if (in < c.value) { - c.value -= rate; - if (c.value < in) c.value = in; + int32_t in = getInputFromTheBack(x, y, c); + int32_t rate = (1 << 20) / (1 + (c.param >> 4)); + if (in < (c.phase >> 9)) { + c.phase -= rate; + if ((c.phase >> 9) < in) c.phase = in << 9; } else { - c.value = in; + c.phase = in << 9; } - val = c.value; + val = c.phase >> 9; } else if (c.type == GridCell::ADSR_SUSTAIN) { // Attenuator - float in = getInputFromTheBack(x, y, c); - val = in * c.param; + int32_t in = getInputFromTheBack(x, y, c); + val = (in * c.param) >> FP_SHIFT; } else if (c.type == GridCell::WIRE) { // Sum inputs from all neighbors that point to me - float sum = getSummedInput(x, y, c); - val = sum; + val = getSummedInput(x, y, c); } else if (c.type == GridCell::LPF) { // Input from Back - float in = getInputFromTheBack(x, y, c); + int32_t in = getInputFromTheBack(x, y, c); // Simple one-pole LPF - // Cutoff mapping: Exponential-ish 20Hz to 15kHz - float cutoff = 20.0f + c.param * c.param * 15000.0f; - float alpha = 2.0f * M_PI * cutoff / (float)_sampleRate; - if (alpha > 1.0f) alpha = 1.0f; + int32_t alpha = (c.param * c.param) >> FP_SHIFT; // c.phase stores previous output - val = c.phase + alpha * (in - c.phase); + val = c.phase + ((alpha * (in - c.phase)) >> FP_SHIFT); c.phase = val; } else if (c.type == GridCell::HPF) { // Input from Back - float in = getInputFromTheBack(x, y, c); + int32_t in = getInputFromTheBack(x, y, c); - float cutoff = 20.0f + c.param * c.param * 15000.0f; - float alpha = 2.0f * M_PI * cutoff / (float)_sampleRate; - if (alpha > 1.0f) alpha = 1.0f; + int32_t alpha = (c.param * c.param) >> FP_SHIFT; // HPF = Input - LPF - // c.phase stores LPF state - float lpf = c.phase + alpha * (in - c.phase); + int32_t lpf = c.phase + ((alpha * (in - c.phase)) >> FP_SHIFT); c.phase = lpf; val = in - lpf; } else if (c.type == GridCell::VCA) { // Input from Back - float in = getInputFromTheBack(x, y, c); + int32_t in = getInputFromTheBack(x, y, c); // Mod from other directions (sum) - float mod = getSummedInput(x, y, c); + int32_t mod = getSummedInput(x, y, c); mod -= in; // Remove signal input from mod sum (it was included in getInput calls) // Gain = Param + Mod - float gain = c.param + mod; - if (gain < 0.0f) gain = 0.0f; - val = in * gain; + int32_t gain = c.param + mod; + if (gain < 0) gain = 0; + val = (in * gain) >> FP_SHIFT; } else if (c.type == GridCell::BITCRUSHER) { - float in = getInputFromTheBack(x, y, c); + int32_t in = getInputFromTheBack(x, y, c); // Bit depth reduction - float bits = 1.0f + c.param * 15.0f; // 1 to 16 bits - float steps = powf(2.0f, bits); - val = roundf(in * steps) / steps; + int32_t mask = 0xFFFF << (16 - (c.param >> 11)); + val = in & mask; } else if (c.type == GridCell::DISTORTION) { - float in = getInputFromTheBack(x, y, c); + int32_t in = getInputFromTheBack(x, y, c); // Soft clipping - float drive = 1.0f + c.param * 20.0f; - float x_driven = in * drive; - // Simple soft clip: x / (1 + |x|) - val = x_driven / (1.0f + fabsf(x_driven)); + int32_t drive = FP_ONE + (c.param << 2); + val = (in * drive) >> FP_SHIFT; + if (val > FP_MAX) val = FP_MAX; + if (val < FP_MIN) val = FP_MIN; } else if (c.type == GridCell::RECTIFIER) { - float in = getInputFromTheBack(x, y, c); + int32_t in = getInputFromTheBack(x, y, c); // Mix between original and rectified based on param - float rect = fabsf(in); - val = in * (1.0f - c.param) + rect * c.param; + int32_t rect = (in < 0) ? -in : in; + val = ((in * (FP_ONE - c.param)) >> FP_SHIFT) + ((rect * c.param) >> FP_SHIFT); } else if (c.type == GridCell::GLITCH) { - float in = getInputFromTheBack(x, y, c); + int32_t in = getInputFromTheBack(x, y, c); // Param controls probability of glitch - float chance = c.param * 0.2f; // 0 to 20% chance per sample - if (_random() < chance) { - int mode = (int)(_random() * 3.0f); - if (mode == 0) val = in * 50.0f; // Massive gain (clipping) - else if (mode == 1) val = _random() * 2.0f - 1.0f; // White noise burst - else val = 0.0f; // Drop out + int32_t chance = c.param >> 2; + if ((_random() & 0x7FFF) < chance) { + int mode = _random() & 3; + if (mode == 0) val = in << 4; // Massive gain (clipping) + else if (mode == 1) val = _random(); // White noise burst + else val = 0; // Drop out } else { val = in; } } else if (c.type == GridCell::OPERATOR || c.type == GridCell::SINK) { // Gather inputs - float inputs[4]; + int32_t inputs[4]; int count = 0; int outDir = (c.type == GridCell::SINK) ? -1 : c.rotation; - float iN = (outDir != 0) ? getInput(x, y, x, y-1) : 0.0f; if(iN!=0) inputs[count++] = iN; - float iE = (outDir != 1) ? getInput(x, y, x+1, y) : 0.0f; if(iE!=0) inputs[count++] = iE; - float iS = (outDir != 2) ? getInput(x, y, x, y+1) : 0.0f; if(iS!=0) inputs[count++] = iS; - float iW = (outDir != 3) ? getInput(x, y, x-1, y) : 0.0f; if(iW!=0) inputs[count++] = iW; + int32_t iN = (outDir != 0) ? getInput(x, y, x, y-1) : 0; if(iN!=0) inputs[count++] = iN; + int32_t iE = (outDir != 1) ? getInput(x, y, x+1, y) : 0; if(iE!=0) inputs[count++] = iE; + int32_t iS = (outDir != 2) ? getInput(x, y, x, y+1) : 0; if(iS!=0) inputs[count++] = iS; + int32_t iW = (outDir != 3) ? getInput(x, y, x-1, y) : 0; if(iW!=0) inputs[count++] = iW; if (c.type == GridCell::SINK) { // Sink just sums everything - val = 0.0f; + val = 0; for(int k=0; k> FP_SHIFT; + if (count == 0) val = 0; else { val = inputs[0]; for (int i=1; i> FP_SHIFT; break; // MUL case 2: val -= inputs[i]; break; // SUB - case 3: if(inputs[i]!=0) val /= inputs[i]; break; // DIV + case 3: if(inputs[i]!=0) val = (val << FP_SHIFT) / inputs[i]; break; // DIV case 4: if(inputs[i]val) val = inputs[i]; break; // MAX } @@ -706,18 +690,18 @@ void SynthEngine::process(int16_t* buffer, uint32_t numFrames) { for (uint32_t i = 0; i < numFrames; ++i) { // The grid is now the primary sound source. - // The processGridStep() returns a float in the approx range of -1.0 to 1.0. - float sampleF = processGridStep(); + // The processGridStep() returns Q15 + int32_t sample = processGridStep(); // Soft clip grid sample to avoid harsh distortion before filtering. - if (sampleF > 1.0f) sampleF = 1.0f; - if (sampleF < -1.0f) sampleF = -1.0f; + if (sample > FP_MAX) sample = FP_MAX; + if (sample < FP_MIN) sample = FP_MIN; // The filters were designed for a signal in the int16 range. - // We scale the grid's float output to match this expected range. - sampleF *= 32767.0f; + // We scale the grid's output to match this expected range. + // It is already Q15, so it matches int16 range. // Apply Master Volume and write to buffer - buffer[i] = static_cast(sampleF * _volume); + buffer[i] = (int16_t)((sample * (int32_t)(_volume * FP_ONE)) >> FP_SHIFT); } } \ No newline at end of file diff --git a/synth_engine.h b/synth_engine.h index 60ec861..109d05f 100644 --- a/synth_engine.h +++ b/synth_engine.h @@ -32,6 +32,13 @@ template using SynthLockGuard = std::lock_guard; #endif +// Fixed-point constants +#define FP_SHIFT 15 +#define FP_ONE (1 << FP_SHIFT) +#define FP_HALF (1 << (FP_SHIFT - 1)) +#define FP_MAX 32767 +#define FP_MIN -32768 + /** * @class SynthEngine * @brief A portable, platform-agnostic synthesizer engine. @@ -102,11 +109,11 @@ public: enum Op { OP_ADD, OP_MUL, OP_SUB, OP_DIV, OP_MIN, OP_MAX }; Type type = EMPTY; - float param = 0.5f; // 0.0 to 1.0 + int32_t param = FP_HALF; // 0.0 to 1.0 -> 0 to 32768 int rotation = 0; // 0:N, 1:E, 2:S, 3:W (Output direction) - 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 + int32_t value = 0; // Current output sample (Q15) + int32_t next_value = 0; // For double-buffering + int32_t phase = 0; // For Oscillator, Noise state, Filter state uint32_t phase_accumulator = 0; // For Oscillators (Fixed point optimization) }; @@ -125,7 +132,7 @@ public: SynthMutex gridMutex; // Helper to process one sample step of the grid - float processGridStep(); + int32_t processGridStep(); private: uint32_t _sampleRate; @@ -140,7 +147,7 @@ private: void rebuildProcessingOrder_locked(); // Internal random number generator - float _random(); + int32_t _random(); }; #endif // SYNTH_ENGINE_H \ No newline at end of file