You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

149 lines
4.3KB

  1. #pragma once
  2. #include "dsp/functions.hpp"
  3. #include "pffft.h"
  4. namespace rack {
  5. /** Performs a direct sum convolution */
  6. inline float convolveNaive(const float *in, const float *kernel, int len) {
  7. float y = 0.f;
  8. for (int i = 0; i < len; i++) {
  9. y += in[len - 1 - i] * kernel[i];
  10. }
  11. return y;
  12. }
  13. /** Computes the impulse response of a boxcar lowpass filter */
  14. inline void boxcarLowpassIR(float *out, int len, float cutoff = 0.5f) {
  15. for (int i = 0; i < len; i++) {
  16. float t = i - (len - 1) / 2.f;
  17. out[i] = 2 * cutoff * sinc(2 * cutoff * t);
  18. }
  19. }
  20. inline void blackmanHarrisWindow(float *x, int len) {
  21. // Constants from https://en.wikipedia.org/wiki/Window_function#Blackman%E2%80%93Harris_window
  22. const float a0 = 0.35875f;
  23. const float a1 = 0.48829f;
  24. const float a2 = 0.14128f;
  25. const float a3 = 0.01168f;
  26. float factor = 2*M_PI / (len - 1);
  27. for (int i = 0; i < len; i++) {
  28. x[i] *=
  29. + a0
  30. - a1 * cosf(1*factor * i)
  31. + a2 * cosf(2*factor * i)
  32. - a3 * cosf(3*factor * i);
  33. }
  34. }
  35. struct RealTimeConvolver {
  36. // `kernelBlocks` number of contiguous FFT blocks of size `blockSize`
  37. // indexed by [i * blockSize*2 + j]
  38. float *kernelFfts = NULL;
  39. float *inputFfts = NULL;
  40. float *outputTail = NULL;
  41. float *tmpBlock = NULL;
  42. size_t blockSize;
  43. size_t kernelBlocks = 0;
  44. size_t inputPos = 0;
  45. PFFFT_Setup *pffft;
  46. /** `blockSize` is the size of each FFT block. It should be >=32 and a power of 2. */
  47. RealTimeConvolver(size_t blockSize) {
  48. this->blockSize = blockSize;
  49. pffft = pffft_new_setup(blockSize*2, PFFFT_REAL);
  50. outputTail = new float[blockSize];
  51. memset(outputTail, 0, blockSize * sizeof(float));
  52. tmpBlock = new float[blockSize*2];
  53. memset(tmpBlock, 0, blockSize*2 * sizeof(float));
  54. }
  55. ~RealTimeConvolver() {
  56. setKernel(NULL, 0);
  57. delete[] outputTail;
  58. delete[] tmpBlock;
  59. pffft_destroy_setup(pffft);
  60. }
  61. void setKernel(const float *kernel, size_t length) {
  62. // Clear existing kernel
  63. if (kernelFfts) {
  64. pffft_aligned_free(kernelFfts);
  65. kernelFfts = NULL;
  66. }
  67. if (inputFfts) {
  68. pffft_aligned_free(inputFfts);
  69. inputFfts = NULL;
  70. }
  71. kernelBlocks = 0;
  72. inputPos = 0;
  73. if (kernel && length > 0) {
  74. // Round up to the nearest factor of `blockSize`
  75. kernelBlocks = (length - 1) / blockSize + 1;
  76. // Allocate blocks
  77. kernelFfts = (float*) pffft_aligned_malloc(sizeof(float) * blockSize*2 * kernelBlocks);
  78. inputFfts = (float*) pffft_aligned_malloc(sizeof(float) * blockSize*2 * kernelBlocks);
  79. memset(inputFfts, 0, sizeof(float) * blockSize*2 * kernelBlocks);
  80. for (size_t i = 0; i < kernelBlocks; i++) {
  81. // Pad each block with zeros
  82. memset(tmpBlock, 0, sizeof(float) * blockSize*2);
  83. size_t len = min((int) blockSize, (int) (length - i*blockSize));
  84. memcpy(tmpBlock, &kernel[i*blockSize], sizeof(float)*len);
  85. // Compute fft
  86. pffft_transform(pffft, tmpBlock, &kernelFfts[blockSize*2 * i], NULL, PFFFT_FORWARD);
  87. }
  88. }
  89. }
  90. /** Applies reverb to input
  91. input and output must be of size `blockSize`
  92. */
  93. void processBlock(const float *input, float *output) {
  94. if (kernelBlocks == 0) {
  95. memset(output, 0, sizeof(float) * blockSize);
  96. return;
  97. }
  98. // Step input position
  99. inputPos = (inputPos + 1) % kernelBlocks;
  100. // Pad block with zeros
  101. memset(tmpBlock, 0, sizeof(float) * blockSize*2);
  102. memcpy(tmpBlock, input, sizeof(float) * blockSize);
  103. // Compute input fft
  104. pffft_transform(pffft, tmpBlock, &inputFfts[blockSize*2 * inputPos], NULL, PFFFT_FORWARD);
  105. // Create output fft
  106. memset(tmpBlock, 0, sizeof(float) * blockSize*2);
  107. // convolve input fft by kernel fft
  108. // Note: This is the CPU bottleneck loop
  109. for (size_t i = 0; i < kernelBlocks; i++) {
  110. size_t pos = (inputPos - i + kernelBlocks) % kernelBlocks;
  111. pffft_zconvolve_accumulate(pffft, &kernelFfts[blockSize*2 * i], &inputFfts[blockSize*2 * pos], tmpBlock, 1.f);
  112. }
  113. // Compute output
  114. pffft_transform(pffft, tmpBlock, tmpBlock, NULL, PFFFT_BACKWARD);
  115. // Add block tail from last output block
  116. for (size_t i = 0; i < blockSize; i++) {
  117. tmpBlock[i] += outputTail[i];
  118. }
  119. // Copy output block to output
  120. float scale = 1.f / (blockSize*2);
  121. for (size_t i = 0; i < blockSize; i++) {
  122. // Scale based on FFT
  123. output[i] = tmpBlock[i] * scale;
  124. }
  125. // Set tail
  126. for (size_t i = 0; i < blockSize; i++) {
  127. outputTail[i] = tmpBlock[i + blockSize];
  128. }
  129. }
  130. };
  131. } // namespace rack