diff --git a/main.cpp b/main.cpp index 0350469..593713f 100644 --- a/main.cpp +++ b/main.cpp @@ -192,14 +192,35 @@ void drawChar(SDL_Renderer* renderer, int x, int y, int size, char c) { case '/': SDL_RenderDrawLine(renderer, x+w, y, x, y+h); break; case '<': SDL_RenderDrawLine(renderer, x+w, y, x, y+h2); SDL_RenderDrawLine(renderer, x, y+h2, x+w, y+h); break; case '>': SDL_RenderDrawLine(renderer, x, y, x+w, y+h2); SDL_RenderDrawLine(renderer, x+w, y+h2, x, y+h); break; + case 'A': SDL_RenderDrawLine(renderer, x, y+h, x+w2, y); SDL_RenderDrawLine(renderer, x+w2, y, x+w, y+h); SDL_RenderDrawLine(renderer, x+w/4, y+h2, x+3*w/4, y+h2); break; + case 'B': SDL_RenderDrawLine(renderer, x, y, x, y+h); SDL_RenderDrawLine(renderer, x, y, x+w2, y); SDL_RenderDrawLine(renderer, x+w2, y, x+w, y+h/4); SDL_RenderDrawLine(renderer, x+w, y+h/4, x+w2, y+h2); SDL_RenderDrawLine(renderer, x+w2, y+h2, x, y+h2); SDL_RenderDrawLine(renderer, x+w2, y+h2, x+w, y+3*h/4); SDL_RenderDrawLine(renderer, x+w, y+3*h/4, x+w2, y+h); SDL_RenderDrawLine(renderer, x+w2, y+h, x, y+h); break; + case 'C': SDL_RenderDrawLine(renderer, x+w, y, x, y); SDL_RenderDrawLine(renderer, x, y, x, y+h); SDL_RenderDrawLine(renderer, x, y+h, x+w, y+h); break; + case 'D': SDL_RenderDrawLine(renderer, x, y, x, y+h); SDL_RenderDrawLine(renderer, x, y, x+w2, y); SDL_RenderDrawLine(renderer, x+w2, y, x+w, y+h2); SDL_RenderDrawLine(renderer, x+w, y+h2, x+w2, y+h); SDL_RenderDrawLine(renderer, x+w2, y+h, x, y+h); break; + case 'E': SDL_RenderDrawLine(renderer, x+w, y, x, y); SDL_RenderDrawLine(renderer, x, y, x, y+h); SDL_RenderDrawLine(renderer, x, y+h, x+w, y+h); SDL_RenderDrawLine(renderer, x, y+h2, x+w2, y+h2); break; case 'F': SDL_RenderDrawLine(renderer, x, y, x, y+h); SDL_RenderDrawLine(renderer, x, y, x+w, y); SDL_RenderDrawLine(renderer, x, y+h2, x+w2, y+h2); break; + case 'G': SDL_RenderDrawLine(renderer, x+w, y, x, y); SDL_RenderDrawLine(renderer, x, y, x, y+h); SDL_RenderDrawLine(renderer, x, y+h, x+w, y+h); SDL_RenderDrawLine(renderer, x+w, y+h, x+w, y+h2); SDL_RenderDrawLine(renderer, x+w, y+h2, x+w2, y+h2); break; + case 'H': SDL_RenderDrawLine(renderer, x, y, x, y+h); SDL_RenderDrawLine(renderer, x+w, y, x+w, y+h); SDL_RenderDrawLine(renderer, x, y+h2, x+w, y+h2); break; + case 'I': SDL_RenderDrawLine(renderer, x+w2, y, x+w2, y+h); SDL_RenderDrawLine(renderer, x, y, x+w, y); SDL_RenderDrawLine(renderer, x, y+h, x+w, y+h); break; + case 'K': SDL_RenderDrawLine(renderer, x, y, x, y+h); SDL_RenderDrawLine(renderer, x+w, y, x, y+h2); SDL_RenderDrawLine(renderer, x, y+h2, x+w, y+h); break; + case 'L': SDL_RenderDrawLine(renderer, x, y, x, y+h); SDL_RenderDrawLine(renderer, x, y+h, x+w, y+h); break; + case 'M': SDL_RenderDrawLine(renderer, x, y+h, x, y); SDL_RenderDrawLine(renderer, x, y, x+w2, y+h2); SDL_RenderDrawLine(renderer, x+w2, y+h2, x+w, y); SDL_RenderDrawLine(renderer, x+w, y, x+w, y+h); break; + case 'N': SDL_RenderDrawLine(renderer, x, y+h, x, y); SDL_RenderDrawLine(renderer, x, y, x+w, y+h); SDL_RenderDrawLine(renderer, x+w, y+h, x+w, y); break; case 'O': drawChar(renderer, x, y, size, '0'); break; + case 'P': SDL_RenderDrawLine(renderer, x, y, x, y+h); SDL_RenderDrawLine(renderer, x, y, x+w, y); SDL_RenderDrawLine(renderer, x+w, y, x+w, y+h2); SDL_RenderDrawLine(renderer, x+w, y+h2, x, y+h2); break; + case 'Q': drawChar(renderer, x, y, size, '0'); SDL_RenderDrawLine(renderer, x+w2, y+h2, x+w, y+h); break; + case 'R': SDL_RenderDrawLine(renderer, x, y, x, y+h); SDL_RenderDrawLine(renderer, x, y, x+w, y); SDL_RenderDrawLine(renderer, x+w, y, x+w, y+h2); SDL_RenderDrawLine(renderer, x+w, y+h2, x, y+h2); SDL_RenderDrawLine(renderer, x, y+h2, x+w, y+h); break; + case 'S': SDL_RenderDrawLine(renderer, x+w, y, x, y); SDL_RenderDrawLine(renderer, x, y, x, y+h2); SDL_RenderDrawLine(renderer, x, y+h2, x+w, y+h2); SDL_RenderDrawLine(renderer, x+w, y+h2, x+w, y+h); SDL_RenderDrawLine(renderer, x+w, y+h, x, y+h); break; + case 'T': SDL_RenderDrawLine(renderer, x, y, x+w, y); SDL_RenderDrawLine(renderer, x+w2, y, x+w2, y+h); break; + case 'U': SDL_RenderDrawLine(renderer, x, y, x, y+h); SDL_RenderDrawLine(renderer, x, y+h, x+w, y+h); SDL_RenderDrawLine(renderer, x+w, y+h, x+w, y); break; + case 'V': SDL_RenderDrawLine(renderer, x, y, x+w2, y+h); SDL_RenderDrawLine(renderer, x+w2, y+h, x+w, y); break; case 'W': SDL_RenderDrawLine(renderer, x, y, x, y+h); SDL_RenderDrawLine(renderer, x, y+h, x+w2, y+h2); SDL_RenderDrawLine(renderer, x+w2, y+h2, x+w, y+h); SDL_RenderDrawLine(renderer, x+w, y+h, x+w, y); break; + case 'X': SDL_RenderDrawLine(renderer, x, y, x+w, y+h); SDL_RenderDrawLine(renderer, x+w, y, x, y+h); break; + case 'Y': SDL_RenderDrawLine(renderer, x, y, x+w2, y+h2); SDL_RenderDrawLine(renderer, x+w, y, x+w2, y+h2); SDL_RenderDrawLine(renderer, x+w2, y+h2, x+w2, y+h); break; } } @@ -212,6 +233,24 @@ void drawString(SDL_Renderer* renderer, int x, int y, int size, const char* str) } } +void drawParamBar(SDL_Renderer* renderer, int x, int y, int size, float value, uint8_t r, uint8_t g, uint8_t b) { + SDL_SetRenderDrawColor(renderer, 50, 50, 50, 255); + SDL_Rect bg = {x + 4, y + size - 6, size - 8, 4}; + SDL_RenderFillRect(renderer, &bg); + + SDL_SetRenderDrawColor(renderer, r, g, b, 255); + int w = (int)((size - 8) * value); + if (w < 0) w = 0; + if (w > size - 8) w = size - 8; + SDL_Rect fg = {x + 4, y + size - 6, w, 4}; + SDL_RenderFillRect(renderer, &fg); +} + +void drawTypeLabel(SDL_Renderer* renderer, int x, int y, char c) { + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + drawChar(renderer, x + 3, y + 3, 8, c); +} + // --- Grid UI Helpers --- void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::GridCell& cell) { // Background @@ -230,6 +269,7 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G if (cell.type == SynthEngine::GridCell::EMPTY) { SDL_RenderDrawPoint(renderer, cx, cy); } else if (cell.type == SynthEngine::GridCell::SINK) { + drawTypeLabel(renderer, x, y, 'S'); SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); SDL_Rect sinkRect = {cx - r, cy - r, r*2, r*2}; SDL_RenderFillRect(renderer, &sinkRect); @@ -246,7 +286,9 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G // Param (Fading) char buf[16]; snprintf(buf, 16, "%.2f", cell.param); SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); - drawString(renderer, x + 5, y + size - 15, 10, buf); + drawString(renderer, x + 5, y + size - 18, 10, buf); + drawParamBar(renderer, x, y, size, cell.param, 0, 255, 0); + drawTypeLabel(renderer, x, y, '-'); } else if (cell.type == SynthEngine::GridCell::FIXED_OSCILLATOR) { DrawCircle(renderer, cx, cy, r); // Direction line @@ -260,7 +302,9 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G char buf[16]; snprintf(buf, 16, "%.0f", 10.0f + cell.param*990.0f); SDL_SetRenderDrawColor(renderer, 0, 255, 255, 255); - drawString(renderer, x + 5, y + size - 15, 10, buf); + drawString(renderer, x + 5, y + size - 18, 10, buf); + drawParamBar(renderer, x, y, size, cell.param, 0, 255, 255); + drawTypeLabel(renderer, x, y, 'O'); } else if (cell.type == SynthEngine::GridCell::INPUT_OSCILLATOR) { DrawCircle(renderer, cx, cy, r); DrawCircle(renderer, cx, cy, r/2); // Inner circle to distinguish @@ -274,7 +318,9 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G // Param (Octave) char buf[16]; snprintf(buf, 16, "O%d", 1 + (int)(cell.param * 4.99f)); SDL_SetRenderDrawColor(renderer, 255, 200, 0, 255); - drawString(renderer, x + 5, y + size - 15, 10, buf); + drawString(renderer, x + 5, y + size - 18, 10, buf); + drawParamBar(renderer, x, y, size, cell.param, 255, 200, 0); + drawTypeLabel(renderer, x, y, 'I'); } else if (cell.type == SynthEngine::GridCell::NOISE) { // Draw static/noise pattern for(int i=0; i<20; ++i) { @@ -292,7 +338,113 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G const char* colors[] = {"BRN", "PNK", "WHT", "YEL", "GRN"}; int idx = (int)(cell.param * 4.99f); SDL_SetRenderDrawColor(renderer, 200, 200, 200, 255); - drawString(renderer, x + 5, y + size - 15, 10, colors[idx]); + drawString(renderer, x + 5, y + size - 18, 10, colors[idx]); + drawParamBar(renderer, x, y, size, cell.param, 200, 200, 200); + drawTypeLabel(renderer, x, y, 'N'); + } else if (cell.type == SynthEngine::GridCell::LFO) { + DrawCircle(renderer, cx, cy, r); + // Direction line + int dx=0, dy=0; + if (cell.rotation == 0) dy = -r; + if (cell.rotation == 1) dx = r; + if (cell.rotation == 2) dy = r; + if (cell.rotation == 3) dx = -r; + SDL_RenderDrawLine(renderer, cx, cy, cx+dx, cy+dy); + drawString(renderer, cx - 8, cy - 5, 12, "LFO"); + // Param (Freq) + char buf[16]; snprintf(buf, 16, "%.1f", 0.1f + cell.param * 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); + drawTypeLabel(renderer, x, y, 'L'); + } else if (cell.type == SynthEngine::GridCell::LPF || cell.type == SynthEngine::GridCell::HPF) { + // Box + SDL_Rect box = {cx - r, cy - r, r*2, r*2}; + SDL_RenderDrawRect(renderer, &box); + // Label + drawString(renderer, cx - 8, cy - 5, 12, cell.type == SynthEngine::GridCell::LPF ? "LPF" : "HPF"); + // I/O + int inDir = (cell.rotation + 2) % 4; + int idx=0, idy=0; + if(inDir==0) idy=-r; else if(inDir==1) idx=r; else if(inDir==2) idy=r; else idx=-r; + SDL_RenderDrawLine(renderer, cx+idx, cy+idy, cx, cy); + int odx=0, ody=0; + 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); + // Param + char buf[16]; snprintf(buf, 16, "%.2f", cell.param); + 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); + drawTypeLabel(renderer, x, y, cell.type == SynthEngine::GridCell::LPF ? 'P' : 'H'); + } else if (cell.type == SynthEngine::GridCell::VCA) { + // Triangle shape for Amp + // Simplified to box with VCA text + SDL_Rect box = {cx - r, cy - r, r*2, r*2}; + SDL_RenderDrawRect(renderer, &box); + drawString(renderer, cx - 8, cy - 5, 12, "VCA"); + // I/O + int inDir = (cell.rotation + 2) % 4; + int idx=0, idy=0; + if(inDir==0) idy=-r; else if(inDir==1) idx=r; else if(inDir==2) idy=r; else idx=-r; + SDL_RenderDrawLine(renderer, cx+idx, cy+idy, cx, cy); + int odx=0, ody=0; + 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); + // Param + char buf[16]; snprintf(buf, 16, "%.2f", cell.param); + 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); + drawTypeLabel(renderer, x, y, 'A'); + } else if (cell.type == SynthEngine::GridCell::BITCRUSHER) { + SDL_Rect box = {cx - r, cy - r, r*2, r*2}; + SDL_RenderDrawRect(renderer, &box); + drawString(renderer, cx - 8, cy - 5, 12, "BIT"); + // I/O + int inDir = (cell.rotation + 2) % 4; + int idx=0, idy=0; + if(inDir==0) idy=-r; else if(inDir==1) idx=r; else if(inDir==2) idy=r; else idx=-r; + SDL_RenderDrawLine(renderer, cx+idx, cy+idy, cx, cy); + int odx=0, ody=0; + 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); + // Param + char buf[16]; snprintf(buf, 16, "%.2f", cell.param); + 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); + drawTypeLabel(renderer, x, y, 'B'); + } else if (cell.type == SynthEngine::GridCell::DISTORTION) { + SDL_Rect box = {cx - r, cy - r, r*2, r*2}; + SDL_RenderDrawRect(renderer, &box); + drawString(renderer, cx - 8, cy - 5, 12, "DST"); + // I/O + int inDir = (cell.rotation + 2) % 4; + int idx=0, idy=0; + if(inDir==0) idy=-r; else if(inDir==1) idx=r; else if(inDir==2) idy=r; else idx=-r; + SDL_RenderDrawLine(renderer, cx+idx, cy+idy, cx, cy); + int odx=0, ody=0; + 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); + char buf[16]; snprintf(buf, 16, "%.2f", cell.param); + 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); + drawTypeLabel(renderer, x, y, 'X'); + } else if (cell.type == SynthEngine::GridCell::GLITCH) { + drawString(renderer, cx - 8, cy - 5, 12, "GLT"); + // I/O + int inDir = (cell.rotation + 2) % 4; + int idx=0, idy=0; + if(inDir==0) idy=-r; else if(inDir==1) idx=r; else if(inDir==2) idy=r; else idx=-r; + SDL_RenderDrawLine(renderer, cx+idx, cy+idy, cx, cy); + int odx=0, ody=0; + 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); + + drawParamBar(renderer, x, y, size, cell.param, 255, 0, 0); + drawTypeLabel(renderer, x, y, 'G'); } else if (cell.type == SynthEngine::GridCell::FORK) { // Draw Y shape based on rotation // Center @@ -314,7 +466,9 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G // Param (Balance) char buf[16]; snprintf(buf, 16, "%.1f", cell.param); SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); - drawString(renderer, x + 5, y + size - 15, 10, buf); + drawString(renderer, x + 5, y + size - 18, 10, buf); + drawParamBar(renderer, x, y, size, cell.param, 0, 255, 0); + drawTypeLabel(renderer, x, y, 'Y'); } else if (cell.type == SynthEngine::GridCell::DELAY) { // Draw D drawString(renderer, cx - 5, cy - 5, 12, "D"); @@ -334,7 +488,9 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G float delay_ms = cell.param * 2000.0f; // Max 2 seconds snprintf(buf, 16, "%.0fms", delay_ms); SDL_SetRenderDrawColor(renderer, 255, 128, 0, 255); - drawString(renderer, x + 5, y + size - 15, 10, buf); + drawString(renderer, x + 5, y + size - 18, 10, buf); + drawParamBar(renderer, x, y, size, cell.param, 255, 128, 0); + drawTypeLabel(renderer, x, y, 'D'); } else if (cell.type == SynthEngine::GridCell::REVERB) { // Draw R drawString(renderer, cx - 5, cy - 5, 12, "R"); @@ -351,7 +507,9 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G // Param (Strength) char buf[16]; snprintf(buf, 16, "%.2f", cell.param); SDL_SetRenderDrawColor(renderer, 200, 100, 255, 255); - drawString(renderer, x + 5, y + size - 15, 10, buf); + drawString(renderer, x + 5, y + size - 18, 10, buf); + drawParamBar(renderer, x, y, size, cell.param, 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}; SDL_RenderDrawRect(renderer, &opRect); @@ -371,7 +529,9 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G else if (opType == 3) opChar = '/'; else if (opType == 4) opChar = '<'; else if (opType == 5) opChar = '>'; - drawChar(renderer, cx - 5, cy - 5, 12, opChar); + drawChar(renderer, cx - 15, cy - 15, 12, opChar); + drawParamBar(renderer, x, y, size, cell.param, 255, 255, 255); + drawTypeLabel(renderer, x, y, 'M'); } else if (cell.type == SynthEngine::GridCell::WAVETABLE) { drawString(renderer, cx - 5, cy - 5, 12, "W"); // Direction line @@ -386,7 +546,9 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G char buf[4]; snprintf(buf, 4, "%d", idx); SDL_SetRenderDrawColor(renderer, 128, 128, 255, 255); - drawString(renderer, x + 5, y + size - 15, 10, buf); + drawString(renderer, x + 5, y + size - 18, 10, buf); + drawParamBar(renderer, x, y, size, cell.param, 128, 128, 255); + drawTypeLabel(renderer, x, y, 'W'); } } @@ -415,7 +577,7 @@ void randomizeGrid() { std::lock_guard lock(engine.gridMutex); // 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 for (int x = 0; x < 5; ++x) { @@ -433,9 +595,10 @@ void randomizeGrid() { } int attempts = 0; - bool connected = false; + bool inputOscillatorReachable = false; + bool visited[5][8]; - while (!connected && attempts < 1000) { + while (!inputOscillatorReachable && attempts < 1000) { attempts++; // 2. Randomize (without allocation) @@ -452,14 +615,14 @@ void randomizeGrid() { // 3. Check Connectivity // BFS from SINK (2,3) backwards - bool visited[5][8] = {false}; + memset(visited, 0, sizeof(visited)); std::vector> q; q.push_back({2, 3}); visited[2][3] = true; int head = 0; - while(head < (int)q.size()){ + while(head < (int)q.size()) { std::pair curr = q[head++]; int cx = curr.first; int cy = curr.second; @@ -471,9 +634,7 @@ void randomizeGrid() { for(int i=0; i<4; ++i) { int tx = cx + nx[i]; int ty = cy + ny[i]; - - if (tx >= 0 && tx < 5 && ty >= 0 && ty < 8) { - if (visited[tx][ty]) continue; + if (tx >= 0 && tx < 5 && ty >= 0 && ty < 8 && !visited[tx][ty]) { SynthEngine::GridCell& neighbor = engine.grid[tx][ty]; bool pointsToCurr = false; @@ -506,23 +667,39 @@ void randomizeGrid() { } if (pointsToCurr) { - if (neighbor.type == SynthEngine::GridCell::FIXED_OSCILLATOR || - neighbor.type == SynthEngine::GridCell::INPUT_OSCILLATOR || - neighbor.type == SynthEngine::GridCell::WAVETABLE || - neighbor.type == SynthEngine::GridCell::NOISE) { - connected = true; - break; - } visited[tx][ty] = true; q.push_back({tx, ty}); } } } - if (connected) break; + } + + // After BFS, check if an input oscillator is reachable + for (int x = 0; x < 5; ++x) { + for (int y = 0; y < 8; ++y) { + if (visited[x][y] && engine.grid[x][y].type == SynthEngine::GridCell::INPUT_OSCILLATOR) { + inputOscillatorReachable = true; + break; + } + } + if (inputOscillatorReachable) break; } } - // 4. Allocate buffers for DELAYs + // 4. Prune unreachable elements if a valid grid was found + if (inputOscillatorReachable) { + for (int x = 0; x < 5; ++x) { + for (int y = 0; y < 8; ++y) { + if (!visited[x][y]) { + engine.grid[x][y].type = SynthEngine::GridCell::EMPTY; + engine.grid[x][y].param = 0.5f; + engine.grid[x][y].rotation = 0; + } + } + } + } + + // 5. Allocate buffers for DELAYs and REVERBs that are still present for (int x = 0; x < 5; ++x) { for (int y = 0; y < 8; ++y) { SynthEngine::GridCell& c = engine.grid[x][y]; @@ -534,7 +711,7 @@ void randomizeGrid() { } } - printf("Randomized in %d attempts. Connected: %s\n", attempts, connected ? "YES" : "NO"); + printf("Randomized in %d attempts. Input Osc reachable: %s\n", attempts, inputOscillatorReachable ? "YES" : "NO"); } int main(int argc, char* argv[]) { @@ -601,6 +778,27 @@ int main(int argc, char* argv[]) { engine.setFilter(20.0f + filter_vals[0]*19980.0f, 20.0f + filter_vals[1]*19980.0f); // --- Main Loop --- + const SynthEngine::GridCell::Type cellTypes[] = { + SynthEngine::GridCell::EMPTY, + SynthEngine::GridCell::FIXED_OSCILLATOR, + SynthEngine::GridCell::INPUT_OSCILLATOR, + SynthEngine::GridCell::WAVETABLE, + SynthEngine::GridCell::NOISE, + SynthEngine::GridCell::LFO, + SynthEngine::GridCell::FORK, + SynthEngine::GridCell::WIRE, + SynthEngine::GridCell::LPF, + SynthEngine::GridCell::HPF, + SynthEngine::GridCell::VCA, + SynthEngine::GridCell::BITCRUSHER, + SynthEngine::GridCell::DISTORTION, + SynthEngine::GridCell::GLITCH, + SynthEngine::GridCell::OPERATOR, + SynthEngine::GridCell::DELAY, + SynthEngine::GridCell::REVERB + }; + const int numCellTypes = sizeof(cellTypes) / sizeof(cellTypes[0]); + bool quit = false; SDL_Event e; @@ -645,17 +843,12 @@ int main(int argc, char* argv[]) { SynthEngine::GridCell::Type newType = oldType; if (e.button.button == SDL_BUTTON_LEFT) { - // Cycle: EMPTY -> FIXED -> INPUT -> WAVETABLE -> NOISE -> FORK -> WIRE -> OP -> DELAY -> REVERB -> EMPTY - if (oldType == SynthEngine::GridCell::EMPTY) newType = SynthEngine::GridCell::FIXED_OSCILLATOR; - else if (oldType == SynthEngine::GridCell::FIXED_OSCILLATOR) newType = SynthEngine::GridCell::INPUT_OSCILLATOR; - else if (oldType == SynthEngine::GridCell::INPUT_OSCILLATOR) newType = SynthEngine::GridCell::WAVETABLE; - else if (oldType == SynthEngine::GridCell::WAVETABLE) newType = SynthEngine::GridCell::NOISE; - else if (oldType == SynthEngine::GridCell::NOISE) newType = SynthEngine::GridCell::FORK; - else if (oldType == SynthEngine::GridCell::FORK) newType = SynthEngine::GridCell::WIRE; - else if (oldType == SynthEngine::GridCell::WIRE) newType = SynthEngine::GridCell::OPERATOR; - else if (oldType == SynthEngine::GridCell::OPERATOR) newType = SynthEngine::GridCell::DELAY; - else if (oldType == SynthEngine::GridCell::DELAY) newType = SynthEngine::GridCell::REVERB; - else if (oldType == SynthEngine::GridCell::REVERB) newType = SynthEngine::GridCell::EMPTY; + for (int i = 0; i < numCellTypes; ++i) { + if (cellTypes[i] == oldType) { + newType = cellTypes[(i + 1) % numCellTypes]; + break; + } + } } else if (e.button.button == SDL_BUTTON_RIGHT) { c.rotation = (c.rotation + 1) % 4; } else if (e.button.button == SDL_BUTTON_MIDDLE) { @@ -701,18 +894,22 @@ int main(int argc, char* argv[]) { } } } else if (e.type == SDL_MOUSEWHEEL) { + SDL_Keymod modState = SDL_GetModState(); + bool fineTune = (modState & KMOD_SHIFT); + int mx, my; SDL_GetMouseState(&mx, &my); if (mx < GRID_PANEL_WIDTH) { // Grid Scroll + float step = fineTune ? 0.01f : 0.05f; int gx = mx / 80; int gy = my / 80; if (gx >= 0 && gx < 5 && gy >= 0 && gy < 8) { std::lock_guard lock(engine.gridMutex); SynthEngine::GridCell& c = engine.grid[gx][gy]; - if (e.wheel.y > 0) c.param += 0.05f; - else c.param -= 0.05f; + 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; } @@ -732,8 +929,9 @@ int main(int argc, char* argv[]) { engine.setFrequency(note_to_freq(current_octave, key_to_note_map[ (SDL_Scancode)current_key_scancode ])); } } else { // Right knob (volume) - if (e.wheel.y > 0) knob_vol_val += 0.05f; - else if (e.wheel.y < 0) knob_vol_val -= 0.05f; + float volStep = fineTune ? 0.01f : 0.05f; + if (e.wheel.y > 0) knob_vol_val += volStep; + else if (e.wheel.y < 0) knob_vol_val -= volStep; if (knob_vol_val > 1.0f) knob_vol_val = 1.0f; if (knob_vol_val < 0.0f) knob_vol_val = 0.0f; diff --git a/synth_engine.cpp b/synth_engine.cpp index e142efb..375ffd0 100644 --- a/synth_engine.cpp +++ b/synth_engine.cpp @@ -118,7 +118,7 @@ float SynthEngine::processGridStep() { // Check if neighbor outputs to (tx, ty) 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::OPERATOR || n.type == GridCell::NOISE || n.type == GridCell::DELAY || n.type == GridCell::REVERB) { + 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::LPF || n.type == GridCell::HPF || n.type == GridCell::VCA || n.type == GridCell::BITCRUSHER || n.type == GridCell::DISTORTION || 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; @@ -146,6 +146,24 @@ float SynthEngine::processGridStep() { return connects ? n.value : 0.0f; }; + // Helper to sum inputs excluding the output direction + auto getSummedInput = [&](int x, int y, GridCell& c) -> float { + float sum = 0.0f; + 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; + }; + + auto getInputFromTheBack = [&](int x, int y, GridCell& c) -> float { + 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); + }; + for (int x = 0; x < 5; ++x) { for (int y = 0; y < 8; ++y) { GridCell& c = grid[x][y]; @@ -155,11 +173,7 @@ float SynthEngine::processGridStep() { val = 0.0f; } else if (c.type == GridCell::FIXED_OSCILLATOR) { // Gather inputs for modulation - float mod = 0.0f; - mod += getInput(x, y, x, y-1); - mod += getInput(x, y, x+1, y); - mod += getInput(x, y, x, y+1); - mod += getInput(x, y, x-1, y); + float mod = getSummedInput(x, y, c); // Freq 10 to 1000 Hz float freq = 10.0f + c.param * 990.0f + (mod * 500.0f); // FM @@ -170,28 +184,23 @@ float SynthEngine::processGridStep() { if (c.phase >= SINE_TABLE_SIZE) c.phase -= SINE_TABLE_SIZE; val = (float)sine_table[(int)c.phase] / 32768.0f; } else if (c.type == GridCell::INPUT_OSCILLATOR) { - float mod = 0.0f; - mod += getInput(x, y, x, y-1); - mod += getInput(x, y, x+1, y); - mod += getInput(x, y, x, y+1); - mod += getInput(x, y, x-1, y); + float mod = getSummedInput(x, y, c); // Freq based on current note + octave param (1-5) float baseFreq = getFrequency(); int octave = 1 + (int)(c.param * 4.99f); // Map 0.0-1.0 to 1-5 float freq = baseFreq * (float)(1 << (octave - 1)); // 2^(octave-1) + freq += (mod * 500.0f); // Apply FM + if (freq < 1.0f) freq = 1.0f; // Protect against negative/zero freq float inc = freq * (float)SINE_TABLE_SIZE / (float)_sampleRate; c.phase += inc; if (c.phase >= SINE_TABLE_SIZE) c.phase -= SINE_TABLE_SIZE; val = (float)sine_table[(int)c.phase] / 32768.0f; } else if (c.type == GridCell::WAVETABLE) { - float mod = 0.0f; - mod += getInput(x, y, x, y-1); - mod += getInput(x, y, x+1, y); - mod += getInput(x, y, x, y+1); - mod += getInput(x, y, x-1, y); + float mod = getSummedInput(x, y, c); - float freq = 440.0f + (mod * 500.0f); // Fixed base freq + FM + // Track current note frequency + FM + float freq = getFrequency() + (mod * 500.0f); if (freq < 1.0f) freq = 1.0f; float inc = freq * (float)SINE_TABLE_SIZE / (float)_sampleRate; @@ -220,6 +229,8 @@ float SynthEngine::processGridStep() { break; } } else if (c.type == GridCell::NOISE) { + float mod = getSummedInput(x, y, c); + float white = (float)rand() / (float)RAND_MAX * 2.0f - 1.0f; int shade = (int)(c.param * 4.99f); switch(shade) { @@ -243,28 +254,92 @@ float SynthEngine::processGridStep() { val = white - c.phase; // HPF result break; } + + // Apply Amplitude Modulation (AM) from input + val *= (1.0f + mod); + } else if (c.type == GridCell::LFO) { + // Low Frequency Oscillator (0.1 Hz to 20 Hz) + float freq = 0.1f + c.param * 19.9f; + float inc = freq * (float)SINE_TABLE_SIZE / (float)_sampleRate; + c.phase += inc; + if (c.phase >= SINE_TABLE_SIZE) c.phase -= SINE_TABLE_SIZE; + // Output full range -1.0 to 1.0 + val = (float)sine_table[(int)c.phase] / 32768.0f; } else if (c.type == GridCell::FORK) { // Sum inputs from "Back" (Input direction) - // Rotation is "Forward". Input is "Back" (rot+2) - 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; - // We only read from the specific input neighbor - val = getInput(x, y, x+dx, y+dy); + val = getInputFromTheBack(x, y, c); } else if (c.type == GridCell::WIRE) { // Sum inputs from all neighbors that point to me - float sum = 0.0f; - sum += getInput(x, y, x, y-1); // N - sum += getInput(x, y, x+1, y); // E - sum += getInput(x, y, x, y+1); // S - sum += getInput(x, y, x-1, y); // W + float sum = getSummedInput(x, y, c); val = sum * c.param; // Fading + } else if (c.type == GridCell::LPF) { + // Input from Back + float 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; + + // c.phase stores previous output + val = c.phase + alpha * (in - c.phase); + c.phase = val; + } else if (c.type == GridCell::HPF) { + // Input from Back + float 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; + + // HPF = Input - LPF + // c.phase stores LPF state + float lpf = c.phase + alpha * (in - c.phase); + c.phase = lpf; + val = in - lpf; + } else if (c.type == GridCell::VCA) { + // Input from Back + float in = getInputFromTheBack(x, y, c); + + // Mod from other directions (sum) + float 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; + } else if (c.type == GridCell::BITCRUSHER) { + float 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; + } else if (c.type == GridCell::DISTORTION) { + float 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)); + } else if (c.type == GridCell::GLITCH) { + float in = getInputFromTheBack(x, y, c); + // Param controls probability of glitch + float chance = c.param * 0.2f; // 0 to 20% chance per sample + if ((float)rand() / RAND_MAX < chance) { + int mode = rand() % 3; + if (mode == 0) val = in * 50.0f; // Massive gain (clipping) + else if (mode == 1) val = (float)(rand() % 32768) / 16384.0f - 1.0f; // White noise burst + else val = 0.0f; // Drop out + } else { + val = in; + } } else if (c.type == GridCell::DELAY) { // Input is from the "Back" (rot+2) - 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; - float input_val = getInput(x, y, x+dx, y+dy); + float input_val = getInputFromTheBack(x, y, c); if (c.buffer && c.buffer_size > 0) { // Write current input to buffer @@ -289,10 +364,7 @@ float SynthEngine::processGridStep() { } } else if (c.type == GridCell::REVERB) { // Input is from the "Back" (rot+2) - 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; - float input_val = getInput(x, y, x+dx, y+dy); + float input_val = getInputFromTheBack(x, y, c); if (c.buffer && c.buffer_size > 0) { // Fixed delay for reverb effect (e.g. 50ms) @@ -318,14 +390,17 @@ float SynthEngine::processGridStep() { // Gather inputs float inputs[4]; int count = 0; - float iN = getInput(x, y, x, y-1); if(iN!=0) inputs[count++] = iN; - float iE = getInput(x, y, x+1, y); if(iE!=0) inputs[count++] = iE; - float iS = getInput(x, y, x, y+1); if(iS!=0) inputs[count++] = iS; - float iW = getInput(x, y, x-1, y); if(iW!=0) inputs[count++] = iW; + 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; if (c.type == GridCell::SINK) { // Sink just sums everything - val = iN + iE + iS + iW; + val = 0.0f; + for(int k=0; k