Add readme files for each of the crates
This commit is contained in:
227
crates/synth-core/README.md
Normal file
227
crates/synth-core/README.md
Normal file
@@ -0,0 +1,227 @@
|
||||
# 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<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
|
||||
```
|
||||
Reference in New Issue
Block a user