JUCE-7.0.12-0-g4f43011b96 JUCE-7.0.12-0-g4f43011b96
JUCE — C++ application framework with suport for VST, VST3, LV2 audio plug-ins

« « « Anklang Documentation
Loading...
Searching...
No Matches
juce_DryWetMixer.cpp
Go to the documentation of this file.
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>
35
36template <typename SampleType>
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>
57{
59
60 mix = jlimit (static_cast<SampleType> (0.0), static_cast<SampleType> (1.0), newWetMixProportion);
61 update();
62}
63
64template <typename SampleType>
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 {
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 {
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 {
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 {
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
365
366#endif
367
368} // namespace juce::dsp
Encapsulates the logic for a single-threaded FIFO.
Minimal and lightweight data-structure which contains a list of pointers to channels containing some ...
A processor to handle dry/wet mixing of two audio signals, where the wet signal may have additional l...
void pushDrySamples(const AudioBlock< const SampleType > drySamples)
Copies the dry path samples into an internal delay line.
void setWetMixProportion(SampleType newWetMixProportion)
Sets the current dry/wet mix proportion, with 0.0 being full dry and 1.0 being fully wet.
void reset()
Resets the internal state variables of the processor.
void setMixingRule(MixingRule newRule)
Sets the mix rule.
void setWetLatency(SampleType wetLatencyInSamples)
Sets the relative latency of the wet signal path compared to the dry signal path, and thus the amount...
void prepare(const ProcessSpec &spec)
Initialises the processor.
void mixWetSamples(AudioBlock< SampleType > wetSamples)
Mixes the supplied wet samples with the latency-compensated dry samples from pushDrySamples.
DryWetMixer()
Default constructor.
#define jassert(expression)
Platform-independent assertion macro.
constexpr Type jmap(Type value0To1, Type targetRangeMin, Type targetRangeMax)
Remaps a normalised value (between 0 and 1) to a target range.
constexpr Type jmin(Type a, Type b)
Returns the smaller of two values.
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
Constrains a value to keep it within a given range.
int nextPowerOfTwo(int n) noexcept
Returns the smallest power-of-two which is equal to or greater than the given integer.
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
Definition juce_Memory.h:88
bool isPositiveAndNotGreaterThan(Type1 valueToTest, Type2 upperLimit) noexcept
Returns true if a value is at least zero, and also less than or equal to a specified upper limit.
T pow(T... args)
T sample(T... args)
T sin(T... args)
T sqrt(T... args)
Commonly used mathematical constants.
Contains context information that is passed into an algorithm's process method.
This structure is passed into a DSP algorithm's prepare() method, and contains information about vari...