pyFDN package#

Subpackages#

Submodules#

pyFDN.process module#

FDN processing functions.

pyFDN.process.process_fdn(input_signal, delays, A, B, C, D, *, absorption=None, extra_matrix=None)[source]#

Simulate the feedback delay network using block processing.

Recursion per block (same ordering as the MATLAB processFDN): delay output -> absorption filters -> output gains C, and in the feedback path: absorbed delay output -> feedback matrix A -> extra matrix -> + B input.

Parameters:
  • input_signal (array) – Input of shape (num_samples,) or (num_samples, num_inputs).

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

  • A (array) – Feedback matrix: static (N, N) or FIR polynomial (N, N, order) in z^{-1} convention.

  • B (array) – Static input, output, and direct gains.

  • C (array) – Static input, output, and direct gains.

  • D (array) – Static input, output, and direct gains.

  • absorption (object, optional) – Per-delay-line SOS filters; see pyFDN.dsp.SOSFilterBank for accepted shapes. Applied to the delay outputs inside the loop.

  • extra_matrix (object, optional) – Object with a filter(block) -> block method applied after the feedback matrix (e.g. TimeVaryingMatrix).

Returns:

output – Shape (num_samples, num_outputs), squeezed.

Return type:

ndarray

Module contents#

Top-level package for pyFDN.

class pyFDN.FDNBuild(A, B, C, D, delays, fs, filters=None, post_eq=None)[source]#

Bases: object

Complete FDN parameters returned by fdn_build_gallery().

filters is either None (lossless) or a per-delay first-order absorption SOS bank with shape (num_sections, 6, N) suitable for dss_to_flamo(..., sos_filter=...). post_eq is an optional per-output SOS bank with shape (num_sections, 6, num_outputs) suitable for the output_filter argument of pyFDN.dss_to_flamo().

A: ndarray#
B: ndarray#
C: ndarray#
D: ndarray#
delays: ndarray#
filters: ndarray | None = None#
fs: float#
post_eq: ndarray | None = None#
class pyFDN.FDNSystem(A: np.ndarray, B: np.ndarray, C: np.ndarray, D: np.ndarray)[source]#

Bases: NamedTuple

Full FDN system matrices returned by fdn_system_gallery().

A: ndarray#

Alias for field number 0

B: ndarray#

Alias for field number 1

C: ndarray#

Alias for field number 2

D: ndarray#

Alias for field number 3

class pyFDN.FIRMatrixFilter(coefficients)[source]#

Bases: object

Apply a matrix of FIR filters to a multichannel signal, block by block.

Parameters:

coefficients ((n_out, n_in, order) array) – FIR coefficients per matrix entry in z^{-1} convention (coefficients[i, j, k] is the tap of z^{-k} from input j to output i).

:param Filter state persists across calls to filter(): :param so a long signal: :param can be processed in consecutive blocks.:

filter(block)[source]#

Filter a block of shape (block_size, n_in) to (block_size, n_out).

Return type:

ndarray

class pyFDN.FeedbackDelay(delays, max_block_size)[source]#

Bases: object

Vectorised block delay lines for the FDN.

advance(block_size)[source]#
Return type:

None

get_values(block_size)[source]#
Return type:

ndarray

set_values(block)[source]#
Return type:

None

class pyFDN.FlamoDecompositionForPR(recursion_module, delays, in_subgraph, f_subgraph, out_subgraph, direct_subgraph)[source]#

Bases: object

Decomposition of a FLAMO model into small subgraphs for poles/residues. All subgraph fields are FLAMO modules (with .probe(z)); None means identity.

delays: ndarray#
direct_subgraph: Any#
f_subgraph: Any#
in_subgraph: Any | None#
out_subgraph: Any | None#
recursion_module: Any#
class pyFDN.SDN(room_size, source_pos, receiver_pos, Fs=44100, c=None, wall_filters=None)[source]#

Bases: object

Minimal SDN: from room and source/receiver, compute only network parameters (delay lengths, routing, scattering matrices, wall filters) for use in an FDN.

DEFAULT_C = 343.0#
N_WALLS = 6#
compute()[source]#

Compute all SDN parameters. Call once after construction.

Returns:

result

  • delay_lengthsnp.ndarray shape (6,6), float, seconds

    delay_lengths[i,j] = delay from node i to node j. Diagonal is NaN; only i != j valid.

  • delay_lengths_flatnp.ndarray shape (30,), seconds

    Wall-to-wall delays in order (0,1),(0,2),…,(5,4).

  • routinglist of (from_node, to_node)

    routing[k] = (i, j) means delay line k goes from node i to node j.

  • permutation_matrixnp.ndarray shape (30,30)

    Connectivity: permutation_matrix[k_in, k_out]=1 if output of delay k_out feeds delay k_in.

  • scattering_matriceslist of 6 arrays shape (5,5)

    Isotropic scattering matrix for each wall node.

  • wall_filters_sos : np.ndarray shape (n_sections, 6, 30), SOS coefficients in delay order for FLAMO.

  • source_to_wall_delays : np.ndarray shape (6,), seconds

  • source_to_wall_gains : np.ndarray shape (6,), 1/r gain

  • wall_to_receiver_delays : np.ndarray shape (6,), seconds

  • wall_to_receiver_gains : np.ndarray shape (6,), 1/(1 + d_node_mic/d_source_node)

  • direct_path_delay : float, seconds

  • direct_path_gain : float

  • node_positions : list of 6 (x, y, z) tuples, wall node positions in metres.

  • Fs : float, sampling frequency in Hz.

  • c : float, speed of sound in m/s.

Input routing (FDN): 6 gains -> 6 delays -> 6-to-30 matrix (0.5 in matrix).

  • input_delays : np.ndarray shape (6,), seconds

  • input_gains : np.ndarray shape (6,), gain per node (source_to_wall_gains, 1/r).

  • input_matrix : np.ndarray shape (30, 6), input_matrix[k, j] = 0.5 if delay k leaves node j else 0. Use: x = input_gains * input; x = delay(x, input_delays); injection = input_matrix @ x.

Output routing (FDN): 30-to-6 matrix -> 6 delays -> 6 gains -> sum.

  • output_matrix : np.ndarray shape (6, 30), output_matrix[j, k] = (2/5) if delay k leaves node j else 0.

  • output_gains : np.ndarray shape (6,), wall_to_receiver_gains.

  • output_delays : np.ndarray shape (6,), seconds Use: y = output_matrix @ state_30; y = delay(y, output_delays); output_reflected = sum(output_gains * y).

  • output_node_to_delay_indices : list of 6 lists; output_node_to_delay_indices[j] = delay indices leaving node j.

Return type:

dict

property result#

Return last result from compute(); None if compute() not called yet.

sdn_to_flamo(nfft=131072, device=None)[source]#

Build a runnable FLAMO model from this SDN’s parameters.

Calls compute() if not already done.

Parameters:
  • nfft (int) – FFT size for FLAMO.

  • device (torch device or None) – Device for FLAMO modules.

Returns:

  • model (flamo.processor.system.Shell)

  • result (dict) – The SDN compute result.

visualize(show=True, room_alpha=0.08, room_edge_color='gray')[source]#

Plot the 3D room with source, receiver, and wall node positions (Plotly). Call compute() first (or it will be called for you).

Parameters:
  • show (bool) – If True, call fig.show() at the end.

  • room_alpha (float) – Transparency of room faces (0=invisible, 1=opaque).

  • room_edge_color (str) – Color of room wireframe edges (e.g. “gray”, “rgb(128,128,128)”).

Returns:

fig

Return type:

plotly.graph_objects.Figure

class pyFDN.SOSFilterBank(sos, num_channels)[source]#

Bases: object

Apply one SOS filter cascade per channel, block by block.

Parameters:

:param Filter state persists across calls to filter(): :param so a long signal: :param can be processed in consecutive blocks.:

filter(block)[source]#

Filter a block of shape (block_size, N) channel-wise.

Return type:

ndarray

class pyFDN.TrainLog(train_loss=<factory>, loss_log=<factory>, steps_run=0, stopped_early=False)[source]#

Bases: object

Per-step loss history and stopping info from a training run.

train_loss#

Total loss at each step.

Type:

list of float

loss_log#

Per-criterion loss history, keyed by criterion class name.

Type:

dict of str to list of float

steps_run#

Steps actually run.

