pyFDN.auxiliary package#

Submodules#

pyFDN.auxiliary.acoustics module#

Acoustics and RT related functions.

pyFDN.auxiliary.acoustics.echo_density(ir, n=1024, fs=48000.0, pre_delay=0, mixing_thresh=1.0, hop=500)[source]#

Echo density and mixing time (Abel & Huang 2006).

Computes the transition time between early reflections and stochastic reverberation assuming sound pressure in a reverberant field is Gaussian distributed.

Reference: Abel & Huang (2006), “A simple, robust measure of reverberation echo density”, Proc. 121st AES Convention, San Francisco.

Parameters:
  • ir (ArrayLike) – Impulse response (1 channel only). Converted to 1D.

  • n (int) – Window length (must be even). Default 1024.

  • fs (float) – Sampling rate in Hz. Default 48000.

  • pre_delay (int) – Onset delay in samples for mixing time. Default 0.

  • mixing_thresh (float) – Normalized echo density threshold for mixing time (Abel & Huang use 1). Default 1.0.

  • hop (int) – Hop size in samples for sparse analysis. Default 500.

Return type:

tuple[float, ndarray]

Returns:

  • t_abel (float) – Mixing time in milliseconds (time at which echo density first exceeds mixing_thresh, relative to pre_delay). 0 if not found.

  • echo_dens (np.ndarray) – Echo density vector (length = len(ir)), normalized; interpolated from sparse analysis.

pyFDN.auxiliary.acoustics.edc(ir, axis=0)[source]#

Energy decay curve: backward cumulative sum of squared signal along an axis.

EDC(t) = sum(ir[t:]^2), so the curve decreases from total energy to zero. Typically used with impulse responses with shape (n_samples, n_channels).

Parameters:
  • ir (ArrayLike) – Signal(s). If 1D, EDC of that signal. If 2D (e.g. samples x channels), EDC is computed along the time axis for each channel.

  • axis (int) – Axis along which time runs (default 0). EDC is computed along this axis.

Returns:

Same shape as ir. Values are non-negative and non-increasing along axis.

Return type:

ndarray

pyFDN.auxiliary.acoustics.estimate_initial_level_bands(ir, rt, fs, fc=1000.0, start=-4.0, n=8, filter_order=8)[source]#

Estimate the initial level of the exponential decay per octave band.

Companion to estimate_rt_bands() (same octave filterbank). Models the squared band-filtered impulse response as L^2 * 10^(-6 t / T) and matches the total band energy, see slope_amplitude_to_level(). This replaces the DecayFitNet initial-level estimate used in the MATLAB example_RIR2FDN.

Parameters:
Return type:

tuple[ndarray, ndarray]

Returns:

  • level ((n_bands,) ndarray) – Initial level (linear amplitude) per band.

  • f_centre ((n_bands,) ndarray) – Centre frequencies in Hz corresponding to each level.

pyFDN.auxiliary.acoustics.estimate_rt_bands(ir, fs, fc=1000.0, start=-4.0, n=8, filter_order=8, decay_db=30.0)[source]#

Estimate RT in octave bands via Butterworth bandpass filtering.

Filters the impulse response into octave bands (octave_bands(), octave_band_filterbank()), then fits a line to the Schroeder decay curve of each band: the fit starts at -5 dB and spans decay_db, and its slope is extrapolated to a 60 dB decay.

Assumes a single-slope decay per band. For multi-exponential decays (coupled rooms) estimate the slopes with a dedicated multi-slope estimator and convert its amplitudes with slope_amplitude_to_level().

Default bands: 63, 125, 250, 500, 1000, 2000, 4000, 8000 Hz (start=-4, n=8). Bands whose upper edge reaches fs/2 are dropped.

Parameters:
  • ir (ArrayLike) – Impulse response.

  • fs (float) – Sampling rate in Hz.

  • fc (float) – Octave-band reference centre frequency in Hz (default 1000).

  • start (float) – Octave offset of the lowest band relative to fc (default -4 → 62.5 Hz).

  • n (int) – Number of octave bands (default 8).

  • filter_order (int) – Butterworth filter order (default 8).

  • decay_db (float) – Decay range in dB used for the linear fit. The default 30 dB fit is extrapolated to a 60 dB reverberation time.

Return type:

tuple[ndarray, ndarray]

Returns:

  • rt ((n_bands,) ndarray) – Estimated RT in seconds per band.

  • f_centre ((n_bands,) ndarray) – Centre frequencies in Hz corresponding to each RT value.

pyFDN.auxiliary.acoustics.octave_band_filterbank(bands, fs, filter_order=8)[source]#

Butterworth bandpass filters (SOS) for the given band edges.

Parameters:
  • bands (ndarray) – Lower and upper band edges in Hz, e.g. from octave_bands().

  • fs (float) – Sampling rate in Hz.

  • filter_order (int) – Order of the bandpass filters (default 8). The Butterworth prototype order is filter_order // 2, so the value counts poles of the bandpass, not of the lowpass prototype.

Returns:

One SOS array per band.

Return type:

list[ndarray]

pyFDN.auxiliary.acoustics.octave_bands(fc=1000.0, start=-4.0, n=8, fs=None)[source]#

Octave band edges and centre frequencies.

Centre frequencies are fc * 2**k for k = start start + n - 1; the band edges are the centre frequency divided and multiplied by sqrt(2).

Parameters:
  • fc (float) – Reference centre frequency in Hz (default 1000).

  • start (float) – Octave offset of the lowest band relative to fc (default -4 → 62.5 Hz).

  • n (int) – Number of octave bands (default 8).

  • fs (float | None) – If given, bands whose upper edge reaches Nyquist (fs/2) are dropped.

Return type:

tuple[ndarray, ndarray]

Returns:

  • bands ((n_bands, 2) ndarray) – Lower and upper edge of each band in Hz.

  • f_centre ((n_bands,) ndarray) – Centre frequency of each band in Hz.

pyFDN.auxiliary.acoustics.rt_to_gain_per_sample(rt, fs)[source]#

Convert reverb time (seconds) to gain coefficient per sample.

The gain g satisfies g^(rt*fs) = 10^(-3), i.e. about -30 dB after rt seconds.

Return type:

float

pyFDN.auxiliary.acoustics.rt_to_slope(rt, fs)[source]#

Convert reverb time (RT, seconds) to energy decay slope (dB per sample).

Return type:

ndarray

pyFDN.auxiliary.acoustics.slope_amplitude_to_level(amplitude, decay_time, fs)[source]#

Initial amplitude of an exponential decay from its energy (EDC amplitude).

A decay with initial amplitude L and reverberation time T, i.e. the envelope L * 10**(-3 t / T), carries the energy E = L**2 * T * fs / (6 ln 10), so L = sqrt(6 ln(10) E / (T fs)).

E is the amplitude of the energy decay curve at t = 0, which is what multi-slope estimators (DecayFitNet, Bayesian decay analysis) report as the slope amplitude A — one value per slope and band. The conversion is therefore how a multi-slope estimate becomes a set of per-slope FDN levels. Note that estimators usually normalise the EDC to 0 dB, in which case the amplitudes must be multiplied by the reported normalisation value first.

Slopes with decay_time == 0 are inactive and map to level 0.

Parameters:
  • amplitude (ArrayLike) – Energy of the decay, i.e. the EDC amplitude of the slope. Any shape.

  • decay_time (ArrayLike) – Reverberation time in seconds, broadcastable against amplitude.

  • fs (float) – Sampling rate in Hz.

Returns:

Initial level (linear amplitude), broadcast shape of the inputs.

Return type:

ndarray

See also

estimate_initial_level_bands

single-slope band levels straight from an IR.

pyFDN.auxiliary.acoustics.slope_to_rt(slope, fs)[source]#

Convert slope (dB/sample) to reverb time in seconds.

Return type:

ndarray

pyFDN.auxiliary.acoustics.sos_gain_per_sample_curves(sos, delays, nfft=512)[source]#

Magnitude response (gain per sample vs angle) for a per-channel SOS bank.

Evaluates \(|H(e^{j\omega})|\) at nfft angles from 0 to \(\pi\) (Nyquist) for each channel’s SOS cascade, then scales by delay length so that the result is gain per sample: for channel j with delay m_j, the curve is \(|H|^{1/m_j}\), so that after m_j samples the effective gain is \(|H|\). Useful for plotting absorption/gain curves (e.g. on a pole plot).

Parameters:
Return type:

tuple[ndarray, ndarray]

