8.9 KiB
AUDIO-WORKBENCH.md — Sound Shaping & Debug Interface
Problem
Live packets arrive randomly and animate too fast to understand what's happening musically. You hear sound, but can't connect it to what the data is doing — which bytes become which notes, why this packet sounds different from that one.
Milestone 1: Packet Jukebox
A standalone page (#/audio-lab) that lets you trigger packets manually and understand the data→sound mapping.
Packet Buckets
Pre-load representative packets from the database, bucketed by type:
| Type ID | Name | Typical Size | Notes |
|---|---|---|---|
| 0x04 | ADVERT | 109-177 bytes | Node advertisements, most musical (long payload) |
| 0x05 | GRP_TXT | 18-173 bytes | Group messages, wide size range |
| 0x01 | TXT_MSG | 22-118 bytes | Direct messages |
| 0x02 | ACK/REQ | 22-57 bytes | Short acknowledgments |
| 0x09 | TRACE | 11-13 bytes | Very short, sparse |
| 0x00 | RAW | 22-33 bytes | Raw packets |
For each type, pull 5-10 representative packets spanning the size range (smallest, median, largest) and observation count range (1 obs, 10+ obs, 50+ obs).
API
New endpoint: GET /api/audio-lab/buckets
Returns pre-selected packets grouped by type with decoded data and raw_hex. Server picks representatives so the client doesn't need to sift through hundreds.
UI Layout
┌─────────────────────────────────────────────────────┐
│ 🎵 Audio Lab │
├──────────┬──────────────────────────────────────────┤
│ │ │
│ ADVERT │ [▶ Play] [🔁 Loop] [⏱ Slow 0.5x] │
│ ▸ #1 │ │
│ ▸ #2 │ ┌─ Packet Data ──────────────────────┐ │
│ ▸ #3 │ │ Type: ADVERT │ │
│ │ │ Size: 141 bytes (payload: 138) │ │
│ GRP_TXT │ │ Hops: 3 Observations: 12 │ │
│ ▸ #1 │ │ Raw: 04 8b 33 87 e9 c5 cd ea ... │ │
│ ▸ #2 │ └────────────────────────────────────┘ │
│ │ │
│ TXT_MSG │ ┌─ Sound Mapping ────────────────────┐ │
│ ▸ #1 │ │ Instrument: Bell (triangle) │ │
│ │ │ Scale: C major pentatonic │ │
│ TRACE │ │ Notes: 12 (√138 ≈ 11.7) │ │
│ ▸ #1 │ │ Filter: 4200 Hz (3 hops) │ │
│ │ │ Volume: 0.48 (12 obs) │ │
│ │ │ Voices: 4 (12 obs, capped) │ │
│ │ │ Pan: -0.3 (lon: -105.2) │ │
│ │ └────────────────────────────────────┘ │
│ │ │
│ │ ┌─ Note Sequence ────────────────────┐ │
│ │ │ #1: byte 0x8B → C4 (880Hz) 310ms │ │
│ │ │ gap: 82ms (Δ=0x58) │ │
│ │ │ #2: byte 0x33 → G3 (392Hz) 120ms │ │
│ │ │ gap: 210ms (Δ=0xB4) │ │
│ │ │ ... │ │
│ │ └────────────────────────────────────┘ │
│ │ │
│ │ ┌─ Byte Visualizer ──────────────────┐ │
│ │ │ ████░░██████░░░████████░░██░░░░████ │ │
│ │ │ ↑ ↑ ↑ ↑ │ │
│ │ │ sampled bytes highlighted in payload │ │
│ │ └────────────────────────────────────┘ │
├──────────┴──────────────────────────────────────────┤
│ BPM [====●========] 120 Vol [==●===========] 30 │
│ Voice: [constellation ▾] │
└─────────────────────────────────────────────────────┘
Key Features
- Play button — triggers
sonifyPacket()with the selected packet - Loop — retrigger every N seconds (configurable)
- Slow mode — 0.25x / 0.5x / 1x / 2x tempo override (separate from BPM, multiplies it)
- Note sequence breakdown — shows every sampled byte, its MIDI note, frequency, duration, gap to next. Highlights each note in real-time as it plays.
- Byte visualizer — hex dump of payload with sampled bytes highlighted. Shows which bytes the voice module chose and what they became.
- Sound mapping panel — shows computed parameters (instrument, scale, filter, pan, volume, voice count) so you can see exactly why it sounds the way it does.
Playback Highlighting
As each note plays, highlight:
- The corresponding byte in the hex dump
- The note row in the sequence table
- A playhead marker on the byte visualizer bar
This connects the visual and auditory — you SEE which byte is playing RIGHT NOW.
Milestone 2: Parameter Overrides
Once you can hear individual packets clearly, add override sliders to shape the sound:
Envelope & Tone
- Oscillator type — sine / triangle / square / sawtooth
- ADSR sliders — attack, decay, sustain, release (with real-time envelope visualizer curve)
- Scale override — force any scale regardless of packet type (C maj pent, A min pent, E nat minor, D whole tone, chromatic, etc.)
- Root note — base MIDI note for the scale
Spatial & Filter
- Filter type — lowpass / highpass / bandpass
- Filter cutoff — manual override of hop-based cutoff (Hz slider + "data-driven" toggle)
- Filter Q/resonance — 0.1 to 20
- Pan lock — force stereo position (-1 to +1)
Voicing & Dynamics
- Voice count — force 1-8 voices regardless of observation count
- Detune spread — cents per voice (0-50)
- Volume — manual override of observation-based volume
- Limiter threshold — per-packet compressor threshold (dB)
- Limiter ratio — 1:1 to 20:1
Note Timing
- Note duration range — min/max duration mapped from byte value
- Note gap range — min/max gap mapped from byte delta
- Lookahead — scheduling buffer (ms)
Each override has a "lock 🔒" toggle — locked = your value, unlocked = data-driven. Unlocked shows the computed value in real-time so you can see what the data would produce.
The voice module's play() accepts an overrides object from the workbench. Locked parameters override computed values; unlocked ones pass through.
Milestone 3: A/B Voice Comparison
- Split-screen: two voice modules side by side
- Same packet, different voices
- "Play Both" button with configurable delay between them
- Good for iterating on v2/v3 voices against v1 constellation
Milestone 4: Sequence Editor
- Drag packets into a timeline to create a sequence
- Adjust timing between packets manually
- Play the sequence as a composition
- Export as audio (MediaRecorder API → WAV/WebM)
- Useful for demoing "this is what the mesh sounds like" without waiting for live traffic
Milestone 5: Live Annotation Mode
- Toggle on live map that shows the sound mapping panel for each packet as it plays
- Small floating card near the animated path showing: type, notes, instrument
- Fades out after the notes finish
- Connects the live visualization with the audio in real-time
Architecture Notes
- Audio Lab is a new SPA page like packets/nodes/analytics
- Reuses existing
MeshAudio.sonifyPacket()and voice modules - Voice modules need a small extension:
play()should return aNoteSequenceobject describing what it will play, not just play it. This enables the visualizer. - Or: add a
describe(parsed, opts)method that returns the mapping without playing - BPM/volume/voice selection shared with live map via
MeshAudio.*
Implementation Order
- API endpoint for bucketed representative packets
- Basic page layout with packet list and play button
- Sound mapping panel (computed parameters display)
- Note sequence breakdown
- Playback highlighting
- Byte visualizer
- Override sliders (M2)