# The sound of quack
quack's flock is the authority on behavior: `src/pond.ts` decides who calls,
who answers, who dabbles and who gets the bread. The sound is the vendored
[duck-pond](https://github.com/celphase/duck-pond) Rust core, compiled to
`public/quack_audio.wasm`, running in a Web Worker and played through an
AudioWorklet. It is the same synthesis, distance attenuation, speed-of-sound
delay, head shading and Freeverb as the original pond; only the birds are ours.
The bridge lives in `src/audio.ts`; the browser half is
`public/quack-audio-worker.js` and `public/quack-audio-worklet.js`.
## What makes which sound
`duck-pond/sounds.md` is the exhaustive sound reference. This is the mapping
from those sounds to what quack's ducks actually do.
| Rust sound | Trigger in quack | Where |
| --- | --- | --- |
| `quack` | a hen's single call | `Pond.onCall` → `AudioCore.call` |
| `quack quack` | a hen's 2–3 note series, the social answer chain's start | same |
| `QUACK quack quack` | a hen's 5–7 note decrescendo, also social | same |
| `wak-wak-wak` | a hen feeding (or a dabble lottery) chats to herself | same |
| `kwek-kwek` | a hen gathering her brood, or calling lost young back | same |
| `rhaeb` | a drake's 1–3 mutters, sooner when his mate is out of reach | same |
| `peep peep` | a duckling that has lost its mother, distress whistles | same |
| `pip-pip` | a duckling keeping in touch with the brood | same |
| dabbling | an adult starts a spell of dabbling on the water | `Duck.onDabble` |
| peck | a bill lands on a piece of bread; splash only when afloat | `Pond.onPeck` |
| pea plop | a thrown piece lands on open water | `Pond.onBreadLand` |
| pea tap | a thrown piece lands on the bank or the winter lid | `Pond.onBreadLand` |
| drip / bubble | random points around the listener, 3–46 m out | `Stage::ambient` |
| water bed | continuous, mixed without spatialisation | `Stage::render` |
| reverb | continuous, fed by every voice | `Stage::render` |
Two notes on the mapping:
- The Rust sound list has no entry for flight, begging, upending, drinking or
preening, so quack's new activities are silent except where they trip an
existing sound. A duck's bill on the ice makes the same pecks as a bill on
water, minus the splash; a dabble ashore (grazing) is silent.
- Peas became bread. The plop and tap keep the pea synthesis, scaled by
`AUDIO.bread` because a crust is much bigger than a pea and has to read
across the pond.
## How a call reaches the speaker
1. `Pond.call` builds a `CallEvent` — `kind`, `notes`, `duration` — and shows
the head/bill gesture. The call model (timers, lottery, answers, brood
recall) is quack's; the sound is not.
2. `AudioCore.call` converts the duck's world position into the listener's
frame: `+y` straight ahead, `+x` to the right, metres, from the camera's
planted eye. It sends the event with that place.
3. The worker sets the duck's position in the Rust `Stage` and calls
`quack_audio_call`, which picks the note generator from the `CallKind` and
the duck's voice:
- `quack`: the event's duration minus the synth's envelope tail;
- `series`, `decrescendo`, `brood`, `contact`: the note count;
- `chatter`, `peeps`: the event's duration minus the 0.2 s tail;
- `rhaeb`: the note count and the gap that fills the event.
4. The stage emits a `Voice` from the duck's place, with the Rust level for
that sound, and the block render applies delay, attenuation, air/head
filtering, panning and reverb.
5. Rendered blocks (512 frames, planar stereo) go to the worklet, which keeps
`AUDIO.queue` blocks (8 — about 85 ms) ahead of playback. A call that
arrives while the queue is full is picked up by the next block rendered, so
the sound trails the gesture by roughly that queue.
## Controls and plumbing
- Sound starts on the first press or key, as browser autoplay rules require.
- `?audio=0` builds no audio core at all; the pond is silent and `Pond.onCall`
is left free for probes. `?headphones=1` (or `H`) switches the stage to the
spherical-head render.
- `space` suspends the AudioContext with the simulation; resuming continues
the same queued blocks. `H` toggles headphones; the choice is remembered in
`localStorage`.
- `AUDIO` in `src/config.ts` holds the queue depth, position-sync period,
output gain and the bread plop/tap gains. Everything else is the original
Rust constant.
- The stage exports: `quack_audio_new`, `quack_audio_set_voice`,
`quack_audio_set_pos`, `quack_audio_set_headphones`, `quack_audio_call`,
`quack_audio_dabble`, `quack_audio_peck`, `quack_audio_fixed`,
`quack_audio_render`, `quack_audio_block`, `quack_audio_ducks`.
## Rebuilding the wasm
The vendored clone is kept out of this repo's history. The small API the
browser needs — `Stage` in `src/scene.rs` and the `quack_audio_*` exports in
`web/src/lib.rs` — is stored as a patch:
```sh
git -C duck-pond apply audio/duck-pond-stage.patch
npm run audio # cargo build --release --target wasm32-unknown-unknown
# then copies to public/quack_audio.wasm
```
`npm run audio` checks that the patch has been applied and tells you if not.
`public/quack_audio.wasm` is committed, so a clone without the Rust clone can
still build and deploy the page. The stage is duck-pond's MIT-licensed code;
see the credit in the README.