I am working on a filter which performs a FFT on 1024 samples, while pushing 256 new samples every time it iterates. This creates a 75% overlap and I am dealing with the overlap-add so it is working well for 256 and multiples of 256 buffer size. How to deal with other sample sizes, I want to have a consistent sounding results across different buffer sizes so I do not want to update it more frequently or something.
Here is the code that works for 256 samples and multiples of it :
const int numSamples = bufferToWritePointer->getNumSamples();
accumulation += numSamples;
int sampleIndex = 0;
while ((accumulation - maxAccumulation) >= 0) {
for (int channel = 0; channel < 2; ++channel) {
float* channelData = bufferToWritePointer->getWritePointer(channel);
const float* rawData = readBuff->getReadPointer(channel);
float* fftDataChannel = fftData[channel].data();
float* tempDataChannel = tempData[channel].data();
float* overlapBuffer1 = overlapStore[0]->getWritePointer(channel);
float* overlapBuffer2 = overlapStore[1]->getWritePointer(channel);
float* overlapBuffer3 = overlapStore[2]->getWritePointer(channel);
// Shift the previous samples forward by maxAccumulation.
for (int i = 0; i < fftSize - maxAccumulation; ++i) {
fftDataChannel[i] = fftDataChannel[i + maxAccumulation];
}
// Append maxAccumulation new samples to the end of the fftData buffer.
for (int i = 0; i < maxAccumulation; ++i) {
fftDataChannel[fftSize - maxAccumulation + i] = rawData[sampleIndex + i];
}
// Apply the windowing function to the entire fftSize buffer and copy it to temp buffer.
for (int i = 0; i < fftSize; ++i) {
tempDataChannel[i] = fftDataChannel[i] * windowCoefficients[i];
}
// Perform an inplace FFT on the tempDataChannel.
forwardFFT.performRealOnlyForwardTransform(tempDataChannel);
// Multiply with the corresponding filter coefficients.
// Perform the inverse FFT
inverseFFT.performRealOnlyInverseTransform(tempDataChannel);
// Overlap-add the processed samples into the output buffer
for (int i = 0; i < maxAccumulation; ++i) {
channelData[sampleIndex + i] += ( tempDataChannel[i] +
overlapBuffer3[i + maxAccumulation] +
overlapBuffer2[i + 2 * maxAccumulation] +
overlapBuffer1[i + 3 * maxAccumulation]) / 4.0f;
}
// Store the new overlap samples
std::copy(tempDataChannel, tempDataChannel + fftSize, overlapBuffer1);
}
// change the order of the pointers as
// from [ 1, 2, 3 ]
// to [ 2, 3, 1 ]
juce::AudioBuffer<float>* temp = overlapStore[0];
overlapStore[0] = overlapStore[1];
overlapStore[1] = overlapStore[2];
overlapStore[2] = temp;
sampleIndex += maxAccumulation;
accumulation -= maxAccumulation;
}
After tearing my hair out to make this work for a couple of days finally settled on this :
here maxAccumulation is 256
and leftOverSamples is 256 at the beginning (this kind of introduces a latency as @zsliu98 mentioned) and because we are reading from partBufferOut make sure to clear it out before processing the first block :
void BExp() {
const int numSamples = bufferToWritePointer->getNumSamples();
int sampleIndex = 0;
int sampleIndexRead = 0;
// Handle leftover samples first
if (leftOverSamples) {
float* write_right_ch_data = bufferToWritePointer->getWritePointer(1);
float* write_left_ch_data = bufferToWritePointer->getWritePointer(0);
float* part_out_right = partStoreBufferOUT.getWritePointer(1);
float* part_out_left = partStoreBufferOUT.getWritePointer(0);
while (leftOverSamples > 0 && sampleIndex < numSamples) {
write_right_ch_data[sampleIndex] = part_out_right[partBufferConsumeIndex];
write_left_ch_data[sampleIndex] = part_out_left[partBufferConsumeIndex];
sampleIndex++;
partBufferConsumeIndex++;
leftOverSamples--;
}
}
while (sampleIndexRead < numSamples) {
int remainingSamples = numSamples - sampleIndexRead;
// if there are enough samples to process them and write them to the output.
if (partBufferFillIndex + remainingSamples >= maxAccumulation) {
float* part_in_right = partStoreBufferIN.getWritePointer(1);
float* part_in_left = partStoreBufferIN.getWritePointer(0);
const float* read_right = readBuff->getReadPointer(1);
const float* read_left = readBuff->getReadPointer(0);
int samplesToFill = maxAccumulation - partBufferFillIndex;
for (int i = 0; i < samplesToFill; i++, sampleIndexRead++, partBufferFillIndex++) {
part_in_left[partBufferFillIndex] = read_left[sampleIndexRead];
part_in_right[partBufferFillIndex] = read_right[sampleIndexRead];
}
// PROCESS THE PARTIAL BUFFER
processPartBuffer();
partBufferFillIndex = 0;
leftOverSamples = 256;
partBufferConsumeIndex = 0;
float* write_right_ch_data = bufferToWritePointer->getWritePointer(1);
float* write_left_ch_data = bufferToWritePointer->getWritePointer(0);
const float* part_out_right = partStoreBufferOUT.getReadPointer(1);
const float* part_out_left = partStoreBufferOUT.getReadPointer(0);
// copy the processed samples to the output buffer to write.
for (; partBufferConsumeIndex < maxAccumulation && sampleIndex < numSamples; sampleIndex++, partBufferConsumeIndex++, leftOverSamples--) {
write_right_ch_data[sampleIndex] = part_out_right[partBufferConsumeIndex];
write_left_ch_data[sampleIndex] = part_out_left[partBufferConsumeIndex];
}
} else {
// if the samples are not enough just copy them we will process the next time.
float* part_in_right = partStoreBufferIN.getWritePointer(1);
float* part_in_left = partStoreBufferIN.getWritePointer(0);
const float* raw_in_right = readBuff->getWritePointer(1);
const float* raw_in_left = readBuff->getWritePointer(0);
for (int i = 0; i < remainingSamples; i++, sampleIndexRead++, partBufferFillIndex++) {
part_in_left[partBufferFillIndex] = raw_in_left[sampleIndexRead];
part_in_right[partBufferFillIndex] = raw_in_right[sampleIndexRead];
}
}
}
}
void processPartBuffer() {
for (int channel = 0; channel < 2; ++channel) {
float* channelData = partStoreBufferOUT.getWritePointer(channel);
const float* rawData = partStoreBufferIN.getReadPointer(channel);
float* fftDataChannel = fftData[channel].data();
float* tempDataChannel = tempData[channel].data();
float* overlapBuffer1 = overlapStore[0]->getWritePointer(channel);
const float* overlapBuffer2 = overlapStore[1]->getReadPointer(channel);
const float* overlapBuffer3 = overlapStore[2]->getReadPointer(channel);
// Shift the previous samples forward by maxAccumulation.
for (int i = 0; i < fftSize - maxAccumulation; ++i) {
fftDataChannel[i] = fftDataChannel[i + maxAccumulation];
}
// Append maxAccumulation new samples to the end of the fftData buffer.
for (int i = 0; i < maxAccumulation; ++i) {
fftDataChannel[fftSize - maxAccumulation + i] = rawData[i];
}
// Apply the windowing function to the entire fftSize buffer and copy it to temp buffer.
for (int i = 0; i < fftSize; ++i) {
tempDataChannel[i] = fftDataChannel[i] * windowCoefficients[i];
}
// Perform an inplace FFT on the tempDataChannel.
forwardFFT.performRealOnlyForwardTransform(tempDataChannel);
// DO WHAT YOU WANT WITH THE FREQUENCY DOMAIN SIGNAL HERE.
/////////////////////////////
// Perform the inverse FFT
inverseFFT.performRealOnlyInverseTransform(tempDataChannel);
for (int i = 0; i < fftSize; ++i) {
tempDataChannel[i] = tempDataChannel[i] * windowCoefficients[i];
}
// Overlap-add the processed samples into the output buffer
for (int i = 0; i < maxAccumulation; ++i) {
channelData[i] = (float)(tempDataChannel[i] +
overlapBuffer3[i + maxAccumulation] +
overlapBuffer2[i + 2 * maxAccumulation] +
overlapBuffer1[i + 3 * maxAccumulation]) * 0.25f;
}
// Store the new overlap samples(replace the oldest ones)
std::copy(tempDataChannel, tempDataChannel + fftSize, overlapBuffer1);
}
// change the order of the pointers as
// from [ 1, 2, 3 ]
// to [ 2, 3, 1 ]
juce::AudioBuffer<float>* temp = overlapStore[0];
overlapStore[0] = overlapStore[1];
overlapStore[1] = overlapStore[2];
overlapStore[2] = temp;
}