Compare commits
2 Commits
58af6bd3dc
...
7ff85048df
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ff85048df | ||
|
|
db76f4fcef |
436
main.cpp
436
main.cpp
@ -27,9 +27,6 @@ std::atomic<size_t> vis_write_index{0};
|
||||
// --- Control State ---
|
||||
int current_octave = 4; // C4 is middle C
|
||||
float knob_vol_val = 0.5f;
|
||||
// ADSR (A, D, R in seconds, S in 0-1)
|
||||
float adsr_vals[4] = {0.05f, 0.2f, 0.6f, 0.5f};
|
||||
float filter_vals[2] = {1.0f, 0.0f}; // LP (1.0=Open), HP (0.0=Open)
|
||||
|
||||
// --- MIDI / Keyboard Input State ---
|
||||
std::map<SDL_Scancode, int> key_to_note_map;
|
||||
@ -192,14 +189,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 +230,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 +266,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 +283,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 +299,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 +315,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 +335,207 @@ 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::GATE) {
|
||||
SDL_Rect box = {cx - r, cy - r, r*2, r*2};
|
||||
SDL_RenderDrawRect(renderer, &box);
|
||||
if (cell.value > 0.5f) SDL_RenderFillRect(renderer, &box);
|
||||
drawString(renderer, cx - 8, cy - 5, 12, "GAT");
|
||||
drawTypeLabel(renderer, x, y, '!');
|
||||
} else if (cell.type == SynthEngine::GridCell::GATE_INPUT) {
|
||||
SDL_Rect box = {cx - r, cy - r, r*2, r*2};
|
||||
SDL_RenderDrawRect(renderer, &box);
|
||||
if (cell.value > 0.5f) 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) {
|
||||
// Draw Ramp Up
|
||||
SDL_RenderDrawLine(renderer, cx-r, cy+r, cx+r, cy-r);
|
||||
// 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, 255, 255);
|
||||
drawTypeLabel(renderer, x, y, 'A');
|
||||
} else if (cell.type == SynthEngine::GridCell::ADSR_DECAY) {
|
||||
// Draw Ramp Down
|
||||
SDL_RenderDrawLine(renderer, cx-r, cy-r, cx+r, cy+r);
|
||||
// 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, 255, 255);
|
||||
drawTypeLabel(renderer, x, y, 'D');
|
||||
} else if (cell.type == SynthEngine::GridCell::ADSR_SUSTAIN) {
|
||||
// Draw Level
|
||||
SDL_RenderDrawLine(renderer, cx-r, cy, cx+r, cy);
|
||||
// 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, 255, 255);
|
||||
drawTypeLabel(renderer, x, y, 'S');
|
||||
} else if (cell.type == SynthEngine::GridCell::ADSR_RELEASE) {
|
||||
// Draw Ramp Down
|
||||
SDL_RenderDrawLine(renderer, cx-r, cy-r, cx+r, cy+r);
|
||||
// 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, 255, 255);
|
||||
drawTypeLabel(renderer, x, y, 'E');
|
||||
} 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::RECTIFIER) {
|
||||
SDL_Rect box = {cx - r, cy - r, r*2, r*2};
|
||||
SDL_RenderDrawRect(renderer, &box);
|
||||
drawString(renderer, cx - 8, cy - 5, 12, "ABS");
|
||||
// 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, 150, 0);
|
||||
drawTypeLabel(renderer, x, y, '|');
|
||||
} else if (cell.type == SynthEngine::GridCell::PITCH_SHIFTER) {
|
||||
drawString(renderer, cx - 8, cy - 5, 12, "PIT");
|
||||
// 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, 100, 255, 100);
|
||||
drawTypeLabel(renderer, x, y, '^');
|
||||
} 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 +557,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 +579,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 +598,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 +620,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 +637,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');
|
||||
}
|
||||
}
|
||||
|
||||
@ -397,7 +650,7 @@ void clearGrid() {
|
||||
SynthEngine::GridCell& c = engine.grid[x][y];
|
||||
if (c.type == SynthEngine::GridCell::SINK) continue;
|
||||
|
||||
if ((c.type == SynthEngine::GridCell::DELAY || c.type == SynthEngine::GridCell::REVERB) && c.buffer) {
|
||||
if ((c.type == SynthEngine::GridCell::DELAY || c.type == SynthEngine::GridCell::REVERB || c.type == SynthEngine::GridCell::PITCH_SHIFTER) && c.buffer) {
|
||||
delete[] c.buffer;
|
||||
c.buffer = nullptr;
|
||||
c.buffer_size = 0;
|
||||
@ -421,7 +674,7 @@ void randomizeGrid() {
|
||||
for (int x = 0; x < 5; ++x) {
|
||||
for (int y = 0; y < 8; ++y) {
|
||||
SynthEngine::GridCell& c = engine.grid[x][y];
|
||||
if ((c.type == SynthEngine::GridCell::DELAY || c.type == SynthEngine::GridCell::REVERB) && c.buffer) {
|
||||
if (c.buffer) {
|
||||
delete[] c.buffer;
|
||||
c.buffer = nullptr;
|
||||
c.buffer_size = 0;
|
||||
@ -433,9 +686,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 +706,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 +725,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,27 +758,43 @@ 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];
|
||||
if (c.type == SynthEngine::GridCell::DELAY || c.type == SynthEngine::GridCell::REVERB) {
|
||||
if (c.type == SynthEngine::GridCell::DELAY || c.type == SynthEngine::GridCell::REVERB || c.type == SynthEngine::GridCell::PITCH_SHIFTER) {
|
||||
c.buffer_size = 2 * SAMPLE_RATE;
|
||||
c.buffer = new float[c.buffer_size]();
|
||||
c.write_idx = 0;
|
||||
@ -534,7 +802,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[]) {
|
||||
@ -596,11 +864,36 @@ int main(int argc, char* argv[]) {
|
||||
engine.setVolume(knob_vol_val);
|
||||
engine.setGate(false); // Start with silence
|
||||
|
||||
// Init engine with default UI values
|
||||
engine.setADSR(adsr_vals[0]*2.0f, adsr_vals[1]*2.0f, adsr_vals[2], adsr_vals[3]*2.0f);
|
||||
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::GATE,
|
||||
SynthEngine::GridCell::GATE_INPUT,
|
||||
SynthEngine::GridCell::ADSR_ATTACK,
|
||||
SynthEngine::GridCell::ADSR_DECAY,
|
||||
SynthEngine::GridCell::ADSR_SUSTAIN,
|
||||
SynthEngine::GridCell::ADSR_RELEASE,
|
||||
SynthEngine::GridCell::FORK,
|
||||
SynthEngine::GridCell::WIRE,
|
||||
SynthEngine::GridCell::LPF,
|
||||
SynthEngine::GridCell::HPF,
|
||||
SynthEngine::GridCell::VCA,
|
||||
SynthEngine::GridCell::BITCRUSHER,
|
||||
SynthEngine::GridCell::DISTORTION,
|
||||
SynthEngine::GridCell::RECTIFIER,
|
||||
SynthEngine::GridCell::PITCH_SHIFTER,
|
||||
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 +938,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) {
|
||||
@ -666,14 +954,14 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
if (newType != oldType) {
|
||||
// If old type was DELAY, free its buffer
|
||||
if ((oldType == SynthEngine::GridCell::DELAY || oldType == SynthEngine::GridCell::REVERB) && c.buffer) {
|
||||
if ((oldType == SynthEngine::GridCell::DELAY || oldType == SynthEngine::GridCell::REVERB || oldType == SynthEngine::GridCell::PITCH_SHIFTER) && c.buffer) {
|
||||
delete[] c.buffer;
|
||||
c.buffer = nullptr;
|
||||
c.buffer_size = 0;
|
||||
}
|
||||
c.type = newType;
|
||||
// If new type is DELAY, allocate its buffer
|
||||
if (newType == SynthEngine::GridCell::DELAY || newType == SynthEngine::GridCell::REVERB) {
|
||||
if (newType == SynthEngine::GridCell::DELAY || newType == SynthEngine::GridCell::REVERB || newType == SynthEngine::GridCell::PITCH_SHIFTER) {
|
||||
c.buffer_size = 2 * SAMPLE_RATE; // Max 2 seconds delay
|
||||
c.buffer = new float[c.buffer_size](); // Allocate and zero-initialize
|
||||
c.write_idx = 0; // Reset write index
|
||||
@ -701,18 +989,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 +1024,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;
|
||||
@ -747,37 +1040,6 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
if (mouseX >= GRID_PANEL_WIDTH) {
|
||||
int synthX = mouseX - GRID_PANEL_WIDTH;
|
||||
|
||||
// Handle Sliders
|
||||
// ADSR: x=200, 250, 300, 350. y=380, h=150
|
||||
// Filters: x=450, 500.
|
||||
int sliderY = 380;
|
||||
int sliderH = 150;
|
||||
int sliderW = 30;
|
||||
|
||||
auto checkSlider = [&](int idx, int sx, float* val) {
|
||||
if (synthX >= sx && synthX <= sx + sliderW && mouseY >= sliderY - 20 && mouseY <= sliderY + sliderH + 20) {
|
||||
*val = 1.0f - (float)(mouseY - sliderY) / (float)sliderH;
|
||||
if (*val < 0.0f) *val = 0.0f;
|
||||
if (*val > 1.0f) *val = 1.0f;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
bool changed = false;
|
||||
if (checkSlider(0, 200, &adsr_vals[0])) changed = true;
|
||||
if (checkSlider(1, 250, &adsr_vals[1])) changed = true;
|
||||
if (checkSlider(2, 300, &adsr_vals[2])) changed = true;
|
||||
if (checkSlider(3, 350, &adsr_vals[3])) changed = true;
|
||||
if (changed) engine.setADSR(adsr_vals[0]*2.0f, adsr_vals[1]*2.0f, adsr_vals[2], adsr_vals[3]*2.0f);
|
||||
|
||||
if (checkSlider(0, 450, &filter_vals[0]) || checkSlider(1, 500, &filter_vals[1])) {
|
||||
// Map 0-1 to 20Hz-20kHz
|
||||
float lpFreq = 20.0f + pow(filter_vals[0], 2.0f) * 19980.0f; // Exponential feel
|
||||
float hpFreq = 20.0f + pow(filter_vals[1], 2.0f) * 19980.0f;
|
||||
engine.setFilter(lpFreq, hpFreq);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (e.type == SDL_KEYDOWN) {
|
||||
@ -880,14 +1142,6 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
drawToggle(renderer, 580, 450, 30, auto_melody_enabled);
|
||||
|
||||
drawSlider(renderer, 200, 380, 30, 150, adsr_vals[0], "A");
|
||||
drawSlider(renderer, 250, 380, 30, 150, adsr_vals[1], "D");
|
||||
drawSlider(renderer, 300, 380, 30, 150, adsr_vals[2], "S");
|
||||
drawSlider(renderer, 350, 380, 30, 150, adsr_vals[3], "R");
|
||||
|
||||
drawSlider(renderer, 450, 380, 30, 150, filter_vals[0], "LP");
|
||||
drawSlider(renderer, 500, 380, 30, 150, filter_vals[1], "HP");
|
||||
|
||||
// --- Draw Grid Panel (Left) ---
|
||||
SDL_Rect gridViewport = {0, 0, GRID_PANEL_WIDTH, WINDOW_HEIGHT};
|
||||
SDL_RenderSetViewport(renderer, &gridViewport);
|
||||
|
||||
361
synth_engine.cpp
361
synth_engine.cpp
@ -25,20 +25,12 @@ SynthEngine::SynthEngine(uint32_t sampleRate)
|
||||
_volume(0.5f),
|
||||
_waveform(SAWTOOTH),
|
||||
_isGateOpen(false),
|
||||
_envState(ENV_IDLE),
|
||||
_envLevel(0.0f),
|
||||
_attackInc(0.0f),
|
||||
_decayDec(0.0f),
|
||||
_sustainLevel(1.0f),
|
||||
_releaseDec(0.0f),
|
||||
_lpAlpha(1.0f), _hpAlpha(0.0f),
|
||||
_lpVal(0.0f), _hpVal(0.0f),
|
||||
grid{}
|
||||
grid{},
|
||||
_rngState(12345)
|
||||
{
|
||||
fill_sine_table();
|
||||
// Initialize with a default frequency
|
||||
setFrequency(440.0f);
|
||||
setADSR(0.05f, 0.1f, 0.7f, 0.2f); // Default envelope
|
||||
|
||||
// Initialize SINK
|
||||
grid[2][3].type = GridCell::SINK;
|
||||
@ -76,49 +68,28 @@ void SynthEngine::setWaveform(Waveform form) {
|
||||
|
||||
void SynthEngine::setGate(bool isOpen) {
|
||||
_isGateOpen = isOpen;
|
||||
if (isOpen) {
|
||||
_envState = ENV_ATTACK;
|
||||
} else {
|
||||
_envState = ENV_RELEASE;
|
||||
}
|
||||
}
|
||||
|
||||
void SynthEngine::setADSR(float attack, float decay, float sustain, float release) {
|
||||
// Calculate increments per sample based on time in seconds
|
||||
// Avoid division by zero
|
||||
_attackInc = (attack > 0.001f) ? (1.0f / (attack * _sampleRate)) : 1.0f;
|
||||
_decayDec = (decay > 0.001f) ? (1.0f / (decay * _sampleRate)) : 1.0f;
|
||||
_sustainLevel = sustain;
|
||||
_releaseDec = (release > 0.001f) ? (1.0f / (release * _sampleRate)) : 1.0f;
|
||||
}
|
||||
|
||||
void SynthEngine::setFilter(float lpCutoff, float hpCutoff) {
|
||||
// Simple one-pole filter coefficient calculation: alpha = 2*PI*fc/fs
|
||||
_lpAlpha = 2.0f * M_PI * lpCutoff / _sampleRate;
|
||||
if (_lpAlpha > 1.0f) _lpAlpha = 1.0f;
|
||||
if (_lpAlpha < 0.0f) _lpAlpha = 0.0f;
|
||||
|
||||
_hpAlpha = 2.0f * M_PI * hpCutoff / _sampleRate;
|
||||
if (_hpAlpha > 1.0f) _hpAlpha = 1.0f;
|
||||
if (_hpAlpha < 0.0f) _hpAlpha = 0.0f;
|
||||
}
|
||||
|
||||
float SynthEngine::getFrequency() const {
|
||||
return (float)((double)_increment * (double)_sampleRate / 4294967296.0);
|
||||
}
|
||||
|
||||
float SynthEngine::_random() {
|
||||
// Simple Linear Congruential Generator
|
||||
_rngState = _rngState * 1664525 + 1013904223;
|
||||
return (float)_rngState / 4294967296.0f;
|
||||
}
|
||||
|
||||
float SynthEngine::processGridStep() {
|
||||
// Double buffer for values to handle feedback loops gracefully (1-sample delay)
|
||||
float next_values[5][8];
|
||||
|
||||
// Helper to get input from a neighbor
|
||||
auto getInput = [&](int tx, int ty, int from_x, int from_y) -> float {
|
||||
if (from_x < 0 || from_x >= 5 || from_y < 0 || from_y >= 8) return 0.0f;
|
||||
auto isConnected = [&](int tx, int ty, int from_x, int from_y) -> bool {
|
||||
if (from_x < 0 || from_x >= 5 || from_y < 0 || from_y >= 8) return false;
|
||||
GridCell& n = grid[from_x][from_y];
|
||||
|
||||
// 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::GATE || n.type == GridCell::GATE_INPUT || n.type == GridCell::ADSR_ATTACK || n.type == GridCell::ADSR_DECAY || n.type == GridCell::ADSR_SUSTAIN || n.type == GridCell::ADSR_RELEASE || n.type == GridCell::LPF || n.type == GridCell::HPF || n.type == GridCell::VCA || n.type == GridCell::BITCRUSHER || n.type == GridCell::DISTORTION || n.type == GridCell::RECTIFIER || n.type == GridCell::PITCH_SHIFTER || n.type == GridCell::GLITCH || n.type == GridCell::OPERATOR || n.type == GridCell::DELAY || n.type == GridCell::REVERB) {
|
||||
// Check rotation
|
||||
// 0:N (y-1), 1:E (x+1), 2:S (y+1), 3:W (x-1)
|
||||
if (n.rotation == 0 && from_y - 1 == ty && from_x == tx) connects = true;
|
||||
@ -139,11 +110,65 @@ float SynthEngine::processGridStep() {
|
||||
int leftOut = (n.rotation + 3) % 4;
|
||||
int rightOut = (n.rotation + 1) % 4;
|
||||
|
||||
if (dir == leftOut || dir == rightOut) connects = true;
|
||||
}
|
||||
return connects;
|
||||
};
|
||||
|
||||
// Helper to get input from a neighbor
|
||||
auto getInput = [&](int tx, int ty, int from_x, int from_y) -> float {
|
||||
if (!isConnected(tx, ty, from_x, from_y)) return 0.0f;
|
||||
GridCell& n = grid[from_x][from_y];
|
||||
|
||||
if (n.type == GridCell::FORK) {
|
||||
int dx = tx - from_x;
|
||||
int dy = ty - from_y;
|
||||
int dir = -1;
|
||||
if (dx == 0 && dy == -1) dir = 0; // N
|
||||
if (dx == 1 && dy == 0) dir = 1; // E
|
||||
if (dx == 0 && dy == 1) dir = 2; // S
|
||||
if (dx == -1 && dy == 0) dir = 3; // W
|
||||
|
||||
int leftOut = (n.rotation + 3) % 4;
|
||||
int rightOut = (n.rotation + 1) % 4;
|
||||
|
||||
if (dir == leftOut) return n.value * (1.0f - n.param) * 2.0f;
|
||||
if (dir == rightOut) return n.value * n.param * 2.0f;
|
||||
}
|
||||
|
||||
return connects ? n.value : 0.0f;
|
||||
return n.value;
|
||||
};
|
||||
|
||||
// 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);
|
||||
};
|
||||
|
||||
auto getSideInputGain = [&](int x, int y, GridCell& c) -> float {
|
||||
float gain = 0.0f;
|
||||
bool hasSide = false;
|
||||
// Left (rot+3)
|
||||
int lDir = (c.rotation + 3) % 4;
|
||||
int ldx=0, ldy=0; if(lDir==0) ldy=-1; else if(lDir==1) ldx=1; else if(lDir==2) ldy=1; else ldx=-1;
|
||||
if (isConnected(x, y, x+ldx, y+ldy)) { hasSide = true; gain += getInput(x, y, x+ldx, y+ldy); }
|
||||
// Right (rot+1)
|
||||
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;
|
||||
};
|
||||
|
||||
for (int x = 0; x < 5; ++x) {
|
||||
@ -155,11 +180,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 = getInputFromTheBack(x, y, c);
|
||||
|
||||
// Freq 10 to 1000 Hz
|
||||
float freq = 10.0f + c.param * 990.0f + (mod * 500.0f); // FM
|
||||
@ -169,29 +190,26 @@ float SynthEngine::processGridStep() {
|
||||
c.phase += inc;
|
||||
if (c.phase >= SINE_TABLE_SIZE) c.phase -= SINE_TABLE_SIZE;
|
||||
val = (float)sine_table[(int)c.phase] / 32768.0f;
|
||||
val *= getSideInputGain(x, y, c);
|
||||
} 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 = getInputFromTheBack(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;
|
||||
val *= getSideInputGain(x, y, c);
|
||||
} 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 = getInputFromTheBack(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;
|
||||
@ -219,8 +237,11 @@ float SynthEngine::processGridStep() {
|
||||
val /= 0.9f; // Normalize
|
||||
break;
|
||||
}
|
||||
val *= getSideInputGain(x, y, c);
|
||||
} else if (c.type == GridCell::NOISE) {
|
||||
float white = (float)rand() / (float)RAND_MAX * 2.0f - 1.0f;
|
||||
float mod = getInputFromTheBack(x, y, c);
|
||||
|
||||
float white = _random() * 2.0f - 1.0f;
|
||||
int shade = (int)(c.param * 4.99f);
|
||||
switch(shade) {
|
||||
case 0: // Brown (Leaky integrator)
|
||||
@ -243,28 +264,171 @@ float SynthEngine::processGridStep() {
|
||||
val = white - c.phase; // HPF result
|
||||
break;
|
||||
}
|
||||
|
||||
// Apply Amplitude Modulation (AM) from input
|
||||
val *= (1.0f + mod);
|
||||
val *= getSideInputGain(x, y, c);
|
||||
} 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::GATE || c.type == GridCell::GATE_INPUT) {
|
||||
// Outputs 1.0 when gate is open (key pressed), 0.0 otherwise
|
||||
val = _isGateOpen ? 1.0f : 0.0f;
|
||||
} 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;
|
||||
} else {
|
||||
c.value = in;
|
||||
}
|
||||
val = c.value;
|
||||
} 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;
|
||||
} else {
|
||||
c.value = in;
|
||||
}
|
||||
val = c.value;
|
||||
} else if (c.type == GridCell::ADSR_SUSTAIN) {
|
||||
// Attenuator
|
||||
float in = getInputFromTheBack(x, y, c);
|
||||
val = in * c.param;
|
||||
} 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::RECTIFIER) {
|
||||
float 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;
|
||||
} else if (c.type == GridCell::PITCH_SHIFTER) {
|
||||
float in = getInputFromTheBack(x, y, c);
|
||||
if (c.buffer && c.buffer_size > 0) {
|
||||
c.buffer[c.write_idx] = in;
|
||||
|
||||
// Granular pitch shift
|
||||
// Pitch ratio: 0.5 to 2.0
|
||||
float pitchRatio = 0.5f + c.param * 1.5f;
|
||||
// Delay rate change: 1.0 - pitchRatio
|
||||
// If pitch=1, rate=0 (delay constant). If pitch=2, rate=-1 (delay decreases).
|
||||
float rate = 1.0f - pitchRatio;
|
||||
|
||||
c.phase += rate;
|
||||
// Wrap phase within window (e.g. 4096 samples)
|
||||
float windowSize = 4096.0f;
|
||||
if (c.phase >= windowSize) c.phase -= windowSize;
|
||||
if (c.phase < 0.0f) c.phase += windowSize;
|
||||
|
||||
// Read from buffer
|
||||
// Simple crossfade windowing would be better, but for now just a single tap with moving delay
|
||||
// To reduce clicks, we really need 2 taps. Let's stick to single tap for simplicity in this grid context, or maybe just a vibrato if rate is LFO?
|
||||
// Actually, let's implement the 2-tap crossfade for quality.
|
||||
// Tap 1
|
||||
float p1 = c.phase;
|
||||
float p2 = c.phase + windowSize * 0.5f;
|
||||
if (p2 >= windowSize) p2 -= windowSize;
|
||||
|
||||
// Window function (Triangle)
|
||||
auto getWindow = [&](float p) -> float {
|
||||
return 1.0f - fabsf(2.0f * (p / windowSize) - 1.0f);
|
||||
};
|
||||
|
||||
// Read indices
|
||||
int r1 = (int)c.write_idx - (int)p1;
|
||||
if (r1 < 0) r1 += c.buffer_size;
|
||||
int r2 = (int)c.write_idx - (int)p2;
|
||||
if (r2 < 0) r2 += c.buffer_size;
|
||||
|
||||
val = c.buffer[r1] * getWindow(p1) + c.buffer[r2] * getWindow(p2);
|
||||
|
||||
c.write_idx = (c.write_idx + 1) % c.buffer_size;
|
||||
} else {
|
||||
val = 0.0f;
|
||||
}
|
||||
} 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 (_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
|
||||
} 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 +453,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 +479,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);
|
||||
@ -376,39 +540,6 @@ void SynthEngine::process(int16_t* buffer, uint32_t numFrames) {
|
||||
// We scale the grid's float output to match this expected range.
|
||||
sampleF *= 32767.0f;
|
||||
|
||||
// Apply Filters (One-pole)
|
||||
// Low Pass
|
||||
_lpVal += _lpAlpha * (sampleF - _lpVal);
|
||||
sampleF = _lpVal;
|
||||
|
||||
// High Pass (implemented as Input - LowPass(hp_cutoff))
|
||||
_hpVal += _hpAlpha * (sampleF - _hpVal);
|
||||
sampleF = sampleF - _hpVal;
|
||||
|
||||
// Apply ADSR Envelope
|
||||
switch (_envState) {
|
||||
case ENV_ATTACK:
|
||||
_envLevel += _attackInc;
|
||||
if (_envLevel >= 1.0f) { _envLevel = 1.0f; _envState = ENV_DECAY; }
|
||||
break;
|
||||
case ENV_DECAY:
|
||||
_envLevel -= _decayDec;
|
||||
if (_envLevel <= _sustainLevel) { _envLevel = _sustainLevel; _envState = ENV_SUSTAIN; }
|
||||
break;
|
||||
case ENV_SUSTAIN:
|
||||
_envLevel = _sustainLevel;
|
||||
break;
|
||||
case ENV_RELEASE:
|
||||
_envLevel -= _releaseDec;
|
||||
if (_envLevel <= 0.0f) { _envLevel = 0.0f; _envState = ENV_IDLE; }
|
||||
break;
|
||||
case ENV_IDLE:
|
||||
_envLevel = 0.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
sampleF *= _envLevel;
|
||||
|
||||
// Apply Master Volume and write to buffer
|
||||
buffer[i] = static_cast<int16_t>(sampleF * _volume);
|
||||
}
|
||||
|
||||
@ -68,25 +68,9 @@ public:
|
||||
*/
|
||||
float getFrequency() const;
|
||||
|
||||
/**
|
||||
* @brief Configures the ADSR envelope.
|
||||
* @param attack Attack time in seconds.
|
||||
* @param decay Decay time in seconds.
|
||||
* @param sustain Sustain level (0.0 to 1.0).
|
||||
* @param release Release time in seconds.
|
||||
*/
|
||||
void setADSR(float attack, float decay, float sustain, float release);
|
||||
|
||||
/**
|
||||
* @brief Configures the filters.
|
||||
* @param lpCutoff Low-pass cutoff frequency in Hz.
|
||||
* @param hpCutoff High-pass cutoff frequency in Hz.
|
||||
*/
|
||||
void setFilter(float lpCutoff, float hpCutoff);
|
||||
|
||||
// --- 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, GATE, GATE_INPUT, ADSR_ATTACK, ADSR_DECAY, ADSR_SUSTAIN, ADSR_RELEASE, FORK, WIRE, LPF, HPF, VCA, BITCRUSHER, DISTORTION, RECTIFIER, PITCH_SHIFTER, GLITCH, OPERATOR, DELAY, REVERB, SINK };
|
||||
enum Op { OP_ADD, OP_MUL, OP_SUB, OP_DIV, OP_MIN, OP_MAX };
|
||||
|
||||
Type type = EMPTY;
|
||||
@ -112,18 +96,10 @@ private:
|
||||
float _volume;
|
||||
Waveform _waveform;
|
||||
bool _isGateOpen;
|
||||
uint32_t _rngState;
|
||||
|
||||
// ADSR State
|
||||
enum EnvState { ENV_IDLE, ENV_ATTACK, ENV_DECAY, ENV_SUSTAIN, ENV_RELEASE };
|
||||
EnvState _envState;
|
||||
float _envLevel;
|
||||
float _attackInc, _decayDec, _sustainLevel, _releaseDec;
|
||||
|
||||
// Filter State
|
||||
float _lpAlpha;
|
||||
float _hpAlpha;
|
||||
float _lpVal;
|
||||
float _hpVal;
|
||||
// Internal random number generator
|
||||
float _random();
|
||||
};
|
||||
|
||||
#endif // SYNTH_ENGINE_H
|
||||
Loading…
Reference in New Issue
Block a user