Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p><a href="https://github.com/danilobellini/audiolazy" rel="nofollow noreferrer">AudioLazy</a> works natively with time varying filters</p> <pre><code>from audiolazy import sHz, white_noise, line, resonator, AudioIO rate = 44100 s, Hz = sHz(rate) sig = white_noise() # Endless white noise Stream dur = 8 * s # Some few seconds of audio freq = line(dur, 200, 800) # A lazy iterable range bw = line(dur, 100, 240) filt = resonator(freq * Hz, bw * Hz) # A simple bandpass filter with AudioIO(True) as player: player.play(filt(sig), rate=rate) </code></pre> <p>You can also use it for plotting (or analysis, in general), by using <code>list(filt(sig))</code> or <code>filt(sig).take(inf)</code>. There are a lot of other resources that might be useful as well, such as applying time-varying coefficients directly in a Z-transform filter equation.</p> <p><strong>EDIT: More information about the AudioLazy components</strong></p> <p>The following examples were done using the IPython.</p> <p>Resonator is a <code>StrategyDict</code> instance, which ties many implementations in one place.</p> <pre><code>In [1]: from audiolazy import * In [2]: resonator Out[2]: {('freq_poles_exp',): &lt;function audiolazy.lazy_filters.freq_poles_exp&gt;, ('freq_z_exp',): &lt;function audiolazy.lazy_filters.freq_z_exp&gt;, ('poles_exp',): &lt;function audiolazy.lazy_filters.poles_exp&gt;, ('z_exp',): &lt;function audiolazy.lazy_filters.z_exp&gt;} In [3]: resonator.default Out[3]: &lt;function audiolazy.lazy_filters.poles_exp&gt; </code></pre> <p>So <code>resonator</code> calls internally the <code>resonator.poles_exp</code> function, from which you can get some help</p> <pre><code>In [4]: resonator.poles_exp? Type: function String Form:&lt;function poles_exp at 0x2a55b18&gt; 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). </code></pre> <p>So a verbose filter assignment would be</p> <pre><code>filt = resonator.poles_exp(freq=freq * Hz, bandwidth=bw * Hz) </code></pre> <p>Where the <code>Hz</code> is just a number to change the unit from Hz to rad/sample, as used in most AudioLazy components.</p> <p>Let's do so with <code>freq = pi/4</code> and <code>bw = pi/8</code> (<code>pi</code> is already in the <code>audiolazy</code> namespace):</p> <pre><code>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 </code></pre> <p>You can try using this filter instead of the one given in the first example.</p> <p>Another way to do so would be using the <code>z</code> object from the package. First let's find the constants for that all-poles resonator:</p> <pre><code>In [8]: freq, bw = pi/4, pi/8 In [9]: R = e ** (-bw / 2) In [10]: c = cos(freq) * 2 * R / (1 + R ** 2) # AudioLazy included the cosine In [11]: gain = (1 - R ** 2) * sqrt(1 - c ** 2) </code></pre> <p>The denominator can be done directly by using the <code>z</code> in the equation:</p> <pre><code>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 </code></pre> <p><strong>EDIT 2: About the time varying coefficients</strong></p> <p>The filter coefficients can also be a Stream instance (which can be cast from any iterable).</p> <pre><code>In [16]: coeff = Stream([1, -1, 1, -1, 1, -1, 1, -1, 1, -1]) # Cast from a list In [17]: (1 - coeff * z ** -2)(impulse()).take(inf) Out[17]: [1.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] </code></pre> <p>The same, given a list input instead of the <code>impulse()</code> Stream:</p> <pre><code>In [18]: coeff = Stream((1, -1, 1, -1, 1, -1, 1, -1, 1, -1)) # Cast from a tuple In [19]: (1 - coeff * z ** -2)([1, 0, 0, 0, 0, 0, 0]).take(inf) Out[19]: [1.0, 0.0, -1, 0, 0, 0, 0] </code></pre> <p>A NumPy 1D array is also an iterable:</p> <pre><code>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) # Cast from an array In [23]: (1 - coeff * z ** -2)([0, 1, 0, 0, 0, 0, 0]).take(inf) Out[23]: [0.0, 1.0, 0, 1, 0, 0, 0] </code></pre> <p>This last example shows the time-variant behaviour.</p> <p><strong>EDIT 3: Chunked-repeat sequences behaviour</strong></p> <p>The line function has a behaviour similar to the <code>numpy.linspace</code>, which gets the range "length" instead of "step". </p> <pre><code>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] </code></pre> <p>With that, the filter equation has a different behaviour sample-per-sample (1-sample "chunk"). Anyhow, you can use a repeater for larger chunk sizes:</p> <pre><code>In [29]: five_items = _ # List from the last Out[] value In [30]: @tostream ....: def repeater(sig, n): ....: for el in sig: ....: for _ in xrange(n): ....: yield el ....: In [31]: repeater(five_items, 2).take(inf) Out[31]: [10.0, 10.0, 12.5, 12.5, 15.0, 15.0, 17.5, 17.5, 20.0, 20.0] </code></pre> <p>And use it in the line from the first example, so that <code>freq</code> and <code>bw</code> becomes:</p> <pre><code>chunk_size = 100 freq = repeater(line(dur / chunk_size, 200, 800), chunk_size) bw = repeater(line(dur / chunk_size, 100, 240), chunk_size) </code></pre> <p><strong>EDIT 4: Emulating time-varying filters/coefficients from LTI filters using time-varying gain/envelope</strong></p> <p>Another way around would be using different "weights" for two different filtered versions of the signal, and making some "crossfade" math with the signal, something like:</p> <pre><code>signal = thub(sig, 2) # T-Hub is a T (tee) auto-copy filt1(signal) * line(dur, 0, 1) + filt2(signal) * line(dur, 1, 0) </code></pre> <p>This would apply a linear envelope (from 0 to 1 and from 1 to 0) from different filtered versions of the same signal. If <code>thub</code> looks confusing, try <code>sig1, sig2 = tee(sig, 2)</code> applying <code>filt(sig1)</code> and <code>filt(sig2)</code> instead, these should do the same.</p> <p><strong>EDIT 5: Time-variant Butterworth filter</strong></p> <p>I spent the last hours trying to let that Butterworth be personalized as your example, imposing <code>order = 2</code> and giving the half-power bandwidth (~3dB) directly. I've done four examples, the code is <a href="https://gist.github.com/danilobellini/6419374" rel="nofollow noreferrer">in this Gist</a>, and I've updated AudioLazy to include a <code>gauss_noise</code> Gaussian-distributed noise stream. Please note that the code in gist has nothing optimized, it was done ony to work in this particular case, and the chirp example makes it really slow due to a "per sample" coefficient finding behaviour. The instant frequency can be get from the [filtered] data in rad/sample with:</p> <pre><code>diff(unwrap(phase(hilbert(filtered_data)))) </code></pre> <p>where <code>diff = 1 - z ** -1</code> or another approach to find derivatives in discrete time, <code>hilbert</code> is the function from <code>scipy.signal</code> that gives us the analytical signal (the Discrete Hilbert Transform is the imaginary part of its result) and the other two are helper functions from AudioLazy.</p> <p>This is what happens when Butterworth changes its coefficients abruptly while keeping its memory, without noise:</p> <p><img src="https://i.stack.imgur.com/G71yH.png" alt="variable_butterworth_abrupt_pure_sinusoid.png"></p> <p>It's noticeable a oscilatory behaviour in this transition. You can use a moving median to "smooth" that in the lower frequency side while keeping the abrupt transition, but that won't work with the higher frequency. Well, that was what we would expect from a perfect sinusoid, but with noise (a LOT of noise, the Gaussian has the standard deviation equals to the sinusoid amplitude), it becomes:</p> <p><img src="https://i.stack.imgur.com/WkH5X.png" alt="variable_butterworth_abrupt_noisy.png"></p> <p>I tried then to do the same with a chirp, precisely this:</p> <p><img src="https://i.stack.imgur.com/OisIp.png" alt="variable_butterworth_pure_sinusoid.png"></p> <p>This shows a strange behaviour when filtering with the lower bandwidth, at the top frequency. And with the additive noise:</p> <p><img src="https://i.stack.imgur.com/D2xCU.png" alt="variable_butterworth_noisy.png"></p> <p>The code in gist also <code>AudioIO().play</code> this last noisy chirp.</p> <p><strong>EDIT 6: Time-variant resonator filter</strong></p> <p>I've added to <a href="https://gist.github.com/danilobellini/6419374" rel="nofollow noreferrer">the same Gist</a> an example using resonators instead of Butterworth. They're in pure Python and aren't optimized, but performs faster than calling <code>butter</code> for each sample during a chirp, and is far easier to implement, as all the <code>resonator</code> strategies accepts Stream instances as valid inputs. Here are the plots for a cascade of two resonators (i.e., a 2nd order filter):</p> <p><img src="https://i.stack.imgur.com/AypzD.png" alt="reson_2_abrupt_pure_sinusoid.png"> <img src="https://i.stack.imgur.com/WM411.png" alt="reson_2_abrupt_noisy.png"> <img src="https://i.stack.imgur.com/3XKAc.png" alt="reson_2_pure_sinusoid.png"> <img src="https://i.stack.imgur.com/5gcrm.png" alt="reson_2_noisy.png"></p> <p>And the same for a cascade of three resonators (i.e., a 3nd order filter):</p> <p><img src="https://i.stack.imgur.com/NK40J.png" alt="reson_3_abrupt_pure_sinusoid.png"> <img src="https://i.stack.imgur.com/YqesR.png" alt="reson_3_abrupt_noisy.png"> <img src="https://i.stack.imgur.com/bQTD5.png" alt="reson_3_pure_sinusoid.png"> <img src="https://i.stack.imgur.com/jytkp.png" alt="reson_3_noisy.png"></p> <p>These resonators have gain equals to 1 (0 dB) at the center frequency, and that oscillation pattern from the "Abrupt pure sinusoid" plots in the transition happens even without any filtering at all.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
    1. COThanks for your comment - I hadn't come across audiolazy before but this could be very useful. In your example above I am assuming that `freq` gives the central point of the bandpass filter, while `bw` gives the bandwidth - is that correct? Is there any more documentation of the type of filter being used? I have previously been using a Butterworth filter. I have another question regarding `freq`: say my Stream is 1000 points long, but I only want to supply filter coefficients for every 100 point "chunks". Is it possible to specify a `line` with these properties?
      singulars
    2. COInstead of line with the proposed repeater, you can also create your own repeater, or perhaps something like `Stream(100).limit(200).append(240).limit(300)` or `Stream([100] * 200 + [240] * 100)`. Any Stream instance can be used as a coefficient in a Z-Filter in AudioLazy, and Stream intances works with elementwise/broadcast operators like a 1D NumPy Array.
      singulars
    3. COI'm still struggling to get a simple filter set up. To filter my data thus far I have been using static Butterworth bandpass filters. When I try to develop an analogous filter using audiolazy, there remains significant noise in my data. For instance, say I want to filter a signal using a static bandpass filter with cutoffs at 600 and 700 Hz. This can easily be done with routines in `scipy.signal`. Trying the same in audiolazy I have used `freq=line(dur, 650, 650)`, `bw=line(dur, 50, 50)` and then `filt=resonator(freq*Hz, bw*Hz)`. Could you advise on why the results of both are so different?
      singulars
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload