# 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 ``. ```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 ``. ```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 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 ``. ```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 ``` ## 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 |