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.

165 lines
5.0KB

  1. /*
  2. * DISTRHO Cardinal Plugin
  3. * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com>
  4. *
  5. * This program is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU General Public License as
  7. * published by the Free Software Foundation; either version 3 of
  8. * the License, or any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * For a full copy of the GNU General Public License see the LICENSE file.
  16. */
  17. /**
  18. * This file is an edited version of VCVRack's dsp/fir.hpp
  19. * Copyright (C) 2016-2021 VCV.
  20. *
  21. * This program is free software: you can redistribute it and/or
  22. * modify it under the terms of the GNU General Public License as
  23. * published by the Free Software Foundation; either version 3 of
  24. * the License, or (at your option) any later version.
  25. */
  26. #pragma once
  27. #include <pffft.h>
  28. #include <dsp/common.hpp>
  29. namespace rack {
  30. namespace dsp {
  31. /** Performs a direct sum convolution */
  32. inline float convolveNaive(const float* in, const float* kernel, int len) {
  33. float y = 0.f;
  34. for (int i = 0; i < len; i++) {
  35. y += in[len - 1 - i] * kernel[i];
  36. }
  37. return y;
  38. }
  39. /** Computes the impulse response of a boxcar lowpass filter */
  40. inline void boxcarLowpassIR(float* out, int len, float cutoff = 0.5f) {
  41. for (int i = 0; i < len; i++) {
  42. float t = i - (len - 1) / 2.f;
  43. out[i] = 2 * cutoff * sinc(2 * cutoff * t);
  44. }
  45. }
  46. struct RealTimeConvolver {
  47. // `kernelBlocks` number of contiguous FFT blocks of size `blockSize`
  48. // indexed by [i * blockSize*2 + j]
  49. float* kernelFfts = NULL;
  50. float* inputFfts = NULL;
  51. float* outputTail = NULL;
  52. float* tmpBlock = NULL;
  53. size_t blockSize;
  54. size_t kernelBlocks = 0;
  55. size_t inputPos = 0;
  56. PFFFT_Setup* pffft;
  57. /** `blockSize` is the size of each FFT block. It should be >=32 and a power of 2. */
  58. RealTimeConvolver(size_t blockSize) {
  59. this->blockSize = blockSize;
  60. pffft = pffft_new_setup(blockSize * 2, PFFFT_REAL);
  61. outputTail = (float*) pffft_aligned_malloc(sizeof(float) * blockSize);
  62. std::memset(outputTail, 0, blockSize * sizeof(float));
  63. tmpBlock = (float*) pffft_aligned_malloc(sizeof(float) * blockSize * 2);
  64. std::memset(tmpBlock, 0, blockSize * 2 * sizeof(float));
  65. }
  66. ~RealTimeConvolver() {
  67. setKernel(NULL, 0);
  68. pffft_aligned_free(outputTail);
  69. pffft_aligned_free(tmpBlock);
  70. pffft_destroy_setup(pffft);
  71. }
  72. void setKernel(const float* kernel, size_t length) {
  73. // Clear existing kernel
  74. if (kernelFfts) {
  75. pffft_aligned_free(kernelFfts);
  76. kernelFfts = NULL;
  77. }
  78. if (inputFfts) {
  79. pffft_aligned_free(inputFfts);
  80. inputFfts = NULL;
  81. }
  82. kernelBlocks = 0;
  83. inputPos = 0;
  84. if (kernel && length > 0) {
  85. // Round up to the nearest factor of `blockSize`
  86. kernelBlocks = (length - 1) / blockSize + 1;
  87. // Allocate blocks
  88. kernelFfts = (float*) pffft_aligned_malloc(sizeof(float) * blockSize * 2 * kernelBlocks);
  89. inputFfts = (float*) pffft_aligned_malloc(sizeof(float) * blockSize * 2 * kernelBlocks);
  90. std::memset(inputFfts, 0, sizeof(float) * blockSize * 2 * kernelBlocks);
  91. for (size_t i = 0; i < kernelBlocks; i++) {
  92. // Pad each block with zeros
  93. std::memset(tmpBlock, 0, sizeof(float) * blockSize * 2);
  94. size_t len = std::min((int) blockSize, (int)(length - i * blockSize));
  95. std::memcpy(tmpBlock, &kernel[i * blockSize], sizeof(float)*len);
  96. // Compute fft
  97. pffft_transform(pffft, tmpBlock, &kernelFfts[blockSize * 2 * i], NULL, PFFFT_FORWARD);
  98. }
  99. }
  100. }
  101. /** Applies reverb to input
  102. input and output must be of size `blockSize`
  103. */
  104. void processBlock(const float* input, float* output) {
  105. if (kernelBlocks == 0) {
  106. std::memset(output, 0, sizeof(float) * blockSize);
  107. return;
  108. }
  109. // Step input position
  110. inputPos = (inputPos + 1) % kernelBlocks;
  111. // Pad block with zeros
  112. std::memset(tmpBlock, 0, sizeof(float) * blockSize * 2);
  113. std::memcpy(tmpBlock, input, sizeof(float) * blockSize);
  114. // Compute input fft
  115. pffft_transform(pffft, tmpBlock, &inputFfts[blockSize * 2 * inputPos], NULL, PFFFT_FORWARD);
  116. // Create output fft
  117. std::memset(tmpBlock, 0, sizeof(float) * blockSize * 2);
  118. // convolve input fft by kernel fft
  119. // Note: This is the CPU bottleneck loop
  120. for (size_t i = 0; i < kernelBlocks; i++) {
  121. size_t pos = (inputPos - i + kernelBlocks) % kernelBlocks;
  122. pffft_zconvolve_accumulate(pffft, &kernelFfts[blockSize * 2 * i], &inputFfts[blockSize * 2 * pos], tmpBlock, 1.f);
  123. }
  124. // Compute output
  125. pffft_transform(pffft, tmpBlock, tmpBlock, NULL, PFFFT_BACKWARD);
  126. // Add block tail from last output block
  127. for (size_t i = 0; i < blockSize; i++) {
  128. tmpBlock[i] += outputTail[i];
  129. }
  130. // Copy output block to output
  131. float scale = 1.f / (blockSize * 2);
  132. for (size_t i = 0; i < blockSize; i++) {
  133. // Scale based on FFT
  134. output[i] = tmpBlock[i] * scale;
  135. }
  136. // Set tail
  137. for (size_t i = 0; i < blockSize; i++) {
  138. outputTail[i] = tmpBlock[i + blockSize];
  139. }
  140. }
  141. };
  142. } // namespace dsp
  143. } // namespace rack