Add readme files for each of the crates

This commit is contained in:
2026-03-25 16:46:09 +00:00
parent 496b6bdc71
commit c3cb7aa84b
3 changed files with 587 additions and 0 deletions

View File

@@ -0,0 +1,232 @@
# 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 |