1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2017 - ROLI Ltd.
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 5 End-User License
11    Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
12    27th April 2017).
13 
14    End User License Agreement: www.juce.com/juce-5-licence
15    Privacy Policy: www.juce.com/juce-5-privacy-policy
16 
17    Or: You may also use this code under the terms of the GPL v3 (see
18    www.gnu.org/licenses).
19 
20    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
21    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
22    DISCLAIMED.
23 
24   ==============================================================================
25 */
26 
27 namespace juce
28 {
29 namespace dsp
30 {
31 
32 /** This class is the convolution engine itself, processing only one channel at
33     a time of input signal.
34 */
35 struct ConvolutionEngine
36 {
37     ConvolutionEngine() = default;
38 
39     //==============================================================================
40     struct ProcessingInformation
41     {
42         enum class SourceType
43         {
44             sourceBinaryData,
45             sourceAudioFile,
46             sourceAudioBuffer,
47             sourceNone
48         };
49 
50         SourceType sourceType = SourceType::sourceNone;
51 
52         const void* sourceData;
53         int sourceDataSize;
54         File fileImpulseResponse;
55 
56         double originalSampleRate;
57         int originalSize = 0;
58         int originalNumChannels = 1;
59 
60         AudioBuffer<float>* buffer;
61 
62         bool wantsStereo = true;
63         bool wantsTrimming = true;
64         bool wantsNormalisation = true;
65         int64 wantedSize = 0;
66         int finalSize = 0;
67 
68         double sampleRate = 0;
69         size_t maximumBufferSize = 0;
70     };
71 
72     //==============================================================================
resetjuce::dsp::ConvolutionEngine73     void reset()
74     {
75         bufferInput.clear();
76         bufferOverlap.clear();
77         bufferTempOutput.clear();
78 
79         for (auto i = 0; i < buffersInputSegments.size(); ++i)
80             buffersInputSegments.getReference (i).clear();
81 
82         currentSegment = 0;
83         inputDataPos = 0;
84     }
85 
86     /** Initalize all the states and objects to perform the convolution. */
initializeConvolutionEnginejuce::dsp::ConvolutionEngine87     void initializeConvolutionEngine (ProcessingInformation& info, int channel)
88     {
89         blockSize = (size_t) nextPowerOfTwo ((int) info.maximumBufferSize);
90 
91         FFTSize = blockSize > 128 ? 2 * blockSize
92                                   : 4 * blockSize;
93 
94         numSegments = ((size_t) info.finalSize) / (FFTSize - blockSize) + 1u;
95 
96         numInputSegments = (blockSize > 128 ? numSegments : 3 * numSegments);
97 
98         FFTobject.reset (new FFT (roundToInt (std::log2 (FFTSize))));
99 
100         bufferInput.setSize      (1, static_cast<int> (FFTSize));
101         bufferOutput.setSize     (1, static_cast<int> (FFTSize * 2));
102         bufferTempOutput.setSize (1, static_cast<int> (FFTSize * 2));
103         bufferOverlap.setSize    (1, static_cast<int> (FFTSize));
104 
105         buffersInputSegments.clear();
106         buffersImpulseSegments.clear();
107         bufferOutput.clear();
108 
109         for (size_t i = 0; i < numInputSegments; ++i)
110         {
111             AudioBuffer<float> newInputSegment;
112             newInputSegment.setSize (1, static_cast<int> (FFTSize * 2));
113             buffersInputSegments.add (newInputSegment);
114         }
115 
116         for (auto i = 0u; i < numSegments; ++i)
117         {
118             AudioBuffer<float> newImpulseSegment;
119             newImpulseSegment.setSize (1, static_cast<int> (FFTSize * 2));
120             buffersImpulseSegments.add (newImpulseSegment);
121         }
122 
123         std::unique_ptr<FFT> FFTTempObject (new FFT (roundToInt (std::log2 (FFTSize))));
124 
125         auto* channelData = info.buffer->getWritePointer (channel);
126 
127         for (size_t n = 0; n < numSegments; ++n)
128         {
129             buffersImpulseSegments.getReference (static_cast<int> (n)).clear();
130 
131             auto* impulseResponse = buffersImpulseSegments.getReference (static_cast<int> (n)).getWritePointer (0);
132 
133             if (n == 0)
134                 impulseResponse[0] = 1.0f;
135 
136             for (size_t i = 0; i < FFTSize - blockSize; ++i)
137                 if (i + n * (FFTSize - blockSize) < (size_t) info.finalSize)
138                     impulseResponse[i] = channelData[i + n * (FFTSize - blockSize)];
139 
140             FFTTempObject->performRealOnlyForwardTransform (impulseResponse);
141             prepareForConvolution (impulseResponse);
142         }
143 
144         reset();
145 
146         isReady = true;
147     }
148 
149     /** Copy the states of another engine. */
copyStateFromOtherEnginejuce::dsp::ConvolutionEngine150     void copyStateFromOtherEngine (const ConvolutionEngine& other)
151     {
152         if (FFTSize != other.FFTSize)
153         {
154             FFTobject.reset (new FFT (roundToInt (std::log2 (other.FFTSize))));
155             FFTSize = other.FFTSize;
156         }
157 
158         currentSegment      = other.currentSegment;
159         numInputSegments    = other.numInputSegments;
160         numSegments         = other.numSegments;
161         blockSize           = other.blockSize;
162         inputDataPos        = other.inputDataPos;
163 
164         bufferInput         = other.bufferInput;
165         bufferTempOutput    = other.bufferTempOutput;
166         bufferOutput        = other.bufferOutput;
167 
168         buffersInputSegments    = other.buffersInputSegments;
169         buffersImpulseSegments  = other.buffersImpulseSegments;
170         bufferOverlap           = other.bufferOverlap;
171 
172         isReady = true;
173     }
174 
175     /** Performs the uniform partitioned convolution using FFT. */
processSamplesjuce::dsp::ConvolutionEngine176     void processSamples (const float* input, float* output, size_t numSamples)
177     {
178         if (! isReady)
179             return;
180 
181         // Overlap-add, zero latency convolution algorithm with uniform partitioning
182         size_t numSamplesProcessed = 0;
183 
184         auto indexStep = numInputSegments / numSegments;
185 
186         auto* inputData      = bufferInput.getWritePointer (0);
187         auto* outputTempData = bufferTempOutput.getWritePointer (0);
188         auto* outputData     = bufferOutput.getWritePointer (0);
189         auto* overlapData    = bufferOverlap.getWritePointer (0);
190 
191         while (numSamplesProcessed < numSamples)
192         {
193             const bool inputDataWasEmpty = (inputDataPos == 0);
194             auto numSamplesToProcess = jmin (numSamples - numSamplesProcessed, blockSize - inputDataPos);
195 
196             // copy the input samples
197             FloatVectorOperations::copy (inputData + inputDataPos, input + numSamplesProcessed, static_cast<int> (numSamplesToProcess));
198 
199             auto* inputSegmentData = buffersInputSegments.getReference (static_cast<int> (currentSegment)).getWritePointer (0);
200             FloatVectorOperations::copy (inputSegmentData, inputData, static_cast<int> (FFTSize));
201 
202             // Forward FFT
203             FFTobject->performRealOnlyForwardTransform (inputSegmentData);
204             prepareForConvolution (inputSegmentData);
205 
206             // Complex multiplication
207             if (inputDataWasEmpty)
208             {
209                 FloatVectorOperations::fill (outputTempData, 0, static_cast<int> (FFTSize + 1));
210 
211                 auto index = currentSegment;
212 
213                 for (size_t i = 1; i < numSegments; ++i)
214                 {
215                     index += indexStep;
216 
217                     if (index >= numInputSegments)
218                         index -= numInputSegments;
219 
220                     convolutionProcessingAndAccumulate (buffersInputSegments.getReference (static_cast<int> (index)).getWritePointer (0),
221                                                         buffersImpulseSegments.getReference (static_cast<int> (i)).getWritePointer (0),
222                                                         outputTempData);
223                 }
224             }
225 
226             FloatVectorOperations::copy (outputData, outputTempData, static_cast<int> (FFTSize + 1));
227 
228             convolutionProcessingAndAccumulate (buffersInputSegments.getReference (static_cast<int> (currentSegment)).getWritePointer (0),
229                                                 buffersImpulseSegments.getReference (0).getWritePointer (0),
230                                                 outputData);
231 
232             // Inverse FFT
233             updateSymmetricFrequencyDomainData (outputData);
234             FFTobject->performRealOnlyInverseTransform (outputData);
235 
236             // Add overlap
237             for (size_t i = 0; i < numSamplesToProcess; ++i)
238                 output[i + numSamplesProcessed] = outputData[inputDataPos + i] + overlapData[inputDataPos + i];
239 
240             // Input buffer full => Next block
241             inputDataPos += numSamplesToProcess;
242 
243             if (inputDataPos == blockSize)
244             {
245                 // Input buffer is empty again now
246                 FloatVectorOperations::fill (inputData, 0.0f, static_cast<int> (FFTSize));
247 
248                 inputDataPos = 0;
249 
250                 // Extra step for segSize > blockSize
251                 FloatVectorOperations::add (&(outputData[blockSize]), &(overlapData[blockSize]), static_cast<int> (FFTSize - 2 * blockSize));
252 
253                 // Save the overlap
254                 FloatVectorOperations::copy (overlapData, &(outputData[blockSize]), static_cast<int> (FFTSize - blockSize));
255 
256                 // Update current segment
257                 currentSegment = (currentSegment > 0) ? (currentSegment - 1) : (numInputSegments - 1);
258             }
259 
260             numSamplesProcessed += numSamplesToProcess;
261         }
262     }
263 
264     /** After each FFT, this function is called to allow convolution to be performed with only 4 SIMD functions calls. */
prepareForConvolutionjuce::dsp::ConvolutionEngine265     void prepareForConvolution (float *samples) noexcept
266     {
267         auto FFTSizeDiv2 = FFTSize / 2;
268 
269         for (size_t i = 0; i < FFTSizeDiv2; i++)
270             samples[i] = samples[2 * i];
271 
272         samples[FFTSizeDiv2] = 0;
273 
274         for (size_t i = 1; i < FFTSizeDiv2; i++)
275             samples[i + FFTSizeDiv2] = -samples[2 * (FFTSize - i) + 1];
276     }
277 
278     /** Does the convolution operation itself only on half of the frequency domain samples. */
convolutionProcessingAndAccumulatejuce::dsp::ConvolutionEngine279     void convolutionProcessingAndAccumulate (const float *input, const float *impulse, float *output)
280     {
281         auto FFTSizeDiv2 = FFTSize / 2;
282 
283         FloatVectorOperations::addWithMultiply      (output, input, impulse, static_cast<int> (FFTSizeDiv2));
284         FloatVectorOperations::subtractWithMultiply (output, &(input[FFTSizeDiv2]), &(impulse[FFTSizeDiv2]), static_cast<int> (FFTSizeDiv2));
285 
286         FloatVectorOperations::addWithMultiply      (&(output[FFTSizeDiv2]), input, &(impulse[FFTSizeDiv2]), static_cast<int> (FFTSizeDiv2));
287         FloatVectorOperations::addWithMultiply      (&(output[FFTSizeDiv2]), &(input[FFTSizeDiv2]), impulse, static_cast<int> (FFTSizeDiv2));
288 
289         output[FFTSize] += input[FFTSize] * impulse[FFTSize];
290     }
291 
292     /** Undo the re-organization of samples from the function prepareForConvolution.
293         Then, takes the conjugate of the frequency domain first half of samples, to fill the
294         second half, so that the inverse transform will return real samples in the time domain.
295     */
updateSymmetricFrequencyDomainDatajuce::dsp::ConvolutionEngine296     void updateSymmetricFrequencyDomainData (float* samples) noexcept
297     {
298         auto FFTSizeDiv2 = FFTSize / 2;
299 
300         for (size_t i = 1; i < FFTSizeDiv2; i++)
301         {
302             samples[2 * (FFTSize - i)] = samples[i];
303             samples[2 * (FFTSize - i) + 1] = -samples[FFTSizeDiv2 + i];
304         }
305 
306         samples[1] = 0.f;
307 
308         for (size_t i = 1; i < FFTSizeDiv2; i++)
309         {
310             samples[2 * i] = samples[2 * (FFTSize - i)];
311             samples[2 * i + 1] = -samples[2 * (FFTSize - i) + 1];
312         }
313     }
314 
315     //==============================================================================
316     std::unique_ptr<FFT> FFTobject;
317 
318     size_t FFTSize = 0;
319     size_t currentSegment = 0, numInputSegments = 0, numSegments = 0, blockSize = 0, inputDataPos = 0;
320 
321     AudioBuffer<float> bufferInput, bufferOutput, bufferTempOutput, bufferOverlap;
322     Array<AudioBuffer<float>> buffersInputSegments, buffersImpulseSegments;
323 
324     bool isReady = false;
325 
326     //==============================================================================
327     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConvolutionEngine)
328 };
329 
330 
331 
332 //==============================================================================
333 /** Manages all the changes requested by the main convolution engine, to minimize
334     the number of calls of the convolution engine initialization, and the potential
335     consequences of multiple quick calls to the function Convolution::loadImpulseResponse.
336 */
337 struct Convolution::Pimpl  : private Thread
338 {
339     enum class ChangeRequest
340     {
341         changeEngine = 0,
342         changeSampleRate,
343         changeMaximumBufferSize,
344         changeSource,
345         changeImpulseResponseSize,
346         changeStereo,
347         changeTrimming,
348         changeNormalisation,
349         changeIgnore,
350         numChangeRequestTypes
351     };
352 
353     using SourceType = ConvolutionEngine::ProcessingInformation::SourceType;
354 
355     //==============================================================================
Pimpljuce::dsp::Convolution::Pimpl356     Pimpl()  : Thread ("Convolution"), abstractFifo (fifoSize)
357     {
358         abstractFifo.reset();
359         fifoRequestsType.resize (fifoSize);
360         fifoRequestsParameter.resize (fifoSize);
361 
362         requestsType.resize (fifoSize);
363         requestsParameter.resize (fifoSize);
364 
365         for (auto i = 0; i < 4; ++i)
366             engines.add (new ConvolutionEngine());
367 
368         currentInfo.maximumBufferSize = 0;
369         currentInfo.buffer = &impulseResponse;
370 
371         temporaryBuffer.setSize (2, static_cast<int> (maximumTimeInSamples), false, false, true);
372         impulseResponseOriginal.setSize (2, static_cast<int> (maximumTimeInSamples), false, false, true);
373         impulseResponse.setSize (2, static_cast<int> (maximumTimeInSamples), false, false, true);
374     }
375 
~Pimpljuce::dsp::Convolution::Pimpl376     ~Pimpl() override
377     {
378         stopThread (10000);
379     }
380 
381     //==============================================================================
382     /** Inits the size of the interpolation buffer. */
initProcessingjuce::dsp::Convolution::Pimpl383     void initProcessing (int maximumBufferSize)
384     {
385         stopThread (1000);
386 
387         interpolationBuffer.setSize (1, maximumBufferSize, false, false, true);
388         mustInterpolate = false;
389     }
390 
391     //==============================================================================
392     /** Adds a new change request. */
addToFifojuce::dsp::Convolution::Pimpl393     void addToFifo (ChangeRequest type, juce::var parameter)
394     {
395         int start1, size1, start2, size2;
396         abstractFifo.prepareToWrite (1, start1, size1, start2, size2);
397 
398         // If you hit this assertion then you have requested more impulse response
399         // changes than the Convolution class can handle.
400         jassert (size1 + size2 > 0);
401 
402         if (size1 > 0)
403         {
404             fifoRequestsType.setUnchecked (start1, type);
405             fifoRequestsParameter.setUnchecked (start1, parameter);
406         }
407 
408         if (size2 > 0)
409         {
410             fifoRequestsType.setUnchecked (start2, type);
411             fifoRequestsParameter.setUnchecked (start2, parameter);
412         }
413 
414         abstractFifo.finishedWrite (size1 + size2);
415     }
416 
417     /** Adds a new array of change requests. */
addToFifojuce::dsp::Convolution::Pimpl418     void addToFifo (ChangeRequest* types, juce::var* parameters, int numEntries)
419     {
420         int start1, size1, start2, size2;
421         abstractFifo.prepareToWrite (numEntries, start1, size1, start2, size2);
422 
423         // If you hit this assertion then you have requested more impulse response
424         // changes than the Convolution class can handle.
425         jassert (numEntries > 0 && size1 + size2 > 0);
426 
427         if (size1 > 0)
428         {
429             for (auto i = 0; i < size1; ++i)
430             {
431                 fifoRequestsType.setUnchecked (start1 + i, types[i]);
432                 fifoRequestsParameter.setUnchecked (start1 + i, parameters[i]);
433             }
434         }
435 
436         if (size2 > 0)
437         {
438             for (auto i = 0; i < size2; ++i)
439             {
440                 fifoRequestsType.setUnchecked (start2 + i, types[i + size1]);
441                 fifoRequestsParameter.setUnchecked (start2 + i, parameters[i + size1]);
442             }
443         }
444 
445         abstractFifo.finishedWrite (size1 + size2);
446     }
447 
448     /** Reads requests from the fifo. */
readFromFifojuce::dsp::Convolution::Pimpl449     void readFromFifo (ChangeRequest& type, juce::var& parameter)
450     {
451         int start1, size1, start2, size2;
452         abstractFifo.prepareToRead (1, start1, size1, start2, size2);
453 
454         if (size1 > 0)
455         {
456             type = fifoRequestsType[start1];
457             parameter = fifoRequestsParameter[start1];
458         }
459 
460         if (size2 > 0)
461         {
462             type = fifoRequestsType[start2];
463             parameter = fifoRequestsParameter[start2];
464         }
465 
466         abstractFifo.finishedRead (size1 + size2);
467     }
468 
469     /** Returns the number of requests that still need to be processed. */
getNumRemainingEntriesjuce::dsp::Convolution::Pimpl470     int getNumRemainingEntries() const noexcept
471     {
472         return abstractFifo.getNumReady();
473     }
474 
475     //==============================================================================
476     /** This function processes all the change requests to remove all the the
477         redundant ones, and to tell what kind of initialization must be done.
478 
479         Depending on the results, the convolution engines might be reset, or
480         simply updated, or they might not need any change at all.
481     */
processFifojuce::dsp::Convolution::Pimpl482     void processFifo()
483     {
484         if (getNumRemainingEntries() == 0 || isThreadRunning() || mustInterpolate)
485             return;
486 
487         auto numRequests = 0;
488 
489         // retrieve the information from the FIFO for processing
490         while (getNumRemainingEntries() > 0 && numRequests < fifoSize)
491         {
492             ChangeRequest type = ChangeRequest::changeEngine;
493             juce::var parameter;
494 
495             readFromFifo (type, parameter);
496 
497             requestsType.setUnchecked (numRequests, type);
498             requestsParameter.setUnchecked (numRequests, parameter);
499 
500             numRequests++;
501         }
502 
503         // remove any useless messages
504         for (auto i = 0; i < (int) ChangeRequest::numChangeRequestTypes; ++i)
505         {
506             bool exists = false;
507 
508             for (auto n = numRequests; --n >= 0;)
509             {
510                 if (requestsType[n] == (ChangeRequest) i)
511                 {
512                     if (! exists)
513                         exists = true;
514                     else
515                         requestsType.setUnchecked (n, ChangeRequest::changeIgnore);
516                 }
517             }
518         }
519 
520         changeLevel = 0;
521 
522         for (auto n = 0; n < numRequests; ++n)
523         {
524             switch (requestsType[n])
525             {
526                 case ChangeRequest::changeEngine:
527                     changeLevel = 3;
528                     break;
529 
530                 case ChangeRequest::changeSampleRate:
531                 {
532                     double newSampleRate = requestsParameter[n];
533 
534                     if (currentInfo.sampleRate != newSampleRate)
535                         changeLevel = 3;
536 
537                     currentInfo.sampleRate = newSampleRate;
538                 }
539                 break;
540 
541                 case ChangeRequest::changeMaximumBufferSize:
542                 {
543                     int newMaximumBufferSize = requestsParameter[n];
544 
545                     if (currentInfo.maximumBufferSize != (size_t) newMaximumBufferSize)
546                         changeLevel = 3;
547 
548                     currentInfo.maximumBufferSize = (size_t) newMaximumBufferSize;
549                 }
550                 break;
551 
552                 case ChangeRequest::changeSource:
553                 {
554                     auto* arrayParameters = requestsParameter[n].getArray();
555                     auto newSourceType = static_cast<SourceType> (static_cast<int> (arrayParameters->getUnchecked (0)));
556 
557                     if (currentInfo.sourceType != newSourceType)
558                         changeLevel = jmax (2, changeLevel);
559 
560                     if (newSourceType == SourceType::sourceBinaryData)
561                     {
562                         auto& prm = arrayParameters->getRawDataPointer()[1];
563                         auto* newMemoryBlock = prm.getBinaryData();
564 
565                         auto* newPtr = newMemoryBlock->getData();
566                         auto newSize = (int) newMemoryBlock->getSize();
567 
568                         if (currentInfo.sourceData != newPtr || currentInfo.sourceDataSize != newSize)
569                             changeLevel = jmax (2, changeLevel);
570 
571                         currentInfo.sourceType = SourceType::sourceBinaryData;
572                         currentInfo.sourceData = newPtr;
573                         currentInfo.sourceDataSize = newSize;
574                         currentInfo.fileImpulseResponse = File();
575                     }
576                     else if (newSourceType == SourceType::sourceAudioFile)
577                     {
578                         File newFile (arrayParameters->getUnchecked (1).toString());
579 
580                         if (currentInfo.fileImpulseResponse != newFile)
581                             changeLevel = jmax (2, changeLevel);
582 
583                         currentInfo.sourceType = SourceType::sourceAudioFile;
584                         currentInfo.fileImpulseResponse = newFile;
585                         currentInfo.sourceData = nullptr;
586                         currentInfo.sourceDataSize = 0;
587                     }
588                     else if (newSourceType == SourceType::sourceAudioBuffer)
589                     {
590                         double originalSampleRate (arrayParameters->getUnchecked (1));
591                         changeLevel = jmax (2, changeLevel);
592 
593                         currentInfo.sourceType = SourceType::sourceAudioBuffer;
594                         currentInfo.originalSampleRate = originalSampleRate;
595                         currentInfo.fileImpulseResponse = File();
596                         currentInfo.sourceData = nullptr;
597                         currentInfo.sourceDataSize = 0;
598                     }
599                 }
600                 break;
601 
602                 case ChangeRequest::changeImpulseResponseSize:
603                 {
604                     int64 newSize = requestsParameter[n];
605 
606                     if (currentInfo.wantedSize != newSize)
607                         changeLevel = jmax (1, changeLevel);
608 
609                     currentInfo.wantedSize = newSize;
610                 }
611                 break;
612 
613                 case ChangeRequest::changeStereo:
614                 {
615                     bool newWantsStereo = requestsParameter[n];
616 
617                     if (currentInfo.wantsStereo != newWantsStereo)
618                         changeLevel = jmax (0, changeLevel);
619 
620                     currentInfo.wantsStereo = newWantsStereo;
621                 }
622                 break;
623 
624                 case ChangeRequest::changeTrimming:
625                 {
626                     bool newWantsTrimming = requestsParameter[n];
627 
628                     if (currentInfo.wantsTrimming != newWantsTrimming)
629                         changeLevel = jmax (1, changeLevel);
630 
631                     currentInfo.wantsTrimming = newWantsTrimming;
632                 }
633                 break;
634 
635                 case ChangeRequest::changeNormalisation:
636                 {
637                     bool newWantsNormalisation = requestsParameter[n];
638 
639                     if (currentInfo.wantsNormalisation != newWantsNormalisation)
640                         changeLevel = jmax (1, changeLevel);
641 
642                     currentInfo.wantsNormalisation = newWantsNormalisation;
643                 }
644                 break;
645 
646                 case ChangeRequest::changeIgnore:
647                     break;
648 
649                 default:
650                     jassertfalse;
651                     break;
652             }
653         }
654 
655         if (currentInfo.sourceType == SourceType::sourceNone)
656         {
657             currentInfo.sourceType = SourceType::sourceAudioBuffer;
658 
659             if (currentInfo.sampleRate == 0)
660                 currentInfo.sampleRate = 44100;
661 
662             if (currentInfo.maximumBufferSize == 0)
663                 currentInfo.maximumBufferSize = 128;
664 
665             currentInfo.originalSampleRate = currentInfo.sampleRate;
666             currentInfo.wantedSize = 1;
667             currentInfo.fileImpulseResponse = File();
668             currentInfo.sourceData = nullptr;
669             currentInfo.sourceDataSize = 0;
670 
671             AudioBuffer<float> newBuffer;
672             newBuffer.setSize (1, 1);
673             newBuffer.setSample (0, 0, 1.f);
674 
675             copyBufferToTemporaryLocation (newBuffer);
676         }
677 
678         // action depending on the change level
679         if (changeLevel == 3)
680         {
681             loadImpulseResponse();
682             processImpulseResponse();
683             initializeConvolutionEngines();
684         }
685         else if (changeLevel > 0)
686         {
687             startThread();
688         }
689     }
690 
691     //==============================================================================
692     /** This function copies a buffer to a temporary location, so that any external
693         audio source can be processed then in the dedicated thread.
694     */
copyBufferToTemporaryLocationjuce::dsp::Convolution::Pimpl695     void copyBufferToTemporaryLocation (dsp::AudioBlock<float> block)
696     {
697         const SpinLock::ScopedLockType sl (processLock);
698 
699         currentInfo.originalNumChannels = (block.getNumChannels() > 1 ? 2 : 1);
700         currentInfo.originalSize = (int) jmin ((size_t) maximumTimeInSamples, block.getNumSamples());
701 
702         for (auto channel = 0; channel < currentInfo.originalNumChannels; ++channel)
703             temporaryBuffer.copyFrom (channel, 0, block.getChannelPointer ((size_t) channel), (int) currentInfo.originalSize);
704     }
705 
706     //==============================================================================
707     /** Resets the convolution engines states. */
resetjuce::dsp::Convolution::Pimpl708     void reset()
709     {
710         for (auto* e : engines)
711             e->reset();
712 
713         mustInterpolate = false;
714 
715         processFifo();
716     }
717 
718     /** Convolution processing handling interpolation between previous and new states
719         of the convolution engines.
720     */
processSamplesjuce::dsp::Convolution::Pimpl721     void processSamples (const AudioBlock<const float>& input, AudioBlock<float>& output)
722     {
723         processFifo();
724 
725         size_t numChannels = jmin (input.getNumChannels(), (size_t) (currentInfo.wantsStereo ? 2 : 1));
726         size_t numSamples  = jmin (input.getNumSamples(), output.getNumSamples());
727 
728         if (mustInterpolate == false)
729         {
730             for (size_t channel = 0; channel < numChannels; ++channel)
731                 engines[(int) channel]->processSamples (input.getChannelPointer (channel), output.getChannelPointer (channel), numSamples);
732         }
733         else
734         {
735             auto interpolated = dsp::AudioBlock<float> (interpolationBuffer).getSubBlock (0, numSamples);
736 
737             for (size_t channel = 0; channel < numChannels; ++channel)
738             {
739                 auto&& buffer = output.getSingleChannelBlock (channel);
740 
741                 interpolationBuffer.copyFrom (0, 0, input.getChannelPointer (channel), (int) numSamples);
742 
743                 engines[(int) channel]->processSamples (input.getChannelPointer (channel), buffer.getChannelPointer (0), numSamples);
744                 changeVolumes[channel].applyGain (buffer.getChannelPointer (0), (int) numSamples);
745 
746                 auto* interPtr = interpolationBuffer.getWritePointer (0);
747                 engines[(int) channel + 2]->processSamples (interPtr, interPtr, numSamples);
748                 changeVolumes[channel + 2].applyGain (interPtr, (int) numSamples);
749 
750                 buffer += interpolated;
751             }
752 
753             if (input.getNumChannels() > 1 && currentInfo.wantsStereo == false)
754             {
755                 auto&& buffer = output.getSingleChannelBlock (1);
756 
757                 changeVolumes[1].applyGain (buffer.getChannelPointer (0), (int) numSamples);
758                 changeVolumes[3].applyGain (buffer.getChannelPointer (0), (int) numSamples);
759             }
760 
761             if (changeVolumes[0].isSmoothing() == false)
762             {
763                 mustInterpolate = false;
764 
765                 for (auto channel = 0; channel < 2; ++channel)
766                     engines[channel]->copyStateFromOtherEngine (*engines[channel + 2]);
767             }
768         }
769 
770         if (input.getNumChannels() > 1 && currentInfo.wantsStereo == false)
771             output.getSingleChannelBlock (1).copyFrom (output.getSingleChannelBlock (0));
772     }
773 
774     //==============================================================================
775     const int64 maximumTimeInSamples = 10 * 96000;
776 
777 private:
778     //==============================================================================
779     /** This the thread run function which does the preparation of data depending
780         on the requested change level.
781     */
runjuce::dsp::Convolution::Pimpl782     void run() override
783     {
784         if (changeLevel == 2)
785         {
786             loadImpulseResponse();
787 
788             if (isThreadRunning() && threadShouldExit())
789                 return;
790         }
791 
792         processImpulseResponse();
793 
794         if (isThreadRunning() && threadShouldExit())
795             return;
796 
797         initializeConvolutionEngines();
798     }
799 
800     /** Loads the impulse response from the requested audio source. */
loadImpulseResponsejuce::dsp::Convolution::Pimpl801     void loadImpulseResponse()
802     {
803         if (currentInfo.sourceType == SourceType::sourceBinaryData)
804         {
805             if (! (copyAudioStreamInAudioBuffer (new MemoryInputStream (currentInfo.sourceData, (size_t) currentInfo.sourceDataSize, false))))
806                 return;
807         }
808         else if (currentInfo.sourceType == SourceType::sourceAudioFile)
809         {
810             if (! (copyAudioStreamInAudioBuffer (new FileInputStream (currentInfo.fileImpulseResponse))))
811                 return;
812         }
813         else if (currentInfo.sourceType == SourceType::sourceAudioBuffer)
814         {
815             copyBufferFromTemporaryLocation();
816         }
817     }
818 
819     /** Processes the impulse response data with the requested treatments
820         and resampling if needed.
821     */
processImpulseResponsejuce::dsp::Convolution::Pimpl822     void processImpulseResponse()
823     {
824         trimAndResampleImpulseResponse (currentInfo.originalNumChannels, currentInfo.originalSampleRate, currentInfo.wantsTrimming);
825 
826         if (isThreadRunning() && threadShouldExit())
827             return;
828 
829         if (currentInfo.wantsNormalisation)
830         {
831             if (currentInfo.originalNumChannels > 1)
832             {
833                 normaliseImpulseResponse (currentInfo.buffer->getWritePointer (0), (int) currentInfo.finalSize, 1.0);
834                 normaliseImpulseResponse (currentInfo.buffer->getWritePointer (1), (int) currentInfo.finalSize, 1.0);
835             }
836             else
837             {
838                 normaliseImpulseResponse (currentInfo.buffer->getWritePointer (0), (int) currentInfo.finalSize, 1.0);
839             }
840         }
841 
842         if (currentInfo.originalNumChannels == 1)
843             currentInfo.buffer->copyFrom (1, 0, *currentInfo.buffer, 0, 0, (int) currentInfo.finalSize);
844     }
845 
846     /** Converts the data from an audio file into a stereo audio buffer of floats, and
847         performs resampling if necessary.
848     */
copyAudioStreamInAudioBufferjuce::dsp::Convolution::Pimpl849     bool copyAudioStreamInAudioBuffer (InputStream* stream)
850     {
851         AudioFormatManager manager;
852         manager.registerBasicFormats();
853         std::unique_ptr<AudioFormatReader> formatReader (manager.createReaderFor (stream));
854 
855         if (formatReader != nullptr)
856         {
857             currentInfo.originalNumChannels = formatReader->numChannels > 1 ? 2 : 1;
858             currentInfo.originalSampleRate = formatReader->sampleRate;
859             currentInfo.originalSize = static_cast<int> (jmin (maximumTimeInSamples, formatReader->lengthInSamples));
860 
861             impulseResponseOriginal.clear();
862             formatReader->read (&(impulseResponseOriginal), 0, (int) currentInfo.originalSize, 0, true, currentInfo.originalNumChannels > 1);
863 
864             return true;
865         }
866 
867         return false;
868     }
869 
870     /** Copies a buffer from a temporary location to the impulseResponseOriginal
871         buffer for the sourceAudioBuffer.
872     */
copyBufferFromTemporaryLocationjuce::dsp::Convolution::Pimpl873     void copyBufferFromTemporaryLocation()
874     {
875         const SpinLock::ScopedLockType sl (processLock);
876 
877         for (auto channel = 0; channel < currentInfo.originalNumChannels; ++channel)
878             impulseResponseOriginal.copyFrom (channel, 0, temporaryBuffer, channel, 0, (int) currentInfo.originalSize);
879     }
880 
881     /** Trim and resample the impulse response if needed. */
trimAndResampleImpulseResponsejuce::dsp::Convolution::Pimpl882     void trimAndResampleImpulseResponse (int numChannels, double srcSampleRate, bool mustTrim)
883     {
884         auto thresholdTrim = Decibels::decibelsToGain (-80.0f);
885         auto indexStart = 0;
886         auto indexEnd = currentInfo.originalSize - 1;
887 
888         if (mustTrim)
889         {
890             indexStart = currentInfo.originalSize - 1;
891             indexEnd = 0;
892 
893             for (auto channel = 0; channel < numChannels; ++channel)
894             {
895                 auto localIndexStart = 0;
896                 auto localIndexEnd = currentInfo.originalSize - 1;
897 
898                 auto* channelData = impulseResponseOriginal.getReadPointer (channel);
899 
900                 while (localIndexStart < currentInfo.originalSize - 1
901                         && channelData[localIndexStart] <= thresholdTrim
902                         && channelData[localIndexStart] >= -thresholdTrim)
903                     ++localIndexStart;
904 
905                 while (localIndexEnd >= 0
906                         && channelData[localIndexEnd] <= thresholdTrim
907                         && channelData[localIndexEnd] >= -thresholdTrim)
908                     --localIndexEnd;
909 
910                 indexStart = jmin (indexStart, localIndexStart);
911                 indexEnd = jmax (indexEnd, localIndexEnd);
912             }
913 
914             if (indexStart > 0)
915             {
916                 for (auto channel = 0; channel < numChannels; ++channel)
917                 {
918                     auto* channelData = impulseResponseOriginal.getWritePointer (channel);
919 
920                     for (auto i = 0; i < indexEnd - indexStart + 1; ++i)
921                         channelData[i] = channelData[i + indexStart];
922 
923                     for (auto i = indexEnd - indexStart + 1; i < currentInfo.originalSize - 1; ++i)
924                         channelData[i] = 0.0f;
925                 }
926             }
927         }
928 
929         if (currentInfo.sampleRate == srcSampleRate)
930         {
931             // No resampling
932             currentInfo.finalSize = jmin (static_cast<int> (currentInfo.wantedSize), indexEnd - indexStart + 1);
933 
934             impulseResponse.clear();
935 
936             for (auto channel = 0; channel < numChannels; ++channel)
937                 impulseResponse.copyFrom (channel, 0, impulseResponseOriginal, channel, 0, (int) currentInfo.finalSize);
938         }
939         else
940         {
941             // Resampling
942             auto factorReading = srcSampleRate / currentInfo.sampleRate;
943             currentInfo.finalSize = jmin (static_cast<int> (currentInfo.wantedSize), roundToInt ((indexEnd - indexStart + 1) / factorReading));
944 
945             impulseResponse.clear();
946 
947             MemoryAudioSource memorySource (impulseResponseOriginal, false);
948             ResamplingAudioSource resamplingSource (&memorySource, false, (int) numChannels);
949 
950             resamplingSource.setResamplingRatio (factorReading);
951             resamplingSource.prepareToPlay ((int) currentInfo.finalSize, currentInfo.sampleRate);
952 
953             AudioSourceChannelInfo info;
954             info.startSample = 0;
955             info.numSamples = (int) currentInfo.finalSize;
956             info.buffer = &impulseResponse;
957 
958             resamplingSource.getNextAudioBlock (info);
959         }
960 
961         // Filling the second channel with the first if necessary
962         if (numChannels == 1)
963             impulseResponse.copyFrom (1, 0, impulseResponse, 0, 0, (int) currentInfo.finalSize);
964     }
965 
966     /** Normalisation of the impulse response based on its energy. */
normaliseImpulseResponsejuce::dsp::Convolution::Pimpl967     void normaliseImpulseResponse (float* samples, int numSamples, double factorResampling) const
968     {
969         auto magnitude = 0.0f;
970 
971         for (auto i = 0; i < numSamples; ++i)
972             magnitude += samples[i] * samples[i];
973 
974         auto magnitudeInv = 1.0f / (4.0f * std::sqrt (magnitude)) * 0.5f * static_cast <float> (factorResampling);
975 
976         for (auto i = 0; i < numSamples; ++i)
977             samples[i] *= magnitudeInv;
978     }
979 
980     // ================================================================================================================
981     /** Initializes the convolution engines depending on the provided sizes
982         and performs the FFT on the impulse responses.
983     */
initializeConvolutionEnginesjuce::dsp::Convolution::Pimpl984     void initializeConvolutionEngines()
985     {
986         if (currentInfo.maximumBufferSize == 0)
987             return;
988 
989         if (changeLevel == 3)
990         {
991             for (auto i = 0; i < 2; ++i)
992                 engines[i]->initializeConvolutionEngine (currentInfo, i);
993 
994             mustInterpolate = false;
995         }
996         else
997         {
998             for (auto i = 0; i < 2; ++i)
999             {
1000                 engines[i + 2]->initializeConvolutionEngine (currentInfo, i);
1001                 engines[i + 2]->reset();
1002 
1003                 if (isThreadRunning() && threadShouldExit())
1004                     return;
1005             }
1006 
1007             for (auto i = 0; i < 2; ++i)
1008             {
1009                 changeVolumes[i].setTargetValue (1.0f);
1010                 changeVolumes[i].reset (currentInfo.sampleRate, 0.05);
1011                 changeVolumes[i].setTargetValue (0.0f);
1012 
1013                 changeVolumes[i + 2].setTargetValue (0.0f);
1014                 changeVolumes[i + 2].reset (currentInfo.sampleRate, 0.05);
1015                 changeVolumes[i + 2].setTargetValue (1.0f);
1016 
1017             }
1018 
1019             mustInterpolate = true;
1020         }
1021     }
1022 
1023 
1024     //==============================================================================
1025     static constexpr int fifoSize = 1024;           // the size of the fifo which handles all the change requests
1026     AbstractFifo abstractFifo;                      // the abstract fifo
1027 
1028     Array<ChangeRequest> fifoRequestsType;          // an array of ChangeRequest
1029     Array<juce::var> fifoRequestsParameter;         // an array of change parameters
1030 
1031     Array<ChangeRequest> requestsType;              // an array of ChangeRequest
1032     Array<juce::var> requestsParameter;             // an array of change parameters
1033 
1034     int changeLevel = 0;                            // the current level of requested change in the convolution engine
1035 
1036     //==============================================================================
1037     ConvolutionEngine::ProcessingInformation currentInfo;  // the information about the impulse response to load
1038 
1039     AudioBuffer<float> temporaryBuffer;             // a temporary buffer that is used when the function copyAndLoadImpulseResponse is called in the main API
1040     SpinLock processLock;                           // a necessary lock to use with this temporary buffer
1041 
1042     AudioBuffer<float> impulseResponseOriginal;     // a buffer with the original impulse response
1043     AudioBuffer<float> impulseResponse;             // a buffer with the impulse response trimmed, resampled, resized and normalised
1044 
1045     //==============================================================================
1046     OwnedArray<ConvolutionEngine> engines;          // the 4 convolution engines being used
1047 
1048     AudioBuffer<float> interpolationBuffer;         // a buffer to do the interpolation between the convolution engines 0-1 and 2-3
1049     LogRampedValue<float> changeVolumes[4];         // the volumes for each convolution engine during interpolation
1050 
1051     bool mustInterpolate = false;                   // tells if the convolution engines outputs must be currently interpolated
1052 
1053     //==============================================================================
1054     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
1055 };
1056 
1057 
1058 //==============================================================================
Convolution()1059 Convolution::Convolution()
1060 {
1061     pimpl.reset (new Pimpl());
1062     pimpl->addToFifo (Convolution::Pimpl::ChangeRequest::changeEngine, juce::var (0));
1063 }
1064 
~Convolution()1065 Convolution::~Convolution()
1066 {
1067 }
1068 
loadImpulseResponse(const void * sourceData,size_t sourceDataSize,bool wantsStereo,bool wantsTrimming,size_t size,bool wantsNormalisation)1069 void Convolution::loadImpulseResponse (const void* sourceData, size_t sourceDataSize,
1070                                        bool wantsStereo, bool wantsTrimming, size_t size,
1071                                        bool wantsNormalisation)
1072 {
1073     if (sourceData == nullptr)
1074         return;
1075 
1076     auto maximumSamples = (size_t) pimpl->maximumTimeInSamples;
1077     auto wantedSize = (size == 0 ? maximumSamples : jmin (size, maximumSamples));
1078 
1079     Pimpl::ChangeRequest types[] = { Pimpl::ChangeRequest::changeSource,
1080                                      Pimpl::ChangeRequest::changeImpulseResponseSize,
1081                                      Pimpl::ChangeRequest::changeStereo,
1082                                      Pimpl::ChangeRequest::changeTrimming,
1083                                      Pimpl::ChangeRequest::changeNormalisation };
1084 
1085     Array<juce::var> sourceParameter;
1086 
1087     sourceParameter.add (juce::var ((int) ConvolutionEngine::ProcessingInformation::SourceType::sourceBinaryData));
1088     sourceParameter.add (juce::var (sourceData, sourceDataSize));
1089 
1090     juce::var parameters[] = { juce::var (sourceParameter),
1091                                juce::var (static_cast<int64> (wantedSize)),
1092                                juce::var (wantsStereo),
1093                                juce::var (wantsTrimming),
1094                                juce::var (wantsNormalisation) };
1095 
1096     pimpl->addToFifo (types, parameters, 5);
1097 }
1098 
loadImpulseResponse(const File & fileImpulseResponse,bool wantsStereo,bool wantsTrimming,size_t size,bool wantsNormalisation)1099 void Convolution::loadImpulseResponse (const File& fileImpulseResponse, bool wantsStereo,
1100                                        bool wantsTrimming, size_t size, bool wantsNormalisation)
1101 {
1102     if (! fileImpulseResponse.existsAsFile())
1103         return;
1104 
1105     auto maximumSamples = (size_t) pimpl->maximumTimeInSamples;
1106     auto wantedSize = (size == 0 ? maximumSamples : jmin (size, maximumSamples));
1107 
1108     Pimpl::ChangeRequest types[] = { Pimpl::ChangeRequest::changeSource,
1109                                      Pimpl::ChangeRequest::changeImpulseResponseSize,
1110                                      Pimpl::ChangeRequest::changeStereo,
1111                                      Pimpl::ChangeRequest::changeTrimming,
1112                                      Pimpl::ChangeRequest::changeNormalisation };
1113 
1114     Array<juce::var> sourceParameter;
1115 
1116     sourceParameter.add (juce::var ((int) ConvolutionEngine::ProcessingInformation::SourceType::sourceAudioFile));
1117     sourceParameter.add (juce::var (fileImpulseResponse.getFullPathName()));
1118 
1119     juce::var parameters[] = { juce::var (sourceParameter),
1120                                juce::var (static_cast<int64> (wantedSize)),
1121                                juce::var (wantsStereo),
1122                                juce::var (wantsTrimming),
1123                                juce::var (wantsNormalisation) };
1124 
1125     pimpl->addToFifo (types, parameters, 5);
1126 }
1127 
copyAndLoadImpulseResponseFromBuffer(AudioBuffer<float> & buffer,double bufferSampleRate,bool wantsStereo,bool wantsTrimming,bool wantsNormalisation,size_t size)1128 void Convolution::copyAndLoadImpulseResponseFromBuffer (AudioBuffer<float>& buffer,
1129                                                         double bufferSampleRate, bool wantsStereo, bool wantsTrimming, bool wantsNormalisation, size_t size)
1130 {
1131     copyAndLoadImpulseResponseFromBlock (AudioBlock<float> (buffer), bufferSampleRate,
1132         wantsStereo, wantsTrimming, wantsNormalisation, size);
1133 }
1134 
copyAndLoadImpulseResponseFromBlock(AudioBlock<float> block,double bufferSampleRate,bool wantsStereo,bool wantsTrimming,bool wantsNormalisation,size_t size)1135 void Convolution::copyAndLoadImpulseResponseFromBlock (AudioBlock<float> block, double bufferSampleRate,
1136                                                        bool wantsStereo, bool wantsTrimming, bool wantsNormalisation, size_t size)
1137 {
1138     jassert (bufferSampleRate > 0);
1139 
1140     if (block.getNumSamples() == 0)
1141         return;
1142 
1143     auto maximumSamples = (size_t) pimpl->maximumTimeInSamples;
1144     auto wantedSize = (size == 0 ? maximumSamples : jmin (size, maximumSamples));
1145 
1146     pimpl->copyBufferToTemporaryLocation (block);
1147 
1148     Pimpl::ChangeRequest types[] = { Pimpl::ChangeRequest::changeSource,
1149                                      Pimpl::ChangeRequest::changeImpulseResponseSize,
1150                                      Pimpl::ChangeRequest::changeStereo,
1151                                      Pimpl::ChangeRequest::changeTrimming,
1152                                      Pimpl::ChangeRequest::changeNormalisation };
1153 
1154     Array<juce::var> sourceParameter;
1155     sourceParameter.add (juce::var ((int) ConvolutionEngine::ProcessingInformation::SourceType::sourceAudioBuffer));
1156     sourceParameter.add (juce::var (bufferSampleRate));
1157 
1158     juce::var parameters[] = { juce::var (sourceParameter),
1159                                juce::var (static_cast<int64> (wantedSize)),
1160                                juce::var (wantsStereo),
1161                                juce::var (wantsTrimming),
1162                                juce::var (wantsNormalisation) };
1163 
1164     pimpl->addToFifo (types, parameters, 5);
1165 }
1166 
prepare(const ProcessSpec & spec)1167 void Convolution::prepare (const ProcessSpec& spec)
1168 {
1169     jassert (isPositiveAndBelow (spec.numChannels, static_cast<uint32> (3))); // only mono and stereo is supported
1170 
1171     Pimpl::ChangeRequest types[] = { Pimpl::ChangeRequest::changeSampleRate,
1172                                      Pimpl::ChangeRequest::changeMaximumBufferSize };
1173 
1174     juce::var parameters[] = { juce::var (spec.sampleRate),
1175                                juce::var (static_cast<int> (spec.maximumBlockSize)) };
1176 
1177     pimpl->addToFifo (types, parameters, 2);
1178     pimpl->initProcessing (static_cast<int> (spec.maximumBlockSize));
1179 
1180     for (size_t channel = 0; channel < spec.numChannels; ++channel)
1181     {
1182         volumeDry[channel].reset (spec.sampleRate, 0.05);
1183         volumeWet[channel].reset (spec.sampleRate, 0.05);
1184     }
1185 
1186     sampleRate = spec.sampleRate;
1187     dryBuffer = AudioBlock<float> (dryBufferStorage,
1188                                    jmin (spec.numChannels, 2u),
1189                                    spec.maximumBlockSize);
1190 
1191     isActive = true;
1192 }
1193 
reset()1194 void Convolution::reset() noexcept
1195 {
1196     dryBuffer.clear();
1197     pimpl->reset();
1198 }
1199 
processSamples(const AudioBlock<const float> & input,AudioBlock<float> & output,bool isBypassed)1200 void Convolution::processSamples (const AudioBlock<const float>& input, AudioBlock<float>& output, bool isBypassed) noexcept
1201 {
1202     if (! isActive)
1203         return;
1204 
1205     jassert (input.getNumChannels() == output.getNumChannels());
1206     jassert (isPositiveAndBelow (input.getNumChannels(), static_cast<size_t> (3))); // only mono and stereo is supported
1207 
1208     auto numChannels = jmin (input.getNumChannels(), (size_t) 2);
1209     auto numSamples  = jmin (input.getNumSamples(), output.getNumSamples());
1210 
1211     auto dry = dryBuffer.getSubsetChannelBlock (0, numChannels);
1212 
1213     if (volumeDry[0].isSmoothing())
1214     {
1215         dry.copyFrom (input);
1216 
1217         for (size_t channel = 0; channel < numChannels; ++channel)
1218             volumeDry[channel].applyGain (dry.getChannelPointer (channel), (int) numSamples);
1219 
1220         pimpl->processSamples (input, output);
1221 
1222         for (size_t channel = 0; channel < numChannels; ++channel)
1223             volumeWet[channel].applyGain (output.getChannelPointer (channel), (int) numSamples);
1224 
1225         output += dry;
1226     }
1227     else
1228     {
1229         if (! currentIsBypassed)
1230             pimpl->processSamples (input, output);
1231 
1232         if (isBypassed != currentIsBypassed)
1233         {
1234             currentIsBypassed = isBypassed;
1235 
1236             for (size_t channel = 0; channel < numChannels; ++channel)
1237             {
1238                 volumeDry[channel].setTargetValue (isBypassed ? 0.0f : 1.0f);
1239                 volumeDry[channel].reset (sampleRate, 0.05);
1240                 volumeDry[channel].setTargetValue (isBypassed ? 1.0f : 0.0f);
1241 
1242                 volumeWet[channel].setTargetValue (isBypassed ? 1.0f : 0.0f);
1243                 volumeWet[channel].reset (sampleRate, 0.05);
1244                 volumeWet[channel].setTargetValue (isBypassed ? 0.0f : 1.0f);
1245             }
1246         }
1247     }
1248 }
1249 
1250 } // namespace dsp
1251 } // namespace juce
1252