Type:

int

stopped_early#

Whether a plateau stopped it before max_steps.

Type:

bool

loss_log: dict[str, list[float]]#
steps_run: int = 0#
stopped_early: bool = False#
train_loss: list[float]#
class pyFDN.Trainable(feedback=True, input_gain=True, output_gain=True, direct=False)[source]#

Bases: object

Which FDN parameter groups are trained. Delays are always fixed.

direct: bool = False#
feedback: bool = True#
input_gain: bool = True#
output_gain: bool = True#
pyFDN.absorption_filters(frequency, target_rt, filterOrder, delays, fs)[source]#

Generate FIR absorption filters for each channel. frequency: [freq_points] target_rt: shape (freq_points, channels) delays: array of length channels

Return type:

ndarray

pyFDN.absorption_geq(rt, delays, fs)[source]#

Design per-delay GEQ absorption filters matching target reverberation times.

Each delay line gets its own cascade of biquad sections whose combined attenuation per round trip matches the desired RT at each frequency band.

Parameters:
  • rt (ndarray) – Target reverberation time in seconds at 10 frequency bands, shape (10,) or broadcastable.

  • delays (ndarray) – Delay lengths in samples, shape (num_delays,).

  • fs (float) – Sampling frequency in Hz.

Return type:

ndarray

Returns:

Per-channel SOS bank of shape (num_bands, 6, num_delays) (the canonical SOS bank layout) where num_bands = 11 (flat + low-shelf + 8 bandpass + high-shelf). All sections are normalised so a[0] = 1.

pyFDN.absorption_to_rt(filterCoeffs, delays, nfft, fs)[source]#

Compute reverb time from recursive absorption filter with delay.

Return type:

tuple[ndarray, ndarray]

pyFDN.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 (array) – 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.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.allpass_in_fdn(g, A, b, c, d)[source]#

Create an allpass structure embedded in an FDN of size [2N, 2N].

See Schlecht, S. (2017). Feedback delay networks in artificial reverberation and reverberation enhancement.

Parameters:
  • g (array-like, shape (N,)) – Per-channel feedforward/back allpass gains.

  • A (array-like, shape (N, N)) – Inner FDN feedback matrix.

  • b (array-like, shape (N,) or (N, 1)) – Input gains of the inner FDN.

  • c (array-like, shape (N,) or (1, N)) – Output gains of the inner FDN.

  • d (float) – Direct gain.

Return type:

tuple[ndarray, ndarray, ndarray, ndarray]

Returns:

  • A_out (ndarray, shape (2N, 2N)) – FDN feedback matrix.

  • B_out (ndarray, shape (2N, 1)) – FDN input gains.

  • C_out (ndarray, shape (1, 2N)) – FDN output gains.

  • D_out (ndarray, shape (1, 1)) – FDN direct gain.

Example

>>> import numpy as np
>>> from pyFDN.generate.random_orthogonal import random_orthogonal
>>> g = np.random.randn(3)
>>> A, B, C, D = allpass_in_fdn(g, random_orthogonal(3),
...                              np.ones((3, 1)), np.ones((1, 3)), 0.0)
>>> A.shape
(6, 6)
pyFDN.anderson_matrix(N, K=None, matrix_type='Hadamard')[source]#

Build an N×N block-circulant orthogonal matrix.

The matrix is block-diagonal with N/K blocks of size K×K, then row-shifted by K to produce the block-circulant structure.

Parameters:
  • N (int) – Total matrix size.

  • K (int | None) – Block size. Defaults to the smallest prime factor of N.

  • matrix_type (str) – Type string passed to fdn_matrix_gallery() for each block (default "Hadamard").

Return type:

ndarray

Returns:

Orthogonal matrix of shape (N, N).

pyFDN.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) – 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) – One argument per frame, passed positionally to plot_fn.

  • labels (sequence, optional) – Slider label per frame. Defaults to the frame index.

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

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

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

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

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

Returns:

Call .show() to display.

Return type:

go.Figure

pyFDN.apply_diagonal_similarity(A, x)[source]#

A_tilde = X^{-1/2} A X^{1/2} with X = diag(x)

Return type:

ndarray

pyFDN.bandpass_filter(omega_c, gain, Q)[source]#

Design a peaking bandpass biquad filter.

Parameters:
  • omega_c (float) – Center frequency in radians.

  • gain (float) – Linear gain at center frequency.

  • Q (float) – Quality factor.

Return type:

tuple[ndarray, ndarray]

Returns:

(b, a) — numerator and denominator coefficients of length 3.

pyFDN.block_matrix(A, B, C, D)[source]#
Return type:

ndarray

pyFDN.build_fdn(*, delays=None, N=None, rt=2.0, matrix='orthogonal', feedback=None, input_gain=None, output_gain=None, direct=0.0, trainable=None, fs=48000.0, nfft=16384, output='time', device=None, dtype=None, rng=None)[source]#

Build a trainable flamo Shell from a config.

Parameters:
  • delays (np.ndarray, optional) – Explicit integer delay lengths in samples. If omitted, N coprime delays are sampled (pyFDN.sample_delay_lengths()).

  • N (int, optional) – Number of delay lines when delays is omitted.

  • rt (float, (rt_dc, rt_nyquist), or None) – Reverberation time in seconds. None builds a lossless FDN.

  • matrix ({"orthogonal", "random"}) – Feedback-matrix parametrization.

  • feedback (np.ndarray, optional) – Initial (N, N) feedback matrix; defaults to a random SO(N) matrix.

  • input_gain (np.ndarray, optional) – B ((N, n_in)) and C ((n_out, N)); default ones / sqrt(N).

  • output_gain (np.ndarray, optional) – B ((N, n_in)) and C ((n_out, N)); default ones / sqrt(N).

  • direct (float or np.ndarray) – Direct path D; a scalar fills (n_out, n_in).

  • trainable (Trainable, optional) – Trainable parameter groups (default Trainable).

  • fs (see trainable_from_build().)

  • nfft (see trainable_from_build().)

  • output (see trainable_from_build().)

  • device (see trainable_from_build().)

  • dtype (see trainable_from_build().)

  • rng (np.random.Generator, int, or None) – Seed for the sampled delays / default feedback matrix.

Return type:

flamo.processor.system.Shell

pyFDN.build_to_flamo(build, nfft=65536, device=None, *, shell=True, dtype=None, post_delay_module=None)[source]#

Build a FLAMO model from a complete FDNBuild config.

Thin wrapper over dss_to_flamo() that unpacks an FDNBuild (as returned by pyFDN.fdn_build_gallery()) into its state-space arguments, mapping the in-loop absorption build.filters to sos_filter and the per-output build.post_eq to output_filter.

Parameters:
  • build (FDNBuild) – Complete FDN parameters (A, B, C, D, delays, fs, optional filters and post_eq), e.g. from pyFDN.fdn_build_gallery().

  • nfft (int) – FFT size for FLAMO (default 2**16).

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

  • shell (bool) – If True (default), wrap the core in a Shell with FFT/iFFT. Use pyFDN.flamo_time_response() to obtain a NumPy impulse response. If False, return only the core.

  • dtype (torch.dtype or None) – Optional dtype for FLAMO delay/gain/filter modules (e.g., torch.float64). If None, wrapper defaults are used.

  • post_delay_module (FLAMO module or None) – Optional module to append after the delay in the recursion (e.g. a Schroeder allpass core). Must have input/output size N. Loop becomes: delay -> post_delay_module -> A.

Returns:

model – If shell=True, a FLAMO Shell. Use pyFDN.flamo_time_response() for a NumPy impulse response. If shell=False, the core module.

Return type:

flamo.processor.system.Shell or core

pyFDN.check_completion(A, B, C, D)[source]#
Check balanced orthogonality/unitarity of V = [[A,B],[C,D]]:

V^* V should be identity.

Return type:

dict[str, Any]

Returns:

dict with Frobenius norm error and max-abs error.

pyFDN.complete_fdn(A, k=None, preprocessing='optimize', q=1.0, tol_one=1e-08, psd_clip=0.0, return_similarity=True)[source]#

Compute a completion (B,C,D) for given A.

