Filtro de paso bajo con una frecuencia de corte variable con el tiempo, con Python

¿Cómo aplicar un filtro de paso bajo, con una frecuencia de corte que varía linealmente (o con una curva más general que lineal) de, por ejemplo, 10000 hz a 200 hz a lo largo del tiempo , con numpy / scipy y posiblemente ninguna otra biblioteca?

Ejemplo:

  • a las 00: 00,000, corte de paso bajo = 10000 hz
  • a las 00: 05,000, corte de paso bajo = 5000 hz
  • a las 00: 09,000, corte de paso bajo = 1000 hz
  • luego el corte permanece a 1000 hz durante 10 segundos, luego el corte se reduce a 200 hz

Aquí es cómo hacer un simple paso bajo de 100Hz:

from scipy.io import wavfile import numpy as np from scipy.signal import butter, lfilter sr, x = wavfile.read('test.wav') b, a = butter(2, 100.0 / sr, btype='low') # Butterworth y = lfilter(b, a, x) wavfile.write('out.wav', sr, np.asarray(y, dtype=np.int16)) 

¿Pero cómo hacer que el corte varíe?

Nota: Ya he leído Aplicar filtro de variante de tiempo en Python, pero la respuesta es bastante compleja (y se aplica a muchos tipos de filtro en general).

    Un método comparativamente fácil es mantener el filtro fijo y modular el tiempo de la señal. Por ejemplo, si el tiempo de la señal se ejecuta 10 veces más rápido, un paso bajo de 10 KHz actuará como un paso bajo de 1 KHz en tiempo estándar.

    Para ello necesitamos resolver una EDO sencilla.

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

    Aquí t se modula el tiempo y el tiempo real y f el corte deseado en el tiempo y .

    Implementación de prototipos:

     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)) 

    Salida de muestra:

    Espectrograma de los primeros 25 segundos de BWV 826 Capriccio filtrado con un paso bajo variable en el tiempo implementado a través del doblado en el tiempo.

    Espectrogtwig de los primeros 25 segundos de BWV 826 Capriccio filtrado con un paso bajo variable en el tiempo implementado a través del doblado en el tiempo.

    puede usar scipy.fftpack.fftfreq y scipy.fftpack.rfft para establecer umbrales

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

    Para el time_step hice dos veces la frecuencia de muestreo del sonido

     fft[(freqs < 200)] = 0 

    esto establecería todas las frecuencias establecidas menos de 200 hz a cero

    por el tiempo variando el corte, dividiría el sonido y luego aplicaría los filtros. asumiendo que el sonido tiene una frecuencia de muestreo de 44100, el filtro de 5000 hz comenzaría en la muestra 220500 (cinco segundos)

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

    luego para el siguiente filtro:

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

    etc

    edición: para que sea "deslizante" o un filtro gradual en lugar de pieza, puede hacer que las "piezas" sean mucho más pequeñas y aplicar umbrales de frecuencia cada vez más grandes a la pieza correspondiente (5000 -> 5001 -> 5002)