From ad0fb039fc8d0eaad0fa4aa28ec1b1310fedc9d1 Mon Sep 17 00:00:00 2001 From: Dejvino Date: Sun, 1 Mar 2026 10:42:04 +0100 Subject: [PATCH] Optimizations 1 --- AudioThread.cpp | 60 ++++++++++++----- UIThread.cpp | 120 +++++++++++++++++++++++---------- simulator/main.cpp | 163 +++++++++++++++++++++++++++++++-------------- synth_engine.cpp | 122 ++------------------------------- synth_engine.h | 3 - 5 files changed, 246 insertions(+), 222 deletions(-) diff --git a/AudioThread.cpp b/AudioThread.cpp index e5606f0..259b760 100644 --- a/AudioThread.cpp +++ b/AudioThread.cpp @@ -12,8 +12,8 @@ const int I2S_LRC_PIN = 10; // Left-Right Clock (GP10) const int I2S_DOUT_PIN = 11; // Data Out (GP11) // Audio parameters -const int SAMPLE_RATE = 44100; -const int16_t AMPLITUDE = 16383; // Use a lower amplitude to avoid clipping (max is 32767 for 16-bit) +const int SAMPLE_RATE = 44100 / 2; +const int16_t AMPLITUDE = 16383 / 2; // Use a lower amplitude to avoid clipping (max is 32767 for 16-bit) // Create an I2S output object I2S i2s(OUTPUT); @@ -43,6 +43,7 @@ void setupAudio() { // Initialize the portable synth engine globalSynth = new SynthEngine(SAMPLE_RATE); + globalSynth->loadPreset(2); } void loopAudio() { @@ -51,27 +52,56 @@ void loopAudio() { // Every 500ms, pick a new random note to play if (now - lastNoteChangeTime > 500) { lastNoteChangeTime = now; - int noteIndex = random(0, SCALES[currentScaleIndex].numNotes); + int noteIndex = random(0, SCALES[currentScaleIndex].numNotes + 2); - // Calculate frequency based on key, scale, and octave - const float baseFrequency = 261.63f; // C4 - float keyFrequency = baseFrequency * pow(2.0f, currentKeyIndex / 12.0f); - int semitoneOffset = SCALES[currentScaleIndex].semitones[noteIndex]; - currentFrequency = keyFrequency * pow(2.0f, semitoneOffset / 12.0f); + bool rest = noteIndex >= SCALES[currentScaleIndex].numNotes; + if (!rest) { + // Calculate frequency based on key, scale, and octave + const float baseFrequency = 261.63f; // C4 + float keyFrequency = baseFrequency * pow(2.0f, currentKeyIndex / 12.0f); + int semitoneOffset = SCALES[currentScaleIndex].semitones[noteIndex]; + currentFrequency = keyFrequency * pow(2.0f, semitoneOffset / 12.0f); + } else { + currentFrequency = 0; + } if (globalSynth) { - globalSynth->setFrequency(currentFrequency); - globalSynth->setGate(true); // Trigger envelope + globalSynth->setFrequency(currentFrequency > 0 ? currentFrequency : 440.0f); + globalSynth->setGate(!rest); // Trigger envelope } - Serial.println("Playing note: " + String(currentFrequency) + " Hz"); + } + + const int BATCH_SIZE = 16; + + // Ensure we don't generate samples faster than the I2S can consume them. + // We write 2 samples (Left + Right) for every 1 synth sample. + if (i2s.availableForWrite() < BATCH_SIZE * 2) { + return; } // Process a small batch of samples - int16_t samples[32]; - if (globalSynth) globalSynth->process(samples, 32); - else memset(samples, 0, sizeof(samples)); + static int16_t samples[BATCH_SIZE]; - for (int i = 0; i < 32; ++i) { + // Generate sound samples + if (globalSynth) { + // using synth engine + globalSynth->process(samples, BATCH_SIZE); + } else { + // using fallback sawtooth + for (int i = 0; i < BATCH_SIZE; ++i) { + if (currentFrequency > 0) { + //samples[i] = (int16_t)(sin(phase) * AMPLITUDE); + phase += 2.0 * M_PI * currentFrequency / SAMPLE_RATE; + if (phase >= 2.0 * M_PI) phase -= 2.0 * M_PI; + samples[i] = phase * 0.1f * AMPLITUDE; + } else { + samples[i] = 0; + } + } + } + + // write out stereo samples + for (int i = 0; i < BATCH_SIZE; ++i) { i2s.write(samples[i]); i2s.write(samples[i]); } diff --git a/UIThread.cpp b/UIThread.cpp index c29a77e..e0c192b 100644 --- a/UIThread.cpp +++ b/UIThread.cpp @@ -181,48 +181,70 @@ void handleInput() { void drawUI() { display.clearDisplay(); - display.setTextSize(1); - display.setTextColor(SSD1306_WHITE); - display.setCursor(0, 0); - if (currentState == UI_MENU) { - for (int i = 0; i < NUM_MENU_ITEMS; i++) { - if (i == menuSelection) { - display.fillRect(0, i * 10, SCREEN_WIDTH, 10, SSD1306_WHITE); - display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); - } else { - display.setTextColor(SSD1306_WHITE); - } - display.setCursor(2, i * 10 + 1); - display.print(MENU_ITEMS[i].label); - display.print(": "); + if (globalSynth) { + // Copy grid state to local buffer to minimize lock time + struct MiniCell { + uint8_t type; + uint8_t rotation; + float value; + }; + MiniCell gridCopy[SynthEngine::GRID_W][SynthEngine::GRID_H]; - // Display current value - switch (MENU_ITEMS[i].editState) { - case UI_EDIT_SCALE_TYPE: display.print(SCALES[currentScaleIndex].name); break; - case UI_EDIT_SCALE_KEY: display.print(KEY_NAMES[currentKeyIndex]); break; - case UI_EDIT_WAVETABLE: display.print(WAVETABLE_NAMES[currentWavetableIndex]); break; - default: break; + { + SynthLockGuard lock(globalSynth->gridMutex); + for(int x=0; xgrid[x][y].type; + gridCopy[x][y].rotation = (uint8_t)globalSynth->grid[x][y].rotation; + gridCopy[x][y].value = globalSynth->grid[x][y].value; + } } } - } else { - // In an edit screen - const char* title = MENU_ITEMS[menuSelection].label; - const char* value = ""; - switch (currentState) { - case UI_EDIT_SCALE_TYPE: value = SCALES[currentScaleIndex].name; break; - case UI_EDIT_SCALE_KEY: value = KEY_NAMES[currentKeyIndex]; break; - case UI_EDIT_WAVETABLE: value = WAVETABLE_NAMES[currentWavetableIndex]; break; - default: break; + + int cellW = 10; + int cellH = 5; + int marginX = (SCREEN_WIDTH - (SynthEngine::GRID_W * cellW)) / 2; + int marginY = (SCREEN_HEIGHT - (SynthEngine::GRID_H * cellH)) / 2; + + for(int x=0; x= SynthEngine::GridCell::FIXED_OSCILLATOR && type <= SynthEngine::GridCell::GATE_INPUT) { + // Sources: Filled rect + display.fillRect(cx - 1, cy - 1, 3, 3, SSD1306_WHITE); + } else if (type != SynthEngine::GridCell::WIRE) { + // Processors: Hollow rect + display.drawRect(cx - 1, cy - 1, 3, 3, SSD1306_WHITE); + } + } + } } - display.println(title); - display.drawLine(0, 10, SCREEN_WIDTH, 10, SSD1306_WHITE); - display.setCursor(10, 25); - display.setTextSize(2); - display.print(value); - display.setTextSize(1); - display.setCursor(0, 50); - display.println(F("(Press to confirm)")); } display.display(); @@ -232,6 +254,8 @@ void checkSerial() { static int state = 0; // 0: Header, 1: Data static int headerIdx = 0; static const char* header = "NSGRID"; + static int loadHeaderIdx = 0; + static const char* loadHeader = "NSLOAD"; static uint8_t buffer[SynthEngine::SERIALIZED_GRID_SIZE]; static int bufferIdx = 0; @@ -244,17 +268,39 @@ void checkSerial() { state = 1; bufferIdx = 0; headerIdx = 0; + loadHeaderIdx = 0; } } else { headerIdx = 0; if (b == 'N') headerIdx = 1; } + + if (state == 0) { + if (b == loadHeader[loadHeaderIdx]) { + loadHeaderIdx++; + if (loadHeaderIdx == 6) { + if (globalSynth) { + uint8_t buf[SynthEngine::SERIALIZED_GRID_SIZE]; + globalSynth->exportGrid(buf); + Serial.write("NSGRID", 6); + Serial.write(buf, sizeof(buf)); + Serial.flush(); + } + loadHeaderIdx = 0; + headerIdx = 0; + } + } else { + loadHeaderIdx = 0; + if (b == 'N') loadHeaderIdx = 1; + } + } } else if (state == 1) { buffer[bufferIdx++] = b; if (bufferIdx == SynthEngine::SERIALIZED_GRID_SIZE) { if (globalSynth) { globalSynth->importGrid(buffer); saveGridToEEPROM(); + Serial.println(F("OK: Grid Received")); } state = 0; bufferIdx = 0; diff --git a/simulator/main.cpp b/simulator/main.cpp index 30db709..357ca58 100644 --- a/simulator/main.cpp +++ b/simulator/main.cpp @@ -12,6 +12,11 @@ #include +#if !defined(_WIN32) +#include +#include +#endif + // --- Configuration --- const uint32_t SAMPLE_RATE = 44100; const uint32_t CHANNELS = 1; // Mono @@ -73,6 +78,61 @@ void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uin 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 + + printf("Grid import maybe?\n"); + + static int state = 0; + static int headerIdx = 0; + static const char* header = "NSGRID"; + static uint8_t buffer[SynthEngine::SERIALIZED_GRID_SIZE]; + static int bufferIdx = 0; + + for (ssize_t i = 0; i < n; ++i) { + uint8_t b = buf[i]; + if (state == 0) { + if (b == header[headerIdx]) { + headerIdx++; + if (headerIdx == 6) { + state = 1; + bufferIdx = 0; + headerIdx = 0; + printf("Grid import starting.\n"); + } + } else { + headerIdx = 0; + if (b == 'N') headerIdx = 1; + } + } else if (state == 1) { + buffer[bufferIdx++] = b; + if (bufferIdx == SynthEngine::SERIALIZED_GRID_SIZE) { + engine.importGrid(buffer); + printf("Grid imported from serial.\n"); + state = 0; + bufferIdx = 0; + } + } + } +} + // --- UI Drawing Helpers --- void DrawCircle(SDL_Renderer * renderer, int32_t centreX, int32_t centreY, int32_t radius) { @@ -706,23 +766,24 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G } void randomizeGrid() { + printf("Randomizing grid...\n"); + Uint32 startTime = SDL_GetTicks(); SynthLockGuard lock(engine.gridMutex); // Number of types to choose from (excluding SINK) const int numTypes = (int)SynthEngine::GridCell::SINK; - // 1. Clear existing buffers first + // 1. Clear existing buffers first (resets the pool) + // engine.clearGrid(); // Avoid deadlock by clearing manually 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.buffer) { - delete[] c.buffer; - c.buffer = nullptr; - c.buffer_size = 0; - } - if (c.type != SynthEngine::GridCell::SINK) { - c.type = SynthEngine::GridCell::EMPTY; - } + if (c.type == SynthEngine::GridCell::SINK) continue; + c.type = SynthEngine::GridCell::EMPTY; + c.param = 0.5f; + c.rotation = 0; + c.value = 0.0f; + c.phase = 0.0f; } } @@ -731,6 +792,10 @@ void randomizeGrid() { 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) @@ -837,18 +902,6 @@ void randomizeGrid() { } } - // 5. Allocate buffers for DELAYs and REVERBs - 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::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; - } - } - } - // 6. Run Simulation engine.setGate(true); float oldFreq = engine.getFrequency(); @@ -874,16 +927,6 @@ void randomizeGrid() { } } else { // Failed simulation, cleanup buffers - 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.buffer) { - delete[] c.buffer; - c.buffer = nullptr; - c.buffer_size = 0; - } - } - } } } } @@ -902,7 +945,7 @@ void randomizeGrid() { } } - printf("Randomized in %d attempts. Valid: %s\n", attempts, validGrid ? "YES" : "NO"); + printf("Randomized in %d attempts (%d ms). Valid: %s\n", attempts, SDL_GetTicks() - startTime, validGrid ? "YES" : "NO"); } int main(int argc, char* argv[]) { @@ -943,7 +986,7 @@ int main(int argc, char* argv[]) { ma_device_start(&device); if (argc > 1) { - serialPort = fopen(argv[1], "wb"); + serialPort = fopen(argv[1], "r+b"); if (serialPort) printf("Opened serial port: %s\n", argv[1]); else printf("Failed to open serial port: %s\n", argv[1]); } @@ -992,19 +1035,18 @@ int main(int argc, char* argv[]) { 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 + SynthEngine::GridCell::OPERATOR }; const int numCellTypes = sizeof(cellTypes) / sizeof(cellTypes[0]); bool quit = false; SDL_Event e; bool exportButtonPressed = false; + bool importButtonPressed = 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 @@ -1060,19 +1102,7 @@ int main(int argc, char* argv[]) { } if (newType != oldType) { - // If old type was DELAY, free its buffer - if ((oldType == SynthEngine::GridCell::DELAY || oldType == SynthEngine::GridCell::REVERB || 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 - } } } } @@ -1101,6 +1131,12 @@ int main(int argc, char* argv[]) { 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; + } } } else if (e.type == SDL_MOUSEWHEEL) { SDL_Keymod modState = SDL_GetModState(); @@ -1192,13 +1228,39 @@ int main(int argc, char* argv[]) { fwrite("NSGRID", 1, 6, serialPort); fwrite(buf, 1, sizeof(buf), serialPort); fflush(serialPort); - printf("Grid exported to serial.\n"); + 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; + } } else if (e.type == SDL_KEYUP) { if (!auto_melody_enabled && e.key.keysym.scancode == current_key_scancode) { engine.setGate(false); @@ -1278,6 +1340,7 @@ int main(int argc, char* argv[]) { drawToggle(renderer, 580, 450, 30, auto_melody_enabled); drawButton(renderer, 300, 435, 100, 30, "EXPORT", exportButtonPressed); + drawButton(renderer, 410, 435, 100, 30, "IMPORT", importButtonPressed); // --- Draw Grid Panel (Left) --- SDL_Rect gridViewport = {0, 0, GRID_PANEL_WIDTH, WINDOW_HEIGHT}; diff --git a/synth_engine.cpp b/synth_engine.cpp index 6f71f5e..4d3cc75 100644 --- a/synth_engine.cpp +++ b/synth_engine.cpp @@ -1,5 +1,6 @@ #include "synth_engine.h" #include +#include // A simple sine lookup table for the sine oscillator const int SINE_TABLE_SIZE = 256; @@ -37,14 +38,6 @@ SynthEngine::SynthEngine(uint32_t sampleRate) } SynthEngine::~SynthEngine() { - for (int x = 0; x < GRID_W; ++x) { - for (int y = 0; y < GRID_H; ++y) { - if (grid[x][y].buffer) { - delete[] grid[x][y].buffer; - grid[x][y].buffer = nullptr; - } - } - } } void SynthEngine::exportGrid(uint8_t* buffer) { @@ -62,6 +55,7 @@ void SynthEngine::exportGrid(uint8_t* buffer) { void SynthEngine::importGrid(const uint8_t* buffer) { SynthLockGuard lock(gridMutex); + size_t idx = 0; for(int y=0; y bool { if (from_x < 0 || from_x >= GRID_W || from_y < 0 || from_y >= GRID_H) return false; GridCell& n = grid[from_x][from_y]; @@ -545,50 +526,6 @@ float SynthEngine::processGridStep() { // Mix between original and rectified based on param float rect = fabsf(in); val = in * (1.0f - c.param) + rect * c.param; - } else if (c.type == GridCell::PITCH_SHIFTER) { - float in = getInputFromTheBack(x, y, c); - if (c.buffer && c.buffer_size > 0) { - c.buffer[c.write_idx] = in; - - // Granular pitch shift - // Pitch ratio: 0.5 to 2.0 - float pitchRatio = 0.5f + c.param * 1.5f; - // Delay rate change: 1.0 - pitchRatio - // If pitch=1, rate=0 (delay constant). If pitch=2, rate=-1 (delay decreases). - float rate = 1.0f - pitchRatio; - - c.phase += rate; - // Wrap phase within window (e.g. 4096 samples) - float windowSize = 4096.0f; - if (c.phase >= windowSize) c.phase -= windowSize; - if (c.phase < 0.0f) c.phase += windowSize; - - // Read from buffer - // Simple crossfade windowing would be better, but for now just a single tap with moving delay - // To reduce clicks, we really need 2 taps. Let's stick to single tap for simplicity in this grid context, or maybe just a vibrato if rate is LFO? - // Actually, let's implement the 2-tap crossfade for quality. - // Tap 1 - float p1 = c.phase; - float p2 = c.phase + windowSize * 0.5f; - if (p2 >= windowSize) p2 -= windowSize; - - // Window function (Triangle) - auto getWindow = [&](float p) -> float { - return 1.0f - fabsf(2.0f * (p / windowSize) - 1.0f); - }; - - // Read indices - int r1 = (int)c.write_idx - (int)p1; - if (r1 < 0) r1 += c.buffer_size; - int r2 = (int)c.write_idx - (int)p2; - if (r2 < 0) r2 += c.buffer_size; - - val = c.buffer[r1] * getWindow(p1) + c.buffer[r2] * getWindow(p2); - - c.write_idx = (c.write_idx + 1) % c.buffer_size; - } else { - val = 0.0f; - } } else if (c.type == GridCell::GLITCH) { float in = getInputFromTheBack(x, y, c); // Param controls probability of glitch @@ -601,55 +538,6 @@ float SynthEngine::processGridStep() { } else { val = in; } - } else if (c.type == GridCell::DELAY) { - // Input is from the "Back" (rot+2) - float input_val = getInputFromTheBack(x, y, c); - - if (c.buffer && c.buffer_size > 0) { - // Write current input to buffer - c.buffer[c.write_idx] = input_val; - - // Calculate read index based on parameter. Max delay is buffer_size. - uint32_t delay_samples = c.param * (c.buffer_size - 1); - - // Using modulo for wraparound. Need to handle negative result from subtraction. - int read_idx = (int)c.write_idx - (int)delay_samples; - if (read_idx < 0) { - read_idx += c.buffer_size; - } - - // Read delayed value for output - val = c.buffer[read_idx]; - - // Increment write index - c.write_idx = (c.write_idx + 1) % c.buffer_size; - } else { - val = 0.0f; // No buffer, no output - } - } else if (c.type == GridCell::REVERB) { - // Input is from the "Back" (rot+2) - float input_val = getInputFromTheBack(x, y, c); - - if (c.buffer && c.buffer_size > 0) { - // Fixed delay for reverb effect (e.g. 50ms) - uint32_t delay_samples = (uint32_t)(0.05f * _sampleRate); - if (delay_samples >= c.buffer_size) delay_samples = c.buffer_size - 1; - - int read_idx = (int)c.write_idx - (int)delay_samples; - if (read_idx < 0) read_idx += c.buffer_size; - - float delayed = c.buffer[read_idx]; - // Feedback controlled by param (0.0 to 0.95) - float feedback = c.param * 0.95f; - float newValue = input_val + delayed * feedback; - - c.buffer[c.write_idx] = newValue; - val = newValue; - - c.write_idx = (c.write_idx + 1) % c.buffer_size; - } else { - val = 0.0f; - } } else if (c.type == GridCell::OPERATOR || c.type == GridCell::SINK) { // Gather inputs float inputs[4]; diff --git a/synth_engine.h b/synth_engine.h index a82fcfe..3e3370c 100644 --- a/synth_engine.h +++ b/synth_engine.h @@ -104,9 +104,6 @@ public: int rotation = 0; // 0:N, 1:E, 2:S, 3:W (Output direction) float value = 0.0f; // Current output sample float phase = 0.0f; // For Oscillator, Noise state - float* buffer = nullptr; // For Delay - uint32_t buffer_size = 0; // For Delay - uint32_t write_idx = 0; // For Delay }; static const int GRID_W = 12;