Guide — The round-trip invariant

The single rule the toolkit is built around.

The rule

At every stage of processing — raw, bandpassed, segmented, clustered, embedded — there must be a way to get back to playable audio.

Why it matters

Acoustic-communication analysis routinely produces strange-looking results: a "novel" cluster, an unusual transition, a perplexity spike. Most of the time, when you listen to the underlying clip, the cause is obvious — a single contaminating event, a microphone artefact, a clipped peak. If you can listen in two minutes, you debug in two minutes. If the clustering and the audio have drifted apart, you debug for a day.

How it shows up in the API

  • load_audio returns an AudioSource whose samples are immediately writable to disk.
  • bandpass and resample_to return raw float32 arrays that write_wav consumes directly.
  • detect_units returns Unit objects each holding their own source_samples — a unit is listenable on its own.
  • cluster_features labels feature rows; the contract is that the caller keeps the parallel units list, so units[i] is the audio for labels[i].
  • SymbolStream bundles the symbols and units together so the round-trip is a constructor invariant.

The invariant in action

Four stages of the same humpback clip — raw, bandpassed, one detected unit, and a cluster exemplar. Every stage is a WAV you can play.

Stage 0 — raw (30 s)
Stage 0 — raw (30 s)
Output of fetch(paper_id='001_payne_mcvay_1971', ...)[0]. 44100 Hz 30 s
Stage 1 — bandpassed 150–4000 Hz
Stage 1 — bandpassed 150–4000 Hz
After bandpass(). Still perfectly listenable. 44100 Hz 30 s
Stage 2 — one detected unit
Stage 2 — one detected unit
First unit from detect_units — each Unit carries its own source_samples. 44100 Hz 0.375 s
Stage 3 — cluster exemplar
A representative unit for cluster 0 after cluster_features — the label never leaves the audio behind.

What the contract excludes

You will never find a primitive that returns a feature matrix without a parallel structure that lets you recover the audio. If you find one, that is a bug.