2
votes

I would like to apply a FIR or IIR filter (example: lowpass filter) on successive blocks/time-frames of 1024 samples each.

Possible applications:

  • realtime audio processing, such as EQing. At a precise time, we only have the next 1024 samples in a buffer. The next samples to process are not available yet (realtime).

  • make a cutoff-time-varying filter by splitting the input signal in blocks, as suggested in this answer.

I 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 with a Butterworth filter or a FIR (replace the line before by b, a = firwin(1000, cutoff=f, fs=sr), 1.0)

  • both with lfilter and filtfilt (the latter has the advantage to apply the filter forward and backwards, and this solves phase issues),

but here is the problem:

At the boundaries of each time-frames' output, there is a continuity issue, that makes the audio signal heavily distorded.

How to solve this discontinuity problem? I thought about windowing+OverlapAdd method, but there surely must be an easier way.

enter image description here

1
Since you are using an IIR-filter, you will need to persist the state of your filter from one block to the next.sobek
Not the parameters, the state of the filter, the data that is in its memory. If you jump from one block to the next and use a completely uninitialized IIR-filter, of course you are going to get an artifact. The output of your processing has to be identical to if you hadn't processed it in blocks, except for the lag from buffering.sobek
Look at the z_f output and z_i input parameters of lfilt. You need to set z_i of the filter for the current block to z_f of the filter of the previous block. That way your filter state is persisted from one block to the next. This topic from dsp stackexchange might also be useful. dsp.stackexchange.com/questions/28725/… Also it would probably be a good idea for you to understand the basics of how a filter is built up internally so this all makes sense.sobek
@sobek Thanks! I posted an answer. It seems to work. Do you think it's ok even in the case where the cutoff changes at each iteration (thus the a, b too)?Basj
I think it's alright in this case.sobek

1 Answers

3
votes

As mentioned by @sobek in a comment, it's of course needed to specify the initial conditions to allow continuity. This is done with the zi parameter of lfilter.

The problem is solved by changing the main loop by:

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 (and thus the a and b) is modified at each iteration.