Parameters:
  • A (ndarray) – (N,N) feedback matrix

  • k (int | None) – number of IO channels. If None -> full MIMO (k=N).

  • preprocessing (str) – “none” -> X = I “optimize” -> find diagonal similarity X=diag(x) that makes G(u) PSD with rank ~ k.

  • q (float | ndarray) – RHS for diagonal similarity solve (scalar or (N,) vector).

  • tol_one (float) – tolerance for ‘singular value equals 1’ decisions in general MIMO.

  • psd_clip (float) – eigenvalue clip (>=0) for PSD square roots in full MIMO completion.

  • return_similarity (bool) – if True, returns (B,C,D,x). else (B,C,D).

Returns:

B, C, D, x (if return_similarity), where x is diagonal of X used (x=ones if none).

pyFDN.complete_full_mimo_halmos(A, psd_clip=0.0)[source]#

Full MIMO (k=N) Halmos/Julia dilation.

For A with ||A||_2 <= 1 (contraction), the block matrix
V = [[A, (I - A A^*)^{1/2}],

[(I - A^* A)^{1/2}, -A^*]]

is unitary/orthogonal.

Returns: B, C, D (all NxN)

pyFDN.complete_general_mimo_svd(A, k, tol_one=1e-08, choose_smallest_if_needed=True)[source]#

General MIMO completion via defect subspace (CS/SVD-based).

Given A (NxN) and k <= N, we aim for a balanced orthogonal/unitary block:

V = [[A, B],
     [C, D]]

with B in Nxk, C in kxN, D in kxk.

Exact orthogonality is guaranteed when A has:

  • N-k singular values equal to 1 (within tol_one)

  • k singular values strictly < 1

If the singular value pattern doesn’t match, we can still build the completion using the k smallest singular values (choose_smallest_if_needed=True), but V may deviate from unitary.

Construction (in the singular vector basis):

A = U1 S V1^* + U2 I V2^*
B = U1 sqrt(I - S^2)
C = sqrt(I - S^2) V1^*
D = -S

Returns: B (Nxk), C (kxN), D (kxk)

pyFDN.complete_orthogonal(A, num_io)[source]#

Solve the orthogonal completion problem for feedback matrix A.

Finds b, c, d such that V = [[A, b], [c, d]] is orthogonal, where d is (num_io, num_io). The num_io smallest singular values of A must be strictly less than 1.

The construction uses the SVD of A: for the num_io smallest singular values σ, with left/right singular vectors U_s and V_s

b = U_s * diag(sqrt(1 - σ²))
c = diag(sqrt(1 - σ²)) * V_s^T
d = -diag(σ)
Parameters:
  • A (ndarray) – Feedback matrix of shape (N, N).

  • num_io (int) – Number of input/output channels.

Return type:

tuple[ndarray, ndarray, ndarray, ndarray]

Returns:

(b, c, d, V) with shapes (N, num_io), (num_io, N), (num_io, num_io), and (N + num_io, N + num_io) respectively.

pyFDN.construct_cascaded_paraunitary_matrix(n, k, *, sparsity=1.0, matrix_type='Hadamard', gain_per_sample=1.0)[source]#

Construct a paraunitary matrix and its reverse response.

Return type:

tuple[ndarray, ndarray]

pyFDN.construct_paraunitary_from_elementals(n, degree)[source]#

Construct a random paraunitary matrix as a cascade of elemental factors.

The matrix is a random orthogonal matrix multiplied by degree - 1 random degree-one lossless factors V(z) = (I - vv^T) + z^{-1} vv^T.

Parameters:
  • n (int) – Size of the paraunitary matrix.

  • degree (int) – Polynomial degree of the matrix (number of taps).

Return type:

tuple[ndarray, ndarray]

Returns:

  • matrix ((n, n, degree) ndarray) – Random paraunitary FIR matrix in z^{-1} convention.

  • v ((n, degree - 1) ndarray) – The unit-norm direction vectors of the elemental factors.

pyFDN.construct_velvet_feedback_matrix(n, stages, sparsity)[source]#

Wrapper for construct_cascaded_paraunitary_matrix using Hadamard stages.

Return type:

tuple[ndarray, ndarray]

pyFDN.db_to_lin(db)[source]#

Convert decibel values to linear magnitude.

Return type:

ndarray

pyFDN.db_to_sq(db)[source]#

Convert decibel values to squared magnitude (power).

Return type:

ndarray

pyFDN.degree_one_lossless(v)[source]#

Build the degree-one lossless polynomial matrix V(z) = (I - vv^T) + z^{-1} vv^T.

Parameters:

v (ndarray) – Vector of shape (N,) or (N, 1).

Return type:

ndarray

Returns:

Polynomial matrix of shape (N, N, 2) where index [..., 0] is the z^0 coefficient and [..., 1] is the z^{-1} coefficient.

pyFDN.design_geq(target_g, fs=48000.0)[source]#

Design a 10-band graphic EQ matching a target magnitude response.

The EQ has 8 peaking bandpass bands plus low and high shelving filters, plus a flat-gain section (11 sections total).

Parameters:
  • target_g (ndarray) – Target magnitude response in dB at 10 frequency bands (DC=1 Hz, 63, 125, 250, 500, 1k, 2k, 4k, 8k Hz, Nyquist). Shape (10,).

  • fs (float) – Sampling frequency in Hz (default 48000).

Return type:

tuple[ndarray, ndarray]

Returns:

(sos, target_f) where

  • sos — single SOS cascade of shape (n_sections, 6) (n_sections = 11).

  • target_f — 10-point frequency grid used for the target.

pyFDN.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:

determinant

pyFDN.diag_inv_sqrt(x, eps=0.0)[source]#
Return type:

ndarray

pyFDN.diag_sqrt(x)[source]#
Return type:

ndarray

pyFDN.diagonal_similarity_from_abs2_lyapunov(A, q=1.0, eps=1e-12)[source]#
Heuristic diagonal X = diag(x) ≻ 0 by enforcing the diagonal Lyapunov-like equation

x - diag(A diag(x) A^*) = q

which is linear in x using M = |A|^2 elementwise:

(I - M) x = q, M_ij = |A_ij|^2

This is a sufficient-condition style preprocessing; it does NOT guarantee existence for arbitrary A. If (I - M) is ill-conditioned or yields nonpositive entries, this fails.

Returns:

(N,) positive vector, X = diag(x)

Return type:

x

pyFDN.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 (array-like or None) – X-values. If None, uses sample indices 0 .. len(y)-1.

  • y (array-like) – Real-valued y-values.

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

Returns:

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

Return type:

ndarray

pyFDN.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.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.dss_to_flamo(A, B, C, D, m, Fs, nfft=65536, device=None, *, shell=True, dtype=None, sos_filter=None, output_filter=None, post_delay_module=None)[source]#

Build a FLAMO model from delay state-space (A, B, C, D, m).

Signal flow: input -> B -> [recursion: delay -> (optional filter/module) -> A] -> C -> output, with direct path D summed in parallel.

Parameters:
  • A ((N, N) or (N, N, L) array) – Feedback matrix. A 3-D array is a polynomial (FIR) matrix in z^{-1} convention (e.g. paraunitary) and is placed as a FLAMO Filter module.

  • B ((N, num_in) array) – Input gain.

  • C ((num_out, N) array) – Output gain.

  • D ((num_out, num_in) array) – Direct gain.

  • m ((N,) array) – Delay lengths in samples (one per delay line).

  • Fs (float) – Sampling rate in Hz.

  • nfft (int) – FFT size for FLAMO (default 2**16).

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

  • shell (bool) – If True (default), wrap the core in a Shell with FFT/iFFT. Use pyFDN.flamo_time_response() to obtain a NumPy impulse response. If False, return only the core (e.g. for use as post_delay_module in another dss_to_flamo).

  • dtype (torch.dtype or None) – Optional dtype for FLAMO delay/gain/filter modules (e.g., torch.float64). If None, wrapper defaults are used.

  • sos_filter ((n_sections, 6, N) array or None) – Optional SOS filter in the loop after delays.

  • output_filter ((n_sections, 6, num_out) array or None) – Optional SOS filter cascade applied per output channel after the output gain C (e.g. an output equalizer), matching the output filters of the MATLAB dss2impz.

  • post_delay_module (FLAMO module or None) – Optional module to append after the delay in the recursion (e.g. a Schroeder allpass core). Must have input/output size N. Loop becomes: delay -> post_delay_module -> A.

Returns:

model – If shell=True, FLAMO Shell. Use pyFDN.flamo_time_response() for a NumPy impulse response. If shell=False, the core module (same I/O as B.shape[1] / C.shape[0]).

