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.

303 lines
9.0KB

  1. #include <string.h>
  2. #include "Befaco.hpp"
  3. #include "dsp.hpp"
  4. #include "pffft.h"
  5. float *springReverbIR;
  6. int springReverbIRLen;
  7. void springReverbInit() {
  8. std::string irFilename = assetPlugin(plugin, "res/SpringReverbIR.pcm");
  9. FILE *f = fopen(irFilename.c_str(), "rb");
  10. assert(f);
  11. fseek(f, 0, SEEK_END);
  12. int size = ftell(f);
  13. fseek(f, 0, SEEK_SET);
  14. springReverbIRLen = size / sizeof(float);
  15. springReverbIR = new float[springReverbIRLen];
  16. fread(springReverbIR, sizeof(float), springReverbIRLen, f);
  17. fclose(f);
  18. // TODO Add springReverbDestroy() function once plugins have destroy() callbacks
  19. }
  20. struct RealTimeConvolver {
  21. // `kernelBlocks` number of contiguous FFT blocks of size `blockSize`
  22. // indexed by [i * blockSize*2 + j]
  23. float *kernelFfts = NULL;
  24. float *inputFfts = NULL;
  25. float *outputTail = NULL;
  26. float *tmpBlock = NULL;
  27. size_t blockSize;
  28. size_t kernelBlocks = 0;
  29. size_t inputPos = 0;
  30. // kiss_fftr_cfg fft_cfg;
  31. // kiss_fftr_cfg ifft_cfg;
  32. PFFFT_Setup *pffft;
  33. /** blocksize should be >=32 and a power of 2 */
  34. RealTimeConvolver(size_t blockSize) {
  35. this->blockSize = blockSize;
  36. pffft = pffft_new_setup(blockSize*2, PFFFT_REAL);
  37. outputTail = new float[blockSize]();
  38. tmpBlock = new float[blockSize*2]();
  39. }
  40. ~RealTimeConvolver() {
  41. clear();
  42. delete[] outputTail;
  43. delete[] tmpBlock;
  44. pffft_destroy_setup(pffft);
  45. }
  46. void clear() {
  47. if (kernelFfts) {
  48. pffft_aligned_free(kernelFfts);
  49. kernelFfts = NULL;
  50. }
  51. if (inputFfts) {
  52. pffft_aligned_free(inputFfts);
  53. inputFfts = NULL;
  54. }
  55. kernelBlocks = 0;
  56. inputPos = 0;
  57. }
  58. void setKernel(const float *kernel, size_t length) {
  59. clear();
  60. if (!kernel || length == 0)
  61. return;
  62. // Round up to the nearest factor blockSize
  63. kernelBlocks = (length - 1) / blockSize + 1;
  64. // Allocate blocks
  65. kernelFfts = (float*) pffft_aligned_malloc(sizeof(float) * blockSize*2 * kernelBlocks);
  66. inputFfts = (float*) pffft_aligned_malloc(sizeof(float) * blockSize*2 * kernelBlocks);
  67. memset(inputFfts, 0, sizeof(float) * blockSize*2 * kernelBlocks);
  68. for (size_t i = 0; i < kernelBlocks; i++) {
  69. // Pad each block with zeros
  70. memset(tmpBlock, 0, sizeof(float) * blockSize*2);
  71. size_t len = mini(blockSize, length - i*blockSize);
  72. memcpy(tmpBlock, &kernel[i*blockSize], sizeof(float)*len);
  73. // Compute fft
  74. pffft_transform(pffft, tmpBlock, &kernelFfts[blockSize*2 * i], NULL, PFFFT_FORWARD);
  75. }
  76. }
  77. /** Applies reverb to input
  78. input and output must be size blockSize
  79. */
  80. void processBlock(const float *input, float *output) {
  81. if (kernelBlocks == 0) {
  82. memset(output, 0, sizeof(float) * blockSize);
  83. return;
  84. }
  85. // Step input position
  86. inputPos = (inputPos + 1) % kernelBlocks;
  87. // Pad block with zeros
  88. memset(tmpBlock, 0, sizeof(float) * blockSize*2);
  89. memcpy(tmpBlock, input, sizeof(float) * blockSize);
  90. // Compute input fft
  91. pffft_transform(pffft, tmpBlock, &inputFfts[blockSize*2 * inputPos], NULL, PFFFT_FORWARD);
  92. // Create output fft
  93. memset(tmpBlock, 0, sizeof(float) * blockSize*2);
  94. // convolve input fft by kernel fft
  95. // Note: This is the CPU bottleneck loop
  96. for (size_t i = 0; i < kernelBlocks; i++) {
  97. size_t pos = (inputPos - i + kernelBlocks) % kernelBlocks;
  98. pffft_zconvolve_accumulate(pffft, &kernelFfts[blockSize*2 * i], &inputFfts[blockSize*2 * pos], tmpBlock, 1.0);
  99. }
  100. // Compute output
  101. pffft_transform(pffft, tmpBlock, tmpBlock, NULL, PFFFT_BACKWARD);
  102. // Add block tail from last output block
  103. for (size_t i = 0; i < blockSize; i++) {
  104. tmpBlock[i] += outputTail[i];
  105. }
  106. // Copy output block to output
  107. for (size_t i = 0; i < blockSize; i++) {
  108. // Scale based on FFT
  109. output[i] = tmpBlock[i] / blockSize;
  110. }
  111. // Set tail
  112. for (size_t i = 0; i < blockSize; i++) {
  113. outputTail[i] = tmpBlock[i + blockSize];
  114. }
  115. }
  116. };
  117. #define BLOCKSIZE 1024
  118. struct SpringReverb : Module {
  119. enum ParamIds {
  120. WET_PARAM,
  121. LEVEL1_PARAM,
  122. LEVEL2_PARAM,
  123. HPF_PARAM,
  124. NUM_PARAMS
  125. };
  126. enum InputIds {
  127. CV1_INPUT,
  128. CV2_INPUT,
  129. IN1_INPUT,
  130. IN2_INPUT,
  131. MIX_CV_INPUT,
  132. NUM_INPUTS
  133. };
  134. enum OutputIds {
  135. MIX_OUTPUT,
  136. WET_OUTPUT,
  137. NUM_OUTPUTS
  138. };
  139. RealTimeConvolver *convolver = NULL;
  140. SampleRateConverter<1> inputSrc;
  141. SampleRateConverter<1> outputSrc;
  142. DoubleRingBuffer<Frame<1>, 16*BLOCKSIZE> inputBuffer;
  143. DoubleRingBuffer<Frame<1>, 16*BLOCKSIZE> outputBuffer;
  144. RCFilter dryFilter;
  145. PeakFilter vuFilter;
  146. PeakFilter lightFilter;
  147. float vuLights[7] = {};
  148. float lights[1] = {};
  149. SpringReverb();
  150. ~SpringReverb();
  151. void step();
  152. };
  153. SpringReverb::SpringReverb() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {
  154. convolver = new RealTimeConvolver(BLOCKSIZE);
  155. convolver->setKernel(springReverbIR, springReverbIRLen);
  156. }
  157. SpringReverb::~SpringReverb() {
  158. delete convolver;
  159. }
  160. void SpringReverb::step() {
  161. float in1 = inputs[IN1_INPUT].value;
  162. float in2 = inputs[IN2_INPUT].value;
  163. const float levelScale = 0.030;
  164. const float levelBase = 25.0;
  165. float level1 = levelScale * exponentialBipolar(levelBase, params[LEVEL1_PARAM].value) * inputs[CV1_INPUT].normalize(10.0) / 10.0;
  166. float level2 = levelScale * exponentialBipolar(levelBase, params[LEVEL2_PARAM].value) * inputs[CV2_INPUT].normalize(10.0) / 10.0;
  167. float dry = in1 * level1 + in2 * level2;
  168. // HPF on dry
  169. float dryCutoff = 200.0 * powf(20.0, params[HPF_PARAM].value) / gSampleRate;
  170. dryFilter.setCutoff(dryCutoff);
  171. dryFilter.process(dry);
  172. // Add dry to input buffer
  173. if (!inputBuffer.full()) {
  174. Frame<1> inputFrame;
  175. inputFrame.samples[0] = dryFilter.highpass();
  176. inputBuffer.push(inputFrame);
  177. }
  178. if (outputBuffer.empty()) {
  179. float input[BLOCKSIZE] = {};
  180. float output[BLOCKSIZE];
  181. // Convert input buffer
  182. {
  183. inputSrc.setRatio(48000.0 / gSampleRate);
  184. int inLen = inputBuffer.size();
  185. int outLen = BLOCKSIZE;
  186. inputSrc.process(inputBuffer.startData(), &inLen, (Frame<1>*) input, &outLen);
  187. inputBuffer.startIncr(inLen);
  188. }
  189. // Convolve block
  190. convolver->processBlock(input, output);
  191. // Convert output buffer
  192. {
  193. outputSrc.setRatio(gSampleRate / 48000.0);
  194. int inLen = BLOCKSIZE;
  195. int outLen = outputBuffer.capacity();
  196. outputSrc.process((Frame<1>*) output, &inLen, outputBuffer.endData(), &outLen);
  197. outputBuffer.endIncr(outLen);
  198. }
  199. }
  200. // Set output
  201. if (outputBuffer.empty())
  202. return;
  203. float wet = outputBuffer.shift().samples[0];
  204. float crossfade = clampf(params[WET_PARAM].value + inputs[MIX_CV_INPUT].value / 10.0, 0.0, 1.0);
  205. float mix = crossf(in1, wet, crossfade);
  206. outputs[WET_OUTPUT].value =clampf(wet, -10.0, 10.0);
  207. outputs[MIX_OUTPUT].value =clampf(mix, -10.0, 10.0);
  208. // Set lights
  209. float lightRate = 5.0 / gSampleRate;
  210. vuFilter.setRate(lightRate);
  211. vuFilter.process(fabsf(wet));
  212. lightFilter.setRate(lightRate);
  213. lightFilter.process(fabsf(dry*50.0));
  214. float vuValue = vuFilter.peak();
  215. for (int i = 0; i < 7; i++) {
  216. float light = powf(1.413, i) * vuValue / 10.0 - 1.0;
  217. vuLights[i] = clampf(light, 0.0, 1.0);
  218. }
  219. lights[0] = lightFilter.peak();
  220. }
  221. SpringReverbWidget::SpringReverbWidget() {
  222. SpringReverb *module = new SpringReverb();
  223. setModule(module);
  224. box.size = Vec(15*8, 380);
  225. {
  226. SVGPanel *panel = new SVGPanel();
  227. panel->box.size = box.size;
  228. panel->setBackground(SVG::load(assetPlugin(plugin, "res/SpringReverb.svg")));
  229. addChild(panel);
  230. }
  231. addChild(createScrew<ScrewBlack>(Vec(15, 0)));
  232. addChild(createScrew<ScrewBlack>(Vec(15, 365)));
  233. addChild(createScrew<ScrewBlack>(Vec(15*6, 0)));
  234. addChild(createScrew<ScrewBlack>(Vec(15*6, 365)));
  235. addParam(createParam<BefacoBigKnob>(Vec(22, 29), module, SpringReverb::WET_PARAM, 0.0, 1.0, 0.5));
  236. addParam(createParam<BefacoSlidePot>(Vec(12, 116), module, SpringReverb::LEVEL1_PARAM, 0.0, 1.0, 0.0));
  237. addParam(createParam<BefacoSlidePot>(Vec(93, 116), module, SpringReverb::LEVEL2_PARAM, 0.0, 1.0, 0.0));
  238. addParam(createParam<Davies1900hWhiteKnob>(Vec(42, 210), module, SpringReverb::HPF_PARAM, 0.0, 1.0, 0.5));
  239. addInput(createInput<PJ3410Port>(Vec(4, 240), module, SpringReverb::CV1_INPUT));
  240. addInput(createInput<PJ3410Port>(Vec(84, 240), module, SpringReverb::CV2_INPUT));
  241. addInput(createInput<PJ3410Port>(Vec(24, 278), module, SpringReverb::IN1_INPUT));
  242. addInput(createInput<PJ3410Port>(Vec(63, 278), module, SpringReverb::IN2_INPUT));
  243. addOutput(createOutput<PJ3410Port>(Vec(4, 314), module, SpringReverb::MIX_OUTPUT));
  244. addInput(createInput<PJ3410Port>(Vec(44, 321), module, SpringReverb::MIX_CV_INPUT));
  245. addOutput(createOutput<PJ3410Port>(Vec(85, 314), module, SpringReverb::WET_OUTPUT));
  246. addChild(createValueLight<SmallLight<RedValueLight>>(Vec(56, 114), &module->vuLights[0]));
  247. addChild(createValueLight<SmallLight<YellowValueLight>>(Vec(56, 127), &module->vuLights[1]));
  248. addChild(createValueLight<SmallLight<YellowValueLight>>(Vec(56, 139), &module->vuLights[2]));
  249. addChild(createValueLight<SmallLight<GreenValueLight>>(Vec(56, 151), &module->vuLights[3]));
  250. addChild(createValueLight<SmallLight<GreenValueLight>>(Vec(56, 164), &module->vuLights[4]));
  251. addChild(createValueLight<SmallLight<GreenValueLight>>(Vec(56, 176), &module->vuLights[5]));
  252. addChild(createValueLight<SmallLight<GreenValueLight>>(Vec(56, 189), &module->vuLights[6]));
  253. addChild(createValueLight<SmallLight<GreenRedPolarityLight>>(Vec(56, 270), &module->lights[0]));
  254. }