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.

147 lines
4.1KB

  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. memset(outputTail, 0, blockSize * sizeof(float));
  50. tmpBlock = new float[blockSize*2];
  51. memset(tmpBlock, 0, blockSize*2 * sizeof(float));
  52. }
  53. ~RealTimeConvolver() {
  54. setKernel(NULL, 0);
  55. delete[] outputTail;
  56. delete[] tmpBlock;
  57. pffft_destroy_setup(pffft);
  58. }
  59. void setKernel(const float *kernel, size_t length) {
  60. // Clear existing kernel
  61. if (kernelFfts) {
  62. pffft_aligned_free(kernelFfts);
  63. kernelFfts = NULL;
  64. }
  65. if (inputFfts) {
  66. pffft_aligned_free(inputFfts);
  67. inputFfts = NULL;
  68. }
  69. kernelBlocks = 0;
  70. inputPos = 0;
  71. if (kernel && length > 0) {
  72. // Round up to the nearest factor of `blockSize`
  73. kernelBlocks = (length - 1) / blockSize + 1;
  74. // Allocate blocks
  75. kernelFfts = (float*) pffft_aligned_malloc(sizeof(float) * blockSize*2 * kernelBlocks);
  76. inputFfts = (float*) pffft_aligned_malloc(sizeof(float) * blockSize*2 * kernelBlocks);
  77. memset(inputFfts, 0, sizeof(float) * blockSize*2 * kernelBlocks);
  78. for (size_t i = 0; i < kernelBlocks; i++) {
  79. // Pad each block with zeros
  80. memset(tmpBlock, 0, sizeof(float) * blockSize*2);
  81. size_t len = min((int) blockSize, (int) (length - i*blockSize));
  82. memcpy(tmpBlock, &kernel[i*blockSize], sizeof(float)*len);
  83. // Compute fft
  84. pffft_transform(pffft, tmpBlock, &kernelFfts[blockSize*2 * i], NULL, PFFFT_FORWARD);
  85. }
  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.f);
  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. float scale = 1.f / (blockSize*2);
  119. for (size_t i = 0; i < blockSize; i++) {
  120. // Scale based on FFT
  121. output[i] = tmpBlock[i] * scale;
  122. }
  123. // Set tail
  124. for (size_t i = 0; i < blockSize; i++) {
  125. outputTail[i] = tmpBlock[i + blockSize];
  126. }
  127. }
  128. };
  129. } // namespace rack