Returns:

  • angles ((nfft,) array) – Angles in rad/sample, 0 to pi.

  • magnitude ((nfft, N) array) – Gain per sample (linear), i.e. \(|H(e^{j\omega})|^{1/m}\) per channel.

pyFDN.auxiliary.allpass module#

Allpass FDN helpers (Poletti MIMO reverberator, uniallpass test, etc.).

Based on Poletti (1995) and “Allpass Feedback Delay Networks” by Sebastian J. Schlecht.

pyFDN.auxiliary.allpass.is_allpass(A, B, C, D, delays, tol=1e-09)[source]#

Test whether the delay state-space system is allpass.

Checks that the determinant transfer function has numerator = reversed(denominator) (up to sign). See “Allpass Feedback Delay Networks” by Sebastian J. Schlecht.

Parameters:
  • A (ArrayLike) – Delay state-space matrices.

  • B (ArrayLike) – Delay state-space matrices.

  • C (ArrayLike) – Delay state-space matrices.

  • D (ArrayLike) – Delay state-space matrices.

  • delays (ArrayLike) – Delay lengths (samples), length N.

  • tol (float) – Tolerance for coefficient comparison.

Return type:

tuple[bool, ndarray, ndarray]

Returns:

  • is_a (bool) – True if allpass.

  • den (ndarray) – Denominator polynomial (z^{-1} ordering).

  • num (ndarray) – Numerator polynomial (z^{-1} ordering).

pyFDN.auxiliary.allpass.is_paraunitary(ir, tol=1e-09)[source]#

Test whether a MIMO impulse response is paraunitary (lossless).

For real IR matrix H(t), checks that sum_t H(t) H(t)’ = I (output correlation) and sum_t H(t)’ H(t) = I (input correlation).

Parameters:
  • ir (ArrayLike) – Impulse response [time, output, input].

  • tol (float) – Tolerance for identity check.

Return type:

tuple[bool, ndarray, float]

Returns:

  • is_p (bool) – True if paraunitary.

  • test_matrix (ndarray) – Output correlation matrix (n_out, n_out); should be identity.

  • max_off_diagonal (float) – Max absolute off-diagonal value in test_matrix.

pyFDN.auxiliary.allpass.is_uniallpass(A, B, C, D, tol=1e-09)[source]#

Test whether the FDN is uniallpass (lossless with a diagonal Lyapunov matrix).

See Michaletzky, G. Factorization of discrete-time all-pass functions; and “Allpass Feedback Delay Networks” by Sebastian J. Schlecht.

Parameters:
  • A (ArrayLike) – Delay state-space matrices (feedback, input gain, output gain, direct).

  • B (ArrayLike) – Delay state-space matrices (feedback, input gain, output gain, direct).

  • C (ArrayLike) – Delay state-space matrices (feedback, input gain, output gain, direct).

  • D (ArrayLike) – Delay state-space matrices (feedback, input gain, output gain, direct).

  • tol (float) – Tolerance for zero and diagonal checks.

Return type:

tuple[bool, ndarray]

Returns:

  • is_a (bool) – True if the system is uniallpass.

  • P (ndarray) – Solution of discrete Lyapunov A P A’ - P + B B’ = 0; diagonal if uniallpass. All-NaN when A is not strictly stable, see the note below.

Notes

The Lyapunov equation only has a (unique, finite) solution when A is strictly stable. If A itself is lossless – as in the allpass-in-FDN structure, where the feedback matrix has all its eigenvalues on the unit circle – no such P exists, and the linear system scipy solves is exactly singular. Rather than let that surface as an ill-conditioned solve (which raises or returns garbage depending on the LAPACK build), the spectral radius is checked up front and the system reported as not uniallpass.

pyFDN.auxiliary.allpass.nested_allpass(g)[source]#

Create Gardner’s nested allpass FDN (SISO).

Iteratively nests a feedforward/back allpass around the previous FDN. From Gardner, W. G. (1992). A real-time multichannel room simulator. J. Acoust. Soc. Am. 92, 1–23. See “Allpass Feedback Delay Networks”, Schlecht.

Parameters:

g (ArrayLike) – Feedforward/back gains for each nesting stage.

Return type:

tuple[ndarray, ndarray, ndarray, ndarray]

Returns:

  • A (ndarray (N, N)) – Feedback matrix.

  • B (ndarray (N, 1)) – Input gain (column vector).

  • C (ndarray (1, N)) – Output gain (row vector).

  • D (ndarray (1, 1)) – Direct gain (scalar).

pyFDN.auxiliary.allpass.poletti_allpass(g, U)[source]#

Create Poletti’s MIMO unitary reverberator (allpass FDN).

From Poletti, M. (1995). A unitary reverberator for reduced colouration in assisted reverberation systems. INTER-NOISE and NOISE-CON, 5, 1223–1232.

Parameters:
  • g (float) – Scalar feedback gain (e.g. 0.7).

  • U (ndarray) – Unitary (orthogonal) feedback matrix.

Returns:

A, B, C, D – Delay state-space matrices: A = -g*U, B = (1+g)*I, C = (1-g)*U, D = g*I.

Return type:

tuple[ndarray, ndarray, ndarray, ndarray]

pyFDN.auxiliary.allpass.series_allpass(g)[source]#

Create Schroeder’s series allpass FDN (SISO).

Iterative series connection of feedforward/back allpass filters (same as seriesAllpass.m). Each stage appends one delay line via seriesFDNinAllpass. From Schroeder & Logan (1961). “Colorless” artificial reverberation. IRE Trans. Audio AU-9, 209–214. See “Allpass Feedback Delay Networks”, Schlecht.

Parameters:

g (ArrayLike) – Per-section gains (e.g. in (0, 1)).

Return type:

tuple[ndarray, ndarray, ndarray, ndarray]

Returns:

  • A (ndarray (N, N)) – Feedback matrix.

  • B (ndarray (N, 1)) – Input gain (column vector).

  • C (ndarray (1, N)) – Output gain (row vector).

  • D (ndarray (1, 1)) – Direct gain (scalar).

pyFDN.auxiliary.audio module#

Helpers for the compact audio examples distributed with pyFDN.

pyFDN.auxiliary.audio.audio_metadata(name)[source]#

Return attribution and license metadata for a packaged audio sample.

Return type:

dict[str, Any]

pyFDN.auxiliary.audio.available_audio()[source]#

Return the names accepted by load_audio().

Return type:

tuple[str, ...]

pyFDN.auxiliary.audio.load_audio(name, *, fs=None, mono=True)[source]#

Load an audio sample distributed with pyFDN.

Parameters:
  • name (str) – Sample name, with or without the .wav extension. See available_audio().

  • fs (int | None) – Target sampling rate. A differing source rate is resampled.

  • mono (bool) – If True, retain only the first channel of multichannel audio.

Return type:

tuple[ndarray, int]

Returns:

  • signal (np.ndarray) – Audio samples as float64.

  • fs (int) – Sampling rate of the returned signal.

pyFDN.auxiliary.coupled_rooms module#

Coupled rooms FDN builder.

Translation of example_coupledRooms.m from fdnToolbox. Original MATLAB code: (c) Sebastian Jiro Schlecht, 2020 Python translation: Facundo Franchino, 2025

pyFDN.auxiliary.coupled_rooms.create_coupled_rooms_fdn()[source]#

Create a coupled rooms FDN.

This function builds a 12-delay-line FDN modeling two acoustically coupled rooms with different reverberation characteristics.

Returns:

Impulse response (numpy array, shape [samples, 2]) fs: Sample rate (48000 Hz) feedback_matrix: The 12x12 feedback matrix used delay_lengths: The 12 delay lengths in samples

Return type:

ir

Example

>>> torch.manual_seed(5)
>>> np.random.seed(5)
>>> ir, fs, fb, delays = create_coupled_rooms_fdn()
>>> ir.shape
(96000, 2)

pyFDN.auxiliary.delay module#

Delay related functions.

pyFDN.auxiliary.delay.flamo_delay_feedback_matrix(model, delays, delays_in, delays_out, *, inplace=False)[source]#

Place a delay-matrix-delay chain in a FLAMO FDN feedback path.

The model is expected to have the topology produced by pyFDN.dss_to_flamo(). Its feedforward delay is set to delays and its feedback matrix is wrapped by delays of delays_in and delays_out samples. By default, the operation returns a deep copy.

Return type:

Any

pyFDN.auxiliary.delay.matrix_delay_approximation(matrix)[source]#

Rank-1 approximation of matrix group delay.

