| 
							- /*
 -   ==============================================================================
 - 
 -    This file is part of the JUCE library.
 -    Copyright (c) 2020 - Raw Material Software Limited
 - 
 -    JUCE is an open source library subject to commercial or open-source
 -    licensing.
 - 
 -    By using JUCE, you agree to the terms of both the JUCE 6 End-User License
 -    Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
 - 
 -    End User License Agreement: www.juce.com/juce-6-licence
 -    Privacy Policy: www.juce.com/juce-privacy-policy
 - 
 -    Or: You may also use this code under the terms of the GPL v3 (see
 -    www.gnu.org/licenses).
 - 
 -    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
 -    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
 -    DISCLAIMED.
 - 
 -   ==============================================================================
 - */
 - 
 - namespace juce
 - {
 - namespace dsp
 - {
 - 
 - template <typename Element>
 - class Queue
 - {
 - public:
 -     explicit Queue (int size)
 -         : fifo (size), storage (static_cast<size_t> (size)) {}
 - 
 -     bool push (Element& element) noexcept
 -     {
 -         if (fifo.getFreeSpace() == 0)
 -             return false;
 - 
 -         const auto writer = fifo.write (1);
 - 
 -         if (writer.blockSize1 != 0)
 -             storage[static_cast<size_t> (writer.startIndex1)] = std::move (element);
 -         else if (writer.blockSize2 != 0)
 -             storage[static_cast<size_t> (writer.startIndex2)] = std::move (element);
 - 
 -         return true;
 -     }
 - 
 -     template <typename Fn>
 -     void pop (Fn&& fn) { popN (1, std::forward<Fn> (fn)); }
 - 
 -     template <typename Fn>
 -     void popAll (Fn&& fn) { popN (fifo.getNumReady(), std::forward<Fn> (fn)); }
 - 
 -     bool hasPendingMessages() const noexcept { return fifo.getNumReady() > 0; }
 - 
 - private:
 -     template <typename Fn>
 -     void popN (int n, Fn&& fn)
 -     {
 -         fifo.read (n).forEach ([&] (int index)
 -                                {
 -                                    fn (storage[static_cast<size_t> (index)]);
 -                                });
 -     }
 - 
 -     AbstractFifo fifo;
 -     std::vector<Element> storage;
 - };
 - 
 - class BackgroundMessageQueue  : private Thread
 - {
 - public:
 -     explicit BackgroundMessageQueue (int entries)
 -         : Thread ("Convolution background loader"), queue (entries)
 -     {}
 - 
 -     using IncomingCommand = FixedSizeFunction<400, void()>;
 - 
 -     // Push functions here, and they'll be called later on a background thread.
 -     // This function is wait-free.
 -     // This function is only safe to call from a single thread at a time.
 -     bool push (IncomingCommand& command) { return queue.push (command); }
 - 
 -     void popAll()
 -     {
 -         const ScopedLock lock (popMutex);
 -         queue.popAll ([] (IncomingCommand& command) { command(); command = nullptr; });
 -     }
 - 
 -     using Thread::startThread;
 -     using Thread::stopThread;
 - 
 - private:
 -     void run() override
 -     {
 -         while (! threadShouldExit())
 -         {
 -             const auto tryPop = [&]
 -             {
 -                 const ScopedLock lock (popMutex);
 - 
 -                 if (! queue.hasPendingMessages())
 -                     return false;
 - 
 -                 queue.pop ([] (IncomingCommand& command) { command(); command = nullptr;});
 -                 return true;
 -             };
 - 
 -             if (! tryPop())
 -                 sleep (10);
 -         }
 -     }
 - 
 -     CriticalSection popMutex;
 -     Queue<IncomingCommand> queue;
 - 
 -     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BackgroundMessageQueue)
 - };
 - 
 - struct ConvolutionMessageQueue::Impl  : public BackgroundMessageQueue
 - {
 -     using BackgroundMessageQueue::BackgroundMessageQueue;
 - };
 - 
 - ConvolutionMessageQueue::ConvolutionMessageQueue()
 -     : ConvolutionMessageQueue (1000)
 - {}
 - 
 - ConvolutionMessageQueue::ConvolutionMessageQueue (int entries)
 -     : pimpl (std::make_unique<Impl> (entries))
 - {
 -     pimpl->startThread();
 - }
 - 
 - ConvolutionMessageQueue::~ConvolutionMessageQueue() noexcept
 - {
 -     pimpl->stopThread (-1);
 - }
 - 
 - ConvolutionMessageQueue::ConvolutionMessageQueue (ConvolutionMessageQueue&&) noexcept = default;
 - ConvolutionMessageQueue& ConvolutionMessageQueue::operator= (ConvolutionMessageQueue&&) noexcept = default;
 - 
 - //==============================================================================
 - struct ConvolutionEngine
 - {
 -     ConvolutionEngine (const float* samples,
 -                        size_t numSamples,
 -                        size_t maxBlockSize)
 -         : blockSize ((size_t) nextPowerOfTwo ((int) maxBlockSize)),
 -           fftSize (blockSize > 128 ? 2 * blockSize : 4 * blockSize),
 -           fftObject (std::make_unique<FFT> (roundToInt (std::log2 (fftSize)))),
 -           numSegments (numSamples / (fftSize - blockSize) + 1u),
 -           numInputSegments ((blockSize > 128 ? numSegments : 3 * numSegments)),
 -           bufferInput      (1, static_cast<int> (fftSize)),
 -           bufferOutput     (1, static_cast<int> (fftSize * 2)),
 -           bufferTempOutput (1, static_cast<int> (fftSize * 2)),
 -           bufferOverlap    (1, static_cast<int> (fftSize))
 -     {
 -         bufferOutput.clear();
 - 
 -         auto updateSegmentsIfNecessary = [this] (size_t numSegmentsToUpdate,
 -                                                  std::vector<AudioBuffer<float>>& segments)
 -         {
 -             if (numSegmentsToUpdate == 0
 -                 || numSegmentsToUpdate != (size_t) segments.size()
 -                 || (size_t) segments[0].getNumSamples() != fftSize * 2)
 -             {
 -                 segments.clear();
 - 
 -                 for (size_t i = 0; i < numSegmentsToUpdate; ++i)
 -                     segments.push_back ({ 1, static_cast<int> (fftSize * 2) });
 -             }
 -         };
 - 
 -         updateSegmentsIfNecessary (numInputSegments, buffersInputSegments);
 -         updateSegmentsIfNecessary (numSegments,      buffersImpulseSegments);
 - 
 -         auto FFTTempObject = std::make_unique<FFT> (roundToInt (std::log2 (fftSize)));
 -         size_t currentPtr = 0;
 - 
 -         for (auto& buf : buffersImpulseSegments)
 -         {
 -             buf.clear();
 - 
 -             auto* impulseResponse = buf.getWritePointer (0);
 - 
 -             if (&buf == &buffersImpulseSegments.front())
 -                 impulseResponse[0] = 1.0f;
 - 
 -             FloatVectorOperations::copy (impulseResponse,
 -                                          samples + currentPtr,
 -                                          static_cast<int> (jmin (fftSize - blockSize, numSamples - currentPtr)));
 - 
 -             FFTTempObject->performRealOnlyForwardTransform (impulseResponse);
 -             prepareForConvolution (impulseResponse);
 - 
 -             currentPtr += (fftSize - blockSize);
 -         }
 - 
 -         reset();
 -     }
 - 
 -     void reset()
 -     {
 -         bufferInput.clear();
 -         bufferOverlap.clear();
 -         bufferTempOutput.clear();
 -         bufferOutput.clear();
 - 
 -         for (auto& buf : buffersInputSegments)
 -             buf.clear();
 - 
 -         currentSegment = 0;
 -         inputDataPos = 0;
 -     }
 - 
 -     void processSamples (const float* input, float* output, size_t numSamples)
 -     {
 -         // Overlap-add, zero latency convolution algorithm with uniform partitioning
 -         size_t numSamplesProcessed = 0;
 - 
 -         auto indexStep = numInputSegments / numSegments;
 - 
 -         auto* inputData      = bufferInput.getWritePointer (0);
 -         auto* outputTempData = bufferTempOutput.getWritePointer (0);
 -         auto* outputData     = bufferOutput.getWritePointer (0);
 -         auto* overlapData    = bufferOverlap.getWritePointer (0);
 - 
 -         while (numSamplesProcessed < numSamples)
 -         {
 -             const bool inputDataWasEmpty = (inputDataPos == 0);
 -             auto numSamplesToProcess = jmin (numSamples - numSamplesProcessed, blockSize - inputDataPos);
 - 
 -             FloatVectorOperations::copy (inputData + inputDataPos, input + numSamplesProcessed, static_cast<int> (numSamplesToProcess));
 - 
 -             auto* inputSegmentData = buffersInputSegments[currentSegment].getWritePointer (0);
 -             FloatVectorOperations::copy (inputSegmentData, inputData, static_cast<int> (fftSize));
 - 
 -             fftObject->performRealOnlyForwardTransform (inputSegmentData);
 -             prepareForConvolution (inputSegmentData);
 - 
 -             // Complex multiplication
 -             if (inputDataWasEmpty)
 -             {
 -                 FloatVectorOperations::fill (outputTempData, 0, static_cast<int> (fftSize + 1));
 - 
 -                 auto index = currentSegment;
 - 
 -                 for (size_t i = 1; i < numSegments; ++i)
 -                 {
 -                     index += indexStep;
 - 
 -                     if (index >= numInputSegments)
 -                         index -= numInputSegments;
 - 
 -                     convolutionProcessingAndAccumulate (buffersInputSegments[index].getWritePointer (0),
 -                                                         buffersImpulseSegments[i].getWritePointer (0),
 -                                                         outputTempData);
 -                 }
 -             }
 - 
 -             FloatVectorOperations::copy (outputData, outputTempData, static_cast<int> (fftSize + 1));
 - 
 -             convolutionProcessingAndAccumulate (inputSegmentData,
 -                                                 buffersImpulseSegments.front().getWritePointer (0),
 -                                                 outputData);
 - 
 -             updateSymmetricFrequencyDomainData (outputData);
 -             fftObject->performRealOnlyInverseTransform (outputData);
 - 
 -             // Add overlap
 -             FloatVectorOperations::add (&output[numSamplesProcessed], &outputData[inputDataPos], &overlapData[inputDataPos], (int) numSamplesToProcess);
 - 
 -             // Input buffer full => Next block
 -             inputDataPos += numSamplesToProcess;
 - 
 -             if (inputDataPos == blockSize)
 -             {
 -                 // Input buffer is empty again now
 -                 FloatVectorOperations::fill (inputData, 0.0f, static_cast<int> (fftSize));
 - 
 -                 inputDataPos = 0;
 - 
 -                 // Extra step for segSize > blockSize
 -                 FloatVectorOperations::add (&(outputData[blockSize]), &(overlapData[blockSize]), static_cast<int> (fftSize - 2 * blockSize));
 - 
 -                 // Save the overlap
 -                 FloatVectorOperations::copy (overlapData, &(outputData[blockSize]), static_cast<int> (fftSize - blockSize));
 - 
 -                 currentSegment = (currentSegment > 0) ? (currentSegment - 1) : (numInputSegments - 1);
 -             }
 - 
 -             numSamplesProcessed += numSamplesToProcess;
 -         }
 -     }
 - 
 -     void processSamplesWithAddedLatency (const float* input, float* output, size_t numSamples)
 -     {
 -         // Overlap-add, zero latency convolution algorithm with uniform partitioning
 -         size_t numSamplesProcessed = 0;
 - 
 -         auto indexStep = numInputSegments / numSegments;
 - 
 -         auto* inputData      = bufferInput.getWritePointer (0);
 -         auto* outputTempData = bufferTempOutput.getWritePointer (0);
 -         auto* outputData     = bufferOutput.getWritePointer (0);
 -         auto* overlapData    = bufferOverlap.getWritePointer (0);
 - 
 -         while (numSamplesProcessed < numSamples)
 -         {
 -             auto numSamplesToProcess = jmin (numSamples - numSamplesProcessed, blockSize - inputDataPos);
 - 
 -             FloatVectorOperations::copy (inputData + inputDataPos, input + numSamplesProcessed, static_cast<int> (numSamplesToProcess));
 - 
 -             FloatVectorOperations::copy (output + numSamplesProcessed, outputData + inputDataPos, static_cast<int> (numSamplesToProcess));
 - 
 -             numSamplesProcessed += numSamplesToProcess;
 -             inputDataPos += numSamplesToProcess;
 - 
 -             // processing itself when needed (with latency)
 -             if (inputDataPos == blockSize)
 -             {
 -                 // Copy input data in input segment
 -                 auto* inputSegmentData = buffersInputSegments[currentSegment].getWritePointer (0);
 -                 FloatVectorOperations::copy (inputSegmentData, inputData, static_cast<int> (fftSize));
 - 
 -                 fftObject->performRealOnlyForwardTransform (inputSegmentData);
 -                 prepareForConvolution (inputSegmentData);
 - 
 -                 // Complex multiplication
 -                 FloatVectorOperations::fill (outputTempData, 0, static_cast<int> (fftSize + 1));
 - 
 -                 auto index = currentSegment;
 - 
 -                 for (size_t i = 1; i < numSegments; ++i)
 -                 {
 -                     index += indexStep;
 - 
 -                     if (index >= numInputSegments)
 -                         index -= numInputSegments;
 - 
 -                     convolutionProcessingAndAccumulate (buffersInputSegments[index].getWritePointer (0),
 -                                                         buffersImpulseSegments[i].getWritePointer (0),
 -                                                         outputTempData);
 -                 }
 - 
 -                 FloatVectorOperations::copy (outputData, outputTempData, static_cast<int> (fftSize + 1));
 - 
 -                 convolutionProcessingAndAccumulate (inputSegmentData,
 -                                                     buffersImpulseSegments.front().getWritePointer (0),
 -                                                     outputData);
 - 
 -                 updateSymmetricFrequencyDomainData (outputData);
 -                 fftObject->performRealOnlyInverseTransform (outputData);
 - 
 -                 // Add overlap
 -                 FloatVectorOperations::add (outputData, overlapData, static_cast<int> (blockSize));
 - 
 -                 // Input buffer is empty again now
 -                 FloatVectorOperations::fill (inputData, 0.0f, static_cast<int> (fftSize));
 - 
 -                 // Extra step for segSize > blockSize
 -                 FloatVectorOperations::add (&(outputData[blockSize]), &(overlapData[blockSize]), static_cast<int> (fftSize - 2 * blockSize));
 - 
 -                 // Save the overlap
 -                 FloatVectorOperations::copy (overlapData, &(outputData[blockSize]), static_cast<int> (fftSize - blockSize));
 - 
 -                 currentSegment = (currentSegment > 0) ? (currentSegment - 1) : (numInputSegments - 1);
 - 
 -                 inputDataPos = 0;
 -             }
 -         }
 -     }
 - 
 -     // After each FFT, this function is called to allow convolution to be performed with only 4 SIMD functions calls.
 -     void prepareForConvolution (float *samples) noexcept
 -     {
 -         auto FFTSizeDiv2 = fftSize / 2;
 - 
 -         for (size_t i = 0; i < FFTSizeDiv2; i++)
 -             samples[i] = samples[i << 1];
 - 
 -         samples[FFTSizeDiv2] = 0;
 - 
 -         for (size_t i = 1; i < FFTSizeDiv2; i++)
 -             samples[i + FFTSizeDiv2] = -samples[((fftSize - i) << 1) + 1];
 -     }
 - 
 -     // Does the convolution operation itself only on half of the frequency domain samples.
 -     void convolutionProcessingAndAccumulate (const float *input, const float *impulse, float *output)
 -     {
 -         auto FFTSizeDiv2 = fftSize / 2;
 - 
 -         FloatVectorOperations::addWithMultiply      (output, input, impulse, static_cast<int> (FFTSizeDiv2));
 -         FloatVectorOperations::subtractWithMultiply (output, &(input[FFTSizeDiv2]), &(impulse[FFTSizeDiv2]), static_cast<int> (FFTSizeDiv2));
 - 
 -         FloatVectorOperations::addWithMultiply      (&(output[FFTSizeDiv2]), input, &(impulse[FFTSizeDiv2]), static_cast<int> (FFTSizeDiv2));
 -         FloatVectorOperations::addWithMultiply      (&(output[FFTSizeDiv2]), &(input[FFTSizeDiv2]), impulse, static_cast<int> (FFTSizeDiv2));
 - 
 -         output[fftSize] += input[fftSize] * impulse[fftSize];
 -     }
 - 
 -     // Undoes the re-organization of samples from the function prepareForConvolution.
 -     // Then takes the conjugate of the frequency domain first half of samples to fill the
 -     // second half, so that the inverse transform will return real samples in the time domain.
 -     void updateSymmetricFrequencyDomainData (float* samples) noexcept
 -     {
 -         auto FFTSizeDiv2 = fftSize / 2;
 - 
 -         for (size_t i = 1; i < FFTSizeDiv2; i++)
 -         {
 -             samples[(fftSize - i) << 1] = samples[i];
 -             samples[((fftSize - i) << 1) + 1] = -samples[FFTSizeDiv2 + i];
 -         }
 - 
 -         samples[1] = 0.f;
 - 
 -         for (size_t i = 1; i < FFTSizeDiv2; i++)
 -         {
 -             samples[i << 1] = samples[(fftSize - i) << 1];
 -             samples[(i << 1) + 1] = -samples[((fftSize - i) << 1) + 1];
 -         }
 -     }
 - 
 -     //==============================================================================
 -     const size_t blockSize;
 -     const size_t fftSize;
 -     const std::unique_ptr<FFT> fftObject;
 -     const size_t numSegments;
 -     const size_t numInputSegments;
 -     size_t currentSegment = 0, inputDataPos = 0;
 - 
 -     AudioBuffer<float> bufferInput, bufferOutput, bufferTempOutput, bufferOverlap;
 -     std::vector<AudioBuffer<float>> buffersInputSegments, buffersImpulseSegments;
 - };
 - 
 - //==============================================================================
 - class MultichannelEngine
 - {
 - public:
 -     MultichannelEngine (const AudioBuffer<float>& buf,
 -                         int maxBlockSize,
 -                         int maxBufferSize,
 -                         Convolution::NonUniform headSizeIn,
 -                         bool isZeroDelayIn)
 -         : tailBuffer (1, maxBlockSize),
 -           latency (isZeroDelayIn ? 0 : maxBufferSize),
 -           irSize (buf.getNumSamples()),
 -           blockSize (maxBlockSize),
 -           isZeroDelay (isZeroDelayIn)
 -     {
 -         constexpr auto numChannels = 2;
 - 
 -         const auto makeEngine = [&] (int channel, int offset, int length, uint32 thisBlockSize)
 -         {
 -             return std::make_unique<ConvolutionEngine> (buf.getReadPointer (jmin (buf.getNumChannels() - 1, channel), offset),
 -                                                         length,
 -                                                         static_cast<size_t> (thisBlockSize));
 -         };
 - 
 -         if (headSizeIn.headSizeInSamples == 0)
 -         {
 -             for (int i = 0; i < numChannels; ++i)
 -                 head.emplace_back (makeEngine (i, 0, buf.getNumSamples(), static_cast<uint32> (maxBufferSize)));
 -         }
 -         else
 -         {
 -             const auto size = jmin (buf.getNumSamples(), headSizeIn.headSizeInSamples);
 - 
 -             for (int i = 0; i < numChannels; ++i)
 -                 head.emplace_back (makeEngine (i, 0, size, static_cast<uint32> (maxBufferSize)));
 - 
 -             const auto tailBufferSize = static_cast<uint32> (headSizeIn.headSizeInSamples + (isZeroDelay ? 0 : maxBufferSize));
 - 
 -             if (size != buf.getNumSamples())
 -                 for (int i = 0; i < numChannels; ++i)
 -                     tail.emplace_back (makeEngine (i, size, buf.getNumSamples() - size, tailBufferSize));
 -         }
 -     }
 - 
 -     void reset()
 -     {
 -         for (const auto& e : head)
 -             e->reset();
 - 
 -         for (const auto& e : tail)
 -             e->reset();
 -     }
 - 
 -     void processSamples (const AudioBlock<const float>& input, AudioBlock<float>& output)
 -     {
 -         const auto numChannels = jmin (head.size(), input.getNumChannels(), output.getNumChannels());
 -         const auto numSamples  = jmin (input.getNumSamples(), output.getNumSamples());
 - 
 -         const AudioBlock<float> fullTailBlock (tailBuffer);
 -         const auto tailBlock = fullTailBlock.getSubBlock (0, (size_t) numSamples);
 - 
 -         const auto isUniform = tail.empty();
 - 
 -         for (size_t channel = 0; channel < numChannels; ++channel)
 -         {
 -             if (! isUniform)
 -                 tail[channel]->processSamplesWithAddedLatency (input.getChannelPointer (channel),
 -                                                                tailBlock.getChannelPointer (0),
 -                                                                numSamples);
 - 
 -             if (isZeroDelay)
 -                 head[channel]->processSamples (input.getChannelPointer (channel),
 -                                                output.getChannelPointer (channel),
 -                                                numSamples);
 -             else
 -                 head[channel]->processSamplesWithAddedLatency (input.getChannelPointer (channel),
 -                                                                output.getChannelPointer (channel),
 -                                                                numSamples);
 - 
 -             if (! isUniform)
 -                 output.getSingleChannelBlock (channel) += tailBlock;
 -         }
 - 
 -         const auto numOutputChannels = output.getNumChannels();
 - 
 -         for (auto i = numChannels; i < numOutputChannels; ++i)
 -             output.getSingleChannelBlock (i).copyFrom (output.getSingleChannelBlock (0));
 -     }
 - 
 -     int getIRSize() const noexcept     { return irSize; }
 -     int getLatency() const noexcept    { return latency; }
 -     int getBlockSize() const noexcept  { return blockSize; }
 - 
 - private:
 -     std::vector<std::unique_ptr<ConvolutionEngine>> head, tail;
 -     AudioBuffer<float> tailBuffer;
 - 
 -     const int latency;
 -     const int irSize;
 -     const int blockSize;
 -     const bool isZeroDelay;
 - };
 - 
 - static AudioBuffer<float> fixNumChannels (const AudioBuffer<float>& buf, Convolution::Stereo stereo)
 - {
 -     const auto numChannels = jmin (buf.getNumChannels(), stereo == Convolution::Stereo::yes ? 2 : 1);
 -     const auto numSamples = buf.getNumSamples();
 - 
 -     AudioBuffer<float> result (numChannels, buf.getNumSamples());
 - 
 -     for (auto channel = 0; channel != numChannels; ++channel)
 -         result.copyFrom (channel, 0, buf.getReadPointer (channel), numSamples);
 - 
 -     if (result.getNumSamples() == 0 || result.getNumChannels() == 0)
 -     {
 -         result.setSize (1, 1);
 -         result.setSample (0, 0, 1.0f);
 -     }
 - 
 -     return result;
 - }
 - 
 - static AudioBuffer<float> trimImpulseResponse (const AudioBuffer<float>& buf)
 - {
 -     const auto thresholdTrim = Decibels::decibelsToGain (-80.0f);
 - 
 -     const auto numChannels = buf.getNumChannels();
 -     const auto numSamples = buf.getNumSamples();
 - 
 -     std::ptrdiff_t offsetBegin = numSamples;
 -     std::ptrdiff_t offsetEnd   = numSamples;
 - 
 -     for (auto channel = 0; channel < numChannels; ++channel)
 -     {
 -         const auto indexAboveThreshold = [&] (auto begin, auto end)
 -         {
 -             return std::distance (begin, std::find_if (begin, end, [&] (float sample)
 -             {
 -                 return std::abs (sample) >= thresholdTrim;
 -             }));
 -         };
 - 
 -         const auto channelBegin = buf.getReadPointer (channel);
 -         const auto channelEnd = channelBegin + numSamples;
 -         const auto itStart = indexAboveThreshold (channelBegin, channelEnd);
 -         const auto itEnd = indexAboveThreshold (std::make_reverse_iterator (channelEnd),
 -                                                 std::make_reverse_iterator (channelBegin));
 - 
 -         offsetBegin = jmin (offsetBegin, itStart);
 -         offsetEnd   = jmin (offsetEnd,   itEnd);
 -     }
 - 
 -     if (offsetBegin == numSamples)
 -     {
 -         auto result = AudioBuffer<float> (numChannels, 1);
 -         result.clear();
 -         return result;
 -     }
 - 
 -     const auto newLength = jmax (1, numSamples - static_cast<int> (offsetBegin + offsetEnd));
 - 
 -     AudioBuffer<float> result (numChannels, newLength);
 - 
 -     for (auto channel = 0; channel < numChannels; ++channel)
 -     {
 -         result.copyFrom (channel,
 -                          0,
 -                          buf.getReadPointer (channel, static_cast<int> (offsetBegin)),
 -                          result.getNumSamples());
 -     }
 - 
 -     return result;
 - }
 - 
 - static float calculateNormalisationFactor (float sumSquaredMagnitude)
 - {
 -     if (sumSquaredMagnitude < 1e-8f)
 -         return 1.0f;
 - 
 -     return 0.125f / std::sqrt (sumSquaredMagnitude);
 - }
 - 
 - static void normaliseImpulseResponse (AudioBuffer<float>& buf)
 - {
 -     const auto numChannels = buf.getNumChannels();
 -     const auto numSamples  = buf.getNumSamples();
 -     const auto channelPtrs = buf.getArrayOfWritePointers();
 - 
 -     const auto maxSumSquaredMag = std::accumulate (channelPtrs, channelPtrs + numChannels, 0.0f, [numSamples] (auto max, auto* channel)
 -     {
 -         return jmax (max, std::accumulate (channel, channel + numSamples, 0.0f, [] (auto sum, auto samp)
 -         {
 -             return sum + (samp * samp);
 -         }));
 -     });
 - 
 -     const auto normalisationFactor = calculateNormalisationFactor (maxSumSquaredMag);
 - 
 -     std::for_each (channelPtrs, channelPtrs + numChannels, [normalisationFactor, numSamples] (auto* channel)
 -     {
 -         FloatVectorOperations::multiply (channel, normalisationFactor, numSamples);
 -     });
 - }
 - 
 - static AudioBuffer<float> resampleImpulseResponse (const AudioBuffer<float>& buf,
 -                                                    const double srcSampleRate,
 -                                                    const double destSampleRate)
 - {
 -     if (srcSampleRate == destSampleRate)
 -         return buf;
 - 
 -     const auto factorReading = srcSampleRate / destSampleRate;
 - 
 -     AudioBuffer<float> original = buf;
 -     MemoryAudioSource memorySource (original, false);
 -     ResamplingAudioSource resamplingSource (&memorySource, false, buf.getNumChannels());
 - 
 -     const auto finalSize = roundToInt (jmax (1.0, buf.getNumSamples() / factorReading));
 -     resamplingSource.setResamplingRatio (factorReading);
 -     resamplingSource.prepareToPlay (finalSize, srcSampleRate);
 - 
 -     AudioBuffer<float> result (buf.getNumChannels(), finalSize);
 -     resamplingSource.getNextAudioBlock ({ &result, 0, result.getNumSamples() });
 - 
 -     return result;
 - }
 - 
 - //==============================================================================
 - template <typename Element>
 - class TryLockedPtr
 - {
 - public:
 -     void set (std::unique_ptr<Element> p)
 -     {
 -         const SpinLock::ScopedLockType lock (mutex);
 -         ptr = std::move (p);
 -     }
 - 
 -     std::unique_ptr<MultichannelEngine> get()
 -     {
 -         const SpinLock::ScopedTryLockType lock (mutex);
 -         return lock.isLocked() ? std::move (ptr) : nullptr;
 -     }
 - 
 - private:
 -     std::unique_ptr<Element> ptr;
 -     SpinLock mutex;
 - };
 - 
 - struct BufferWithSampleRate
 - {
 -     BufferWithSampleRate() = default;
 - 
 -     BufferWithSampleRate (AudioBuffer<float>&& bufferIn, double sampleRateIn)
 -         : buffer (std::move (bufferIn)), sampleRate (sampleRateIn) {}
 - 
 -     AudioBuffer<float> buffer;
 -     double sampleRate = 0.0;
 - };
 - 
 - static BufferWithSampleRate loadStreamToBuffer (std::unique_ptr<InputStream> stream, size_t maxLength)
 - {
 -     AudioFormatManager manager;
 -     manager.registerBasicFormats();
 -     std::unique_ptr<AudioFormatReader> formatReader (manager.createReaderFor (std::move (stream)));
 - 
 -     if (formatReader == nullptr)
 -         return {};
 - 
 -     const auto fileLength = static_cast<size_t> (formatReader->lengthInSamples);
 -     const auto lengthToLoad = maxLength == 0 ? fileLength : jmin (maxLength, fileLength);
 - 
 -     BufferWithSampleRate result { { jlimit (1, 2, static_cast<int> (formatReader->numChannels)),
 -                                     static_cast<int> (lengthToLoad) },
 -                                   formatReader->sampleRate };
 - 
 -     formatReader->read (result.buffer.getArrayOfWritePointers(),
 -                         result.buffer.getNumChannels(),
 -                         0,
 -                         result.buffer.getNumSamples());
 - 
 -     return result;
 - }
 - 
 - // This class caches the data required to build a new convolution engine
 - // (in particular, impulse response data and a ProcessSpec).
 - // Calls to `setProcessSpec` and `setImpulseResponse` construct a
 - // new engine, which can be retrieved by calling `getEngine`.
 - class ConvolutionEngineFactory
 - {
 - public:
 -     ConvolutionEngineFactory (Convolution::Latency requiredLatency,
 -                               Convolution::NonUniform requiredHeadSize)
 -         : latency  { (requiredLatency.latencyInSamples   <= 0) ? 0 : jmax (64, nextPowerOfTwo (requiredLatency.latencyInSamples)) },
 -           headSize { (requiredHeadSize.headSizeInSamples <= 0) ? 0 : jmax (64, nextPowerOfTwo (requiredHeadSize.headSizeInSamples)) },
 -           shouldBeZeroLatency (requiredLatency.latencyInSamples == 0)
 -     {}
 - 
 -     // It is safe to call this method simultaneously with other public
 -     // member functions.
 -     void setProcessSpec (const ProcessSpec& spec)
 -     {
 -         const std::lock_guard<std::mutex> lock (mutex);
 -         processSpec = spec;
 - 
 -         engine.set (makeEngine());
 -     }
 - 
 -     // It is safe to call this method simultaneously with other public
 -     // member functions.
 -     void setImpulseResponse (BufferWithSampleRate&& buf,
 -                              Convolution::Stereo stereo,
 -                              Convolution::Trim trim,
 -                              Convolution::Normalise normalise)
 -     {
 -         const std::lock_guard<std::mutex> lock (mutex);
 -         wantsNormalise = normalise;
 -         originalSampleRate = buf.sampleRate;
 - 
 -         impulseResponse = [&]
 -         {
 -             auto corrected = fixNumChannels (buf.buffer, stereo);
 -             return trim == Convolution::Trim::yes ? trimImpulseResponse (corrected) : corrected;
 -         }();
 - 
 -         engine.set (makeEngine());
 -     }
 - 
 -     // Returns the most recently-created engine, or nullptr
 -     // if there is no pending engine, or if the engine is currently
 -     // being updated by one of the setter methods.
 -     // It is safe to call this simultaneously with other public
 -     // member functions.
 -     std::unique_ptr<MultichannelEngine> getEngine() { return engine.get(); }
 - 
 - private:
 -     std::unique_ptr<MultichannelEngine> makeEngine()
 -     {
 -         auto resampled = resampleImpulseResponse (impulseResponse, originalSampleRate, processSpec.sampleRate);
 - 
 -         if (wantsNormalise == Convolution::Normalise::yes)
 -             normaliseImpulseResponse (resampled);
 -         else
 -             resampled.applyGain ((float) (originalSampleRate / processSpec.sampleRate));
 - 
 -         const auto currentLatency = jmax (processSpec.maximumBlockSize, (uint32) latency.latencyInSamples);
 -         const auto maxBufferSize = shouldBeZeroLatency ? static_cast<int> (processSpec.maximumBlockSize)
 -                                                        : nextPowerOfTwo (static_cast<int> (currentLatency));
 - 
 -         return std::make_unique<MultichannelEngine> (resampled,
 -                                                      processSpec.maximumBlockSize,
 -                                                      maxBufferSize,
 -                                                      headSize,
 -                                                      shouldBeZeroLatency);
 -     }
 - 
 -     static AudioBuffer<float> makeImpulseBuffer()
 -     {
 -         AudioBuffer<float> result (1, 1);
 -         result.setSample (0, 0, 1.0f);
 -         return result;
 -     }
 - 
 -     ProcessSpec processSpec { 44100.0, 128, 2 };
 -     AudioBuffer<float> impulseResponse = makeImpulseBuffer();
 -     double originalSampleRate = processSpec.sampleRate;
 -     Convolution::Normalise wantsNormalise = Convolution::Normalise::no;
 -     const Convolution::Latency latency;
 -     const Convolution::NonUniform headSize;
 -     const bool shouldBeZeroLatency;
 - 
 -     TryLockedPtr<MultichannelEngine> engine;
 - 
 -     mutable std::mutex mutex;
 - };
 - 
 - static void setImpulseResponse (ConvolutionEngineFactory& factory,
 -                                 const void* sourceData,
 -                                 size_t sourceDataSize,
 -                                 Convolution::Stereo stereo,
 -                                 Convolution::Trim trim,
 -                                 size_t size,
 -                                 Convolution::Normalise normalise)
 - {
 -     factory.setImpulseResponse (loadStreamToBuffer (std::make_unique<MemoryInputStream> (sourceData, sourceDataSize, false), size),
 -                                 stereo, trim, normalise);
 - }
 - 
 - static void setImpulseResponse (ConvolutionEngineFactory& factory,
 -                                 const File& fileImpulseResponse,
 -                                 Convolution::Stereo stereo,
 -                                 Convolution::Trim trim,
 -                                 size_t size,
 -                                 Convolution::Normalise normalise)
 - {
 -     factory.setImpulseResponse (loadStreamToBuffer (std::make_unique<FileInputStream> (fileImpulseResponse), size),
 -                                 stereo, trim, normalise);
 - }
 - 
 - // This class acts as a destination for convolution engines which are loaded on
 - // a background thread.
 - 
 - // Deriving from `enable_shared_from_this` allows us to capture a reference to
 - // this object when adding commands to the background message queue.
 - // That way, we can avoid dangling references in the background thread in the case
 - // that a Convolution instance is deleted before the background message queue.
 - class ConvolutionEngineQueue  : public std::enable_shared_from_this<ConvolutionEngineQueue>
 - {
 - public:
 -     ConvolutionEngineQueue (BackgroundMessageQueue& queue,
 -                             Convolution::Latency latencyIn,
 -                             Convolution::NonUniform headSizeIn)
 -         : messageQueue (queue), factory (latencyIn, headSizeIn) {}
 - 
 -     void loadImpulseResponse (AudioBuffer<float>&& buffer,
 -                               double sr,
 -                               Convolution::Stereo stereo,
 -                               Convolution::Trim trim,
 -                               Convolution::Normalise normalise)
 -     {
 -         callLater ([b = std::move (buffer), sr, stereo, trim, normalise] (ConvolutionEngineFactory& f) mutable
 -         {
 -             f.setImpulseResponse ({ std::move (b), sr }, stereo, trim, normalise);
 -         });
 -     }
 - 
 -     void loadImpulseResponse (const void* sourceData,
 -                               size_t sourceDataSize,
 -                               Convolution::Stereo stereo,
 -                               Convolution::Trim trim,
 -                               size_t size,
 -                               Convolution::Normalise normalise)
 -     {
 -         callLater ([sourceData, sourceDataSize, stereo, trim, size, normalise] (ConvolutionEngineFactory& f) mutable
 -         {
 -             setImpulseResponse (f, sourceData, sourceDataSize, stereo, trim, size, normalise);
 -         });
 -     }
 - 
 -     void loadImpulseResponse (const File& fileImpulseResponse,
 -                               Convolution::Stereo stereo,
 -                               Convolution::Trim trim,
 -                               size_t size,
 -                               Convolution::Normalise normalise)
 -     {
 -         callLater ([fileImpulseResponse, stereo, trim, size, normalise] (ConvolutionEngineFactory& f) mutable
 -         {
 -             setImpulseResponse (f, fileImpulseResponse, stereo, trim, size, normalise);
 -         });
 -     }
 - 
 -     void prepare (const ProcessSpec& spec)
 -     {
 -         factory.setProcessSpec (spec);
 -     }
 - 
 -     // Call this regularly to try to resend any pending message.
 -     // This allows us to always apply the most recently requested
 -     // state (eventually), even if the message queue fills up.
 -     void postPendingCommand()
 -     {
 -         if (pendingCommand == nullptr)
 -             return;
 - 
 -         if (messageQueue.push (pendingCommand))
 -             pendingCommand = nullptr;
 -     }
 - 
 -     std::unique_ptr<MultichannelEngine> getEngine() { return factory.getEngine(); }
 - 
 - private:
 -     template <typename Fn>
 -     void callLater (Fn&& fn)
 -     {
 -         // If there was already a pending command (because the queue was full) we'll end up deleting it here.
 -         // Not much we can do about that!
 -         pendingCommand = [weak = weakFromThis(), callback = std::forward<Fn> (fn)]() mutable
 -         {
 -             if (auto t = weak.lock())
 -                 callback (t->factory);
 -         };
 - 
 -         postPendingCommand();
 -     }
 - 
 -     std::weak_ptr<ConvolutionEngineQueue> weakFromThis() { return shared_from_this(); }
 - 
 -     BackgroundMessageQueue& messageQueue;
 -     ConvolutionEngineFactory factory;
 -     BackgroundMessageQueue::IncomingCommand pendingCommand;
 - };
 - 
 - class CrossoverMixer
 - {
 - public:
 -     void reset()
 -     {
 -         smoother.setCurrentAndTargetValue (1.0f);
 -     }
 - 
 -     void prepare (const ProcessSpec& spec)
 -     {
 -         smoother.reset (spec.sampleRate, 0.05);
 -         smootherBuffer.setSize (1, static_cast<int> (spec.maximumBlockSize));
 -         mixBuffer.setSize (static_cast<int> (spec.numChannels), static_cast<int> (spec.maximumBlockSize));
 -         reset();
 -     }
 - 
 -     template <typename ProcessCurrent, typename ProcessPrevious, typename NotifyDone>
 -     void processSamples (const AudioBlock<const float>& input,
 -                          AudioBlock<float>& output,
 -                          ProcessCurrent&& current,
 -                          ProcessPrevious&& previous,
 -                          NotifyDone&& notifyDone)
 -     {
 -         if (smoother.isSmoothing())
 -         {
 -             const auto numSamples = static_cast<int> (input.getNumSamples());
 - 
 -             for (auto sample = 0; sample != numSamples; ++sample)
 -                 smootherBuffer.setSample (0, sample, smoother.getNextValue());
 - 
 -             AudioBlock<float> mixBlock (mixBuffer);
 -             mixBlock.clear();
 -             previous (input, mixBlock);
 - 
 -             for (size_t channel = 0; channel != output.getNumChannels(); ++channel)
 -             {
 -                 FloatVectorOperations::multiply (mixBlock.getChannelPointer (channel),
 -                                                  smootherBuffer.getReadPointer (0),
 -                                                  numSamples);
 -             }
 - 
 -             FloatVectorOperations::multiply (smootherBuffer.getWritePointer (0), -1.0f, numSamples);
 -             FloatVectorOperations::add (smootherBuffer.getWritePointer (0), 1.0f, numSamples);
 - 
 -             current (input, output);
 - 
 -             for (size_t channel = 0; channel != output.getNumChannels(); ++channel)
 -             {
 -                 FloatVectorOperations::multiply (output.getChannelPointer (channel),
 -                                                  smootherBuffer.getReadPointer (0),
 -                                                  numSamples);
 -                 FloatVectorOperations::add (output.getChannelPointer (channel),
 -                                             mixBlock.getChannelPointer (channel),
 -                                             numSamples);
 -             }
 - 
 -             if (! smoother.isSmoothing())
 -                 notifyDone();
 -         }
 -         else
 -         {
 -             current (input, output);
 -         }
 -     }
 - 
 -     void beginTransition()
 -     {
 -         smoother.setCurrentAndTargetValue (1.0f);
 -         smoother.setTargetValue (0.0f);
 -     }
 - 
 - private:
 -     LinearSmoothedValue<float> smoother;
 -     AudioBuffer<float> smootherBuffer;
 -     AudioBuffer<float> mixBuffer;
 - };
 - 
 - using OptionalQueue = OptionalScopedPointer<ConvolutionMessageQueue>;
 - 
 - class Convolution::Impl
 - {
 - public:
 -     Impl (Latency requiredLatency,
 -           NonUniform requiredHeadSize,
 -           OptionalQueue&& queue)
 -         : messageQueue (std::move (queue)),
 -           engineQueue (std::make_shared<ConvolutionEngineQueue> (*messageQueue->pimpl,
 -                                                                  requiredLatency,
 -                                                                  requiredHeadSize))
 -     {}
 - 
 -     void reset()
 -     {
 -         mixer.reset();
 - 
 -         if (currentEngine != nullptr)
 -             currentEngine->reset();
 - 
 -         destroyPreviousEngine();
 -     }
 - 
 -     void prepare (const ProcessSpec& spec)
 -     {
 -         messageQueue->pimpl->popAll();
 -         mixer.prepare (spec);
 -         engineQueue->prepare (spec);
 - 
 -         if (auto newEngine = engineQueue->getEngine())
 -             currentEngine = std::move (newEngine);
 - 
 -         previousEngine = nullptr;
 -         jassert (currentEngine != nullptr);
 -     }
 - 
 -     void processSamples (const AudioBlock<const float>& input, AudioBlock<float>& output)
 -     {
 -         engineQueue->postPendingCommand();
 - 
 -         if (previousEngine == nullptr)
 -             installPendingEngine();
 - 
 -         mixer.processSamples (input,
 -                               output,
 -                               [this] (const AudioBlock<const float>& in, AudioBlock<float>& out)
 -                               {
 -                                   currentEngine->processSamples (in, out);
 -                               },
 -                               [this] (const AudioBlock<const float>& in, AudioBlock<float>& out)
 -                               {
 -                                   if (previousEngine != nullptr)
 -                                       previousEngine->processSamples (in, out);
 -                                   else
 -                                       out.copyFrom (in);
 -                               },
 -                               [this] { destroyPreviousEngine(); });
 -     }
 - 
 -     int getCurrentIRSize() const { return currentEngine != nullptr ? currentEngine->getIRSize() : 0; }
 - 
 -     int getLatency() const { return currentEngine != nullptr ? currentEngine->getLatency() : 0; }
 - 
 -     void loadImpulseResponse (AudioBuffer<float>&& buffer,
 -                               double originalSampleRate,
 -                               Stereo stereo,
 -                               Trim trim,
 -                               Normalise normalise)
 -     {
 -         engineQueue->loadImpulseResponse (std::move (buffer), originalSampleRate, stereo, trim, normalise);
 -     }
 - 
 -     void loadImpulseResponse (const void* sourceData,
 -                               size_t sourceDataSize,
 -                               Stereo stereo,
 -                               Trim trim,
 -                               size_t size,
 -                               Normalise normalise)
 -     {
 -         engineQueue->loadImpulseResponse (sourceData, sourceDataSize, stereo, trim, size, normalise);
 -     }
 - 
 -     void loadImpulseResponse (const File& fileImpulseResponse,
 -                               Stereo stereo,
 -                               Trim trim,
 -                               size_t size,
 -                               Normalise normalise)
 -     {
 -         engineQueue->loadImpulseResponse (fileImpulseResponse, stereo, trim, size, normalise);
 -     }
 - 
 - private:
 -     void destroyPreviousEngine()
 -     {
 -         // If the queue is full, we'll destroy this straight away
 -         BackgroundMessageQueue::IncomingCommand command = [p = std::move (previousEngine)]() mutable { p = nullptr; };
 -         messageQueue->pimpl->push (command);
 -     }
 - 
 -     void installNewEngine (std::unique_ptr<MultichannelEngine> newEngine)
 -     {
 -         destroyPreviousEngine();
 -         previousEngine = std::move (currentEngine);
 -         currentEngine = std::move (newEngine);
 -         mixer.beginTransition();
 -     }
 - 
 -     void installPendingEngine()
 -     {
 -         if (auto newEngine = engineQueue->getEngine())
 -             installNewEngine (std::move (newEngine));
 -     }
 - 
 -     OptionalQueue messageQueue;
 -     std::shared_ptr<ConvolutionEngineQueue> engineQueue;
 -     std::unique_ptr<MultichannelEngine> previousEngine, currentEngine;
 -     CrossoverMixer mixer;
 - };
 - 
 - //==============================================================================
 - void Convolution::Mixer::prepare (const ProcessSpec& spec)
 - {
 -     for (auto& dry : volumeDry)
 -         dry.reset (spec.sampleRate, 0.05);
 - 
 -     for (auto& wet : volumeWet)
 -         wet.reset (spec.sampleRate, 0.05);
 - 
 -     sampleRate = spec.sampleRate;
 - 
 -     dryBlock = AudioBlock<float> (dryBlockStorage,
 -                                   jmin (spec.numChannels, 2u),
 -                                   spec.maximumBlockSize);
 - 
 - }
 - 
 - template <typename ProcessWet>
 - void Convolution::Mixer::processSamples (const AudioBlock<const float>& input,
 -                                          AudioBlock<float>& output,
 -                                          bool isBypassed,
 -                                          ProcessWet&& processWet) noexcept
 - {
 -     const auto numChannels = jmin (input.getNumChannels(), volumeDry.size());
 -     const auto numSamples  = jmin (input.getNumSamples(), output.getNumSamples());
 - 
 -     auto dry = dryBlock.getSubsetChannelBlock (0, numChannels);
 - 
 -     if (volumeDry[0].isSmoothing())
 -     {
 -         dry.copyFrom (input);
 - 
 -         for (size_t channel = 0; channel < numChannels; ++channel)
 -             volumeDry[channel].applyGain (dry.getChannelPointer (channel), (int) numSamples);
 - 
 -         processWet (input, output);
 - 
 -         for (size_t channel = 0; channel < numChannels; ++channel)
 -             volumeWet[channel].applyGain (output.getChannelPointer (channel), (int) numSamples);
 - 
 -         output += dry;
 -     }
 -     else
 -     {
 -         if (! currentIsBypassed)
 -             processWet (input, output);
 - 
 -         if (isBypassed != currentIsBypassed)
 -         {
 -             currentIsBypassed = isBypassed;
 - 
 -             for (size_t channel = 0; channel < numChannels; ++channel)
 -             {
 -                 volumeDry[channel].setTargetValue (isBypassed ? 0.0f : 1.0f);
 -                 volumeDry[channel].reset (sampleRate, 0.05);
 -                 volumeDry[channel].setTargetValue (isBypassed ? 1.0f : 0.0f);
 - 
 -                 volumeWet[channel].setTargetValue (isBypassed ? 1.0f : 0.0f);
 -                 volumeWet[channel].reset (sampleRate, 0.05);
 -                 volumeWet[channel].setTargetValue (isBypassed ? 0.0f : 1.0f);
 -             }
 -         }
 -     }
 - }
 - 
 - void Convolution::Mixer::reset() { dryBlock.clear(); }
 - 
 - //==============================================================================
 - Convolution::Convolution()
 -     : Convolution (Latency { 0 })
 - {}
 - 
 - Convolution::Convolution (ConvolutionMessageQueue& queue)
 -     : Convolution (Latency { 0 }, queue)
 - {}
 - 
 - Convolution::Convolution (const Latency& requiredLatency)
 -     : Convolution (requiredLatency,
 -                    {},
 -                    OptionalQueue { std::make_unique<ConvolutionMessageQueue>() })
 - {}
 - 
 - Convolution::Convolution (const NonUniform& nonUniform)
 -     : Convolution ({},
 -                    nonUniform,
 -                    OptionalQueue { std::make_unique<ConvolutionMessageQueue>() })
 - {}
 - 
 - Convolution::Convolution (const Latency& requiredLatency, ConvolutionMessageQueue& queue)
 -     : Convolution (requiredLatency, {}, OptionalQueue { queue })
 - {}
 - 
 - Convolution::Convolution (const NonUniform& nonUniform, ConvolutionMessageQueue& queue)
 -     : Convolution ({}, nonUniform, OptionalQueue { queue })
 - {}
 - 
 - Convolution::Convolution (const Latency& latency,
 -                           const NonUniform& nonUniform,
 -                           OptionalQueue&& queue)
 -     : pimpl (std::make_unique<Impl> (latency, nonUniform, std::move (queue)))
 - {}
 - 
 - Convolution::~Convolution() noexcept = default;
 - 
 - void Convolution::loadImpulseResponse (const void* sourceData,
 -                                        size_t sourceDataSize,
 -                                        Stereo stereo,
 -                                        Trim trim,
 -                                        size_t size,
 -                                        Normalise normalise)
 - {
 -     pimpl->loadImpulseResponse (sourceData, sourceDataSize, stereo, trim, size, normalise);
 - }
 - 
 - void Convolution::loadImpulseResponse (const File& fileImpulseResponse,
 -                                        Stereo stereo,
 -                                        Trim trim,
 -                                        size_t size,
 -                                        Normalise normalise)
 - {
 -     pimpl->loadImpulseResponse (fileImpulseResponse, stereo, trim, size, normalise);
 - }
 - 
 - void Convolution::loadImpulseResponse (AudioBuffer<float>&& buffer,
 -                                        double originalSampleRate,
 -                                        Stereo stereo,
 -                                        Trim trim,
 -                                        Normalise normalise)
 - {
 -     pimpl->loadImpulseResponse (std::move (buffer), originalSampleRate, stereo, trim, normalise);
 - }
 - 
 - void Convolution::prepare (const ProcessSpec& spec)
 - {
 -     mixer.prepare (spec);
 -     pimpl->prepare (spec);
 -     isActive = true;
 - }
 - 
 - void Convolution::reset() noexcept
 - {
 -     mixer.reset();
 -     pimpl->reset();
 - }
 - 
 - void Convolution::processSamples (const AudioBlock<const float>& input,
 -                                   AudioBlock<float>& output,
 -                                   bool isBypassed) noexcept
 - {
 -     if (! isActive)
 -         return;
 - 
 -     jassert (input.getNumChannels() == output.getNumChannels());
 -     jassert (isPositiveAndBelow (input.getNumChannels(), static_cast<size_t> (3))); // only mono and stereo is supported
 - 
 -     mixer.processSamples (input, output, isBypassed, [this] (const auto& in, auto& out)
 -     {
 -         pimpl->processSamples (in, out);
 -     });
 - }
 - 
 - int Convolution::getCurrentIRSize() const { return pimpl->getCurrentIRSize(); }
 - 
 - int Convolution::getLatency() const { return pimpl->getLatency(); }
 - 
 - } // namespace dsp
 - } // namespace juce
 
 
  |