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.

148 lines
3.9KB

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