29template <
typename Element>
33 explicit Queue (
int size)
34 : fifo (size), storage (
static_cast<size_t> (size)) {}
36 bool push (Element& element)
noexcept
41 const auto writer = fifo.
write (1);
43 if (writer.blockSize1 != 0)
44 storage[
static_cast<size_t> (writer.startIndex1)] = std::move (element);
45 else if (writer.blockSize2 != 0)
46 storage[
static_cast<size_t> (writer.startIndex2)] = std::move (element);
51 template <
typename Fn>
54 template <
typename Fn>
57 bool hasPendingMessages()
const noexcept {
return fifo.
getNumReady() > 0; }
60 template <
typename Fn>
61 void popN (
int n,
Fn&& fn)
65 fn (storage[
static_cast<size_t> (index)]);
77 :
Thread (
"Convolution background loader"), queue (entries)
80 using IncomingCommand = FixedSizeFunction<400, void()>;
85 bool push (IncomingCommand& command) {
return queue.push (command); }
90 queue.popAll ([] (IncomingCommand& command) { command(); command =
nullptr; });
101 const auto tryPop = [&]
105 if (! queue.hasPendingMessages())
108 queue.pop ([] (IncomingCommand& command) { command(); command =
nullptr;});
125 using BackgroundMessageQueue::BackgroundMessageQueue;
133 : pimpl (
std::make_unique<
Impl> (entries))
135 pimpl->startThread();
138ConvolutionMessageQueue::~ConvolutionMessageQueue() noexcept
140 pimpl->stopThread (-1);
144ConvolutionMessageQueue& ConvolutionMessageQueue::operator= (ConvolutionMessageQueue&&) noexcept = default;
153 fftSize (blockSize > 128 ? 2 * blockSize : 4 * blockSize),
155 numSegments (numSamples / (fftSize - blockSize) + 1u),
156 numInputSegments ((blockSize > 128 ? numSegments : 3 * numSegments)),
157 bufferInput (1,
static_cast<int> (fftSize)),
158 bufferOutput (1,
static_cast<int> (fftSize * 2)),
159 bufferTempOutput (1,
static_cast<int> (fftSize * 2)),
160 bufferOverlap (1,
static_cast<int> (fftSize))
162 bufferOutput.clear();
164 auto updateSegmentsIfNecessary = [
this] (
size_t numSegmentsToUpdate,
167 if (numSegmentsToUpdate == 0
168 || numSegmentsToUpdate != (
size_t) segments.size()
169 || (
size_t) segments[0].getNumSamples() != fftSize * 2)
173 for (
size_t i = 0; i < numSegmentsToUpdate; ++i)
174 segments.push_back ({ 1,
static_cast<int> (fftSize * 2) });
178 updateSegmentsIfNecessary (numInputSegments, buffersInputSegments);
179 updateSegmentsIfNecessary (numSegments, buffersImpulseSegments);
182 size_t currentPtr = 0;
184 for (
auto& buf : buffersImpulseSegments)
188 auto* impulseResponse = buf.getWritePointer (0);
190 if (&buf == &buffersImpulseSegments.front())
191 impulseResponse[0] = 1.0f;
193 FloatVectorOperations::copy (impulseResponse,
194 samples + currentPtr,
195 static_cast<int> (
jmin (fftSize - blockSize, numSamples - currentPtr)));
197 FFTTempObject->performRealOnlyForwardTransform (impulseResponse);
198 prepareForConvolution (impulseResponse);
200 currentPtr += (fftSize - blockSize);
209 bufferOverlap.clear();
210 bufferTempOutput.clear();
211 bufferOutput.clear();
213 for (
auto& buf : buffersInputSegments)
220 void processSamples (
const float* input,
float* output,
size_t numSamples)
223 size_t numSamplesProcessed = 0;
225 auto indexStep = numInputSegments / numSegments;
227 auto* inputData = bufferInput.getWritePointer (0);
228 auto* outputTempData = bufferTempOutput.getWritePointer (0);
229 auto* outputData = bufferOutput.getWritePointer (0);
230 auto* overlapData = bufferOverlap.getWritePointer (0);
232 while (numSamplesProcessed < numSamples)
234 const bool inputDataWasEmpty = (inputDataPos == 0);
235 auto numSamplesToProcess =
jmin (numSamples - numSamplesProcessed, blockSize - inputDataPos);
237 FloatVectorOperations::copy (inputData + inputDataPos, input + numSamplesProcessed,
static_cast<int> (numSamplesToProcess));
239 auto* inputSegmentData = buffersInputSegments[currentSegment].getWritePointer (0);
240 FloatVectorOperations::copy (inputSegmentData, inputData,
static_cast<int> (fftSize));
242 fftObject->performRealOnlyForwardTransform (inputSegmentData);
243 prepareForConvolution (inputSegmentData);
246 if (inputDataWasEmpty)
248 FloatVectorOperations::fill (outputTempData, 0,
static_cast<int> (fftSize + 1));
250 auto index = currentSegment;
252 for (
size_t i = 1; i < numSegments; ++i)
256 if (index >= numInputSegments)
257 index -= numInputSegments;
259 convolutionProcessingAndAccumulate (buffersInputSegments[index].getWritePointer (0),
260 buffersImpulseSegments[i].getWritePointer (0),
265 FloatVectorOperations::copy (outputData, outputTempData,
static_cast<int> (fftSize + 1));
267 convolutionProcessingAndAccumulate (inputSegmentData,
268 buffersImpulseSegments.front().getWritePointer (0),
271 updateSymmetricFrequencyDomainData (outputData);
272 fftObject->performRealOnlyInverseTransform (outputData);
275 FloatVectorOperations::add (&output[numSamplesProcessed], &outputData[inputDataPos], &overlapData[inputDataPos], (
int) numSamplesToProcess);
278 inputDataPos += numSamplesToProcess;
280 if (inputDataPos == blockSize)
283 FloatVectorOperations::fill (inputData, 0.0f,
static_cast<int> (fftSize));
288 FloatVectorOperations::add (&(outputData[blockSize]), &(overlapData[blockSize]),
static_cast<int> (fftSize - 2 * blockSize));
291 FloatVectorOperations::copy (overlapData, &(outputData[blockSize]),
static_cast<int> (fftSize - blockSize));
293 currentSegment = (currentSegment > 0) ? (currentSegment - 1) : (numInputSegments - 1);
296 numSamplesProcessed += numSamplesToProcess;
300 void processSamplesWithAddedLatency (
const float* input,
float* output,
size_t numSamples)
303 size_t numSamplesProcessed = 0;
305 auto indexStep = numInputSegments / numSegments;
307 auto* inputData = bufferInput.getWritePointer (0);
308 auto* outputTempData = bufferTempOutput.getWritePointer (0);
309 auto* outputData = bufferOutput.getWritePointer (0);
310 auto* overlapData = bufferOverlap.getWritePointer (0);
312 while (numSamplesProcessed < numSamples)
314 auto numSamplesToProcess =
jmin (numSamples - numSamplesProcessed, blockSize - inputDataPos);
316 FloatVectorOperations::copy (inputData + inputDataPos, input + numSamplesProcessed,
static_cast<int> (numSamplesToProcess));
318 FloatVectorOperations::copy (output + numSamplesProcessed, outputData + inputDataPos,
static_cast<int> (numSamplesToProcess));
320 numSamplesProcessed += numSamplesToProcess;
321 inputDataPos += numSamplesToProcess;
324 if (inputDataPos == blockSize)
327 auto* inputSegmentData = buffersInputSegments[currentSegment].getWritePointer (0);
328 FloatVectorOperations::copy (inputSegmentData, inputData,
static_cast<int> (fftSize));
330 fftObject->performRealOnlyForwardTransform (inputSegmentData);
331 prepareForConvolution (inputSegmentData);
334 FloatVectorOperations::fill (outputTempData, 0,
static_cast<int> (fftSize + 1));
336 auto index = currentSegment;
338 for (
size_t i = 1; i < numSegments; ++i)
342 if (index >= numInputSegments)
343 index -= numInputSegments;
345 convolutionProcessingAndAccumulate (buffersInputSegments[index].getWritePointer (0),
346 buffersImpulseSegments[i].getWritePointer (0),
350 FloatVectorOperations::copy (outputData, outputTempData,
static_cast<int> (fftSize + 1));
352 convolutionProcessingAndAccumulate (inputSegmentData,
353 buffersImpulseSegments.front().getWritePointer (0),
356 updateSymmetricFrequencyDomainData (outputData);
357 fftObject->performRealOnlyInverseTransform (outputData);
360 FloatVectorOperations::add (outputData, overlapData,
static_cast<int> (blockSize));
363 FloatVectorOperations::fill (inputData, 0.0f,
static_cast<int> (fftSize));
366 FloatVectorOperations::add (&(outputData[blockSize]), &(overlapData[blockSize]),
static_cast<int> (fftSize - 2 * blockSize));
369 FloatVectorOperations::copy (overlapData, &(outputData[blockSize]),
static_cast<int> (fftSize - blockSize));
371 currentSegment = (currentSegment > 0) ? (currentSegment - 1) : (numInputSegments - 1);
379 void prepareForConvolution (
float *samples)
noexcept
381 auto FFTSizeDiv2 = fftSize / 2;
383 for (
size_t i = 0; i < FFTSizeDiv2; i++)
384 samples[i] = samples[i << 1];
386 samples[FFTSizeDiv2] = 0;
388 for (
size_t i = 1; i < FFTSizeDiv2; i++)
389 samples[i + FFTSizeDiv2] = -samples[((fftSize - i) << 1) + 1];
393 void convolutionProcessingAndAccumulate (
const float *input,
const float *impulse,
float *output)
395 auto FFTSizeDiv2 = fftSize / 2;
397 FloatVectorOperations::addWithMultiply (output, input, impulse,
static_cast<int> (FFTSizeDiv2));
398 FloatVectorOperations::subtractWithMultiply (output, &(input[FFTSizeDiv2]), &(impulse[FFTSizeDiv2]),
static_cast<int> (FFTSizeDiv2));
400 FloatVectorOperations::addWithMultiply (&(output[FFTSizeDiv2]), input, &(impulse[FFTSizeDiv2]),
static_cast<int> (FFTSizeDiv2));
401 FloatVectorOperations::addWithMultiply (&(output[FFTSizeDiv2]), &(input[FFTSizeDiv2]), impulse,
static_cast<int> (FFTSizeDiv2));
403 output[fftSize] += input[fftSize] * impulse[fftSize];
409 void updateSymmetricFrequencyDomainData (
float* samples)
noexcept
411 auto FFTSizeDiv2 = fftSize / 2;
413 for (
size_t i = 1; i < FFTSizeDiv2; i++)
415 samples[(fftSize - i) << 1] = samples[i];
416 samples[((fftSize - i) << 1) + 1] = -samples[FFTSizeDiv2 + i];
421 for (
size_t i = 1; i < FFTSizeDiv2; i++)
423 samples[i << 1] = samples[(fftSize - i) << 1];
424 samples[(i << 1) + 1] = -samples[((fftSize - i) << 1) + 1];
429 const size_t blockSize;
430 const size_t fftSize;
432 const size_t numSegments;
433 const size_t numInputSegments;
434 size_t currentSegment = 0, inputDataPos = 0;
436 AudioBuffer<float> bufferInput, bufferOutput, bufferTempOutput, bufferOverlap;
449 : tailBuffer (1, maxBlockSize),
450 latency (isZeroDelayIn ? 0 : maxBufferSize),
452 blockSize (maxBlockSize),
453 isZeroDelay (isZeroDelayIn)
455 constexpr auto numChannels = 2;
457 const auto makeEngine = [&] (
int channel,
int offset,
int length,
uint32 thisBlockSize)
461 static_cast<size_t> (thisBlockSize));
464 if (headSizeIn.headSizeInSamples == 0)
466 for (
int i = 0; i < numChannels; ++i)
467 head.emplace_back (makeEngine (i, 0, buf.
getNumSamples(),
static_cast<uint32> (maxBufferSize)));
473 for (
int i = 0; i < numChannels; ++i)
474 head.emplace_back (makeEngine (i, 0, size,
static_cast<uint32> (maxBufferSize)));
476 const auto tailBufferSize =
static_cast<uint32> (headSizeIn.headSizeInSamples + (isZeroDelay ? 0 : maxBufferSize));
479 for (
int i = 0; i < numChannels; ++i)
480 tail.emplace_back (makeEngine (i, size, buf.
getNumSamples() - size, tailBufferSize));
486 for (
const auto& e : head)
489 for (
const auto& e : tail)
499 const auto tailBlock = fullTailBlock.
getSubBlock (0, (
size_t) numSamples);
501 const auto isUniform = tail.empty();
503 for (
size_t channel = 0; channel < numChannels; ++channel)
506 tail[channel]->processSamplesWithAddedLatency (input.
getChannelPointer (channel),
507 tailBlock.getChannelPointer (0),
515 head[channel]->processSamplesWithAddedLatency (input.
getChannelPointer (channel),
525 for (
auto i = numChannels; i < numOutputChannels; ++i)
529 int getIRSize()
const noexcept {
return irSize; }
530 int getLatency()
const noexcept {
return latency; }
531 int getBlockSize()
const noexcept {
return blockSize; }
540 const bool isZeroDelay;
545 const auto numChannels =
jmin (buf.
getNumChannels(), stereo == Convolution::Stereo::yes ? 2 : 1);
550 for (
auto channel = 0; channel != numChannels; ++channel)
551 result.copyFrom (channel, 0, buf.
getReadPointer (channel), numSamples);
553 if (result.getNumSamples() == 0 || result.getNumChannels() == 0)
555 result.setSize (1, 1);
556 result.setSample (0, 0, 1.0f);
562static AudioBuffer<float> trimImpulseResponse (
const AudioBuffer<float>& buf)
566 const auto numChannels = buf.getNumChannels();
567 const auto numSamples = buf.getNumSamples();
572 for (
auto channel = 0; channel < numChannels; ++channel)
574 const auto indexAboveThreshold = [&] (
auto begin,
auto end)
578 return std::abs (sample) >= thresholdTrim;
582 const auto channelBegin = buf.getReadPointer (channel);
583 const auto channelEnd = channelBegin + numSamples;
584 const auto itStart = indexAboveThreshold (channelBegin, channelEnd);
588 offsetBegin =
jmin (offsetBegin, itStart);
589 offsetEnd =
jmin (offsetEnd, itEnd);
592 if (offsetBegin == numSamples)
594 auto result = AudioBuffer<float> (numChannels, 1);
599 const auto newLength =
jmax (1, numSamples -
static_cast<int> (offsetBegin + offsetEnd));
601 AudioBuffer<float> result (numChannels, newLength);
603 for (
auto channel = 0; channel < numChannels; ++channel)
605 result.copyFrom (channel,
607 buf.getReadPointer (channel,
static_cast<int> (offsetBegin)),
608 result.getNumSamples());
614static float calculateNormalisationFactor (
float sumSquaredMagnitude)
616 if (sumSquaredMagnitude < 1e-8f)
619 return 0.125f /
std::sqrt (sumSquaredMagnitude);
622static void normaliseImpulseResponse (AudioBuffer<float>& buf)
624 const auto numChannels = buf.getNumChannels();
625 const auto numSamples = buf.getNumSamples();
626 const auto channelPtrs = buf.getArrayOfWritePointers();
628 const auto maxSumSquaredMag =
std::accumulate (channelPtrs, channelPtrs + numChannels, 0.0f, [numSamples] (
auto max,
auto* channel)
630 return jmax (max,
std::accumulate (channel, channel + numSamples, 0.0f, [] (
auto sum,
auto samp)
632 return sum + (samp * samp);
636 const auto normalisationFactor = calculateNormalisationFactor (maxSumSquaredMag);
638 std::for_each (channelPtrs, channelPtrs + numChannels, [normalisationFactor, numSamples] (
auto* channel)
640 FloatVectorOperations::multiply (channel, normalisationFactor, numSamples);
644static AudioBuffer<float> resampleImpulseResponse (
const AudioBuffer<float>& buf,
645 const double srcSampleRate,
646 const double destSampleRate)
651 const auto factorReading = srcSampleRate / destSampleRate;
653 AudioBuffer<float> original = buf;
654 MemoryAudioSource memorySource (original,
false);
655 ResamplingAudioSource resamplingSource (&memorySource,
false, buf.getNumChannels());
657 const auto finalSize =
roundToInt (
jmax (1.0, buf.getNumSamples() / factorReading));
658 resamplingSource.setResamplingRatio (factorReading);
659 resamplingSource.prepareToPlay (finalSize, srcSampleRate);
661 AudioBuffer<float> result (buf.getNumChannels(), finalSize);
662 resamplingSource.getNextAudioBlock ({ &result, 0, result.getNumSamples() });
668template <
typename Element>
681 return lock.isLocked() ? std::move (ptr) :
nullptr;
694 : buffer (std::move (bufferIn)), sampleRate (sampleRateIn) {}
697 double sampleRate = 0.0;
706 if (formatReader ==
nullptr)
709 const auto fileLength =
static_cast<size_t> (formatReader->lengthInSamples);
710 const auto lengthToLoad = maxLength == 0 ? fileLength :
jmin (maxLength, fileLength);
712 BufferWithSampleRate result { {
jlimit (1, 2,
static_cast<int> (formatReader->numChannels)),
713 static_cast<int> (lengthToLoad) },
714 formatReader->sampleRate };
716 formatReader->read (result.buffer.getArrayOfWritePointers(),
717 result.buffer.getNumChannels(),
719 result.buffer.getNumSamples());
733 : latency { (requiredLatency.latencyInSamples <= 0) ? 0 :
jmax (64,
nextPowerOfTwo (requiredLatency.latencyInSamples)) },
734 headSize { (requiredHeadSize.headSizeInSamples <= 0) ? 0 :
jmax (64,
nextPowerOfTwo (requiredHeadSize.headSizeInSamples)) },
735 shouldBeZeroLatency (requiredLatency.latencyInSamples == 0)
745 engine.set (makeEngine());
751 Convolution::Stereo stereo,
752 Convolution::Trim trim,
753 Convolution::Normalise normalise)
756 wantsNormalise = normalise;
757 originalSampleRate = buf.sampleRate;
759 impulseResponse = [&]
761 auto corrected = fixNumChannels (buf.buffer, stereo);
762 return trim == Convolution::Trim::yes ? trimImpulseResponse (corrected) : corrected;
765 engine.set (makeEngine());
778 auto resampled = resampleImpulseResponse (impulseResponse, originalSampleRate, processSpec.sampleRate);
780 if (wantsNormalise == Convolution::Normalise::yes)
781 normaliseImpulseResponse (resampled);
783 resampled.applyGain ((
float) (originalSampleRate / processSpec.sampleRate));
785 const auto currentLatency =
jmax (processSpec.maximumBlockSize, (
uint32) latency.latencyInSamples);
786 const auto maxBufferSize = shouldBeZeroLatency ?
static_cast<int> (processSpec.maximumBlockSize)
790 processSpec.maximumBlockSize,
793 shouldBeZeroLatency);
805 double originalSampleRate = processSpec.sampleRate;
806 Convolution::Normalise wantsNormalise = Convolution::Normalise::no;
809 const bool shouldBeZeroLatency;
817 const void* sourceData,
818 size_t sourceDataSize,
819 Convolution::Stereo stereo,
820 Convolution::Trim trim,
822 Convolution::Normalise normalise)
825 stereo, trim, normalise);
828static void setImpulseResponse (ConvolutionEngineFactory& factory,
829 const File& fileImpulseResponse,
830 Convolution::Stereo stereo,
831 Convolution::Trim trim,
833 Convolution::Normalise normalise)
836 stereo, trim, normalise);
852 : messageQueue (queue), factory (latencyIn, headSizeIn) {}
856 Convolution::Stereo stereo,
857 Convolution::Trim trim,
858 Convolution::Normalise normalise)
862 f.setImpulseResponse ({ std::move (b), sr }, stereo, trim, normalise);
866 void loadImpulseResponse (
const void* sourceData,
867 size_t sourceDataSize,
868 Convolution::Stereo stereo,
869 Convolution::Trim trim,
871 Convolution::Normalise normalise)
875 setImpulseResponse (f, sourceData, sourceDataSize, stereo, trim, size, normalise);
879 void loadImpulseResponse (
const File& fileImpulseResponse,
880 Convolution::Stereo stereo,
881 Convolution::Trim trim,
883 Convolution::Normalise normalise)
887 setImpulseResponse (f, fileImpulseResponse, stereo, trim, size, normalise);
893 factory.setProcessSpec (spec);
899 void postPendingCommand()
901 if (pendingCommand ==
nullptr)
904 if (messageQueue.push (pendingCommand))
905 pendingCommand =
nullptr;
911 template <
typename Fn>
912 void callLater (
Fn&& fn)
916 pendingCommand = [weak = weakFromThis(), callback =
std::forward<Fn> (fn)]()
mutable
918 if (
auto t = weak.lock())
919 callback (t->factory);
922 postPendingCommand();
929 BackgroundMessageQueue::IncomingCommand pendingCommand;
937 smoother.setCurrentAndTargetValue (1.0f);
948 template <
typename ProcessCurrent,
typename ProcessPrevious,
typename NotifyDone>
951 ProcessCurrent&& current,
952 ProcessPrevious&& previous,
953 NotifyDone&& notifyDone)
955 if (smoother.isSmoothing())
957 const auto numSamples =
static_cast<int> (input.
getNumSamples());
959 for (
auto sample = 0; sample != numSamples; ++sample)
960 smootherBuffer.setSample (0, sample, smoother.getNextValue());
964 previous (input, mixBlock);
966 for (
size_t channel = 0; channel != output.
getNumChannels(); ++channel)
969 smootherBuffer.getReadPointer (0),
973 FloatVectorOperations::multiply (smootherBuffer.getWritePointer (0), -1.0f, numSamples);
974 FloatVectorOperations::add (smootherBuffer.getWritePointer (0), 1.0f, numSamples);
976 current (input, output);
978 for (
size_t channel = 0; channel != output.
getNumChannels(); ++channel)
981 smootherBuffer.getReadPointer (0),
988 if (! smoother.isSmoothing())
993 current (input, output);
997 void beginTransition()
999 smoother.setCurrentAndTargetValue (1.0f);
1000 smoother.setTargetValue (0.0f);
1017 : messageQueue (std::move (queue)),
1027 if (currentEngine !=
nullptr)
1028 currentEngine->reset();
1030 destroyPreviousEngine();
1035 messageQueue->pimpl->popAll();
1036 mixer.prepare (spec);
1037 engineQueue->prepare (spec);
1039 if (
auto newEngine = engineQueue->getEngine())
1040 currentEngine = std::move (newEngine);
1042 previousEngine =
nullptr;
1043 jassert (currentEngine !=
nullptr);
1048 engineQueue->postPendingCommand();
1050 if (previousEngine ==
nullptr)
1051 installPendingEngine();
1053 mixer.processSamples (input,
1057 currentEngine->processSamples (in, out);
1061 if (previousEngine !=
nullptr)
1062 previousEngine->processSamples (in, out);
1066 [
this] { destroyPreviousEngine(); });
1069 int getCurrentIRSize()
const {
return currentEngine !=
nullptr ? currentEngine->getIRSize() : 0; }
1071 int getLatency()
const {
return currentEngine !=
nullptr ? currentEngine->getLatency() : 0; }
1074 double originalSampleRate,
1077 Normalise normalise)
1079 engineQueue->loadImpulseResponse (std::move (buffer), originalSampleRate, stereo, trim, normalise);
1082 void loadImpulseResponse (
const void* sourceData,
1083 size_t sourceDataSize,
1087 Normalise normalise)
1089 engineQueue->loadImpulseResponse (sourceData, sourceDataSize, stereo, trim, size, normalise);
1092 void loadImpulseResponse (
const File& fileImpulseResponse,
1096 Normalise normalise)
1098 engineQueue->loadImpulseResponse (fileImpulseResponse, stereo, trim, size, normalise);
1102 void destroyPreviousEngine()
1105 BackgroundMessageQueue::IncomingCommand command = [p = std::move (previousEngine)]()
mutable { p =
nullptr; };
1106 messageQueue->pimpl->push (command);
1111 destroyPreviousEngine();
1112 previousEngine = std::move (currentEngine);
1113 currentEngine = std::move (newEngine);
1114 mixer.beginTransition();
1117 void installPendingEngine()
1119 if (
auto newEngine = engineQueue->getEngine())
1120 installNewEngine (std::move (newEngine));
1130void Convolution::Mixer::prepare (
const ProcessSpec& spec)
1132 for (
auto& dry : volumeDry)
1133 dry.reset (spec.sampleRate, 0.05);
1135 for (
auto& wet : volumeWet)
1136 wet.reset (spec.sampleRate, 0.05);
1146template <
typename ProcessWet>
1150 ProcessWet&& processWet)
noexcept
1152 const auto numChannels =
jmin (input.getNumChannels(), volumeDry.size());
1153 const auto numSamples =
jmin (input.getNumSamples(), output.getNumSamples());
1155 auto dry = dryBlock.getSubsetChannelBlock (0, numChannels);
1157 if (volumeDry[0].isSmoothing())
1159 dry.copyFrom (input);
1161 for (
size_t channel = 0; channel < numChannels; ++channel)
1162 volumeDry[channel].applyGain (dry.getChannelPointer (channel), (
int) numSamples);
1164 processWet (input, output);
1166 for (
size_t channel = 0; channel < numChannels; ++channel)
1167 volumeWet[channel].applyGain (output.getChannelPointer (channel), (
int) numSamples);
1173 if (! currentIsBypassed)
1174 processWet (input, output);
1176 if (isBypassed != currentIsBypassed)
1178 currentIsBypassed = isBypassed;
1180 for (
size_t channel = 0; channel < numChannels; ++channel)
1182 volumeDry[channel].setTargetValue (isBypassed ? 0.0f : 1.0f);
1183 volumeDry[channel].reset (sampleRate, 0.05);
1184 volumeDry[channel].setTargetValue (isBypassed ? 1.0f : 0.0f);
1186 volumeWet[channel].setTargetValue (isBypassed ? 1.0f : 0.0f);
1187 volumeWet[channel].reset (sampleRate, 0.05);
1188 volumeWet[channel].setTargetValue (isBypassed ? 0.0f : 1.0f);
1194void Convolution::Mixer::reset() { dryBlock.clear(); }
1197Convolution::Convolution()
1226 const NonUniform& nonUniform,
1227 OptionalQueue&& queue)
1228 : pimpl (
std::make_unique<Impl> (latency, nonUniform,
std::move (queue)))
1231Convolution::~Convolution() noexcept = default;
1234 size_t sourceDataSize,
1238 Normalise normalise)
1247 Normalise normalise)
1249 pimpl->loadImpulseResponse (fileImpulseResponse, stereo, trim, size, normalise);
1253 double originalSampleRate,
1256 Normalise normalise)
1258 pimpl->loadImpulseResponse (std::move (buffer), originalSampleRate, stereo, trim, normalise);
1263 mixer.prepare (spec);
1264 pimpl->prepare (spec);
1276 bool isBypassed)
noexcept
1281 jassert (input.getNumChannels() == output.getNumChannels());
1284 mixer.processSamples (input, output, isBypassed, [
this] (
const auto& in,
auto& out)
1286 pimpl->processSamples (in, out);
void forEach(FunctionToApply &&func) const
Calls the passed function with each index that was deemed valid for the current read/write operation.
Encapsulates the logic required to implement a lock-free FIFO.
ScopedRead read(int numToRead) noexcept
Replaces prepareToRead/finishedRead with a single function.
int getFreeSpace() const noexcept
Returns the number of items that can currently be added to the buffer without it overflowing.
ScopedWrite write(int numToWrite) noexcept
Replaces prepareToWrite/finishedWrite with a single function.
int getNumReady() const noexcept
Returns the number of items that can currently be read from the buffer.
A multi-channel buffer containing floating point audio samples.
int getNumChannels() const noexcept
Returns the number of channels of audio data that this buffer contains.
int getNumSamples() const noexcept
Returns the number of samples allocated in each of the buffer's channels.
void setSample(int destChannel, int destSample, Type newValue) noexcept
Sets a sample in the buffer.
const Type * getReadPointer(int channelNumber) const noexcept
Returns a pointer to an array of read-only samples in one of the buffer's channels.
static Type decibelsToGain(Type decibels, Type minusInfinityDb=Type(defaultMinusInfinitydB))
Converts a dBFS value to its equivalent gain level.
Represents a local file or directory.
Automatically locks and unlocks a mutex object.
Automatically locks and unlocks a mutex object.
A utility class for values that need smoothing to avoid audio glitches.
A simple spin-lock class that can be used as a simple, low-overhead mutex for uncontended situations.
bool startThread()
Attempts to start a new thread with default ('Priority::normal') priority.
bool threadShouldExit() const
Checks whether the thread has been told to stop running.
bool stopThread(int timeOutMilliseconds)
Attempts to stop the thread running.
Minimal and lightweight data-structure which contains a list of pointers to channels containing some ...
AudioBlock getSubBlock(size_t newOffset, size_t newLength) const noexcept
Return a new AudioBlock pointing to a sub-block inside this block.
constexpr size_t getNumChannels() const noexcept
Returns the number of channels referenced by this block.
SampleType * getChannelPointer(size_t channel) const noexcept
Returns a raw pointer into one of the channels in this block.
AudioBlock & copyFrom(const AudioBlock< OtherSampleType > &src) noexcept
Copies the values in src to this block.
AudioBlock getSingleChannelBlock(size_t channel) const noexcept
Returns an AudioBlock that represents one of the channels in this block.
constexpr size_t getNumSamples() const noexcept
Returns the number of samples referenced by this block.
AudioBlock & clear() noexcept
Clears the memory referenced by this AudioBlock.
Used by the Convolution to dispatch engine-update messages on a background thread.
ConvolutionMessageQueue()
Initialises the queue to a default size.
Performs stereo partitioned convolution of an input signal with an impulse response in the frequency ...
int getCurrentIRSize() const
This function returns the size of the current IR in samples.
Convolution()
Initialises an object for performing convolution in the frequency domain.
int getLatency() const
This function returns the current latency of the process in samples.
void prepare(const ProcessSpec &)
Must be called before first calling process.
void loadImpulseResponse(const void *sourceData, size_t sourceDataSize, Stereo isStereo, Trim requiresTrimming, size_t size, Normalise requiresNormalisation=Normalise::yes)
This function loads an impulse response audio file from memory, added in a JUCE project with the Proj...
void reset() noexcept
Resets the processing pipeline ready to start a new stream of data.
Contains configuration information for a convolution with a fixed latency.
auto & get(ProcessorChain< Processors... > &chain) noexcept
Non-member equivalent of ProcessorChain::get which avoids awkward member template syntax.
T make_reverse_iterator(T... args)
constexpr bool approximatelyEqual(Type a, Type b, Tolerance< Type > tolerance=Tolerance< Type >{} .withAbsolute(std::numeric_limits< Type >::min()) .withRelative(std::numeric_limits< Type >::epsilon()))
Returns true if the two floating-point numbers are approximately equal.
constexpr Type jmin(Type a, Type b)
Returns the smaller of two values.
constexpr Type jmax(Type a, Type b)
Returns the larger of two values.
RangedDirectoryIterator end(const RangedDirectoryIterator &)
Returns a default-constructed sentinel value.
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
Constrains a value to keep it within a given range.
int nextPowerOfTwo(int n) noexcept
Returns the smallest power-of-two which is equal to or greater than the given integer.
bool isPositiveAndBelow(Type1 valueToTest, Type2 upperLimit) noexcept
Returns true if a value is at least zero, and also below a specified upper limit.
unsigned int uint32
A platform-independent 32-bit unsigned integer type.
int roundToInt(const FloatType value) noexcept
Fast floating-point-to-integer conversion.
RangedDirectoryIterator begin(const RangedDirectoryIterator &it)
Returns the iterator that was passed in.
This structure is passed into a DSP algorithm's prepare() method, and contains information about vari...
uint32 numChannels
The number of channels that the process() method will be expected to handle.
double sampleRate
The sample rate that will be used for the data that is sent to the processor.
uint32 maximumBlockSize
The maximum number of samples that will be in the blocks sent to process() method.