AudioLazy works with time-varying filters
from audiolazy import sHz, white_noise, line, resonator, AudioIO rate = 44100 s, Hz = sHz(rate) sig = white_noise()
You can also use it to build (or analyze in general) with list(filt(sig)) or filt(sig).take(inf) . There are many other resources that can be useful, for example, applying time-varying coefficients directly in the Z-transform filter equation.
EDIT: Additional Information on AudioLazy Components
The following examples were performed using IPython.
Resonator is an instance of StrategyDict that links many implementations in one place.
In [1]: from audiolazy import * In [2]: resonator Out[2]: {('freq_poles_exp',): <function audiolazy.lazy_filters.freq_poles_exp>, ('freq_z_exp',): <function audiolazy.lazy_filters.freq_z_exp>, ('poles_exp',): <function audiolazy.lazy_filters.poles_exp>, ('z_exp',): <function audiolazy.lazy_filters.z_exp>} In [3]: resonator.default Out[3]: <function audiolazy.lazy_filters.poles_exp>
So resonator calls the internal function resonator.poles_exp from which you can get some help
In [4]: resonator.poles_exp? Type: function String Form:<function poles_exp at 0x2a55b18> File: /usr/lib/python2.7/site-packages/audiolazy/lazy_filters.py Definition: resonator.poles_exp(freq, bandwidth) Docstring: Resonator filter with 2-poles (conjugated pair) and no zeros (constant numerator), with exponential approximation for bandwidth calculation. Parameters ---------- freq : Resonant frequency in rad/sample (max gain). bandwidth : Bandwidth frequency range in rad/sample following the equation: ``R = exp(-bandwidth / 2)`` where R is the pole amplitude (radius). Returns ------- A ZFilter object. Gain is normalized to have peak with 0 dB (1.0 amplitude).
So the purpose of a detailed filter would be
filt = resonator.poles_exp(freq=freq * Hz, bandwidth=bw * Hz)
Where Hz is simply the number to change the unit of measurement from Hz to rad / sample, as used in most AudioLazy components.
Do this with freq = pi/4 and bw = pi/8 ( pi already in the audiolazy namespace):
In [5]: filt = resonator(freq=pi/4, bandwidth=pi/8) In [6]: filt Out[6]: 0.233921 ------------------------------------ 1 - 1.14005 * z^-1 + 0.675232 * z^-2 In [7]: type(filt) Out[7]: audiolazy.lazy_filters.ZFilter
You can try using this filter, not the one specified in the first example.
Another way to do this is to use the z object from the package. First we find the constants for this resonator of all poles:
In [8]: freq, bw = pi/4, pi/8 In [9]: R = e ** (-bw / 2) In [10]: c = cos(freq) * 2 * R / (1 + R ** 2)
The denominator can be made directly using z in the equation:
In [12]: denominator = 1 - 2 * R * c * z ** -1 + R ** 2 * z ** -2 In [13]: gain / denominator Out[14]: 0.233921 ------------------------------------ 1 - 1.14005 * z^-1 + 0.675232 * z^-2 In [15]: type(_) # The "_" is the last returned value in IPython Out[15]: audiolazy.lazy_filters.ZFilter
EDIT 2: About time-varying coefficients
Filter coefficients can also be Stream instances (which can be inferred from any iterable).
In [16]: coeff = Stream([1, -1, 1, -1, 1, -1, 1, -1, 1, -1])
Same thing, given entering a list instead of impulse() Stream:
In [18]: coeff = Stream((1, -1, 1, -1, 1, -1, 1, -1, 1, -1))
The NumPy 1D array is also iterable:
In [20]: from numpy import array In [21]: array_data = array([1, -1, 1, -1, 1, -1, 1, -1, 1, -1]) In [22]: coeff = Stream(array_data)
This last example shows time behavior.
EDIT 3: repetitive sequence behavior
The line function has a behavior similar to numpy.linspace , which gets a range of "length" instead of "step".
In [24]: import numpy In [25]: numpy.linspace(10, 20, 5) # Start, stop (included), length Out[25]: array([ 10. , 12.5, 15. , 17.5, 20. ]) In [26]: numpy.linspace(10, 20, 5, endpoint=False) # Makes stop not included Out[26]: array([ 10., 12., 14., 16., 18.]) In [27]: line(5, 10, 20).take(inf) # Length, start, stop (range-like) Out[27]: [10.0, 12.0, 14.0, 16.0, 18.0] In [28]: line(5, 10, 20, finish=True).take(inf) # Include the "stop" Out[28]: [10.0, 12.5, 15.0, 17.5, 20.0]
Moreover, the filter equation has a different sample behavior per sample (1-sample "piece"). Anyway, you can use a repeater for large block sizes:
In [29]: five_items = _
And use it in the line from the first example, so freq and bw will become:
chunk_size = 100 freq = repeater(line(dur / chunk_size, 200, 800), chunk_size) bw = repeater(line(dur / chunk_size, 100, 240), chunk_size)
EDIT 4: Emulation of time-varying filters / coefficients from LTI filters using a time-varying gain / envelope
Another way: use different “weights” for two different filtered versions of the signal and do some “crossfade” math with the signal, something like:
signal = thub(sig, 2)
This will apply a linear envelope (from 0 to 1 and from 1 to 0) from different filtered versions of the same signal. If thub looks confusing, try sig1, sig2 = tee(sig, 2) apply filt(sig1) and filt(sig2) , they should do the same.
EDIT 5: Temporary Butterworth Filter Option
I spent the last hours trying to let this Butterworth be personalized as your example, imposing order = 2 and providing half-power bandwidth (~ 3dB) directly. I made four examples, the code in this Gist , and I updated AudioLazy to include the gauss_noise Gaussian distributed noise stream. Please note that the code in gist has nothing optimized, it was made ony to work in this particular case, and the chirp example makes it very slow due to the behavior for determining the coefficient on the sample. The current frequency can be obtained from the [filtered] data in rad / sample using:
diff(unwrap(phase(hilbert(filtered_data))))
where diff = 1 - z ** -1 or another approach to finding derivatives in discrete time, hilbert is a function from scipy.signal that gives us an analytical signal (The discrete Hilbert transform is the imaginary part of its result), and the other two are helper functions from AudioLazy.
Here's what happens when Butterworth dramatically changes its coefficients, preserving its memory without noise:

There is noticeable behavior in this transition. You can use the moving median to “smooth” that at the lower frequency, while maintaining a sharp transition, but this will not work at a higher frequency. Well, this is what we expect from a perfect sine wave, but with noise (MUCH noise, Gaussian has a standard deviation equal to the sinusoidal amplitude), it becomes:

I tried to do the same with the chirp, exactly this:

This shows strange filtering behavior with a lower passband at the upper frequency. And with extra noise:

The code in gist is also AudioIO().play this last noisy chirp.
EDIT 6: Filter with a temporary version of the resonator
I added the example using resonators instead of Butterworth to the same Gist . They are in pure Python and not optimized, but they are faster than calling butter for each sample during the chirp, and it is much easier to implement, since all resonator strategies accept Stream instances as valid inputs. Here are the graphs for the cascade of two resonators (i.e. a 2nd order filter):




And the same for a cascade of three resonators (i.e. a 3rd order filter):


<T411>
These resonators have a gain of 1 (0 dB) at the center frequency, and that the oscillation diagram from the “Sharp Pure Sine Wave” plots occurs during the transition even without any filtering.