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
Base per-layer gain (dB, relative to the voice bus at 0 dB). |
|
Attenuation (dB) applied at maximum distance (distance == 1.0); ~inverse-distance. |
|
Air-absorption one-pole low-pass coefficient at maximum distance (0 = none, →1 = darkest). |
|
Reverb-send impulse-response length (seconds) and decay time-constant. |
Functions
|
Apply a distance recipe: attenuation + air-absorption low-pass (report 06 §3.4). |
|
Pan a (mono or stereo) clip to a stereo image with a constant-power law (report 06 §3.3). |
|
Convolution reverb with a real (recorded or synthesised) impulse response (report 06 §3.5). |
|
Convert decibels to a linear amplitude factor ( |
|
Apply short in/out fades so an edit point produces no click (report 06 §3.6). |
|
Concatenate |
|
Fit |
|
Add |
|
Mix a pure-numpy exponential-decay reverb tail into |
|
Build a (linear) gain envelope that dips to |
|
Pitch-preserving time-stretch by |
- 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_dbso 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 == 0is a no-op.
- 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)·π/4andpan ∈ [-1, 1], soL² + 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: convolvesclipwith a supplied IR (a recorded room/plate, or one generated bypyroomacoustics) via FFT — unity-gain-normalised, dry/wet-blended byamount, 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 == 0or an empty IR is a no-op (fully dry).- Parameters:
- 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:
- 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
athenbwith an equal-power crossfade overoverlap_samples(report 06 §3.6).The tail of
aand head ofbare ramped withcos/singains 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 asa).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
cliptodurationseconds — seamless loop (beds) or trim/pass-through (report 06 §4).duration is Nonereturns the clip unchanged (a one-shot plays once). Whenloopand the clip is shorter thanduration, it is tiled and equal-power crossfaded at the seam to fill the span; anything longer thandurationis trimmed. A non-looping short clip is returned as-is (it plays once). Whenstretch, the clip is pitch-preservingly time-stretched to exactly fillduration(viatime_stretch()), falling back to loop/trim ifrubberbandis 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 filldurationwhen shorter.crossfade_s (
float) – Equal-power crossfade length at each loop seam.stretch (
bool) – Pitch-preserving time-stretch to fillduration(needsrubberband).
- Return type:
ndarray- Returns:
The duration-fitted clip.
- foley.weave.mix.overlay(bus, clip, onset_samples)[source]#
Add
clipintobusatonset_samples(report 06 §6.4overlay).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 optionalfoley[weave]upgrade.amount == 0is a no-op (fully dry).
- 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_dbduring 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
rateviarubberband(report 06 §4, optional).rate > 1shortens (plays faster),< 1lengthens — so fitting a clip ofnsamples to atargetspan usesrate = n / target. Lazy: needspyrubberband(which shells out to therubberbandbinary, a WEAVE system requirement); raisesRuntimeErrorwhen unavailable so callers (e.g.fit_duration()) fall back to loop/trim. Preserves the channel layout.- Return type:
ndarray