音频懒惰与随时间变化的过滤器一起工作
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)
您还可以使用它来绘图(或一般分析),方法是使用list(filt(sig))
or filt(sig).take(inf)
。还有许多其他资源也可能有用,例如直接在 Z 变换滤波器方程中应用时变系数。
编辑:有关 AudioLazy 组件的更多信息
以下示例是使用 IPython 完成的。
谐振器是一个StrategyDict
例如,它将许多实现绑定在一个地方。
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
内部调用resonator.poles_exp
函数,从中您可以获得一些帮助
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).
因此,详细的过滤器分配将是
filt = resonator.poles_exp(freq=freq * Hz, bandwidth=bw * Hz)
哪里的Hz
只是一个将单位从 Hz 更改为 rad/sample 的数字,如大多数 AudioLazy 组件中所使用的那样。
让我们这样做freq = pi/4
and bw = pi/8
(pi
已经在audiolazy
命名空间):
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
您可以尝试使用此过滤器而不是第一个示例中给出的过滤器。
另一种方法是使用z
包中的对象。
首先让我们找到该全极谐振器的常数:
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)
分母可以直接使用z
在等式中:
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
编辑2:关于时变系数
滤波器系数也可以是 Stream 实例(可以从任何可迭代对象进行转换)。
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]
同样,给定一个列表输入而不是impulse()
Stream:
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]
NumPy 一维数组也是可迭代的:
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]
最后一个示例显示了随时间变化的行为。
编辑3:分块重复序列行为
线函数的行为类似于numpy.linspace
,它获取范围“长度”而不是“步长”。
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]
这样,滤波器方程具有不同的每个样本行为(1 个样本“块”)。无论如何,您可以使用转发器来处理更大的块:
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]
并在第一个示例的行中使用它,这样freq
and bw
变成:
chunk_size = 100
freq = repeater(line(dur / chunk_size, 200, 800), chunk_size)
bw = repeater(line(dur / chunk_size, 100, 240), chunk_size)
编辑 4:使用时变增益/包络从 LTI 滤波器模拟时变滤波器/系数
另一种方法是对信号的两个不同滤波版本使用不同的“权重”,并对信号进行一些“交叉淡入淡出”数学运算,例如:
signal = thub(sig, 2) # T-Hub is a T (tee) auto-copy
filt1(signal) * line(dur, 0, 1) + filt2(signal) * line(dur, 1, 0)
这将应用来自同一信号的不同滤波版本的线性包络(从 0 到 1 和从 1 到 0)。如果thub
看起来很混乱,尝试一下sig1, sig2 = tee(sig, 2)
申请filt(sig1)
and filt(sig2)
相反,这些应该做同样的事情。
编辑 5:时变巴特沃斯滤波器
我花了最后几个小时试图让巴特沃斯个性化作为你的例子,强加order = 2
并直接给出半功率带宽(~3dB)。我做了四个例子,代码是在这个要点中,并且我更新了 AudioLazy 以包含gauss_noise
高斯分布噪声流。请注意,要点中的代码没有任何优化,它只是在这种特殊情况下工作,并且由于“每个样本”系数查找行为,chirp 示例使其变得非常慢。瞬时频率可以从 rad/sample 中的[过滤]数据中获得:
diff(unwrap(phase(hilbert(filtered_data))))
where diff = 1 - z ** -1
或另一种在离散时间内求导数的方法,hilbert
是函数scipy.signal
它为我们提供了分析信号(离散希尔伯特变换是其结果的虚部),另外两个是 AudioLazy 的辅助函数。
这就是当巴特沃斯突然改变其系数同时保持其记忆而没有噪声时所发生的情况:
![variable_butterworth_abrupt_pure_sinusoid.png](https://i.stack.imgur.com/G71yH.png)
在这个转变过程中,出现了明显的振荡行为。您可以使用移动中值来“平滑”较低频率侧的频率,同时保持突然的过渡,但这不适用于较高频率。好吧,这就是我们对完美正弦曲线的期望,但是有了噪声(大量噪声,高斯函数的标准差等于正弦曲线幅度),它就变成了:
![variable_butterworth_abrupt_noisy.png](https://i.stack.imgur.com/WkH5X.png)
然后我尝试用鸣叫声做同样的事情,正是这样:
![variable_butterworth_pure_sinusoid.png](https://i.stack.imgur.com/OisIp.png)
当在最高频率下使用较低带宽进行滤波时,这显示出奇怪的行为。加上附加噪声:
![variable_butterworth_noisy.png](https://i.stack.imgur.com/D2xCU.png)
gist 中的代码也AudioIO().play
这最后的嘈杂的鸣叫声。
编辑 6:时变谐振器滤波器
我已经添加到相同的要点使用谐振器代替巴特沃斯的示例。它们采用纯 Python 编写,未经过优化,但执行速度比调用更快butter
对于啁啾期间的每个样本,并且更容易实现,因为所有resonator
策略接受 Stream 实例作为有效输入。以下是两个谐振器级联(即二阶滤波器)的图:
![reson_2_noisy.png](https://i.stack.imgur.com/5gcrm.png)
对于三个谐振器的级联(即三阶滤波器)也是如此:
![reson_3_noisy.png](https://i.stack.imgur.com/jytkp.png)
这些谐振器在中心频率处的增益等于 1 (0 dB),并且即使根本没有任何滤波,过渡中“突变纯正弦曲线”图的振荡模式也会发生。