#ifndef SYNTH_ENGINE_H #define SYNTH_ENGINE_H #include #include #include #if defined(ARDUINO_ARCH_RP2040) #include class SynthMutex { public: SynthMutex() { mutex_init(&mtx); } void lock() { mutex_enter_blocking(&mtx); } void unlock() { mutex_exit(&mtx); } private: mutex_t mtx; }; template 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 using SynthMutex = std::mutex; template using SynthLockGuard = std::lock_guard; #endif // Fixed-point constants #define FP_SHIFT 15 #define FP_ONE (1 << FP_SHIFT) #define FP_HALF (1 << (FP_SHIFT - 1)) #define FP_MAX 32767 #define FP_MIN -32768 /** * @class SynthEngine * @brief A portable, platform-agnostic synthesizer engine. * * This class contains the core digital signal processing (DSP) logic. * It has no dependencies on any specific hardware, OS, or audio API. * It works by filling a provided buffer with 16-bit signed audio samples. * * The oscillator uses a 32-bit unsigned integer as a phase accumulator, * which is highly efficient and avoids floating-point math in the audio loop, * making it ideal for the RP2040. */ class SynthEngine { public: enum Waveform { SAWTOOTH, SQUARE, SINE }; /** * @brief Constructs the synthesizer engine. * @param sampleRate The audio sample rate in Hz (e.g., 44100). */ ~SynthEngine(); SynthEngine(uint32_t sampleRate); /** * @brief Fills a buffer with audio samples. This is the main audio callback. * @param buffer Pointer to the output buffer to be filled. * @param numFrames The number of audio frames (samples) to generate. */ void process(int16_t* buffer, uint32_t numFrames); /** * @brief Sets the frequency of the oscillator. * @param freq The frequency in Hz. */ void setFrequency(float freq); /** * @brief Sets the output volume. * @param vol Volume from 0.0 (silent) to 1.0 (full). */ void setVolume(float vol); /** * @brief Sets the oscillator's waveform. * @param form The waveform to use. */ void setWaveform(Waveform form); /** * @brief Opens or closes the sound gate. * @param isOpen True to produce sound, false for silence. */ void setGate(bool isOpen); /** * @brief Gets the current frequency of the oscillator. * @return The frequency in Hz. */ float getFrequency() const; // --- Grid Synth --- 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 Op { OP_ADD, OP_MUL, OP_SUB, OP_DIV, OP_MIN, OP_MAX }; Type type = EMPTY; int32_t param = FP_HALF; // 0.0 to 1.0 -> 0 to 32768 int rotation = 0; // 0:N, 1:E, 2:S, 3:W (Output direction) int32_t value = 0; // Current output sample (Q15) int32_t next_value = 0; // For double-buffering int32_t phase = 0; // For Oscillator, Noise state, Filter state uint32_t phase_accumulator = 0; // For Oscillators (Fixed point optimization) }; static const int GRID_W = 12; static const int GRID_H = 12; static const size_t MAX_SERIALIZED_GRID_SIZE = 1024; size_t exportGrid(uint8_t* buffer); int importGrid(const uint8_t* buffer, size_t size); void loadPreset(int preset); void rebuildProcessingOrder(); void updateGraph(); void clearGrid(); GridCell grid[GRID_W][GRID_H]; SynthMutex gridMutex; // Helper to process one sample step of the grid int32_t processGridStep(); private: uint32_t _sampleRate; uint32_t _phase; // Phase accumulator for the oscillator. uint32_t _increment; // Phase increment per sample, determines frequency. float _volume; Waveform _waveform; float _freqToPhaseInc; // Pre-calculated constant for frequency to phase increment conversion bool _isGateOpen; uint32_t _rngState; std::vector> _processing_order; void rebuildProcessingOrder_locked(); bool isConnected(int tx, int ty, int from_x, int from_y); int32_t getInput(int tx, int ty, int from_x, int from_y, int depth = 0); int32_t getSummedInput(int x, int y, GridCell& c, int depth = 0); // Internal random number generator int32_t _random(); }; #endif // SYNTH_ENGINE_H