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