Return type:

tuple[ndarray, ndarray]

pyFDN.auxiliary.delay.mgrpdelay(matrix)[source]#

Group delay for each entry of an FIR matrix.

Return type:

tuple[ndarray, ndarray]

pyFDN.auxiliary.delay.ms_to_smp(ms, fs)[source]#

Convert milliseconds to samples.

Return type:

ndarray

pyFDN.auxiliary.delay.swap_flamo_recursion_paths(model, *, inplace=False)[source]#

Swap the feedforward and feedback paths of a FLAMO FDN recursion.

Return type:

Any

pyFDN.auxiliary.flamo module#

Standard wrappers for FLAMO modules that accept numpy arrays and return FLAMO modules.

All functions require flamo to be installed. They take numpy arrays and common options (nfft, device, etc.) and return configured FLAMO dsp modules with values assigned.

pyFDN.auxiliary.flamo.assemble_fdn_core(*, input_gain, feedback, delays, output_gain, direct=None, post_delay=None, post_matrix=None, post_output=None)[source]#

Wire pre-built FLAMO modules into an FDN core (no FFT/iFFT wrapping).

Single source of truth for the FDN signal flow, shared by the render path (pyFDN.dss_to_flamo()) and the training builder (pyFDN.train.trainable_from_build()). All arguments are already-built FLAMO dsp/system modules; this only composes them, so leaf names and topology stay identical across both callers (and match the names pyFDN.extract_build() requires).

Signal flow:

input_gain -> [recursion: fF = delay -> (post_delay)
                          fB = feedback -> (post_matrix)]
           -> output_gain -> (post_output)

with the direct path direct summed in parallel when provided.

The three optional filter slots are the same three hooks, in the same three positions and under the same three names, that pyFDN.process_fdn() takes in numpy – post_delay on the shared delay output (so it shapes both what leaves the network and what is fed back), post_matrix on the feedback path only, post_output on the wet signal only. An FDNBuild has a field of each name, holding the SOS bank a hook bakes down to; a hook holding something that does not bake – a nested core, a time-varying matrix – simply has no build field to go in.

Parameters:
  • input_gain (Any) – Input gain B (named input_gain) and output gain C (named output_gain).

  • output_gain (Any) – Input gain B (named input_gain) and output gain C (named output_gain).

  • feedback (Any) – Feedback matrix placed on the recursion feedback branch (fB); a plain Gain/Filter (render) or a parametrized Matrix (training).

  • delays (Any) – Delay module on the recursion forward branch (named delay).

  • direct (Any) – Direct path D. When None the core is the plain feedforward Series (no Parallel wrapper) – this keeps core.feedback_loop reachable for losses such as sparsity_loss. When provided the core is Parallel(brA=fdn_branch, brB=direct).

  • post_delay (Any) – In-loop filter after the delays (named post_delay). Any module of input/output size N: a AttenuationFilter or a plain sos_filter_module() for attenuation, or a whole nested core such as a Schroeder allpass. Only an SOS filter here is extractable into an FDNBuild.

  • post_matrix (Any) – Filter on the feedback path after the feedback matrix (named post_matrix); the position a time-varying mixing stage occupies. Adding it names the feedback branch mixing_matrix rather than leaving the matrix bare on fB, which is where pyFDN.extract_build() looks for it either way.

  • post_output (Any) – Per-output filter after the output gain (named post_output); typically an OutputEQ.

Returns:

core – The FDN core, ready for wrap_fdn_shell().

Return type:

Any

pyFDN.auxiliary.flamo.core_alias_decay_db(core)[source]#

The anti-aliasing decay the FLAMO core was built with, in dB.

FLAMO containers (Series/Parallel/Recursion) assert that every module agrees on alias_decay_db, so the core is the single source of truth – reading it back beats threading the value through by hand and risking a mismatch with the modules.

Return type:

float

pyFDN.auxiliary.flamo.delay_module(lengths_seconds, nfft, *, fs, device=None, dtype=None, isint=True, alias_decay_db=0, requires_grad=False)[source]#

Build a FLAMO parallelDelay module from delay lengths in seconds.

Values are assigned directly (no sample conversion); buffer size is derived from fs.

Parameters:
  • lengths_seconds (ndarray) – 1D array of delay lengths in seconds, one per channel.

  • nfft (int) – FFT size for the FLAMO module.

  • fs (float) – Sampling rate in Hz (used for buffer size max_len = max(lengths_seconds) * fs).

  • device (torch device or None) – Device for the module; default is cuda if available else cpu.

  • dtype (torch.dtype or None) – Optional dtype for module parameters (e.g., torch.float64). If None, uses float32 to preserve previous behavior.

  • isint (bool) – Whether delays are integer-sample (True) or fractional.

  • alias_decay_db (float) – FLAMO alias decay in dB.

  • requires_grad (bool) – Whether the delay parameters are trainable.

Returns:

FLAMO parallelDelay module with lengths assigned (in seconds).

Return type:

flamo.processor.dsp.parallelDelay

pyFDN.auxiliary.flamo.fir_matrix_module(coeffs, nfft, *, device=None, dtype=None, requires_grad=False)[source]#

Build a FLAMO Filter module from a matrix FIR coefficient array.

Parameters:
  • coeffs (ndarray) – FIR matrix in z^{-1} convention, shape (n_output, n_input, n_taps) (e.g. a paraunitary feedback matrix).

  • nfft (int) – FFT size for the FLAMO module.

  • device (torch device or None) – Device for the module; default is cuda if available else cpu.

  • dtype (torch.dtype or None) – Optional dtype for module parameters (e.g., torch.float64). If None, uses float32.

  • requires_grad (bool) – Whether the filter parameters are trainable.

Returns:

FLAMO Filter module with coefficients assigned.

Return type:

flamo.processor.dsp.Filter

pyFDN.auxiliary.flamo.flamo_freq_response(model, fs=48000, identity=False)[source]#

Return a FLAMO model’s (complex) frequency response as a NumPy array.

The NumPy-facing counterpart of FLAMO’s model.get_freq_response() and the frequency-domain sibling of flamo_time_response(). It detaches the returned tensor from any autograd graph, transfers it to CPU memory, and preserves its shape and (complex) dtype. Take np.abs(...) for the magnitude response, np.angle(...) for the phase.

get_freq_response evaluates over nfft DFT bins by temporarily swapping the model’s input/output layers to FFT and restoring them before returning, so this is side-effect-free regardless of the model’s current output layer.

Parameters:
  • model – FLAMO model exposing get_freq_response (e.g. a Shell).

  • fs (int) – Sampling frequency passed to FLAMO.

  • identity (bool) – Whether to request FLAMO’s input-free identity response.

Returns:

Complex frequency response with the same shape and numeric dtype as FLAMO’s tensor.

Return type:

ndarray

pyFDN.auxiliary.flamo.flamo_process(model, signal, *, fs=None, tail_seconds=0.0, dtype=None)[source]#

Run a 1-D signal through a FLAMO Shell model offline.

Wraps the boilerplate of turning a NumPy signal into the (batch, time, channel) tensor FLAMO expects, running a no-grad forward pass, and converting the result back to NumPy.

The model convolves in the frequency domain over a block of length nfft (read from the model’s input layer), so the signal is truncated or zero-padded to nfft. Because that is a circular convolution, a long reverb tail can wrap around onto the start of the block; pass tail_seconds to reserve that much trailing silence for the tail to decay into (requires fs).

Parameters:
  • model – FLAMO Shell whose input layer exposes nfft (e.g. the output of pyFDN.dss_to_flamo()).

  • signal (ndarray) – 1-D input signal.

  • fs (int | None) – Sampling rate, required only when tail_seconds > 0.

  • tail_seconds (float) – Trailing silence to reserve so the reverb tail does not wrap around.

  • dtype (torch.dtype or None) – Tensor dtype for the forward pass; defaults to float32.

Returns:

Squeezed model output on CPU.

Return type:

ndarray

pyFDN.auxiliary.flamo.flamo_time_response(model, fs=48000, identity=False)[source]#

Return a FLAMO model’s time response as a NumPy array.

This is the NumPy-facing counterpart of FLAMO’s model.get_time_response(). It detaches the returned tensor from any autograd graph, transfers it to CPU memory, and preserves its dimensions and dtype during conversion.

Parameters:
  • model – FLAMO model exposing get_time_response.

  • fs (int) – Sampling frequency passed to FLAMO.

  • identity (bool) – Whether to request FLAMO’s input-free identity response.

