Main Interface
source
IIRMethod
IIRMethod (value, names=None, module=None, qualname=None, type=None,
start=1)
An enumeration over IIR computation backends. Available options:
IIRMethod.FFT: Uses the FFT frequency sampling method.
IIRMethod.TDFII: Uses the TDF-II algorithm.
source
IIRParameters
IIRParameters (b:Optional[torch.Tensor]=None,
a:Optional[torch.Tensor]=None,
poles:Optional[torch.Tensor]=None,
zeros:Optional[torch.Tensor]=None,
gains:Optional[torch.Tensor]=None,
constrain_poles:bool=True, constrain_zeros:bool=True,
constraint_eps:float=0.0)
A class for storing IIR filter parameters.
b
typing.Optional[torch.Tensor]
None
a
typing.Optional[torch.Tensor]
None
The numerator coefficients (…, n_parallel, n_biquad, 3)
poles
typing.Optional[torch.Tensor]
None
The denominator coefficients (…, n_parallel, n_biquad, 3)
zeros
typing.Optional[torch.Tensor]
None
The complex poles (…, n_parallel, n_biquad)
gains
typing.Optional[torch.Tensor]
None
The complex zeros (…, n_parallel, n_biquad)
constrain_poles
bool
True
Whether to constrain the poles to the unit circle
constrain_zeros
bool
True
Whether to constrain the zeros to the unit circle
constraint_eps
float
0.0
The epsilon value to use for pole/zero constraint
We can demonstrate hybrid freqz computation by creating a network of passthrough filters:
n_biquads = 15
n_parallel = 8
n_samples = 1000
b = torch.zeros(n_parallel, n_biquads, 3 )
b[:, :, 0 ] = 1.0
a = torch.zeros(n_parallel, n_biquads, 3 )
a[:, :, 0 ] = 1.0
gains = torch.ones(n_parallel, n_biquads) / (n_parallel ** (1 / n_biquads))
params = IIRParameters(b= b, a= a, gains= gains)
plt.stem(params.freqz(128 ).abs ())
plt.show()
source
apply_iir
apply_iir (x:torch.Tensor, parameters:__main__.IIRParameters,
method:__main__.IIRMethod=<function _apply_biquad_fft>,
expand_x:bool=True, reduce_x:bool=True)
Applies a biquad filter to a signal. The filter can be specified in two ways:
Directly as the biquad coefficients (b, a)
As the complex poles and zeros (poles, zeros)
In both cases, the filter can be specified as a parallel (n_biquad = 1), cascade (n_parallel = 1), or hybrid ((n_biquad > 1) and (n_parallel > 1)).
The method parameter specifies the method to use for filtering. The FFT method is computed without recursion by frequency sampling, while the TDFII method is computed recursively using the transposed direct form II structure.
x
Tensor
The input signal (…, time)
parameters
IIRParameters
The IIR filter parameters
method
IIRMethod
_apply_biquad_fft
The method to use for filtering
expand_x
bool
True
Whether to expand the input signal to match the number of parallel filters
reduce_x
bool
True
Whether to sum the output signal along the parallel filter dimension
Test that expected shape is returned:
n_biquads = 2
n_parallel = 9
n_samples = 1000
batch_size = 4
poles = torch.exp(- 1j * torch.rand(batch_size, n_parallel, n_biquads) * np.pi)
zeros = torch.exp(- 1j * torch.rand(batch_size, n_parallel, n_biquads) * np.pi)
gains = torch.rand(batch_size, n_parallel, n_biquads)
params = IIRParameters(poles= poles, zeros= zeros, gains= gains)
x = torch.rand(batch_size, n_samples)
assert apply_iir(x, params, IIRMethod.FFT).shape == x.shape
n_biquads = 2
n_parallel = 9
n_samples = 1000
batch_size = 4
b = torch.rand(batch_size, n_parallel, n_biquads, 3 )
a = torch.rand(batch_size, n_parallel, n_biquads, 3 )
gains = None
params = IIRParameters(b= b, a= a, gains= gains)
x = torch.rand(batch_size, n_samples)
assert apply_iir(x, params, IIRMethod.TDFII).shape == x.shape
We can also have an optimized version of the above
source
apply_filter
apply_filter (x_arr:numpy.ndarray, a:numpy.ndarray, b:numpy.ndarray,
state:Optional[numpy.ndarray]=None)
x_arr
ndarray
[n_samples]
a
ndarray
[n_parallel, n_biquads, 3]
b
ndarray
[n_parallel, n_biquads, 3]
state
typing.Optional[numpy.ndarray]
None
[n_parallel, n_biquads, 2]
Test fast filter
# create a pole zero pair
pole = torch.complex (real= torch.tensor(0.5 ), imag= torch.tensor(0.3 ))
zero = torch.complex (real= torch.tensor(0.8 ), imag= torch.tensor(- 0.4 ))
# convert to IIR coefficients
b = pole_or_zero_to_iir_coeff(zero)
a = pole_or_zero_to_iir_coeff(constrain_complex_pole_or_zero(pole))
b = b.view(1 , 1 , 3 )
a = a.view(1 , 1 , 3 )
impulse = torch.zeros(256 )
impulse[0 ] = 1.0
gains = None
params = IIRParameters(b= b, a= a)
filtered_torch = apply_iir(impulse.unsqueeze(0 ), params, IIRMethod.TDFII)
filtered = apply_filter(
x_arr= impulse.cpu().numpy(),
a = a.cpu().numpy(),
b = b.cpu().numpy(),
)
# check that the output is the same
assert np.allclose(filtered, filtered_torch[0 ].detach().cpu().numpy())