that's a very good start!
It would seem that you have a pretty large base latency, as you do FFT even for the smallest block ... you could instead do time-domain convolution on the smallest block to minimise the latency there. Given how this is meant for a reverb, it may not be a big deal, but if one wanted to do convolution for e.g.: cabinet simulation, it'd be better with minimal latency. You could use the Convolver
library.
At a quick look, it seems that there is a lot of code repetition and it needs refactoring. Whenever you find yourself using variables with names ending in 1,2,3 ... it probably means you should be using a vector instead. I think you could the full step and take advantage of the (void* arg)
in the AuxiliaryTask
callbacks to pass distinct arguments to each and then you could use only one process_fft_background()
and one process_fft()
function. You will also need to get rid of the static variables in process_fft()
: make them global and pass around pointers to them.
It's probably easier to group all the relevant stuff in a struct
.
For instance:
struct FftData {
std::vector<std::vector<float>> fdr;
std::vector<std::vector<float>> fdi;
std::vector<float> unwrappedBuffer;
unsigned int fftSize;
unsigned int hopSize;
unsigned int windowSize;
unsigned int cachedInputBufferPointer;
unsigned int outputBufferWritePointer;
unsigned int hopCounter;
unsigned int outSize;
};
std::vector<FftData> gFftData;
std::vector<AuxiliaryTask> gTasks;
unsigned int gNumPartitions = 4;
unsigned int gFirstTaskPriority = 90;
void process_fft_background(void * arg);
bool setup(BelaContext* context, void*)
{
// resize before starting so the objects never get moved
gFftData.resize(gNumPartitions);
gTasks.resize(gNumPartitions);
for(unsigned int n = 0; n < gNumPartitions; ++n) {
unsigned int fftSize = 128 * ((1 + n) * 4); // double check I got these right
unsigned int hopSize = 64 * ((1 + n) * 4); // double check I got these right
std::vector<std::vector<float>> fd(5, std::vector<float> (fftSize, 0));
gFftData[n] = FftData{
.fftSize = fftSize,
.hopSize = hopSize,
.windowSize = hopSize,
.fdr = fd,
.fdi = fd,
.unwrappedBuffer = std::vector<float>(fftSize),
.cachedInputBufferPointer = 0,
.outputBufferWritePointer = 0,
.hopCounter = 0,
.outSize = hopSize * 2 -1,
};
std::string taskName = "bela-process-fft" + std::to_string(n);
void* arg = (void*)&gFftData[n]; // a pointer to the nth element, casted to void*
gTasks[n] = Bela_createAuxiliaryTask(process_fft_background, gFirstTaskPriority - n, taskName.c_str(), arg);
}
return true;
}
void process_fft(std::vector<float> const& inBuffer, std::vector<float>& outBuffer, FftData* fftData)
{
// here, use:
// fftData->fftSize, fftData->fdr, fftData->cachedInputBufferPointer, ...
// instead of the corresponding global variables
}
void process_fft_background(void * arg)
{
FftData* fftData = (FftData*)arg; // cast the pointer back
process_fft(gInputBuffer, gOutputBuffer, fftData);
fftData->outputBufferWritePointer = (fftData->outputBufferWritePointer + fftData->hopSize) % gBufferSize;
}
void render(BelaContext* context, void*)
{
// here, use gFftData[k]->hopSize, gFftData[k]->hopCounter and
// gFftTasks[k], gFftData[k]->cachedInputBufferPointer
// instead of the corresponding 1,2,3 variables
}