decipher.audio
Provenance-tracked audio loading and signal-processing primitives. All functions preserve the round-trip invariant.
decipher.audio.AudioSource @dataclass
class AudioSource:
path: Path
samples: np.ndarray # float32 mono
sr: int # current sample rate (Hz)
original_sr: int # what was on disk
original_channels: int
duration_s: float
source_url: str = ""
label: str = "" # optional dataset/class label The result of load_audio. samples is always
float32 mono at sr. Writing it back via write_wav yields a playable WAV — that is the round-trip invariant.
decipher.audio.Unit @dataclass
class Unit:
index: int
start_s: float # offset in parent waveform
end_s: float
source_samples: np.ndarray # the unit's audio, at sr
sr: int
peak_db: float = 0.0
@property
def duration_s(self) -> float A single acoustic unit — a listenable slice of a parent waveform.
Returned by detect_units.
decipher.audio.load_audio def load_audio(
path: Path | str,
source_url: str = "",
label: str = "",
) -> AudioSource Read a WAV/FLAC into an AudioSource. Stereo input is
downmixed to mono by mean. The original sample rate and channel count
are preserved on the dataclass.
decipher.audio.write_wav def write_wav(
path: Path | str,
audio: np.ndarray,
sr: int,
) -> None Write float audio to 16-bit PCM WAV with clipping protection (samples are scaled down if the peak exceeds 1.0).
decipher.audio.render_spectrogram def render_spectrogram(
audio: np.ndarray,
sr: int,
*,
out_path: Path | str,
lo_hz: float | None = None, # default: 20
hi_hz: float | None = None, # default: min(sr/2, 24000)
n_fft: int = 1024,
hop: int = 256,
title: str | None = None,
width_px: int = 800,
height_px: int = 300,
dpi: int = 120,
) -> Path Render a log-magnitude STFT spectrogram of audio to a PNG.
The lo_hz / hi_hz parameters bound the y-axis
only; the FFT itself is full-bandwidth.
decipher.audio.bandpass def bandpass(
audio: np.ndarray,
sr: int,
lo_hz: float,
hi_hz: float,
order: int = 4,
) -> np.ndarray Zero-phase Butterworth bandpass. Output remains float32 and writable
to a WAV via write_wav.
decipher.audio.resample_to def resample_to(
audio: np.ndarray,
sr_in: int,
sr_out: int,
) -> np.ndarray Resample with torchaudio's Kaiser-windowed sinc filter (the same
parameters HuBERT uses for preprocessing). Operates on raw arrays —
wrap the result in a new AudioSource if you want to
preserve provenance through the resample.
decipher.audio.detect_units def detect_units(
audio: np.ndarray,
sr: int,
*,
min_dur_s: float = 0.08,
max_dur_s: float = 8.0,
merge_gap_s: float = 0.05,
envelope: str = "hilbert", # or "rms"
smooth_ms: float = 25.0,
rms_win_ms: float = 25.0,
rms_hop_ms: float = 5.0,
threshold_db: float = 6.0,
noise_floor: str = "global", # or "local"
local_win_s: float = 20.0,
local_pctile: float = 20.0,
absolute_silence_db: float = -60.0,
) -> list[Unit] Energy-based unit detection. Pipeline: envelope → noise-floor estimation
→ threshold → merge nearby → duration filter → extract listenable Unit objects.
The input should already be bandpassed to the species' vocal range. See the segmentation-tuning guide for parameter choices.