Return type:

flamo.processor.system.Shell or core

pyFDN.dss_to_impz(ir_len, delays, A, B, C, D)[source]#

Compute MIMO impulse response from delay state-space (DSS) representation.

Runs one simulation per input channel (Dirac at t=0 on that channel only) and stacks the results into a single array.

Parameters:
  • ir_len (int) – Length of impulse response in samples

  • delays (list or array) – Delay lengths in samples

  • A (ndarray) – Delay state-space matrices (static, numeric only). For FDNs with absorption filters use dss_to_flamo.

  • B (ndarray) – Delay state-space matrices (static, numeric only). For FDNs with absorption filters use dss_to_flamo.

  • C (ndarray) – Delay state-space matrices (static, numeric only). For FDNs with absorption filters use dss_to_flamo.

  • D (ndarray) – Delay state-space matrices (static, numeric only). For FDNs with absorption filters use dss_to_flamo.

Returns:

impulse_response – Shape [ir_len, num_outputs, num_inputs]

Return type:

ndarray

pyFDN.dss_to_pr(delays, A, B, C, D, *, mode='eig', deflation_type='fullDeflation', quality_threshold=1e-10, maximum_iterations=50, refinement_tol=1e-12, reject_unstable_poles=False, svd_refine=True, symmetrize=True, verbose=False, Fs=1.0, nfft=65536, dtype=None, device=None)[source]#

Modal decomposition of an FDN from raw DSS matrices.

H(z) = C·(diag(z^m_i) A)^{-1}·B + D = Σ_k residue_k / (1 p_k z^{-1}) + D

Parameters:
  • delays (array-like of int, shape (N,)) – Per-line delays in samples.

  • A (array-like (N, N)) – Static feedback matrix.

  • B (array-like (N, n_in))

  • C (array-like (n_out, N))

  • D (array-like (n_out, n_in))

  • mode ({"eig", "roots", "eai"}, default "eig") –

    Pole-extraction algorithm.

    • "eig" — eigenvalues of the minimal state-space realization.

    • "roots" — roots of the generalized characteristic polynomial.

    • "eai" — Ehrlich-Aberth iteration in w = 1/z via FLAMO (delegates to pyFDN.translate.flamo_to_pr.flamo_to_pr()).

Return type:

tuple[ndarray, ndarray, ndarray, ndarray, dict[str, Any]]

Returns:

  • residues (ndarray (n_poles_reduced, n_out, n_in), complex)

  • poles (ndarray (n_poles_reduced,), complex)

  • direct (ndarray (n_out, n_in), complex)

  • is_conjugate (ndarray of bool (n_poles_reduced,))

  • meta (dict)

pyFDN.dss_to_ss(m, A, b=None, c=None, d=None)[source]#

Convert delay state-space (DSS) FDN to standard state-space.

Parameters:
  • m (list or array) – Vector of delays in samples (min 3 samples).

  • A (ndarray) – Feedback matrix (NxN).

  • b (ndarray, optional) – Input gains (Nx1). Defaults to ones(N,1).

  • c (ndarray, optional) – Output gains (1xN). Defaults to ones(1,N).

  • d (ndarray, optional) – Direct gains (1x1). Defaults to np.ones((1,1)).

Return type:

tuple[ndarray, ndarray, ndarray, ndarray]

Returns:

  • AA (ndarray) – State-space transition matrix.

  • bb (ndarray) – State-space input gains.

  • cc (ndarray) – State-space output gains.

  • dd (ndarray) – State-space direct gains.

pyFDN.dss_to_tf(delays, A, B, C, D)[source]#

From delay state-space to transfer function matrix (numerator and denominator).

Parameters:
  • delays (array-like) – Delays in samples, shape (N,).

  • A (ndarray) – Feedback matrix, shape (N, N) or (N, N, order) for polynomial matrix.

  • B (array-like) – Input gains, shape (N, num_input).

  • C (array-like) – Output gains, shape (num_output, N).

  • D (array-like) – Direct gains, shape (num_output, num_input).

Return type:

tuple[ndarray, ndarray]

Returns:

  • tfB (ndarray) – Numerator of transfer function matrix, shape (num_output, num_input, order).

  • tfA (ndarray) – Denominator polynomial (common), shape (order,) in z^{-1} convention.

pyFDN.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 (array-like) – Impulse response (1 channel only). Converted to 1D.

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

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

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

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

  • hop (int, optional) – 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.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 (array-like) – 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, optional) – 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:

np.ndarray

pyFDN.eig_sqrt_psd(M, eps=0.0)[source]#

Matrix square root for Hermitian PSD matrix (fallback if SciPy is unavailable). Clips eigenvalues below eps to eps (use eps=0 for pure PSD).

Return type:

ndarray

pyFDN.ensure_3d(matrix)[source]#

Ensure the matrix has a trailing polynomial dimension.

Return type:

ndarray

pyFDN.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: E = L^2 * T * fs / (6 ln 10), hence L = sqrt(6 ln(10) E / (T fs)). 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.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 using pyroomacoustics.bandpass_filterbank, then estimates RT per band using pyroomacoustics.measure_rt60 (extrapolated from decay_db).

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

Parameters:
  • ir (array-like, 1-D) – 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.extract_build(model)[source]#

Extract a complete FDNBuild from a named FLAMO model graph.

The graph must contain 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. Optional attenuation (filters), output-filter (post_eq), and direct-path leaves are included when present. The sample rate is read from the delay module and is required: a graph that does not expose fs is malformed and raises ValueError.

Return type:

FDNBuild

Build a complete FDN from a delay range, a reverberation time, and an EQ.

The feedback matrix A is a random orthogonal matrix. In-loop decay is realised as per-delay first-order shelving absorption filters matching rt at DC and rt_nyquist at Nyquist; pass rt=None for a lossless FDN (filters=None). An optional per-output first-order shelving post EQ is specified directly in decibels at DC and Nyquist.

Delays and I/O matrices use a local numpy.random.Generator; passing an integer or generator makes the build reproducible without mutating NumPy’s global random state.

Parameters:
  • N (int | None) – Number of delay lines. Inferred from delays when given.

  • fs (float) – Sample rate in Hz.

  • delays (ndarray | None) – Optional explicit delay lengths in samples.

  • delay_range (tuple[int, int]) – Half-open random delay range when delays is omitted.

  • sort_delays (bool) – Sort randomly generated or supplied delays.

  • num_inputs (int) – Number of input channels.

  • num_outputs (int) – Number of output channels.

  • io_type (str) – I/O matrix style: ones, normalized, identity, or random.

  • input_scale (float) – Scalar applied to B.

  • output_scale (float) – Scalar applied to C.

  • direct_gain (float | None) – Constant direct gain, or None for random D.

  • rt (float | None) – Reverberation time in seconds at DC, or None for a lossless FDN with no in-loop absorption filters.

  • rt_nyquist (float | None) – Reverberation time in seconds at Nyquist. Defaults to rt (frequency-flat decay).

  • rt_crossover (float | None) – Shelf crossover for the absorption filters in Hz.

  • post_eq_db_dc (Union[_Buffer, _SupportsArray[dtype[Any]], _NestedSequence[_SupportsArray[dtype[Any]]], complex, bytes, str, _NestedSequence[complex | bytes | str], None]) – Post-EQ gain in dB at DC, scalar or length num_outputs. Setting either post-EQ argument enables a per-output output filter.

  • post_eq_db_nyquist (Union[_Buffer, _SupportsArray[dtype[Any]], _NestedSequence[_SupportsArray[dtype[Any]]], complex, bytes, str, _NestedSequence[complex | bytes | str], None]) – Post-EQ gain in dB at Nyquist, scalar or length num_outputs. Defaults to post_eq_db_dc (flat gain).

  • post_eq_crossover (float | None) – Shelf crossover for the post EQ in Hz.

  • rng (Generator | int | None) – Local NumPy generator or integer seed.

Return type:

FDNBuild

Returns:

A complete FDNBuild.

Return a feedback matrix of the requested type, or list all type names.

Parameters:
  • N (int | None) – Matrix size. Ignored when matrix_type is None.

  • matrix_type (str | None) – One of the supported type strings. Pass None (or call with no arguments) to get the list of all type names.

Return type:

ndarray | list[str]

