Reconstructing a signal from multiple filtered copies using overlapping tapered windows

I have an audio signal that I need to process and analyse in blocks. I've now decided to process it using overlapping non-symetric Hanning windows of length M = 9600 and a hopsize of R= M/2. I zero padd each end of the signal with R zeros. If I just take the original signal, process it using the overlapping windows, and then sum together the results with no other processing I can reconstruct the signal perfectly apart from what I'm assuming are quantisation errors.

The problem comes when I try to introduce other processing into the signal chain. I filter the signal using a bank of FIR filterbanks and I also pass the filterstate at the end of the block onto the next block. These are reconstructed the same way as with the original signal and I end up with (for example) 10 filtered copies of my signal, each copy being filtered at a different frequency band. To then reconstruct the whole signal from the filtered copies I sum them together. But when I plot the resulting signal there are issues around where the window hops would take place, like something isn't summing correctly but in theory at the hop edges, the window for block 1 would be at 1 and the window for block 2 would be at 0. Meaning summing them together should result in the original magnitude.

The error at the beginning would be cropped out, as I've incuded zero padding to account for that. But I can't work out why there are discontinuities within the waveform itself.

Below is a simplified version of the code (I've taken out all the parts I'm not currently troubleshooting)

# get window and calculate hopsize
win = np.repeat([signal.windows.hann(blocksize, sym = True)],4,axis = 0).T
winsize = win.shape[0]
hopsize = winsize/2

# zero pad audio array for filtering and calculate number of hops

audio_np = np.pad(audio_np,((int(hopsize),int(hopsize)),(0,0)), 'constant', constant_values =(0))

Nhop = np.ceil(audio_np.shape[0]/hopsize)+2
# preallocate arrays

filt_audio = np.zeros((23,)+audio_np.shape)

audio_block = np.zeros((audio_np.shape[0], audio_np.shape[1]))

tempAudioBlocks = np.zeros((audio_np.shape[0], audio_np.shape[1]))
tempFilteredBlocks = np.zeros((filt_audio1.shape))

# set up some function arguments
zi1 = None
pastSample_filt = None

# pad start with zeros start and end (half window?)

for idx in np.arange(0,(Nhop-3)*hopsize,hopsize):

idx = int(idx)
block = audio_np[idx:idx+winsize]

block_win = block*win

tempAudioBlocks[idx:idx+winsize,:],tempFilteredBlocks[:,idx:idx+winsize,:],zi1, pastSample_filt = function(block_win, fs, filt_taps, first_band, channels, zi=zi1, pastSample_filt = pastSample_filt)

if idx == 0:
audio_block[idx:idx+winsize] = tempAudioBlocks[idx:idx+winsize]
filt_audio[:,idx:idx+winsize,:]  = tempFilteredBlocks[:,idx:idx+winsize,:]

pastAudioBlock = np.copy(tempAudioBlocks)
pastFilteredBlock = np.copy(tempFilteredBlocks)
tempAudioBlocks.fill(0)
tempFilteredBlocks.fill(0)
else:
tempFilteredBlocks = np.add(tempFilteredBlocks, pastFilteredBlocks)
audio_block[idx:idx+winsize] = tempAudioBlocks[idx:idx+winsize]
filt_audio[:,idx:idx+winsize,:] = tempFilteredBlocks[:,idx:idx+winsize,:]
pastAudioBlocks = np.copy(tempAudioBlocks)
pastFilteredBlocks = np.copy(tempFilteredBlocks)
tempAudioBlocks.fill(0)
tempFilteredBlocks.fill(0)


The first plot is generated from the data in audio_block and this is just passed into the function and returned out again - nothing happens to it inside the function.

The additional filtering/processing in order to get filt_audio is below


filters, filter_band_freqs = np.array(erbSpaced_filterBank(first_band ,bandsize,channels,filt_taps,fs))
filt_audio = np.zeros((len(filters),)+audio.shape)

if zi is not None:
zi = zi
else:

zi = [np.repeat([signal.lfilter_zi(filt,[1.0])],audio_pad.shape[1], axis =0).T for filt in filters]

for i in range(len(filters)):
filt_audio[i], zi[i] = signal.lfilter(filters[i], [1.0], audio_pad, axis = 0, zi=zi[i])



filt_audio within the function space is returned into the TempFilteredBlocks variable. When reconsturcted it gives the second plot.

One idea that has been proposed, is that it's the passing of the filter state that is causing issues because of the overlapping windows. The filterstate needed for the start of block 2, would be the filterstate halfway through block 1 - due to the overlap. At present I'm passing forward the state at the end of block 1, which would actually be half way through block 2.

• hi! could you tell us what specifically "process it with overlapping windows" is? It feels a bit like you actually want to filter it (i.e. convolve it with a filter impulse response), but you specifically chose to not use that word, so I'm not sure what you're doing. – Marcus Müller Aug 2 at 10:36
• Sorry, I take a block of my signal, I then convolve it with the hanning window and it gets passed into another function contianing my FIR filterbank.If I bypass the FIR filterbank I can reconstruct it no problem which is the first plot If I introduce the filterbank - and split it into 10 bands, I then try and reconstruct from those 10 bands, and that results in plot two. Hope that's clearer :-) – Molem7b5 Aug 2 at 16:01
• wait, when you convolve with something, it stops being called "window"; it's now a filter. I suspect your problem is that your block-wise convolution is discontinuous at the block boundaries, and your filter just "demonstrates" that shortcoming. However, nothing specific can be said without seeing what exactly you're doing there (as in: formulas or code) – Marcus Müller Aug 2 at 16:05
• I've added some code. Thanks for your help so far. – Molem7b5 Aug 2 at 16:54
• By the way, in the bottom of my feet (for apodization), I think you could push the Hann window into the filterbank. Typically, using an STFT at different hop length is a traditional FB job – Laurent Duval Aug 2 at 17:15