OpenShot Audio Library | OpenShotAudio 0.4.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
26namespace juce::dsp
27{
28
29//==============================================================================
30template <typename SampleType>
32 : DryWetMixer (0)
33{
34}
35
36template <typename SampleType>
37DryWetMixer<SampleType>::DryWetMixer (int maximumWetLatencyInSamplesIn)
38 : dryDelayLine (maximumWetLatencyInSamplesIn),
39 maximumWetLatencyInSamples (maximumWetLatencyInSamplesIn)
40{
41 dryDelayLine.setDelay (0);
42
43 update();
44 reset();
45}
46
47//==============================================================================
48template <typename SampleType>
50{
51 currentMixingRule = newRule;
52 update();
53}
54
55template <typename SampleType>
56void 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
64template <typename SampleType>
65void DryWetMixer<SampleType>::setWetLatency (SampleType wetLatencySamples)
66{
67 dryDelayLine.setDelay (wetLatencySamples);
68}
69
70//==============================================================================
71template <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
86template <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//==============================================================================
99template <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
126template <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//==============================================================================
150template <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//==============================================================================
203template class DryWetMixer<float>;
204template class DryWetMixer<double>;
205
206
207//==============================================================================
208//==============================================================================
209#if JUCE_UNIT_TESTS
210
211struct 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
364static 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 multiplyBy(NumericType value) noexcept
AudioBlock &JUCE_VECTOR_CALLTYPE add(NumericType value) noexcept
AudioBlock & copyFrom(const AudioBlock< OtherSampleType > &src) noexcept
constexpr size_t getNumSamples() const 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)