Add readme files for each of the crates
This commit is contained in:
232
crates/synth-visualiser/README.md
Normal file
232
crates/synth-visualiser/README.md
Normal 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 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>`.
|
||||
|
||||
```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 |
|
||||
Reference in New Issue
Block a user