Files

228 lines
5.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 01).
### `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.01.0
adsr.release_s = 0.5;
adsr.gate_on(); // key press
let mut env = [0.0f32; 256];
adsr.process(&mut env); // outputs 0.01.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<const BLOCK: usize>`
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<const BLOCK: usize>`
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
```