Audio plugin host https://kx.studio/carla
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.

juce_DryWetMixer.cpp 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - 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 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-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. fifo = SingleThreadedAbstractFifo (nextPowerOfTwo (bufferDry.getNumSamples()));
  75. bufferDry.setSize (bufferDry.getNumChannels(), fifo.getSize(), false, false, true);
  76. }
  77. //==============================================================================
  78. template <typename SampleType>
  79. void DryWetMixer<SampleType>::pushDrySamples (const AudioBlock<const SampleType> drySamples)
  80. {
  81. jassert (drySamples.getNumChannels() <= (size_t) bufferDry.getNumChannels());
  82. jassert (drySamples.getNumSamples() <= (size_t) fifo.getRemainingSpace());
  83. auto offset = 0;
  84. for (const auto& range : fifo.write ((int) drySamples.getNumSamples()))
  85. {
  86. if (range.getLength() == 0)
  87. continue;
  88. auto block = AudioBlock<SampleType> (bufferDry).getSubsetChannelBlock (0, drySamples.getNumChannels())
  89. .getSubBlock ((size_t) range.getStart(), (size_t) range.getLength());
  90. auto inputBlock = drySamples.getSubBlock ((size_t) offset, (size_t) range.getLength());
  91. if (maximumWetLatencyInSamples == 0)
  92. block.copyFrom (inputBlock);
  93. else
  94. dryDelayLine.process (ProcessContextNonReplacing<SampleType> (inputBlock, block));
  95. offset += range.getLength();
  96. }
  97. }
  98. template <typename SampleType>
  99. void DryWetMixer<SampleType>::mixWetSamples (AudioBlock<SampleType> inOutBlock)
  100. {
  101. inOutBlock.multiplyBy (wetVolume);
  102. jassert (inOutBlock.getNumSamples() <= (size_t) fifo.getNumReadable());
  103. auto offset = 0;
  104. for (const auto& range : fifo.read ((int) inOutBlock.getNumSamples()))
  105. {
  106. if (range.getLength() == 0)
  107. continue;
  108. auto block = AudioBlock<SampleType> (bufferDry).getSubsetChannelBlock (0, inOutBlock.getNumChannels())
  109. .getSubBlock ((size_t) range.getStart(), (size_t) range.getLength());
  110. block.multiplyBy (dryVolume);
  111. inOutBlock.getSubBlock ((size_t) offset).add (block);
  112. offset += range.getLength();
  113. }
  114. }
  115. //==============================================================================
  116. template <typename SampleType>
  117. void DryWetMixer<SampleType>::update()
  118. {
  119. SampleType dryValue, wetValue;
  120. switch (currentMixingRule)
  121. {
  122. case MixingRule::balanced:
  123. dryValue = static_cast<SampleType> (2.0) * jmin (static_cast<SampleType> (0.5), static_cast<SampleType> (1.0) - mix);
  124. wetValue = static_cast<SampleType> (2.0) * jmin (static_cast<SampleType> (0.5), mix);
  125. break;
  126. case MixingRule::linear:
  127. dryValue = static_cast<SampleType> (1.0) - mix;
  128. wetValue = mix;
  129. break;
  130. case MixingRule::sin3dB:
  131. dryValue = static_cast<SampleType> (std::sin (0.5 * MathConstants<double>::pi * (1.0 - mix)));
  132. wetValue = static_cast<SampleType> (std::sin (0.5 * MathConstants<double>::pi * mix));
  133. break;
  134. case MixingRule::sin4p5dB:
  135. dryValue = static_cast<SampleType> (std::pow (std::sin (0.5 * MathConstants<double>::pi * (1.0 - mix)), 1.5));
  136. wetValue = static_cast<SampleType> (std::pow (std::sin (0.5 * MathConstants<double>::pi * mix), 1.5));
  137. break;
  138. case MixingRule::sin6dB:
  139. dryValue = static_cast<SampleType> (std::pow (std::sin (0.5 * MathConstants<double>::pi * (1.0 - mix)), 2.0));
  140. wetValue = static_cast<SampleType> (std::pow (std::sin (0.5 * MathConstants<double>::pi * mix), 2.0));
  141. break;
  142. case MixingRule::squareRoot3dB:
  143. dryValue = std::sqrt (static_cast<SampleType> (1.0) - mix);
  144. wetValue = std::sqrt (mix);
  145. break;
  146. case MixingRule::squareRoot4p5dB:
  147. dryValue = static_cast<SampleType> (std::pow (std::sqrt (1.0 - mix), 1.5));
  148. wetValue = static_cast<SampleType> (std::pow (std::sqrt (mix), 1.5));
  149. break;
  150. default:
  151. dryValue = jmin (static_cast<SampleType> (0.5), static_cast<SampleType> (1.0) - mix);
  152. wetValue = jmin (static_cast<SampleType> (0.5), mix);
  153. break;
  154. }
  155. dryVolume.setTargetValue (dryValue);
  156. wetVolume.setTargetValue (wetValue);
  157. }
  158. //==============================================================================
  159. template class DryWetMixer<float>;
  160. template class DryWetMixer<double>;
  161. //==============================================================================
  162. //==============================================================================
  163. #if JUCE_UNIT_TESTS
  164. struct DryWetMixerTests : public UnitTest
  165. {
  166. DryWetMixerTests() : UnitTest ("DryWetMixer", UnitTestCategories::dsp) {}
  167. enum class Kind { down, up };
  168. static auto getRampBuffer (ProcessSpec spec, Kind kind)
  169. {
  170. AudioBuffer<float> buffer ((int) spec.numChannels, (int) spec.maximumBlockSize);
  171. for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
  172. {
  173. for (uint32_t channel = 0; channel < spec.numChannels; ++channel)
  174. {
  175. const auto ramp = kind == Kind::up ? sample : spec.maximumBlockSize - sample;
  176. buffer.setSample ((int) channel,
  177. (int) sample,
  178. jmap ((float) ramp, 0.0f, (float) spec.maximumBlockSize, 0.0f, 1.0f));
  179. }
  180. }
  181. return buffer;
  182. }
  183. void runTest() override
  184. {
  185. constexpr ProcessSpec spec { 44100.0, 512, 2 };
  186. constexpr auto numBlocks = 5;
  187. const auto wetBuffer = getRampBuffer (spec, Kind::up);
  188. const auto dryBuffer = getRampBuffer (spec, Kind::down);
  189. for (auto maxLatency : { 0, 100, 200, 512 })
  190. {
  191. beginTest ("Mixer can push multiple small buffers");
  192. {
  193. DryWetMixer<float> mixer (maxLatency);
  194. mixer.setWetMixProportion (0.5f);
  195. mixer.prepare (spec);
  196. for (auto block = 0; block < numBlocks; ++block)
  197. {
  198. // Push samples one-by-one
  199. for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
  200. mixer.pushDrySamples (AudioBlock<const float> (dryBuffer).getSubBlock (sample, 1));
  201. // Mix wet samples in one go
  202. auto outputBlock = wetBuffer;
  203. mixer.mixWetSamples ({ outputBlock });
  204. // The output block should contain the wet and dry samples averaged
  205. for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
  206. {
  207. for (uint32_t channel = 0; channel < spec.numChannels; ++channel)
  208. {
  209. const auto outputValue = outputBlock.getSample ((int) channel, (int) sample);
  210. expectWithinAbsoluteError (outputValue, 0.5f, 0.0001f);
  211. }
  212. }
  213. }
  214. }
  215. beginTest ("Mixer can pop 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 in one go
  223. mixer.pushDrySamples ({ dryBuffer });
  224. // Process wet samples one-by-one
  225. for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
  226. {
  227. AudioBuffer<float> outputBlock ((int) spec.numChannels, 1);
  228. AudioBlock<const float> (wetBuffer).getSubBlock (sample, 1).copyTo (outputBlock);
  229. mixer.mixWetSamples ({ outputBlock });
  230. // The output block should contain the wet and dry samples averaged
  231. for (uint32_t channel = 0; channel < spec.numChannels; ++channel)
  232. {
  233. const auto outputValue = outputBlock.getSample ((int) channel, 0);
  234. expectWithinAbsoluteError (outputValue, 0.5f, 0.0001f);
  235. }
  236. }
  237. }
  238. }
  239. beginTest ("Mixer can push and 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 dry samples and process wet samples one-by-one
  247. for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
  248. {
  249. mixer.pushDrySamples (AudioBlock<const float> (dryBuffer).getSubBlock (sample, 1));
  250. AudioBuffer<float> outputBlock ((int) spec.numChannels, 1);
  251. AudioBlock<const float> (wetBuffer).getSubBlock (sample, 1).copyTo (outputBlock);
  252. mixer.mixWetSamples ({ outputBlock });
  253. // The output block should contain the wet and dry samples averaged
  254. for (uint32_t channel = 0; channel < spec.numChannels; ++channel)
  255. {
  256. const auto outputValue = outputBlock.getSample ((int) channel, 0);
  257. expectWithinAbsoluteError (outputValue, 0.5f, 0.0001f);
  258. }
  259. }
  260. }
  261. }
  262. beginTest ("Mixer can push and pop full-sized blocks after encountering a shorter block");
  263. {
  264. DryWetMixer<float> mixer (maxLatency);
  265. mixer.setWetMixProportion (0.5f);
  266. mixer.prepare (spec);
  267. constexpr auto shortBlockLength = spec.maximumBlockSize / 2;
  268. AudioBuffer<float> shortBlock (spec.numChannels, shortBlockLength);
  269. mixer.pushDrySamples (AudioBlock<const float> (dryBuffer).getSubBlock (shortBlockLength));
  270. mixer.mixWetSamples ({ shortBlock });
  271. for (auto block = 0; block < numBlocks; ++block)
  272. {
  273. // Push a full block of dry samples
  274. mixer.pushDrySamples ({ dryBuffer });
  275. // Mix a full block of wet samples
  276. auto outputBlock = wetBuffer;
  277. mixer.mixWetSamples ({ outputBlock });
  278. // The output block should contain the wet and dry samples averaged
  279. for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
  280. {
  281. for (uint32_t channel = 0; channel < spec.numChannels; ++channel)
  282. {
  283. const auto outputValue = outputBlock.getSample ((int) channel, (int) sample);
  284. expectWithinAbsoluteError (outputValue, 0.5f, 0.0001f);
  285. }
  286. }
  287. }
  288. }
  289. }
  290. }
  291. };
  292. static const DryWetMixerTests dryWetMixerTests;
  293. #endif
  294. } // namespace dsp
  295. } // namespace juce