The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

400 lines
16KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. namespace dsp
  21. {
  22. //==============================================================================
  23. template <typename SampleType>
  24. DryWetMixer<SampleType>::DryWetMixer()
  25. : DryWetMixer (0)
  26. {
  27. }
  28. template <typename SampleType>
  29. DryWetMixer<SampleType>::DryWetMixer (int maximumWetLatencyInSamplesIn)
  30. : dryDelayLine (maximumWetLatencyInSamplesIn),
  31. maximumWetLatencyInSamples (maximumWetLatencyInSamplesIn)
  32. {
  33. dryDelayLine.setDelay (0);
  34. update();
  35. reset();
  36. }
  37. //==============================================================================
  38. template <typename SampleType>
  39. void DryWetMixer<SampleType>::setMixingRule (MixingRule newRule)
  40. {
  41. currentMixingRule = newRule;
  42. update();
  43. }
  44. template <typename SampleType>
  45. void DryWetMixer<SampleType>::setWetMixProportion (SampleType newWetMixProportion)
  46. {
  47. jassert (isPositiveAndNotGreaterThan (newWetMixProportion, 1.0));
  48. mix = jlimit (static_cast<SampleType> (0.0), static_cast<SampleType> (1.0), newWetMixProportion);
  49. update();
  50. }
  51. template <typename SampleType>
  52. void DryWetMixer<SampleType>::setWetLatency (SampleType wetLatencySamples)
  53. {
  54. dryDelayLine.setDelay (wetLatencySamples);
  55. }
  56. //==============================================================================
  57. template <typename SampleType>
  58. void DryWetMixer<SampleType>::prepare (const ProcessSpec& spec)
  59. {
  60. jassert (spec.sampleRate > 0);
  61. jassert (spec.numChannels > 0);
  62. sampleRate = spec.sampleRate;
  63. dryDelayLine.prepare (spec);
  64. bufferDry.setSize ((int) spec.numChannels, (int) spec.maximumBlockSize, false, false, true);
  65. update();
  66. reset();
  67. }
  68. template <typename SampleType>
  69. void DryWetMixer<SampleType>::reset()
  70. {
  71. dryVolume.reset (sampleRate, 0.05);
  72. wetVolume.reset (sampleRate, 0.05);
  73. dryDelayLine.reset();
  74. offsetInBuffer = 0;
  75. numUsedSamples = 0;
  76. }
  77. template <typename SampleType>
  78. struct FirstAndSecondPartBlocks
  79. {
  80. AudioBlock<SampleType> first, second;
  81. };
  82. template <typename SampleType>
  83. static FirstAndSecondPartBlocks<SampleType> getFirstAndSecondPartBlocks (AudioBuffer<SampleType>& bufferDry,
  84. size_t firstPartStart,
  85. size_t channelsToCopy,
  86. size_t samplesToCopy)
  87. {
  88. const auto actualChannelsToCopy = jmin (channelsToCopy, (size_t) bufferDry.getNumChannels());
  89. const auto firstPartLength = jmin ((size_t) bufferDry.getNumSamples() - firstPartStart, samplesToCopy);
  90. const auto secondPartLength = samplesToCopy - firstPartLength;
  91. const auto channelBlock = AudioBlock<SampleType> (bufferDry).getSubsetChannelBlock (0, actualChannelsToCopy);
  92. return { channelBlock.getSubBlock (firstPartStart, firstPartLength),
  93. secondPartLength != 0 ? channelBlock.getSubBlock (0, samplesToCopy - firstPartLength) : AudioBlock<SampleType>() };
  94. }
  95. //==============================================================================
  96. template <typename SampleType>
  97. void DryWetMixer<SampleType>::pushDrySamples (const AudioBlock<const SampleType> drySamples)
  98. {
  99. const auto remainingSpace = (size_t) bufferDry.getNumSamples() - numUsedSamples;
  100. jassert (drySamples.getNumChannels() <= (size_t) bufferDry.getNumChannels());
  101. jassert (drySamples.getNumSamples() <= remainingSpace);
  102. auto blocks = getFirstAndSecondPartBlocks (bufferDry,
  103. (offsetInBuffer + numUsedSamples) % (size_t) bufferDry.getNumSamples(),
  104. drySamples.getNumChannels(),
  105. jmin (drySamples.getNumSamples(), remainingSpace));
  106. const auto processSubBlock = [this, &drySamples] (AudioBlock<SampleType> block, size_t startOffset)
  107. {
  108. auto inputBlock = drySamples.getSubBlock (startOffset, block.getNumSamples());
  109. if (maximumWetLatencyInSamples == 0)
  110. block.copyFrom (inputBlock);
  111. else
  112. dryDelayLine.process (ProcessContextNonReplacing<SampleType> (inputBlock, block));
  113. };
  114. processSubBlock (blocks.first, 0);
  115. if (blocks.second.getNumSamples() > 0)
  116. processSubBlock (blocks.second, blocks.first.getNumSamples());
  117. numUsedSamples += blocks.first.getNumSamples() + blocks.second.getNumSamples();
  118. }
  119. template <typename SampleType>
  120. void DryWetMixer<SampleType>::mixWetSamples (AudioBlock<SampleType> inOutBlock)
  121. {
  122. inOutBlock.multiplyBy (wetVolume);
  123. jassert (inOutBlock.getNumSamples() <= numUsedSamples);
  124. auto blocks = getFirstAndSecondPartBlocks (bufferDry,
  125. offsetInBuffer,
  126. inOutBlock.getNumChannels(),
  127. jmin (inOutBlock.getNumSamples(), numUsedSamples));
  128. blocks.first.multiplyBy (dryVolume);
  129. inOutBlock.add (blocks.first);
  130. if (blocks.second.getNumSamples() != 0)
  131. {
  132. blocks.second.multiplyBy (dryVolume);
  133. inOutBlock.getSubBlock (blocks.first.getNumSamples()).add (blocks.second);
  134. }
  135. const auto samplesToCopy = blocks.first.getNumSamples() + blocks.second.getNumSamples();
  136. offsetInBuffer = (offsetInBuffer + samplesToCopy) % (size_t) bufferDry.getNumSamples();
  137. numUsedSamples -= samplesToCopy;
  138. }
  139. //==============================================================================
  140. template <typename SampleType>
  141. void DryWetMixer<SampleType>::update()
  142. {
  143. SampleType dryValue, wetValue;
  144. switch (currentMixingRule)
  145. {
  146. case MixingRule::balanced:
  147. dryValue = static_cast<SampleType> (2.0) * jmin (static_cast<SampleType> (0.5), static_cast<SampleType> (1.0) - mix);
  148. wetValue = static_cast<SampleType> (2.0) * jmin (static_cast<SampleType> (0.5), mix);
  149. break;
  150. case MixingRule::linear:
  151. dryValue = static_cast<SampleType> (1.0) - mix;
  152. wetValue = mix;
  153. break;
  154. case MixingRule::sin3dB:
  155. dryValue = static_cast<SampleType> (std::sin (0.5 * MathConstants<double>::pi * (1.0 - mix)));
  156. wetValue = static_cast<SampleType> (std::sin (0.5 * MathConstants<double>::pi * mix));
  157. break;
  158. case MixingRule::sin4p5dB:
  159. dryValue = static_cast<SampleType> (std::pow (std::sin (0.5 * MathConstants<double>::pi * (1.0 - mix)), 1.5));
  160. wetValue = static_cast<SampleType> (std::pow (std::sin (0.5 * MathConstants<double>::pi * mix), 1.5));
  161. break;
  162. case MixingRule::sin6dB:
  163. dryValue = static_cast<SampleType> (std::pow (std::sin (0.5 * MathConstants<double>::pi * (1.0 - mix)), 2.0));
  164. wetValue = static_cast<SampleType> (std::pow (std::sin (0.5 * MathConstants<double>::pi * mix), 2.0));
  165. break;
  166. case MixingRule::squareRoot3dB:
  167. dryValue = std::sqrt (static_cast<SampleType> (1.0) - mix);
  168. wetValue = std::sqrt (mix);
  169. break;
  170. case MixingRule::squareRoot4p5dB:
  171. dryValue = static_cast<SampleType> (std::pow (std::sqrt (1.0 - mix), 1.5));
  172. wetValue = static_cast<SampleType> (std::pow (std::sqrt (mix), 1.5));
  173. break;
  174. default:
  175. dryValue = jmin (static_cast<SampleType> (0.5), static_cast<SampleType> (1.0) - mix);
  176. wetValue = jmin (static_cast<SampleType> (0.5), mix);
  177. break;
  178. }
  179. dryVolume.setTargetValue (dryValue);
  180. wetVolume.setTargetValue (wetValue);
  181. }
  182. //==============================================================================
  183. template class DryWetMixer<float>;
  184. template class DryWetMixer<double>;
  185. //==============================================================================
  186. //==============================================================================
  187. #if JUCE_UNIT_TESTS
  188. struct DryWetMixerTests : public UnitTest
  189. {
  190. DryWetMixerTests() : UnitTest ("DryWetMixer", UnitTestCategories::dsp) {}
  191. enum class Kind { down, up };
  192. static auto getRampBuffer (ProcessSpec spec, Kind kind)
  193. {
  194. AudioBuffer<float> buffer ((int) spec.numChannels, (int) spec.maximumBlockSize);
  195. for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
  196. {
  197. for (uint32_t channel = 0; channel < spec.numChannels; ++channel)
  198. {
  199. const auto ramp = kind == Kind::up ? sample : spec.maximumBlockSize - sample;
  200. buffer.setSample ((int) channel,
  201. (int) sample,
  202. jmap ((float) ramp, 0.0f, (float) spec.maximumBlockSize, 0.0f, 1.0f));
  203. }
  204. }
  205. return buffer;
  206. }
  207. void runTest() override
  208. {
  209. constexpr ProcessSpec spec { 44100.0, 512, 2 };
  210. constexpr auto numBlocks = 5;
  211. const auto wetBuffer = getRampBuffer (spec, Kind::up);
  212. const auto dryBuffer = getRampBuffer (spec, Kind::down);
  213. for (auto maxLatency : { 0, 512 })
  214. {
  215. beginTest ("Mixer can push multiple small buffers");
  216. {
  217. DryWetMixer<float> mixer (maxLatency);
  218. mixer.setWetMixProportion (0.5f);
  219. mixer.prepare (spec);
  220. for (auto block = 0; block < numBlocks; ++block)
  221. {
  222. // Push samples one-by-one
  223. for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
  224. mixer.pushDrySamples (AudioBlock<const float> (dryBuffer).getSubBlock (sample, 1));
  225. // Mix wet samples in one go
  226. auto outputBlock = wetBuffer;
  227. mixer.mixWetSamples ({ outputBlock });
  228. // The output block should contain the wet and dry samples averaged
  229. for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
  230. {
  231. for (uint32_t channel = 0; channel < spec.numChannels; ++channel)
  232. {
  233. const auto outputValue = outputBlock.getSample ((int) channel, (int) sample);
  234. expectWithinAbsoluteError (outputValue, 0.5f, 0.0001f);
  235. }
  236. }
  237. }
  238. }
  239. beginTest ("Mixer can pop multiple small buffers");
  240. {
  241. DryWetMixer<float> mixer (maxLatency);
  242. mixer.setWetMixProportion (0.5f);
  243. mixer.prepare (spec);
  244. for (auto block = 0; block < numBlocks; ++block)
  245. {
  246. // Push samples in one go
  247. mixer.pushDrySamples ({ dryBuffer });
  248. // Process wet samples one-by-one
  249. for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
  250. {
  251. AudioBuffer<float> outputBlock ((int) spec.numChannels, 1);
  252. AudioBlock<const float> (wetBuffer).getSubBlock (sample, 1).copyTo (outputBlock);
  253. mixer.mixWetSamples ({ outputBlock });
  254. // The output block should contain the wet and dry samples averaged
  255. for (uint32_t channel = 0; channel < spec.numChannels; ++channel)
  256. {
  257. const auto outputValue = outputBlock.getSample ((int) channel, 0);
  258. expectWithinAbsoluteError (outputValue, 0.5f, 0.0001f);
  259. }
  260. }
  261. }
  262. }
  263. beginTest ("Mixer can push and pop multiple small buffers");
  264. {
  265. DryWetMixer<float> mixer (maxLatency);
  266. mixer.setWetMixProportion (0.5f);
  267. mixer.prepare (spec);
  268. for (auto block = 0; block < numBlocks; ++block)
  269. {
  270. // Push dry samples and process wet samples one-by-one
  271. for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
  272. {
  273. mixer.pushDrySamples (AudioBlock<const float> (dryBuffer).getSubBlock (sample, 1));
  274. AudioBuffer<float> outputBlock ((int) spec.numChannels, 1);
  275. AudioBlock<const float> (wetBuffer).getSubBlock (sample, 1).copyTo (outputBlock);
  276. mixer.mixWetSamples ({ outputBlock });
  277. // The output block should contain the wet and dry samples averaged
  278. for (uint32_t channel = 0; channel < spec.numChannels; ++channel)
  279. {
  280. const auto outputValue = outputBlock.getSample ((int) channel, 0);
  281. expectWithinAbsoluteError (outputValue, 0.5f, 0.0001f);
  282. }
  283. }
  284. }
  285. }
  286. beginTest ("Mixer can push and pop full-sized blocks after encountering a shorter block");
  287. {
  288. DryWetMixer<float> mixer (maxLatency);
  289. mixer.setWetMixProportion (0.5f);
  290. mixer.prepare (spec);
  291. constexpr auto shortBlockLength = spec.maximumBlockSize / 2;
  292. AudioBuffer<float> shortBlock (spec.numChannels, shortBlockLength);
  293. mixer.pushDrySamples (AudioBlock<const float> (dryBuffer).getSubBlock (shortBlockLength));
  294. mixer.mixWetSamples ({ shortBlock });
  295. for (auto block = 0; block < numBlocks; ++block)
  296. {
  297. // Push a full block of dry samples
  298. mixer.pushDrySamples ({ dryBuffer });
  299. // Mix a full block of wet samples
  300. auto outputBlock = wetBuffer;
  301. mixer.mixWetSamples ({ outputBlock });
  302. // The output block should contain the wet and dry samples averaged
  303. for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
  304. {
  305. for (uint32_t channel = 0; channel < spec.numChannels; ++channel)
  306. {
  307. const auto outputValue = outputBlock.getSample ((int) channel, (int) sample);
  308. expectWithinAbsoluteError (outputValue, 0.5f, 0.0001f);
  309. }
  310. }
  311. }
  312. }
  313. }
  314. }
  315. };
  316. static const DryWetMixerTests dryWetMixerTests;
  317. #endif
  318. } // namespace dsp
  319. } // namespace juce