Lowpass filter with a time-varying cutoff frequency, with Python

前端 未结 2 1670
没有蜡笔的小新
没有蜡笔的小新 2020-12-31 14:16

How to apply a lowpass filter, with cutoff frequency varying linearly (or with a more general curve than linear) from e.g. 10000hz to 200hz along time, with

2条回答
  •  无人及你
    2020-12-31 15:09

    One comparatively easy method is to keep the filter fixed and modulate signal time instead. For example, if signal time runs 10x faster a 10KHz lowpass will act like a 1KHz lowpass in standard time.

    To do this we need to solve a simple ODE

    dy       1
    --  =  ----
    dt     f(y)
    

    Here t is modulated time y real time and f the desired cutoff at time y.

    Prototype implementation:

    from __future__ import division
    import numpy as np
    from scipy import integrate, interpolate
    from scipy.signal import butter, lfilter, spectrogram
    
    slack_l, slack = 0.1, 1
    cutoff = 50
    L = 25
    
    from scipy.io import wavfile
    sr, x = wavfile.read('capriccio.wav')
    x = x[:(L + slack) * sr, 0]
    x = x
    
    # sr = 44100
    # x = np.random.normal(size=((L + slack) * sr,))
    
    b, a = butter(2, 2 * cutoff / sr, btype='low')  # Butterworth
    
    # cutoff function
    def f(t):
        return (10000 - 1000 * np.clip(t, 0, 9) - 1000 * np.clip(t-19, 0, 0.8)) \
            / cutoff
    
    # and its reciprocal
    def fr(_, t):
        return cutoff / (10000 - 1000 * t.clip(0, 9) - 1000 * (t-19).clip(0, 0.8))
    
    # modulate time
    # calculate upper end of td first
    tdmax = integrate.quad(f, 0, L + slack_l, points=[9, 19, 19.8])[0]
    span = (0, tdmax)
    t = np.arange(x.size) / sr
    tdinfo = integrate.solve_ivp(fr, span, np.zeros((1,)),
                                 t_eval=np.arange(0, span[-1], 1 / sr),
                                 vectorized=True)
    td = tdinfo.y.ravel()
    # modulate signal
    xd = interpolate.interp1d(t, x)(td)
    # and linearly filter
    yd = lfilter(b, a, xd)
    # modulate signal back to linear time
    y = interpolate.interp1d(td, yd)(t[:-sr*slack])
    
    # check
    import pylab
    xa, ya, z = spectrogram(y, sr)
    pylab.pcolor(ya, xa, z, vmax=2**8, cmap='nipy_spectral')
    pylab.savefig('tst.png')
    
    wavfile.write('capriccio_vandalized.wav', sr, y.astype(np.int16))
    

    Sample output:

    Spectrogram of first 25 seconds of BWV 826 Capriccio filtered with a time varying lowpass implemented via time bending.

提交回复
热议问题