Files
analogue_synth/crates/synth-visualiser

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 modelSynthParams struct 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 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>.

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