foley.weave.mix#

Pure-numpy mixing DSP — making a clip sit under the voice (report 06 §3).

Deterministic, testable, shell-out-free primitives for the WEAVE mixer: level (gain staging), envelope-follower ducking, constant-power panning, a distance recipe (attenuation + air-absorption low-pass), a pure-numpy reverb send, equal-power crossfades / declicks, seamless loop/trim to a duration, and overlay.

Everything operates on the working representation (float32, time on axis 0); the mix graph is stereo (frames, 2) so panning composes — constant_power_pan() is the mono→stereo point. numpy is imported lazily inside each function so import foley.weave stays dol-only (mirroring foley.audio). Nothing here reaches for scipy/pyroomacoustics; those are optional-upgrade paths for a later slice behind foley[weave].

Module Attributes

LAYER_GAIN_DB

Base per-layer gain (dB, relative to the voice bus at 0 dB).

DISTANCE_MAX_ATTEN_DB

Attenuation (dB) applied at maximum distance (distance == 1.0); ~inverse-distance.

DISTANCE_MAX_LP

Air-absorption one-pole low-pass coefficient at maximum distance (0 = none, →1 = darkest).

REVERB_IR_S

Reverb-send impulse-response length (seconds) and decay time-constant.

Functions

apply_distance(clip, sample_rate, distance)

Apply a distance recipe: attenuation + air-absorption low-pass (report 06 §3.4).

constant_power_pan(clip, pan)

Pan a (mono or stereo) clip to a stereo image with a constant-power law (report 06 §3.3).

convolution_reverb(clip, sample_rate, ir, *, ...)

Convolution reverb with a real (recorded or synthesised) impulse response (report 06 §3.5).

db_to_lin(db)

Convert decibels to a linear amplitude factor (10 ** (db/20)).

declick(clip, sample_rate, *[, fade_in, ...])

Apply short in/out fades so an edit point produces no click (report 06 §3.6).

equal_power_crossfade(a, b, overlap_samples)

Concatenate a then b with an equal-power crossfade over overlap_samples (report 06 §3.6).

fit_duration(clip, sample_rate, *[, ...])

Fit clip to duration seconds — seamless loop (beds) or trim/pass-through (report 06 §4).

overlay(bus, clip, onset_samples)

Add clip into bus at onset_samples (report 06 §6.4 overlay).

reverb_send(clip, sample_rate, amount)

Mix a pure-numpy exponential-decay reverb tail into clip (report 06 §3.5).

speech_duck_gain(n_samples, sample_rate, ...)

Build a (linear) gain envelope that dips to duck_db during speech spans (report 06 §3.2).

time_stretch(clip, sample_rate, *, rate)

Pitch-preserving time-stretch by rate via rubberband (report 06 §4, optional).

foley.weave.mix.DISTANCE_MAX_ATTEN_DB: float = -12.0#

Attenuation (dB) applied at maximum distance (distance == 1.0); ~inverse-distance.

foley.weave.mix.DISTANCE_MAX_LP: float = 0.85#

Air-absorption one-pole low-pass coefficient at maximum distance (0 = none, →1 = darkest).

foley.weave.mix.LAYER_GAIN_DB: dict[Layer, float] = {Layer.ambience: -21.0, Layer.music: -21.0, Layer.sfx_fg: -9.0, Layer.stinger: -6.0, Layer.voice: 0.0}#

Base per-layer gain (dB, relative to the voice bus at 0 dB). The render applies this before each item’s own processing.gain_db so levels have a sane default.

foley.weave.mix.REVERB_IR_S: float = 0.18#

Reverb-send impulse-response length (seconds) and decay time-constant.

foley.weave.mix.apply_distance(clip, sample_rate, distance)[source]#

Apply a distance recipe: attenuation + air-absorption low-pass (report 06 §3.4).

Distance reads as a combination of cues: quieter (~inverse-distance) and duller (a low-pass, as air absorbs highs). The reverb component of distance is applied separately via reverb_send(). distance == 0 is a no-op.

Parameters:
  • clip (ndarray) – Working array (mono or stereo).

  • sample_rate (int) – Sample rate in Hz (kept for API symmetry / future filters).

  • distance (float) – 0 (near) .. 1 (far).

Return type:

ndarray

Returns:

The attenuated, low-passed clip (a new array; input not mutated).

foley.weave.mix.constant_power_pan(clip, pan)[source]#

Pan a (mono or stereo) clip to a stereo image with a constant-power law (report 06 §3.3).

L = cos(θ), R = sin(θ) with θ = (pan+1)·π/4 and pan ∈ [-1, 1], so L² + R² (perceived loudness) stays flat across the field — no ~3 dB centre dip.

Parameters:
  • clip (ndarray) – Mono (frames,) or stereo (frames, 2) working array.

  • pan (float) – -1 (hard left) .. 0 (centre) .. +1 (hard right).

Return type:

ndarray

Returns:

A stereo (frames, 2) array.

foley.weave.mix.convolution_reverb(clip, sample_rate, ir, *, amount)[source]#

Convolution reverb with a real (recorded or synthesised) impulse response (report 06 §3.5).

The optional upgrade over reverb_send()’s synthetic exponential tail: convolves clip with a supplied IR (a recorded room/plate, or one generated by pyroomacoustics) via FFT — unity-gain-normalised, dry/wet-blended by amount, and truncated to the clip length so the placement onset is unchanged. Pure-numpy (no heavy dependency): only producing an IR needs an external tool; applying one does not. amount == 0 or an empty IR is a no-op (fully dry).

