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