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_Convolution.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
29template <typename Element>
30class Queue
31{
32public:
33 explicit Queue (int size)
34 : fifo (size), storage (static_cast<size_t> (size)) {}
35
36 bool push (Element& element) noexcept
37 {
38 if (fifo.getFreeSpace() == 0)
39 return false;
40
41 const auto writer = fifo.write (1);
42
43 if (writer.blockSize1 != 0)
44 storage[static_cast<size_t> (writer.startIndex1)] = std::move (element);
45 else if (writer.blockSize2 != 0)
46 storage[static_cast<size_t> (writer.startIndex2)] = std::move (element);
47
48 return true;
49 }
50
51 template <typename Fn>
52 void pop (Fn&& fn) { popN (1, std::forward<Fn> (fn)); }
53
54 template <typename Fn>
55 void popAll (Fn&& fn) { popN (fifo.getNumReady(), std::forward<Fn> (fn)); }
56
57 bool hasPendingMessages() const noexcept { return fifo.getNumReady() > 0; }
58
59private:
60 template <typename Fn>
61 void popN (int n, Fn&& fn)
62 {
63 fifo.read (n).forEach ([&] (int index)
64 {
65 fn (storage[static_cast<size_t> (index)]);
66 });
67 }
68
69 AbstractFifo fifo;
71};
72
74{
75public:
76 explicit BackgroundMessageQueue (int entries)
77 : Thread ("Convolution background loader"), queue (entries)
78 {}
79
80 using IncomingCommand = FixedSizeFunction<400, void()>;
81
82 // Push functions here, and they'll be called later on a background thread.
83 // This function is wait-free.
84 // This function is only safe to call from a single thread at a time.
85 bool push (IncomingCommand& command) { return queue.push (command); }
86
87 void popAll()
88 {
89 const ScopedLock lock (popMutex);
90 queue.popAll ([] (IncomingCommand& command) { command(); command = nullptr; });
91 }
92
95
96private:
97 void run() override
98 {
99 while (! threadShouldExit())
100 {
101 const auto tryPop = [&]
102 {
103 const ScopedLock lock (popMutex);
104
105 if (! queue.hasPendingMessages())
106 return false;
107
108 queue.pop ([] (IncomingCommand& command) { command(); command = nullptr;});
109 return true;
110 };
111
112 if (! tryPop())
113 sleep (10);
114 }
115 }
116
117 CriticalSection popMutex;
119
121};
122
124{
125 using BackgroundMessageQueue::BackgroundMessageQueue;
126};
127
131
133 : pimpl (std::make_unique<Impl> (entries))
134{
135 pimpl->startThread();
136}
137
138ConvolutionMessageQueue::~ConvolutionMessageQueue() noexcept
139{
140 pimpl->stopThread (-1);
141}
142
143ConvolutionMessageQueue::ConvolutionMessageQueue (ConvolutionMessageQueue&&) noexcept = default;
144ConvolutionMessageQueue& ConvolutionMessageQueue::operator= (ConvolutionMessageQueue&&) noexcept = default;
145
146//==============================================================================
148{
149 ConvolutionEngine (const float* samples,
150 size_t numSamples,
151 size_t maxBlockSize)
152 : blockSize ((size_t) nextPowerOfTwo ((int) maxBlockSize)),
153 fftSize (blockSize > 128 ? 2 * blockSize : 4 * blockSize),
154 fftObject (std::make_unique<FFT> (roundToInt (std::log2 (fftSize)))),
155 numSegments (numSamples / (fftSize - blockSize) + 1u),
156 numInputSegments ((blockSize > 128 ? numSegments : 3 * numSegments)),
157 bufferInput (1, static_cast<int> (fftSize)),
158 bufferOutput (1, static_cast<int> (fftSize * 2)),
159 bufferTempOutput (1, static_cast<int> (fftSize * 2)),
160 bufferOverlap (1, static_cast<int> (fftSize))
161 {
162 bufferOutput.clear();
163
166 {
167 if (numSegmentsToUpdate == 0
168 || numSegmentsToUpdate != (size_t) segments.size()
169 || (size_t) segments[0].getNumSamples() != fftSize * 2)
170 {
171 segments.clear();
172
173 for (size_t i = 0; i < numSegmentsToUpdate; ++i)
174 segments.push_back ({ 1, static_cast<int> (fftSize * 2) });
175 }
176 };
177
178 updateSegmentsIfNecessary (numInputSegments, buffersInputSegments);
179 updateSegmentsIfNecessary (numSegments, buffersImpulseSegments);
180
181 auto FFTTempObject = std::make_unique<FFT> (roundToInt (std::log2 (fftSize)));
182 size_t currentPtr = 0;
183
184 for (auto& buf : buffersImpulseSegments)
185 {
186 buf.clear();
187
188 auto* impulseResponse = buf.getWritePointer (0);
189
190 if (&buf == &buffersImpulseSegments.front())
191 impulseResponse[0] = 1.0f;
192
193 FloatVectorOperations::copy (impulseResponse,
195 static_cast<int> (jmin (fftSize - blockSize, numSamples - currentPtr)));
196
197 FFTTempObject->performRealOnlyForwardTransform (impulseResponse);
198 prepareForConvolution (impulseResponse);
199
200 currentPtr += (fftSize - blockSize);
201 }
202
203 reset();
204 }
205
206 void reset()
207 {
208 bufferInput.clear();
209 bufferOverlap.clear();
210 bufferTempOutput.clear();
211 bufferOutput.clear();
212
213 for (auto& buf : buffersInputSegments)
214 buf.clear();
215
216 currentSegment = 0;
217 inputDataPos = 0;
218 }
219
220 void processSamples (const float* input, float* output, size_t numSamples)
221 {
222 // Overlap-add, zero latency convolution algorithm with uniform partitioning
223 size_t numSamplesProcessed = 0;
224
225 auto indexStep = numInputSegments / numSegments;
226
227 auto* inputData = bufferInput.getWritePointer (0);
228 auto* outputTempData = bufferTempOutput.getWritePointer (0);
229 auto* outputData = bufferOutput.getWritePointer (0);
230 auto* overlapData = bufferOverlap.getWritePointer (0);
231
232 while (numSamplesProcessed < numSamples)
233 {
234 const bool inputDataWasEmpty = (inputDataPos == 0);
235 auto numSamplesToProcess = jmin (numSamples - numSamplesProcessed, blockSize - inputDataPos);
236
237 FloatVectorOperations::copy (inputData + inputDataPos, input + numSamplesProcessed, static_cast<int> (numSamplesToProcess));
238
239 auto* inputSegmentData = buffersInputSegments[currentSegment].getWritePointer (0);
240 FloatVectorOperations::copy (inputSegmentData, inputData, static_cast<int> (fftSize));
241
242 fftObject->performRealOnlyForwardTransform (inputSegmentData);
243 prepareForConvolution (inputSegmentData);
244
245 // Complex multiplication
247 {
248 FloatVectorOperations::fill (outputTempData, 0, static_cast<int> (fftSize + 1));
249
250 auto index = currentSegment;
251
252 for (size_t i = 1; i < numSegments; ++i)
253 {
254 index += indexStep;
255
256 if (index >= numInputSegments)
257 index -= numInputSegments;
258
259 convolutionProcessingAndAccumulate (buffersInputSegments[index].getWritePointer (0),
260 buffersImpulseSegments[i].getWritePointer (0),
262 }
263 }
264
265 FloatVectorOperations::copy (outputData, outputTempData, static_cast<int> (fftSize + 1));
266
267 convolutionProcessingAndAccumulate (inputSegmentData,
268 buffersImpulseSegments.front().getWritePointer (0),
269 outputData);
270
271 updateSymmetricFrequencyDomainData (outputData);
272 fftObject->performRealOnlyInverseTransform (outputData);
273
274 // Add overlap
275 FloatVectorOperations::add (&output[numSamplesProcessed], &outputData[inputDataPos], &overlapData[inputDataPos], (int) numSamplesToProcess);
276
277 // Input buffer full => Next block
278 inputDataPos += numSamplesToProcess;
279
280 if (inputDataPos == blockSize)
281 {
282 // Input buffer is empty again now
283 FloatVectorOperations::fill (inputData, 0.0f, static_cast<int> (fftSize));
284
285 inputDataPos = 0;
286
287 // Extra step for segSize > blockSize
288 FloatVectorOperations::add (&(outputData[blockSize]), &(overlapData[blockSize]), static_cast<int> (fftSize - 2 * blockSize));
289
290 // Save the overlap
291 FloatVectorOperations::copy (overlapData, &(outputData[blockSize]), static_cast<int> (fftSize - blockSize));
292
293 currentSegment = (currentSegment > 0) ? (currentSegment - 1) : (numInputSegments - 1);
294 }
295
297 }
298 }
299
300 void processSamplesWithAddedLatency (const float* input, float* output, size_t numSamples)
301 {
302 // Overlap-add, zero latency convolution algorithm with uniform partitioning
303 size_t numSamplesProcessed = 0;
304
305 auto indexStep = numInputSegments / numSegments;
306
307 auto* inputData = bufferInput.getWritePointer (0);
308 auto* outputTempData = bufferTempOutput.getWritePointer (0);
309 auto* outputData = bufferOutput.getWritePointer (0);
310 auto* overlapData = bufferOverlap.getWritePointer (0);
311
312 while (numSamplesProcessed < numSamples)
313 {
314 auto numSamplesToProcess = jmin (numSamples - numSamplesProcessed, blockSize - inputDataPos);
315
316 FloatVectorOperations::copy (inputData + inputDataPos, input + numSamplesProcessed, static_cast<int> (numSamplesToProcess));
317
318 FloatVectorOperations::copy (output + numSamplesProcessed, outputData + inputDataPos, static_cast<int> (numSamplesToProcess));
319
321 inputDataPos += numSamplesToProcess;
322
323 // processing itself when needed (with latency)
324 if (inputDataPos == blockSize)
325 {
326 // Copy input data in input segment
327 auto* inputSegmentData = buffersInputSegments[currentSegment].getWritePointer (0);
328 FloatVectorOperations::copy (inputSegmentData, inputData, static_cast<int> (fftSize));
329
330 fftObject->performRealOnlyForwardTransform (inputSegmentData);
331 prepareForConvolution (inputSegmentData);
332
333 // Complex multiplication
334 FloatVectorOperations::fill (outputTempData, 0, static_cast<int> (fftSize + 1));
335
336 auto index = currentSegment;
337
338 for (size_t i = 1; i < numSegments; ++i)
339 {
340 index += indexStep;
341
342 if (index >= numInputSegments)
343 index -= numInputSegments;
344
345 convolutionProcessingAndAccumulate (buffersInputSegments[index].getWritePointer (0),
346 buffersImpulseSegments[i].getWritePointer (0),
348 }
349
350 FloatVectorOperations::copy (outputData, outputTempData, static_cast<int> (fftSize + 1));
351
352 convolutionProcessingAndAccumulate (inputSegmentData,
353 buffersImpulseSegments.front().getWritePointer (0),
354 outputData);
355
356 updateSymmetricFrequencyDomainData (outputData);
357 fftObject->performRealOnlyInverseTransform (outputData);
358
359 // Add overlap
360 FloatVectorOperations::add (outputData, overlapData, static_cast<int> (blockSize));
361
362 // Input buffer is empty again now
363 FloatVectorOperations::fill (inputData, 0.0f, static_cast<int> (fftSize));
364
365 // Extra step for segSize > blockSize
366 FloatVectorOperations::add (&(outputData[blockSize]), &(overlapData[blockSize]), static_cast<int> (fftSize - 2 * blockSize));
367
368 // Save the overlap
369 FloatVectorOperations::copy (overlapData, &(outputData[blockSize]), static_cast<int> (fftSize - blockSize));
370
371 currentSegment = (currentSegment > 0) ? (currentSegment - 1) : (numInputSegments - 1);
372
373 inputDataPos = 0;
374 }
375 }
376 }
377
378 // After each FFT, this function is called to allow convolution to be performed with only 4 SIMD functions calls.
379 void prepareForConvolution (float *samples) noexcept
380 {
381 auto FFTSizeDiv2 = fftSize / 2;
382
383 for (size_t i = 0; i < FFTSizeDiv2; i++)
384 samples[i] = samples[i << 1];
385
386 samples[FFTSizeDiv2] = 0;
387
388 for (size_t i = 1; i < FFTSizeDiv2; i++)
389 samples[i + FFTSizeDiv2] = -samples[((fftSize - i) << 1) + 1];
390 }
391
392 // Does the convolution operation itself only on half of the frequency domain samples.
393 void convolutionProcessingAndAccumulate (const float *input, const float *impulse, float *output)
394 {
395 auto FFTSizeDiv2 = fftSize / 2;
396
397 FloatVectorOperations::addWithMultiply (output, input, impulse, static_cast<int> (FFTSizeDiv2));
398 FloatVectorOperations::subtractWithMultiply (output, &(input[FFTSizeDiv2]), &(impulse[FFTSizeDiv2]), static_cast<int> (FFTSizeDiv2));
399
400 FloatVectorOperations::addWithMultiply (&(output[FFTSizeDiv2]), input, &(impulse[FFTSizeDiv2]), static_cast<int> (FFTSizeDiv2));
401 FloatVectorOperations::addWithMultiply (&(output[FFTSizeDiv2]), &(input[FFTSizeDiv2]), impulse, static_cast<int> (FFTSizeDiv2));
402
403 output[fftSize] += input[fftSize] * impulse[fftSize];
404 }
405
406 // Undoes the re-organization of samples from the function prepareForConvolution.
407 // Then takes the conjugate of the frequency domain first half of samples to fill the
408 // second half, so that the inverse transform will return real samples in the time domain.
409 void updateSymmetricFrequencyDomainData (float* samples) noexcept
410 {
411 auto FFTSizeDiv2 = fftSize / 2;
412
413 for (size_t i = 1; i < FFTSizeDiv2; i++)
414 {
415 samples[(fftSize - i) << 1] = samples[i];
416 samples[((fftSize - i) << 1) + 1] = -samples[FFTSizeDiv2 + i];
417 }
418
419 samples[1] = 0.f;
420
421 for (size_t i = 1; i < FFTSizeDiv2; i++)
422 {
423 samples[i << 1] = samples[(fftSize - i) << 1];
424 samples[(i << 1) + 1] = -samples[((fftSize - i) << 1) + 1];
425 }
426 }
427
428 //==============================================================================
429 const size_t blockSize;
430 const size_t fftSize;
431 const std::unique_ptr<FFT> fftObject;
432 const size_t numSegments;
433 const size_t numInputSegments;
434 size_t currentSegment = 0, inputDataPos = 0;
435
436 AudioBuffer<float> bufferInput, bufferOutput, bufferTempOutput, bufferOverlap;
437 std::vector<AudioBuffer<float>> buffersInputSegments, buffersImpulseSegments;
438};
439
440//==============================================================================
442{
443public:
445 int maxBlockSize,
446 int maxBufferSize,
447 Convolution::NonUniform headSizeIn,
448 bool isZeroDelayIn)
449 : tailBuffer (1, maxBlockSize),
450 latency (isZeroDelayIn ? 0 : maxBufferSize),
451 irSize (buf.getNumSamples()),
452 blockSize (maxBlockSize),
453 isZeroDelay (isZeroDelayIn)
454 {
455 constexpr auto numChannels = 2;
456
457 const auto makeEngine = [&] (int channel, int offset, int length, uint32 thisBlockSize)
458 {
459 return std::make_unique<ConvolutionEngine> (buf.getReadPointer (jmin (buf.getNumChannels() - 1, channel), offset),
460 length,
461 static_cast<size_t> (thisBlockSize));
462 };
463
464 if (headSizeIn.headSizeInSamples == 0)
465 {
466 for (int i = 0; i < numChannels; ++i)
467 head.emplace_back (makeEngine (i, 0, buf.getNumSamples(), static_cast<uint32> (maxBufferSize)));
468 }
469 else
470 {
471 const auto size = jmin (buf.getNumSamples(), headSizeIn.headSizeInSamples);
472
473 for (int i = 0; i < numChannels; ++i)
474 head.emplace_back (makeEngine (i, 0, size, static_cast<uint32> (maxBufferSize)));
475
476 const auto tailBufferSize = static_cast<uint32> (headSizeIn.headSizeInSamples + (isZeroDelay ? 0 : maxBufferSize));
477
478 if (size != buf.getNumSamples())
479 for (int i = 0; i < numChannels; ++i)
480 tail.emplace_back (makeEngine (i, size, buf.getNumSamples() - size, tailBufferSize));
481 }
482 }
483
484 void reset()
485 {
486 for (const auto& e : head)
487 e->reset();
488
489 for (const auto& e : tail)
490 e->reset();
491 }
492
493 void processSamples (const AudioBlock<const float>& input, AudioBlock<float>& output)
494 {
495 const auto numChannels = jmin (head.size(), input.getNumChannels(), output.getNumChannels());
496 const auto numSamples = jmin (input.getNumSamples(), output.getNumSamples());
497
498 const AudioBlock<float> fullTailBlock (tailBuffer);
499 const auto tailBlock = fullTailBlock.getSubBlock (0, (size_t) numSamples);
500
501 const auto isUniform = tail.empty();
502
503 for (size_t channel = 0; channel < numChannels; ++channel)
504 {
505 if (! isUniform)
506 tail[channel]->processSamplesWithAddedLatency (input.getChannelPointer (channel),
507 tailBlock.getChannelPointer (0),
508 numSamples);
509
510 if (isZeroDelay)
511 head[channel]->processSamples (input.getChannelPointer (channel),
512 output.getChannelPointer (channel),
513 numSamples);
514 else
515 head[channel]->processSamplesWithAddedLatency (input.getChannelPointer (channel),
516 output.getChannelPointer (channel),
517 numSamples);
518
519 if (! isUniform)
520 output.getSingleChannelBlock (channel) += tailBlock;
521 }
522
523 const auto numOutputChannels = output.getNumChannels();
524
525 for (auto i = numChannels; i < numOutputChannels; ++i)
527 }
528
529 int getIRSize() const noexcept { return irSize; }
530 int getLatency() const noexcept { return latency; }
531 int getBlockSize() const noexcept { return blockSize; }
532
533private:
535 AudioBuffer<float> tailBuffer;
536
537 const int latency;
538 const int irSize;
539 const int blockSize;
540 const bool isZeroDelay;
541};
542
543static AudioBuffer<float> fixNumChannels (const AudioBuffer<float>& buf, Convolution::Stereo stereo)
544{
545 const auto numChannels = jmin (buf.getNumChannels(), stereo == Convolution::Stereo::yes ? 2 : 1);
546 const auto numSamples = buf.getNumSamples();
547
548 AudioBuffer<float> result (numChannels, buf.getNumSamples());
549
550 for (auto channel = 0; channel != numChannels; ++channel)
551 result.copyFrom (channel, 0, buf.getReadPointer (channel), numSamples);
552
553 if (result.getNumSamples() == 0 || result.getNumChannels() == 0)
554 {
555 result.setSize (1, 1);
556 result.setSample (0, 0, 1.0f);
557 }
558
559 return result;
560}
561
562static AudioBuffer<float> trimImpulseResponse (const AudioBuffer<float>& buf)
563{
564 const auto thresholdTrim = Decibels::decibelsToGain (-80.0f);
565
566 const auto numChannels = buf.getNumChannels();
567 const auto numSamples = buf.getNumSamples();
568
569 std::ptrdiff_t offsetBegin = numSamples;
570 std::ptrdiff_t offsetEnd = numSamples;
571
572 for (auto channel = 0; channel < numChannels; ++channel)
573 {
574 const auto indexAboveThreshold = [&] (auto begin, auto end)
575 {
576 return std::distance (begin, std::find_if (begin, end, [&] (float sample)
577 {
578 return std::abs (sample) >= thresholdTrim;
579 }));
580 };
581
582 const auto channelBegin = buf.getReadPointer (channel);
583 const auto channelEnd = channelBegin + numSamples;
587
590 }
591
592 if (offsetBegin == numSamples)
593 {
594 auto result = AudioBuffer<float> (numChannels, 1);
595 result.clear();
596 return result;
597 }
598
599 const auto newLength = jmax (1, numSamples - static_cast<int> (offsetBegin + offsetEnd));
600
601 AudioBuffer<float> result (numChannels, newLength);
602
603 for (auto channel = 0; channel < numChannels; ++channel)
604 {
605 result.copyFrom (channel,
606 0,
607 buf.getReadPointer (channel, static_cast<int> (offsetBegin)),
608 result.getNumSamples());
609 }
610
611 return result;
612}
613
614static float calculateNormalisationFactor (float sumSquaredMagnitude)
615{
616 if (sumSquaredMagnitude < 1e-8f)
617 return 1.0f;
618
619 return 0.125f / std::sqrt (sumSquaredMagnitude);
620}
621
622static void normaliseImpulseResponse (AudioBuffer<float>& buf)
623{
624 const auto numChannels = buf.getNumChannels();
625 const auto numSamples = buf.getNumSamples();
626 const auto channelPtrs = buf.getArrayOfWritePointers();
627
628 const auto maxSumSquaredMag = std::accumulate (channelPtrs, channelPtrs + numChannels, 0.0f, [numSamples] (auto max, auto* channel)
629 {
630 return jmax (max, std::accumulate (channel, channel + numSamples, 0.0f, [] (auto sum, auto samp)
631 {
632 return sum + (samp * samp);
633 }));
634 });
635
636 const auto normalisationFactor = calculateNormalisationFactor (maxSumSquaredMag);
637
638 std::for_each (channelPtrs, channelPtrs + numChannels, [normalisationFactor, numSamples] (auto* channel)
639 {
640 FloatVectorOperations::multiply (channel, normalisationFactor, numSamples);
641 });
642}
643
644static AudioBuffer<float> resampleImpulseResponse (const AudioBuffer<float>& buf,
645 const double srcSampleRate,
646 const double destSampleRate)
647{
649 return buf;
650
652
653 AudioBuffer<float> original = buf;
654 MemoryAudioSource memorySource (original, false);
655 ResamplingAudioSource resamplingSource (&memorySource, false, buf.getNumChannels());
656
657 const auto finalSize = roundToInt (jmax (1.0, buf.getNumSamples() / factorReading));
658 resamplingSource.setResamplingRatio (factorReading);
660
661 AudioBuffer<float> result (buf.getNumChannels(), finalSize);
662 resamplingSource.getNextAudioBlock ({ &result, 0, result.getNumSamples() });
663
664 return result;
665}
666
667//==============================================================================
668template <typename Element>
670{
671public:
672 void set (std::unique_ptr<Element> p)
673 {
674 const SpinLock::ScopedLockType lock (mutex);
675 ptr = std::move (p);
676 }
677
679 {
680 const SpinLock::ScopedTryLockType lock (mutex);
681 return lock.isLocked() ? std::move (ptr) : nullptr;
682 }
683
684private:
686 SpinLock mutex;
687};
688
690{
691 BufferWithSampleRate() = default;
692
693 BufferWithSampleRate (AudioBuffer<float>&& bufferIn, double sampleRateIn)
694 : buffer (std::move (bufferIn)), sampleRate (sampleRateIn) {}
695
696 AudioBuffer<float> buffer;
697 double sampleRate = 0.0;
698};
699
700static BufferWithSampleRate loadStreamToBuffer (std::unique_ptr<InputStream> stream, size_t maxLength)
701{
702 AudioFormatManager manager;
703 manager.registerBasicFormats();
705
706 if (formatReader == nullptr)
707 return {};
708
709 const auto fileLength = static_cast<size_t> (formatReader->lengthInSamples);
710 const auto lengthToLoad = maxLength == 0 ? fileLength : jmin (maxLength, fileLength);
711
712 BufferWithSampleRate result { { jlimit (1, 2, static_cast<int> (formatReader->numChannels)),
713 static_cast<int> (lengthToLoad) },
714 formatReader->sampleRate };
715
716 formatReader->read (result.buffer.getArrayOfWritePointers(),
717 result.buffer.getNumChannels(),
718 0,
719 result.buffer.getNumSamples());
720
721 return result;
722}
723
724// This class caches the data required to build a new convolution engine
725// (in particular, impulse response data and a ProcessSpec).
726// Calls to `setProcessSpec` and `setImpulseResponse` construct a
727// new engine, which can be retrieved by calling `getEngine`.
729{
730public:
732 Convolution::NonUniform requiredHeadSize)
733 : latency { (requiredLatency.latencyInSamples <= 0) ? 0 : jmax (64, nextPowerOfTwo (requiredLatency.latencyInSamples)) },
734 headSize { (requiredHeadSize.headSizeInSamples <= 0) ? 0 : jmax (64, nextPowerOfTwo (requiredHeadSize.headSizeInSamples)) },
735 shouldBeZeroLatency (requiredLatency.latencyInSamples == 0)
736 {}
737
738 // It is safe to call this method simultaneously with other public
739 // member functions.
740 void setProcessSpec (const ProcessSpec& spec)
741 {
742 const std::lock_guard<std::mutex> lock (mutex);
743 processSpec = spec;
744
745 engine.set (makeEngine());
746 }
747
748 // It is safe to call this method simultaneously with other public
749 // member functions.
750 void setImpulseResponse (BufferWithSampleRate&& buf,
751 Convolution::Stereo stereo,
752 Convolution::Trim trim,
753 Convolution::Normalise normalise)
754 {
755 const std::lock_guard<std::mutex> lock (mutex);
756 wantsNormalise = normalise;
757 originalSampleRate = buf.sampleRate;
758
759 impulseResponse = [&]
760 {
761 auto corrected = fixNumChannels (buf.buffer, stereo);
762 return trim == Convolution::Trim::yes ? trimImpulseResponse (corrected) : corrected;
763 }();
764
765 engine.set (makeEngine());
766 }
767
768 // Returns the most recently-created engine, or nullptr
769 // if there is no pending engine, or if the engine is currently
770 // being updated by one of the setter methods.
771 // It is safe to call this simultaneously with other public
772 // member functions.
773 std::unique_ptr<MultichannelEngine> getEngine() { return engine.get(); }
774
775private:
777 {
778 auto resampled = resampleImpulseResponse (impulseResponse, originalSampleRate, processSpec.sampleRate);
779
780 if (wantsNormalise == Convolution::Normalise::yes)
781 normaliseImpulseResponse (resampled);
782 else
783 resampled.applyGain ((float) (originalSampleRate / processSpec.sampleRate));
784
785 const auto currentLatency = jmax (processSpec.maximumBlockSize, (uint32) latency.latencyInSamples);
786 const auto maxBufferSize = shouldBeZeroLatency ? static_cast<int> (processSpec.maximumBlockSize)
787 : nextPowerOfTwo (static_cast<int> (currentLatency));
788
789 return std::make_unique<MultichannelEngine> (resampled,
790 processSpec.maximumBlockSize,
791 maxBufferSize,
792 headSize,
793 shouldBeZeroLatency);
794 }
795
796 static AudioBuffer<float> makeImpulseBuffer()
797 {
798 AudioBuffer<float> result (1, 1);
799 result.setSample (0, 0, 1.0f);
800 return result;
801 }
802
803 ProcessSpec processSpec { 44100.0, 128, 2 };
804 AudioBuffer<float> impulseResponse = makeImpulseBuffer();
805 double originalSampleRate = processSpec.sampleRate;
806 Convolution::Normalise wantsNormalise = Convolution::Normalise::no;
807 const Convolution::Latency latency;
808 const Convolution::NonUniform headSize;
809 const bool shouldBeZeroLatency;
810
812
813 mutable std::mutex mutex;
814};
815
816static void setImpulseResponse (ConvolutionEngineFactory& factory,
817 const void* sourceData,
818 size_t sourceDataSize,
819 Convolution::Stereo stereo,
820 Convolution::Trim trim,
821 size_t size,
822 Convolution::Normalise normalise)
823{
824 factory.setImpulseResponse (loadStreamToBuffer (std::make_unique<MemoryInputStream> (sourceData, sourceDataSize, false), size),
825 stereo, trim, normalise);
826}
827
828static void setImpulseResponse (ConvolutionEngineFactory& factory,
830 Convolution::Stereo stereo,
831 Convolution::Trim trim,
832 size_t size,
833 Convolution::Normalise normalise)
834{
835 factory.setImpulseResponse (loadStreamToBuffer (std::make_unique<FileInputStream> (fileImpulseResponse), size),
836 stereo, trim, normalise);
837}
838
839// This class acts as a destination for convolution engines which are loaded on
840// a background thread.
841
842// Deriving from `enable_shared_from_this` allows us to capture a reference to
843// this object when adding commands to the background message queue.
844// That way, we can avoid dangling references in the background thread in the case
845// that a Convolution instance is deleted before the background message queue.
846class ConvolutionEngineQueue final : public std::enable_shared_from_this<ConvolutionEngineQueue>
847{
848public:
850 Convolution::Latency latencyIn,
851 Convolution::NonUniform headSizeIn)
852 : messageQueue (queue), factory (latencyIn, headSizeIn) {}
853
854 void loadImpulseResponse (AudioBuffer<float>&& buffer,
855 double sr,
856 Convolution::Stereo stereo,
857 Convolution::Trim trim,
858 Convolution::Normalise normalise)
859 {
860 callLater ([b = std::move (buffer), sr, stereo, trim, normalise] (ConvolutionEngineFactory& f) mutable
861 {
862 f.setImpulseResponse ({ std::move (b), sr }, stereo, trim, normalise);
863 });
864 }
865
866 void loadImpulseResponse (const void* sourceData,
867 size_t sourceDataSize,
868 Convolution::Stereo stereo,
869 Convolution::Trim trim,
870 size_t size,
871 Convolution::Normalise normalise)
872 {
873 callLater ([sourceData, sourceDataSize, stereo, trim, size, normalise] (ConvolutionEngineFactory& f) mutable
874 {
875 setImpulseResponse (f, sourceData, sourceDataSize, stereo, trim, size, normalise);
876 });
877 }
878
879 void loadImpulseResponse (const File& fileImpulseResponse,
880 Convolution::Stereo stereo,
881 Convolution::Trim trim,
882 size_t size,
883 Convolution::Normalise normalise)
884 {
885 callLater ([fileImpulseResponse, stereo, trim, size, normalise] (ConvolutionEngineFactory& f) mutable
886 {
887 setImpulseResponse (f, fileImpulseResponse, stereo, trim, size, normalise);
888 });
889 }
890
891 void prepare (const ProcessSpec& spec)
892 {
893 factory.setProcessSpec (spec);
894 }
895
896 // Call this regularly to try to resend any pending message.
897 // This allows us to always apply the most recently requested
898 // state (eventually), even if the message queue fills up.
899 void postPendingCommand()
900 {
901 if (pendingCommand == nullptr)
902 return;
903
904 if (messageQueue.push (pendingCommand))
905 pendingCommand = nullptr;
906 }
907
908 std::unique_ptr<MultichannelEngine> getEngine() { return factory.getEngine(); }
909
910private:
911 template <typename Fn>
912 void callLater (Fn&& fn)
913 {
914 // If there was already a pending command (because the queue was full) we'll end up deleting it here.
915 // Not much we can do about that!
916 pendingCommand = [weak = weakFromThis(), callback = std::forward<Fn> (fn)]() mutable
917 {
918 if (auto t = weak.lock())
919 callback (t->factory);
920 };
921
922 postPendingCommand();
923 }
924
925 std::weak_ptr<ConvolutionEngineQueue> weakFromThis() { return shared_from_this(); }
926
927 BackgroundMessageQueue& messageQueue;
929 BackgroundMessageQueue::IncomingCommand pendingCommand;
930};
931
933{
934public:
935 void reset()
936 {
937 smoother.setCurrentAndTargetValue (1.0f);
938 }
939
940 void prepare (const ProcessSpec& spec)
941 {
942 smoother.reset (spec.sampleRate, 0.05);
943 smootherBuffer.setSize (1, static_cast<int> (spec.maximumBlockSize));
944 mixBuffer.setSize (static_cast<int> (spec.numChannels), static_cast<int> (spec.maximumBlockSize));
945 reset();
946 }
947
948 template <typename ProcessCurrent, typename ProcessPrevious, typename NotifyDone>
949 void processSamples (const AudioBlock<const float>& input,
950 AudioBlock<float>& output,
951 ProcessCurrent&& current,
952 ProcessPrevious&& previous,
953 NotifyDone&& notifyDone)
954 {
955 if (smoother.isSmoothing())
956 {
957 const auto numSamples = static_cast<int> (input.getNumSamples());
958
959 for (auto sample = 0; sample != numSamples; ++sample)
960 smootherBuffer.setSample (0, sample, smoother.getNextValue());
961
962 AudioBlock<float> mixBlock (mixBuffer);
963 mixBlock.clear();
964 previous (input, mixBlock);
965
966 for (size_t channel = 0; channel != output.getNumChannels(); ++channel)
967 {
968 FloatVectorOperations::multiply (mixBlock.getChannelPointer (channel),
969 smootherBuffer.getReadPointer (0),
970 numSamples);
971 }
972
973 FloatVectorOperations::multiply (smootherBuffer.getWritePointer (0), -1.0f, numSamples);
974 FloatVectorOperations::add (smootherBuffer.getWritePointer (0), 1.0f, numSamples);
975
976 current (input, output);
977
978 for (size_t channel = 0; channel != output.getNumChannels(); ++channel)
979 {
980 FloatVectorOperations::multiply (output.getChannelPointer (channel),
981 smootherBuffer.getReadPointer (0),
982 numSamples);
983 FloatVectorOperations::add (output.getChannelPointer (channel),
984 mixBlock.getChannelPointer (channel),
985 numSamples);
986 }
987
988 if (! smoother.isSmoothing())
989 notifyDone();
990 }
991 else
992 {
993 current (input, output);
994 }
995 }
996
997 void beginTransition()
998 {
999 smoother.setCurrentAndTargetValue (1.0f);
1000 smoother.setTargetValue (0.0f);
1001 }
1002
1003private:
1005 AudioBuffer<float> smootherBuffer;
1006 AudioBuffer<float> mixBuffer;
1007};
1008
1010
1012{
1013public:
1014 Impl (Latency requiredLatency,
1015 NonUniform requiredHeadSize,
1016 OptionalQueue&& queue)
1017 : messageQueue (std::move (queue)),
1018 engineQueue (std::make_shared<ConvolutionEngineQueue> (*messageQueue->pimpl,
1019 requiredLatency,
1020 requiredHeadSize))
1021 {}
1022
1023 void reset()
1024 {
1025 mixer.reset();
1026
1027 if (currentEngine != nullptr)
1028 currentEngine->reset();
1029
1030 destroyPreviousEngine();
1031 }
1032
1033 void prepare (const ProcessSpec& spec)
1034 {
1035 messageQueue->pimpl->popAll();
1036 mixer.prepare (spec);
1037 engineQueue->prepare (spec);
1038
1039 if (auto newEngine = engineQueue->getEngine())
1040 currentEngine = std::move (newEngine);
1041
1042 previousEngine = nullptr;
1043 jassert (currentEngine != nullptr);
1044 }
1045
1046 void processSamples (const AudioBlock<const float>& input, AudioBlock<float>& output)
1047 {
1048 engineQueue->postPendingCommand();
1049
1050 if (previousEngine == nullptr)
1051 installPendingEngine();
1052
1053 mixer.processSamples (input,
1054 output,
1055 [this] (const AudioBlock<const float>& in, AudioBlock<float>& out)
1056 {
1057 currentEngine->processSamples (in, out);
1058 },
1059 [this] (const AudioBlock<const float>& in, AudioBlock<float>& out)
1060 {
1061 if (previousEngine != nullptr)
1062 previousEngine->processSamples (in, out);
1063 else
1064 out.copyFrom (in);
1065 },
1066 [this] { destroyPreviousEngine(); });
1067 }
1068
1069 int getCurrentIRSize() const { return currentEngine != nullptr ? currentEngine->getIRSize() : 0; }
1070
1071 int getLatency() const { return currentEngine != nullptr ? currentEngine->getLatency() : 0; }
1072
1073 void loadImpulseResponse (AudioBuffer<float>&& buffer,
1074 double originalSampleRate,
1075 Stereo stereo,
1076 Trim trim,
1077 Normalise normalise)
1078 {
1079 engineQueue->loadImpulseResponse (std::move (buffer), originalSampleRate, stereo, trim, normalise);
1080 }
1081
1082 void loadImpulseResponse (const void* sourceData,
1083 size_t sourceDataSize,
1084 Stereo stereo,
1085 Trim trim,
1086 size_t size,
1087 Normalise normalise)
1088 {
1089 engineQueue->loadImpulseResponse (sourceData, sourceDataSize, stereo, trim, size, normalise);
1090 }
1091
1092 void loadImpulseResponse (const File& fileImpulseResponse,
1093 Stereo stereo,
1094 Trim trim,
1095 size_t size,
1096 Normalise normalise)
1097 {
1098 engineQueue->loadImpulseResponse (fileImpulseResponse, stereo, trim, size, normalise);
1099 }
1100
1101private:
1102 void destroyPreviousEngine()
1103 {
1104 // If the queue is full, we'll destroy this straight away
1105 BackgroundMessageQueue::IncomingCommand command = [p = std::move (previousEngine)]() mutable { p = nullptr; };
1106 messageQueue->pimpl->push (command);
1107 }
1108
1109 void installNewEngine (std::unique_ptr<MultichannelEngine> newEngine)
1110 {
1111 destroyPreviousEngine();
1112 previousEngine = std::move (currentEngine);
1113 currentEngine = std::move (newEngine);
1114 mixer.beginTransition();
1115 }
1116
1117 void installPendingEngine()
1118 {
1119 if (auto newEngine = engineQueue->getEngine())
1120 installNewEngine (std::move (newEngine));
1121 }
1122
1123 OptionalQueue messageQueue;
1125 std::unique_ptr<MultichannelEngine> previousEngine, currentEngine;
1126 CrossoverMixer mixer;
1127};
1128
1129//==============================================================================
1130void Convolution::Mixer::prepare (const ProcessSpec& spec)
1131{
1132 for (auto& dry : volumeDry)
1133 dry.reset (spec.sampleRate, 0.05);
1134
1135 for (auto& wet : volumeWet)
1136 wet.reset (spec.sampleRate, 0.05);
1137
1138 sampleRate = spec.sampleRate;
1139
1140 dryBlock = AudioBlock<float> (dryBlockStorage,
1141 jmin (spec.numChannels, 2u),
1142 spec.maximumBlockSize);
1143
1144}
1145
1146template <typename ProcessWet>
1147void Convolution::Mixer::processSamples (const AudioBlock<const float>& input,
1148 AudioBlock<float>& output,
1149 bool isBypassed,
1150 ProcessWet&& processWet) noexcept
1151{
1152 const auto numChannels = jmin (input.getNumChannels(), volumeDry.size());
1153 const auto numSamples = jmin (input.getNumSamples(), output.getNumSamples());
1154
1155 auto dry = dryBlock.getSubsetChannelBlock (0, numChannels);
1156
1157 if (volumeDry[0].isSmoothing())
1158 {
1159 dry.copyFrom (input);
1160
1161 for (size_t channel = 0; channel < numChannels; ++channel)
1162 volumeDry[channel].applyGain (dry.getChannelPointer (channel), (int) numSamples);
1163
1164 processWet (input, output);
1165
1166 for (size_t channel = 0; channel < numChannels; ++channel)
1167 volumeWet[channel].applyGain (output.getChannelPointer (channel), (int) numSamples);
1168
1169 output += dry;
1170 }
1171 else
1172 {
1173 if (! currentIsBypassed)
1174 processWet (input, output);
1175
1176 if (isBypassed != currentIsBypassed)
1177 {
1178 currentIsBypassed = isBypassed;
1179
1180 for (size_t channel = 0; channel < numChannels; ++channel)
1181 {
1182 volumeDry[channel].setTargetValue (isBypassed ? 0.0f : 1.0f);
1183 volumeDry[channel].reset (sampleRate, 0.05);
1184 volumeDry[channel].setTargetValue (isBypassed ? 1.0f : 0.0f);
1185
1186 volumeWet[channel].setTargetValue (isBypassed ? 1.0f : 0.0f);
1187 volumeWet[channel].reset (sampleRate, 0.05);
1188 volumeWet[channel].setTargetValue (isBypassed ? 0.0f : 1.0f);
1189 }
1190 }
1191 }
1192}
1193
1194void Convolution::Mixer::reset() { dryBlock.clear(); }
1195
1196//==============================================================================
1197Convolution::Convolution()
1198 : Convolution (Latency { 0 })
1199{}
1200
1204
1207 {},
1208 OptionalQueue { std::make_unique<ConvolutionMessageQueue>() })
1209{}
1210
1212 : Convolution ({},
1213 nonUniform,
1214 OptionalQueue { std::make_unique<ConvolutionMessageQueue>() })
1215{}
1216
1220
1224
1225Convolution::Convolution (const Latency& latency,
1226 const NonUniform& nonUniform,
1227 OptionalQueue&& queue)
1228 : pimpl (std::make_unique<Impl> (latency, nonUniform, std::move (queue)))
1229{}
1230
1231Convolution::~Convolution() noexcept = default;
1232
1233void Convolution::loadImpulseResponse (const void* sourceData,
1234 size_t sourceDataSize,
1235 Stereo stereo,
1236 Trim trim,
1237 size_t size,
1238 Normalise normalise)
1239{
1240 pimpl->loadImpulseResponse (sourceData, sourceDataSize, stereo, trim, size, normalise);
1241}
1242
1244 Stereo stereo,
1245 Trim trim,
1246 size_t size,
1247 Normalise normalise)
1248{
1249 pimpl->loadImpulseResponse (fileImpulseResponse, stereo, trim, size, normalise);
1250}
1251
1253 double originalSampleRate,
1254 Stereo stereo,
1255 Trim trim,
1256 Normalise normalise)
1257{
1258 pimpl->loadImpulseResponse (std::move (buffer), originalSampleRate, stereo, trim, normalise);
1259}
1260
1262{
1263 mixer.prepare (spec);
1264 pimpl->prepare (spec);
1265 isActive = true;
1266}
1267
1268void Convolution::reset() noexcept
1269{
1270 mixer.reset();
1271 pimpl->reset();
1272}
1273
1274void Convolution::processSamples (const AudioBlock<const float>& input,
1275 AudioBlock<float>& output,
1276 bool isBypassed) noexcept
1277{
1278 if (! isActive)
1279 return;
1280
1281 jassert (input.getNumChannels() == output.getNumChannels());
1282 jassert (isPositiveAndBelow (input.getNumChannels(), static_cast<size_t> (3))); // only mono and stereo is supported
1283
1284 mixer.processSamples (input, output, isBypassed, [this] (const auto& in, auto& out)
1285 {
1286 pimpl->processSamples (in, out);
1287 });
1288}
1289
1290int Convolution::getCurrentIRSize() const { return pimpl->getCurrentIRSize(); }
1291
1292int Convolution::getLatency() const { return pimpl->getLatency(); }
1293
1294} // namespace juce::dsp
T accumulate(T... args)
void forEach(FunctionToApply &&func) const
Calls the passed function with each index that was deemed valid for the current read/write operation.
Encapsulates the logic required to implement a lock-free FIFO.
ScopedRead read(int numToRead) noexcept
Replaces prepareToRead/finishedRead with a single function.
int getFreeSpace() const noexcept
Returns the number of items that can currently be added to the buffer without it overflowing.
ScopedWrite write(int numToWrite) noexcept
Replaces prepareToWrite/finishedWrite with a single function.
int getNumReady() const noexcept
Returns the number of items that can currently be read from the buffer.
A multi-channel buffer containing floating point audio samples.
int getNumChannels() const noexcept
Returns the number of channels of audio data that this buffer contains.
int getNumSamples() const noexcept
Returns the number of samples allocated in each of the buffer's channels.
void setSample(int destChannel, int destSample, Type newValue) noexcept
Sets a sample in the buffer.
const Type * getReadPointer(int channelNumber) const noexcept
Returns a pointer to an array of read-only samples in one of the buffer's channels.
A class for keeping a list of available audio formats, and for deciding which one to use to open a gi...
AudioFormatReader * createReaderFor(const File &audioFile)
Searches through the known formats to try to create a suitable reader for this file.
void registerBasicFormats()
Handy method to make it easy to register the formats that come with JUCE.
static Type decibelsToGain(Type decibels, Type minusInfinityDb=Type(defaultMinusInfinitydB))
Converts a dBFS value to its equivalent gain level.
Represents a local file or directory.
Definition juce_File.h:45
Automatically locks and unlocks a mutex object.
Automatically locks and unlocks a mutex object.
A utility class for values that need smoothing to avoid audio glitches.
A simple spin-lock class that can be used as a simple, low-overhead mutex for uncontended situations.
Encapsulates a thread.
Definition juce_Thread.h:43
bool startThread()
Attempts to start a new thread with default ('Priority::normal') priority.
bool threadShouldExit() const
Checks whether the thread has been told to stop running.
bool stopThread(int timeOutMilliseconds)
Attempts to stop the thread running.
Minimal and lightweight data-structure which contains a list of pointers to channels containing some ...
AudioBlock getSubBlock(size_t newOffset, size_t newLength) const noexcept
Return a new AudioBlock pointing to a sub-block inside this block.
constexpr size_t getNumChannels() const noexcept
Returns the number of channels referenced by this block.
SampleType * getChannelPointer(size_t channel) const noexcept
Returns a raw pointer into one of the channels in this block.
AudioBlock & copyFrom(const AudioBlock< OtherSampleType > &src) noexcept
Copies the values in src to this block.
AudioBlock getSingleChannelBlock(size_t channel) const noexcept
Returns an AudioBlock that represents one of the channels in this block.
constexpr size_t getNumSamples() const noexcept
Returns the number of samples referenced by this block.
AudioBlock & clear() noexcept
Clears the memory referenced by this AudioBlock.
Used by the Convolution to dispatch engine-update messages on a background thread.
ConvolutionMessageQueue()
Initialises the queue to a default size.
Performs stereo partitioned convolution of an input signal with an impulse response in the frequency ...
int getCurrentIRSize() const
This function returns the size of the current IR in samples.
Convolution()
Initialises an object for performing convolution in the frequency domain.
int getLatency() const
This function returns the current latency of the process in samples.
void prepare(const ProcessSpec &)
Must be called before first calling process.
void loadImpulseResponse(const void *sourceData, size_t sourceDataSize, Stereo isStereo, Trim requiresTrimming, size_t size, Normalise requiresNormalisation=Normalise::yes)
This function loads an impulse response audio file from memory, added in a JUCE project with the Proj...
void reset() noexcept
Resets the processing pipeline ready to start a new stream of data.
Contains configuration information for a convolution with a fixed latency.
Contains configuration information for a non-uniform convolution.
T distance(T... args)
T find_if(T... args)
T for_each(T... args)
T get(T... args)
#define jassert(expression)
Platform-independent assertion macro.
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
This is a shorthand way of writing both a JUCE_DECLARE_NON_COPYABLE and JUCE_LEAK_DETECTOR macro for ...
auto & get(ProcessorChain< Processors... > &chain) noexcept
Non-member equivalent of ProcessorChain::get which avoids awkward member template syntax.
T log2(T... args)
T make_reverse_iterator(T... args)
constexpr bool approximatelyEqual(Type a, Type b, Tolerance< Type > tolerance=Tolerance< Type >{} .withAbsolute(std::numeric_limits< Type >::min()) .withRelative(std::numeric_limits< Type >::epsilon()))
Returns true if the two floating-point numbers are approximately equal.
constexpr Type jmin(Type a, Type b)
Returns the smaller of two values.
constexpr Type jmax(Type a, Type b)
Returns the larger of two values.
RangedDirectoryIterator end(const RangedDirectoryIterator &)
Returns a default-constructed sentinel value.
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 isPositiveAndBelow(Type1 valueToTest, Type2 upperLimit) noexcept
Returns true if a value is at least zero, and also below a specified upper limit.
unsigned int uint32
A platform-independent 32-bit unsigned integer type.
int roundToInt(const FloatType value) noexcept
Fast floating-point-to-integer conversion.
RangedDirectoryIterator begin(const RangedDirectoryIterator &it)
Returns the iterator that was passed in.
T reset(T... args)
sleep
T sqrt(T... args)
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.
typedef size_t