Parameters:
  • clip (ndarray) – Working array (mono or stereo).

  • sample_rate (int) – Sample rate in Hz (kept for API symmetry).

  • ir (ndarray) – The impulse response (mono (n,) or stereo (n, 2)).

  • amount (float) – 0 (dry) .. 1 (fully wet).

Return type:

ndarray

Returns:

The dry/wet-blended clip (a new float32 array; input not mutated).

foley.weave.mix.db_to_lin(db)[source]#

Convert decibels to a linear amplitude factor (10 ** (db/20)).

Return type:

float

foley.weave.mix.declick(clip, sample_rate, *, fade_in=0.008, fade_out=0.012)[source]#

Apply short in/out fades so an edit point produces no click (report 06 §3.6).

Thin wrapper over foley.audio.fade() (linear ramps), sized in seconds.

Return type:

ndarray

foley.weave.mix.equal_power_crossfade(a, b, overlap_samples)[source]#

Concatenate a then b with an equal-power crossfade over overlap_samples (report 06 §3.6).

The tail of a and head of b are ramped with cos/sin gains so perceived loudness stays flat across the seam (no ~3 dB dip). Used for seamless bed loops and ambience swaps.

Parameters:
  • a (ndarray) – The leading clip (mono or stereo).

  • b (ndarray) – The trailing clip (same channel layout as a).

  • overlap_samples (int) – Crossfade length in samples (clamped to both clip lengths).

Return type:

ndarray

Returns:

The crossfaded concatenation (len(a) + len(b) - overlap,) frames.

foley.weave.mix.fit_duration(clip, sample_rate, *, duration=None, loop=False, crossfade_s=0.05, stretch=False)[source]#

Fit clip to duration seconds — seamless loop (beds) or trim/pass-through (report 06 §4).

duration is None returns the clip unchanged (a one-shot plays once). When loop and the clip is shorter than duration, it is tiled and equal-power crossfaded at the seam to fill the span; anything longer than duration is trimmed. A non-looping short clip is returned as-is (it plays once). When stretch, the clip is pitch-preservingly time-stretched to exactly fill duration (via time_stretch()), falling back to loop/trim if rubberband is unavailable.

Parameters:
  • clip (ndarray) – Working array (mono or stereo).

  • sample_rate (int) – Sample rate in Hz.

  • duration (float | None) – Target duration in seconds (None = full clip length).

  • loop (bool) – Seamless-loop to fill duration when shorter.

  • crossfade_s (float) – Equal-power crossfade length at each loop seam.

  • stretch (bool) – Pitch-preserving time-stretch to fill duration (needs rubberband).

Return type:

ndarray

Returns:

The duration-fitted clip.

foley.weave.mix.overlay(bus, clip, onset_samples)[source]#

Add clip into bus at onset_samples (report 06 §6.4 overlay).

Sums (never replaces) into a copy of the bus; a clip whose tail would exceed the bus length is truncated to fit (the mix length equals the narration length).

Parameters:
  • bus (ndarray) – The destination layer bus (stereo (frames, 2)).

  • clip (ndarray) – The (mono or stereo) clip to place.

  • onset_samples (int) – Start offset in samples (clamped at 0).

Return type:

ndarray

Returns:

A new bus with the clip summed in.

foley.weave.mix.reverb_send(clip, sample_rate, amount)[source]#

Mix a pure-numpy exponential-decay reverb tail into clip (report 06 §3.5).

A deterministic dry/wet blend: the clip is convolved with a short exponentially-decaying impulse response and mixed back at amount. This is the zero-dependency default; pyroomacoustics / recorded-IR convolution is the optional foley[weave] upgrade. amount == 0 is a no-op (fully dry).

Parameters:
  • clip (ndarray) – Working array (mono or stereo).

  • sample_rate (int) – Sample rate in Hz.

  • amount (float) – 0 (dry) .. 1 (fully wet).

Return type:

ndarray

Returns:

The dry/wet-blended clip (a new array; input not mutated).

foley.weave.mix.speech_duck_gain(n_samples, sample_rate, speech_spans, *, duck_db=-10.0, attack=0.02, release=0.3)[source]#

Build a (linear) gain envelope that dips to duck_db during speech spans (report 06 §3.2).

A one-pole attack/release smooths the transitions so the bed dips and recovers without clicks. Deterministic and testable — the envelope-follower alternative to ffmpeg sidechaincompress.

Parameters:
  • n_samples (int) – Length of the bed bus in samples.

  • sample_rate (int) – Sample rate in Hz.

  • speech_spans (list[tuple[float, float]]) – [(start_s, end_s), ...] from the word timeline.

  • duck_db (float) – Attenuation depth during speech (negative dB).

  • attack (float) – Attack time constant (seconds) as the bed drops.

  • release (float) – Release time constant (seconds) as the bed recovers.

Return type:

ndarray

Returns:

A (n_samples,) linear gain envelope in [db_to_lin(duck_db), 1.0].

foley.weave.mix.time_stretch(clip, sample_rate, *, rate)[source]#

Pitch-preserving time-stretch by rate via rubberband (report 06 §4, optional).

rate > 1 shortens (plays faster), < 1 lengthens — so fitting a clip of n samples to a target span uses rate = n / target. Lazy: needs pyrubberband (which shells out to the rubberband binary, a WEAVE system requirement); raises RuntimeError when unavailable so callers (e.g. fit_duration()) fall back to loop/trim. Preserves the channel layout.

Return type:

ndarray