# synth-core A `no_std` DSP library providing analogue-modelling synthesizer building blocks. Designed to compile for any target — embedded bare-metal (Cortex-M), WebAssembly, and native — with no heap allocation. ## Overview `synth-core` is the shared engine that powers both the RP2350 firmware (`synth-embedded`) and the browser visualiser (`synth-visualiser`). All modules operate on fixed-size blocks of `f32` samples using const-generic arrays, so every allocation happens on the stack at compile time. ``` VCO → Filter → VCA ← ADSR ↑ ↑ LFO LFO ``` ## Modules ### `oscillator` — Voltage-Controlled Oscillator Phase-accumulator oscillator with five waveforms. ```rust use synth_core::{config::SR_48000, oscillator::{Vco, Waveform}, AudioProcessor}; let mut vco = Vco::new(SR_48000, 440.0, Waveform::Saw); vco.waveform = Waveform::Square; vco.freq_hz = 880.0; let mut buf = [0.0f32; 256]; vco.process(&mut buf); // fills buf with one block of audio ``` **Waveforms:** `Sine`, `Saw`, `Square`, `Triangle`, `Pulse(f32)` (duty cycle 0–1). ### `filter` — State-Variable Filter Two-integrator SVF with selectable mode and voltage-controlled cutoff. ```rust use synth_core::{config::SR_48000, filter::{FilterMode, Svf}, AudioProcessor}; let mut filt = Svf::new(SR_48000, 2_000.0, 0.5, FilterMode::LowPass); filt.cutoff_hz = 1_000.0; filt.resonance = 0.8; // 0.0 = flat, 1.0 = self-oscillation filt.process(&mut buf); // in-place ``` **Modes:** `LowPass`, `HighPass`, `BandPass`, `Notch`. ### `envelope` — ADSR Envelope Generator Classic Attack / Decay / Sustain / Release envelope. ```rust use synth_core::{config::SR_48000, envelope::Adsr, AudioProcessor}; let mut adsr = Adsr::new(SR_48000); adsr.attack_s = 0.01; adsr.decay_s = 0.2; adsr.sustain = 0.7; // 0.0–1.0 adsr.release_s = 0.5; adsr.gate_on(); // key press let mut env = [0.0f32; 256]; adsr.process(&mut env); // outputs 0.0–1.0 amplitude envelope adsr.gate_off(); // key release ``` ### `vca` — Voltage-Controlled Amplifier Multiplies audio by a gain value, optionally driven by an envelope. ```rust use synth_core::{config::SR_48000, vca::Vca}; let mut vca = Vca::new(0.8); // fixed gain vca.apply_envelope(&mut audio, &env); // per-sample multiply by envelope ``` ### `lfo` — Low-Frequency Oscillator Same waveform engine as the VCO, tuned to sub-audio rates. ```rust use synth_core::{config::SR_48000, lfo::Lfo, oscillator::Waveform, AudioProcessor}; let mut lfo = Lfo::new(SR_48000); lfo.rate_hz = 5.0; lfo.depth = 0.5; lfo.waveform = Waveform::Sine; let mut cv = [0.0f32; 256]; lfo.process(&mut cv); // outputs ±depth CV ``` ### `midi` — MIDI Byte-Stream Parser Parses a raw MIDI byte stream (running status supported) into typed events. ```rust use synth_core::midi::{MidiParser, MidiEvent}; let mut parser = MidiParser::new(); // Feed bytes from UART / USB if let Some(event) = parser.push_byte(byte) { match event { MidiEvent::NoteOn { channel, note, velocity } => { /* ... */ } MidiEvent::NoteOff { channel, note, velocity } => { /* ... */ } MidiEvent::ControlChange { channel, controller, value } => { /* ... */ } MidiEvent::PitchBend { channel, value } => { /* ... */ } MidiEvent::ProgramChange { channel, program } => { /* ... */ } _ => {} } } ``` ### `patch` — Signal Routing / Patch Bay Fixed-capacity cable graph connecting named outputs to inputs, no heap required. ```rust use synth_core::patch::Patch; let mut patch: Patch<16> = Patch::new(); // up to 16 cables patch.connect("vco.out", "filter.in"); patch.connect("lfo.out", "filter.cv"); for cable in patch.cables() { println!("{} → {}", cable.src, cable.dst); } ``` ### `math` — Utility Functions ```rust use synth_core::math::{midi_note_to_hz, db_to_linear, linear_to_db, lerp}; let freq = midi_note_to_hz(69); // 440.0 Hz (A4) let gain = db_to_linear(-6.0); // ≈ 0.501 ``` ### `config` — Sample Rate ```rust use synth_core::config::{SampleRate, SR_44100, SR_48000}; let period = SR_48000.period(); // 1.0 / 48000.0 ``` ## Traits ### `AudioProcessor` Implemented by every DSP module. | Method | Description | |--------|-------------| | `process(&mut self, out: &mut [f32; BLOCK])` | Fill `out` with one block of samples | | `reset(&mut self)` | Reset all internal state (phase, integrators, …) | ### `CVProcessor` Extends `AudioProcessor` for modules that accept a control-voltage input. | Method | Description | |--------|-------------| | `set_cv(&mut self, cv: f32)` | Apply modulation (1 V/octave convention for pitch) | ## Complete Voice Example ```rust use synth_core::{ config::SR_48000, oscillator::{Vco, Waveform}, filter::{FilterMode, Svf}, envelope::Adsr, vca::Vca, AudioProcessor, }; const BLOCK: usize = 256; let sr = SR_48000; let mut vco = Vco::new(sr, 440.0, Waveform::Saw); let mut filt = Svf::new(sr, 2_000.0, 0.4, FilterMode::LowPass); let mut adsr = Adsr::new(sr); let mut vca = Vca::new(1.0); adsr.gate_on(); let mut audio = [0.0f32; BLOCK]; let mut env = [0.0f32; BLOCK]; loop { vco.process(&mut audio); adsr.process(&mut env); filt.process(&mut audio); vca.apply_envelope(&mut audio, &env); // send `audio` to output device } ``` ## Dependencies | Crate | Purpose | |-------|---------| | `libm` | `no_std`-compatible transcendental math (`sin`, `pow`, …) | | `micromath` | Fast numerical approximations | | `num-traits` | Numeric trait abstractions | | `heapless` | Fixed-size `Vec`/`ArrayVec` without allocation | | `midi-types` | MIDI type definitions | | `serde` | Optional serialization support | ## Building ```bash # Native (for tests) cargo test -p synth-core # WebAssembly cargo build -p synth-core --target wasm32-unknown-unknown # Embedded (Cortex-M33, RP2350) cargo build -p synth-core --target thumbv8m.main-none-eabihf ```