Pure-Rust audio filters (volume, gate, echo, resample, spectrogram) for oxideav
Part of the oxideav framework — a pure-Rust media transcoding and streaming stack. Codec, container, and filter crates are implemented from the spec (no C codec libraries linked or wrapped, no *-sys crates). Optional hardware-engine crates (oxideav-videotoolbox / -audiotoolbox / -vaapi / -vdpau / -nvidia / -vulkan-video) bridge to OS APIs via runtime dlsym; pass --no-hwaccel (or omit the hwaccel feature) to opt out.
-
volume — linear / dB gain with hard clipping
-
noise_gate — threshold + attack/release/hold envelope gate; optional two-threshold hysteresis (
open_db/close_db, sticky latch eliminates chatter when the drive dances around the threshold) and Hermite-smoothstep soft-knee (knee_db, C¹-continuous transition with 0.5 gain at the knee centre).NoiseGate::newkeeps the legacy hard-knee single-threshold behaviour byte-for-byte;NoiseGate::withexposes the upgraded parameters. -
echo — single-tap delay line with feedback and wet/dry mix
-
resample — polyphase windowed-sinc rational sample-rate conversion, implementing the ideal-bandlimited-interpolation theory of
docs/audio/filter/jos-theory-of-sample-rate-conversion.html/jos-bandlimited-interpolation.html/jos-resample.html(Shannon reconstructionx(t) = Σ x(nTs)·hₛ(t − nTs),hₛthe normalised sinc, evaluated at the new sampling instants). Rational factoringL = lcm(src, dst),up = L/srcphases,down = L/dstphase step; Kaiser-β8 windowed-sinc prototype low-pass with cutoff at half the lower rate (the theory's anti-alias / anti-image placement). The prototype length now scales withmax(up, down)so the anti-aliasing filter stays sharp under decimation: a fixed 32-tap window leaked aliasing at ≈ −13 dB on a 48 k→16 k downsample (an above-Nyquist tone folding back into band); the ratio-scaled length rejects it ≥ 40 dB end-to-end. Closed-formprototype_response_db(f)evaluator (DTFT of the windowed-sinc prototype, 0 dB DC reference) pluspassband_edge_hz/up_factor/down_factor/src_rate/dst_rateaccessors expose the design for verification. Frequency-response identities asserted across integer-decimate, integer-interpolate, and the coprime 48 k↔44.1 k ratios: passband flat to ±0.05 dB out to 0.8·edge, exactly −6.02 dB at the band edge (half-band point), ≥ 70 dB stop-band rejection by 1.2·edge, monotone transition with no passband bump, plus theup·src = down·dst = L/ coprime-up,downLCM invariant and DC-preservation through integer interpolation. -
spectrogram — STFT renderer with PNG / video output
-
downmix — channel-layout fold-down (LoRo / LtRt / Average / Binaural)
-
biquad — eleven-config IIR EQ family with DF-II-T
f64state, covering the complete staged EQ-cookbook catalogue (docs/audio/filter/audio-eq-cookbook.html): LPF / HPF / BPF in both constant-skirt (peak gain = Q) and constant-0-dB-peak variants / notch / peaking / low + high shelves in both Q- and shelf-slope-(S)-parameterised forms (α = (sinω/2)·√((A+1/A)(1/S−1)+2); S = 1 → steepest monotonic transition, equal to Q = 1/√2 for any gain) / all-pass. Closed-formmagnitude_response_db(f, fs)evaluator for response plotting and design verification. Frequency-response identities asserted analytically to ≤ 1e-9 dB: constant-peak BPF = 0 dB at fc for Q ∈ {0.3 … 32}; skirt − peak = 20·log10(Q) dB at every frequency; shelf midpoint = gain/2 dB exactly; shelf DC/Nyquist plateaus ± 0.01 dB; S = 1 monotonic over a 1/12-octave 20 Hz–20 kHz sweep while S = 2 overshoots ±1.38 dB -
compressor — soft-knee compressor with attack/release follower, make-up gain, and a selectable peak / RMS detector.
Compressor::newkeeps the legacy peak-sensing behaviour byte-for-byte;Compressor::with_detectorselectsEnvelopeMode::Rms(power-averaged, runs the one-pole follower onx²and reports√env) for the perceptually-relaxed "power measurement" sensing described in thedocs/audio/filter/dynamic-range reference's "Peak vs RMS sensing" section — a sine drives the RMS detector off its true RMS (−3.01 dBbelow the peak) so the gain reduction tracks loudness, not peaks. Orthogonally, a detector topology (DetectorTopology,Compressor::with_topology) selects the detector placement per thedocs/audio/filter/dynamic-range reference's "Design" section:FeedForward(default) senses the input (the modern layout "used today in most compressors");Feedbacksenses the previous outputy[n-1](the earlier vintage layout where "the signal level was measured after the amplifier"), a self-stabilising loop whose gain reduction settles softer and more program-dependent than feed-forward (steady-state GR strictly smaller in magnitude for the same threshold/ratio — the gentle opto / vari-mu "knee").prev_outcarry keepsy[n-1]continuous across processing blocks. Registrydetectorkey ("peak"default /"rms") andtopologykey ("feedforward"default /"feedback") -
parallel_compressor — parallel ("New York" / "Motown") compression (
docs/audio/filter/'s dynamic-range reference §7): the dry input is split, one copy passes through untouched and the other through a soft-knee compressor, and the two are summed (y = x·10^(dry_db/20) + comp(x)·10^(wet_db/20)). Distinct from the in-seriescompressor: the reference calls it "a form of upward compression that facilitates dynamic control without significant audible side effects" — "combining a linear signal with a compressor … results in low-level detail enhancement without any peak reduction; the compressor significantly adds to the combined gain at low levels only." At a quiet passage the wet copy is un-reduced and (with the trims at unity) the sum reads +6 dB; at a loud peak the wet copy is squashed flat and the dry path dominates so the peak survives — quiet detail lifted, transients preserved.dry_db/wet_dbtrim the two paths before the sum (the classic recipe pullswet_dbunder the dry signal); the inner compressor's make-up gain is folded intowet_db. Same soft-knee static curve + attack/release one-pole follower + selectable peak / RMS detector ascompressor, peak-linked across channels so both paths' stereo image is preserved.ParallelCompressor::new(unity trims, peak detector) /with_mix(explicitdry_db/wet_db/ detector). Registry entry"parallel_compressor"accepts JSONthreshold_db/ratio/attack_ms/release_ms/knee_db/dry_db/wet_db/detectorkeys. -
limiter — brickwall peak limiter with optional look-ahead (0..=2048 samples)
-
dc_blocker — single-pole IIR HPF (
y[n] = x[n] - x[n-1] + R·y[n-1]) for DC-offset removal -
stereo_widener — Mid/Side width control with
width ∈ [0, 2](0 = mono, 1 = bypass, 2 = wide) -
crossfeed — headphone crossfeed: each channel bleeds into the other delayed by the interaural time difference (default 300 µs, fractional-sample exact via the in-crate
FracDelayLine), attenuated (level_db, default −6 dB), and head-shadow low-passed (one-pole, default 700 Hz). Direct/cross gains satisfyd + g/(1+g) · … = 1so mono content passes at unity at low frequencies. Narrows hard-panned material the way loudspeakers in a room do — the direction-preserving counterpart to the M/S-basedstereo_widener/stereo_imager. Stereo only; other channel counts pass through. Registry keyslevel_db/cutoff_hz/delay_us -
reverb — Schroeder algorithmic reverb (4 parallel combs ║ 2 serial all-passes) with room_size / damping / wet / dry knobs
-
tremolo — sine-LFO amplitude modulation with rate / depth knobs
-
loudness_itu — ITU-R BS.1770-4 / EBU R128 integrated loudness meter (LUFS) with K-weighting + channel weights
-
pitch_shift — time-domain SOLA-style granular pitch shifter (
-12..=+12semitones, no FFT) -
chorus — 1..=4 LFO-modulated short delay taps with phase-offset voices
-
flanger — short-delay feedback comb (1..=15 ms, swept resonance)
-
phaser — N (2..=12) cascaded first-order all-pass sections with LFO-swept cutoffs
-
equalizer — builder over N
Biquadsections in series (low/high-pass, both BPF variants, notch, peaking, Q- and slope-parameterised shelves, all-pass) -
white_noise / pink_noise / brown_noise — splitmix64-seeded generators with flat / 1/f / 1/f² spectra
-
silence_detector — pass-through observer with attack/release RMS envelope + hold-threshold flag
-
vibrato — LFO-modulated fractional-delay pitch shift (pitch counterpart to
tremolo) -
auto_pan — LFO-modulated L/R stereo placement (mono-sum conservative pan law)
-
bitcrusher — bit-depth quantisation + sample-and-hold rate reduction
-
tape_saturation —
tanhsoft-clip waveshaper with asymmetric drive (odd + even harmonics) -
hum_filter — cascaded narrow notches at 50/60 Hz mains fundamental + harmonics
-
crossover — two-way LPF/HPF band split (
output channels = 2× input); Butterworth-2 (12 dB/oct, default, byte-for-byte legacy viaCrossover::new/butterworth) orCrossoverSlope::LinkwitzRiley4(24 dB/oct, two cascaded Butterworth-2 per band → −6 dB at fc, in-phase, magnitude-flat|low + high| = 1reconstruction). Registryslopekey ("lr4") -
mid_side — explicit L/R ↔ M/S transcoder (stateless, exact roundtrip)
-
envelope_follower — peak / RMS amplitude envelope detector (pass-through, observe via API)
-
de_esser — split-band downward compressor targeting sibilance
-
wah — LFO-swept resonant band-pass (classic wah log sweep)
-
octave_doubler — Octavia-style full-wave-rectifier octave-up layer with DC block
-
adaptive_noise_gate — gate with self-learned asymmetric noise floor
-
exciter — high-band
tanhsaturation enhancer (HPF + dry mix; adds "air" without raising broadband level) -
multiband_compressor — three-band (low / mid / high) independent compression with Butterworth-2 crossovers (default 250 Hz / 2500 Hz)
-
stereo_imager — frequency-dependent stereo width via M/S side split (mono bass + wide treble preset)
-
talkbox — LFO-morphed parallel formant band-pass bank (vowel filter, 6 vowels, no FFT)
-
transient_designer — two-envelope (fast vs slow) attack + sustain shaper (
attack/sustain ∈ [-1, +1]) -
ducker — internally-keyed sidechain compressor with safety-floored gain reduction (broadcast voice-over duck)
-
gain_normalizer — slow programme-level AGC with silence-freeze integrator (
target_db, default ~1 s response) -
freq_shifter — Hilbert-FIR SSB frequency shifter (additive
Δfin Hz, harmonic-destroying) -
ring_modulator — sine-carrier double-sideband suppressed-carrier AM (
y = x · cos(2π·fc·n/fs), audible carrier; produces mirror sidebands atf ± fc, Dalek / bell timbre) -
hard_clipper — memoryless symmetric clipping distortion (
y = clamp(drive·x, ±ceiling); odd-harmonic fuzz / overdrive, distinct from thetanhsoft-clip oftape_saturation) -
slew_limiter — slope-limited smoother (
Δ = x − y_prev; y = y_prev + clamp(Δ, ±s)withs = max_slew_per_sec / fs); linear-ramp response (vs the LPF's exponential), with optional asymmetric rise / fall caps. Anti-zipper / portamento / anti-pop primitive. -
expander — proportional downward expander:
gr_db = -(R − 1) · max(0, threshold_db − env_db). Distinct fromnoise_gate(binary open/close) and fromcompressor(slope on the above-threshold side).ratio = 1.0→ identity,ratio = ∞→ hard downward gate, finite ratios in between trade off between gentle noise-floor management (1.5:1/2:1) and aggressive expansion (4:1+). Soft-knee width smooths the threshold transition; attack/release one-pole follower; peak-linked detector across channels. -
true_peak_detector — pass-through inter-sample peak observer reporting dBTP via 4× polyphase Kaiser-windowed FIR oversampling. Sample-domain
max |x[n]|understates the band-limited reconstructed peak (canonical example: anfs/4 + π/4-phase full-scale sine reads-3.01 dBFSon the grid but reconstructs to≈ 0 dBTP); this filter reveals the true peak by upsampling byL = 4with a unit-DC-gain FIR (default 48 taps → 12 taps per polyphase sub-filter, Kaiser β chosen for ~100 dB stop-band) and trackingmax |y|inf64. Pass-through audio (no modification); consumers readcurrent_dbtp(last frame),max_dbtp(running), andovers(count of oversampled samples above the configurable linear threshold, default1.0= 0 dBTP). Distinct fromlimiter(sample-peak gain reduction),envelope_follower(smoothed envelope), andloudness_itu(K-weighted integrated loudness). -
svf — Chamberlin two-integrator-loop State Variable Filter. Simultaneous LP / BP / HP / Notch taps from a single recurrence (
hp = x - q·bp_prev - lp_prev; bp += f·hp; lp += f·bp), so the output mode can be reconfigured without touching state.f = 2·sin(π·f_c/f_s),q = 1/Q; per-sample modulation of cutoff is cheap (onesinonset_cutoff) and avoids the bilinear-transform coefficient resolve that a regular biquad needs — this is the canonical synth filter for envelope-/LFO-swept cutoff. State-space topology distinct from thebiquadfamily (transfer-function DF-II-T realisation). Stable whilef_c < f_s / 6andQ ∈ [0.5, 50]; values outside that range are clamped at construction. Notch tap ishp + lp, deep at low Q (~−24 dB at Q = 0.5) and degrading as Q rises — usebiquadnotch for high-Q narrow-band reject. -
median_filter — non-linear sliding-window median filter for impulse-noise (click / pop) removal. Per channel keeps an
N-sample ring; output is the median of the latestNsamples. Step edges spanning more thanN / 2samples pass through unaltered, but isolated impulse outliers are entirely discarded — a behaviour no linear LPF achieves. Window∈ [1, MedianFilter::MAX_WINDOW](=[1, 257]); default5(canonical click-removal value). Even windows return the mean of the two centre sorted samples. Insertion-sort over the ring (best-caseO(window)on near-sorted data, steady-state common case). Joins the restoration family next tohum_filter(cyclic-mains) anddc_blocker(DC drift) — its niche is transient impulse noise instead of cyclic / DC. Registry entry"median_filter"accepts JSONwindowkey. -
crest_factor_meter — pass-through observer reporting peak-to-RMS ratio (crest factor) in dB over a sliding rectangular window. The crest factor is the textbook scalar that quantifies how "spiky" or transient-rich a signal is: a sine wave reads
3.01 dB, a square wave reads0 dB, broadband noise lands near11 dB, modern broadcast pop sits in5..8 dBafter heavy dynamic compression, sparse drum transients exceed20 dB. Algorithm: running sum-of-squares (incremental add new sample's square, subtract leaving sample's square, with periodic per-window rebuild that boundsf64drift on long streams) gives the windowed RMS inO(1)per sample; a monotonic-decreasing deque of(|x|, sample_index)(front always holds the active-window maximum, rear pops while incoming sample dominates, front pops when its index expires) gives the windowedmax |x|inO(1)amortised per sample. Channel-linked bymaxso a loud transient on one channel of a split stereo bed isn't masked. Window default400 ms(EBU R128 short-term loudness window), clamped to[0.1, 10_000] msand additionally to[1, 192_000]samples (4 s at 48 kHz). Until the window first fills, the readout returnsf32::NEG_INFINITY(and0.0for the linear form) so callers can branch on "not yet ready". Distinct fromtrue_peak_detector(absolute oversampled peak only),loudness_itu(K-weighted integrated loudness only),envelope_follower(single one-pole envelope, not a ratio), andsilence_detector(binary above/below threshold flag). Registry entry"crest_factor_meter"accepts JSONwindow_mskey. -
stereo_correlation_meter — pass-through observer reporting the windowed Pearson correlation coefficient
ρ ∈ [-1, +1]between the L and R channels.ρ = +1for identical channels (mono content panned centre);ρ ≈ 0for orthogonal channels (uncorrelated stereo bed);ρ = -1for phase-inverted channels (canonical mono-fold-down cancellation hazard most downstream broadcast chains still emit). Five incremental running sums(Σx, Σy, Σx², Σy², Σxy)give the closed-form Pearsonρ = (N·Σxy − Σx·Σy) / √((N·Σx² − Σx²)·(N·Σy² − Σy²))atO(1)per sample; per-window rebuild boundsf64drift on long streams. Polarcurrent_degrees() = acos(ρ)·180/πexposes the classical goniometer's0° / 90° / 180°angular reading. Runningmin()latches on the worst-case phase-cancellation reading so a transiently-inverted frame survives a quieter tail. Window default400 ms(matchingcrest_factor_meter, so both meters share a time axis on a display); clamped to[0.1, 10_000] msand to[1, 192_000]samples. Stereo input only — mono and multichannel (channel count not equal to two) layouts pass through unchanged with the meter state untouched. Pearson is both mean-centred (DC offsets don't bias the metric) and scale-invariant (per-channel volume changes don't bias it). Distinct fromstereo_widener/stereo_imager(both processors of the stereo image), from the single-channel meterscrest_factor_meter/true_peak_detector/loudness_itu(none of which carries inter-channel phase information), and fromsilence_detector(single-channel binary flag). Registry entry"stereo_correlation_meter"accepts JSONwindow_mskey. -
stereo_balance_meter — pass-through observer reporting the L/R energy balance
B = (R_rms − L_rms)/(R_rms + L_rms) ∈ [-1, +1]over a sliding rectangular window.B = 0centred (equal-energy channels — mono panned dead-centre or a symmetric bed),B = -1all energy on the left (right silent),B = +1all energy on the right,+1/3when the right channel is twice as loud as the left,-1/3for the mirror case. The level complement tostereo_correlation_meter(phase): Pearson correlation is mean-centred and scale-invariant, so two perfectly correlated channels at+12 dB/-12 dBstill readρ = +1— correlation is blind to a level imbalance and balance reports exactly that discarded dimension; the two meters together pin down both axes (phase + level) of a stereo image on a shared time axis. A persistent non-zero balance flags an accidental pan offset, a channel-trim mismatch in the capture chain, one dead / intermittent channel, or a mono source mis-routed to a single leg of a stereo bus. Algorithm: per-channelf32ring ofNsamples plus anf64running sum-of-squaresQ = Σ x²updated incrementally (Q ← Q + x_new² − x_old²) atO(1)per sample; windowed RMS is√(Q/N)and the balance follows in closed form. Periodic per-window rebuild of both sums boundsf64drift on long streams (same cadence ascrest_factor_meter/stereo_correlation_meter). Stereo input only — mono and multichannel (channel count not equal to two) layouts pass through unchanged with the meter state untouched. Window default400 ms(matching the other R128-aligned meters); clamped to[0.1, 10_000] msand to[1, 192_000]samples. Until the window first fills the readout returns the neutral0.0(centred); both-channels-silent over the window is likewise undefined and reads0.0. Consumers pollcurrent(),rms_left()/rms_right(),max_abs()/reset_max(),samples_seen(). Registry entry"stereo_balance_meter"accepts JSONwindow_mskey. Distinct fromstereo_correlation_meter(phase, level-blind),stereo_widener/stereo_imager(image processors), and the single-channelcrest_factor_meter/true_peak_detector/loudness_itu/dc_offset_meter(none of which compares the two channels' energy). -
comb_filter — tunable single-tap comb (feedforward FIR
y[n] = x[n] + g·x[n−D]or feedback IIRy[n] = x[n] + g·damped(y[n−D])) with an optional one-pole LPdamped(·)in the feedback path. The FIR form yieldsD + 1evenly-spaced peaks (or notches, withg < 0) in[0, fs/2]with magnitude√(1 + g² + 2g·cos(ωD))— useful for stereo widener side-paths, decorrelation networks, and frequency-domain dereverberation. The IIR form yields resonant peaks of1/(1−g)atfs/Dand its harmonics with−3 dBbandwidth≈ (1 − g)·fs/(πD)— the canonical building block for Karplus-Strong plucked-string synthesis ([CombFilter::karplus_strong] picksD = round(fs / freq_hz)and sets damping to0.5for the classic decaying-overtone string tone). Feedback gain clamped to[−0.999, +0.999]and damping clamped to[0, 0.999]at construction so the loop is strictly stable. Delay specifiable either as exact samples (sample-rate-dependent) or in milliseconds (rate-portable, resolved against the first stream's sample rate); both clamped to[1, MAX_DELAY_SAMPLES = 192_000]. Per-channel ring buffers + per-channel LP state; channels do not cross-talk. Registry entry"comb_filter"accepts JSONmode("feedforward"/"feedback"/"karplus_strong"),delay_ms/delay_samples,gain,damping, and (for thekarplus_strongshortcut)freq_hz+decay. Distinct fromecho(audible tens-of-ms repetition, wet/dry mix),flanger(LFO-modulated delay length on top of a feedback comb), andreverb(four parallel combs + two serial all-passes presented as a room simulator). -
zero_crossing_rate — pass-through observer reporting how often the signal crosses zero per unit time over a sliding rectangular window.
-
dc_offset_meter — pass-through observer reporting the per-channel running mean (DC component) over a sliding rectangular window. Where
dc_blockerremoves a DC bias with a single-pole HPF, this meter reports it without altering the signal. A non-zero mean reveals a low-frequency bias — preamp trim drift, ADC quantiser-midpoint offset, sagging field-recorder rail, accidental unipolar synthesis bus — that leaves the speaker cone parked off-centre and wastes a chunk of available headroom on a constant push. Algorithm: per-channelf32ring ofNsamples plus anf64running sumS = Σ xupdated incrementally (S ← S + x_new − x_old) atO(1)per sample, with a periodic per-window rebuild boundingf64drift on long streams (same cadence ascrest_factor_meter/stereo_correlation_meter). Channel-link picks the per-channel mean with largest|·|, sign preserved, so a +0.05 channel and a −0.20 channel report −0.20, and equal-and-opposite biases (+0.1 / −0.1) do not cancel in the readout. Window default400 ms(matching the other R128-aligned meters); clamped to[0.1, 10_000] msand additionally to[1, 192_000]samples. Until the window first fills the readout returns0.0(linear) /NEG_INFINITY(dB) so callers can branch on "not yet ready". Registry entry"dc_offset_meter"accepts JSONwindow_mskey. Distinct fromcrest_factor_meter(insensitive to DC — peak and RMS grow together),loudness_itu(BS.1770 RLB pre-filter already wipes DC at 38 Hz),envelope_follower/silence_detector(both rectify to|x|early),stereo_correlation_meter(Pearson is mean-centred by construction), andzero_crossing_rate(DC bias is only indirectly visible because it shifts the sign-change axis). The zero-crossing rate is a textbook scalar that countssign(x[n]) != sign(x[n-1])events over a window ofNadjacent-sample pairs, exposed either as a per-sample fraction∈ [0, 1]or — more usefully — in crossings per second (Hz) ascount · fs / N. It is a cheap proxy for the spectral centroid: a pure sine atf₀produces≈ 2·f₀crossings/sec, broadband noise produces a stable elevated rate, an alternating-sign signal saturates atfs, and a constant DC signal sits at0. Used widely in voiced/unvoiced speech classification (vowels low, fricatives high), tone-pitch proxy, percussion vs harmonic separation, and silence/noise-floor gating. Algorithm: per-channel ring ofNboolean crossing flags + one-sample latch holding the previously-seen sample; each new sample forms a(prev, x)pair, the resulting flag is pushed into the ring and the flag rotating out is subtracted from the runningcount— allO(1)per sample, nof64drift to manage. Channel-link bymax. Sign convention reduced to{-1, +1}withsign(0.0) = +1so a run of bit-exact zeros doesn't manufacture phantom crossings. Window default25 ms(canonical short-time speech frame), clamped to[0.1, 10_000] msand additionally to[1, 192_000]samples. Until the flag-ring first fills the readout returnsNAN(rate) /0.0(fraction) /0(count) so callers can branch on "not yet ready". Registry entry"zero_crossing_rate"accepts JSONwindow_mskey. Distinct from the energy/amplitude observers —crest_factor_meter(peak-to-RMS),true_peak_detector(oversampled inter-sample peak),loudness_itu(K-weighted integrated loudness),envelope_follower(smoothed envelope),silence_detector(binary RMS threshold flag) — none of which exposes the per-sample sign sequence. -
pre_emphasis / de_emphasis — paired analog-broadcast / tape / FM record EQ shelving filters. The cascade
pre · deis the identity by construction (withinf64round-off; ≤ −60 dB RMS error per sample on white-noise round-trip). SharedEmphasisCurveenum coversFm50us(European FM),Fm75us(North-American FM),J17(ITU-R J.17 voice-band),Custom { tau_s }(user-specified single-time-constant), andRiaa3180_318_75(phonograph / vinyl second-order three-time-constant curve). Coefficients are derived in-source by bilinear transform ofH_pre(s) = (1 + s·τ) / (1 + s·τ/G)and its inverse, with the asymptotic shelf-top gainG(default 10, capped at 1000) bounding HF boost so the discrete transfer stays well-behaved at Nyquist. First-order(b₀, b₁, a₁)direct-form-I IIR per channel for FM / J.17 / Custom; second-order(b₀, b₁, b₂, a₁, a₂)for RIAA. Per-channel state,reset(),set_sample_ratere-derives coefficients. Registry entries"pre_emphasis"/"de_emphasis"accept JSONcurvekey ("fm_50us"/"fm_75us"/"j17"/"riaa"/"custom"+"tau_us"for the custom case) andgfor the asymptotic gain. 15 hand-derived unit tests per filter including DC-gain-unity (both first- and second-order), Nyquist-asymptote-equals-G, derived corner frequencies (3183 Hz @ 50 µs, 2122 Hz @ 75 µs), +20 dB/decade slope-between-corners, channel-independence, streaming continuity, sample-rate-change re-derivation, alias-curve identity (J.17 ≡ FM-50, Custom ≡ named); plus three cascade-identity tests (FM-50 / FM-75 / RIAA) proving the inverse property. -
dither — word-length-reduction requantizer with TPDF / RPDF dither and first- / second-order error-feedback noise shaping; the transparency-grade end-of-chain primitive for a float pipeline feeding a fixed-point encode. Rounds every sample onto the exact
bits-wide signed mid-tread code grid (Δ = 2^(1−bits),bits ∈ [2, 24], default 16 → outputs are exact 16-bit PCM codes) while decorrelating the rounding error from the programme: bare rounding produces correlated error (harmonic distortion, and a deadband that erases any sine underΔ/2outright), RPDF dither (uniform, pk-pkΔ) zeroes the error mean but leaves its variance signal-dependent, TPDF (triangular, pk-pk2Δ, sum of two RPDF draws — the default) renders mean and variance signal-independent at constantΔ²/4total (+4.77 dB over bare rounding, flat and benign). Optional error feedback shapes the noise throughNTF = 1 − z⁻¹(+6 dB/oct tilt, DC zero, total power ×2) orNTF = (1 − z⁻¹)²(+12 dB/oct, double DC zero, ×6) — noise leaves the ear's sensitive low/mid band for the top octave; the closed-form Parseval gains (×1 / ×2 / ×6) and the spectral tilt are both asserted by FFT in the unit tests, alongside the deadband-rescue pair (a 0.4Δ sine quantises to exact silence undithered but its fundamental survives ≈ 25× above the noise floor with TPDF). splitmix64 dither PRNG, seedable for bit-reproducible output; per-channel feedback state; channels draw independent dither. Distinct frombitcrusher(creative degradation: no dither, no shaping, aliasing sample-and-hold). Registry entry"dither"accepts JSONbits/mode("none"/"rpdf"/"tpdf") /shaping("off"/"first"/"second") /seedkeys. -
upward_compressor — the complementary dynamics quadrant to
compressor/expander: boosts quiet signal below the threshold by(1 − 1/R)of each dB of under-shoot (capped atrange_db), leaving peaks above the threshold untouched. The four-quadrant taxonomy fromdocs/audio/filter/'s dynamic-range reference is: above-threshold reduce = downward compression (compressor); below-threshold reduce = upward compression (this); below-threshold widen = downward expansion (expander); above-threshold widen = upward expansion. Where downward compression turns the loud parts down and the engineer adds make-up gain, upward compression turns the quiet parts up — narrowing the dynamic range from the bottom while preserving transient peaks and perceived punch. Static curve:under = max(0, threshold_db − env_db);boost_db = min(range_db, (1 − 1/R)·under).ratio = 1.0→ identity;ratio = ∞→ recover = 1 (lift quiet signal up to the threshold, subject to the range cap); finite ratios recover a fraction (2:1 → half each dB of under-shoot, 4:1 → three-quarters). Therange_dbcap (default 24 dB viaUpwardCompressor::upward) bounds noise-floor amplification as the input approaches silence;range_db = 0is identity. Soft-knee width centred on the threshold uses the same C¹ quadratic blend ascompressor/expander. One-pole peak-linked detector (attack on rising, release on falling) shared across channels so loud peaks on one channel hold the boost off both, keeping the stereo image. Registry entry"upward_compressor"accepts JSONthreshold_db/ratio/attack_ms/release_ms/knee_db/range_dbkeys. Distinct fromcompressor(reduces above threshold),expander(attenuates below threshold; this boosts), andgain_normalizer(slow whole-programme AGC, not per-sample peak-preserving). -
upward_expander — the fourth and final quadrant of the dynamics taxonomy, completing
compressor/upward_compressor/expander: boosts loud signal above the threshold by(R − 1)of each dB of over-shoot (boost_db = min(range_db, (R − 1)·over), capped atrange_db), leaving quiet signal below the threshold untouched. The four-quadrant taxonomy fromdocs/audio/filter/'s dynamic-range reference ("upward expansion makes the louder sounds above the threshold even louder"): above-threshold reduce = downward compression (compressor); above-threshold widen = upward expansion (this); below-threshold reduce = upward compression (upward_compressor); below-threshold widen = downward expansion (expander). Widens the dynamic range from the top — re-opening transients an over-compressed master has flattened, or accentuating the swell of a crescendo.ratio = 1.0→ identity;ratio = 2:1doubles each dB of over-shoot; therange_dbcap (default 12 dB viaUpwardExpander::upward) bounds peak amplification so an unbounded slope cannot drive the loudest peaks past full-scale;range_db = 0is identity. C¹ soft-knee quadratic blend centred on the threshold (the above-threshold mirror ofupward_compressor's knee). One-pole peak-linked detector (attack on rising, release on falling) shared across channels so a loud peak on one channel holds the boost on across both, preserving the stereo image. Registry entry"upward_expander"accepts JSONthreshold_db/ratio/attack_ms/release_ms/knee_db/range_dbkeys. Distinct fromcompressor(reduces above threshold),upward_compressor(boosts below threshold),expander(attenuates below threshold), andtransient_designer(two-envelope attack/sustain shaper, not a static-curve level-dependent boost).
- window (
WindowFunction) — reusable FIR analysis-window catalogue gathering the full closed-form family fromdocs/audio/filter/wikipedia-window-function.htmlin one place (previously the spectrogram hard-coded a three-case Hann / Hamming / Blackman switch and the resampler / true-peak detector each grew their own one-off Kaiser taper). Variants:Rectangular,Triangular(Bartlett, 1st-order B-spline),Welch(single parabolic sectionw[n] = 1 − ((n − N/2)/(N/2))², endpoint-nulling — the canonical window of Welch's periodogram-averaging PSD estimate),Parzen(4th-order B-spline / de la Vallée Poussin, the smoothest piecewise-cubic polynomial taper,2·(1/L)³endpoints),Hann,Hamming(optimala = [0.53836, 0.46164]),Blackman(classicα = 0.16),BlackmanExact(rational7938/18608…, non-nulling endpoints),Nuttall,BlackmanNuttall,BlackmanHarris,FlatTop(five-term cosine sum, minimal scalloping loss, partially negative),Sine/ cosine / half-sine,Lanczos(sinc(2n/N − 1)), and the parameterised adjustable windowsGaussian(σ)(σ ≤ 0.5),Tukey(α)(α = 0 ≡rectangular,α = 1 ≡Hann),Kaiser(β)(I₀(β·√(1 − r²)) / I₀(β), localf64Bessel-I₀series). General alternating cosine-sum formw[n] = a0 − a1·cos(2πn/N) + a2·cos(4πn/N) − …on the symmetricN = L − 1convention (matching the spectrogram's existingdenom = n − 1). API:value(n, len)/generate(len)/generate_f32(len)plus thecoherent_gain(DC amplitude correction) andequivalent_noise_bandwidth(ENBW in DFT bins; rectangular = 1, Hann → 1.5, Blackman / flat-top progressively wider) metrics. The polynomial B-spline family widens its ENBW with spline order: rectangular 1.0 < Welch ≈ 1.20 < Triangular ≈ 1.34 < Parzen ≈ 1.92. Pure utility — not anAudioFilter, no registry entry. - frac_delay (
FracDelayLine) — per-channel ring buffer read at arbitrary fractional (between-sample) delays, with the reconstruction kernel chosen at construction. This is the bandlimited-interpolation problem stated indocs/audio/filter/jos-bandlimited-interpolation.html/jos-theory-of-sample-rate-conversion.html/jos-resample.html: a signal bandlimited toFs/2is, by Shannon's sampling theorem, uniquely recoverable at any continuous timepfrom its samples (x(t) = Σ x(nTs)·hₛ(t − nTs)withhₛthe normalised sinc). Time-varying delay effects (chorus/flanger/vibrato/pitch_shift, Doppler, comb resonators) all need such reads and currently each hand-roll a two-tap linear blend; this primitive gathers the kernel family in one place.Interpvariants:Linear(two-tap(1 − f)·x[i] + f·x[i+1]; cheapest, treble roll-off deepest atf = 0.5);Hermite(four-tap C¹ Catmull–Rom cubic — centred-difference tangents, markedly flatter passband for one extra tap pair);Lagrange(n)(the order-npolynomial through then + 1nearest taps via the product Lagrange basisLⱼ(x) = Πₘ≠ⱼ (x − xₘ)/(xⱼ − xₘ);n = 1reduces toLinear,n = 3is the cubic-Lagrange the reference relates to a truncated sinc, clamped to[1, 8]since high-order equally-spaced interpolation is ill-conditioned); andSinc { half_taps, beta }(the most faithful — windowed-sinc reconstructionΣ x[k]·sinc(p − k)truncated to2·half_tapsneighbours and Kaiser-tapered viawindow::Window::Kaiserto suppress truncation ripple, DC-normalised so a constant input reconstructs exactly,half_tapsclamped to[1, 32]). Newest-boundary reads use edge extension (clamp to the most recent sample) so a kernel reaching one or two taps past the head degrades gracefully instead of injecting a spurious zero;max_delay()reports the largest delay readable without the kernel running off the buffered history (capacity − 1 − reach, wherereachgrows with kernel width). All four kernels reproduce a constant DC and a pure integer delay exactly;Lagrange(3)is verified exact on a quadratic;Sinc(16, β=9)tracks an 8 kHz sine ≥ 4× more faithfully than linear at the worst-casef = 0.5. Pure utility — not anAudioFilter, no registry entry; mirrors thewindowcatalogue.
[dependencies]
oxideav-audio-filter = "0.0"Criterion harness benches/filters.rs measures the per-sample DSP cost of seven
representative filter families on deterministic xorshift32-synthesised PCM:
biquad_lpf— single second-order IIR (DF-II-T,f64state), stereo F32 @ 48 kHzequalizer_3band— low-shelf + peaking + high-shelf cascade, stereo F32 @ 48 kHzloudness_itu— BS.1770-4 K-weighting + RLB HPF + per-channel mean-square, stereo F32 @ 48 kHzcompressor— peak-detector soft-knee compressor, stereo F32 @ 48 kHzreverb— Schroeder 4 combs ║ 2 all-passes, stereo F32 @ 48 kHzresample_44k1_48k— polyphase windowed-sinc rate conversion, mono F32true_peak_4x— 4× polyphase Kaiser-FIR inter-sample peak detection, stereo F32 @ 48 kHz
Every PCM input is built once outside the timed region; the bench body
exercises only the filter's public AudioFilter::process / process_in_place
path. Run with:
cargo bench -p oxideav-audio-filter --bench filtersMIT — see LICENSE.
