Files
analogue_synth/crates/synth-visualiser/README.md

233 lines
6.8 KiB
Markdown
Raw 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-visualiser
A browser-based synthesizer front-end compiled to WebAssembly. Provides real-time oscilloscope and spectrum analysis via the Web Audio API, a drag-and-drop patch-bay for signal routing, and a parameter model that can be posted to an `AudioWorkletNode`.
## Features
- **Oscilloscope** — time-domain waveform display using `AnalyserNode.getFloatTimeDomainData()`
- **Spectrum analyser** — frequency-magnitude bar chart using `AnalyserNode.getByteFrequencyData()`
- **Patch bay** — canvas-based jack-and-cable UI for routing signal connections
- **Parameter model** — `SynthParams` struct that serialises to JSON for worklet messaging
## Building
### Prerequisites
```bash
cargo install wasm-pack
```
### Build
```bash
wasm-pack build crates/synth-visualiser --target web --out-dir ../../www/pkg
```
This produces:
```
www/pkg/
synth_visualiser.wasm # compiled WASM module
synth_visualiser.js # JS glue / bindings
synth_visualiser.d.ts # TypeScript type declarations
package.json
```
### Serve
```bash
# Any static HTTP server works; WASM requires a server (not file://)
npx serve www/
# or
python3 -m http.server --directory www/
```
## API
All public types are exported via `wasm-bindgen` and available as ES module exports.
### `AudioEngine`
Wraps an `AudioContext`, `AnalyserNode`, and `GainNode`.
```js
import { AudioEngine } from './pkg/synth_visualiser.js';
const engine = new AudioEngine();
// AudioContext is created suspended; resume after a user gesture:
engine.audio_context().resume();
const analyser = engine.analyser_node();
const sr = engine.sample_rate(); // 44100
```
The signal chain is: `GainNode → AnalyserNode → AudioContext.destination`.
FFT size: 2048. Smoothing time constant: 0.8.
### `OscilloscopeView`
Renders the time-domain waveform onto a `<canvas>`.
```js
import { OscilloscopeView } from './pkg/synth_visualiser.js';
const scope = new OscilloscopeView('scope-canvas', analyser);
function frame() {
scope.draw();
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
```
`draw()` fetches 1024 float samples from the analyser and draws a cyan line on a dark background.
### `SpectrumView`
Renders the frequency-magnitude bar chart onto a `<canvas>`.
```js
import { SpectrumView } from './pkg/synth_visualiser.js';
const spectrum = new SpectrumView('spectrum-canvas', analyser);
function frame() {
spectrum.draw();
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
```
`draw()` fetches 1024 frequency bins (byte magnitudes 0255) and draws colour-coded bars, graduating from cyan at low frequencies to magenta at high frequencies.
### `PatchBay`
Interactive cable-routing UI on a `<canvas>`.
```js
import { PatchBay } from './pkg/synth_visualiser.js';
const patchbay = new PatchBay('patchbay-canvas');
// Register jacks: (module_id, jack_id, x, y, is_output)
patchbay.register_jack('vco', 'out', 60, 80, true);
patchbay.register_jack('filter', 'in', 200, 80, false);
patchbay.register_jack('lfo', 'out', 60, 200, true);
patchbay.register_jack('filter', 'cv', 200, 200, false);
// Forward pointer events from the canvas element
const canvas = document.getElementById('patchbay-canvas');
canvas.addEventListener('pointerdown', e => patchbay.on_pointer_down(e.offsetX, e.offsetY));
canvas.addEventListener('pointermove', e => patchbay.on_pointer_move(e.offsetX, e.offsetY));
canvas.addEventListener('pointerup', e => patchbay.on_pointer_up(e.offsetX, e.offsetY));
function frame() {
patchbay.draw();
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
```
- Output jacks are drawn in **cyan**, input jacks in **pink**.
- Drag from an output jack to an input jack to connect a cable.
- Cables are drawn as quadratic Bézier curves.
- Only output → input connections are valid; the drag is reversed automatically if needed.
### `SynthParams`
Parameter object that mirrors the embedded firmware's `SynthParams`.
```js
import { SynthParams } from './pkg/synth_visualiser.js';
const params = new SynthParams();
params.osc_freq = 440.0;
params.osc_wave = 1; // 0=Sine 1=Saw 2=Square 3=Triangle
params.filter_cutoff = 2000.0;
params.filter_res = 0.5;
params.env_attack = 0.01;
params.env_decay = 0.2;
params.env_sustain = 0.7;
params.env_release = 0.5;
params.lfo_rate = 5.0;
params.lfo_depth = 0.3;
params.master_gain = 0.8;
const json = params.to_json(); // → JSON string for AudioWorklet postMessage
```
## Complete Example
```html
<!DOCTYPE html>
<html>
<body>
<canvas id="scope" width="600" height="200"></canvas>
<canvas id="spectrum" width="600" height="200"></canvas>
<canvas id="patchbay" width="600" height="400"></canvas>
<script type="module">
import init, {
AudioEngine, OscilloscopeView, SpectrumView, PatchBay
} from './pkg/synth_visualiser.js';
await init();
const engine = new AudioEngine();
const analyser = engine.analyser_node();
const scope = new OscilloscopeView('scope', analyser);
const spectrum = new SpectrumView('spectrum', analyser);
const patchbay = new PatchBay('patchbay');
patchbay.register_jack('vco', 'out', 50, 80, true);
patchbay.register_jack('filter', 'in', 200, 80, false);
const pb = document.getElementById('patchbay');
pb.addEventListener('pointerdown', e => patchbay.on_pointer_down(e.offsetX, e.offsetY));
pb.addEventListener('pointermove', e => patchbay.on_pointer_move(e.offsetX, e.offsetY));
pb.addEventListener('pointerup', e => patchbay.on_pointer_up(e.offsetX, e.offsetY));
document.addEventListener('click', () => engine.audio_context().resume(), { once: true });
(function frame() {
scope.draw();
spectrum.draw();
patchbay.draw();
requestAnimationFrame(frame);
})();
</script>
</body>
</html>
```
## Project Structure
```
src/
lib.rs — wasm-bindgen entry, feature flags
engine.rs — AudioEngine (AudioContext + AnalyserNode + GainNode)
oscilloscope.rs — OscilloscopeView
spectrum.rs — SpectrumView
patchbay.rs — PatchBay (jacks, cables, hit-testing, drag)
params.rs — SynthParams (mirrors embedded firmware params)
```
## Dependencies
| Crate | Purpose |
|-------|---------|
| `wasm-bindgen` | Rust ↔ JavaScript bindings |
| `wasm-bindgen-futures` | `async/await` and `Promise` interop |
| `js-sys` | Raw JavaScript API access |
| `web-sys` | WebAPI bindings (AudioContext, Canvas 2D, Pointer events) |
| `serde` | Derive macros for JSON serialization |
| `console_error_panic_hook` | Forward Rust panics to the browser console |
| `synth-core` | Shared DSP types (e.g., `Waveform`) |
## Features
| Feature | Default | Description |
|---------|---------|-------------|
| `console-panic` | ✓ | Install `console_error_panic_hook` for readable panic messages |