Processing
To process a sample batch the equalizer will process each channel. The states of the equalizer will not be reset between batches. This is equal to just processing the whole file and then playing batches of it.
Linear Phase Processing
To filter a file with an equalizer without distorting the phase an easy solution is to just filter the file twice - once forward and once backward - the phase distortion of each filtering will cancel the other out. Doing this process in a batch isn’t directly possible as we’d have to wait for the forward pass to finish before the backward pass can start and we can’t do the backward pass backwards in time either. But under the assumption that the impulse response of the equalizer is finite the odds change in our favour. With a finite impulse response the filtering won’t be influenced by samples who’s impulse response is already over. This means that it doesn’t matter what the equalizer processed before that time - the values for the current sample will stay the same.
Under this assumption the process of doing a forward and backward pass can easiely be done on batches - all we need to do is to delay the signal a bit. The forward pass was never a problem and stimm isn’t, that part of our code will stay the same. We only have to add the backward pass. For this we first delay the signal by the length of the impulse response of the equalizer. We get the delayed bit of the previous batch in the so called front buffer. The rest is done the following way:
- Copy the last samples of the batch into the so called back buffer, wich will get passed to the next batch.
- Reset the equalizer states for the backward pass (wich differ from those of the forward pass).
- Prepare the filterstates for the current batch by filtering the back buffer without saving the filtered samples. As the back buffer has the length of the impulse response everything after this will be filtered as if the whole file was filtered before them - not just the little buffer.
- Filter the samples in the channel and save them delayed by the impulse response length. We do this in place in the channel data buffer.
- Filter the buffer of the last batch (the so called front buffer) and write the result to the channel data buffer.
- Swap front and back buffer - now what was just written to the back buffer will be in the front buffer of the next batch.
Reacting to changed Equalizer Settings
When the parameter of filters in the equalizer change, the biquad coefficients can’t be updated immediatly as it will get updated in another thread as the process block. With the coefficient update the filter states have to be reset as well as biquad filters with arbitrary states can lead to unintended behavior like potentially loud crackling and popping noise. But just updating and reseting both will still lead to crackling noises albeit ones wich aren’t as loud but still annoying. Our solution to this is to just filter a few (~32) samples with the old coefficients, save the output into a temporary buffer, update the coefficients, reset the states, filter those samples again and then linearly crossfade between both outputs. This easy solution removes the remaining crackling and it does mean that changing values is still responsive.
Reacting to changed Equalizer Settings in Linear Phase Mode
In zero phase mode we encounter another problem: the impulse response length of the equalizer will change. This does mean we have to resize the buffers for the zero phase mode, but we can’t just resize them inside the process block as it’s not advised to allocate memory there. We solve this by asynchronously applying the updated settings to a second equalizer and calculating it’s impulse response length. Then we’ll resize a second pair of buffers. When finished we’ll swap the buffers at the start of the next batch. We will also copy the front buffer to the resized front buffer (or at least what will fit inside it - or pad the rest with zeroes). The forward pass we will be done with a crossfade like before, this will also update the coefficients of the equalizer. The backward pass will be done like before as well, but this does mean that it’ll either filter some zero padding or a forward pass wich was filtered with different settings. This does mean that this part isn’t zero phase. This solution is not optimal, but “good enough” to make changing values in zero phase mode possible albeit with the occasional little crackle.