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.

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