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 —
SynthParamsstruct that serialises to JSON for worklet messaging
Building
Prerequisites
cargo install wasm-pack
Build
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
# 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.
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>.
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>.
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 0–255) and draws colour-coded bars, graduating from cyan at low frequencies to magenta at high frequencies.
PatchBay
Interactive cable-routing UI on a <canvas>.
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.
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
<!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 |