Editing and effects¶
kudio.effects.edit
¶
Everyday editing moves: fades, reversal, DC removal.
None of these are clever. They are here because every audio editor has them and because doing them by hand is where the off-by-one lives — a fade that reaches zero one sample early leaves a click exactly where the fade was meant to prevent one.
Functions:
| Name | Description |
|---|---|
fade_in |
Ramp the first duration seconds up from silence. |
fade_out |
Ramp the last duration seconds down to silence. |
fade |
Fade both ends in one call. |
crossfade |
Join two clips through a short overlap, instead of butting them together. |
splice |
Join several clips end to end, crossfading every seam. |
reverse |
Play the waveform backwards. Multi-channel audio keeps its channels. |
remove_dc |
Subtract the mean, per channel. |
DEFAULT_CROSSFADE
module-attribute
¶
Enough to remove the step at a cut, short enough that nothing is heard to happen. Below ~2 ms the click starts coming back.
fade_in
¶
Ramp the first duration seconds up from silence.
A 10 ms default is the usual anti-click amount: long enough to remove the step at the start of a cut, short enough not to be heard as a fade.
Source code in kudio/effects/edit.py
fade_out
¶
Ramp the last duration seconds down to silence.
Source code in kudio/effects/edit.py
fade
¶
fade(
y: ndarray,
sr: int,
fade_in_s: float = 0.01,
fade_out_s: float = 0.01,
shape: str = "linear",
) -> ndarray
Fade both ends in one call.
The two ramps are checked against the clip length together, so a fade in and a fade out that overlap is an error rather than a silently multiplied middle.
Source code in kudio/effects/edit.py
crossfade
¶
crossfade(
first: ndarray,
second: ndarray,
sr: int,
duration: float = DEFAULT_CROSSFADE,
shape: str = "equal_power",
) -> ndarray
Join two clips through a short overlap, instead of butting them together.
A hard splice puts a step in the waveform, and a step is a click. Blending the last duration of first into the first duration of second removes it.
The result is shorter than the two parts by duration — the overlap is shared, not inserted. That is not a rounding error to be papered over: a caller tracking positions in the audio has to account for it, and hiding it would make those positions silently wrong.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
shape
|
str
|
|
'equal_power'
|
Source code in kudio/effects/edit.py
splice
¶
splice(
parts: Sequence[ndarray],
sr: int,
duration: float = DEFAULT_CROSSFADE,
shape: str = "linear",
) -> ndarray
Join several clips end to end, crossfading every seam.
Empty parts are skipped rather than producing a seam with nothing on one
side. The result is shorter than the sum of the parts by one duration per
seam; crossfade explains why that matters.
The default shape differs from crossfade's, deliberately. A splice
joins two moments of the same recording, which are usually alike, and
equal-power over-shoots on alike material: measured on a sine cut at a
period boundary, equal-power raised the peak from 0.500 to 0.567 while
linear left it at 0.500. A crossfade between genuinely different material
is the other case, and defaults the other way.
Source code in kudio/effects/edit.py
reverse
¶
remove_dc
¶
Subtract the mean, per channel.
A constant offset costs headroom and shows up as an audible thump at every
cut. This removes a constant one; an offset that drifts over a long
recording is a very low frequency, and kudio.highpass is the tool
for that.
Source code in kudio/effects/edit.py
kudio.effects.filters
¶
Butterworth band filters.
The everyday shaping moves — drop the rumble below 80 Hz, throw away everything
above the band you actually care about, isolate one. wavelet_low_pass_filter
in kudio.enhance is a denoising algorithm that happens to be low-pass;
this is the plain filter, and the two are not substitutes.
Filtering is zero-phase by default (scipy.signal.sosfiltfilt): the signal
is run forwards and backwards, so nothing is shifted in time. That matters when
a filtered copy has to line up sample-for-sample with the original — an A/B, a
spectrogram overlay, a training target. Pass zero_phase=False for the
causal, real-time-shaped version.
Functions:
| Name | Description |
|---|---|
band_filter |
Butterworth filter of any of the four kinds. |
highpass |
Remove everything below cutoff Hz — rumble, handling noise, DC drift. |
lowpass |
Remove everything above cutoff Hz — hiss, and anything past the band |
bandpass |
Keep only |
bandstop |
Remove |
band_filter
¶
band_filter(
y: ndarray,
sr: int,
cutoff: Union[float, Sequence[float]],
btype: str = "highpass",
order: int = 4,
zero_phase: bool = True,
) -> ndarray
Butterworth filter of any of the four kinds.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cutoff
|
Union[float, Sequence[float]]
|
one frequency in Hz, or |
required |
order
|
int
|
filter order. Zero-phase filtering applies it twice, so the
effective roll-off is |
4
|
zero_phase
|
bool
|
forwards-and-backwards (no delay) when True. A |
True
|
Source code in kudio/effects/filters.py
highpass
¶
highpass(
y: ndarray,
sr: int,
cutoff: float = 80.0,
order: int = 4,
zero_phase: bool = True,
) -> ndarray
Remove everything below cutoff Hz — rumble, handling noise, DC drift.
Source code in kudio/effects/filters.py
lowpass
¶
lowpass(
y: ndarray,
sr: int,
cutoff: float = 8000.0,
order: int = 4,
zero_phase: bool = True,
) -> ndarray
Remove everything above cutoff Hz — hiss, and anything past the band a downstream model was trained on.
A cutoff at or above Nyquist leaves nothing to remove, so the clip comes back unchanged (a copy). That is also what the 8 kHz default does to audio at 16 kHz and below.
Source code in kudio/effects/filters.py
bandpass
¶
bandpass(
y: ndarray,
sr: int,
low: float = 300.0,
high: float = 3400.0,
order: int = 4,
zero_phase: bool = True,
) -> ndarray
Keep only low..high Hz. The default is the telephone band.
Source code in kudio/effects/filters.py
bandstop
¶
bandstop(
y: ndarray,
sr: int,
low: float = 45.0,
high: float = 55.0,
order: int = 4,
zero_phase: bool = True,
) -> ndarray
Remove low..high Hz. The default straddles 50 Hz mains hum; use
(55, 65) where the mains run at 60.
Source code in kudio/effects/filters.py
kudio.effects.silence
¶
Silence trimming and splitting.
Measured down from the loudest frame, so it suits a sound against a quiet
background: a studio take, a knock in a still room. Speech over a room that is
never quiet is kudio.vad's job.
Functions:
| Name | Description |
|---|---|
trim_silence |
Trim leading/trailing silence below |
split_on_silence |
Split y into non-silent segments (each below- |
trim_silence
¶
trim_silence(
y: ndarray,
top_db: float = 30.0,
frame_length: int = 2048,
hop_length: int = 512,
) -> Tuple[ndarray, Tuple[int, int]]
Trim leading/trailing silence below top_db dB under peak.
Returns (trimmed_waveform, (start_sample, end_sample)). The waveform is
a copy, and a (frames, channels) array is trimmed along its frames.
Source code in kudio/effects/silence.py
split_on_silence
¶
split_on_silence(
y: ndarray,
top_db: float = 30.0,
frame_length: int = 2048,
hop_length: int = 512,
) -> List[ndarray]
Split y into non-silent segments (each below-top_db gap is a cut).
Each segment is a copy; a (frames, channels) array is split along its
frames.
Source code in kudio/effects/silence.py
kudio.effects.augment
¶
Waveform / spectrogram data augmentation.
Every function is pure (returns a new array) and, where randomness is involved,
accepts a seed for reproducibility.
Functions:
| Name | Description |
|---|---|
time_stretch |
Speed up ( |
pitch_shift |
Shift pitch by n_steps semitones without changing duration. |
gain |
Apply a fixed gain in decibels. |
normalize |
Scale so the loudest sample sits at peak. |
normalize_db |
Scale so the peak sits at dbfs decibels below full scale. |
random_gain |
Apply a random gain sampled uniformly from db_range dB. |
add_noise_snr |
Mix noise into y at the target signal-to-noise ratio (dB). |
reverb |
Cheap synthetic reverb via an exponentially-decaying comb filter: an |
spec_augment |
SpecAugment: mask random frequency bands and time spans of a spectrogram. |
time_stretch
¶
Speed up (rate>1) or slow down (rate<1) without changing pitch.
A (frames, channels) array is stretched channel by channel, and
integer audio comes back in its own type, as gain does it.
Source code in kudio/effects/augment.py
pitch_shift
¶
Shift pitch by n_steps semitones without changing duration.
A (frames, channels) array is shifted channel by channel, and integer
audio comes back in its own type, as gain does it.
Source code in kudio/effects/augment.py
gain
¶
Apply a fixed gain in decibels.
Integer audio stays in its type, rounded and clipped at full scale rather than wrapped round, which would turn an int16 20000 at +6 dB into -25631.
Source code in kudio/effects/augment.py
normalize
¶
Scale so the loudest sample sits at peak.
Silence is returned unchanged rather than divided by zero — the guard everyone writes inline and someone eventually forgets. For integer audio peak is a fraction of the type's full scale, as it is for float, and the result keeps the input's type.
Source code in kudio/effects/augment.py
normalize_db
¶
Scale so the peak sits at dbfs decibels below full scale.
normalize_db(y, -1.0) is normalize(y, 10 ** (-1 / 20)); use
whichever unit the rest of the pipeline speaks.
Source code in kudio/effects/augment.py
random_gain
¶
random_gain(
y: ndarray,
db_range: Tuple[float, float] = (-6.0, 6.0),
seed: Optional[int] = None,
) -> ndarray
Apply a random gain sampled uniformly from db_range dB.
Source code in kudio/effects/augment.py
add_noise_snr
¶
Mix noise into y at the target signal-to-noise ratio (dB).
noise is tiled/cropped (with a random offset) to match len(y). Both
are mono.
Raises:
| Type | Description |
|---|---|
FeatureError
|
for noise with no variation in the stretch used -- silence or a constant. Its mean is removed first, so nothing would be left to scale, and the result would be y back with no noise in it at any SNR. Synthesizer refuses the same file for the same reason. |
Source code in kudio/effects/augment.py
reverb
¶
Cheap synthetic reverb via an exponentially-decaying comb filter: an
echo every delay_ms, each decay times the one before. For a room that
behaves like a room, use kudio.rir with kudio.apply_rir.
A (frames, channels) array gets the same echoes on every channel.
Source code in kudio/effects/augment.py
spec_augment
¶
spec_augment(
spec: ndarray,
n_freq_masks: int = 1,
n_time_masks: int = 1,
freq_mask_width: int = 8,
time_mask_width: int = 16,
mask_value: Optional[float] = None,
seed: Optional[int] = None,
) -> ndarray
SpecAugment: mask random frequency bands and time spans of a spectrogram.
spec is (frames, bins), the layout every spectrogram in kudio comes
in (or squeezable to it). Returns a masked copy.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mask_value
|
Optional[float]
|
what a masked cell becomes. |
None
|