Python – continuity problem when applying IIR filters on continuous timeframes

continuity problem when applying IIR filters on continuous timeframes… here is a solution to the problem.

continuity problem when applying IIR filters on continuous timeframes

I want to apply a FIR or IIR filter on a continuous block/timeframe of 1024 samples (example: low-pass filter).

Possible applications:

  • Real-time audio processing, such as EQing. At precise time, there are only the next 1024 samples in the buffer. The next sample to be processed is not yet available (live).

  • As suggested, make a cutoff time-varying filter in this answer

I’ve tried this here :

import numpy as np
from scipy.io import wavfile
from scipy.signal import butter, lfilter, filtfilt, firwin

sr, x = wavfile.read('input.wav')
x = np.float32(x)
y = np.zeros_like(x)

N  = 1024  # buffer block size = 23ms for a 44.1 Khz audio file
f = 1000  # cutoff
pos = 0  # position

while True:
    b, a = butter(2, 2.0 * f / sr, btype='low')
    y[pos:pos+N] = filtfilt(b, a, x[pos:pos+N])
    pos += N
    f -= 1   # cutoff decreases of 1 hz every 23 ms, but the issue described here also present with constant cutoff!
    print f
    if pos+N > len(x):
        break

y /= max(y)  # normalize

wavfile.write('out_fir.wav', sr, y)

I tried:

  • Both use the Butterworth filter or FIR (replace the previous line with b, a = firwin(1000, cutoff=f, fs=sr), 1.0).

  • All with >lfilter and filtfilt ( The advantage of the latter is that the filter can be applied forward and backward, which solves the phase problem).

But the question is:

At the boundary of each timeframe output, there is a continuity problem, which causes the audio signal to be severely distorted.

How to solve this discontinuous problem? The windowing+OverlapAdd approach came to mind, but there are definitely simpler ways.

enter image description here

Solution

As mentioned in @sobek comment, of course it is necessary to specify initial conditions to allow continuity. This is done via lfilter's zi parameter.

The problem is solved by changing the main loop:

while True:
    b, a = butter(2, 2.0 * f / sr, btype='low')
    if pos == 0:
        zi = lfilter_zi(b, a)
    y[pos:pos+N], zi = lfilter(b, a, x[pos:pos+N], zi=zi)
    pos += N
    f -= 1 
    if pos+N > len(x):
        break

This seems to work even if the filter’s cutoff value (and therefore a and b) is modified on each iteration.

Related Problems and Solutions