30template <
typename SampleType>
36template <
typename SampleType>
38 : dryDelayLine (maximumWetLatencyInSamplesIn),
39 maximumWetLatencyInSamples (maximumWetLatencyInSamplesIn)
41 dryDelayLine.setDelay (0);
48template <
typename SampleType>
51 currentMixingRule = newRule;
55template <
typename SampleType>
58 jassert (isPositiveAndNotGreaterThan (newWetMixProportion, 1.0));
60 mix = jlimit (
static_cast<SampleType
> (0.0),
static_cast<SampleType
> (1.0), newWetMixProportion);
64template <
typename SampleType>
67 dryDelayLine.setDelay (wetLatencySamples);
71template <
typename SampleType>
79 dryDelayLine.prepare (spec);
86template <
typename SampleType>
89 dryVolume.reset (sampleRate, 0.05);
90 wetVolume.reset (sampleRate, 0.05);
95 bufferDry.setSize (bufferDry.getNumChannels(), fifo.getSize(),
false,
false,
true);
99template <
typename SampleType>
102 jassert (drySamples.
getNumChannels() <= (
size_t) bufferDry.getNumChannels());
103 jassert (drySamples.
getNumSamples() <= (
size_t) fifo.getRemainingSpace());
107 for (
const auto& range : fifo.write ((
int) drySamples.
getNumSamples()))
109 if (range.getLength() == 0)
113 .
getSubBlock ((
size_t) range.getStart(), (
size_t) range.getLength());
115 auto inputBlock = drySamples.
getSubBlock ((
size_t) offset, (
size_t) range.getLength());
117 if (maximumWetLatencyInSamples == 0)
122 offset += range.getLength();
126template <
typename SampleType>
131 jassert (inOutBlock.
getNumSamples() <= (
size_t) fifo.getNumReadable());
135 for (
const auto& range : fifo.read ((
int) inOutBlock.
getNumSamples()))
137 if (range.getLength() == 0)
141 .
getSubBlock ((
size_t) range.getStart(), (
size_t) range.getLength());
145 offset += range.getLength();
150template <
typename SampleType>
153 SampleType dryValue, wetValue;
155 switch (currentMixingRule)
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);
162 case MixingRule::linear:
163 dryValue =
static_cast<SampleType
> (1.0) - mix;
167 case MixingRule::sin3dB:
172 case MixingRule::sin4p5dB:
177 case MixingRule::sin6dB:
182 case MixingRule::squareRoot3dB:
183 dryValue = std::sqrt (
static_cast<SampleType
> (1.0) - mix);
184 wetValue = std::sqrt (mix);
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));
193 dryValue = jmin (
static_cast<SampleType
> (0.5),
static_cast<SampleType
> (1.0) - mix);
194 wetValue = jmin (
static_cast<SampleType
> (0.5), mix);
198 dryVolume.setTargetValue (dryValue);
199 wetVolume.setTargetValue (wetValue);
203template class DryWetMixer<float>;
204template class DryWetMixer<double>;
211struct DryWetMixerTests final :
public UnitTest
213 DryWetMixerTests() : UnitTest (
"DryWetMixer", UnitTestCategories::dsp) {}
215 enum class Kind { down, up };
217 static auto getRampBuffer (ProcessSpec spec, Kind kind)
219 AudioBuffer<float> buffer ((
int) spec.numChannels, (
int) spec.maximumBlockSize);
221 for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
223 for (uint32_t channel = 0; channel < spec.numChannels; ++channel)
225 const auto ramp = kind == Kind::up ? sample : spec.maximumBlockSize - sample;
227 buffer.setSample ((
int) channel,
229 jmap ((
float) ramp, 0.0f, (
float) spec.maximumBlockSize, 0.0f, 1.0f));
236 void runTest()
override
238 constexpr ProcessSpec spec { 44100.0, 512, 2 };
239 constexpr auto numBlocks = 5;
241 const auto wetBuffer = getRampBuffer (spec, Kind::up);
242 const auto dryBuffer = getRampBuffer (spec, Kind::down);
244 for (
auto maxLatency : { 0, 100, 200, 512 })
246 beginTest (
"Mixer can push multiple small buffers");
248 DryWetMixer<float> mixer (maxLatency);
249 mixer.setWetMixProportion (0.5f);
250 mixer.prepare (spec);
252 for (
auto block = 0; block < numBlocks; ++block)
255 for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
256 mixer.pushDrySamples (AudioBlock<const float> (dryBuffer).getSubBlock (sample, 1));
259 auto outputBlock = wetBuffer;
260 mixer.mixWetSamples ({ outputBlock });
263 for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
265 for (uint32_t channel = 0; channel < spec.numChannels; ++channel)
267 const auto outputValue = outputBlock.getSample ((
int) channel, (
int) sample);
268 expectWithinAbsoluteError (outputValue, 0.5f, 0.0001f);
274 beginTest (
"Mixer can pop multiple small buffers");
276 DryWetMixer<float> mixer (maxLatency);
277 mixer.setWetMixProportion (0.5f);
278 mixer.prepare (spec);
280 for (
auto block = 0; block < numBlocks; ++block)
283 mixer.pushDrySamples ({ dryBuffer });
286 for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
288 AudioBuffer<float> outputBlock ((
int) spec.numChannels, 1);
289 AudioBlock<const float> (wetBuffer).getSubBlock (sample, 1).copyTo (outputBlock);
290 mixer.mixWetSamples ({ outputBlock });
293 for (uint32_t channel = 0; channel < spec.numChannels; ++channel)
295 const auto outputValue = outputBlock.getSample ((
int) channel, 0);
296 expectWithinAbsoluteError (outputValue, 0.5f, 0.0001f);
302 beginTest (
"Mixer can push and pop multiple small buffers");
304 DryWetMixer<float> mixer (maxLatency);
305 mixer.setWetMixProportion (0.5f);
306 mixer.prepare (spec);
308 for (
auto block = 0; block < numBlocks; ++block)
311 for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
313 mixer.pushDrySamples (AudioBlock<const float> (dryBuffer).getSubBlock (sample, 1));
315 AudioBuffer<float> outputBlock ((
int) spec.numChannels, 1);
316 AudioBlock<const float> (wetBuffer).getSubBlock (sample, 1).copyTo (outputBlock);
317 mixer.mixWetSamples ({ outputBlock });
320 for (uint32_t channel = 0; channel < spec.numChannels; ++channel)
322 const auto outputValue = outputBlock.getSample ((
int) channel, 0);
323 expectWithinAbsoluteError (outputValue, 0.5f, 0.0001f);
329 beginTest (
"Mixer can push and pop full-sized blocks after encountering a shorter block");
331 DryWetMixer<float> mixer (maxLatency);
332 mixer.setWetMixProportion (0.5f);
333 mixer.prepare (spec);
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 });
340 for (
auto block = 0; block < numBlocks; ++block)
343 mixer.pushDrySamples ({ dryBuffer });
346 auto outputBlock = wetBuffer;
347 mixer.mixWetSamples ({ outputBlock });
350 for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
352 for (uint32_t channel = 0; channel < spec.numChannels; ++channel)
354 const auto outputValue = outputBlock.getSample ((
int) channel, (
int) sample);
355 expectWithinAbsoluteError (outputValue, 0.5f, 0.0001f);
364static const DryWetMixerTests dryWetMixerTests;
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)