Returns:

Feedback matrix of shape (N, N), or a list of type-name strings.

Example:

fdn_matrix_gallery()             # → list of type strings
fdn_matrix_gallery(4, "orthogonal")
fdn_matrix_gallery(8, "Hadamard")

Return a full FDN system (A, B, C, D) of the requested type, or list all type names.

Parameters:
  • N (int | None) – System order. Ignored when system_type is None.

  • system_type (str | None) – One of the supported type strings. Pass None (or call with no arguments) to get the list of all type names.

Return type:

FDNSystem | list[str]

Returns:

FDNSystem named tuple (A, B, C, D), or a list of type-name strings.

Example:

fdn_system_gallery()                         # → list of type strings
fdn_system_gallery(8, "allpassInFDN")

Return an FIR (filter) feedback matrix of the requested type, or list all type names.

All types are paraunitary (lossless): A^T(z^{-1}) A(z) = I. Used as scattering feedback matrices in an FDN (Schlecht & Habets 2020).

Parameters:
  • N (int | None) – Matrix size. Ignored when matrix_type is None.

  • matrix_type (str | None) – One of "RandomDense" (dense cascaded paraunitary matrix), "Velvet" (sparse velvet-noise feedback matrix), or "FromElementals" (cascade of degree-one lossless factors, polynomial degree N * num_stages). Pass None (or call with no arguments) to get the list of all type names.

  • num_stages (int) – Number of cascade stages (or degree factor for "FromElementals").

  • sparsity (float) – Sparsity of the "Velvet" type (ignored otherwise).

  • stage_matrix_type (str) – Stage matrix for "RandomDense" and "Velvet": "Hadamard" or "random" (random orthogonal; avoids the structural double poles at z = ±1 of Hadamard stages).

Return type:

ndarray | list[str]

Returns:

Feedback matrix of shape (N, N, L) in z^{-1} convention, or a list of type-name strings.

Example:

filter_matrix_gallery()              # → list of type strings
filter_matrix_gallery(4, "Velvet", num_stages=3, sparsity=3)
pyFDN.first_order_absorption(rt_dc, rt_ny, delays, fs, crossover_frequency=None)[source]#

Design first-order shelving absorption filters according to specified reverb time.

Each delay line gets a first-order shelving filter whose gain matches the target decay (rt_dc at DC, rt_ny at Nyquist) for its delay length, with the shelf transition at crossover_frequency.

Reference: Jot, J. M., “Proportional parametric equalizers - Application to digital reverberation and environmental audio processing”, AES 2015.

Parameters:
  • rt_dc (float) – Reverberation time in seconds at DC.

  • rt_ny (float) – Reverberation time in seconds at Nyquist.

  • delays (array-like) – Delay lengths in samples, one per channel.

  • fs (float) – Sampling rate in Hz.

  • crossover_frequency (float, optional) – Shelf crossover frequency in Hz. Defaults to fs/8, the midpoint of the warped (bilinear) frequency axis. Values above fs/5 are clamped to fs/5 since a too high crossover leads to an unstable filter (fs/4 is the limit).

Returns:

One-section per-channel SOS bank of shape (1, 6, N) (the canonical SOS bank layout); section rows are [b0, b1, b2, a0, a1, a2] (b2 = a2 = 0 for these first-order filters).

Return type:

np.ndarray

pyFDN.first_order_shelving_eq(db_dc, db_nyquist, fs, crossover_frequency=None)[source]#

Design first-order shelving EQ filters from gains in dB at DC and Nyquist.

Unlike first_order_absorption() (whose gains are derived from a reverberation time and a delay length), the shelf endpoints are specified directly as decibel gains. Useful as a per-output tone correction (post EQ).

Parameters:
  • db_dc (array-like) – Gain in dB at DC, scalar or one value per channel.

  • db_nyquist (array-like) – Gain in dB at Nyquist, scalar or one value per channel. Broadcast against db_dc to a common number of channels.

  • fs (float) – Sampling rate in Hz.

  • crossover_frequency (float, optional) – Shelf crossover frequency in Hz. Defaults to fs/8; clamped to fs/5.

Returns:

One-section per-channel SOS bank of shape (1, 6, N) (canonical SOS bank layout); section rows are [b0, b1, b2, a0, a1, a2].

Return type:

np.ndarray

pyFDN.flamo_decompose_for_pr(model)[source]#

Decompose a FLAMO model into the subgraphs needed for poles/residues.

Expects core with branchA (Series of input_gain, Recursion(feedforward, feedback), output_gain) and branchB (direct path). Returns small FLAMO subgraphs (no probing); pass the result to flamo_to_pr() as decomposition=....

Supported architecture (see flamo_to_pr() for the full contract): branchA must contain exactly one, non-nested Recursion, and the delay lines must live in that recursion’s feedforward path. SOS-format filters (incl. biquad/SVF cascades) anywhere in the feedforward/feedback are fine; a Recursion nested inside another loop is not supported.

Return type:

FlamoDecompositionForPR

pyFDN.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.flamo_extract_pr_decomposition(model)[source]#

Extract the H(z)=C P(z)^{-1}B+D probes from a FLAMO model.

Returns a dict with keys "P", "F" (feedforward), "B" (input path), "C", "D". For poles/residues use flamo_to_pr().

Return type:

dict[str, Any]

pyFDN.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:

np.ndarray

pyFDN.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 (FLAMO model (Shell, Series, Parallel, Recursion, or dsp module))

  • 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

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

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

Parameters:
  • root (dict) – 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 of dict

pyFDN.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 (np.ndarray) – 1-D input signal.

  • fs (int, optional) – 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:

np.ndarray

pyFDN.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:

np.ndarray

pyFDN.flamo_to_pr(model=None, *, decomposition=None, deflation_type='fullDeflation', reject_unstable_poles=False, quality_threshold=1e-10, maximum_iterations=50, refinement_tol=1e-12, svd_refine=True, symmetrize=True, verbose=True, num_poles=None)[source]#

Poles/residues from a FLAMO transfer H(z) = C(z)P(z)^{-1}B(z) + D(z).

Pass either a FLAMO model or a decomposition from flamo_decompose_for_pr().

Return type:

tuple[ndarray, ndarray, ndarray, ndarray, dict[str, Any]]

Supported architecture#

The model must reduce to a single, non-nested Recursion (the canonical FDN: Parallel(branchA=Series(in_gain, Recursion, out_gain), branchB=direct), as produced by pyFDN.translate.dss_to_flamo.dss_to_flamo()). Two constraints are assumed and not auto-detected:

  • Delays live in the recursion’s feedforward path. Delay lengths are read from feedforward to size the pole search.

  • No nested recursion. A Recursion placed inside another loop’s feedforward/feedback (e.g. an allpass realized as its own sub-FDN) is not supported: its internal poles are folded into F(z) by probe but are not roots of the outer det P and would be silently dropped.

SOS-format filters (one-pole/biquad/SVF cascades stored as second-order sections) anywhere in the feedforward or feedback are supported: their poles are added to the pole count via _count_loop_filter_poles() so the Ehrlich-Aberth search is sized correctly.

type num_poles:

int | None

param num_poles:

Override the auto-detected pole count (sum(delays) plus IIR poles from loop filters). When given, this exact value sizes the Ehrlich-Aberth root search instead of the detected order. Use it when the loop order is known independently (e.g. the characteristic-polynomial degree) or when filters sit where _count_loop_filter_poles() cannot see them.

type num_poles:

int or None, default None

type svd_refine:

bool

param svd_refine:

Run a per-pole SVD null-vector Newton step after the Ehrlich-Aberth loop.

type svd_refine:

bool, default True

type symmetrize:

bool

param symmetrize:

Enforce exact conjugate-pair symmetry on the refined pole set before reduce_conjugate_pairs().

type symmetrize:

bool, default True

pyFDN.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 (array-like) – 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.graphic_eq(center_omega, shelving_omega, R, gain_db)[source]#

Build a graphic EQ as a bank of independent biquad sections.

Band layout (total len(center_omega) + len(shelving_omega) + 1 sections):

  • Band 0: flat gain section.

  • Band 1: low shelving filter.

  • Bands 2 … N-2: peaking bandpass filters.

  • Band N-1: high shelving filter.

