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 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/hopsize)+2 # preallocate arrays filt_audio = np.zeros((23,)+audio_np.shape) audio_block = np.zeros((audio_np.shape, audio_np.shape)) tempAudioBlocks = np.zeros((audio_np.shape, audio_np.shape)) 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: tempAudioBlocks = np.add(tempAudioBlocks,pastAudioBlocks) 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, 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.