#define MINIAUDIO_IMPLEMENTATION #include "miniaudio.h" #include #include #include #include #include #include #include #include "synth_engine.h" // Include our portable engine #include // --- Configuration --- const uint32_t SAMPLE_RATE = 44100; const uint32_t CHANNELS = 1; // Mono const int GRID_PANEL_WIDTH = 400; const int SYNTH_PANEL_WIDTH = 800; const int WINDOW_WIDTH = GRID_PANEL_WIDTH + SYNTH_PANEL_WIDTH; // 1200 const int WINDOW_HEIGHT = 640; // --- Visualization Buffer --- const size_t VIS_BUFFER_SIZE = 8192; std::vector vis_buffer(VIS_BUFFER_SIZE, 0); std::atomic vis_write_index{0}; // --- Control State --- int current_octave = 4; // C4 is middle C float knob_vol_val = 0.5f; // --- MIDI / Keyboard Input State --- std::map key_to_note_map; int current_key_scancode = 0; // 0 for none // --- Automated Melody State --- bool auto_melody_enabled = false; Uint32 auto_melody_next_event_time = 0; const int c_major_scale[] = {0, 2, 4, 5, 7, 9, 11, 12}; // Semitones from root float note_to_freq(int octave, int semitone_offset); // --- Global Synth Engine Instance --- // The audio callback needs access to our synth, so we make it global. SynthEngine engine(SAMPLE_RATE); /** * @brief The audio callback function that miniaudio will call. * * This function acts as the bridge between the audio driver and our synth engine. * It asks the engine to fill the audio buffer provided by the driver. */ void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) { (void)pDevice; // Unused (void)pInput; // Unused // Cast the output buffer to the format our engine expects (int16_t). int16_t* pOutputS16 = (int16_t*)pOutput; // Tell our engine to process `frameCount` samples and fill the buffer. engine.process(pOutputS16, frameCount); // Copy to visualization buffer size_t idx = vis_write_index.load(std::memory_order_relaxed); for (ma_uint32 i = 0; i < frameCount; ++i) { vis_buffer[idx] = pOutputS16[i]; idx = (idx + 1) % VIS_BUFFER_SIZE; } vis_write_index.store(idx, std::memory_order_relaxed); } // --- UI Drawing Helpers --- void DrawCircle(SDL_Renderer * renderer, int32_t centreX, int32_t centreY, int32_t radius) { const int32_t diameter = (radius * 2); int32_t x = (radius - 1); int32_t y = 0; int32_t tx = 1; int32_t ty = 1; int32_t error = (tx - diameter); while (x >= y) { SDL_RenderDrawPoint(renderer, centreX + x, centreY - y); SDL_RenderDrawPoint(renderer, centreX + x, centreY + y); SDL_RenderDrawPoint(renderer, centreX - x, centreY - y); SDL_RenderDrawPoint(renderer, centreX - x, centreY + y); SDL_RenderDrawPoint(renderer, centreX + y, centreY - x); SDL_RenderDrawPoint(renderer, centreX + y, centreY + x); SDL_RenderDrawPoint(renderer, centreX - y, centreY - x); SDL_RenderDrawPoint(renderer, centreX - y, centreY + x); if (error <= 0) { ++y; error += ty; ty += 2; } if (error > 0) { --x; tx += 2; error += (tx - diameter); } } } float note_to_freq(int octave, int semitone_offset) { // C0 frequency is the reference for calculating other notes const float c0_freq = 16.35f; int midi_note = (octave * 12) + semitone_offset; return c0_freq * pow(2.0f, midi_note / 12.0f); } void drawKnob(SDL_Renderer* renderer, int x, int y, int radius, float value) { // Draw outline SDL_SetRenderDrawColor(renderer, 100, 100, 100, 255); DrawCircle(renderer, x, y, radius); DrawCircle(renderer, x, y, radius-1); // Draw indicator float angle = (value * (270.0f * M_PI / 180.0f)) - (135.0f * M_PI / 180.0f); int x2 = x + (int)(sin(angle) * (radius - 2)); int y2 = y - (int)(cos(angle) * (radius - 2)); SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); SDL_RenderDrawLine(renderer, x, y, x2, y2); } void drawToggle(SDL_Renderer* renderer, int x, int y, int size, bool active) { // Draw box SDL_Rect rect = {x - size/2, y - size/2, size, size}; SDL_SetRenderDrawColor(renderer, 100, 100, 100, 255); SDL_RenderDrawRect(renderer, &rect); if (active) { SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); SDL_Rect inner = {x - size/2 + 4, y - size/2 + 4, size - 8, size - 8}; SDL_RenderFillRect(renderer, &inner); } // Draw 'M' SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); int m_w = size / 2; int m_h = size / 2; int m_x = x - m_w / 2; int m_y = y - m_h / 2; SDL_RenderDrawLine(renderer, m_x, m_y + m_h, m_x, m_y); // Left leg SDL_RenderDrawLine(renderer, m_x, m_y, m_x + m_w/2, m_y + m_h); // Diagonal down SDL_RenderDrawLine(renderer, m_x + m_w/2, m_y + m_h, m_x + m_w, m_y); // Diagonal up SDL_RenderDrawLine(renderer, m_x + m_w, m_y, m_x + m_w, m_y + m_h); // Right leg } void drawSlider(SDL_Renderer* renderer, int x, int y, int w, int h, float val, const char* label) { // Track SDL_SetRenderDrawColor(renderer, 80, 80, 80, 255); SDL_Rect track = {x + w/2 - 2, y, 4, h}; SDL_RenderFillRect(renderer, &track); // Handle int handleH = 10; int handleY = y + h - (int)(val * h) - handleH/2; SDL_SetRenderDrawColor(renderer, 200, 200, 200, 255); SDL_Rect handle = {x, handleY, w, handleH}; SDL_RenderFillRect(renderer, &handle); SDL_RenderDrawRect(renderer, &handle); } // --- Simple Vector Font --- void drawChar(SDL_Renderer* renderer, int x, int y, int size, char c) { int w = size * 0.6; int h = size; int h2 = h / 2; int w2 = w / 2; switch(c) { case '0': SDL_RenderDrawLine(renderer, x, y, x+w, y); SDL_RenderDrawLine(renderer, x+w, y, x+w, y+h); SDL_RenderDrawLine(renderer, x+w, y+h, x, y+h); SDL_RenderDrawLine(renderer, x, y+h, x, y); break; case '1': SDL_RenderDrawLine(renderer, x+w2, y, x+w2, y+h); break; case '2': 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, y+h); SDL_RenderDrawLine(renderer, x, y+h, x+w, y+h); break; case '3': SDL_RenderDrawLine(renderer, x, y, x+w, y); SDL_RenderDrawLine(renderer, x+w, y, x+w, y+h); SDL_RenderDrawLine(renderer, x, y+h2, x+w, y+h2); SDL_RenderDrawLine(renderer, x, y+h, x+w, y+h); break; case '4': SDL_RenderDrawLine(renderer, x, y, x, y+h2); SDL_RenderDrawLine(renderer, x, y+h2, x+w, y+h2); SDL_RenderDrawLine(renderer, x+w, y, x+w, y+h); break; case '5': 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 '6': SDL_RenderDrawLine(renderer, x+w, y, x, y); SDL_RenderDrawLine(renderer, x, y, x, y+h); SDL_RenderDrawLine(renderer, x, y+h2, x+w, y+h2); SDL_RenderDrawLine(renderer, x, y+h, x+w, y+h); SDL_RenderDrawLine(renderer, x+w, y+h2, x+w, y+h); break; case '7': SDL_RenderDrawLine(renderer, x, y, x+w, y); SDL_RenderDrawLine(renderer, x+w, y, x, y+h); break; case '8': SDL_RenderDrawLine(renderer, x, y, x+w, y); SDL_RenderDrawLine(renderer, x+w, y, x+w, y+h); SDL_RenderDrawLine(renderer, x+w, y+h, x, y+h); SDL_RenderDrawLine(renderer, x, y+h, x, y); SDL_RenderDrawLine(renderer, x, y+h2, x+w, y+h2); break; case '9': SDL_RenderDrawLine(renderer, x+w, y+h, x+w, y); 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); break; case '.': SDL_RenderDrawPoint(renderer, x+w2, y+h-2); break; case '+': SDL_RenderDrawLine(renderer, x, y+h2, x+w, y+h2); SDL_RenderDrawLine(renderer, x+w2, y, x+w2, y+h); break; case '-': SDL_RenderDrawLine(renderer, x, y+h2, x+w, y+h2); break; case '*': SDL_RenderDrawLine(renderer, x, y, x+w, y+h); SDL_RenderDrawLine(renderer, x+w, y, x, y+h); break; // X 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; } } void drawString(SDL_Renderer* renderer, int x, int y, int size, const char* str) { int cursor = x; while (*str) { drawChar(renderer, cursor, y, size, *str); cursor += (size * 0.6) + 4; 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 SDL_Rect rect = {x, y, size, size}; SDL_SetRenderDrawColor(renderer, 30, 30, 30, 255); SDL_RenderFillRect(renderer, &rect); SDL_SetRenderDrawColor(renderer, 100, 100, 100, 255); SDL_RenderDrawRect(renderer, &rect); int cx = x + size/2; int cy = y + size/2; int r = size/3; SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); 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); } else if (cell.type == SynthEngine::GridCell::WIRE) { // Draw center SDL_RenderDrawPoint(renderer, cx, cy); // 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); // Param (Fading) 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, '-'); } else if (cell.type == SynthEngine::GridCell::FIXED_OSCILLATOR) { 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); // Param (Freq) 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 - 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 // 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); // 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 - 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) { SDL_RenderDrawPoint(renderer, x + rand()%size, y + rand()%size); } // 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); // Param (Color) 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 - 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 SDL_RenderDrawPoint(renderer, cx, cy); // Input (Back) 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, cy, cx+idx, cy+idy); // Outputs (Left/Right) int lDir = (cell.rotation + 3) % 4; int rDir = (cell.rotation + 1) % 4; int ldx=0, ldy=0, rdx=0, rdy=0; if(lDir==0) ldy=-r; else if(lDir==1) ldx=r; else if(lDir==2) ldy=r; else ldx=-r; if(rDir==0) rdy=-r; else if(rDir==1) rdx=r; else if(rDir==2) rdy=r; else rdx=-r; SDL_RenderDrawLine(renderer, cx, cy, cx+ldx, cy+ldy); SDL_RenderDrawLine(renderer, cx, cy, cx+rdx, cy+rdy); // Param (Balance) char buf[16]; snprintf(buf, 16, "%.1f", 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, 'Y'); } else if (cell.type == SynthEngine::GridCell::DELAY) { // Draw D drawString(renderer, cx - 5, cy - 5, 12, "D"); // Input (Back) 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); // Output (Front) int outDir = cell.rotation; int odx=0, ody=0; if(outDir==0) ody=-r; else if(outDir==1) odx=r; else if(outDir==2) ody=r; else odx=-r; SDL_RenderDrawLine(renderer, cx, cy, cx+odx, cy+ody); // Param (Delay time in ms) char buf[16]; 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 - 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"); // Input (Back) 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); // Output (Front) int outDir = cell.rotation; int odx=0, ody=0; if(outDir==0) ody=-r; else if(outDir==1) odx=r; else if(outDir==2) ody=r; else odx=-r; SDL_RenderDrawLine(renderer, cx, cy, cx+odx, cy+ody); // Param (Strength) char buf[16]; snprintf(buf, 16, "%.2f", cell.param); SDL_SetRenderDrawColor(renderer, 200, 100, 255, 255); drawString(renderer, x + 5, y + size - 18, 10, buf); drawParamBar(renderer, x, y, size, cell.param, 200, 100, 255); 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); 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); // Draw Op Symbol char opChar = '?'; int opType = (int)(cell.param * 5.99f); if (opType == 0) opChar = '+'; else if (opType == 1) opChar = '*'; else if (opType == 2) opChar = '-'; else if (opType == 3) opChar = '/'; else if (opType == 4) opChar = '<'; else if (opType == 5) opChar = '>'; drawChar(renderer, cx - 15, cy - 15, 12, opChar); drawParamBar(renderer, x, y, size, cell.param, 255, 255, 255); drawTypeLabel(renderer, x, y, 'M'); } else if (cell.type == SynthEngine::GridCell::WAVETABLE) { drawString(renderer, cx - 5, cy - 5, 12, "W"); // 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); // Param (Wave index) int idx = (int)(cell.param * 7.99f); char buf[4]; snprintf(buf, 4, "%d", idx); SDL_SetRenderDrawColor(renderer, 128, 128, 255, 255); drawString(renderer, x + 5, y + size - 18, 10, buf); drawParamBar(renderer, x, y, size, cell.param, 128, 128, 255); drawTypeLabel(renderer, x, y, 'W'); } } void clearGrid() { std::lock_guard lock(engine.gridMutex); 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::SINK) continue; 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; } c.type = SynthEngine::GridCell::EMPTY; c.param = 0.5f; c.rotation = 0; c.value = 0.0f; c.phase = 0.0f; } } } void randomizeGrid() { std::lock_guard lock(engine.gridMutex); // Number of types to choose from (excluding SINK) const int numTypes = (int)SynthEngine::GridCell::SINK; // 1. Clear existing buffers first for (int x = 0; x < 5; ++x) { for (int y = 0; y < 8; ++y) { SynthEngine::GridCell& c = engine.grid[x][y]; if (c.buffer) { delete[] c.buffer; c.buffer = nullptr; c.buffer_size = 0; } if (c.type != SynthEngine::GridCell::SINK) { c.type = SynthEngine::GridCell::EMPTY; } } } int attempts = 0; bool inputOscillatorReachable = false; bool visited[5][8]; while (!inputOscillatorReachable && attempts < 1000) { attempts++; // 2. Randomize (without allocation) 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::SINK) continue; c.type = (SynthEngine::GridCell::Type)(rand() % numTypes); c.rotation = rand() % 4; c.param = (float)rand() / (float)RAND_MAX; } } // 3. Check Connectivity // BFS from SINK (2,3) backwards 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()) { std::pair curr = q[head++]; int cx = curr.first; int cy = curr.second; // Check neighbors to see if they output to (cx, cy) int nx[4] = {0, 1, 0, -1}; int ny[4] = {-1, 0, 1, 0}; 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 && !visited[tx][ty]) { SynthEngine::GridCell& neighbor = engine.grid[tx][ty]; bool pointsToCurr = false; if (neighbor.type == SynthEngine::GridCell::EMPTY || neighbor.type == SynthEngine::GridCell::SINK) { pointsToCurr = false; } else if (neighbor.type == SynthEngine::GridCell::FORK) { int dx = cx - tx; int dy = cy - ty; int dir = -1; if (dx == 0 && dy == -1) dir = 0; // N else if (dx == 1 && dy == 0) dir = 1; // E else if (dx == 0 && dy == 1) dir = 2; // S else if (dx == -1 && dy == 0) dir = 3; // W int leftOut = (neighbor.rotation + 3) % 4; int rightOut = (neighbor.rotation + 1) % 4; if (dir == leftOut || dir == rightOut) pointsToCurr = true; } else { // Standard directional int dx = cx - tx; int dy = cy - ty; int dir = -1; if (dx == 0 && dy == -1) dir = 0; // N else if (dx == 1 && dy == 0) dir = 1; // E else if (dx == 0 && dy == 1) dir = 2; // S else if (dx == -1 && dy == 0) dir = 3; // W if (neighbor.rotation == dir) pointsToCurr = true; } if (pointsToCurr) { visited[tx][ty] = true; q.push_back({tx, ty}); } } } } // 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. 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 || c.type == SynthEngine::GridCell::PITCH_SHIFTER) { c.buffer_size = 2 * SAMPLE_RATE; c.buffer = new float[c.buffer_size](); c.write_idx = 0; } } } printf("Randomized in %d attempts. Input Osc reachable: %s\n", attempts, inputOscillatorReachable ? "YES" : "NO"); } int main(int argc, char* argv[]) { (void)argc; (void)argv; srand(time(NULL)); // Seed random number generator // --- Init SDL --- if (SDL_Init(SDL_INIT_VIDEO) < 0) { printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError()); return -1; } SDL_Window* window = SDL_CreateWindow("NoiceSynth Integrated", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_SHOWN); if (!window) return -1; SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); if (!renderer) return -1; ma_device_config config = ma_device_config_init(ma_device_type_playback); config.playback.format = ma_format_s16; // Must match our engine's output format config.playback.channels = CHANNELS; config.sampleRate = SAMPLE_RATE; config.dataCallback = data_callback; ma_device device; if (ma_device_init(NULL, &config, &device) != MA_SUCCESS) { printf("Failed to initialize playback device.\n"); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); return -1; } printf("Device Name: %s\n", device.playback.name); ma_device_start(&device); // --- Setup Keyboard to Note Mapping --- // Two rows of keys mapped to a chromatic scale key_to_note_map[SDL_SCANCODE_A] = 0; // C key_to_note_map[SDL_SCANCODE_W] = 1; // C# key_to_note_map[SDL_SCANCODE_S] = 2; // D key_to_note_map[SDL_SCANCODE_E] = 3; // D# key_to_note_map[SDL_SCANCODE_D] = 4; // E key_to_note_map[SDL_SCANCODE_F] = 5; // F key_to_note_map[SDL_SCANCODE_T] = 6; // F# key_to_note_map[SDL_SCANCODE_G] = 7; // G key_to_note_map[SDL_SCANCODE_Y] = 8; // G# key_to_note_map[SDL_SCANCODE_H] = 9; // A key_to_note_map[SDL_SCANCODE_U] = 10; // A# key_to_note_map[SDL_SCANCODE_J] = 11; // B key_to_note_map[SDL_SCANCODE_K] = 12; // C (octave up) key_to_note_map[SDL_SCANCODE_O] = 13; // C# key_to_note_map[SDL_SCANCODE_L] = 14; // D key_to_note_map[SDL_SCANCODE_P] = 15; // D# key_to_note_map[SDL_SCANCODE_SEMICOLON] = 16; // E engine.setVolume(knob_vol_val); engine.setGate(false); // Start with silence // --- 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; while (!quit) { // --- Automated Melody Logic --- if (auto_melody_enabled && SDL_GetTicks() > auto_melody_next_event_time) { auto_melody_next_event_time = SDL_GetTicks() + 200 + (rand() % 150); // Note duration if ((rand() % 10) < 2) { // 20% chance of a rest engine.setGate(false); } else { int note_index = rand() % 8; // Pick from 8 notes in the scale array int semitone = c_major_scale[note_index]; int note_octave = 4 + (rand() % 2); // Play in octave 4 or 5 engine.setFrequency(note_to_freq(note_octave, semitone)); engine.setGate(true); } } // --- Event Handling --- while (SDL_PollEvent(&e) != 0) { if (e.type == SDL_QUIT) { quit = true; } else if (e.type == SDL_WINDOWEVENT && e.window.event == SDL_WINDOWEVENT_CLOSE) { quit = true; // Close app if any window closes } // --- Mouse Handling --- // Check if event is in Grid Panel (Left) or Synth Panel (Right) if (e.type == SDL_MOUSEBUTTONDOWN) { int mx = e.button.x; int my = e.button.y; if (mx < GRID_PANEL_WIDTH) { 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 (c.type != SynthEngine::GridCell::SINK) { SynthEngine::GridCell::Type oldType = c.type; SynthEngine::GridCell::Type newType = oldType; if (e.button.button == SDL_BUTTON_LEFT) { 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) { newType = SynthEngine::GridCell::EMPTY; c.param = 0.5f; c.rotation = 0; } if (newType != oldType) { // If old type was DELAY, free its 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 || 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 } } } } } else { // Synth Panel Click int synthX = mx - GRID_PANEL_WIDTH; // Check Toggle Click int toggleX = 580; int toggleY = 450; int toggleSize = 30; if (synthX >= toggleX - toggleSize/2 && synthX <= toggleX + toggleSize/2 && my >= toggleY - toggleSize/2 && my <= toggleY + toggleSize/2) { auto_melody_enabled = !auto_melody_enabled; engine.setGate(false); // Silence synth on mode change current_key_scancode = 0; if (auto_melody_enabled) { auto_melody_next_event_time = SDL_GetTicks(); // Start immediately } } } } 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 += step; else c.param -= step; if (c.param > 1.0f) c.param = 1.0f; if (c.param < 0.0f) c.param = 0.0f; } } else { // Synth Scroll int synthX = mx - GRID_PANEL_WIDTH; if (synthX < SYNTH_PANEL_WIDTH / 2) { // Left knob (Octave) if (e.wheel.y > 0) current_octave++; else if (e.wheel.y < 0) current_octave--; if (current_octave < 0) current_octave = 0; if (current_octave > 8) current_octave = 8; // If a note is being held, update its frequency to the new octave if (!auto_melody_enabled && current_key_scancode != 0) { engine.setFrequency(note_to_freq(current_octave, key_to_note_map[ (SDL_Scancode)current_key_scancode ])); } } else { // Right knob (volume) 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; engine.setVolume(knob_vol_val); } } } else if (e.type == SDL_MOUSEMOTION) { if (e.motion.state & SDL_BUTTON_LMASK) { int mouseX = e.motion.x; int mouseY = e.motion.y; if (mouseX >= GRID_PANEL_WIDTH) { int synthX = mouseX - GRID_PANEL_WIDTH; } } } else if (e.type == SDL_KEYDOWN) { if (e.key.repeat == 0) { // Ignore key repeats if (e.key.keysym.scancode == SDL_SCANCODE_INSERT) { randomizeGrid(); } else if (e.key.keysym.scancode == SDL_SCANCODE_DELETE) { clearGrid(); } else if (e.key.keysym.scancode == SDL_SCANCODE_M) { auto_melody_enabled = !auto_melody_enabled; engine.setGate(false); // Silence synth on mode change current_key_scancode = 0; if (auto_melody_enabled) { auto_melody_next_event_time = SDL_GetTicks(); // Start immediately } } else { // Only allow manual playing if auto-melody is off if (!auto_melody_enabled && key_to_note_map.count(e.key.keysym.scancode)) { current_key_scancode = e.key.keysym.scancode; int semitone_offset = key_to_note_map[ (SDL_Scancode)current_key_scancode ]; engine.setFrequency(note_to_freq(current_octave, semitone_offset)); engine.setGate(true); } } } } else if (e.type == SDL_KEYUP) { if (!auto_melody_enabled && e.key.keysym.scancode == current_key_scancode) { engine.setGate(false); current_key_scancode = 0; } } } // Update window title with current values char title[256]; snprintf(title, sizeof(title), "NoiceSynth | Vol: %.0f%% | Oct: %d | Auto(M): %s", knob_vol_val * 100.0f, current_octave, auto_melody_enabled ? "ON" : "OFF"); SDL_SetWindowTitle(window, title); // Clear screen SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderClear(renderer); // --- Draw Synth Panel (Right) --- SDL_Rect synthViewport = {GRID_PANEL_WIDTH, 0, SYNTH_PANEL_WIDTH, WINDOW_HEIGHT}; SDL_RenderSetViewport(renderer, &synthViewport); // --- Draw Waveform (Oscilloscope) --- SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); // Green // Determine read position (snapshot atomic write index) size_t write_idx = vis_write_index.load(std::memory_order_relaxed); // Find trigger (zero crossing) to stabilize the display // Look back from write_idx to find a stable point size_t search_start_offset = 2000; size_t read_idx = (write_idx + VIS_BUFFER_SIZE - search_start_offset) % VIS_BUFFER_SIZE; // Simple trigger search: find crossing from negative to positive for (size_t i = 0; i < 1000; ++i) { int16_t s1 = vis_buffer[read_idx]; size_t next_idx = (read_idx + 1) % VIS_BUFFER_SIZE; int16_t s2 = vis_buffer[next_idx]; if (s1 <= 0 && s2 > 0) { read_idx = next_idx; // Found trigger break; } read_idx = next_idx; } // Draw points int prev_x = -1; int prev_y = -1; for (int x = 0; x < SYNTH_PANEL_WIDTH; ++x) { int16_t sample = vis_buffer[read_idx]; read_idx = (read_idx + 1) % VIS_BUFFER_SIZE; // Map 16-bit sample (-32768 to 32767) to screen height // Use top half of window, so divide height by 4 (2 for half, 2 for +/-) int y = WINDOW_HEIGHT / 4 - (sample * (WINDOW_HEIGHT / 4) / 32768); if (prev_x != -1) { SDL_RenderDrawLine(renderer, prev_x, prev_y, x, y); } prev_x = x; prev_y = y; } // --- Draw Controls --- // Draw in the bottom half of the window // Knobs moved to edges float normalized_octave = (float)current_octave / 8.0f; // Max octave 8 drawKnob(renderer, 100, 450, 40, normalized_octave); drawKnob(renderer, 700, 450, 40, knob_vol_val); drawToggle(renderer, 580, 450, 30, auto_melody_enabled); // --- Draw Grid Panel (Left) --- SDL_Rect gridViewport = {0, 0, GRID_PANEL_WIDTH, WINDOW_HEIGHT}; SDL_RenderSetViewport(renderer, &gridViewport); // Draw separator line SDL_SetRenderDrawColor(renderer, 50, 50, 50, 255); SDL_RenderDrawLine(renderer, GRID_PANEL_WIDTH - 1, 0, GRID_PANEL_WIDTH - 1, WINDOW_HEIGHT); { // Lock only for reading state to draw std::lock_guard lock(engine.gridMutex); for(int x=0; x<5; ++x) { for(int y=0; y<8; ++y) { drawGridCell(renderer, x*80, y*80, 80, engine.grid[x][y]); } } } SDL_RenderPresent(renderer); } ma_device_uninit(&device); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); return 0; }