Returns:

Time response with the same shape and numeric dtype as FLAMO’s tensor.

Return type:

ndarray

pyFDN.auxiliary.flamo.gain_module(values, nfft, *, device=None, dtype=None, alias_decay_db=0, requires_grad=False)[source]#

Build a FLAMO Gain module from a numpy array.

Parameters:
  • values (ndarray) – Gain matrix, shape (n_output, n_input). Will be cast to float64.

  • nfft (int) – FFT size for the FLAMO module.

  • device (torch device or None) – Device for the module; default is cuda if available else cpu.

  • dtype (torch.dtype or None) – Optional dtype for module parameters (e.g., torch.float64). If None, uses float32.

  • alias_decay_db (float) – FLAMO alias decay in dB.

  • requires_grad (bool) – Whether the gain parameters are trainable.

Returns:

FLAMO Gain module with values assigned.

Return type:

flamo.processor.dsp.Gain

pyFDN.auxiliary.flamo.hook_module(value, nfft, *, name, device=None, dtype=None, alias_decay_db=0.0)[source]#

One FLAMO module for a filter hook, from an SOS bank, a module, or several.

The three hooks of assemble_fdn_core() each take a single module, but what a caller has is often an (n_sections, 6, n_channels) SOS array, and occasionally more than one thing to put in the same position. This resolves all three cases:

  • None – no module.

  • an array – built with sos_filter_module().

  • a FLAMO module – passed through.

  • a sequence of the above – composed into a Series in the given order, with leaves named {name}_0, {name}_1, …

Parameters:
  • value (Any) – What to put in the hook.

  • nfft (int) – FFT size, matching the rest of the model.

  • name (str) – The hook’s name ("post_delay", "post_matrix", "post_output"), used to name the leaves of a composed Series.

Return type:

Any

pyFDN.auxiliary.flamo.matrix_module(values, nfft, *, matrix_type='orthogonal', device=None, dtype=None, alias_decay_db=0, requires_grad=False)[source]#

Build a FLAMO Matrix initialized to values under a parametrization.

Unlike gain_module() (a plain value container), this preserves the flamo map that constrains the trainable matrix: "orthogonal" keeps it on the SO(N) manifold during optimization, "random" is unconstrained.

Parameters:
  • values (ndarray) – Square (N, N) initial feedback matrix.

  • nfft (int) – FFT size for the FLAMO module.

  • matrix_type (str) – "orthogonal" or "random".

  • device (Any) – Device; default is cuda if available else cpu.

  • dtype (Any) – Module dtype; defaults to float32.

  • alias_decay_db (float) – FLAMO alias decay in dB.

  • requires_grad (bool) – Whether the matrix is trainable.

Returns:

Matrix whose realized value (map(param)) equals values (within the parametrization; an SO(N) projection may apply for orthogonal).

Return type:

flamo.processor.dsp.Matrix

pyFDN.auxiliary.flamo.sos_filter_module(sos, nfft, *, device=None, dtype=None, alias_decay_db=0, requires_grad=False)[source]#

Build a FLAMO parallelSOSFilter from an SOS coefficient array.

Parameters:
  • sos (ndarray) – Shape (n_sections, 6, n_channels). Each section is [b0, b1, b2, a0, a1, a2] (e.g. from SDN wall_filters_sos).

  • nfft (int) – FFT size for the FLAMO module.

  • device (torch device or None) – Device for the module; default is cuda if available else cpu.

  • dtype (torch.dtype or None) – Optional dtype for module parameters (e.g., torch.float64). If None, uses float32 to preserve previous behavior.

  • alias_decay_db (float) – FLAMO alias decay in dB.

  • requires_grad (bool) – Whether the SOS coefficients are trainable. The sections are normalized to a0 = 1 here rather than by flamo’s normalize_a0 map, whose in-place writes break autograd; a0 is then held at 1 by masking its gradient, so the trained coefficients stay a valid SOS array.

Returns:

FLAMO parallelSOSFilter with coefficients assigned.

Return type:

flamo.processor.dsp.parallelSOSFilter

pyFDN.auxiliary.flamo.wrap_fdn_shell(core, *, nfft, dtype=None)[source]#

Wrap an FDN core in a FLAMO Shell that returns the impulse response.

The shell is FFT in, impulse response out: the input layer is an FFT and the output layer the iFFTAntiAlias that matches the core’s own alias_decay_db. A pyFDN model therefore means one thing wherever it is used – rendered, analyzed or trained – and the time domain is a property of how the model was built rather than something a caller sets afterwards.

The core evaluates the system on a circle of radius \(\gamma < 1\), so its response carries a \(\gamma^n\) envelope; the output layer removes it again. What comes out is the true impulse response, accurate to alias_decay_db (see pyFDN.trainable_from_build()). At alias_decay_db=0 the layer is an ordinary inverse FFT.

Parameters:
  • core (Any) – FDN core, e.g. from assemble_fdn_core(). Its alias_decay_db is read back off it, so the output layer cannot disagree with the modules it undoes.

  • nfft (int) – FFT size.

  • dtype (Any) – Dtype for the FFT/iFFT layers; defaults to float32.

Return type:

Any

See also

pyFDN.model_response

the shell’s output as a Response, including the magnitude spectrum a frequency-domain view wants.

pyFDN.auxiliary.flamo_graph module#

Traverse a FLAMO model and build a node tree or visualize it as a flowchart.

Flow: left-to-right. Series and Parallel are shown as boxes with nested modules. Recursion is shown with forward path (fF) and feedback path (fB) inside a box, with the feedback path drawn so the loop is visible (e.g. fB below fF).

pyFDN.auxiliary.flamo_graph.extract_build(model)[source]#

Extract a complete FDNBuild from a named FLAMO model graph.

The graph must be one pyFDN.assemble_fdn_core() would build: leaves named input_gain and output_gain, plus either mixing_matrix or the standard recursion feedback leaf fB. The delay can be named delay or be the graph’s only delay module. The sample rate is read from the delay module and is required: a graph that does not expose fs is malformed and raises ValueError.

All three filter hooks – post_delay, post_matrix, post_output – are read when present. Because a build is baked, a hook must hold something that bakes: a per-channel gain or an SOS bank. A hook holding anything else – a nested allpass core, a cascade of several modules – is refused rather than dropped, since a build missing it would render differently from the model it came from.

Return type:

FDNBuild

pyFDN.auxiliary.flamo_graph.feedback_matrix_module(model)[source]#

Return the live feedback-matrix module from a FLAMO FDN model.

Works for both a plain Series core and a Parallel core (an FDN summed with a direct path). The module returned is the one on the recursion’s feedback branch; apply module.map(module.param) to read the realized matrix in-graph – e.g. inside a training loss, where the detaching extraction path extract_build() would break gradients.

Return type:

Any

pyFDN.auxiliary.flamo_graph.flamo_model_to_nodes(model, name='root', *, include_shell_io=False)[source]#

Traverse a FLAMO model and build a tree of nodes (nested dicts).

Each node has: - type: “Shell” | “Series” | “Parallel” | “Recursion” | “Leaf” - name: str (from parent’s dict key or assigned) - module: the raw FLAMO module (for Leaf, the actual dsp module) - children: list of child nodes (for Series, Parallel; order preserved) - fF, fB: only for Recursion — nodes for forward and feedback path - input_layer, output_layer: only if include_shell_io and type is Shell

Parameters:
  • model (Any)

  • name (str) – Name for the root node.

  • include_shell_io (bool) – If True, include input_layer and output_layer as children for Shell.

Returns:

node – Root node (nested tree). Use flamo_nodes_flat() to get a list of all nodes.

Return type:

dict[str, Any]

pyFDN.auxiliary.flamo_graph.flamo_nodes_flat(root, path='root')[source]#

Flatten the node tree into a list of nodes, each with a ‘path’ key.

Parameters:
  • root (dict[str, Any]) – Root node from flamo_model_to_nodes().

  • path (str) – Path prefix for the root.

Returns:

Each dict has keys from the node plus “path” (e.g. “root/core/feedback_loop/fF”).

Return type:

list[dict[str, Any]]

pyFDN.auxiliary.flamo_graph.plot_flamo_graph(model, *, name='flamo', ax=None, scale=0.85, fontsize=9.0)[source]#

Draw the FLAMO model signal flow with matplotlib.

Signal flows left to right; only the feedback path of a Recursion flows right to left, drawn below the forward path with a loop back to a sum node at the forward path’s input.

