6.3 KiB
Mesh Audio — Sonification Plan
Turn raw packet bytes into generative music.
What Every Packet Has (guaranteed)
raw_hex— melody sourcehop_count— note duration + filter cutoffobservation_count— volume + chord voicingpayload_type— instrument + scale + root keynode_lat/lon— stereo pantimestamp— arrival timing
Final Mapping
| Data | Musical Role |
|---|---|
| payload_type | Instrument + scale + root key |
| payload bytes (evenly sampled, sqrt(len) count) | Melody notes (pitch) |
| byte value | Note length (higher = longer sustain, lower = staccato) |
| byte-to-byte delta | Note spacing (big jump = longer gap, small = rapid) |
| hop_count | Low-pass filter cutoff (more hops = more muffled) |
| observation_count | Volume + chord voicing (more observers = louder + stacked detuned voices) |
| node longitude | Stereo pan (west = left, east = right) |
| BPM tempo (user control) | Master time multiplier on all durations |
Instruments & Scales by Type
| Type | Instrument | Scale | Root |
|---|---|---|---|
| ADVERT | Bell / pad | C major pentatonic | C |
| GRP_TXT | Marimba / pluck | A minor pentatonic | A |
| TXT_MSG | Piano | E natural minor | E |
| TRACE | Ethereal synth | D whole tone | D |
How a Packet Plays
- Header configures the voice — payload type selects instrument, scale, root key. Flags/transport codes select envelope shape. Header bytes are NOT played as notes.
- Sample payload bytes — pick
sqrt(payload_length)bytes, evenly spaced across payload:- 16-byte payload → 4 notes
- 36-byte payload → 6 notes
- 64-byte payload → 8 notes
- Each sampled byte → a note:
- Pitch: byte value (0-255) quantized to selected scale across 2-3 octaves
- Length: byte value maps to sustain duration (low byte = short staccato ~50ms, high byte = sustained ~400ms)
- Spacing: delta between current and next sampled byte determines gap to next note (small delta = rapid fire, large delta = pause). Scaled by BPM tempo multiplier.
- Filter: low-pass cutoff from hop_count — few hops = bright/clear, many hops = muffled (signal traveled far)
- Volume: observation_count — more observers = louder
- Chord voicing: if observations > 1, stack slightly detuned voices (±5-15 cents per voice, chorus effect)
- Pan: origin node longitude mapped to stereo field
- All timings scaled by BPM tempo control
UI Controls
- Audio toggle — on/off (next to Matrix / Rain)
- BPM tempo slider — master time multiplier (slow = ambient, fast = techno)
- Volume slider — master gain
- Mute button — pause audio without losing toggle state
Implementation
Library: Tone.js (~150KB)
Tone.Synth/Tone.PolySynthfor melody + chordsTone.Samplerfor realistic instrumentsTone.Filterfor hop-based cutoffTone.Chorusfor observation detuningTone.Pannerfor geographic stereoTone.Reverbfor spatial depth
Integration
animatePacket(pkt)also callssonifyPacket(pkt)- Optional "Sonify" button on packet detail page
- Web Audio runs on separate thread — won't block UI/animations
- Polyphony capped at 8-12 voices to prevent mudding
- Voice stealing when busy
Core Function
sonifyPacket(pkt):
1. Extract raw_hex → byte array
2. Separate header (first ~3 bytes) from payload
3. Header → select instrument, scale, root key, envelope
4. Sample sqrt(payload.length) bytes evenly across payload
5. For each sampled byte:
- pitch = quantize(byte, scale, rootKey)
- duration = map(byte, 50ms, 400ms) × tempoMultiplier
- gap to next = map(abs(nextByte - byte), 30ms, 300ms) × tempoMultiplier
6. Set filter cutoff from hop_count
7. Set gain from observation_count
8. Set pan from origin longitude
9. If observation_count > 1: detune +/- cents per voice
10. Schedule note sequence via Tone.js
Percussion Layer
Percussion fires instantly on packet arrival — gives you the rhythmic pulse while the melodic notes unfold underneath.
Drum Kit Mapping
| Packet Type | Drum Sound | Why |
|---|---|---|
| Any packet | Kick drum | Network heartbeat. Every arrival = one kick. Busier network = faster kicks. |
| ADVERT | Hi-hat | Most frequent, repetitive — the timekeeper tick. |
| GRP_TXT / TXT_MSG | Snare | Human-initiated messages are accent hits. |
| TRACE | Rim click | Sparse, searching — light metallic tick. |
| 8+ hops OR 10+ observations | Cymbal crash | Big network events get a crash. Rare = special. |
Sound Design (all synthesized, no samples)
Kick: Sine oscillator, frequency ramp 150Hz → 40Hz in ~50ms, short gain envelope.
Hi-hat: White noise through highpass filter (7-10kHz).
- Closed (1-2 hops): 30ms decay — tight tick
- Open (3+ hops): 150ms decay — sizzle
Snare: White noise burst (bandpass ~200-1000Hz) + sine tone body (~180Hz). Observation count scales intensity (more observers = louder crack, longer decay).
Rim click: Short sine pulse at ~800Hz with fast decay (20ms). Dry, metallic.
Cymbal crash: White noise through bandpass (3-8kHz), long decay (500ms-1s). Only triggers on exceptional packets.
Byte-Driven Variation
First payload byte mod 4 selects between variations of each percussion sound:
- Slightly different pitch (±10-20%)
- Different decay length
- Different filter frequency
Prevents machine-gun effect of identical repeated hits.
Timing
- Percussion: fires immediately on packet arrival (t=0)
- Melody: unfolds over 0.6-1.6s starting at t=0
- Result: rhythmic hit gives you the pulse, melody gives you the data underneath
The Full Experience
Matrix mode + Rain + Audio: green hex bytes flow across the map, columns of raw data rain down, and each packet plays its own unique melody derived from its actual bytes. Quiet periods are sparse atmospheric ambience; traffic bursts become dense polyrhythmic cascades. Crank the BPM for techno, slow it down for ambient.
Future Ideas
- "Record" button → export MIDI or WAV
- Per-type mute toggles (silence ADVERTs, only hear messages)
- "DJ mode" — crossfade between regions
- Historical playback at accelerated speed = mesh network symphony
- Presets (ambient, techno, classical, minimal)
- ADVERT ambient drone layer (single modulated oscillator, not per-packet)