New elements and fixes

This commit is contained in:
Dejvino 2026-02-28 17:49:26 +01:00
parent 58af6bd3dc
commit db76f4fcef
3 changed files with 357 additions and 84 deletions

278
main.cpp
View File

@ -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');
}
}
@ -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,7 +615,7 @@ void randomizeGrid() {
// 3. Check Connectivity
// BFS from SINK (2,3) backwards
bool visited[5][8] = {false};
memset(visited, 0, sizeof(visited));
std::vector<std::pair<int, int>> q;
q.push_back({2, 3});
@ -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<std::mutex> 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;

View File

@ -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<count; ++k) val += inputs[k];
} else {
// Operator
int opType = (int)(c.param * 5.99f);

View File

@ -86,7 +86,7 @@ public:
// --- Grid Synth ---
struct GridCell {
enum Type { EMPTY, FIXED_OSCILLATOR, INPUT_OSCILLATOR, WAVETABLE, NOISE, FORK, WIRE, OPERATOR, DELAY, REVERB, SINK };
enum Type { EMPTY, FIXED_OSCILLATOR, INPUT_OSCILLATOR, WAVETABLE, NOISE, LFO, FORK, WIRE, LPF, HPF, VCA, BITCRUSHER, DISTORTION, GLITCH, OPERATOR, DELAY, REVERB, SINK };
enum Op { OP_ADD, OP_MUL, OP_SUB, OP_DIV, OP_MIN, OP_MAX };
Type type = EMPTY;