Parameters:
  • center_omega (ndarray) – Center frequencies of bandpass bands in radians, shape (num_center,).

  • shelving_omega (ndarray) – Cut-off frequencies of shelving bands in radians, shape (2,)[low_crossover, high_crossover].

  • R (float) – Bandwidth parameter; quality factor is sqrt(R) / (R - 1).

  • gain_db (ndarray) – Command gains in dB for each section, shape (num_center + 3,) (flat + low shelf + bandpass + high shelf).

Return type:

ndarray

Returns:

SOS matrix of shape (num_bands, 6) with columns [b0, b1, b2, a0, a1, a2].

pyFDN.hermitize(M)[source]#
Return type:

ndarray

pyFDN.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.hertz_to_unit(hz, fs)[source]#

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

Return type:

ndarray

pyFDN.homogeneous_allpass_fdn(G, X, *, verbose=False, tol=1e-12)[source]#

Generate allpass FDN with homogeneous decay: V = [A,b;c,d] uniallpass, A = U @ G.

Assumptions (as in the Cauchy/CORDIC-style construction you are using):
  • G is diagonal (N,N), real

  • X is diagonal (N,N), real, positive

  • Define R = G^2 X (diagonal). Build a Cauchy-like K_{ij} = 1/(p_i - r_j), with p = diag(X), r = diag(R)

  • Recover rank-1 factors beta, alpha such that

    inv(K) = diag(beta) @ K.T @ diag(alpha)

    and then set

    U = diag(sqrt(beta)) @ K @ diag(sqrt(alpha))

    (up to global sign conventions)

Return type:

tuple[ndarray, ndarray, ndarray, ndarray, ndarray]

Returns:

  • A ((N, N) ndarray)

  • b ((N, 1) ndarray)

  • c ((1, N) ndarray)

  • d ((1, 1) ndarray)

  • U ((N, N) ndarray)

pyFDN.householder_matrix(u)[source]#

Create a Householder reflection matrix from a vector.

H = I - 2 * (u u^T) / (u^T u)

Parameters:

u (ndarray) – Vector orthogonal to the reflection hyperplane, shape (N,).

Return type:

ndarray

Returns:

Householder matrix of shape (N, N). Orthogonal and symmetric.

pyFDN.impz_to_res(impulse_response, poles, is_conjugate_pole_pair)[source]#

Estimate residues from impulse response and known poles via least squares.

Return type:

tuple[ndarray, ndarray, ndarray]

Notes

This function follows fdnToolbox’s impz2res.m and is currently implemented for SISO impulse responses.

pyFDN.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.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 (array-like) – Delay state-space matrices.

  • B (array-like) – Delay state-space matrices.

  • C (array-like) – Delay state-space matrices.

  • D (array-like) – Delay state-space matrices.

  • delays (array-like) – 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.is_almost_zero(A, tol=1e-12)[source]#

Test whether matrix/vector is almost zero in absolute values.

Parameters:
  • A (ndarray) – Numerical values to be tested

  • tol (float) – Tolerance value for max deviation from 0

Returns:

Boolean whether all values in A are almost 0

Return type:

isZ

pyFDN.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: Union[_Buffer, _SupportsArray[dtype[Any]], _NestedSequence[_SupportsArray[dtype[Any]]], complex, bytes, str, _NestedSequence[complex | bytes | str]] :param x_points: x-coordinates of data points (1D array) :type y_points: Union[_Buffer, _SupportsArray[dtype[Any]], _NestedSequence[_SupportsArray[dtype[Any]]], complex, bytes, str, _NestedSequence[complex | bytes | str]] :param y_points: y-coordinates of data points (1D array) :type x_curve: Union[_Buffer, _SupportsArray[dtype[Any]], _NestedSequence[_SupportsArray[dtype[Any]]], complex, bytes, str, _NestedSequence[complex | bytes | str]] :param x_curve: x-coordinates of curve points (1D array) :type y_curve: Union[_Buffer, _SupportsArray[dtype[Any]], _NestedSequence[_SupportsArray[dtype[Any]]], complex, bytes, str, _NestedSequence[complex | bytes | str]] :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:

all_bounded

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

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

Return type:

bool

pyFDN.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 (ndarray, shape (ir_len, n_out, n_in)) – 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.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 (array-like) – Delay state-space matrices (feedback, input gain, output gain, direct).

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

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

  • D (array-like) – 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.

pyFDN.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.last_nonzero_indices(mat)[source]#

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

Return type:

ndarray

pyFDN.lin_to_db(linear)[source]#

Convert linear magnitude to decibels with numerical guard.

Return type:

ndarray

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

Load a packaged audio file as a NumPy array.

Parameters:
  • name (str) – File name within package (e.g. "synth_dry.wav").

  • fs (int, optional) – Target sampling rate. If given and different from the file’s rate, the signal is resampled to fs.

  • package (str) – Importable package holding the audio resource.

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

Returns:

(signal, fs) – Samples as float64 and the (possibly resampled) sampling rate.

Return type:

tuple[np.ndarray, int]

pyFDN.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.map_back_from_similarity(Bt, Ct, Dt, x)[source]#
Map balanced completion (A_tilde, Bt, Ct, Dt) back to original coordinates:

B = X^{1/2} Bt C = Ct X^{-1/2} D = Dt

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

Matrix polynomial multiplication by convolution.

Return type:

ndarray

pyFDN.matrix_delay_approximation(matrix)[source]#

Rank-1 approximation of matrix group delay.

Return type:

tuple[ndarray, ndarray]

pyFDN.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:

Q

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

Evaluate a matrix polynomial P at the complex point z.

Return type:

ndarray

pyFDN.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 (torch.Tensor) – Square matrix (real, will be cast to complex for eig).

Returns:

Real matrix square root of A.

Return type:

torch.Tensor

pyFDN.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 (array) – 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.mgrpdelay(matrix)[source]#

Group delay for each entry of an FIR matrix.

Return type:

tuple[ndarray, ndarray]

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

Convert milliseconds to samples.

Return type:

ndarray

pyFDN.mtf_to_impz(tfB, tfA, ir_len)[source]#

Compute MIMO impulse response from matrix transfer function (numerator/denominator).

Parameters:
  • tfB (array-like) – Numerator polynomials, shape (num_output, num_input, order) in z^{-1} convention.

  • tfA (array-like) – Common denominator polynomial, shape (order,) in z^{-1} convention.

  • ir_len (int) – Length of impulse response in samples.

Returns:

impulse_response – Shape (ir_len, num_output, num_input). Channel (:, o, i) is the response at output o to a unit impulse at input i.

Return type:

ndarray

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

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

Parameters:
Return type:

ndarray

Returns:

Linear-amplitude signal.

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

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

Parameters:
  • x (Union[_Buffer, _SupportsArray[dtype[Any]], _NestedSequence[_SupportsArray[dtype[Any]]], complex, bytes, str, _NestedSequence[complex | bytes | str]]) – 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.nearest_orthogonal(A)[source]#

Return the nearest orthogonal matrix to A in the Frobenius norm.

Computed via the polar decomposition: B = U V^T where A = U S V^T is the SVD of A.

Parameters:

A (ndarray) – Square real matrix, shape (N, N).

Return type:

ndarray

Returns:

Orthogonal matrix of shape (N, N).

pyFDN.nearest_sign_agnostic_orthogonal(A, max_trials=100000, tolerance=2.220446049250313e-11)[source]#

Find the orthogonal matrix U minimising ‖A |U|‖_F.

Solves the non-convex problem by repeated random restarts followed by a sign-variable-exchange local search.

Parameters:
  • A (ndarray) – Input square matrix, shape (N, N). Signs are ignored.

  • max_trials (int) – Number of random sign-pattern restarts.

  • tolerance (float) – Stop early when the Frobenius error is below this value.

Return type:

ndarray

Returns:

Orthogonal matrix of shape (N, N).

pyFDN.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:

q

pyFDN.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 (array-like, shape (N,)) – 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.one_pole_absorption(rt_dc, rt_ny, delays, fs)[source]#

Design one-pole absorption filters according to specified reverb time.

Returns a one-section per-channel SOS bank of shape (1, 6, N) (the canonical SOS bank layout; section rows are [b0, b1, b2, a0, a1, a2]).

Return type:

ndarray

pyFDN.orth_error(V)[source]#

|| V^* V - I ||_F

Return type:

float

pyFDN.outer_sum_approximation(matrix)[source]#

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

Return type:

