#pragma once #include "dsp/functions.hpp" #include "pffft.h" namespace rack { /** Performs a direct sum convolution */ inline float convolveNaive(const float *in, const float *kernel, int len) { float y = 0.f; for (int i = 0; i < len; i++) { y += in[len - 1 - i] * kernel[i]; } return y; } /** Computes the impulse response of a boxcar lowpass filter */ inline void boxcarLowpassIR(float *out, int len, float cutoff = 0.5f) { for (int i = 0; i < len; i++) { float t = i - (len - 1) / 2.f; out[i] = 2 * cutoff * sinc(2 * cutoff * t); } } inline void blackmanHarrisWindow(float *x, int len) { // Constants from https://en.wikipedia.org/wiki/Window_function#Blackman%E2%80%93Harris_window const float a0 = 0.35875f; const float a1 = 0.48829f; const float a2 = 0.14128f; const float a3 = 0.01168f; float factor = 2*M_PI / (len - 1); for (int i = 0; i < len; i++) { x[i] *= + a0 - a1 * cosf(1*factor * i) + a2 * cosf(2*factor * i) - a3 * cosf(3*factor * i); } } struct RealTimeConvolver { // `kernelBlocks` number of contiguous FFT blocks of size `blockSize` // indexed by [i * blockSize*2 + j] float *kernelFfts = NULL; float *inputFfts = NULL; float *outputTail = NULL; float *tmpBlock = NULL; size_t blockSize; size_t kernelBlocks = 0; size_t inputPos = 0; PFFFT_Setup *pffft; /** `blockSize` is the size of each FFT block. It should be >=32 and a power of 2. */ RealTimeConvolver(size_t blockSize) { this->blockSize = blockSize; pffft = pffft_new_setup(blockSize*2, PFFFT_REAL); outputTail = new float[blockSize]; memset(outputTail, 0, blockSize * sizeof(float)); tmpBlock = new float[blockSize*2]; memset(tmpBlock, 0, blockSize*2 * sizeof(float)); } ~RealTimeConvolver() { setKernel(NULL, 0); delete[] outputTail; delete[] tmpBlock; pffft_destroy_setup(pffft); } void setKernel(const float *kernel, size_t length) { // Clear existing kernel if (kernelFfts) { pffft_aligned_free(kernelFfts); kernelFfts = NULL; } if (inputFfts) { pffft_aligned_free(inputFfts); inputFfts = NULL; } kernelBlocks = 0; inputPos = 0; if (kernel && length > 0) { // Round up to the nearest factor of `blockSize` kernelBlocks = (length - 1) / blockSize + 1; // Allocate blocks kernelFfts = (float*) pffft_aligned_malloc(sizeof(float) * blockSize*2 * kernelBlocks); inputFfts = (float*) pffft_aligned_malloc(sizeof(float) * blockSize*2 * kernelBlocks); memset(inputFfts, 0, sizeof(float) * blockSize*2 * kernelBlocks); for (size_t i = 0; i < kernelBlocks; i++) { // Pad each block with zeros memset(tmpBlock, 0, sizeof(float) * blockSize*2); size_t len = min((int) blockSize, (int) (length - i*blockSize)); memcpy(tmpBlock, &kernel[i*blockSize], sizeof(float)*len); // Compute fft pffft_transform(pffft, tmpBlock, &kernelFfts[blockSize*2 * i], NULL, PFFFT_FORWARD); } } } /** Applies reverb to input input and output must be of size `blockSize` */ void processBlock(const float *input, float *output) { if (kernelBlocks == 0) { memset(output, 0, sizeof(float) * blockSize); return; } // Step input position inputPos = (inputPos + 1) % kernelBlocks; // Pad block with zeros memset(tmpBlock, 0, sizeof(float) * blockSize*2); memcpy(tmpBlock, input, sizeof(float) * blockSize); // Compute input fft pffft_transform(pffft, tmpBlock, &inputFfts[blockSize*2 * inputPos], NULL, PFFFT_FORWARD); // Create output fft memset(tmpBlock, 0, sizeof(float) * blockSize*2); // convolve input fft by kernel fft // Note: This is the CPU bottleneck loop for (size_t i = 0; i < kernelBlocks; i++) { size_t pos = (inputPos - i + kernelBlocks) % kernelBlocks; pffft_zconvolve_accumulate(pffft, &kernelFfts[blockSize*2 * i], &inputFfts[blockSize*2 * pos], tmpBlock, 1.f); } // Compute output pffft_transform(pffft, tmpBlock, tmpBlock, NULL, PFFFT_BACKWARD); // Add block tail from last output block for (size_t i = 0; i < blockSize; i++) { tmpBlock[i] += outputTail[i]; } // Copy output block to output float scale = 1.f / (blockSize*2); for (size_t i = 0; i < blockSize; i++) { // Scale based on FFT output[i] = tmpBlock[i] * scale; } // Set tail for (size_t i = 0; i < blockSize; i++) { outputTail[i] = tmpBlock[i + blockSize]; } } }; } // namespace rack