foley.audio

Audio I/O and DSP primitives for foley.

The foundation every other foley layer decodes/encodes/transforms audio through. It commits to a small, explicit set of representations (report 09):

  • Working (in RAM, between DSP ops): a float32 NumPy array at 48 kHz — shape (frames,) mono or (frames, channels). This is the lingua franca of soundfile/librosa/soxr and exactly what CLAP expects.

  • Archive (bytes at rest): FLAC, source sample-rate and bit-depth preserved — lossless, ~40-60% smaller than WAV, self-describing.

  • Delivery / preview (derived): Opus by default (not implemented in the foundation — an FFmpeg/foley[ffmpeg] concern surfaced later).

Design rules honoured here:

  • Zero-dep import. This module imports only the stdlib at top level; the heavy libraries (numpy, soundfile, soxr, librosa, pyloudnorm) are lazy-imported inside the functions that need them, so import foley.audio always succeeds on a bare install and only the function you call pays for (and requires) its dependency.

  • No magic numbers. Every default (sample rate, dtype, archive subtype, resample quality, trim/fade/loudness targets) is a named module-level constant used as a keyword-only argument default.

  • Never bundle FFmpeg / pydub / torchaudio. MP3/AAC/Opus transcode is an optional external-tool concern, not part of this royalty-free core.

All ops assume the working representation (float array, time on axis 0). load is the one function that produces it from arbitrary sources (path, raw bytes, or a file-like object).

foley.audio.AudioSource

a filesystem path, raw encoded bytes, or an already-open binary file-like object (e.g. io.BytesIO).

Type:

A source load can decode

alias of str | PathLike | bytes | BinaryIO

foley.audio.encode(samples: ndarray, sample_rate: int, *, fmt: str = 'flac', subtype: str = 'PCM_24') bytes[source]

Encode samples fully in memory and return the container bytes.

This is the producer for the content-addressed byte store: default output is the FLAC archive form.

Parameters:
  • samples – The working array to encode.

  • sample_rate – Sample rate in Hz.

  • fmt – Container/codec name (case-insensitive).

  • subtype – Sample subtype (e.g. PCM_24).

Returns:

The encoded audio as bytes.

Lazy dependency: soundfile.

foley.audio.ensure_channels(samples: ndarray, *, channels: int) ndarray[source]

Coerce samples to exactly channels channels.

Mappings: mono -> N by duplication; N -> mono by mean; N -> M (N != M, both > 1) by collapsing to mono then tiling up to M.

Parameters:
  • samples – Mono or multichannel working array.

  • channels – Target channel count (must be >= 1).

Returns:

A (frames,) array when channels == 1, else a (frames, channels) array.

Raises:

ValueError – If channels < 1.

Lazy dependency: numpy (only for the up-mix / tile path).

foley.audio.fade(samples: ndarray, sample_rate: int, *, fade_in_s: float = 0.01, fade_out_s: float = 0.01, kind: str = 'linear') ndarray[source]

Apply in/out gain ramps to samples (a short declick by default).

Ramp lengths are clamped to at most len(samples) // 2 so the in- and out-ramps never overlap on tiny inputs.

Parameters:
  • samples – Working array (mono or multichannel).

  • sample_rate – Sample rate in Hz (converts the fade durations to samples).

  • fade_in_s – Fade-in duration in seconds.

  • fade_out_s – Fade-out duration in seconds.

  • kind'linear' or 'equal_power' ramp shape.

Returns:

A new array with the fade envelope applied (input is not mutated).

Lazy dependency: numpy.

foley.audio.load(src: AudioSource, *, target_sr: int | None = None, mono: bool = False, dtype: str = 'float32') tuple['ndarray', int][source]

Decode audio into a float working array.

src may be a filesystem path, raw encoded bytes (wrapped in a BytesIO so nothing touches disk), or any binary file-like object.

Parameters:
  • src – Path, raw bytes, or file-like object to decode.

  • target_sr – If given, resample the decoded audio to this rate (via resample()); otherwise the native rate is returned.

  • mono – If True, down-mix multichannel audio to mono.

  • dtype – NumPy dtype string for the returned array (default float32).

Returns:

A (samples, sample_rate) tuple. samples has shape (frames,) (mono) or (frames, channels); sample_rate reflects any resample.

Lazy dependencies: soundfile (and soxr when target_sr differs).

foley.audio.loudness_normalize(samples: ndarray, sample_rate: int, *, target_lufs: float = -16.0, peak_ceiling_dbfs: float = -1.0, min_block_s: float = 0.4) tuple['ndarray', float][source]