Parameters:
  • model (Any)

  • name (str) – Name for the root node.

  • ax (Any) – Draw into this axes; otherwise a new figure sized to the layout is created.

  • scale (float) – Inches per layout unit when creating a new figure.

  • fontsize (float) – Base font size for leaf labels.

Returns:

ax

Return type:

Any

pyFDN.auxiliary.marimo_utils module#

marimo display helpers used by the example notebooks.

marimo is an optional dependency (the examples / test extras), so it is imported lazily inside each helper – importing pyFDN never requires marimo.

pyFDN.auxiliary.marimo_utils.labeled_audio(label, signal, *, fs, label_size='1.1em', gap=0)[source]#

Stack a text label above an audio player (a marimo element).

Convenience for A/B listening layouts: returns mo.vstack([label, audio]) with label rendered as sized HTML and signal as an mo.audio player at fs. marimo is imported lazily, so this only requires marimo when actually called.

Parameters:
  • label (str) – HTML/text shown above the player.

  • signal (ArrayLike) – Audio samples. 1-D for mono; a 2-D array is treated as multi-channel and oriented for mo.audio, so either (samples, channels) (the pyFDN render convention) or (channels, samples) plays correctly.

  • fs (float) – Sample rate in Hz.

  • label_size (str) – CSS font-size for the label (default "1.1em").

  • gap (float) – Vertical gap between the label and the player (default 0).

Return type:

Any

Returns:

A marimo vstack element.

pyFDN.auxiliary.math module#

Matrix polynomial and math operations.

pyFDN.auxiliary.math.adj_poly(polynomial_matrix, var='z^1', tol=-200.0)[source]#

Adjugate of a polynomial matrix via FFT evaluation.

Evaluates the matrix at N * L DFT points, takes the scalar adjugate() at every bin, and transforms back (approach of Henrion, Hromcik & Sebek 2000; translates adjPoly.m).

Parameters:
  • polynomial_matrix (ArrayLike) – Polynomial matrix of shape (N, N, L).

  • var (str) – Coefficient convention along axis 2: "z^1" — descending powers of z, last slice = z^0 (the loop_tf() convention); "z^-1" — ascending powers of z^{-1}, first slice = z^0 (the pyFDN convention used by det_polynomial()).

  • tol (float) – Noise floor in dB (relative to each entry’s maximum) used to trim the result to its actual degree.

Returns:

adj – Adjugate polynomial matrix (N, N, degree + 1) in the same convention as the input.

Return type:

ndarray

pyFDN.auxiliary.math.adjugate(A)[source]#

Adjugate matrix, valid also for singular and complex matrices.

Uses the SVD identity adj(A) = det(U V^H) V adj(S) U^H (with A = U S V^H), which holds even if A and S are singular.

Translates adjugate.m from fdnToolbox.

Return type:

ndarray

pyFDN.auxiliary.math.det_polynomial(polynomial_matrix)[source]#

Determinant of a polynomial matrix in the z^{-1} convention.

Coefficients are ordered as [z^0, z^{-1}, z^{-2}, …] along axis 2. Uses an FFT-based approach: evaluate at DFT points, compute scalar det at each frequency, then IFFT back.

Parameters:

polynomial_matrix (ndarray) – shape (N, N, L), polynomial matrix entries.

Returns:

1-D array of determinant polynomial coefficients,

ordered [z^0, z^{-1}, …], trimmed to the actual degree.

Return type:

ndarray

pyFDN.auxiliary.math.general_char_poly(delays, A)[source]#

Generalized characteristic polynomial (GCP) for delay state-space.

Implements the formula from Schlecht & Habets (2015), Time-varying feedback matrices in feedback delay networks. J. Acoust. Soc. Amer., 138(3), 1389-1398. Matches the reference generalCharPoly.m.

Parameters:
  • delays (ArrayLike) – Vector of delays in samples (length ND); used with z^{-1} convention.

  • A (ndarray) – Feedback matrix. If 2D (scalar matrix), GCP is built via submatrix determinants. If 3D of shape (N, N, L), polynomial matrix in z^{-1}.

Returns:

p – Generalized characteristic polynomial coefficients in z^{-1} ordering (index 0 = z^0, index k = z^{-k}).

Return type:

ndarray

pyFDN.auxiliary.math.interpolate_orthogonal(A, B, t)[source]#

Geodesic interpolation between two orthogonal matrices.

C(t) = A @ expm(t * logm(A.T @ B)). C(0)=A, C(1)=B; each C(t) is orthogonal.

Return type:

ndarray

pyFDN.auxiliary.math.is_orthogonal(Q, tol=1e-10)[source]#

Check if Q is orthogonal (Q.T @ Q ≈ I).

Return type:

bool

pyFDN.auxiliary.math.is_unilossless(A, tol=1e-10)[source]#

Test whether A is diagonally similar to an orthogonal matrix.

A is unilossless if there exists a diagonal D such that D^{-1} @ A @ D is orthogonal, i.e. the diagonal scaling is a similarity transform (inv(D) == E).

Translates isDiagonallySimilarToOrthogonal.m from fdnToolbox.

Return type:

bool

pyFDN.auxiliary.math.loop_tf(delays, A)[source]#

Loop transfer function P(z) = diag(z^m) - A as a polynomial matrix.

Coefficients are stored in the z^1 convention along axis 2 (descending powers of z, last slice = z^0). A polynomial feedback matrix (N, N, K) in z^{-1} convention is placed at the low-power end, i.e. the result is diag(z^m) - z^{K-1} A(z) (multiplied through by z^{K-1} to clear negative powers), matching loopTF.m.

Return type:

ndarray

pyFDN.auxiliary.math.matrix_convolution(A, B)[source]#

Matrix polynomial multiplication by convolution.

Return type:

ndarray

pyFDN.auxiliary.math.matrix_polyder(B, A)[source]#

Derivative of rational filter matrices in the z^{-1} convention.

Coefficients are ordered as [z^0, z^{-1}, z^{-2}, …] along axis 0.

Parameters:
  • B (ndarray) – Numerator coefficients, shape (order, N, M).

  • A (ndarray) – Denominator coefficients, shape (order, N, M).

Returns:

Numerator of the derivative, shape (order, N, M). P: Denominator of the derivative, shape (order, N, M).

Return type:

tuple[ndarray, ndarray]

pyFDN.auxiliary.math.matrix_polyval(P, z)[source]#

Evaluate a matrix polynomial P at the complex point z.

Return type:

ndarray

pyFDN.auxiliary.math.matrix_sqrt(A)[source]#

Matrix square root via eigenvalue decomposition.

sqrtm(A) = V @ sqrt(D) @ V^(-1) where A = V @ D @ V^(-1).

Parameters:

A (Tensor) – Square matrix (real, will be cast to complex for eig).

Returns:

Real matrix square root of A.

Return type:

Tensor

pyFDN.auxiliary.math.negpolyder(b, a, dont_truncate=False)[source]#

Derivative of rational polynomial with negative exponents.

Parameters:
  • b (ndarray) – Numerator coefficients

  • a (ndarray) – Denominator coefficients

  • dont_truncate (bool) – Leading zeros are not truncated

Returns:

Numerator coefficients of derivative p: Denominator coefficients of derivative

Return type:

tuple[ndarray, ndarray]

pyFDN.auxiliary.math.outer_sum_approximation(matrix)[source]#

Rank-1 approximation minimizing ||u + v^T - matrix||_F.

Return type:

tuple[ndarray, ndarray]

pyFDN.auxiliary.math.poly_degree(polynomial, tol=None)[source]#

Return the polynomial degree in the z^{-1} convention.

Coefficients are ordered as [z^0, z^{-1}, z^{-2}, …]; the degree is the index of the last coefficient whose magnitude is above the noise floor.

Return type:

int

pyFDN.auxiliary.math.polyder_rational(b, a)[source]#

Derivative of rational polynomial using quotient rule.

Return type:

tuple[ndarray, ndarray]

pyFDN.auxiliary.math.polydiag(p)[source]#

Construct a diagonal polynomial matrix from an array of polynomials.

Return type:

ndarray

pyFDN.auxiliary.plot module#

Plot utilities (matrix heatmap, system matrix layout, impulse response grid).

pyFDN.auxiliary.plot.animate(plot_fn, frames, *, labels=None, label_prefix='', label_format='', frame_ms=300, transition_ms=0, title=None)[source]#

Animate a sequence of frames built by any per-frame plotting function.

