OpenShot Audio Library | OpenShotAudio 0.4.0

Loading...
Searching...
No Matches
juce_Convolution.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
26 namespace juce::dsp
27{
28
29 template <typename Element>
30 class Queue
31{
32 public:
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
59 private:
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;
70 std::vector<Element> storage;
71};
72
73 class BackgroundMessageQueue : private Thread
74{
75 public:
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
93 using Thread::startThread;
94 using Thread::stopThread;
95
96 private:
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;
118 Queue<IncomingCommand> queue;
119
120 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BackgroundMessageQueue)
121};
122
123 struct ConvolutionMessageQueue::Impl : public BackgroundMessageQueue
124{
125 using BackgroundMessageQueue::BackgroundMessageQueue;
126};
127
128 ConvolutionMessageQueue::ConvolutionMessageQueue()
129 : ConvolutionMessageQueue (1000)
130{}
131
132 ConvolutionMessageQueue::ConvolutionMessageQueue (int entries)
133 : pimpl (std::make_unique<Impl> (entries))
134{
135 pimpl->startThread();
136}
137
138ConvolutionMessageQueue::~ConvolutionMessageQueue() noexcept
139{
140 pimpl->stopThread (-1);
141}
142
143 ConvolutionMessageQueue::ConvolutionMessageQueue (ConvolutionMessageQueue&&) noexcept = default;
144ConvolutionMessageQueue& ConvolutionMessageQueue::operator= (ConvolutionMessageQueue&&) noexcept = default;
145
146//==============================================================================
147struct ConvolutionEngine
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
164 auto updateSegmentsIfNecessary = [this] (size_t numSegmentsToUpdate,
165 std::vector<AudioBuffer<float>>& segments)
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,
194 samples + currentPtr,
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
246 if (inputDataWasEmpty)
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),
261 outputTempData);
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
296 numSamplesProcessed += numSamplesToProcess;
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
320 numSamplesProcessed += numSamplesToProcess;
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),
347 outputTempData);
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//==============================================================================
441 class MultichannelEngine
442{
443 public:
444 MultichannelEngine (const AudioBuffer<float>& buf,
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)
526 output.getSingleChannelBlock (i).copyFrom (output.getSingleChannelBlock (0));
527 }
528
529 int getIRSize() const noexcept { return irSize; }
530 int getLatency() const noexcept { return latency; }
531 int getBlockSize() const noexcept { return blockSize; }
532
533 private:
534 std::vector<std::unique_ptr<ConvolutionEngine>> head, tail;
535 AudioBuffer<float> tailBuffer;
536
537 const int latency;
538 const int irSize;
539 const int blockSize;
540 const bool isZeroDelay;
541};
542
543 static 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
562 static 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;
584 const auto itStart = indexAboveThreshold (channelBegin, channelEnd);
585 const auto itEnd = indexAboveThreshold (std::make_reverse_iterator (channelEnd),
586 std::make_reverse_iterator (channelBegin));
587
588 offsetBegin = jmin (offsetBegin, itStart);
589 offsetEnd = jmin (offsetEnd, itEnd);
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
614 static float calculateNormalisationFactor (float sumSquaredMagnitude)
615{
616 if (sumSquaredMagnitude < 1e-8f)
617 return 1.0f;
618
619 return 0.125f / std::sqrt (sumSquaredMagnitude);
620}
621
622 static 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
644 static AudioBuffer<float> resampleImpulseResponse (const AudioBuffer<float>& buf,
645 const double srcSampleRate,
646 const double destSampleRate)
647{
648 if (approximatelyEqual (srcSampleRate, destSampleRate))
649 return buf;
650
651 const auto factorReading = srcSampleRate / destSampleRate;
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);
659 resamplingSource.prepareToPlay (finalSize, srcSampleRate);
660
661 AudioBuffer<float> result (buf.getNumChannels(), finalSize);
662 resamplingSource.getNextAudioBlock ({ &result, 0, result.getNumSamples() });
663
664 return result;
665}
666
667//==============================================================================
668 template <typename Element>
669 class TryLockedPtr
670{
671 public:
672 void set (std::unique_ptr<Element> p)
673 {
674 const SpinLock::ScopedLockType lock (mutex);
675 ptr = std::move (p);
676 }
677
678 std::unique_ptr<MultichannelEngine> get()
679 {
680 const SpinLock::ScopedTryLockType lock (mutex);
681 return lock.isLocked() ? std::move (ptr) : nullptr;
682 }
683
684 private:
685 std::unique_ptr<Element> ptr;
686 SpinLock mutex;
687};
688
689 struct BufferWithSampleRate
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
700 static BufferWithSampleRate loadStreamToBuffer (std::unique_ptr<InputStream> stream, size_t maxLength)
701{
702 AudioFormatManager manager;
703 manager.registerBasicFormats();
704 std::unique_ptr<AudioFormatReader> formatReader (manager.createReaderFor (std::move (stream)));
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`.
728 class ConvolutionEngineFactory
729{
730 public:
731 ConvolutionEngineFactory (Convolution::Latency requiredLatency,
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
775 private:
776 std::unique_ptr<MultichannelEngine> makeEngine()
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
811 TryLockedPtr<MultichannelEngine> engine;
812
813 mutable std::mutex mutex;
814};
815
816 static 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
828 static void setImpulseResponse (ConvolutionEngineFactory& factory,
829 const File& fileImpulseResponse,
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.
846 class ConvolutionEngineQueue final : public std::enable_shared_from_this<ConvolutionEngineQueue>
847{
848 public:
849 ConvolutionEngineQueue (BackgroundMessageQueue& queue,
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
910 private:
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;
928 ConvolutionEngineFactory factory;
929 BackgroundMessageQueue::IncomingCommand pendingCommand;
930};
931
932 class CrossoverMixer
933{
934 public:
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
1003 private:
1004 LinearSmoothedValue<float> smoother;
1005 AudioBuffer<float> smootherBuffer;
1006 AudioBuffer<float> mixBuffer;
1007};
1008
1009 using OptionalQueue = OptionalScopedPointer<ConvolutionMessageQueue>;
1010
1011 class Convolution::Impl
1012{
1013 public:
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
1101 private:
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;
1124 std::shared_ptr<ConvolutionEngineQueue> engineQueue;
1125 std::unique_ptr<MultichannelEngine> previousEngine, currentEngine;
1126 CrossoverMixer mixer;
1127};
1128
1129//==============================================================================
1130 void 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
1146 template <typename ProcessWet>
1147 void 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
1194 void Convolution::Mixer::reset() { dryBlock.clear(); }
1195
1196//==============================================================================
1197 Convolution::Convolution()
1198 : Convolution (Latency { 0 })
1199{}
1200
1201 Convolution::Convolution (ConvolutionMessageQueue& queue)
1202 : Convolution (Latency { 0 }, queue)
1203{}
1204
1205 Convolution::Convolution (const Latency& requiredLatency)
1206 : Convolution (requiredLatency,
1207 {},
1208 OptionalQueue { std::make_unique<ConvolutionMessageQueue>() })
1209{}
1210
1211 Convolution::Convolution (const NonUniform& nonUniform)
1212 : Convolution ({},
1213 nonUniform,
1214 OptionalQueue { std::make_unique<ConvolutionMessageQueue>() })
1215{}
1216
1217 Convolution::Convolution (const Latency& requiredLatency, ConvolutionMessageQueue& queue)
1218 : Convolution (requiredLatency, {}, OptionalQueue { queue })
1219{}
1220
1221 Convolution::Convolution (const NonUniform& nonUniform, ConvolutionMessageQueue& queue)
1222 : Convolution ({}, nonUniform, OptionalQueue { queue })
1223{}
1224
1225 Convolution::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
1233 void 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
1243 void Convolution::loadImpulseResponse (const File& fileImpulseResponse,
1244 Stereo stereo,
1245 Trim trim,
1246 size_t size,
1247 Normalise normalise)
1248{
1249 pimpl->loadImpulseResponse (fileImpulseResponse, stereo, trim, size, normalise);
1250}
1251
1252 void Convolution::loadImpulseResponse (AudioBuffer<float>&& buffer,
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
1261 void Convolution::prepare (const ProcessSpec& spec)
1262{
1263 mixer.prepare (spec);
1264 pimpl->prepare (spec);
1265 isActive = true;
1266}
1267
1268 void Convolution::reset() noexcept
1269{
1270 mixer.reset();
1271 pimpl->reset();
1272}
1273
1274 void 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
1290 int Convolution::getCurrentIRSize() const { return pimpl->getCurrentIRSize(); }
1291
1292 int Convolution::getLatency() const { return pimpl->getLatency(); }
1293
1294} // namespace juce::dsp
juce::AbstractFifo::ScopedReadWrite::forEach
void forEach(FunctionToApply &&func) const
Definition juce_AbstractFifo.h:237
juce::AbstractFifo::read
ScopedRead read(int numToRead) noexcept
Definition juce_AbstractFifo.cpp:163
juce::AbstractFifo::getFreeSpace
int getFreeSpace() const noexcept
Definition juce_AbstractFifo.cpp:32
juce::AbstractFifo::write
ScopedWrite write(int numToWrite) noexcept
Definition juce_AbstractFifo.cpp:164
juce::AbstractFifo::getNumReady
int getNumReady() const noexcept
Definition juce_AbstractFifo.cpp:34
juce::AudioBuffer
Definition juce_AudioSampleBuffer.h:34
juce::AudioBuffer::setSize
void setSize(int newNumChannels, int newNumSamples, bool keepExistingContent=false, bool clearExtraSpace=false, bool avoidReallocating=false)
Definition juce_AudioSampleBuffer.h:367
juce::AudioBuffer::getWritePointer
Type * getWritePointer(int channelNumber) noexcept
Definition juce_AudioSampleBuffer.h:291
juce::AudioBuffer::setSample
void setSample(int destChannel, int destSample, Type newValue) noexcept
Definition juce_AudioSampleBuffer.h:655
juce::AudioBuffer::getReadPointer
const Type * getReadPointer(int channelNumber) const noexcept
Definition juce_AudioSampleBuffer.h:253
juce::Decibels::decibelsToGain
static Type decibelsToGain(Type decibels, Type minusInfinityDb=Type(defaultMinusInfinitydB))
Definition juce_Decibels.h:42
juce::File
Definition juce_File.h:45
juce::OptionalScopedPointer< ConvolutionMessageQueue >
juce::OptionalScopedPointer::reset
void reset() noexcept
Definition juce_OptionalScopedPointer.h:129
juce::SpinLock::ScopedTryLockType
GenericScopedTryLock< SpinLock > ScopedTryLockType
Definition juce_SpinLock.h:79
juce::SpinLock::ScopedLockType
GenericScopedLock< SpinLock > ScopedLockType
Definition juce_SpinLock.h:73
juce::Thread::sleep
static void JUCE_CALLTYPE sleep(int milliseconds)
juce::Thread::Thread
Thread(const String &threadName, size_t threadStackSize=osDefaultStackSize)
Definition juce_Thread.cpp:26
juce::Thread::startThread
bool startThread()
Definition juce_Thread.cpp:145
juce::Thread::threadShouldExit
bool threadShouldExit() const
Definition juce_Thread.cpp:202
juce::Thread::stopThread
bool stopThread(int timeOutMilliseconds)
Definition juce_Thread.cpp:233
juce::dsp::AudioBlock
Definition juce_AudioBlock.h:68
juce::dsp::ConvolutionMessageQueue
Definition juce_Convolution.h:38
juce::dsp::ConvolutionMessageQueue::ConvolutionMessageQueue
ConvolutionMessageQueue()
Definition juce_Convolution.cpp:128
juce::dsp::Convolution
Definition juce_Convolution.h:103
juce::dsp::Convolution::getCurrentIRSize
int getCurrentIRSize() const
Definition juce_Convolution.cpp:1290
juce::dsp::Convolution::Convolution
Convolution()
Definition juce_Convolution.cpp:1197
juce::dsp::Convolution::getLatency
int getLatency() const
Definition juce_Convolution.cpp:1292
juce::dsp::Convolution::prepare
void prepare(const ProcessSpec &)
Definition juce_Convolution.cpp:1261
juce::dsp::Convolution::loadImpulseResponse
void loadImpulseResponse(const void *sourceData, size_t sourceDataSize, Stereo isStereo, Trim requiresTrimming, size_t size, Normalise requiresNormalisation=Normalise::yes)
Definition juce_Convolution.cpp:1233
juce::dsp::Convolution::reset
void reset() noexcept
Definition juce_Convolution.cpp:1268
juce::dsp::Convolution::Latency
Definition juce_Convolution.h:117
juce::dsp::Convolution::NonUniform
Definition juce_Convolution.h:132
juce::dsp::ProcessSpec
Definition juce_ProcessContext.h:36
juce::dsp::ProcessSpec::sampleRate
double sampleRate
Definition juce_ProcessContext.h:38
juce::dsp::ProcessSpec::maximumBlockSize
uint32 maximumBlockSize
Definition juce_ProcessContext.h:41