Compare commits
No commits in common. "82bab0698b0bb66176a0dbc847d4b5e6e4f65afc" and "7ff85048df576f8a2766161dc7a67c3ab098ca62" have entirely different histories.
82bab0698b
...
7ff85048df
@ -1,9 +1,7 @@
|
|||||||
#include <mutex>
|
|
||||||
#include "AudioThread.h"
|
#include "AudioThread.h"
|
||||||
#include "SharedState.h"
|
#include "SharedState.h"
|
||||||
#include <I2S.h>
|
#include <I2S.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include "synth_engine.h"
|
|
||||||
|
|
||||||
// I2S Pin definitions
|
// I2S Pin definitions
|
||||||
// You may need to change these to match your hardware setup (e.g., for a specific DAC).
|
// You may need to change these to match your hardware setup (e.g., for a specific DAC).
|
||||||
@ -12,26 +10,18 @@ const int I2S_LRC_PIN = 10; // Left-Right Clock (GP10)
|
|||||||
const int I2S_DOUT_PIN = 11; // Data Out (GP11)
|
const int I2S_DOUT_PIN = 11; // Data Out (GP11)
|
||||||
|
|
||||||
// Audio parameters
|
// Audio parameters
|
||||||
const int SAMPLE_RATE = 44100 / 2;
|
const int SAMPLE_RATE = 44100;
|
||||||
const int16_t AMPLITUDE = 16383 / 2; // Use a lower amplitude to avoid clipping (max is 32767 for 16-bit)
|
const int16_t AMPLITUDE = 16383; // Use a lower amplitude to avoid clipping (max is 32767 for 16-bit)
|
||||||
|
|
||||||
// Create an I2S output object
|
// Create an I2S output object
|
||||||
I2S i2s(OUTPUT);
|
I2S i2s(OUTPUT);
|
||||||
|
|
||||||
extern SynthEngine* globalSynth;
|
|
||||||
|
|
||||||
// --- Synthesizer State ---
|
// --- Synthesizer State ---
|
||||||
float currentFrequency = 440.0f;
|
float currentFrequency = 440.0f;
|
||||||
double phase = 0.0;
|
double phase = 0.0;
|
||||||
unsigned long lastNoteChangeTime = 0;
|
unsigned long lastNoteChangeTime = 0;
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
// Ring Buffer
|
|
||||||
int16_t audioBuffer[AUDIO_BUFFER_SIZE];
|
|
||||||
int audioHead = 0;
|
|
||||||
int audioTail = 0;
|
|
||||||
bool audioBuffering = true;
|
|
||||||
|
|
||||||
void setupAudio() {
|
void setupAudio() {
|
||||||
// Configure I2S pins
|
// Configure I2S pins
|
||||||
i2s.setBCLK(I2S_BCLK_PIN);
|
i2s.setBCLK(I2S_BCLK_PIN);
|
||||||
@ -46,12 +36,6 @@ void setupAudio() {
|
|||||||
|
|
||||||
// Seed the random number generator from an unconnected analog pin
|
// Seed the random number generator from an unconnected analog pin
|
||||||
randomSeed(analogRead(A0));
|
randomSeed(analogRead(A0));
|
||||||
|
|
||||||
// Initialize the portable synth engine
|
|
||||||
globalSynth = new SynthEngine(SAMPLE_RATE);
|
|
||||||
if (globalSynth) {
|
|
||||||
globalSynth->loadPreset(2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void loopAudio() {
|
void loopAudio() {
|
||||||
@ -60,62 +44,42 @@ void loopAudio() {
|
|||||||
// Every 500ms, pick a new random note to play
|
// Every 500ms, pick a new random note to play
|
||||||
if (now - lastNoteChangeTime > 500) {
|
if (now - lastNoteChangeTime > 500) {
|
||||||
lastNoteChangeTime = now;
|
lastNoteChangeTime = now;
|
||||||
int noteIndex = random(0, SCALES[currentScaleIndex].numNotes + 2);
|
int noteIndex = random(0, SCALES[currentScaleIndex].numNotes);
|
||||||
|
|
||||||
bool rest = noteIndex >= SCALES[currentScaleIndex].numNotes;
|
|
||||||
if (!rest) {
|
|
||||||
// Calculate frequency based on key, scale, and octave
|
// Calculate frequency based on key, scale, and octave
|
||||||
const float baseFrequency = 261.63f; // C4
|
const float baseFrequency = 261.63f; // C4
|
||||||
float keyFrequency = baseFrequency * pow(2.0f, currentKeyIndex / 12.0f);
|
float keyFrequency = baseFrequency * pow(2.0f, currentKeyIndex / 12.0f);
|
||||||
int semitoneOffset = SCALES[currentScaleIndex].semitones[noteIndex];
|
int semitoneOffset = SCALES[currentScaleIndex].semitones[noteIndex];
|
||||||
currentFrequency = keyFrequency * pow(2.0f, semitoneOffset / 12.0f);
|
currentFrequency = keyFrequency * pow(2.0f, semitoneOffset / 12.0f);
|
||||||
} else {
|
|
||||||
currentFrequency = 0;
|
Serial.println("Playing note: " + String(currentFrequency) + " Hz");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (globalSynth) {
|
// Generate the sine wave sample
|
||||||
globalSynth->setFrequency(currentFrequency > 0 ? currentFrequency : 440.0f);
|
int16_t sample;
|
||||||
globalSynth->setGate(!rest); // Trigger envelope
|
double phaseIncrement = 2.0 * M_PI * currentFrequency / SAMPLE_RATE;
|
||||||
}
|
phase = fmod(phase + phaseIncrement, 2.0 * M_PI);
|
||||||
}
|
|
||||||
|
|
||||||
// Produce samples in a cyclic buffer
|
switch (currentWavetableIndex) {
|
||||||
int nextHead = (audioHead + 1) % AUDIO_BUFFER_SIZE;
|
case 0: // Sine
|
||||||
if (nextHead != audioTail) {
|
sample = static_cast<int16_t>(AMPLITUDE * sin(phase));
|
||||||
if (audioHead == audioTail) {
|
break;
|
||||||
audioBuffering = true;
|
case 1: // Square
|
||||||
}
|
sample = (phase < M_PI) ? AMPLITUDE : -AMPLITUDE;
|
||||||
int16_t sample = 0;
|
break;
|
||||||
if (globalSynth) {
|
case 2: // Saw
|
||||||
globalSynth->process(&sample, 1);
|
sample = static_cast<int16_t>(AMPLITUDE * (1.0 - (phase / M_PI)));
|
||||||
} else {
|
break;
|
||||||
if (currentFrequency > 0) {
|
case 3: // Triangle
|
||||||
phase += 2.0 * M_PI * currentFrequency / SAMPLE_RATE;
|
sample = static_cast<int16_t>(AMPLITUDE * (2.0 * fabs(phase / M_PI - 1.0) - 1.0));
|
||||||
if (phase >= 2.0 * M_PI) phase -= 2.0 * M_PI;
|
break;
|
||||||
sample = phase * 0.1f * AMPLITUDE;
|
default:
|
||||||
} else {
|
|
||||||
sample = 0;
|
sample = 0;
|
||||||
}
|
|
||||||
}
|
|
||||||
audioBuffer[audioHead] = sample;
|
|
||||||
audioHead = nextHead;
|
|
||||||
} else {
|
|
||||||
audioBuffering = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consume samples from this buffer whenever there is capacity in i2s
|
|
||||||
while (!audioBuffering && audioHead != audioTail) {
|
|
||||||
if (i2s.availableForWrite() < 2) {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
int16_t s = audioBuffer[audioTail];
|
|
||||||
i2s.write(s);
|
|
||||||
i2s.write(s);
|
|
||||||
audioTail = (audioTail + 1) % AUDIO_BUFFER_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update usage stats
|
// Write the same sample to both left and right channels (mono audio).
|
||||||
int usage = audioHead - audioTail;
|
// This call is blocking and will wait until there is space in the DMA buffer.
|
||||||
if (usage < 0) usage += AUDIO_BUFFER_SIZE;
|
i2s.write(sample);
|
||||||
audioBufferUsage = usage;
|
i2s.write(sample);
|
||||||
}
|
}
|
||||||
@ -4,7 +4,7 @@ CXXFLAGS = -std=c++17 -Wall -Wextra -I. $(shell sdl2-config --cflags)
|
|||||||
LDFLAGS = -ldl -lm -lpthread $(shell sdl2-config --libs)
|
LDFLAGS = -ldl -lm -lpthread $(shell sdl2-config --libs)
|
||||||
|
|
||||||
# Source files
|
# Source files
|
||||||
SRCS = main.cpp ../synth_engine.cpp
|
SRCS = main.cpp synth_engine.cpp
|
||||||
|
|
||||||
# Output binary
|
# Output binary
|
||||||
TARGET = noicesynth_linux
|
TARGET = noicesynth_linux
|
||||||
@ -1,8 +1,4 @@
|
|||||||
#include <mutex>
|
|
||||||
#include "SharedState.h"
|
#include "SharedState.h"
|
||||||
#include "synth_engine.h"
|
|
||||||
|
|
||||||
volatile int audioBufferUsage = 0;
|
|
||||||
|
|
||||||
volatile unsigned long lastLoop0Time = 0;
|
volatile unsigned long lastLoop0Time = 0;
|
||||||
volatile unsigned long lastLoop1Time = 0;
|
volatile unsigned long lastLoop1Time = 0;
|
||||||
@ -34,5 +30,3 @@ volatile int currentKeyIndex = 0; // C
|
|||||||
const char* WAVETABLE_NAMES[] = {"Sine", "Square", "Saw", "Triangle"};
|
const char* WAVETABLE_NAMES[] = {"Sine", "Square", "Saw", "Triangle"};
|
||||||
const int NUM_WAVETABLES = sizeof(WAVETABLE_NAMES) / sizeof(WAVETABLE_NAMES[0]);
|
const int NUM_WAVETABLES = sizeof(WAVETABLE_NAMES) / sizeof(WAVETABLE_NAMES[0]);
|
||||||
volatile int currentWavetableIndex = 0; // Sine
|
volatile int currentWavetableIndex = 0; // Sine
|
||||||
|
|
||||||
SynthEngine* globalSynth = nullptr;
|
|
||||||
@ -3,9 +3,6 @@
|
|||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
#define AUDIO_BUFFER_SIZE 512
|
|
||||||
extern volatile int audioBufferUsage;
|
|
||||||
|
|
||||||
extern volatile unsigned long lastLoop0Time;
|
extern volatile unsigned long lastLoop0Time;
|
||||||
extern volatile unsigned long lastLoop1Time;
|
extern volatile unsigned long lastLoop1Time;
|
||||||
extern volatile bool watchdogActive;
|
extern volatile bool watchdogActive;
|
||||||
|
|||||||
217
UIThread.cpp
217
UIThread.cpp
@ -1,14 +1,9 @@
|
|||||||
#include <mutex>
|
|
||||||
#include "UIThread.h"
|
#include "UIThread.h"
|
||||||
#include "SharedState.h"
|
#include "SharedState.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <Wire.h>
|
#include <Wire.h>
|
||||||
#include <Adafruit_GFX.h>
|
#include <Adafruit_GFX.h>
|
||||||
#include <Adafruit_SSD1306.h>
|
#include <Adafruit_SSD1306.h>
|
||||||
#include "synth_engine.h"
|
|
||||||
#include <EEPROM.h>
|
|
||||||
|
|
||||||
extern SynthEngine* globalSynth;
|
|
||||||
|
|
||||||
#define SCREEN_WIDTH 128
|
#define SCREEN_WIDTH 128
|
||||||
#define SCREEN_HEIGHT 64
|
#define SCREEN_HEIGHT 64
|
||||||
@ -58,32 +53,6 @@ void readEncoder() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void saveGridToEEPROM() {
|
|
||||||
if (!globalSynth) return;
|
|
||||||
uint8_t buf[SynthEngine::SERIALIZED_GRID_SIZE];
|
|
||||||
globalSynth->exportGrid(buf);
|
|
||||||
|
|
||||||
EEPROM.write(0, 'N');
|
|
||||||
EEPROM.write(1, 'S');
|
|
||||||
for (size_t i = 0; i < sizeof(buf); i++) {
|
|
||||||
EEPROM.write(2 + i, buf[i]);
|
|
||||||
}
|
|
||||||
EEPROM.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
void loadGridFromEEPROM() {
|
|
||||||
if (!globalSynth) return;
|
|
||||||
if (EEPROM.read(0) == 'N' && EEPROM.read(1) == 'S') {
|
|
||||||
uint8_t buf[SynthEngine::SERIALIZED_GRID_SIZE];
|
|
||||||
for (size_t i = 0; i < sizeof(buf); i++) {
|
|
||||||
buf[i] = EEPROM.read(2 + i);
|
|
||||||
}
|
|
||||||
globalSynth->importGrid(buf);
|
|
||||||
} else {
|
|
||||||
globalSynth->loadPreset(1); // Default to preset 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setupUI() {
|
void setupUI() {
|
||||||
Wire.setSDA(PIN_SDA);
|
Wire.setSDA(PIN_SDA);
|
||||||
Wire.setSCL(PIN_SCL);
|
Wire.setSCL(PIN_SCL);
|
||||||
@ -102,22 +71,6 @@ void setupUI() {
|
|||||||
|
|
||||||
display.clearDisplay();
|
display.clearDisplay();
|
||||||
display.display();
|
display.display();
|
||||||
|
|
||||||
// Initialize EEPROM
|
|
||||||
EEPROM.begin(512);
|
|
||||||
|
|
||||||
// Check for safety clear (Button held on startup)
|
|
||||||
if (digitalRead(PIN_ENC_SW) == LOW) {
|
|
||||||
display.setCursor(0, 0);
|
|
||||||
display.setTextColor(SSD1306_WHITE);
|
|
||||||
display.println(F("CLEARING DATA..."));
|
|
||||||
display.display();
|
|
||||||
EEPROM.write(0, 0); // Invalidate magic
|
|
||||||
EEPROM.commit();
|
|
||||||
delay(1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
loadGridFromEEPROM();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleInput() {
|
void handleInput() {
|
||||||
@ -181,155 +134,55 @@ void handleInput() {
|
|||||||
|
|
||||||
void drawUI() {
|
void drawUI() {
|
||||||
display.clearDisplay();
|
display.clearDisplay();
|
||||||
|
display.setTextSize(1);
|
||||||
|
display.setTextColor(SSD1306_WHITE);
|
||||||
|
display.setCursor(0, 0);
|
||||||
|
|
||||||
{
|
if (currentState == UI_MENU) {
|
||||||
// Copy grid state to local buffer to minimize lock time
|
for (int i = 0; i < NUM_MENU_ITEMS; i++) {
|
||||||
struct MiniCell {
|
if (i == menuSelection) {
|
||||||
uint8_t type;
|
display.fillRect(0, i * 10, SCREEN_WIDTH, 10, SSD1306_WHITE);
|
||||||
uint8_t rotation;
|
display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
|
||||||
float value;
|
|
||||||
};
|
|
||||||
MiniCell gridCopy[SynthEngine::GRID_W][SynthEngine::GRID_H];
|
|
||||||
|
|
||||||
if (globalSynth) {
|
|
||||||
SynthLockGuard<SynthMutex> lock(globalSynth->gridMutex);
|
|
||||||
for(int x=0; x<SynthEngine::GRID_W; ++x) {
|
|
||||||
for(int y=0; y<SynthEngine::GRID_H; ++y) {
|
|
||||||
gridCopy[x][y].type = (uint8_t)globalSynth->grid[x][y].type;
|
|
||||||
gridCopy[x][y].rotation = (uint8_t)globalSynth->grid[x][y].rotation;
|
|
||||||
gridCopy[x][y].value = globalSynth->grid[x][y].value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int cellW = 8;
|
|
||||||
int cellH = 5;
|
|
||||||
int marginX = 2;
|
|
||||||
int marginY = (SCREEN_HEIGHT - (SynthEngine::GRID_H * cellH)) / 2;
|
|
||||||
|
|
||||||
for(int x=0; x<SynthEngine::GRID_W; ++x) {
|
|
||||||
for(int y=0; y<SynthEngine::GRID_H; ++y) {
|
|
||||||
int px = marginX + x * cellW;
|
|
||||||
int py = marginY + y * cellH;
|
|
||||||
int cx = px + cellW / 2;
|
|
||||||
int cy = py + cellH / 2;
|
|
||||||
|
|
||||||
uint8_t type = gridCopy[x][y].type;
|
|
||||||
uint8_t rot = gridCopy[x][y].rotation;
|
|
||||||
|
|
||||||
if (type == SynthEngine::GridCell::EMPTY) {
|
|
||||||
display.drawPixel(cx, cy, SSD1306_WHITE);
|
|
||||||
} else if (type == SynthEngine::GridCell::SINK) {
|
|
||||||
display.fillRect(px + 1, py + 1, cellW - 2, cellH - 2, SSD1306_WHITE);
|
|
||||||
} else {
|
} else {
|
||||||
// Draw direction line
|
display.setTextColor(SSD1306_WHITE);
|
||||||
int dx = 0, dy = 0;
|
|
||||||
switch(rot) {
|
|
||||||
case 0: dy = -2; break; // N
|
|
||||||
case 1: dx = 4; break; // E
|
|
||||||
case 2: dy = 2; break; // S
|
|
||||||
case 3: dx = -4; break; // W
|
|
||||||
}
|
}
|
||||||
display.drawLine(cx, cy, cx + dx, cy + dy, SSD1306_WHITE);
|
display.setCursor(2, i * 10 + 1);
|
||||||
|
display.print(MENU_ITEMS[i].label);
|
||||||
|
display.print(": ");
|
||||||
|
|
||||||
if (type == SynthEngine::GridCell::FORK) {
|
// Display current value
|
||||||
if (rot == 0 || rot == 2) display.drawLine(cx - 2, cy, cx + 2, cy, SSD1306_WHITE);
|
switch (MENU_ITEMS[i].editState) {
|
||||||
else display.drawLine(cx, cy - 2, cx, cy + 2, SSD1306_WHITE);
|
case UI_EDIT_SCALE_TYPE: display.print(SCALES[currentScaleIndex].name); break;
|
||||||
} else if (type >= SynthEngine::GridCell::FIXED_OSCILLATOR && type <= SynthEngine::GridCell::GATE_INPUT) {
|
case UI_EDIT_SCALE_KEY: display.print(KEY_NAMES[currentKeyIndex]); break;
|
||||||
// Sources: Filled rect
|
case UI_EDIT_WAVETABLE: display.print(WAVETABLE_NAMES[currentWavetableIndex]); break;
|
||||||
display.fillRect(cx - 1, cy - 1, 3, 3, SSD1306_WHITE);
|
default: break;
|
||||||
} else if (type != SynthEngine::GridCell::WIRE) {
|
|
||||||
// Processors: Hollow rect
|
|
||||||
display.drawRect(cx - 1, cy - 1, 3, 3, SSD1306_WHITE);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} 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;
|
||||||
}
|
}
|
||||||
}
|
display.println(title);
|
||||||
|
display.drawLine(0, 10, SCREEN_WIDTH, 10, SSD1306_WHITE);
|
||||||
// Draw Buffer Stats
|
display.setCursor(10, 25);
|
||||||
int barX = 110;
|
display.setTextSize(2);
|
||||||
int barY = 10;
|
display.print(value);
|
||||||
int barW = 8;
|
display.setTextSize(1);
|
||||||
int barH = 30;
|
display.setCursor(0, 50);
|
||||||
|
display.println(F("(Press to confirm)"));
|
||||||
display.drawRect(barX, barY, barW, barH, SSD1306_WHITE);
|
|
||||||
|
|
||||||
int usage = audioBufferUsage;
|
|
||||||
int fillH = (usage * (barH - 2)) / AUDIO_BUFFER_SIZE;
|
|
||||||
if (fillH > barH - 2) fillH = barH - 2;
|
|
||||||
if (fillH < 0) fillH = 0;
|
|
||||||
|
|
||||||
display.fillRect(barX + 1, barY + (barH - 1) - fillH, barW - 2, fillH, SSD1306_WHITE);
|
|
||||||
|
|
||||||
// display.setCursor(barX - 4, barY + barH + 4);
|
|
||||||
// display.print(usage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
display.display();
|
display.display();
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
while (Serial.available()) {
|
|
||||||
uint8_t b = Serial.read();
|
|
||||||
if (state == 0) {
|
|
||||||
if (b == header[headerIdx]) {
|
|
||||||
headerIdx++;
|
|
||||||
if (headerIdx == 6) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void loopUI() {
|
void loopUI() {
|
||||||
handleInput();
|
handleInput();
|
||||||
checkSerial();
|
|
||||||
drawUI();
|
drawUI();
|
||||||
delay(20); // Prevent excessive screen refresh
|
delay(20); // Prevent excessive screen refresh
|
||||||
}
|
}
|
||||||
@ -3,28 +3,21 @@
|
|||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <mutex>
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include "../synth_engine.h" // Include our portable engine
|
#include "synth_engine.h" // Include our portable engine
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#if !defined(_WIN32)
|
|
||||||
#include <sys/select.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// --- Configuration ---
|
// --- Configuration ---
|
||||||
const uint32_t SAMPLE_RATE = 44100;
|
const uint32_t SAMPLE_RATE = 44100;
|
||||||
const uint32_t CHANNELS = 1; // Mono
|
const uint32_t CHANNELS = 1; // Mono
|
||||||
const int CELL_SIZE = 60;
|
const int GRID_PANEL_WIDTH = 400;
|
||||||
const int GRID_PANEL_WIDTH = 12 * CELL_SIZE; // 720
|
|
||||||
const int SYNTH_PANEL_WIDTH = 800;
|
const int SYNTH_PANEL_WIDTH = 800;
|
||||||
const int WINDOW_WIDTH = GRID_PANEL_WIDTH + SYNTH_PANEL_WIDTH; // 1200
|
const int WINDOW_WIDTH = GRID_PANEL_WIDTH + SYNTH_PANEL_WIDTH; // 1200
|
||||||
const int WINDOW_HEIGHT = 12 * CELL_SIZE; // 720
|
const int WINDOW_HEIGHT = 640;
|
||||||
|
|
||||||
// --- Visualization Buffer ---
|
// --- Visualization Buffer ---
|
||||||
const size_t VIS_BUFFER_SIZE = 8192;
|
const size_t VIS_BUFFER_SIZE = 8192;
|
||||||
@ -43,7 +36,6 @@ int current_key_scancode = 0; // 0 for none
|
|||||||
bool auto_melody_enabled = false;
|
bool auto_melody_enabled = false;
|
||||||
Uint32 auto_melody_next_event_time = 0;
|
Uint32 auto_melody_next_event_time = 0;
|
||||||
const int c_major_scale[] = {0, 2, 4, 5, 7, 9, 11, 12}; // Semitones from root
|
const int c_major_scale[] = {0, 2, 4, 5, 7, 9, 11, 12}; // Semitones from root
|
||||||
int current_preset = 0;
|
|
||||||
|
|
||||||
|
|
||||||
float note_to_freq(int octave, int semitone_offset);
|
float note_to_freq(int octave, int semitone_offset);
|
||||||
@ -78,61 +70,6 @@ void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uin
|
|||||||
vis_write_index.store(idx, std::memory_order_relaxed);
|
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 ---
|
// --- UI Drawing Helpers ---
|
||||||
|
|
||||||
void DrawCircle(SDL_Renderer * renderer, int32_t centreX, int32_t centreY, int32_t radius) {
|
void DrawCircle(SDL_Renderer * renderer, int32_t centreX, int32_t centreY, int32_t radius) {
|
||||||
@ -212,6 +149,21 @@ void drawToggle(SDL_Renderer* renderer, int x, int y, int size, bool active) {
|
|||||||
SDL_RenderDrawLine(renderer, m_x + m_w, m_y, m_x + m_w, m_y + m_h); // Right leg
|
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 ---
|
// --- Simple Vector Font ---
|
||||||
void drawChar(SDL_Renderer* renderer, int x, int y, int size, char c) {
|
void drawChar(SDL_Renderer* renderer, int x, int y, int size, char c) {
|
||||||
int w = size * 0.6;
|
int w = size * 0.6;
|
||||||
@ -278,29 +230,6 @@ void drawString(SDL_Renderer* renderer, int x, int y, int size, const char* 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) {
|
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_SetRenderDrawColor(renderer, 50, 50, 50, 255);
|
||||||
SDL_Rect bg = {x + 4, y + size - 6, size - 8, 4};
|
SDL_Rect bg = {x + 4, y + size - 6, size - 8, 4};
|
||||||
@ -314,45 +243,6 @@ void drawParamBar(SDL_Renderer* renderer, int x, int y, int size, float value, u
|
|||||||
SDL_RenderFillRect(renderer, &fg);
|
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) {
|
void drawTypeLabel(SDL_Renderer* renderer, int x, int y, char c) {
|
||||||
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
|
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
|
||||||
drawChar(renderer, x + 3, y + 3, 8, c);
|
drawChar(renderer, x + 3, y + 3, 8, c);
|
||||||
@ -390,7 +280,11 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G
|
|||||||
if (cell.rotation == 2) dy = r;
|
if (cell.rotation == 2) dy = r;
|
||||||
if (cell.rotation == 3) dx = -r;
|
if (cell.rotation == 3) dx = -r;
|
||||||
SDL_RenderDrawLine(renderer, cx, cy, cx+dx, cy+dy);
|
SDL_RenderDrawLine(renderer, cx, cy, cx+dx, cy+dy);
|
||||||
drawDirectionArrow(renderer, cx, cy, size, cell.rotation);
|
// 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, '-');
|
drawTypeLabel(renderer, x, y, '-');
|
||||||
} else if (cell.type == SynthEngine::GridCell::FIXED_OSCILLATOR) {
|
} else if (cell.type == SynthEngine::GridCell::FIXED_OSCILLATOR) {
|
||||||
DrawCircle(renderer, cx, cy, r);
|
DrawCircle(renderer, cx, cy, r);
|
||||||
@ -401,7 +295,6 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G
|
|||||||
if (cell.rotation == 2) dy = r;
|
if (cell.rotation == 2) dy = r;
|
||||||
if (cell.rotation == 3) dx = -r;
|
if (cell.rotation == 3) dx = -r;
|
||||||
SDL_RenderDrawLine(renderer, cx, cy, cx+dx, cy+dy);
|
SDL_RenderDrawLine(renderer, cx, cy, cx+dx, cy+dy);
|
||||||
drawDirectionArrow(renderer, cx, cy, size, cell.rotation);
|
|
||||||
// Param (Freq)
|
// Param (Freq)
|
||||||
char buf[16];
|
char buf[16];
|
||||||
snprintf(buf, 16, "%.0f", 10.0f + cell.param*990.0f);
|
snprintf(buf, 16, "%.0f", 10.0f + cell.param*990.0f);
|
||||||
@ -419,7 +312,6 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G
|
|||||||
if (cell.rotation == 2) dy = r;
|
if (cell.rotation == 2) dy = r;
|
||||||
if (cell.rotation == 3) dx = -r;
|
if (cell.rotation == 3) dx = -r;
|
||||||
SDL_RenderDrawLine(renderer, cx, cy, cx+dx, cy+dy);
|
SDL_RenderDrawLine(renderer, cx, cy, cx+dx, cy+dy);
|
||||||
drawDirectionArrow(renderer, cx, cy, size, cell.rotation);
|
|
||||||
// Param (Octave)
|
// Param (Octave)
|
||||||
char buf[16]; snprintf(buf, 16, "O%d", 1 + (int)(cell.param * 4.99f));
|
char buf[16]; snprintf(buf, 16, "O%d", 1 + (int)(cell.param * 4.99f));
|
||||||
SDL_SetRenderDrawColor(renderer, 255, 200, 0, 255);
|
SDL_SetRenderDrawColor(renderer, 255, 200, 0, 255);
|
||||||
@ -438,7 +330,6 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G
|
|||||||
if (cell.rotation == 2) dy = r;
|
if (cell.rotation == 2) dy = r;
|
||||||
if (cell.rotation == 3) dx = -r;
|
if (cell.rotation == 3) dx = -r;
|
||||||
SDL_RenderDrawLine(renderer, cx, cy, cx+dx, cy+dy);
|
SDL_RenderDrawLine(renderer, cx, cy, cx+dx, cy+dy);
|
||||||
drawDirectionArrow(renderer, cx, cy, size, cell.rotation);
|
|
||||||
|
|
||||||
// Param (Color)
|
// Param (Color)
|
||||||
const char* colors[] = {"BRN", "PNK", "WHT", "YEL", "GRN"};
|
const char* colors[] = {"BRN", "PNK", "WHT", "YEL", "GRN"};
|
||||||
@ -456,7 +347,6 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G
|
|||||||
if (cell.rotation == 2) dy = r;
|
if (cell.rotation == 2) dy = r;
|
||||||
if (cell.rotation == 3) dx = -r;
|
if (cell.rotation == 3) dx = -r;
|
||||||
SDL_RenderDrawLine(renderer, cx, cy, cx+dx, cy+dy);
|
SDL_RenderDrawLine(renderer, cx, cy, cx+dx, cy+dy);
|
||||||
drawDirectionArrow(renderer, cx, cy, size, cell.rotation);
|
|
||||||
drawString(renderer, cx - 8, cy - 5, 12, "LFO");
|
drawString(renderer, cx - 8, cy - 5, 12, "LFO");
|
||||||
// Param (Freq)
|
// Param (Freq)
|
||||||
char buf[16]; snprintf(buf, 16, "%.1f", 0.1f + cell.param * 19.9f);
|
char buf[16]; snprintf(buf, 16, "%.1f", 0.1f + cell.param * 19.9f);
|
||||||
@ -464,10 +354,15 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G
|
|||||||
drawString(renderer, x + 5, y + size - 18, 10, buf);
|
drawString(renderer, x + 5, y + size - 18, 10, buf);
|
||||||
drawParamBar(renderer, x, y, size, cell.param, 0, 255, 255);
|
drawParamBar(renderer, x, y, size, cell.param, 0, 255, 255);
|
||||||
drawTypeLabel(renderer, x, y, 'L');
|
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) {
|
} else if (cell.type == SynthEngine::GridCell::GATE_INPUT) {
|
||||||
SDL_Rect box = {cx - r, cy - r, r*2, r*2};
|
SDL_Rect box = {cx - r, cy - r, r*2, r*2};
|
||||||
SDL_RenderDrawRect(renderer, &box);
|
SDL_RenderDrawRect(renderer, &box);
|
||||||
drawDirectionArrow(renderer, cx, cy, size, cell.rotation);
|
|
||||||
if (cell.value > 0.5f) SDL_RenderFillRect(renderer, &box);
|
if (cell.value > 0.5f) SDL_RenderFillRect(renderer, &box);
|
||||||
drawString(renderer, cx - 8, cy - 5, 12, "G-IN");
|
drawString(renderer, cx - 8, cy - 5, 12, "G-IN");
|
||||||
drawTypeLabel(renderer, x, y, 'K');
|
drawTypeLabel(renderer, x, y, 'K');
|
||||||
@ -482,7 +377,6 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G
|
|||||||
int odx=0, ody=0;
|
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;
|
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);
|
SDL_RenderDrawLine(renderer, cx, cy, cx+odx, cy+ody);
|
||||||
drawDirectionArrow(renderer, cx, cy, size, cell.rotation);
|
|
||||||
|
|
||||||
drawParamBar(renderer, x, y, size, cell.param, 255, 255, 255);
|
drawParamBar(renderer, x, y, size, cell.param, 255, 255, 255);
|
||||||
drawTypeLabel(renderer, x, y, 'A');
|
drawTypeLabel(renderer, x, y, 'A');
|
||||||
@ -497,7 +391,6 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G
|
|||||||
int odx=0, ody=0;
|
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;
|
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);
|
SDL_RenderDrawLine(renderer, cx, cy, cx+odx, cy+ody);
|
||||||
drawDirectionArrow(renderer, cx, cy, size, cell.rotation);
|
|
||||||
|
|
||||||
drawParamBar(renderer, x, y, size, cell.param, 255, 255, 255);
|
drawParamBar(renderer, x, y, size, cell.param, 255, 255, 255);
|
||||||
drawTypeLabel(renderer, x, y, 'D');
|
drawTypeLabel(renderer, x, y, 'D');
|
||||||
@ -512,7 +405,6 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G
|
|||||||
int odx=0, ody=0;
|
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;
|
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);
|
SDL_RenderDrawLine(renderer, cx, cy, cx+odx, cy+ody);
|
||||||
drawDirectionArrow(renderer, cx, cy, size, cell.rotation);
|
|
||||||
|
|
||||||
drawParamBar(renderer, x, y, size, cell.param, 255, 255, 255);
|
drawParamBar(renderer, x, y, size, cell.param, 255, 255, 255);
|
||||||
drawTypeLabel(renderer, x, y, 'S');
|
drawTypeLabel(renderer, x, y, 'S');
|
||||||
@ -527,7 +419,6 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G
|
|||||||
int odx=0, ody=0;
|
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;
|
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);
|
SDL_RenderDrawLine(renderer, cx, cy, cx+odx, cy+ody);
|
||||||
drawDirectionArrow(renderer, cx, cy, size, cell.rotation);
|
|
||||||
|
|
||||||
drawParamBar(renderer, x, y, size, cell.param, 255, 255, 255);
|
drawParamBar(renderer, x, y, size, cell.param, 255, 255, 255);
|
||||||
drawTypeLabel(renderer, x, y, 'E');
|
drawTypeLabel(renderer, x, y, 'E');
|
||||||
@ -545,7 +436,6 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G
|
|||||||
int odx=0, ody=0;
|
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;
|
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);
|
SDL_RenderDrawLine(renderer, cx, cy, cx+odx, cy+ody);
|
||||||
drawDirectionArrow(renderer, cx, cy, size, cell.rotation);
|
|
||||||
// Param
|
// Param
|
||||||
char buf[16]; snprintf(buf, 16, "%.2f", cell.param);
|
char buf[16]; snprintf(buf, 16, "%.2f", cell.param);
|
||||||
SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);
|
SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);
|
||||||
@ -566,7 +456,6 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G
|
|||||||
int odx=0, ody=0;
|
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;
|
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);
|
SDL_RenderDrawLine(renderer, cx, cy, cx+odx, cy+ody);
|
||||||
drawDirectionArrow(renderer, cx, cy, size, cell.rotation);
|
|
||||||
// Param
|
// Param
|
||||||
char buf[16]; snprintf(buf, 16, "%.2f", cell.param);
|
char buf[16]; snprintf(buf, 16, "%.2f", cell.param);
|
||||||
SDL_SetRenderDrawColor(renderer, 255, 255, 0, 255);
|
SDL_SetRenderDrawColor(renderer, 255, 255, 0, 255);
|
||||||
@ -585,7 +474,6 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G
|
|||||||
int odx=0, ody=0;
|
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;
|
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);
|
SDL_RenderDrawLine(renderer, cx, cy, cx+odx, cy+ody);
|
||||||
drawDirectionArrow(renderer, cx, cy, size, cell.rotation);
|
|
||||||
// Param
|
// Param
|
||||||
char buf[16]; snprintf(buf, 16, "%.2f", cell.param);
|
char buf[16]; snprintf(buf, 16, "%.2f", cell.param);
|
||||||
SDL_SetRenderDrawColor(renderer, 255, 0, 255, 255);
|
SDL_SetRenderDrawColor(renderer, 255, 0, 255, 255);
|
||||||
@ -604,7 +492,6 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G
|
|||||||
int odx=0, ody=0;
|
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;
|
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);
|
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);
|
char buf[16]; snprintf(buf, 16, "%.2f", cell.param);
|
||||||
SDL_SetRenderDrawColor(renderer, 255, 100, 100, 255);
|
SDL_SetRenderDrawColor(renderer, 255, 100, 100, 255);
|
||||||
drawString(renderer, x + 5, y + size - 18, 10, buf);
|
drawString(renderer, x + 5, y + size - 18, 10, buf);
|
||||||
@ -622,7 +509,6 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G
|
|||||||
int odx=0, ody=0;
|
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;
|
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);
|
SDL_RenderDrawLine(renderer, cx, cy, cx+odx, cy+ody);
|
||||||
drawDirectionArrow(renderer, cx, cy, size, cell.rotation);
|
|
||||||
drawParamBar(renderer, x, y, size, cell.param, 255, 150, 0);
|
drawParamBar(renderer, x, y, size, cell.param, 255, 150, 0);
|
||||||
drawTypeLabel(renderer, x, y, '|');
|
drawTypeLabel(renderer, x, y, '|');
|
||||||
} else if (cell.type == SynthEngine::GridCell::PITCH_SHIFTER) {
|
} else if (cell.type == SynthEngine::GridCell::PITCH_SHIFTER) {
|
||||||
@ -635,7 +521,6 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G
|
|||||||
int odx=0, ody=0;
|
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;
|
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);
|
SDL_RenderDrawLine(renderer, cx, cy, cx+odx, cy+ody);
|
||||||
drawDirectionArrow(renderer, cx, cy, size, cell.rotation);
|
|
||||||
drawParamBar(renderer, x, y, size, cell.param, 100, 255, 100);
|
drawParamBar(renderer, x, y, size, cell.param, 100, 255, 100);
|
||||||
drawTypeLabel(renderer, x, y, '^');
|
drawTypeLabel(renderer, x, y, '^');
|
||||||
} else if (cell.type == SynthEngine::GridCell::GLITCH) {
|
} else if (cell.type == SynthEngine::GridCell::GLITCH) {
|
||||||
@ -648,7 +533,6 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G
|
|||||||
int odx=0, ody=0;
|
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;
|
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);
|
SDL_RenderDrawLine(renderer, cx, cy, cx+odx, cy+ody);
|
||||||
drawDirectionArrow(renderer, cx, cy, size, cell.rotation);
|
|
||||||
|
|
||||||
drawParamBar(renderer, x, y, size, cell.param, 255, 0, 0);
|
drawParamBar(renderer, x, y, size, cell.param, 255, 0, 0);
|
||||||
drawTypeLabel(renderer, x, y, 'G');
|
drawTypeLabel(renderer, x, y, 'G');
|
||||||
@ -669,8 +553,6 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G
|
|||||||
if(rDir==0) rdy=-r; else if(rDir==1) rdx=r; else if(rDir==2) rdy=r; else rdx=-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+ldx, cy+ldy);
|
||||||
SDL_RenderDrawLine(renderer, cx, cy, cx+rdx, cy+rdy);
|
SDL_RenderDrawLine(renderer, cx, cy, cx+rdx, cy+rdy);
|
||||||
drawDirectionArrow(renderer, cx, cy, size, lDir);
|
|
||||||
drawDirectionArrow(renderer, cx, cy, size, rDir);
|
|
||||||
|
|
||||||
// Param (Balance)
|
// Param (Balance)
|
||||||
char buf[16]; snprintf(buf, 16, "%.1f", cell.param);
|
char buf[16]; snprintf(buf, 16, "%.1f", cell.param);
|
||||||
@ -691,7 +573,6 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G
|
|||||||
int odx=0, ody=0;
|
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;
|
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);
|
SDL_RenderDrawLine(renderer, cx, cy, cx+odx, cy+ody);
|
||||||
drawDirectionArrow(renderer, cx, cy, size, cell.rotation);
|
|
||||||
|
|
||||||
// Param (Delay time in ms)
|
// Param (Delay time in ms)
|
||||||
char buf[16];
|
char buf[16];
|
||||||
@ -714,7 +595,6 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G
|
|||||||
int odx=0, ody=0;
|
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;
|
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);
|
SDL_RenderDrawLine(renderer, cx, cy, cx+odx, cy+ody);
|
||||||
drawDirectionArrow(renderer, cx, cy, size, cell.rotation);
|
|
||||||
// Param (Strength)
|
// Param (Strength)
|
||||||
char buf[16]; snprintf(buf, 16, "%.2f", cell.param);
|
char buf[16]; snprintf(buf, 16, "%.2f", cell.param);
|
||||||
SDL_SetRenderDrawColor(renderer, 200, 100, 255, 255);
|
SDL_SetRenderDrawColor(renderer, 200, 100, 255, 255);
|
||||||
@ -730,7 +610,6 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G
|
|||||||
if (cell.rotation == 2) dy = r;
|
if (cell.rotation == 2) dy = r;
|
||||||
if (cell.rotation == 3) dx = -r;
|
if (cell.rotation == 3) dx = -r;
|
||||||
SDL_RenderDrawLine(renderer, cx, cy, cx+dx, cy+dy);
|
SDL_RenderDrawLine(renderer, cx, cy, cx+dx, cy+dy);
|
||||||
drawDirectionArrow(renderer, cx, cy, size, cell.rotation);
|
|
||||||
|
|
||||||
// Draw Op Symbol
|
// Draw Op Symbol
|
||||||
char opChar = '?';
|
char opChar = '?';
|
||||||
@ -753,7 +632,6 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G
|
|||||||
if (cell.rotation == 2) dy = r;
|
if (cell.rotation == 2) dy = r;
|
||||||
if (cell.rotation == 3) dx = -r;
|
if (cell.rotation == 3) dx = -r;
|
||||||
SDL_RenderDrawLine(renderer, cx, cy, cx+dx, cy+dy);
|
SDL_RenderDrawLine(renderer, cx, cy, cx+dx, cy+dy);
|
||||||
drawDirectionArrow(renderer, cx, cy, size, cell.rotation);
|
|
||||||
// Param (Wave index)
|
// Param (Wave index)
|
||||||
int idx = (int)(cell.param * 7.99f);
|
int idx = (int)(cell.param * 7.99f);
|
||||||
char buf[4];
|
char buf[4];
|
||||||
@ -765,20 +643,18 @@ void drawGridCell(SDL_Renderer* renderer, int x, int y, int size, SynthEngine::G
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void randomizeGrid() {
|
void clearGrid() {
|
||||||
printf("Randomizing grid...\n");
|
std::lock_guard<std::mutex> lock(engine.gridMutex);
|
||||||
Uint32 startTime = SDL_GetTicks();
|
for (int x = 0; x < 5; ++x) {
|
||||||
SynthLockGuard<SynthMutex> lock(engine.gridMutex);
|
for (int y = 0; y < 8; ++y) {
|
||||||
|
|
||||||
// Number of types to choose from (excluding SINK)
|
|
||||||
const int numTypes = (int)SynthEngine::GridCell::SINK;
|
|
||||||
|
|
||||||
// 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];
|
SynthEngine::GridCell& c = engine.grid[x][y];
|
||||||
if (c.type == SynthEngine::GridCell::SINK) continue;
|
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.type = SynthEngine::GridCell::EMPTY;
|
||||||
c.param = 0.5f;
|
c.param = 0.5f;
|
||||||
c.rotation = 0;
|
c.rotation = 0;
|
||||||
@ -786,39 +662,55 @@ void randomizeGrid() {
|
|||||||
c.phase = 0.0f;
|
c.phase = 0.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void randomizeGrid() {
|
||||||
|
std::lock_guard<std::mutex> 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;
|
int attempts = 0;
|
||||||
bool validGrid = false;
|
bool inputOscillatorReachable = false;
|
||||||
bool visited[SynthEngine::GRID_W][SynthEngine::GRID_H];
|
bool visited[5][8];
|
||||||
|
|
||||||
while (!validGrid && attempts < 1000) {
|
while (!inputOscillatorReachable && attempts < 1000) {
|
||||||
if (SDL_GetTicks() - startTime > 200) { // Safeguard: Timeout after 200ms
|
|
||||||
printf("Randomization timed out.\n");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
attempts++;
|
attempts++;
|
||||||
|
|
||||||
// 2. Randomize (without allocation)
|
// 2. Randomize (without allocation)
|
||||||
for (int x = 0; x < SynthEngine::GRID_W; ++x) {
|
for (int x = 0; x < 5; ++x) {
|
||||||
for (int y = 0; y < SynthEngine::GRID_H; ++y) {
|
for (int y = 0; y < 8; ++y) {
|
||||||
SynthEngine::GridCell& c = engine.grid[x][y];
|
SynthEngine::GridCell& c = engine.grid[x][y];
|
||||||
if (c.type == SynthEngine::GridCell::SINK) continue;
|
if (c.type == SynthEngine::GridCell::SINK) continue;
|
||||||
|
|
||||||
c.type = (SynthEngine::GridCell::Type)(rand() % numTypes);
|
c.type = (SynthEngine::GridCell::Type)(rand() % numTypes);
|
||||||
c.rotation = rand() % 4;
|
c.rotation = rand() % 4;
|
||||||
c.param = (float)rand() / (float)RAND_MAX;
|
c.param = (float)rand() / (float)RAND_MAX;
|
||||||
c.value = 0.0f;
|
|
||||||
c.phase = 0.0f;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Check Connectivity
|
// 3. Check Connectivity
|
||||||
// BFS from SINK backwards
|
// BFS from SINK (2,3) backwards
|
||||||
memset(visited, 0, sizeof(visited));
|
memset(visited, 0, sizeof(visited));
|
||||||
std::vector<std::pair<int, int>> q;
|
std::vector<std::pair<int, int>> q;
|
||||||
|
|
||||||
q.push_back({SynthEngine::GRID_W / 2, SynthEngine::GRID_H - 1});
|
q.push_back({2, 3});
|
||||||
visited[SynthEngine::GRID_W / 2][SynthEngine::GRID_H - 1] = true;
|
visited[2][3] = true;
|
||||||
|
|
||||||
int head = 0;
|
int head = 0;
|
||||||
while(head < (int)q.size()) {
|
while(head < (int)q.size()) {
|
||||||
@ -833,7 +725,7 @@ void randomizeGrid() {
|
|||||||
for(int i=0; i<4; ++i) {
|
for(int i=0; i<4; ++i) {
|
||||||
int tx = cx + nx[i];
|
int tx = cx + nx[i];
|
||||||
int ty = cy + ny[i];
|
int ty = cy + ny[i];
|
||||||
if (tx >= 0 && tx < SynthEngine::GRID_W && ty >= 0 && ty < SynthEngine::GRID_H && !visited[tx][ty]) {
|
if (tx >= 0 && tx < 5 && ty >= 0 && ty < 8 && !visited[tx][ty]) {
|
||||||
|
|
||||||
SynthEngine::GridCell& neighbor = engine.grid[tx][ty];
|
SynthEngine::GridCell& neighbor = engine.grid[tx][ty];
|
||||||
bool pointsToCurr = false;
|
bool pointsToCurr = false;
|
||||||
@ -873,27 +765,22 @@ void randomizeGrid() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// After BFS, check if requirements are met
|
// After BFS, check if an input oscillator is reachable
|
||||||
bool hasSoundSource = false;
|
for (int x = 0; x < 5; ++x) {
|
||||||
bool hasGate = false;
|
for (int y = 0; y < 8; ++y) {
|
||||||
for (int x = 0; x < SynthEngine::GRID_W; ++x) {
|
if (visited[x][y] && engine.grid[x][y].type == SynthEngine::GridCell::INPUT_OSCILLATOR) {
|
||||||
for (int y = 0; y < SynthEngine::GRID_H; ++y) {
|
inputOscillatorReachable = true;
|
||||||
if (visited[x][y]) {
|
break;
|
||||||
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 (inputOscillatorReachable) break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasSoundSource && hasGate) {
|
// 4. Prune unreachable elements if a valid grid was found
|
||||||
// 4. Prune unreachable elements
|
if (inputOscillatorReachable) {
|
||||||
for (int x = 0; x < SynthEngine::GRID_W; ++x) {
|
for (int x = 0; x < 5; ++x) {
|
||||||
for (int y = 0; y < SynthEngine::GRID_H; ++y) {
|
for (int y = 0; y < 8; ++y) {
|
||||||
if (!visited[x][y]) {
|
if (!visited[x][y]) {
|
||||||
engine.grid[x][y].type = SynthEngine::GridCell::EMPTY;
|
engine.grid[x][y].type = SynthEngine::GridCell::EMPTY;
|
||||||
engine.grid[x][y].param = 0.5f;
|
engine.grid[x][y].param = 0.5f;
|
||||||
@ -901,57 +788,26 @@ void randomizeGrid() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Run Simulation
|
|
||||||
engine.setGate(true);
|
|
||||||
float oldFreq = engine.getFrequency();
|
|
||||||
engine.setFrequency(440.0f);
|
|
||||||
bool soundDetected = false;
|
|
||||||
for(int i=0; i<1000; ++i) {
|
|
||||||
float val = engine.processGridStep();
|
|
||||||
if (fabsf(val) > 0.001f) {
|
|
||||||
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.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Failed simulation, cleanup buffers
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If failed after all attempts, ensure grid is clean
|
// 5. Allocate buffers for DELAYs and REVERBs that are still present
|
||||||
if (!validGrid) {
|
for (int x = 0; x < 5; ++x) {
|
||||||
for (int x = 0; x < SynthEngine::GRID_W; ++x) {
|
for (int y = 0; y < 8; ++y) {
|
||||||
for (int y = 0; y < SynthEngine::GRID_H; ++y) {
|
|
||||||
SynthEngine::GridCell& c = engine.grid[x][y];
|
SynthEngine::GridCell& c = engine.grid[x][y];
|
||||||
if (c.type != SynthEngine::GridCell::SINK) {
|
if (c.type == SynthEngine::GridCell::DELAY || c.type == SynthEngine::GridCell::REVERB || c.type == SynthEngine::GridCell::PITCH_SHIFTER) {
|
||||||
c.type = SynthEngine::GridCell::EMPTY;
|
c.buffer_size = 2 * SAMPLE_RATE;
|
||||||
c.param = 0.5f;
|
c.buffer = new float[c.buffer_size]();
|
||||||
c.rotation = 0;
|
c.write_idx = 0;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("Randomized in %d attempts (%d ms). Valid: %s\n", attempts, SDL_GetTicks() - startTime, validGrid ? "YES" : "NO");
|
printf("Randomized in %d attempts. Input Osc reachable: %s\n", attempts, inputOscillatorReachable ? "YES" : "NO");
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
(void)argc; (void)argv;
|
(void)argc; (void)argv;
|
||||||
|
|
||||||
FILE* serialPort = nullptr;
|
|
||||||
srand(time(NULL)); // Seed random number generator
|
srand(time(NULL)); // Seed random number generator
|
||||||
|
|
||||||
// --- Init SDL ---
|
// --- Init SDL ---
|
||||||
@ -985,12 +841,6 @@ int main(int argc, char* argv[]) {
|
|||||||
|
|
||||||
ma_device_start(&device);
|
ma_device_start(&device);
|
||||||
|
|
||||||
if (argc > 1) {
|
|
||||||
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]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Setup Keyboard to Note Mapping ---
|
// --- Setup Keyboard to Note Mapping ---
|
||||||
// Two rows of keys mapped to a chromatic scale
|
// Two rows of keys mapped to a chromatic scale
|
||||||
key_to_note_map[SDL_SCANCODE_A] = 0; // C
|
key_to_note_map[SDL_SCANCODE_A] = 0; // C
|
||||||
@ -1022,6 +872,7 @@ int main(int argc, char* argv[]) {
|
|||||||
SynthEngine::GridCell::WAVETABLE,
|
SynthEngine::GridCell::WAVETABLE,
|
||||||
SynthEngine::GridCell::NOISE,
|
SynthEngine::GridCell::NOISE,
|
||||||
SynthEngine::GridCell::LFO,
|
SynthEngine::GridCell::LFO,
|
||||||
|
SynthEngine::GridCell::GATE,
|
||||||
SynthEngine::GridCell::GATE_INPUT,
|
SynthEngine::GridCell::GATE_INPUT,
|
||||||
SynthEngine::GridCell::ADSR_ATTACK,
|
SynthEngine::GridCell::ADSR_ATTACK,
|
||||||
SynthEngine::GridCell::ADSR_DECAY,
|
SynthEngine::GridCell::ADSR_DECAY,
|
||||||
@ -1035,18 +886,18 @@ int main(int argc, char* argv[]) {
|
|||||||
SynthEngine::GridCell::BITCRUSHER,
|
SynthEngine::GridCell::BITCRUSHER,
|
||||||
SynthEngine::GridCell::DISTORTION,
|
SynthEngine::GridCell::DISTORTION,
|
||||||
SynthEngine::GridCell::RECTIFIER,
|
SynthEngine::GridCell::RECTIFIER,
|
||||||
|
SynthEngine::GridCell::PITCH_SHIFTER,
|
||||||
SynthEngine::GridCell::GLITCH,
|
SynthEngine::GridCell::GLITCH,
|
||||||
SynthEngine::GridCell::OPERATOR
|
SynthEngine::GridCell::OPERATOR,
|
||||||
|
SynthEngine::GridCell::DELAY,
|
||||||
|
SynthEngine::GridCell::REVERB
|
||||||
};
|
};
|
||||||
const int numCellTypes = sizeof(cellTypes) / sizeof(cellTypes[0]);
|
const int numCellTypes = sizeof(cellTypes) / sizeof(cellTypes[0]);
|
||||||
|
|
||||||
bool quit = false;
|
bool quit = false;
|
||||||
SDL_Event e;
|
SDL_Event e;
|
||||||
bool exportButtonPressed = false;
|
|
||||||
bool importButtonPressed = false;
|
|
||||||
|
|
||||||
while (!quit) {
|
while (!quit) {
|
||||||
checkSerialInput(serialPort);
|
|
||||||
// --- Automated Melody Logic ---
|
// --- Automated Melody Logic ---
|
||||||
if (auto_melody_enabled && SDL_GetTicks() > auto_melody_next_event_time) {
|
if (auto_melody_enabled && SDL_GetTicks() > auto_melody_next_event_time) {
|
||||||
auto_melody_next_event_time = SDL_GetTicks() + 200 + (rand() % 150); // Note duration
|
auto_melody_next_event_time = SDL_GetTicks() + 200 + (rand() % 150); // Note duration
|
||||||
@ -1077,10 +928,10 @@ int main(int argc, char* argv[]) {
|
|||||||
int mx = e.button.x;
|
int mx = e.button.x;
|
||||||
int my = e.button.y;
|
int my = e.button.y;
|
||||||
if (mx < GRID_PANEL_WIDTH) {
|
if (mx < GRID_PANEL_WIDTH) {
|
||||||
int gx = mx / CELL_SIZE;
|
int gx = mx / 80;
|
||||||
int gy = my / CELL_SIZE;
|
int gy = my / 80;
|
||||||
if (gx >= 0 && gx < SynthEngine::GRID_W && gy >= 0 && gy < SynthEngine::GRID_H) {
|
if (gx >= 0 && gx < 5 && gy >= 0 && gy < 8) {
|
||||||
SynthLockGuard<SynthMutex> lock(engine.gridMutex);
|
std::lock_guard<std::mutex> lock(engine.gridMutex);
|
||||||
SynthEngine::GridCell& c = engine.grid[gx][gy];
|
SynthEngine::GridCell& c = engine.grid[gx][gy];
|
||||||
if (c.type != SynthEngine::GridCell::SINK) {
|
if (c.type != SynthEngine::GridCell::SINK) {
|
||||||
SynthEngine::GridCell::Type oldType = c.type;
|
SynthEngine::GridCell::Type oldType = c.type;
|
||||||
@ -1102,7 +953,19 @@ int main(int argc, char* argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (newType != oldType) {
|
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;
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1124,19 +987,6 @@ int main(int argc, char* argv[]) {
|
|||||||
auto_melody_next_event_time = SDL_GetTicks(); // Start immediately
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (e.type == SDL_MOUSEWHEEL) {
|
} else if (e.type == SDL_MOUSEWHEEL) {
|
||||||
SDL_Keymod modState = SDL_GetModState();
|
SDL_Keymod modState = SDL_GetModState();
|
||||||
@ -1148,10 +998,10 @@ int main(int argc, char* argv[]) {
|
|||||||
if (mx < GRID_PANEL_WIDTH) {
|
if (mx < GRID_PANEL_WIDTH) {
|
||||||
// Grid Scroll
|
// Grid Scroll
|
||||||
float step = fineTune ? 0.01f : 0.05f;
|
float step = fineTune ? 0.01f : 0.05f;
|
||||||
int gx = mx / CELL_SIZE;
|
int gx = mx / 80;
|
||||||
int gy = my / CELL_SIZE;
|
int gy = my / 80;
|
||||||
if (gx >= 0 && gx < SynthEngine::GRID_W && gy >= 0 && gy < SynthEngine::GRID_H) {
|
if (gx >= 0 && gx < 5 && gy >= 0 && gy < 8) {
|
||||||
SynthLockGuard<SynthMutex> lock(engine.gridMutex);
|
std::lock_guard<std::mutex> lock(engine.gridMutex);
|
||||||
SynthEngine::GridCell& c = engine.grid[gx][gy];
|
SynthEngine::GridCell& c = engine.grid[gx][gy];
|
||||||
if (e.wheel.y > 0) c.param += step;
|
if (e.wheel.y > 0) c.param += step;
|
||||||
else c.param -= step;
|
else c.param -= step;
|
||||||
@ -1183,18 +1033,21 @@ int main(int argc, char* argv[]) {
|
|||||||
engine.setVolume(knob_vol_val);
|
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) {
|
} else if (e.type == SDL_KEYDOWN) {
|
||||||
if (e.key.repeat == 0) { // Ignore key repeats
|
if (e.key.repeat == 0) { // Ignore key repeats
|
||||||
if (e.key.keysym.scancode == SDL_SCANCODE_INSERT) {
|
if (e.key.keysym.scancode == SDL_SCANCODE_INSERT) {
|
||||||
randomizeGrid();
|
randomizeGrid();
|
||||||
} else if (e.key.keysym.scancode == SDL_SCANCODE_DELETE) {
|
} else if (e.key.keysym.scancode == SDL_SCANCODE_DELETE) {
|
||||||
engine.clearGrid();
|
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) {
|
} else if (e.key.keysym.scancode == SDL_SCANCODE_M) {
|
||||||
auto_melody_enabled = !auto_melody_enabled;
|
auto_melody_enabled = !auto_melody_enabled;
|
||||||
engine.setGate(false); // Silence synth on mode change
|
engine.setGate(false); // Silence synth on mode change
|
||||||
@ -1212,55 +1065,6 @@ int main(int argc, char* argv[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} 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::SERIALIZED_GRID_SIZE];
|
|
||||||
engine.exportGrid(buf);
|
|
||||||
fwrite("NSGRID", 1, 6, serialPort);
|
|
||||||
fwrite(buf, 1, sizeof(buf), 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;
|
|
||||||
}
|
|
||||||
} else if (e.type == SDL_KEYUP) {
|
} else if (e.type == SDL_KEYUP) {
|
||||||
if (!auto_melody_enabled && e.key.keysym.scancode == current_key_scancode) {
|
if (!auto_melody_enabled && e.key.keysym.scancode == current_key_scancode) {
|
||||||
engine.setGate(false);
|
engine.setGate(false);
|
||||||
@ -1271,11 +1075,10 @@ int main(int argc, char* argv[]) {
|
|||||||
|
|
||||||
// Update window title with current values
|
// Update window title with current values
|
||||||
char title[256];
|
char title[256];
|
||||||
snprintf(title, sizeof(title), "NoiceSynth | Vol: %.0f%% | Oct: %d | Auto(M): %s | Preset: %d",
|
snprintf(title, sizeof(title), "NoiceSynth | Vol: %.0f%% | Oct: %d | Auto(M): %s",
|
||||||
knob_vol_val * 100.0f,
|
knob_vol_val * 100.0f,
|
||||||
current_octave,
|
current_octave,
|
||||||
auto_melody_enabled ? "ON" : "OFF",
|
auto_melody_enabled ? "ON" : "OFF");
|
||||||
current_preset);
|
|
||||||
SDL_SetWindowTitle(window, title);
|
SDL_SetWindowTitle(window, title);
|
||||||
|
|
||||||
// Clear screen
|
// Clear screen
|
||||||
@ -1339,9 +1142,6 @@ int main(int argc, char* argv[]) {
|
|||||||
|
|
||||||
drawToggle(renderer, 580, 450, 30, auto_melody_enabled);
|
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) ---
|
// --- Draw Grid Panel (Left) ---
|
||||||
SDL_Rect gridViewport = {0, 0, GRID_PANEL_WIDTH, WINDOW_HEIGHT};
|
SDL_Rect gridViewport = {0, 0, GRID_PANEL_WIDTH, WINDOW_HEIGHT};
|
||||||
SDL_RenderSetViewport(renderer, &gridViewport);
|
SDL_RenderSetViewport(renderer, &gridViewport);
|
||||||
@ -1352,17 +1152,16 @@ int main(int argc, char* argv[]) {
|
|||||||
|
|
||||||
{
|
{
|
||||||
// Lock only for reading state to draw
|
// Lock only for reading state to draw
|
||||||
SynthLockGuard<SynthMutex> lock(engine.gridMutex);
|
std::lock_guard<std::mutex> lock(engine.gridMutex);
|
||||||
for(int x=0; x < SynthEngine::GRID_W; ++x) {
|
for(int x=0; x<5; ++x) {
|
||||||
for(int y=0; y < SynthEngine::GRID_H; ++y) {
|
for(int y=0; y<8; ++y) {
|
||||||
drawGridCell(renderer, x*CELL_SIZE, y*CELL_SIZE, CELL_SIZE, engine.grid[x][y]);
|
drawGridCell(renderer, x*80, y*80, 80, engine.grid[x][y]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SDL_RenderPresent(renderer);
|
SDL_RenderPresent(renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serialPort) fclose(serialPort);
|
|
||||||
ma_device_uninit(&device);
|
ma_device_uninit(&device);
|
||||||
SDL_DestroyRenderer(renderer);
|
SDL_DestroyRenderer(renderer);
|
||||||
SDL_DestroyWindow(window);
|
SDL_DestroyWindow(window);
|
||||||
287
synth_engine.cpp
287
synth_engine.cpp
@ -1,6 +1,5 @@
|
|||||||
#include "synth_engine.h"
|
#include "synth_engine.h"
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
// A simple sine lookup table for the sine oscillator
|
// A simple sine lookup table for the sine oscillator
|
||||||
const int SINE_TABLE_SIZE = 256;
|
const int SINE_TABLE_SIZE = 256;
|
||||||
@ -20,13 +19,13 @@ void fill_sine_table() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SynthEngine::SynthEngine(uint32_t sampleRate)
|
SynthEngine::SynthEngine(uint32_t sampleRate)
|
||||||
: grid{},
|
: _sampleRate(sampleRate),
|
||||||
_sampleRate(sampleRate),
|
|
||||||
_phase(0),
|
_phase(0),
|
||||||
_increment(0),
|
_increment(0),
|
||||||
_volume(0.5f),
|
_volume(0.5f),
|
||||||
_waveform(SAWTOOTH),
|
_waveform(SAWTOOTH),
|
||||||
_isGateOpen(false),
|
_isGateOpen(false),
|
||||||
|
grid{},
|
||||||
_rngState(12345)
|
_rngState(12345)
|
||||||
{
|
{
|
||||||
fill_sine_table();
|
fill_sine_table();
|
||||||
@ -34,171 +33,16 @@ SynthEngine::SynthEngine(uint32_t sampleRate)
|
|||||||
setFrequency(440.0f);
|
setFrequency(440.0f);
|
||||||
|
|
||||||
// Initialize SINK
|
// Initialize SINK
|
||||||
grid[GRID_W / 2][GRID_H - 1].type = GridCell::SINK;
|
grid[2][3].type = GridCell::SINK;
|
||||||
}
|
}
|
||||||
|
|
||||||
SynthEngine::~SynthEngine() {
|
SynthEngine::~SynthEngine() {
|
||||||
|
for (int x = 0; x < 5; ++x) {
|
||||||
|
for (int y = 0; y < 8; ++y) {
|
||||||
|
if (grid[x][y].buffer) {
|
||||||
|
delete[] grid[x][y].buffer;
|
||||||
|
grid[x][y].buffer = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SynthEngine::exportGrid(uint8_t* buffer) {
|
|
||||||
SynthLockGuard<SynthMutex> lock(gridMutex);
|
|
||||||
size_t idx = 0;
|
|
||||||
for(int y=0; y<GRID_H; ++y) {
|
|
||||||
for(int x=0; x<GRID_W; ++x) {
|
|
||||||
GridCell& c = grid[x][y];
|
|
||||||
buffer[idx++] = (uint8_t)c.type;
|
|
||||||
buffer[idx++] = (uint8_t)(c.param * 255.0f);
|
|
||||||
buffer[idx++] = (uint8_t)c.rotation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SynthEngine::importGrid(const uint8_t* buffer) {
|
|
||||||
SynthLockGuard<SynthMutex> lock(gridMutex);
|
|
||||||
|
|
||||||
size_t idx = 0;
|
|
||||||
for(int y=0; y<GRID_H; ++y) {
|
|
||||||
for(int x=0; x<GRID_W; ++x) {
|
|
||||||
GridCell& c = grid[x][y];
|
|
||||||
uint8_t t = buffer[idx++];
|
|
||||||
uint8_t p = buffer[idx++];
|
|
||||||
uint8_t r = buffer[idx++];
|
|
||||||
|
|
||||||
GridCell::Type newType = (GridCell::Type)t;
|
|
||||||
c.type = newType;
|
|
||||||
c.param = (float)p / 255.0f;
|
|
||||||
c.rotation = r;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SynthEngine::clearGrid() {
|
|
||||||
SynthLockGuard<SynthMutex> lock(gridMutex);
|
|
||||||
for (int x = 0; x < GRID_W; ++x) {
|
|
||||||
for (int y = 0; y < GRID_H; ++y) {
|
|
||||||
GridCell& c = grid[x][y];
|
|
||||||
if (c.type == GridCell::SINK) continue;
|
|
||||||
|
|
||||||
c.type = GridCell::EMPTY;
|
|
||||||
c.param = 0.5f;
|
|
||||||
c.rotation = 0;
|
|
||||||
c.value = 0.0f;
|
|
||||||
c.phase = 0.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SynthEngine::loadPreset(int preset) {
|
|
||||||
clearGrid();
|
|
||||||
SynthLockGuard<SynthMutex> lock(gridMutex);
|
|
||||||
|
|
||||||
auto placeOp = [&](int x, int y, float ratio, float att, float rel) {
|
|
||||||
// Layout:
|
|
||||||
// (x, y) : G-IN (South)
|
|
||||||
// (x, y+1) : WIRE (East) -> Feeds envelope chain
|
|
||||||
// (x+1, y+1): ATT (East) ->
|
|
||||||
// (x+2, y+1): REL (East)
|
|
||||||
// (x+3, y+1): VCA (South) -> Output is here. Gets audio from OSC, gain from envelope.
|
|
||||||
// (x+3, y) : OSC (South) -> Audio source. Gets FM from its back (x+3, y-1).
|
|
||||||
|
|
||||||
grid[x][y].type = GridCell::GATE_INPUT; grid[x][y].rotation = 2; // S
|
|
||||||
grid[x][y+1].type = GridCell::WIRE; grid[x][y+1].rotation = 1; // E
|
|
||||||
|
|
||||||
grid[x+1][y+1].type = GridCell::ADSR_ATTACK; grid[x+1][y+1].rotation = 1; // E
|
|
||||||
grid[x+1][y+1].param = att;
|
|
||||||
|
|
||||||
grid[x+2][y+1].type = GridCell::ADSR_RELEASE; grid[x+2][y+1].rotation = 1; // E
|
|
||||||
grid[x+2][y+1].param = rel;
|
|
||||||
|
|
||||||
grid[x+3][y+1].type = GridCell::VCA; grid[x+3][y+1].rotation = 2; // S
|
|
||||||
grid[x+3][y+1].param = 0.0f; // Controlled by Env
|
|
||||||
|
|
||||||
grid[x+3][y].type = GridCell::INPUT_OSCILLATOR; grid[x+3][y].rotation = 2; // S
|
|
||||||
grid[x+3][y].param = (ratio > 1.0f) ? 0.5f : 0.0f;
|
|
||||||
};
|
|
||||||
|
|
||||||
int sinkY = GRID_H - 1;
|
|
||||||
int sinkX = GRID_W / 2;
|
|
||||||
|
|
||||||
if (preset == 1) { // Based on DX7 Algorithm 32
|
|
||||||
placeOp(0, 0, 1.0f, 0.01f, 0.5f); // Op 1
|
|
||||||
placeOp(4, 0, 1.0f, 0.05f, 0.3f); // Op 2
|
|
||||||
placeOp(8, 0, 2.0f, 0.01f, 0.2f); // Op 3
|
|
||||||
|
|
||||||
grid[3][2].type = GridCell::WIRE; grid[3][2].rotation = 2;
|
|
||||||
grid[3][3].type = GridCell::WIRE; grid[3][3].rotation = 1; // E
|
|
||||||
grid[4][3].type = GridCell::WIRE; grid[4][3].rotation = 1; // E
|
|
||||||
grid[5][3].type = GridCell::WIRE; grid[5][3].rotation = 1; // E
|
|
||||||
grid[6][3].type = GridCell::WIRE; grid[6][3].rotation = 2; // S
|
|
||||||
|
|
||||||
grid[7][2].type = GridCell::WIRE; grid[7][2].rotation = 2;
|
|
||||||
grid[7][3].type = GridCell::WIRE; grid[7][3].rotation = 3; // W
|
|
||||||
|
|
||||||
grid[11][2].type = GridCell::WIRE; grid[11][2].rotation = 2;
|
|
||||||
grid[11][3].type = GridCell::WIRE; grid[11][3].rotation = 3; // W
|
|
||||||
grid[10][3].type = GridCell::WIRE; grid[10][3].rotation = 3; // W
|
|
||||||
grid[9][3].type = GridCell::WIRE; grid[9][3].rotation = 3; // W
|
|
||||||
grid[8][3].type = GridCell::WIRE; grid[8][3].rotation = 3; // W
|
|
||||||
|
|
||||||
for(int y=4; y<sinkY; ++y) {
|
|
||||||
grid[6][y].type = GridCell::WIRE; grid[6][y].rotation = 2;
|
|
||||||
}
|
|
||||||
} else if (preset == 2) { // Algo 1: Stack (FM)
|
|
||||||
placeOp(4, 0, 2.0f, 0.01f, 0.2f); // Modulator
|
|
||||||
placeOp(4, 2, 1.0f, 0.01f, 0.8f); // Carrier
|
|
||||||
|
|
||||||
grid[7][4].type = GridCell::WIRE; grid[7][4].rotation = 3; // W
|
|
||||||
grid[6][4].type = GridCell::WIRE; grid[6][4].rotation = 2; // S
|
|
||||||
for(int y=5; y<sinkY; ++y) {
|
|
||||||
grid[6][y].type = GridCell::WIRE; grid[6][y].rotation = 2;
|
|
||||||
}
|
|
||||||
} else if (preset == 3) { // Algo 2
|
|
||||||
placeOp(4, 2, 1.0f, 0.01f, 0.8f);
|
|
||||||
placeOp(4, 0, 2.0f, 0.01f, 0.2f);
|
|
||||||
placeOp(0, 0, 1.0f, 0.01f, 0.5f);
|
|
||||||
|
|
||||||
grid[7][4].type = GridCell::WIRE; grid[7][4].rotation = 3; // W
|
|
||||||
|
|
||||||
grid[3][2].type = GridCell::WIRE; grid[3][2].rotation = 1; // E
|
|
||||||
grid[4][2].type = GridCell::WIRE; grid[4][2].rotation = 1; // E
|
|
||||||
grid[5][2].type = GridCell::WIRE; grid[5][2].rotation = 1; // E
|
|
||||||
grid[sinkX][2].type = GridCell::WIRE; grid[sinkX][2].rotation = 2; // S
|
|
||||||
grid[sinkX][3].type = GridCell::WIRE; grid[sinkX][3].rotation = 2; // S
|
|
||||||
grid[sinkX][4].type = GridCell::WIRE; grid[sinkX][4].rotation = 2; // S
|
|
||||||
|
|
||||||
for(int y=5; y<sinkY; ++y) {
|
|
||||||
grid[sinkX][y].type = GridCell::WIRE; grid[sinkX][y].rotation = 2;
|
|
||||||
}
|
|
||||||
} else if (preset == 4) { // Algo 4
|
|
||||||
placeOp(4, 4, 1.0f, 0.01f, 0.8f);
|
|
||||||
placeOp(4, 2, 2.0f, 0.01f, 0.2f);
|
|
||||||
placeOp(4, 0, 4.0f, 0.01f, 0.1f);
|
|
||||||
|
|
||||||
grid[7][6].type = GridCell::WIRE; grid[7][6].rotation = 3; // W
|
|
||||||
grid[sinkX][6].type = GridCell::WIRE; grid[sinkX][6].rotation = 2; // S
|
|
||||||
|
|
||||||
for(int y=7; y<sinkY; ++y) {
|
|
||||||
grid[sinkX][y].type = GridCell::WIRE; grid[sinkX][y].rotation = 2;
|
|
||||||
}
|
|
||||||
} else if (preset == 5) { // Algo 5
|
|
||||||
placeOp(4, 4, 1.0f, 0.01f, 0.8f);
|
|
||||||
placeOp(4, 2, 2.0f, 0.01f, 0.2f);
|
|
||||||
placeOp(4, 0, 4.0f, 0.01f, 0.1f);
|
|
||||||
placeOp(0, 0, 0.5f, 0.01f, 0.5f);
|
|
||||||
|
|
||||||
grid[7][6].type = GridCell::WIRE; grid[7][6].rotation = 3; // W
|
|
||||||
grid[3][2].type = GridCell::WIRE; grid[3][2].rotation = 2; // S
|
|
||||||
grid[3][3].type = GridCell::WIRE; grid[3][3].rotation = 1; // E
|
|
||||||
grid[4][3].type = GridCell::WIRE; grid[4][3].rotation = 1; // E
|
|
||||||
grid[5][3].type = GridCell::WIRE; grid[5][3].rotation = 1; // E
|
|
||||||
grid[6][3].type = GridCell::WIRE; grid[6][3].rotation = 2; // S
|
|
||||||
grid[sinkX][4].type = GridCell::WIRE; grid[sinkX][4].rotation = 2; // S
|
|
||||||
grid[sinkX][5].type = GridCell::WIRE; grid[sinkX][5].rotation = 2; // S
|
|
||||||
|
|
||||||
grid[sinkX][6].type = GridCell::WIRE; grid[sinkX][6].rotation = 2; // S
|
|
||||||
|
|
||||||
for(int y=7; y<sinkY; ++y) {
|
|
||||||
grid[sinkX][y].type = GridCell::WIRE; grid[sinkX][y].rotation = 2;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -238,14 +82,14 @@ float SynthEngine::_random() {
|
|||||||
|
|
||||||
float SynthEngine::processGridStep() {
|
float SynthEngine::processGridStep() {
|
||||||
// Double buffer for values to handle feedback loops gracefully (1-sample delay)
|
// Double buffer for values to handle feedback loops gracefully (1-sample delay)
|
||||||
static float next_values[GRID_W][GRID_H];
|
float next_values[5][8];
|
||||||
|
|
||||||
auto isConnected = [&](int tx, int ty, int from_x, int from_y) -> bool {
|
auto isConnected = [&](int tx, int ty, int from_x, int from_y) -> bool {
|
||||||
if (from_x < 0 || from_x >= GRID_W || from_y < 0 || from_y >= GRID_H) return false;
|
if (from_x < 0 || from_x >= 5 || from_y < 0 || from_y >= 8) return false;
|
||||||
GridCell& n = grid[from_x][from_y];
|
GridCell& n = grid[from_x][from_y];
|
||||||
|
|
||||||
bool connects = false;
|
bool connects = false;
|
||||||
if (n.type == GridCell::WIRE || n.type == GridCell::FIXED_OSCILLATOR || n.type == GridCell::INPUT_OSCILLATOR || n.type == GridCell::WAVETABLE || n.type == GridCell::NOISE || n.type == GridCell::LFO || n.type == GridCell::GATE_INPUT || n.type == GridCell::ADSR_ATTACK || n.type == GridCell::ADSR_DECAY || n.type == GridCell::ADSR_SUSTAIN || n.type == GridCell::ADSR_RELEASE || n.type == GridCell::LPF || n.type == GridCell::HPF || n.type == GridCell::VCA || n.type == GridCell::BITCRUSHER || n.type == GridCell::DISTORTION || n.type == GridCell::RECTIFIER || n.type == GridCell::PITCH_SHIFTER || n.type == GridCell::GLITCH || n.type == GridCell::OPERATOR || n.type == GridCell::DELAY || n.type == GridCell::REVERB) {
|
if (n.type == GridCell::WIRE || n.type == GridCell::FIXED_OSCILLATOR || n.type == GridCell::INPUT_OSCILLATOR || n.type == GridCell::WAVETABLE || n.type == GridCell::NOISE || n.type == GridCell::LFO || n.type == GridCell::GATE || n.type == GridCell::GATE_INPUT || n.type == GridCell::ADSR_ATTACK || n.type == GridCell::ADSR_DECAY || n.type == GridCell::ADSR_SUSTAIN || n.type == GridCell::ADSR_RELEASE || n.type == GridCell::LPF || n.type == GridCell::HPF || n.type == GridCell::VCA || n.type == GridCell::BITCRUSHER || n.type == GridCell::DISTORTION || n.type == GridCell::RECTIFIER || n.type == GridCell::PITCH_SHIFTER || n.type == GridCell::GLITCH || n.type == GridCell::OPERATOR || n.type == GridCell::DELAY || n.type == GridCell::REVERB) {
|
||||||
// Check rotation
|
// Check rotation
|
||||||
// 0:N (y-1), 1:E (x+1), 2:S (y+1), 3:W (x-1)
|
// 0:N (y-1), 1:E (x+1), 2:S (y+1), 3:W (x-1)
|
||||||
if (n.rotation == 0 && from_y - 1 == ty && from_x == tx) connects = true;
|
if (n.rotation == 0 && from_y - 1 == ty && from_x == tx) connects = true;
|
||||||
@ -327,8 +171,8 @@ float SynthEngine::processGridStep() {
|
|||||||
return hasSide ? gain : 1.0f;
|
return hasSide ? gain : 1.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
for (int x = 0; x < GRID_W; ++x) {
|
for (int x = 0; x < 5; ++x) {
|
||||||
for (int y = 0; y < GRID_H; ++y) {
|
for (int y = 0; y < 8; ++y) {
|
||||||
GridCell& c = grid[x][y];
|
GridCell& c = grid[x][y];
|
||||||
float val = 0.0f;
|
float val = 0.0f;
|
||||||
|
|
||||||
@ -435,7 +279,7 @@ float SynthEngine::processGridStep() {
|
|||||||
} else if (c.type == GridCell::FORK) {
|
} else if (c.type == GridCell::FORK) {
|
||||||
// Sum inputs from "Back" (Input direction)
|
// Sum inputs from "Back" (Input direction)
|
||||||
val = getInputFromTheBack(x, y, c);
|
val = getInputFromTheBack(x, y, c);
|
||||||
} else if (c.type == GridCell::GATE_INPUT) {
|
} else if (c.type == GridCell::GATE || c.type == GridCell::GATE_INPUT) {
|
||||||
// Outputs 1.0 when gate is open (key pressed), 0.0 otherwise
|
// Outputs 1.0 when gate is open (key pressed), 0.0 otherwise
|
||||||
val = _isGateOpen ? 1.0f : 0.0f;
|
val = _isGateOpen ? 1.0f : 0.0f;
|
||||||
} else if (c.type == GridCell::ADSR_ATTACK) {
|
} else if (c.type == GridCell::ADSR_ATTACK) {
|
||||||
@ -467,7 +311,7 @@ float SynthEngine::processGridStep() {
|
|||||||
} else if (c.type == GridCell::WIRE) {
|
} else if (c.type == GridCell::WIRE) {
|
||||||
// Sum inputs from all neighbors that point to me
|
// Sum inputs from all neighbors that point to me
|
||||||
float sum = getSummedInput(x, y, c);
|
float sum = getSummedInput(x, y, c);
|
||||||
val = sum;
|
val = sum * c.param; // Fading
|
||||||
} else if (c.type == GridCell::LPF) {
|
} else if (c.type == GridCell::LPF) {
|
||||||
// Input from Back
|
// Input from Back
|
||||||
float in = getInputFromTheBack(x, y, c);
|
float in = getInputFromTheBack(x, y, c);
|
||||||
@ -526,6 +370,50 @@ float SynthEngine::processGridStep() {
|
|||||||
// Mix between original and rectified based on param
|
// Mix between original and rectified based on param
|
||||||
float rect = fabsf(in);
|
float rect = fabsf(in);
|
||||||
val = in * (1.0f - c.param) + rect * c.param;
|
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) {
|
} else if (c.type == GridCell::GLITCH) {
|
||||||
float in = getInputFromTheBack(x, y, c);
|
float in = getInputFromTheBack(x, y, c);
|
||||||
// Param controls probability of glitch
|
// Param controls probability of glitch
|
||||||
@ -538,6 +426,55 @@ float SynthEngine::processGridStep() {
|
|||||||
} else {
|
} else {
|
||||||
val = in;
|
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) {
|
} else if (c.type == GridCell::OPERATOR || c.type == GridCell::SINK) {
|
||||||
// Gather inputs
|
// Gather inputs
|
||||||
float inputs[4];
|
float inputs[4];
|
||||||
@ -577,18 +514,18 @@ float SynthEngine::processGridStep() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update state
|
// Update state
|
||||||
for(int x=0; x < GRID_W; ++x) {
|
for(int x=0; x<5; ++x) {
|
||||||
for(int y=0; y < GRID_H; ++y) {
|
for(int y=0; y<8; ++y) {
|
||||||
grid[x][y].value = next_values[x][y];
|
grid[x][y].value = next_values[x][y];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return grid[GRID_W / 2][GRID_H - 1].value;
|
return grid[2][3].value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SynthEngine::process(int16_t* buffer, uint32_t numFrames) {
|
void SynthEngine::process(int16_t* buffer, uint32_t numFrames) {
|
||||||
// Lock grid mutex to prevent UI from changing grid structure mid-process
|
// Lock grid mutex to prevent UI from changing grid structure mid-process
|
||||||
SynthLockGuard<SynthMutex> lock(gridMutex);
|
std::lock_guard<std::mutex> lock(gridMutex);
|
||||||
|
|
||||||
for (uint32_t i = 0; i < numFrames; ++i) {
|
for (uint32_t i = 0; i < numFrames; ++i) {
|
||||||
// The grid is now the primary sound source.
|
// The grid is now the primary sound source.
|
||||||
|
|||||||
@ -2,33 +2,7 @@
|
|||||||
#define SYNTH_ENGINE_H
|
#define SYNTH_ENGINE_H
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#if defined(ARDUINO_ARCH_RP2040)
|
|
||||||
#include <pico/mutex.h>
|
|
||||||
class SynthMutex {
|
|
||||||
public:
|
|
||||||
SynthMutex() { mutex_init(&mtx); }
|
|
||||||
void lock() { mutex_enter_blocking(&mtx); }
|
|
||||||
void unlock() { mutex_exit(&mtx); }
|
|
||||||
private:
|
|
||||||
mutex_t mtx;
|
|
||||||
};
|
|
||||||
template <typename Mutex>
|
|
||||||
class SynthLockGuard {
|
|
||||||
public:
|
|
||||||
explicit SynthLockGuard(Mutex& m) : m_mutex(m) { m_mutex.lock(); }
|
|
||||||
~SynthLockGuard() { m_mutex.unlock(); }
|
|
||||||
SynthLockGuard(const SynthLockGuard&) = delete;
|
|
||||||
SynthLockGuard& operator=(const SynthLockGuard&) = delete;
|
|
||||||
private:
|
|
||||||
Mutex& m_mutex;
|
|
||||||
};
|
|
||||||
#else
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
using SynthMutex = std::mutex;
|
|
||||||
template <typename Mutex>
|
|
||||||
using SynthLockGuard = std::lock_guard<Mutex>;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class SynthEngine
|
* @class SynthEngine
|
||||||
@ -96,7 +70,7 @@ public:
|
|||||||
|
|
||||||
// --- Grid Synth ---
|
// --- Grid Synth ---
|
||||||
struct GridCell {
|
struct GridCell {
|
||||||
enum Type { EMPTY, FIXED_OSCILLATOR, INPUT_OSCILLATOR, WAVETABLE, NOISE, LFO, GATE_INPUT, ADSR_ATTACK, ADSR_DECAY, ADSR_SUSTAIN, ADSR_RELEASE, FORK, WIRE, LPF, HPF, VCA, BITCRUSHER, DISTORTION, RECTIFIER, PITCH_SHIFTER, GLITCH, OPERATOR, DELAY, REVERB, SINK };
|
enum Type { EMPTY, FIXED_OSCILLATOR, INPUT_OSCILLATOR, WAVETABLE, NOISE, LFO, GATE, GATE_INPUT, ADSR_ATTACK, ADSR_DECAY, ADSR_SUSTAIN, ADSR_RELEASE, FORK, WIRE, LPF, HPF, VCA, BITCRUSHER, DISTORTION, RECTIFIER, PITCH_SHIFTER, GLITCH, OPERATOR, DELAY, REVERB, SINK };
|
||||||
enum Op { OP_ADD, OP_MUL, OP_SUB, OP_DIV, OP_MIN, OP_MAX };
|
enum Op { OP_ADD, OP_MUL, OP_SUB, OP_DIV, OP_MIN, OP_MAX };
|
||||||
|
|
||||||
Type type = EMPTY;
|
Type type = EMPTY;
|
||||||
@ -104,19 +78,13 @@ public:
|
|||||||
int rotation = 0; // 0:N, 1:E, 2:S, 3:W (Output direction)
|
int rotation = 0; // 0:N, 1:E, 2:S, 3:W (Output direction)
|
||||||
float value = 0.0f; // Current output sample
|
float value = 0.0f; // Current output sample
|
||||||
float phase = 0.0f; // For Oscillator, Noise state
|
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;
|
GridCell grid[5][8];
|
||||||
static const int GRID_H = 12;
|
std::mutex gridMutex;
|
||||||
|
|
||||||
static const size_t SERIALIZED_GRID_SIZE = GRID_W * GRID_H * 3;
|
|
||||||
void exportGrid(uint8_t* buffer);
|
|
||||||
void importGrid(const uint8_t* buffer);
|
|
||||||
void loadPreset(int preset);
|
|
||||||
void clearGrid();
|
|
||||||
|
|
||||||
GridCell grid[GRID_W][GRID_H];
|
|
||||||
SynthMutex gridMutex;
|
|
||||||
|
|
||||||
// Helper to process one sample step of the grid
|
// Helper to process one sample step of the grid
|
||||||
float processGridStep();
|
float processGridStep();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user