plot_fn(frame) is called for each entry in frames and must return a single-subplot Plotly figure (e.g. plot_matrix(), plot_impulse_response()). The traces of each figure become one animation frame; the first figure supplies the base layout (size, axes, color scale), to which a play/pause button and a slider are added.

This composes with the existing plot_* builders instead of re-deriving their styling. To animate a matrix C of shape (rows, cols, T) over time, with fixed color limits:

import functools

fig = pyFDN.animate(
    functools.partial(pyFDN.plot_matrix, zmin=-1, zmax=1),
    [C[:, :, k] for k in range(C.shape[2])],
    labels=t,
    label_prefix="t = ",
    label_format=".2f",
)
fig.show()
Parameters:
  • plot_fn (Callable[[Any], Any]) – Maps one frames entry to a Plotly figure. Use functools.partial() or a lambda to fix extra arguments (e.g. color limits) so every frame is built consistently.

  • frames (Sequence[Any]) – One argument per frame, passed positionally to plot_fn.

  • labels (Sequence[Any] | None) – Slider label per frame. Defaults to the frame index.

  • label_prefix (str) – Prefix shown before the current label (e.g. "t = ").

  • label_format (str) – Format spec applied to each label, e.g. ".2f". Empty uses str.

  • frame_ms (int) – Per-frame duration in milliseconds during playback. Default 300.

  • transition_ms (int) – Tween duration between frames in milliseconds. Default 0.

  • title (str | None) – Figure title. If None, the first frame’s title is kept.

Returns:

Call .show() to display.

Return type:

Any

pyFDN.auxiliary.plot.downsample_lttb(x, y, *, max_points=10000)[source]#

Downsample a line with Largest-Triangle-Three-Buckets.

LTTB keeps points that preserve the visual shape of the connected line. It is a better default for Plotly mode="lines" than min/max bucketing, because it avoids artificial vertical segments between bucket extrema.

Return type:

tuple[ndarray, ndarray]

pyFDN.auxiliary.plot.downsample_minmax(x, y, *, max_points=10000)[source]#

Downsample a line while preserving local minima and maxima.

This is intended for dense time-domain traces such as impulse responses, where naive stride decimation can miss narrow peaks. The returned samples are sorted by their original order, include the first and last sample, and use at most max_points points for long inputs.

Parameters:
  • x (ArrayLike | None) – X-values. If None, uses sample indices 0 .. len(y)-1.

  • y (ArrayLike) – Real-valued y-values.

  • max_points (int) – Maximum number of samples to return. Must be at least 4.

Returns:

x_ds, y_ds – Downsampled x- and y-values.

Return type:

tuple[ndarray, ndarray]

pyFDN.auxiliary.plot.downsample_plotly_trace(trace, *, max_points=10000, method='lttb')[source]#

Return a copy of a Plotly trace with downsampled x and y data.

Traces without y data are returned unchanged. If a trace has no x data, sample indices are generated.

Return type:

Any

pyFDN.auxiliary.plot.downsampled_scatter(*args, max_points=10000, method='lttb', **kwargs)[source]#

Create a Plotly go.Scatter trace with downsampled line data.

The call mirrors plotly.graph_objects.Scatter and only adds the max_points and method keywords:

fig.add_trace(pyFDN.downsampled_scatter(x=t, y=ir, max_points=5000))

Return type:

Any

pyFDN.auxiliary.plot.plot_FDN_build(build, *, nfft=512, zmin=None, zmax=None, title=None)[source]#

Plot the parameters stored in an pyFDN.FDNBuild.

This is a convenience wrapper around plot_fdn_parameter(). A multichannel build.post_output is rendered as one curve per output channel. Each of the build’s three filter hooks becomes its own row, and only the ones it carries.

Return type:

Any

pyFDN.auxiliary.plot.plot_db_per_sample(sos, delays, *, fs=None, nfft=512, title=None)[source]#

Plot SOS magnitude responses normalized by delay length (dB per sample).

Each curve is the magnitude response of one delay line’s filter cascade divided by its delay length, \(20 \log_{10}|H_i| / m_i\). Filters designed for a homogeneous decay (a common T60 target) collapse onto the same gain-per-sample curve. Curve colors encode the delay length (Viridis, short = dark, long = bright).

Parameters:
  • sos (ArrayLike) – Per-delay-line SOS bank, same layout as pyFDN.td.SOSBank: (n_sections, 6, N).

  • delays (ArrayLike) – Delay lengths in samples, shape (N,).

  • fs (float | None) – Sample rate in Hz. If given, the responses are plotted over a logarithmic frequency axis in Hz; otherwise over rad/sample.

  • nfft (int) – Number of frequency points. Default 512.

  • title (str | None) – Figure title.

Returns:

Call .show() to display.

Return type:

Any

pyFDN.auxiliary.plot.plot_edc(*irs, fs=None, labels=None, db=True, normalize=False, dynamic_range=100.0, title='Energy decay curve', max_points=10000)[source]#

Plot the energy decay curve (EDC) of one or more impulse responses.

The EDC is the backward energy integral (pyFDN.edc()); by default it is shown in dB (pyFDN.sq_to_db()). Dense traces are downsampled with LTTB (downsampled_scatter()) before plotting.

Parameters:
  • *irs (ArrayLike) – One or more 1-D impulse responses, plotted as overlaid curves.

  • fs (float | None) – Sample rate in Hz. If given, the time axis is in seconds; otherwise in samples.

  • labels (Sequence[str] | None) – One legend label per impulse response.

  • db (bool) – Plot the decay in dB. Default True.

  • normalize (bool) – Normalize each curve by its initial (total) energy so it starts at 0 dB. Default False.

  • dynamic_range (float | None) – When plotting in dB, limit the y-axis to dynamic_range dB below the peak across all curves (default 100, i.e. a floor at peak - 100 dB). This keeps the late decay from blowing out the axis once the tail reaches silence (-inf dB). Use None for auto scaling. Ignored when db is False.

  • title (str | None) – Figure title.

  • max_points (int) – Maximum number of points per trace after downsampling. Default 10000.

Returns:

Call .show() to display.

Return type:

Any

pyFDN.auxiliary.plot.plot_fdn_parameter(delays, A, b, c, d, *, post_delay_sos=None, post_matrix_sos=None, post_output_sos=None, fs=None, nfft=512, zmin=None, zmax=None, title=None)[source]#

Plot all FDN parameters in one figure.

Extends plot_system_matrix() with the delay lengths and, optionally, any of the three filter hooks, named as pyFDN.process_fdn() and pyFDN.FDNBuild name them:

  • the system matrix blocks A, b, c, d as heatmaps with a shared RdBu color scale;

  • the delays as a bar plot whose bars are aligned with the columns of the feedback matrix A (one bar per delay line);

  • post_delay and post_matrix, the two in-loop hooks, as gain-per-sample curves, as in plot_db_per_sample() – both are per delay line and both act once per round trip, so the two rows are directly comparable;

  • post_output as plain magnitude response in dB.

Bar and curve colors are matched per delay line and encode the delay length (Viridis, short = dark, long = bright).

Parameters:
  • delays (ArrayLike) – Delay lengths in samples, shape (N,).

  • A (ArrayLike) – Feedback matrix, input gains, output gains, direct gains.

  • b (ArrayLike) – Feedback matrix, input gains, output gains, direct gains.

  • c (ArrayLike) – Feedback matrix, input gains, output gains, direct gains.

  • d (ArrayLike) – Feedback matrix, input gains, output gains, direct gains.

  • post_delay_sos (ArrayLike | None) – Per-delay-line SOS banks for the two in-loop hooks, same layout as pyFDN.td.SOSBank: (n_sections, 6, N).

  • post_matrix_sos (ArrayLike | None) – Per-delay-line SOS banks for the two in-loop hooks, same layout as pyFDN.td.SOSBank: (n_sections, 6, N).

  • post_output_sos (ArrayLike | None) – Output EQ as an SOS cascade in scipy format, shape (n_sections, 6) (or (6,) for one section) for a single output, or (n_sections, 6, K) to draw one magnitude curve per output channel.

  • fs (float | None) – Sample rate in Hz. If given, the filter responses are plotted over a logarithmic frequency axis in Hz; otherwise over rad/sample.

  • nfft (int) – Number of frequency points for the filter responses. Default 512.

  • zmin (float | None) – Shared color limits for the heatmaps. If both None, uses (-1, 1).

  • zmax (float | None) – Shared color limits for the heatmaps. If both None, uses (-1, 1).

  • title (str | None) – Figure title.