tuple[ndarray, ndarray]

pyFDN.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:
Return type:

ndarray

Returns:

Scaled array, same shape as input.

pyFDN.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_eq is rendered as one curve per output channel.

Return type:

Any

pyFDN.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 (array-like) – Per-delay-line SOS bank, same layout as pyFDN.dsp.SOSFilterBank: (n_sections, 6, N).

  • delays (array-like) – Delay lengths in samples, shape (N,).

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

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

  • title (str, optional) – Figure title.

Returns:

Call .show() to display.

Return type:

go.Figure

pyFDN.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 (array-like) – One or more 1-D impulse responses, plotted as overlaid curves.

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

  • labels (sequence of str, optional) – One legend label per impulse response.

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

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

  • dynamic_range (float, optional) – 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, optional) – Figure title.

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

Returns:

Call .show() to display.

Return type:

go.Figure

pyFDN.plot_fdn_parameter(delays, A, b, c, d, *, attenuation_sos=None, post_eq_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, the attenuation filters and the post EQ:

  • 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);

  • the attenuation filters as gain-per-sample curves, as in plot_db_per_sample();

  • the post EQ 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 (array-like) – Delay lengths in samples, shape (N,).

  • A (array-like) – Feedback matrix, input gains, output gains, direct gains.

  • b (array-like) – Feedback matrix, input gains, output gains, direct gains.

  • c (array-like) – Feedback matrix, input gains, output gains, direct gains.

  • d (array-like) – Feedback matrix, input gains, output gains, direct gains.

  • attenuation_sos (array-like, optional) – Per-delay-line SOS attenuation bank, same layout as pyFDN.dsp.SOSFilterBank: (n_sections, 6, N).

  • post_eq_sos (array-like, optional) – Post 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, optional) – Sample rate in Hz. If given, the filter responses are plotted over a logarithmic frequency axis in Hz; otherwise over rad/sample.

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

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

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

  • title (str, optional) – Figure title.

Returns:

Call .show() to display.

Return type:

go.Figure

pyFDN.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 (FLAMO model (Shell, Series, Parallel, Recursion, or dsp module))

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

  • ax (matplotlib Axes, optional) – 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:

matplotlib Axes

pyFDN.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 (array-like) – One or more 1-D impulse responses, plotted as overlaid lines.

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

  • labels (sequence of str, optional) – One legend label per impulse response.

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

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

  • title (str, optional) – Figure title.

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

Returns:

Call .show() to display.

Return type:

go.Figure

pyFDN.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 (array-like, optional) – x-values (e.g. time). If None, uses 0 .. size(ir,2)-1.

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

  • xlabel (str, optional) – Shared axis labels and title.

  • ylabel (str, optional) – Shared axis labels and title.

  • title (str, optional) – Shared axis labels and title.

  • xlim (tuple, optional) – Shared axis limits. If None, computed from data.

  • ylim (tuple, optional) – Shared axis limits. If None, computed from data.

  • fig (Figure, optional) – 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.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 (array-like) – 2-D matrix to visualise.

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

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

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

  • block_boundaries (sequence of int, optional) – 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:

go.Figure

pyFDN.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 of array-like) – 2-D matrices to visualise, filled row by row across the grid.

  • titles (sequence of str, optional) – One subplot title per matrix (supports HTML/<br> for line breaks).

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

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

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

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

  • title (str, optional) – Overall figure title.

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

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

Returns:

Call .show() to display.

Return type:

go.Figure

pyFDN.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 (array-like, 1-D) – 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, optional) – Number of overlapping samples. Default nperseg // 4 * 3 (75% overlap).

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

  • xlim (tuple (xmin, xmax)) – Time axis limits in seconds. Use None for auto.

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

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

  • title (str, optional) – 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:

matplotlib.figure.Figure

pyFDN.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 (array-like) – Feedback matrix, input gain, output gain, direct gain.

  • b (array-like) – Feedback matrix, input gain, output gain, direct gain.

  • c (array-like) – Feedback matrix, input gain, output gain, direct gain.

  • d (array-like) – Feedback matrix, input gain, output gain, direct gain.

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

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

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

Returns:

Call .show() to display.

Return type:

go.Figure

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

Find upper and lower pole boundaries for FDN loop. :type delays: Union[_Buffer, _SupportsArray[dtype[Any]], _NestedSequence[_SupportsArray[dtype[Any]]], complex, bytes, str, _NestedSequence[complex | bytes | str]] :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:

MinCurve

pyFDN.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 (N, N)) – 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:

ndarray

pyFDN.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.polyder_rational(b, a)[source]#

Derivative of rational polynomial using quotient rule.

Return type:

tuple[ndarray, ndarray]

pyFDN.polydiag(p)[source]#

Construct a diagonal polynomial matrix from an array of polynomials.

Return type:

ndarray

pyFDN.pr_to_impz(residues, poles, direct, is_conjugate_pole_pair, impulse_response_length, mode='fast')[source]#

Synthesize impulse response from poles and residues.

Parameters:
  • residues (ndarray) – Shape (num_poles, num_outputs, num_inputs).

  • poles (ndarray) – Pole vector of length num_poles.

  • direct (ndarray) – Direct term, shape (num_outputs, num_inputs).

  • is_conjugate_pole_pair (ndarray) – Boolean/vector mask, same length as poles.

  • impulse_response_length (int) – Number of samples in the synthesized response.

  • mode (str) – "fast" (vectorized) or "lowMemory".

Return type:

ndarray

pyFDN.probe_sos(sos, control_frequencies=None, fft_len=4096, fs=48000.0)[source]#

Evaluate the magnitude response of each biquad at control frequencies.

Parameters:
  • sos (ndarray) – Filter matrix of shape (num_bands, 6) with columns [b0, b1, b2, a0, a1, a2] (rows are independent sections).

  • control_frequencies (ndarray | None) – Frequencies in Hz at which to evaluate.

  • fft_len (int) – FFT length for the frequency response computation.

  • fs (float) – Sampling frequency in Hz.

Return type:

tuple[ndarray, ndarray, ndarray]

Returns:

(G, H, W) where

  • G — magnitude in dB, shape (len(control_frequencies), num_bands).

  • H — complex frequency response, shape (fft_len, num_bands).

  • W — frequency axis in Hz, shape (fft_len, num_bands).

pyFDN.process_fdn(input_signal, delays, A, B, C, D, *, absorption=None, extra_matrix=None)[source]#

Simulate the feedback delay network using block processing.

Recursion per block (same ordering as the MATLAB processFDN): delay output -> absorption filters -> output gains C, and in the feedback path: absorbed delay output -> feedback matrix A -> extra matrix -> + B input.

Parameters:
  • input_signal (array) – Input of shape (num_samples,) or (num_samples, num_inputs).

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

  • A (array) – Feedback matrix: static (N, N) or FIR polynomial (N, N, order) in z^{-1} convention.

  • B (array) – Static input, output, and direct gains.

  • C (array) – Static input, output, and direct gains.

  • D (array) – Static input, output, and direct gains.

  • absorption (object, optional) – Per-delay-line SOS filters; see pyFDN.dsp.SOSFilterBank for accepted shapes. Applied to the delay outputs inside the loop.

  • extra_matrix (object, optional) – Object with a filter(block) -> block method applied after the feedback matrix (e.g. TimeVaryingMatrix).

Returns:

output – Shape (num_samples, num_outputs), squeezed.

Return type:

ndarray

pyFDN.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.rand_admissible_homogeneous_allpass(G, range_)[source]#

Generate a random admissible diagonal matrix P for homogeneous uniallpass FDN.

Used with homogeneous_allpass_fdn(G, P) to obtain a uniallpass FDN with homogeneous decay. Admissibility is defined by the construction in the paper.

Parameters:
  • G ((N, N) array-like) – Diagonal attenuation matrix with 0 < diag(G) < 1.

  • range (tuple (low, high) or array-like of length 2) – Random range with 0 < low < high < 1. Diagonal entries of P are built from random ratios in [low, high] scaled by diag(G)^2.

Returns:

P – Admissible diagonal matrix (first diagonal entry 1, rest from cumprod).

Return type:

(N, N) ndarray

Examples

>>> G = np.diag([0.9, 0.8, 0.7])
>>> P = rand_admissible_homogeneous_allpass(G, (0.3, 0.8))
>>> A, b, c, d, U = homogeneous_allpass_fdn(G, P)
pyFDN.random_matrix_shift(max_shift, matrix, matrix_rev=None)[source]#

