#define MINIAUDIO_IMPLEMENTATION #include "miniaudio.h" #include #include #include #include #include #include #include #include #include "../synth_engine.h" // Include our portable engine #include #if !defined(_WIN32) #include #include #include #endif // --- Configuration --- const uint32_t SAMPLE_RATE = 44100 / 4; const uint32_t CHANNELS = 1; // Mono const int CELL_SIZE = 60; const int GRID_PANEL_WIDTH = 12 * CELL_SIZE; // 720 const int SYNTH_PANEL_WIDTH = 800; const int WINDOW_WIDTH = GRID_PANEL_WIDTH + SYNTH_PANEL_WIDTH; // 1200 const int WINDOW_HEIGHT = 12 * CELL_SIZE; // 720 // --- 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 int current_preset = 0; int current_patch_slot = 0; // 0-7 SynthEngine::GridCell clipboardCell; 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); void savePatch(int slot) { char filename[64]; snprintf(filename, sizeof(filename), "noicesynth_patch_%d.dat", slot); FILE* f = fopen(filename, "wb"); if (f) { uint8_t buf[SynthEngine::MAX_SERIALIZED_GRID_SIZE]; size_t size = engine.exportGrid(buf); fwrite(buf, 1, size, f); fclose(f); printf("Saved patch to slot %d (%s)\n", slot, filename); } else { printf("Failed to save patch to slot %d\n", slot); } } void loadPatch(int slot) { char filename[64]; snprintf(filename, sizeof(filename), "noicesynth_patch_%d.dat", slot); FILE* f = fopen(filename, "rb"); if (f) { fseek(f, 0, SEEK_END); long size = ftell(f); fseek(f, 0, SEEK_SET); if (size > 0 && size <= (long)SynthEngine::MAX_SERIALIZED_GRID_SIZE) { uint8_t buf[SynthEngine::MAX_SERIALIZED_GRID_SIZE]; fread(buf, 1, size, f); engine.importGrid(buf, size); printf("Loaded patch from slot %d (%s)\n", slot, filename); } fclose(f); } else { printf("Failed to load patch from slot %d (file not found)\n", slot); } } /** * @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); } void checkSerialInput(FILE* serialPort) { if (!serialPort) return; #if !defined(_WIN32) int fd = fileno(serialPort); fd_set readfds; struct timeval tv; FD_ZERO(&readfds); FD_SET(fd, &readfds); tv.tv_sec = 0; tv.tv_usec = 0; if (select(fd + 1, &readfds, NULL, NULL, &tv) <= 0) return; uint8_t buf[256]; ssize_t n = read(fd, buf, sizeof(buf)); if (n <= 0) return; #else return; #endif static int state = 0; static int headerIdx = 0; static const char* header = "NSGRID"; static uint8_t buffer[SynthEngine::MAX_SERIALIZED_GRID_SIZE]; static int bufferIdx = 0; static uint8_t elementCount = 0; for (ssize_t i = 0; i < n; ++i) { uint8_t b = buf[i]; if (state == 0) { if (b == (uint8_t)header[headerIdx]) { headerIdx++; if (headerIdx == 6) { state = 1; // Expect count bufferIdx = 0; headerIdx = 0; printf("Grid import starting.\n"); } } else { if (headerIdx > 0) { printf("Header mismatch at index %d. Received: %02X\n", headerIdx, b); } headerIdx = 0; if (b == 'N') headerIdx = 1; } } else if (state == 1) { // Count elementCount = b; printf("Grid element count: %d\n", elementCount); if ((size_t)(1 + elementCount * 5 + 1) > sizeof(buffer)) { state = 0; bufferIdx = 0; printf("ERROR: Grid too large (count: %d)\n", elementCount); } else { buffer[bufferIdx++] = b; state = (elementCount == 0) ? 3 : 2; } } else if (state == 2) { // Data buffer[bufferIdx++] = b; if (bufferIdx == 1 + elementCount * 5) { state = 3; } } else if (state == 3) { // End Count buffer[bufferIdx++] = b; printf("Grid import finishing. Total bytes: %d. End count: %d\n", bufferIdx, b); int result = engine.importGrid(buffer, bufferIdx); if (result != 0) { printf("Grid import failed: CRC ERROR %d\n", result); engine.clearGrid(); } else { printf("Grid imported from serial successfully.\n"); } state = 0; bufferIdx = 0; } } } // --- 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 } // --- 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 drawButton(SDL_Renderer* renderer, int x, int y, int w, int h, const char* label, bool pressed) { SDL_Rect rect = {x, y, w, h}; if (pressed) { SDL_SetRenderDrawColor(renderer, 80, 80, 80, 255); } else { SDL_SetRenderDrawColor(renderer, 120, 120, 120, 255); } SDL_RenderFillRect(renderer, &rect); SDL_SetRenderDrawColor(renderer, 200, 200, 200, 255); SDL_RenderDrawRect(renderer, &rect); // Center the text int text_size = 12; int char_width = (int)(text_size * 0.6f) + 4; int text_width = strlen(label) * char_width; int text_x = x + (w - text_width) / 2; int text_y = y + (h - text_size) / 2; SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); drawString(renderer, text_x, text_y, text_size, label); } 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 drawDirectionArrow(SDL_Renderer* renderer, int cx, int cy, int size, int rotation) { int r = size / 2 - 2; int tipX = cx; int tipY = cy; switch(rotation) { case 0: tipY -= r; break; // N case 1: tipX += r; break; // E case 2: tipY += r; break; // S case 3: tipX -= r; break; // W } int arrowSize = 5; int x1, y1, x2, y2, x3, y3; if (rotation == 0) { // N x1 = tipX; y1 = tipY; x2 = tipX - arrowSize; y2 = tipY + arrowSize; x3 = tipX + arrowSize; y3 = tipY + arrowSize; } else if (rotation == 1) { // E x1 = tipX; y1 = tipY; x2 = tipX - arrowSize; y2 = tipY - arrowSize; x3 = tipX - arrowSize; y3 = tipY + arrowSize; } else if (rotation == 2) { // S x1 = tipX; y1 = tipY; x2 = tipX - arrowSize; y2 = tipY - arrowSize; x3 = tipX + arrowSize; y3 = tipY - arrowSize; } else { // W x1 = tipX; y1 = tipY; x2 = tipX + arrowSize; y2 = tipY - arrowSize; x3 = tipX + arrowSize; y3 = tipY + arrowSize; } SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); SDL_RenderDrawLine(renderer, x1, y1, x2, y2); SDL_RenderDrawLine(renderer, x2, y2, x3, y3); SDL_RenderDrawLine(renderer, x3, y3, x1, y1); } 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); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); 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); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); // Param (Freq) char buf[16]; snprintf(buf, 16, "%.0f", 10.0f + (cell.param / 32767.0f)*990.0f); SDL_SetRenderDrawColor(renderer, 0, 255, 255, 255); drawString(renderer, x + 5, y + size - 18, 10, buf); drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 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); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); // Param (Octave) char buf[16]; snprintf(buf, 16, "O%d", 1 + (int)((cell.param / 32767.0f) * 4.99f)); SDL_SetRenderDrawColor(renderer, 255, 200, 0, 255); drawString(renderer, x + 5, y + size - 18, 10, buf); drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 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); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); // Param (Color) const char* colors[] = {"BRN", "PNK", "WHT", "YEL", "GRN"}; int idx = (int)((cell.param / 32767.0f) * 4.99f); SDL_SetRenderDrawColor(renderer, 200, 200, 200, 255); drawString(renderer, x + 5, y + size - 18, 10, colors[idx]); drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 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); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); drawString(renderer, cx - 8, cy - 5, 12, "LFO"); // Param (Freq) char buf[16]; snprintf(buf, 16, "%.1f", 0.1f + (cell.param / 32767.0f) * 19.9f); SDL_SetRenderDrawColor(renderer, 0, 255, 255, 255); drawString(renderer, x + 5, y + size - 18, 10, buf); drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 0, 255, 255); drawTypeLabel(renderer, x, y, 'L'); } else if (cell.type == SynthEngine::GridCell::GATE_INPUT) { SDL_Rect box = {cx - r, cy - r, r*2, r*2}; SDL_RenderDrawRect(renderer, &box); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); if (cell.value > 16384) SDL_RenderFillRect(renderer, &box); drawString(renderer, cx - 8, cy - 5, 12, "G-IN"); drawTypeLabel(renderer, x, y, 'K'); } else if (cell.type == SynthEngine::GridCell::ADSR_ATTACK) { // 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); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 255, 255, 255); drawTypeLabel(renderer, x, y, 'A'); } else if (cell.type == SynthEngine::GridCell::ADSR_DECAY) { // Draw Ramp Down 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); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 255, 255, 255); drawTypeLabel(renderer, x, y, 'D'); } else if (cell.type == SynthEngine::GridCell::ADSR_SUSTAIN) { // Draw Level 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); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 255, 255, 255); drawTypeLabel(renderer, x, y, 'S'); } else if (cell.type == SynthEngine::GridCell::ADSR_RELEASE) { // Draw Ramp Down 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); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 255, 255, 255); drawTypeLabel(renderer, x, y, 'E'); } else if (cell.type == SynthEngine::GridCell::LPF || cell.type == SynthEngine::GridCell::HPF) { // Box 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); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); // Param char buf[16]; snprintf(buf, 16, "%.2f", cell.param / 32767.0f); SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); drawString(renderer, x + 5, y + size - 18, 10, buf); drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 0, 255, 0); drawTypeLabel(renderer, x, y, cell.type == SynthEngine::GridCell::LPF ? 'P' : 'H'); } else if (cell.type == SynthEngine::GridCell::VCA) { // Triangle shape for Amp // 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); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); // Param char buf[16]; snprintf(buf, 16, "%.2f", cell.param / 32767.0f); SDL_SetRenderDrawColor(renderer, 255, 255, 0, 255); drawString(renderer, x + 5, y + size - 18, 10, buf); drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 255, 255, 0); drawTypeLabel(renderer, x, y, 'A'); } else if (cell.type == SynthEngine::GridCell::BITCRUSHER) { SDL_Rect box = {cx - r, cy - r, r*2, r*2}; 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); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); // Param char buf[16]; snprintf(buf, 16, "%.2f", cell.param / 32767.0f); SDL_SetRenderDrawColor(renderer, 255, 0, 255, 255); drawString(renderer, x + 5, y + size - 18, 10, buf); drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 255, 0, 255); drawTypeLabel(renderer, x, y, 'B'); } else if (cell.type == SynthEngine::GridCell::DISTORTION) { SDL_Rect box = {cx - r, cy - r, r*2, r*2}; 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); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); char buf[16]; snprintf(buf, 16, "%.2f", cell.param / 32767.0f); SDL_SetRenderDrawColor(renderer, 255, 100, 100, 255); drawString(renderer, x + 5, y + size - 18, 10, buf); drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 255, 100, 100); drawTypeLabel(renderer, x, y, 'X'); } else if (cell.type == SynthEngine::GridCell::RECTIFIER) { SDL_Rect box = {cx - r, cy - r, r*2, r*2}; 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); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 255, 150, 0); drawTypeLabel(renderer, x, y, '|'); } else if (cell.type == SynthEngine::GridCell::PITCH_SHIFTER) { drawString(renderer, cx - 8, cy - 5, 12, "PIT"); // 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); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 100, 255, 100); drawTypeLabel(renderer, x, y, '^'); } else if (cell.type == SynthEngine::GridCell::GLITCH) { drawString(renderer, cx - 8, cy - 5, 12, "GLT"); // 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); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 255, 0, 0); drawTypeLabel(renderer, x, y, 'G'); } else if (cell.type == SynthEngine::GridCell::FORK) { // Draw Y shape based on rotation // 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); drawDirectionArrow(renderer, cx, cy, size, lDir); drawDirectionArrow(renderer, cx, cy, size, rDir); // Param (Balance) char buf[16]; snprintf(buf, 16, "%.1f", cell.param / 32767.0f); SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); drawString(renderer, x + 5, y + size - 18, 10, buf); drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 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); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); // Param (Delay time in ms) char buf[16]; float delay_ms = (cell.param / 32767.0f) * 2000.0f; // Max 2 seconds snprintf(buf, 16, "%.0fms", delay_ms); SDL_SetRenderDrawColor(renderer, 255, 128, 0, 255); drawString(renderer, x + 5, y + size - 18, 10, buf); drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 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); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); // Param (Strength) char buf[16]; snprintf(buf, 16, "%.2f", cell.param / 32767.0f); SDL_SetRenderDrawColor(renderer, 200, 100, 255, 255); drawString(renderer, x + 5, y + size - 18, 10, buf); drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 200, 100, 255); drawTypeLabel(renderer, x, y, 'R'); } else if (cell.type == SynthEngine::GridCell::OPERATOR) { SDL_Rect opRect = {cx - r, cy - r, r*2, r*2}; 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); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); // Draw Op Symbol char opChar = '?'; int opType = (int)((cell.param / 32767.0f) * 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 / 32767.0f, 255, 255, 255); drawTypeLabel(renderer, x, y, 'M'); } else if (cell.type == SynthEngine::GridCell::WAVETABLE) { drawString(renderer, cx - 5, cy - 5, 12, "W"); // 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); drawDirectionArrow(renderer, cx, cy, size, cell.rotation); // Param (Wave index) int idx = (int)((cell.param / 32767.0f) * 7.99f); char buf[4]; snprintf(buf, 4, "%d", idx); SDL_SetRenderDrawColor(renderer, 128, 128, 255, 255); drawString(renderer, x + 5, y + size - 18, 10, buf); drawParamBar(renderer, x, y, size, cell.param / 32767.0f, 128, 128, 255); drawTypeLabel(renderer, x, y, 'W'); } } void randomizeGrid() { printf("Randomizing grid...\n"); Uint32 startTime = SDL_GetTicks(); int attempts = 0; bool validGrid = false; { SynthLockGuard lock(engine.gridMutex); // Number of types to choose from (excluding SINK) const int numTypes = (int)SynthEngine::GridCell::SINK; // 1. Clear existing buffers first (resets the pool) for (int x = 0; x < SynthEngine::GRID_W; ++x) { for (int y = 0; y < SynthEngine::GRID_H; ++y) { SynthEngine::GridCell& c = engine.grid[x][y]; if (c.type == SynthEngine::GridCell::SINK) continue; c.type = SynthEngine::GridCell::EMPTY; } } bool visited[SynthEngine::GRID_W][SynthEngine::GRID_H]; while (!validGrid && attempts < 1000) { if (SDL_GetTicks() - startTime > 200) { // Safeguard: Timeout after 200ms printf("Randomization timed out.\n"); break; } attempts++; // 2. Randomize (without allocation) for (int x = 0; x < SynthEngine::GRID_W; ++x) { for (int y = 0; y < SynthEngine::GRID_H; ++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 = rand() % 32768; c.value = 0; c.phase = 0; } } // 3. Check Connectivity // BFS from SINK backwards memset(visited, 0, sizeof(visited)); std::vector> q; q.push_back({SynthEngine::GRID_W / 2, SynthEngine::GRID_H - 1}); visited[SynthEngine::GRID_W / 2][SynthEngine::GRID_H - 1] = 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 < SynthEngine::GRID_W && ty >= 0 && ty < SynthEngine::GRID_H && !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 requirements are met bool hasSoundSource = false; bool hasGate = false; for (int x = 0; x < SynthEngine::GRID_W; ++x) { for (int y = 0; y < SynthEngine::GRID_H; ++y) { if (visited[x][y]) { if (engine.grid[x][y].type == SynthEngine::GridCell::INPUT_OSCILLATOR || engine.grid[x][y].type == SynthEngine::GridCell::WAVETABLE) { hasSoundSource = true; } if (engine.grid[x][y].type == SynthEngine::GridCell::GATE_INPUT) { hasGate = true; } } } } if (hasSoundSource && hasGate) { // 4. Prune unreachable elements for (int x = 0; x < SynthEngine::GRID_W; ++x) { for (int y = 0; y < SynthEngine::GRID_H; ++y) { if (!visited[x][y]) { engine.grid[x][y].type = SynthEngine::GridCell::EMPTY; engine.grid[x][y].param = 16384; engine.grid[x][y].rotation = 0; } } } // 5. Update processing order for simulation engine.updateGraph(); // 6. Run Simulation engine.setGate(true); float oldFreq = engine.getFrequency(); engine.setFrequency(440.0f); bool soundDetected = false; for(int i=0; i<1000; ++i) { int32_t val = engine.processGridStep(); if (abs(val) > 10) { soundDetected = true; break; } } engine.setGate(false); engine.setFrequency(oldFreq); if (soundDetected) { validGrid = true; // Reset values to avoid initial pop for (int x = 0; x < SynthEngine::GRID_W; ++x) { for (int y = 0; y < SynthEngine::GRID_H; ++y) { engine.grid[x][y].value = 0; } } } else { // Failed simulation, cleanup buffers } } } // If failed after all attempts, ensure grid is clean if (!validGrid) { for (int x = 0; x < SynthEngine::GRID_W; ++x) { for (int y = 0; y < SynthEngine::GRID_H; ++y) { SynthEngine::GridCell& c = engine.grid[x][y]; if (c.type != SynthEngine::GridCell::SINK) { c.type = SynthEngine::GridCell::EMPTY; c.param = 16384; c.rotation = 0; } } } } } engine.rebuildProcessingOrder(); printf("Randomized in %d attempts (%d ms). Valid: %s\n", attempts, SDL_GetTicks() - startTime, validGrid ? "YES" : "NO"); } int main(int argc, char* argv[]) { (void)argc; (void)argv; FILE* serialPort = nullptr; 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); if (argc > 1) { serialPort = fopen(argv[1], "r+b"); if (serialPort) { printf("Opened serial port: %s\n", argv[1]); #if !defined(_WIN32) int fd = fileno(serialPort); struct termios tty; if (tcgetattr(fd, &tty) != 0) { printf("Error from tcgetattr\n"); } else { cfsetospeed(&tty, B115200); cfsetispeed(&tty, B115200); tty.c_cflag &= ~PARENB; // Clear parity bit, disabling parity (most common) tty.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication (most common) tty.c_cflag &= ~CSIZE; // Clear all bits that set the data size tty.c_cflag |= CS8; // 8 bits per byte (most common) tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control (most common) tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1) tty.c_lflag &= ~ICANON; // Disable canonical mode tty.c_lflag &= ~ECHO; // Disable echo tty.c_lflag &= ~ECHOE; // Disable erasure tty.c_lflag &= ~ECHONL; // Disable new-line echo tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); // Disable any special handling of received bytes tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g. newline chars) tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed tty.c_cc[VTIME] = 0; // No blocking with timeout tty.c_cc[VMIN] = 0; // Non-blocking read if (tcsetattr(fd, TCSANOW, &tty) != 0) { printf("Error from tcsetattr\n"); } } #endif } else printf("Failed to open serial port: %s\n", argv[1]); } // --- 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_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::GLITCH, SynthEngine::GridCell::OPERATOR }; const int numCellTypes = sizeof(cellTypes) / sizeof(cellTypes[0]); bool quit = false; SDL_Event e; bool exportButtonPressed = false; bool importButtonPressed = false; bool saveButtonPressed = false; bool loadButtonPressed = false; bool randomizeButtonPressed = false; bool clearButtonPressed = false; bool nextPresetButtonPressed = false; while (!quit) { checkSerialInput(serialPort); // --- 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 / CELL_SIZE; int gy = my / CELL_SIZE; if (gx >= 0 && gx < SynthEngine::GRID_W && gy >= 0 && gy < SynthEngine::GRID_H) { bool grid_modified = false; { SynthLockGuard lock(engine.gridMutex); SynthEngine::GridCell& c = engine.grid[gx][gy]; if (c.type != SynthEngine::GridCell::SINK) { grid_modified = true; 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 = 16384; c.rotation = 0; } if (newType != oldType) { c.type = newType; } } } if (grid_modified) { engine.rebuildProcessingOrder(); } } } 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 } } // Check Export Button Click SDL_Rect exportButtonRect = {300, 435, 100, 30}; if (synthX >= exportButtonRect.x && synthX <= exportButtonRect.x + exportButtonRect.w && my >= exportButtonRect.y && my <= exportButtonRect.y + exportButtonRect.h) { exportButtonPressed = true; } SDL_Rect importButtonRect = {410, 435, 100, 30}; if (synthX >= importButtonRect.x && synthX <= importButtonRect.x + importButtonRect.w && my >= importButtonRect.y && my <= importButtonRect.y + importButtonRect.h) { importButtonPressed = true; } SDL_Rect saveButtonRect = {270, 535, 80, 30}; if (synthX >= saveButtonRect.x && synthX <= saveButtonRect.x + saveButtonRect.w && my >= saveButtonRect.y && my <= saveButtonRect.y + saveButtonRect.h) { saveButtonPressed = true; } SDL_Rect loadButtonRect = {450, 535, 80, 30}; if (synthX >= loadButtonRect.x && synthX <= loadButtonRect.x + loadButtonRect.w && my >= loadButtonRect.y && my <= loadButtonRect.y + loadButtonRect.h) { loadButtonPressed = true; } SDL_Rect randomizeButtonRect = {250, 380, 100, 30}; if (synthX >= randomizeButtonRect.x && synthX <= randomizeButtonRect.x + randomizeButtonRect.w && my >= randomizeButtonRect.y && my <= randomizeButtonRect.y + randomizeButtonRect.h) { randomizeButtonPressed = true; } SDL_Rect clearButtonRect = {360, 380, 80, 30}; if (synthX >= clearButtonRect.x && synthX <= clearButtonRect.x + clearButtonRect.w && my >= clearButtonRect.y && my <= clearButtonRect.y + clearButtonRect.h) { clearButtonPressed = true; } SDL_Rect nextPresetButtonRect = {450, 380, 80, 30}; if (synthX >= nextPresetButtonRect.x && synthX <= nextPresetButtonRect.x + nextPresetButtonRect.w && my >= nextPresetButtonRect.y && my <= nextPresetButtonRect.y + nextPresetButtonRect.h) { nextPresetButtonPressed = true; } } } 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 int32_t step = fineTune ? 327 : 1638; // ~0.01 and ~0.05 int gx = mx / CELL_SIZE; int gy = my / CELL_SIZE; if (gx >= 0 && gx < SynthEngine::GRID_W && gy >= 0 && gy < SynthEngine::GRID_H) { SynthLockGuard 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 > 32767) c.param = 32767; if (c.param < 0) c.param = 0; } } else { // Synth Scroll int synthX = mx - GRID_PANEL_WIDTH; if (my < 500) { 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 { // Patch Slot Knob if (e.wheel.y > 0) current_patch_slot++; else if (e.wheel.y < 0) current_patch_slot--; if (current_patch_slot < 0) current_patch_slot = 0; if (current_patch_slot > 7) current_patch_slot = 7; } } } 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) { engine.clearGrid(); } else if (e.key.keysym.scancode == SDL_SCANCODE_PAGEUP) { current_preset = (current_preset + 1) % 6; // Increased number of presets engine.loadPreset(current_preset); } else if (e.key.keysym.scancode == SDL_SCANCODE_PAGEDOWN) { current_preset = (current_preset - 1 + 6) % 6; // Increased number of presets engine.loadPreset(current_preset); } 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); } } // Copy & Paste if (e.key.keysym.scancode == SDL_SCANCODE_C) { int mx, my; SDL_GetMouseState(&mx, &my); if (mx < GRID_PANEL_WIDTH) { int gx = mx / CELL_SIZE; int gy = my / CELL_SIZE; if (gx >= 0 && gx < SynthEngine::GRID_W && gy >= 0 && gy < SynthEngine::GRID_H) { SynthLockGuard lock(engine.gridMutex); clipboardCell = engine.grid[gx][gy]; } } } else if (e.key.keysym.scancode == SDL_SCANCODE_V) { int mx, my; SDL_GetMouseState(&mx, &my); if (mx < GRID_PANEL_WIDTH) { int gx = mx / CELL_SIZE; int gy = my / CELL_SIZE; if (gx >= 0 && gx < SynthEngine::GRID_W && gy >= 0 && gy < SynthEngine::GRID_H) { { SynthLockGuard lock(engine.gridMutex); engine.grid[gx][gy] = clipboardCell; // Reset runtime state engine.grid[gx][gy].value = 0; engine.grid[gx][gy].next_value = 0; engine.grid[gx][gy].phase = 0; engine.grid[gx][gy].phase_accumulator = 0; } engine.rebuildProcessingOrder(); } } } } } else if (e.type == SDL_MOUSEBUTTONUP) { if (exportButtonPressed) { int mx = e.button.x; int my = e.button.y; int synthX = mx - GRID_PANEL_WIDTH; SDL_Rect exportButtonRect = {300, 435, 100, 30}; if (mx >= GRID_PANEL_WIDTH && synthX >= exportButtonRect.x && synthX <= exportButtonRect.x + exportButtonRect.w && my >= exportButtonRect.y && my <= exportButtonRect.y + exportButtonRect.h) { if (serialPort) { uint8_t buf[SynthEngine::MAX_SERIALIZED_GRID_SIZE]; size_t size = engine.exportGrid(buf); fwrite("NSGRID", 1, 6, serialPort); fwrite(buf, 1, size, serialPort); fflush(serialPort); printf("Grid exported to serial. Waiting for response...\n"); char response[256]; if (fgets(response, sizeof(response), serialPort)) { printf("Device response: %s", response); } else { printf("No response from device.\n"); } } else { printf("Serial port not open. Pass device path as argument (e.g. ./NoiceSynth /dev/ttyACM0)\n"); } } exportButtonPressed = false; } if (importButtonPressed) { int mx = e.button.x; int my = e.button.y; int synthX = mx - GRID_PANEL_WIDTH; SDL_Rect importButtonRect = {410, 435, 100, 30}; if (mx >= GRID_PANEL_WIDTH && synthX >= importButtonRect.x && synthX <= importButtonRect.x + importButtonRect.w && my >= importButtonRect.y && my <= importButtonRect.y + importButtonRect.h) { if (serialPort) { fwrite("NSLOAD", 1, 6, serialPort); fflush(serialPort); printf("Requested grid import from device...\n"); } else { printf("Serial port not open.\n"); } } importButtonPressed = false; } if (saveButtonPressed) { int mx = e.button.x; int my = e.button.y; int synthX = mx - GRID_PANEL_WIDTH; SDL_Rect saveButtonRect = {270, 535, 80, 30}; if (mx >= GRID_PANEL_WIDTH && synthX >= saveButtonRect.x && synthX <= saveButtonRect.x + saveButtonRect.w && my >= saveButtonRect.y && my <= saveButtonRect.y + saveButtonRect.h) { savePatch(current_patch_slot); } saveButtonPressed = false; } if (loadButtonPressed) { int mx = e.button.x; int my = e.button.y; int synthX = mx - GRID_PANEL_WIDTH; SDL_Rect loadButtonRect = {450, 535, 80, 30}; if (mx >= GRID_PANEL_WIDTH && synthX >= loadButtonRect.x && synthX <= loadButtonRect.x + loadButtonRect.w && my >= loadButtonRect.y && my <= loadButtonRect.y + loadButtonRect.h) { loadPatch(current_patch_slot); } loadButtonPressed = false; } if (randomizeButtonPressed) { int mx = e.button.x; int my = e.button.y; int synthX = mx - GRID_PANEL_WIDTH; SDL_Rect randomizeButtonRect = {250, 380, 100, 30}; if (mx >= GRID_PANEL_WIDTH && synthX >= randomizeButtonRect.x && synthX <= randomizeButtonRect.x + randomizeButtonRect.w && my >= randomizeButtonRect.y && my <= randomizeButtonRect.y + randomizeButtonRect.h) { randomizeGrid(); } randomizeButtonPressed = false; } if (clearButtonPressed) { int mx = e.button.x; int my = e.button.y; int synthX = mx - GRID_PANEL_WIDTH; SDL_Rect clearButtonRect = {360, 380, 80, 30}; if (mx >= GRID_PANEL_WIDTH && synthX >= clearButtonRect.x && synthX <= clearButtonRect.x + clearButtonRect.w && my >= clearButtonRect.y && my <= clearButtonRect.y + clearButtonRect.h) { engine.clearGrid(); } clearButtonPressed = false; } if (nextPresetButtonPressed) { int mx = e.button.x; int my = e.button.y; int synthX = mx - GRID_PANEL_WIDTH; SDL_Rect nextPresetButtonRect = {450, 380, 80, 30}; if (mx >= GRID_PANEL_WIDTH && synthX >= nextPresetButtonRect.x && synthX <= nextPresetButtonRect.x + nextPresetButtonRect.w && my >= nextPresetButtonRect.y && my <= nextPresetButtonRect.y + nextPresetButtonRect.h) { current_preset = (current_preset + 1) % 6; engine.loadPreset(current_preset); } nextPresetButtonPressed = false; } } 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 | Preset: %d | Slot: %d", knob_vol_val * 100.0f, current_octave, auto_melody_enabled ? "ON" : "OFF", current_preset, current_patch_slot); 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); drawButton(renderer, 300, 435, 100, 30, "EXPORT", exportButtonPressed); drawButton(renderer, 410, 435, 100, 30, "IMPORT", importButtonPressed); drawButton(renderer, 250, 380, 100, 30, "RANDOM", randomizeButtonPressed); drawButton(renderer, 360, 380, 80, 30, "CLEAR", clearButtonPressed); drawButton(renderer, 450, 380, 80, 30, "PRESET+", nextPresetButtonPressed); // Patch Slot Control float normalized_slot = (float)current_patch_slot / 7.0f; drawKnob(renderer, 400, 550, 40, normalized_slot); char slotBuf[16]; snprintf(slotBuf, sizeof(slotBuf), "SLOT %d", current_patch_slot); drawString(renderer, 380, 600, 12, slotBuf); // Buffer Preview drawString(renderer, 50, 560, 12, "BUFFER"); drawGridCell(renderer, 50, 580, CELL_SIZE, clipboardCell); drawString(renderer, 50, 580 + CELL_SIZE + 10, 12, "C-COPY"); drawString(renderer, 50, 580 + CELL_SIZE + 30, 12, "V-PASTE"); drawButton(renderer, 270, 535, 80, 30, "SAVE", saveButtonPressed); drawButton(renderer, 450, 535, 80, 30, "LOAD", loadButtonPressed); // --- 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 SynthLockGuard lock(engine.gridMutex); for(int x=0; x < SynthEngine::GRID_W; ++x) { for(int y=0; y < SynthEngine::GRID_H; ++y) { drawGridCell(renderer, x*CELL_SIZE, y*CELL_SIZE, CELL_SIZE, engine.grid[x][y]); } } } SDL_RenderPresent(renderer); } if (serialPort) fclose(serialPort); ma_device_uninit(&device); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); return 0; }