Returns:

Call .show() to display.

Return type:

Any

pyFDN.auxiliary.plot.plot_impulse_response(*irs, fs=None, labels=None, mulaw=True, mu=255.0, title='Impulse response', max_points=10000)[source]#

Plot one or more impulse responses over time, mu-law compressed by default.

Mu-law companding (pyFDN.mulaw_encode()) keeps the quiet late part of a reverberant decay visible alongside the early reflections. Dense traces are downsampled with LTTB (downsampled_scatter()) before plotting.

Parameters:
  • *irs (ArrayLike) – One or more 1-D impulse responses, plotted as overlaid lines.

  • fs (float | None) – Sample rate in Hz. If given, the time axis is in seconds; otherwise in samples.

  • labels (Sequence[str] | None) – One legend label per impulse response.

  • mulaw (bool) – Apply mu-law companding to the amplitudes. Default True.

  • mu (float) – Mu-law compression parameter. Default 255 (G.711).

  • title (str | None) – Figure title.

  • max_points (int) – Maximum number of points per trace after downsampling. Default 10000.

Returns:

Call .show() to display.

Return type:

Any

pyFDN.auxiliary.plot.plot_impulse_response_matrix(t, ir, *, xlabel=None, ylabel=None, title=None, xlim=None, ylim=None, fig=None, **plot_kwargs)[source]#

Plot matrix of impulse responses in a subplot grid (out x in).

Parameters:
  • t (ArrayLike | None) – x-values (e.g. time). If None, uses 0 .. size(ir,2)-1.

  • ir (ArrayLike) – Shape (n_samples, n_out, n_in). Each subplot is ir[:, out, in].

  • xlabel (str | None) – Shared axis labels and title.

  • ylabel (str | None) – Shared axis labels and title.

  • title (str | None) – Shared axis labels and title.

  • xlim (tuple[float, float] | None) – Shared axis limits. If None, computed from data.

  • ylim (tuple[float, float] | None) – Shared axis limits. If None, computed from data.

  • fig (Figure | None) – Figure to use.

  • **plot_kwargs (Any) – Passed to ax.plot().

Return type:

tuple[Figure, ndarray, ndarray]

Returns:

  • fig (Figure)

  • plot_axes (ndarray of Axes) – Shape (n_out, n_in).

  • plot_handles (ndarray of Line2D) – Shape (n_out, n_in).

pyFDN.auxiliary.plot.plot_matrix(A, title=None, zmin=None, zmax=None, *, block_boundaries=None)[source]#

Plot a single matrix as a Plotly heatmap (RdBu, square pixels).

Parameters:
  • A (ArrayLike) – 2-D matrix to visualise.

  • title (str | None) – Figure title (supports HTML/<sup> for subtitles).

  • zmin (float | None) – Color limits. Default (-1, 1).

  • zmax (float | None) – Color limits. Default (-1, 1).

  • block_boundaries (Sequence[int] | None) – Indices at which to draw dashed dividing lines on both axes, e.g. to separate the sub-blocks of a coupled feedback matrix. A boundary at index k is drawn between rows/columns k-1 and k.

Returns:

Call .show() to display.

Return type:

Any

pyFDN.auxiliary.plot.plot_matrix_grid(matrices, *, titles=None, ncols=2, zmin=None, zmax=None, show_ticks=False, title=None, height=None, width=None)[source]#

Plot several matrices as a grid of Plotly heatmaps sharing one color scale.

Each matrix is rendered like plot_matrix() (RdBu, zero-centered, top-left origin, square cells). Use this to compare several matrices side by side, e.g. a feedback matrix against its nearest orthogonal approximations.

Parameters:
  • matrices (Sequence[ArrayLike]) – 2-D matrices to visualise, filled row by row across the grid.

  • titles (Sequence[str] | None) – One subplot title per matrix (supports HTML/<br> for line breaks).

  • ncols (int) – Number of columns in the grid. Default 2.

  • zmin (float | None) – Shared color limits. If both None, uses (-1, 1).

  • zmax (float | None) – Shared color limits. If both None, uses (-1, 1).

  • show_ticks (bool) – If True, label axes with integer row/column indices. Default False.

  • title (str | None) – Overall figure title.

  • height (int | None) – Figure size in pixels. Defaults scale with the grid shape.

  • width (int | None) – Figure size in pixels. Defaults scale with the grid shape.

Returns:

Call .show() to display.

Return type:

Any

pyFDN.auxiliary.plot.plot_spectrogram(ir, fs, *, nperseg=1024, noverlap=None, window='blackman', xlim=(None, None), ylim=(None, None), dynamic_range=80.0, title='Spectrogram', xlabel='Time [s]', ylabel='Frequency [Hz]', height=500, colorscale='Viridis')[source]#

Plot spectrogram of a 1-D signal as a Matplotlib image.

Uses the same default parameters as the Poletti example: Blackman window, 1024-point segments, 75% overlap, log y-axis, dB magnitude.

Parameters:
  • ir (ArrayLike) – Time-domain signal (e.g. one channel of an impulse response).

  • fs (float) – Sample rate in Hz (for axis labels and frequency scale).

  • nperseg (int) – Length of each segment for the STFT. Default 1024.

  • noverlap (int | None) – Number of overlapping samples. Default nperseg // 4 * 3 (75% overlap).

  • window (str | tuple[Any, ...]) – Window name or (name, param). Default “blackman”.

  • xlim (tuple[float | None, float | None]) – Time axis limits in seconds. Use None for auto.

  • ylim (tuple[float | None, float | None]) – Frequency axis limits in Hz. Use None for auto (ymax defaults to fs/2).

  • dynamic_range (float | None) – Color (magnitude) range in dB below the peak of the displayed spectrogram. Default 80. Use None for Plotly’s auto scaling.

  • title (str | None) – Figure title.

  • xlabel (str) – Axis labels.

  • ylabel (str) – Axis labels.

  • height (int) – Figure height in pixels.

  • colorscale (str) – Colormap name (lowercased to a Matplotlib colormap). Default “Viridis”.

Returns:

fig

Return type:

Any

pyFDN.auxiliary.plot.plot_system_matrix(A, b, c, d, zmin=None, zmax=None, title=None)[source]#

Plot system matrix [A b; c d] as 2x2 Plotly heatmaps, shared RdBu color scale.

Subplot sizes are proportional to block dimensions so that each matrix element (pixel) has the same physical size across all four plots.

Parameters:
  • A (ArrayLike) – Feedback matrix, input gain, output gain, direct gain.

  • b (ArrayLike) – Feedback matrix, input gain, output gain, direct gain.

  • c (ArrayLike) – Feedback matrix, input gain, output gain, direct gain.

  • d (ArrayLike) – Feedback matrix, input gain, output gain, direct gain.

  • zmin (float | None) – Shared color limits. If both None, uses (-1, 1).

  • zmax (float | None) – Shared color limits. If both None, uses (-1, 1).

  • title (str | None) – Figure title (supports HTML/<sup> for subtitles).

Returns:

Call .show() to display.

Return type:

Any

pyFDN.auxiliary.poles module#

Pole utilities (conjugate pairing, etc.).

pyFDN.auxiliary.poles.reduce_conjugate_pairs(poles, *, tol_real=1e-10, tol_pair=1e-08, verbose=False, strict=False)[source]#

Group poles into real and conjugate pairs using optimal assignment.

For real-coefficient systems, poles are either real or occur in conjugate pairs. This uses the linear sum assignment problem (Hungarian method): cost \(C[i,j] = |poles[j] - conj(poles[i])|\); the minimum-cost permutation pairs each pole with its conjugate (or itself for real poles). Then:

  • Real: assignment[i] == i and C[i,i] < tol_real (i.e. \(|Im(pole_i)|\) small).

  • Conjugate pair: assignment[i] == j, assignment[j] == i, C[i,j] < tol_pair.

  • Unpaired: otherwise (ambiguous or numerical orphans).

Unpaired poles are reported via non_paired AND a UserWarning so callers cannot silently lose poles to imprecise pairing. Set strict=True to raise ValueError instead of warning.

Return type:

tuple[ndarray, ndarray, ndarray]

Returns:

  • poles_out (np.ndarray) – One representative per real pole and per conjugate pair (imag >= 0).

  • is_conjugate (np.ndarray) – Boolean, same length as poles_out: False for real, True for conjugate pair or unpaired.

  • non_paired (np.ndarray) – Poles that could not be paired.

pyFDN.auxiliary.tiny_rotation_matrix module#

