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

前端 未结 2 1668
没有蜡笔的小新
没有蜡笔的小新 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:03

    you can use scipy.fftpack.fftfreq and scipy.fftpack.rfft to set thresholds

    fft = scipy.fftpack.fft(sound)
    freqs = scipy.fftpack.fftfreq(sound.size, time_step)
    

    for the time_step I did twice the sampling rate of the sound

    fft[(freqs < 200)] = 0
    

    this would set all set all frequencies less than 200 hz to zero

    for the time varying cut off, I'd split the sound and apply the filters then. assuming the sound has a sampling rate of 44100, the 5000hz filter would start at sample 220500 (five seconds in)

    10ksound = sound[:220500]
    10kfreq = scipy.fftpack.fftreq(10ksound.size, time_step)
    10kfft = scipy.fftpack.fft(10ksound)
    10kfft[(10kfreqs < 10000)] = 0
    

    then for the next filter:

    5ksound = sound[220500:396900]
    5kfreq = scipy.fftpack.fftreq(10ksound.size, time_step)
    5kfft = scipy.fftpack.fft(10ksound)
    5kfft[(5kfreqs < 5000)] = 0
    

    etc

    edit: to make it "sliding" or a gradual filter instead of piece wise, you could make the "pieces" much smaller and apply increasingly bigger frequency thresholds to the corresponding piece(5000 -> 5001 -> 5002)

    0 讨论(0)
  • 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.

    0 讨论(0)
提交回复
热议问题