Tutorial 2 — Detect acoustic units
Take a continuous recording, segment it into discrete listenable units.
Bandpass first
The detector assumes the input is already restricted to the species' vocal range. Otherwise the noise floor estimate is dominated by out-of-band energy.
from decipher import load_audio, bandpass
src = load_audio("data/raw/excerpt.wav")
bp = bandpass(src.samples, src.sr, lo_hz=40, hi_hz=4000, order=4)
Detect
from decipher import detect_units
units = detect_units(
bp, src.sr,
min_dur_s=0.08,
max_dur_s=8.0,
merge_gap_s=0.05,
envelope="hilbert", # or "rms" for long recordings with drifting noise
smooth_ms=25.0,
threshold_db=6.0,
noise_floor="global", # or "local" for sliding-window low-percentile
)
print(len(units), "units")
for u in units[:3]:
print(f" unit {u.index}: {u.start_s:.2f}–{u.end_s:.2f} s, peak={u.peak_db:.1f} dB")
threshold_db dB.Listen to the units
Each unit carries its own audio. Write them out and play them in order:
from decipher import write_wav
for u in units[:5]:
write_wav(f"audio_samples/02_units/unit_{u.index:04d}.wav",
u.source_samples, u.sr)
Older segmentation examples (different corpus)
Tune the parameters
| Parameter | What it does | If too low / too high |
|---|---|---|
threshold_db | How far above the noise floor a frame must rise. | Low: noise becomes units. High: quiet calls are missed. |
smooth_ms | Smoothing window applied to the envelope. | Low: rapid amplitude modulation fragments a single call. High: nearby calls fuse. |
merge_gap_s | Above-threshold runs separated by less than this gap are merged. | Low: clicks within a click-train become separate units. High: distinct calls fuse. |
min_dur_s / max_dur_s | Hard duration filter. | Drops candidates outside this window. |
noise_floor | "global" = constant median; "local" = sliding-window low-percentile. | Use "local" when background noise drifts across long recordings. |
min_dur_s and max_dur_s are set correctly.