Loudness-normalize to target_lufs, then keep it peak-safe.

Integrated loudness is measured (ITU-R BS.1770-4 / EBU R128), the signal is scaled to target_lufs, and finally attenuated so its sample peak sits at or below peak_ceiling_dbfs. Two inputs are returned unchanged (flag them, don’t amplify): near-silent input (measured loudness at or below LUFS_GATE_FLOOR), and a clip shorter than one BS.1770 gating block (min_block_s) — which pyloudnorm cannot measure and would otherwise raise ValueError on (routine for one-shots: clicks, blips, gunshots).

Parameters:
  • samples – Working array (mono or multichannel, time on axis 0 — the layout pyloudnorm expects).

  • sample_rate – Sample rate in Hz.

  • target_lufs – Desired integrated loudness (default = foley’s podcast target).

  • peak_ceiling_dbfs – Sample-peak ceiling (dBFS) applied after loudness normalization. Note this is a sample-peak limit, not an inter-sample true-peak (dBTP) limit — see foley.qc.true_peak_dbtp() for the oversampled measurement.

  • min_block_s – Minimum clip length (seconds) that can be loudness-measured; shorter clips are returned unchanged with measured = -inf.

Returns:

(normalized, measured_input_lufs). When the input is near-silent or too short to measure, normalized is the unchanged input and measured_input_lufs is at or below LUFS_GATE_FLOOR (-inf for the too-short case).

Lazy dependencies: pyloudnorm (+ numpy).

foley.audio.resample(samples: ndarray, sample_rate: int, *, target_sr: int = 48000, quality: str = 'HQ') ndarray[source]

Resample samples to target_sr (a no-op when already there).

Parameters:
  • samples – Working array (mono (frames,) or (frames, channels)).

  • sample_rate – The array’s current rate in Hz.

  • target_sr – Desired output rate in Hz (default = the working rate).

  • quality – soxr quality preset (QQ/LQ/MQ/HQ/VHQ).

Returns:

The resampled array (the input unchanged when sample_rate == target_sr). dtype is preserved by soxr.

Lazy dependency: soxr.

foley.audio.save(samples: ndarray, sample_rate: int, dst: str | os.PathLike | BinaryIO, *, fmt: str = 'flac', subtype: str = 'PCM_24') None[source]

Write samples to dst as fmt/subtype (default = FLAC archive).

Parameters:
  • samples – The working array to write (shape (frames,) or (frames, channels)).

  • sample_rate – Sample rate in Hz.

  • dst – Destination path or writable binary file-like object.

  • fmt – Container/codec name (case-insensitive; passed to libsndfile).

  • subtype – Sample subtype (e.g. PCM_24, PCM_16, FLOAT).

Lazy dependency: soundfile.

foley.audio.to_mono(samples: ndarray) ndarray[source]

Down-mix to mono by averaging channels; 1-D input passes through.

Parameters:

samples – Mono (frames,) or multichannel (frames, channels) array.

Returns:

A 1-D mono array (dtype preserved).

foley.audio.to_working(samples: ndarray, sample_rate: int, *, mono: bool = True, target_sr: int = 48000, dtype: str = 'float32') ndarray[source]

Produce the canonical CLAP/QC working array from an arbitrary clip.

Down-mixes (when mono), resamples to target_sr, and casts to dtype — the float32 @ 48 kHz mono array every embedder/tagger/QC check consumes.

Parameters:
  • samples – Decoded working array (mono or multichannel).

  • sample_rate – The array’s current rate in Hz.

  • mono – If True, down-mix to mono.

  • target_sr – Working sample rate in Hz.

  • dtype – Output NumPy dtype string.

Returns:

The canonical working array.

Lazy dependency: soxr (only when sample_rate != target_sr).

foley.audio.trim_silence(samples: ndarray, sample_rate: int, *, top_db: float = 30.0) tuple['ndarray', tuple[int, int]][source]

Strip leading/trailing silence, returning the clip and its kept span.

Silence detection runs on a transient mono down-mix (so librosa’s time-last convention never clashes with foley’s time-first (frames, channels) layout); the returned sample indices then slice the original array along axis 0, preserving its channel layout.

Parameters:
  • samples – Working array (mono or multichannel).

  • sample_rate – Sample rate in Hz (kept in the signature for API symmetry; trimming is index-based).

  • top_db – A frame is silent when it sits at least this many dB below the reference (peak) level.

Returns:

(trimmed, (start_sample, end_sample)). On all-silent (or otherwise degenerate) input the original array is returned unchanged with a full-length span (0, len(samples)).

Lazy dependency: librosa.