Randomly shift polynomial matrices in time.

Return type:

tuple[ndarray, ndarray | None, ndarray, ndarray]

pyFDN.random_orthogonal(n)[source]#

Generate a random orthogonal matrix distributed according to the Haar measure.

Return type:

ndarray

pyFDN.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:

rotation_matrix

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.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.rt_to_slope(rt, fs)[source]#

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

Return type:

ndarray

pyFDN.sample_delay_lengths(N, delay_range=(400, 1200), *, distribution='uniform', coprime=False, sort=False, rng=None)[source]#

Generate N delay-line lengths in samples.

Targets are drawn within delay_range according to distribution and, when coprime is set, snapped to the nearest distinct, pairwise-coprime integers. A local numpy.random.Generator is used so passing an integer seed (or generator) makes the result reproducible without touching NumPy’s global random state.

Parameters:
  • N (int) – Number of delay lines.

  • delay_range (tuple[int, int]) – Inclusive (low, high) range in samples.

  • distribution (str) –

    Sampling distribution for the delay lengths:

    • "uniform" – flat in linear space.

    • "geometric" – flat in log space (log-uniform), i.e. geometrically spaced with equal probability per octave.

    • "lognormal" – Gaussian in log space, centred on the geometric mean of the range. The range span roughly ±2 sigma.

  • coprime (bool) – When True, snap the sampled values to the nearest distinct, pairwise-coprime integers. Coprime delays avoid coinciding echoes and degenerate modes; snapping may nudge values slightly outside delay_range.

  • sort (bool) – Sort the returned delays in ascending order.

  • rng (Generator | int | None) – Local NumPy generator or integer seed.

Return type:

ndarray

Returns:

Integer array of shape (N,) with the delay lengths in samples.

Example:

sample_delay_lengths(8)                                   # uniform, may repeat
sample_delay_lengths(8, (500, 4000), distribution="geometric")
sample_delay_lengths(8, (500, 4000), distribution="lognormal", coprime=True)
pyFDN.schroeder_reverberator(allpass_gain, comb_gain, b, c, d)[source]#

Create combs and allpass filters as a single FDN.

Combines parallel comb filters with a series allpass section into one feedback delay network. See Schlecht (2017), Feedback delay networks in artificial reverberation and reverberation enhancement.

Parameters:
  • allpass_gain (array-like, shape (Na,)) – Feedforward/back gains for the series allpass stages.

  • comb_gain (array-like, shape (Nc,)) – Feedback gains for the parallel comb filters.

  • b (array-like, shape (Nc,) or (Nc, 1)) – Input gains of the comb filters.

  • c (array-like, shape (Nc,) or (1, Nc)) – Output gains of the comb filters.

  • d (float) – Direct gain.

Return type:

tuple[ndarray, ndarray, ndarray, ndarray]

Returns:

  • A (ndarray, shape (Na+Nc, Na+Nc)) – FDN feedback matrix.

  • B (ndarray, shape (Na+Nc, 1)) – FDN input gains.

  • C (ndarray, shape (1, Na+Nc)) – FDN output gains.

  • D (ndarray, shape (1, 1)) – FDN direct gain.

Example

>>> import numpy as np
>>> g_ap = np.array([0.5, 0.4, 0.3])
>>> g_c  = np.array([0.7, 0.6, 0.5])
>>> A, B, C, D = schroeder_reverberator(g_ap, g_c, np.ones(3)/3, np.ones(3)/3, 0.0)
>>> A.shape
(6, 6)
pyFDN.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 (array-like, shape (N,)) – 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.shelving_filter(omega_c, gain, filter_type)[source]#

Design a shelving biquad filter.

Parameters:
  • omega_c (float) – Cut-off frequency in radians.

  • gain (float) – Linear gain (not dB).

  • filter_type (str) – "low" for low-shelf, "high" for high-shelf.

Return type:

tuple[ndarray, ndarray]

Returns:

(b, a) — numerator and denominator coefficients of length 3.

pyFDN.shift_matrix(mat, shift, direction)[source]#

Shift a polynomial matrix in time-domain by shift samples.

Return type:

ndarray

pyFDN.shift_matrix_distribute(mat, sparsity, *, pulse_size=None)[source]#

Randomly distribute time shifts for a polynomial matrix.

Return type:

ndarray

pyFDN.skew(X)[source]#

Return skew-symmetric matrix from upper triangle (Matlab skew convention).

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.slope_to_rt(slope, fs)[source]#

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

Return type:

ndarray

pyFDN.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:
  • sos ((n_sections, 6, N) array) – Per-channel SOS bank; section rows are [b0, b1, b2, a0, a1, a2]. Same format as one_pole_absorption() / first_order_absorption() return.

  • delays ((N,) array-like) – Delay lengths in samples, one per channel. Used to scale gain to per-sample.

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

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.sq_to_db(squared)[source]#

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

Return type:

ndarray

pyFDN.sqrtm_psd(M, eps=0.0)[source]#

Square root for Hermitian PSD matrix using SciPy if available; otherwise eigen fallback. Clips eigenvalues below eps to eps.

Return type:

ndarray

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

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

Return type:

Any

pyFDN.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:

rotation_matrix

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.train_fdn(model, mode, *, target=None, criteria=None, sparsity_alpha=0.2, mss_nfft=(256, 512, 1024), max_steps=2000, lr=0.001, optimizer='adam', patience=10, tol=1e-06, device=None, dtype=None, rng=None, log=False, train_dir=None)[source]#

Train model for mode in place and return a TrainLog.

Read the trained result back with pyFDN.extract_build().

Parameters:
  • model (flamo Shell) – A trainable model from pyFDN.build_fdn() / trainable_from_build.

  • mode (str) – "colorless", "match_spectrogram" or "match_mel_spectrogram". "colorless" is single-input/single-output only.

  • target (np.ndarray, optional) – Reference impulse response for the matching modes (unused for colorless). Shape (n_samples,) or (n_samples, n_out), or a 3-D (n_samples, n_out, n_in) IR matrix to fit a full MIMO system.

  • criteria (list of (criterion, alpha, requires_model), optional) – Replace the default loss list (primary loss + sparsity) with your own.

  • sparsity_alpha (float) – Weight of the feedback-matrix sparsity penalty (default 0.2; 0 disables).

  • mss_nfft (tuple of int) – STFT window sizes for the spectrogram modes.

  • max_steps (max gradient steps, learning rate, plateau patience.)

  • lr (max gradient steps, learning rate, plateau patience.)

  • patience (max gradient steps, learning rate, plateau patience.)

  • optimizer (str) – "adam" (default) or "lbfgs".

  • tol (float) – Relative-improvement threshold for the plateau early stop.

  • device (optional) – Torch device / dtype (default cpu / float32).

  • dtype (optional) – Torch device / dtype (default cpu / float32).

  • rng (int or None) – Integer seed for torch.manual_seed.

  • log (bool) – If True, log/checkpoint to train_dir.

  • train_dir (str, optional) – Checkpoint directory (used when log=True).

Return type:

TrainLog

pyFDN.trainable_from_build(build, *, trainable=None, matrix='orthogonal', nfft=16384, output='time', device=None, dtype=None)[source]#

Build a trainable flamo Shell initialized from an FDNBuild.

Parameters:
  • build (FDNBuild) – Initial FDN (A/B/C/D/delays/fs + optional filters/post_eq).

  • trainable (Trainable, optional) – Trainable parameter groups (default Trainable).

  • matrix ({"orthogonal", "random"}) – Feedback-matrix parametrization.

  • nfft (int) – FFT size.

  • output (str) – "time" or "magnitude" output layer (train_fdn sets this to match the mode).

  • device (optional) – Torch device / dtype (default cpu-or-cuda / float32).

  • dtype (optional) – Torch device / dtype (default cpu-or-cuda / float32).

Return type:

flamo.processor.system.Shell

pyFDN.with_decay(build, rt, *, rt_crossover=None)[source]#

Return a copy of build with homogeneous decay matching rt.

Sets per-delay first-order absorption (pyFDN.first_order_absorption()) for rt (a single value, or (rt_dc, rt_nyquist)). Decay does not change colouration, so this is the natural way to add a tail to a colorless build.

Return type:

FDNBuild