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.

310 lines
9.7KB

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