Tiny rotation matrix generator for FDN feedback matrices.

Translation of tinyRotationMatrix.m from fdnToolbox. Original MATLAB code: (c) Sebastian Jiro Schlecht, 2020 Python translation: Facundo Franchino, 2025

pyFDN.auxiliary.tiny_rotation_matrix.rotation_matrix_from_angles(angles, n=None)[source]#

Generate orthogonal matrix with prescribed eigenvalue angles.

Builds a block-diagonal matrix of 2x2 Givens rotations, one block per angle, so the eigenvalues are exp(+-1j * angles). For odd matrix sizes, a single eigenvalue at 1 is appended.

Parameters:
  • angles (Tensor) – Eigenvalue angles in radians, one per conjugate pair

  • n (int | None) – Matrix size; either 2 * len(angles) or 2 * len(angles) + 1 (default 2 * len(angles))

Returns:

Orthogonal matrix of shape (n, n)

Return type:

Tensor

Example

>>> angles = torch.tensor([0.1, 0.2], dtype=torch.float64)
>>> R = rotation_matrix_from_angles(angles, n=5)
>>> R.shape
torch.Size([5, 5])
>>> torch.allclose(R @ R.T, torch.eye(5, dtype=R.dtype), atol=1e-12)
True
pyFDN.auxiliary.tiny_rotation_matrix.tiny_rotation_matrix(n, delta, spread=0.1, dtype=None)[source]#

Generate orthogonal matrix with small eigenvalue angles.

Creates a rotation matrix suitable for use in FDN feedback structures, where small eigenvalue angles help control the density of the impulse response. The eigenvalue angles are delta * pi, randomly spread by the spread factor. The matrix is constructed from 2x2 Givens rotations with these angles, pre- and post-multiplied by a random orthogonal matrix so that the result is dense but keeps the prescribed eigenvalues. For odd matrix sizes, one eigenvalue is at 1.

Parameters:
  • n (int) – Matrix size

  • delta (float) – Mean normalized eigenvalue angle

  • spread (float) – Spreading of eigenvalue angle (default 0.1)

  • dtype (dtype | None) – Floating point dtype of the result (default torch default)

Returns:

Orthogonal matrix of shape (n, n)

Return type:

Tensor

Example

>>> R = tiny_rotation_matrix(6, 12)
>>> R.shape
torch.Size([6, 6])
>>> torch.allclose(R @ R.T, torch.eye(6), atol=1e-5)
True

pyFDN.auxiliary.utils module#

General utility functions.

pyFDN.auxiliary.utils.db_to_lin(db)[source]#

Convert decibel values to linear magnitude.

Return type:

ndarray

pyFDN.auxiliary.utils.db_to_sq(db)[source]#

Convert decibel values to squared magnitude (power).

Return type:

ndarray

pyFDN.auxiliary.utils.ensure_3d(matrix)[source]#

Ensure the matrix has a trailing polynomial dimension.

Return type:

ndarray

pyFDN.auxiliary.utils.fade_out(x, fade_samples)[source]#

Apply a linear fade-out over the last fade_samples samples.

Ramps the tail of x linearly from 1 to 0 along the last axis, so a finite render can end at zero instead of clicking on an abrupt cutoff.

Parameters:
  • x (ArrayLike) – Input signal; the fade is applied along the last axis.

  • fade_samples (int) – Number of trailing samples to ramp down. Clamped to the signal length; values <= 0 leave the signal unchanged.

Return type:

ndarray

Returns:

Faded copy of x (float), same shape as the input. x is not modified in place.

pyFDN.auxiliary.utils.hertz_to_rad(hz, fs)[source]#

Convert frequency (Hz) to angular frequency (rad/sample).

Relationship: omega = 2*pi*f/fs. Inverse of rad_to_hertz().

Return type:

ndarray

pyFDN.auxiliary.utils.hertz_to_unit(hz, fs)[source]#

Convert frequency (Hz) to normalised frequency (0-1).

Return type:

ndarray

pyFDN.auxiliary.utils.is_bounding_curve(x_points, y_points, x_curve, y_curve, bound_type)[source]#

Check if all value points are bounded by the curve. :type x_points: ArrayLike :param x_points: x-coordinates of data points (1D array) :type y_points: ArrayLike :param y_points: y-coordinates of data points (1D array) :type x_curve: ArrayLike :param x_curve: x-coordinates of curve points (1D array) :type y_curve: ArrayLike :param y_curve: y-coordinates of curve points (1D array) :type bound_type: str :param bound_type: ‘upper’ or ‘lower’

Returns:

bool, whether all data points are bounded is_bounded: boolean array, whether each data point is bounded

Return type:

tuple[Any, ndarray]

pyFDN.auxiliary.utils.last_nonzero_indices(mat)[source]#

Return 1-based indices of the last non-zero element along axis 2.

Return type:

ndarray

pyFDN.auxiliary.utils.lin_to_db(linear)[source]#

Convert linear magnitude to decibels with numerical guard.

Return type:

ndarray

pyFDN.auxiliary.utils.max_corr(signals)[source]#

Pairwise maximum normalized cross-correlation of a MIMO signal matrix.

The (N1, N2, time) input is unfolded column-major into K = N1 * N2 signals (signal k is entry (k % N1, k // N1)); entry (i, j) of the result is the cross-correlation value of largest magnitude between signals i and j over all lags, keeping its sign, normalized so that the autocorrelation at zero lag is 1.

Translates maxCorr.m (Jon Fagerström) from fdnToolbox.

Parameters:

signals (ArrayLike) – MIMO signal matrix of shape (N1, N2, time), e.g. an adjugate polynomial matrix from pyFDN.adj_poly().

Returns:

max_corr_matrix – Symmetric (K, K) matrix of signed maximum correlations.

Return type:

ndarray

pyFDN.auxiliary.utils.mulaw_decode(y, mu=255.0)[source]#

Mu-law companding (decode): companded to linear amplitude.

Parameters:
  • y (ArrayLike) – Companded signal.

  • mu (float) – Compression parameter (default 255, as in G.711).

Return type:

ndarray

Returns:

Linear-amplitude signal.

pyFDN.auxiliary.utils.mulaw_encode(x, mu=255.0)[source]#

Mu-law companding (encode): linear amplitude to companded.

Parameters:
  • x (ArrayLike) – Input signal. If input is in [-1, 1], the output will be in [-1, 1].

  • mu (float) – Compression parameter (default 255, as in G.711).

Return type:

ndarray

Returns:

Companded signal.

pyFDN.auxiliary.utils.peak_normalize(x, target_peak=1.0)[source]#

Scale array so the maximum absolute value equals target_peak.

If the array is all zeros, it is returned unchanged.

Parameters:
  • x (ArrayLike) – Input signal (any shape).

  • target_peak (float) – Desired peak magnitude (default 1.0).

Return type:

ndarray

Returns:

Scaled array, same shape as input.

pyFDN.auxiliary.utils.pole_boundaries(delays, absorption, feedback_matrix, fs, nfft=4096)[source]#

Find upper and lower pole boundaries for FDN loop. :type delays: ArrayLike :param delays: 1D array of delays in samples (length N) :type absorption: Any :param absorption: object with .b and .a attributes, each shape (N, 1, len) :type feedback_matrix: ndarray :param feedback_matrix: 3D numpy array (N, N, len) :type fs: float :param fs: sampling frequency :type nfft: int :param nfft: number of frequency bins (default: 4096)

Returns:

lower bound of pole magnitude (shape: nfft) MaxCurve: upper bound of pole magnitude (shape: nfft) f: frequency points (Hz, shape: nfft)

Return type:

tuple[ndarray, ndarray, ndarray]

pyFDN.auxiliary.utils.rad_to_hertz(rad, fs)[source]#

Convert angular frequency (rad/sample) to frequency (Hz).

Relationship: omega = 2*pi*f/fs, so f = omega * fs / (2*pi).

Return type:

ndarray

pyFDN.auxiliary.utils.skew(X)[source]#

Return skew-symmetric matrix from upper triangle.

Y = triu(X, 1) - triu(X, 1).T so that Y is skew-symmetric. Equivalent to skew.m: Y = X - X’ with X = triu(X, 1).

Return type:

ndarray

pyFDN.auxiliary.utils.sq_to_db(squared)[source]#

Convert squared magnitude (power) to decibels with numerical guard.

Return type:

ndarray

Module contents#

Auxiliary modules (utils, acoustics, allpass, flamo wrappers, etc.).