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>
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>
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>
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);
187 case MixingRule::squareRoot4p5dB:
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;
Encapsulates the logic for a single-threaded FIFO.
Minimal and lightweight data-structure which contains a list of pointers to channels containing some ...
static void process(AudioBlock< Src1SampleType > inBlock, AudioBlock< Src2SampleType > outBlock, FunctionType &&function)
Applies a function to each value in an input block, putting the result into an output block.
AudioBlock getSubBlock(size_t newOffset, size_t newLength) const noexcept
Return a new AudioBlock pointing to a sub-block inside this block.
AudioBlock getSubsetChannelBlock(size_t channelStart, size_t numChannelsToUse) const noexcept
Returns a subset of contiguous channels.
constexpr size_t getNumChannels() const noexcept
Returns the number of channels referenced by this block.
AudioBlock &JUCE_VECTOR_CALLTYPE multiplyBy(NumericType value) noexcept
Multiplies the elements in this block by a fixed value.
AudioBlock &JUCE_VECTOR_CALLTYPE add(NumericType value) noexcept
Adds a fixed value to the elements in this block.
AudioBlock & copyFrom(const AudioBlock< OtherSampleType > &src) noexcept
Copies the values in src to this block.
constexpr size_t getNumSamples() const noexcept
Returns the number of samples referenced by this block.
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.
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.
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.
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...
uint32 numChannels
The number of channels that the process() method will be expected to handle.
double sampleRate
The sample rate that will be used for the data that is sent to the processor.
uint32 maximumBlockSize
The maximum number of samples that will be in the blocks sent to process() method.