quantumVERB  1.0.0
A FOSS convolution reverb plugin
PluginProcessor.cpp
1 /*
2  ==============================================================================
3 
4  PluginProcessor.cpp
5 
6  ==============================================================================
7 */
8 
9 #include "PluginProcessor.h"
10 
11 #include "IRBank.h"
12 #include "PluginEditor.h"
13 #include "Logger.h"
14 
15 #ifdef WIN32
16 #include <windows.h>
17 #endif
18 
19 #include <thread>
20 
21 namespace reverb
22 {
23 
24  //==============================================================================
25  /**
26  * @brief Constructs a reverb audio processor & initialises its inner pipelines
27  */
29  : parameters(*this, nullptr)
30 #ifndef JucePlugin_PreferredChannelConfigurations
32 #if ! JucePlugin_IsMidiEffect
33 #if ! JucePlugin_IsSynth
34  .withInput("Input", juce::AudioChannelSet::stereo(), true)
35 #endif
36  .withOutput("Output", juce::AudioChannelSet::stereo(), true)
37 #endif
38  )
39 #endif
40  {
42  }
43 
44  AudioProcessor::~AudioProcessor()
45  {
46  std::lock_guard<std::mutex> lock(updatingParams);
47  }
48 
49  //==============================================================================
50  const juce::String AudioProcessor::getName() const
51  {
52  return JucePlugin_Name;
53  }
54 
55  bool AudioProcessor::acceptsMidi() const
56  {
57 #if JucePlugin_WantsMidiInput
58  return true;
59 #else
60  return false;
61 #endif
62  }
63 
64  bool AudioProcessor::producesMidi() const
65  {
66 #if JucePlugin_ProducesMidiOutput
67  return true;
68 #else
69  return false;
70 #endif
71  }
72 
73  bool AudioProcessor::isMidiEffect() const
74  {
75 #if JucePlugin_IsMidiEffect
76  return true;
77 #else
78  return false;
79 #endif
80  }
81 
82  double AudioProcessor::getTailLengthSeconds() const
83  {
84  return 0.0;
85  }
86 
87  int AudioProcessor::getNumPrograms()
88  {
89  return 1; // NB: some hosts don't cope very well if you tell them there are 0 programs,
90  // so this should be at least 1, even if you're not really implementing programs.
91  }
92 
93  int AudioProcessor::getCurrentProgram()
94  {
95  return 0;
96  }
97 
98  void AudioProcessor::setCurrentProgram(int)
99  {
100  }
101 
102  const juce::String AudioProcessor::getProgramName(int)
103  {
104  return {};
105  }
106 
107  void AudioProcessor::changeProgramName(int, const juce::String&)
108  {
109  }
110 
111  //==============================================================================
112  /**
113  * @brief Prepare the processor before playback starts
114  *
115  * Called before playback starts, to let the processor prepare itself.
116  *
117  * You can call getTotalNumInputChannels and getTotalNumOutputChannels or query
118  * the busLayout member variable to find out the number of channels your
119  * processBlock callback must process.
120  *
121  * The samplesPerBlock value is a strong hint about the maximum
122  * number of samples that will be provided in each block. You may want to use
123  * this value to resize internal buffers. You should program defensively in case
124  * a buggy host exceeds this value. The actual block sizes that the host uses
125  * may be different each time the callback happens: completely variable block
126  * sizes can be expected from some hosts.
127  *
128  * @param sampleRate [in] Target sample rate (constant until playback stops)
129  * @param samplesPerBlock [in] Hint about max. expected samples in upcoming block
130  */
131  void AudioProcessor::prepareToPlay(double sampleRate, int)
132  {
133  size_t numChannels = getTotalNumInputChannels();
134 
135  if (getTotalNumInputChannels() != getTotalNumOutputChannels())
136  {
137  std::string errMsg = "Mismatch between input (" +
138  std::to_string(getTotalNumInputChannels()) +
139  ") and output (" +
140  std::to_string(getTotalNumOutputChannels()) +
141  ") channels. Forcing output to " +
142  std::to_string(numChannels) +
143  " channels.";
144 
145  logger.dualPrint(Logger::Level::Error, errMsg);
146  }
147 
148  // Add/remove pipelines as needed to meet requested number of channels
149  for (size_t i = numChannels; i < irPipelines.size(); ++i)
150  {
151  irPipelines.pop_back();
152  }
153 
154  for (size_t i = irPipelines.size(); i < numChannels; ++i)
155  {
156  irPipelines.emplace_back(new IRPipeline(this, (int)i));
157  }
158 
159  for (size_t i = numChannels; i < mainPipelines.size(); ++i)
160  {
161  mainPipelines.pop_back();
162  }
163 
164  for (size_t i = mainPipelines.size(); i < numChannels; ++i)
165  {
166  mainPipelines.emplace_back(new MainPipeline(this));
167  }
168 
169  // Update parameters across pipelines
170  updateParams(sampleRate);
171  }
172 
173  /**
174  * @brief Release resource when playback stops
175  *
176  * Used when playback stops as an opportunity to free up any spare memory, etc.
177  */
179  {
180  }
181 
182 #ifndef JucePlugin_PreferredChannelConfigurations
184  {
185 #if JucePlugin_IsMidiEffect
187  return true;
188 #else
189  // This is the place where you check if the layout is supported.
190  // In this template code we only support mono or stereo.
193  return false;
194 
195  // This checks if the input layout matches the output layout
196 #if ! JucePlugin_IsSynth
198  return false;
199 #endif
200 
201  return true;
202 #endif
203  }
204 #endif
205 
206  /**
207  * @brief Applies reverb effect on audio buffer using parameters given through the editor
208  *
209  * @param [in] buffer Audio buffer containing samples to process
210  * @param midiMessages Unused
211  */
212  void AudioProcessor::processBlock(juce::AudioSampleBuffer& audio, juce::MidiBuffer&)
213  {
214 #ifdef WIN32
215  SetThreadPriority(GetCurrentThread(),
216  THREAD_PRIORITY_TIME_CRITICAL);
217 #endif
218 
219  // Reset the bypass detection parameter
220  auto activeParam = parameters.getParameter(PID_ACTIVE);
221 
222  if (activeParam->getValue() < 0.5f)
223  {
224  activeParam->beginChangeGesture();
225  activeParam->setValueNotifyingHost(1.0f);
226  activeParam->endChangeGesture();
227  }
228 
229  // Disable denormals for performance
230  juce::ScopedNoDenormals noDenormals;
231 
232  // In case we have more outputs than inputs, this code clears any output
233  // channels that didn't contain input data, (because these aren't
234  // guaranteed to be empty - they may contain garbage).
235  const int totalNumInputChannels = getTotalNumInputChannels();
236  const int totalNumOutputChannels = getTotalNumOutputChannels();
237 
238  for (int i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
239  {
240  audio.clear(i, 0, audio.getNumSamples());
241  }
242 
243  // We should have the right number of pipelines and channel buffers from
244  // a previous call to prepareToPlay(), but just to be safe let's check
245  // these values here.
246  for (size_t i = irPipelines.size(); i < totalNumInputChannels; ++i)
247  {
248  irPipelines.emplace_back(new IRPipeline(this, (int)i));
249  }
250 
251  for (size_t i = mainPipelines.size(); i < totalNumInputChannels; ++i)
252  {
253  mainPipelines.emplace_back(new MainPipeline(this));
254  }
255 
256  // Associate audio block with input
257  audioChannels = audio;
258 
259  // Update parameters asynchronously
260  if (blocksProcessed % NUM_BLOCKS_PER_UPDATE_PARAMS)
261  {
262  std::unique_lock<std::mutex> lock(updatingParams, std::try_to_lock);
263 
264  // If parameters are already being updated, skip this and go straight
265  // to processing.
266  if (lock.owns_lock())
267  {
268  std::thread updateParamsThread(&AudioProcessor::updateParams,
269  this, getSampleRate());
270 
271  lock.unlock();
272 
273  // updateParams() uses double buffering to update everything
274  // asynchronously, so we can let it do its own thing.
275  updateParamsThread.detach();
276  }
277  }
278 
279 #if REVERB_MULTITHREADED > 0
280  // Process each channel in its own thread
281  std::vector<std::thread> channelThreads;
282 
283  for (int i = 0; i < totalNumInputChannels; ++i)
284  {
285  channelThreads.emplace_back(&AudioProcessor::processChannel, this, i);
286  }
287 
288  // Wait for each thread to complete
289  for (int i = 0; i < channelThreads.size(); ++i)
290  {
291  channelThreads[i].join();
292  }
293 #else
294  for (int i = 0; i < totalNumInputChannels; ++i)
295  {
297  }
298 #endif
299 
300  blocksProcessed++;
301 
302 #ifdef WIN32
303  SetThreadPriority(GetCurrentThread(),
304  THREAD_PRIORITY_NORMAL);
305 #endif
306  }
307 
308  /**
309  * @brief Applies reverb effect on single channel using preconfigured pipelines
310  *
311  * @param [in] channelIdx Index of channel to process
312  */
313  void AudioProcessor::processChannel(int channelIdx)
314  {
315 #ifdef WIN32
316  SetThreadPriority(GetCurrentThread(),
317  THREAD_PRIORITY_TIME_CRITICAL);
318 #endif
319 
320  AudioBlock channelAudio = audioChannels.getSingleChannelBlock(channelIdx);
321  mainPipelines[channelIdx]->exec(channelAudio);
322 
323 #ifdef WIN32
324  SetThreadPriority(GetCurrentThread(),
325  THREAD_PRIORITY_NORMAL);
326 #endif
327  }
328 
329  //==============================================================================
330  /**
331  * @brief Update parameters across all channels
332  *
333  * Update parameters in all IRPipeline and MainPipeline objects. If necessary,
334  * reprocess IR and add to MainPipeline.
335  *
336  * MainPipeline is double-buffered to avoid interfering with processChannel().
337  * IRPipeline is not used by any other methods, so it does not need protection.
338  *
339  * @param [in] sampleRate Current sample rate
340  */
341  void AudioProcessor::updateParams(double sampleRate)
342  {
343 #ifdef WIN32
344  SetThreadPriority(GetCurrentThread(),
345  THREAD_MODE_BACKGROUND_BEGIN);
346 #endif
347 
348  // Obtain lock
349  std::lock_guard<std::mutex> lock(updatingParams);
350 
351  // Check number of channels
352  const int numChannels = (int)irPipelines.size();
353 
354  jassert(mainPipelines.size() == numChannels);
355 
356  // Process parameters for each channel
357  for (int i = 0; i < numChannels; ++i)
358  {
359  updateParamsForChannel(i, sampleRate);
360  }
361 
362 #ifdef WIN32
363  SetThreadPriority(GetCurrentThread(),
364  THREAD_MODE_BACKGROUND_END);
365 #endif
366  }
367 
368  /**
369  * @brief Update parameters for a given channel
370  *
371  * Update parameters in IRPipeline and Mainpipeline for given channel. If
372  * necessary, reprocess IR using new parameters and copy to MainPipeline.
373  *
374  * MainPipeline is double-buffered to avoid interfering with processChannel().
375  * IRPipeline is not used by any other methods, so it does not need protection.
376  *
377  * @param [in] channelIdx Channel parameters should be updated for
378  * @param [in] sampleRate Current sample rate
379  */
380  void AudioProcessor::updateParamsForChannel(int channelIdx, double sampleRate)
381  {
382  auto& processorLock = getCallbackLock();
383 
384  auto& irPipeline = irPipelines[channelIdx];
385  auto& mainPipeline = mainPipelines[channelIdx];
386 
387  AudioBlock irChannel;
388 
389  // Update IR parameters
390  irPipeline->updateSampleRate(sampleRate);
391  irPipeline->updateParams(parameters);
392 
393  // Reprocess IR if necessary
394  bool updateIR = irPipeline->needsToRun();
395 
396  if (updateIR)
397  {
398  irChannel = irPipeline->exec();
399  }
400 
401  // Update main parameters (critical section: mainPipeline is used by
402  // processChannel)
403  {
404  juce::ScopedLock lock(processorLock);
405 
406  mainPipeline->updateParams(parameters);
407  mainPipeline->updateSampleRate(sampleRate);
408 
409  if (updateIR)
410  {
411  mainPipeline->loadIR(irChannel);
412  }
413  }
414  }
415 
416  //==============================================================================
417  void AudioProcessor::processBlockBypassed(juce::AudioSampleBuffer& audio, juce::MidiBuffer& midi)
418  {
419  // Set the bypass detection parameter
420  auto activeParam = parameters.getParameter(PID_ACTIVE);
421 
422  if (activeParam->getValue() > 0.5f)
423  {
424  activeParam->beginChangeGesture();
425  activeParam->setValueNotifyingHost(0.0f);
426  activeParam->endChangeGesture();
427  }
428 
429 
430  // Run JUCE's regular implementation
431  this->juce::AudioProcessor::processBlockBypassed(audio, midi);
432  }
433 
434  //==============================================================================
435  bool AudioProcessor::hasEditor() const
436  {
437  return true; // (change this to false if you choose to not supply an editor)
438  }
439 
440  juce::AudioProcessorEditor* AudioProcessor::createEditor()
441  {
442  return new AudioProcessorEditor(*this);
443  }
444 
445  //==============================================================================
446  void AudioProcessor::getStateInformation(juce::MemoryBlock& destData)
447  {
448  std::unique_ptr<juce::XmlElement> xmlState(parameters.state.createXml());
449 
450  if (xmlState)
451  {
452  copyXmlToBinary(*xmlState, destData);
453  }
454  else
455  {
456  logger.dualPrint(Logger::Level::Error, "Failed to get state information");
457  }
458  }
459 
460  void AudioProcessor::setStateInformation(const void* data, int sizeInBytes)
461  {
462  std::unique_ptr<juce::XmlElement> xmlState(getXmlFromBinary(data, sizeInBytes));
463 
464  if (xmlState && xmlState->hasTagName(parameters.state.getType()))
465  {
466  parameters.state = juce::ValueTree::fromXml(*xmlState);
467  }
468  else
469  {
470  logger.dualPrint(Logger::Level::Error, "Failed to set state information");
471  }
472  }
473 
474  //==============================================================================
476  {
477  // Only build parameters once. This saves work and avoids memory leaks due to
478  // allocating memory for the same parameter more than once.
479  if (paramsInitialised)
480  {
481  return;
482  }
483 
484  static const float gain12dB = juce::Decibels::decibelsToGain<float>(15);
485  static const float gain6dBneg = juce::Decibels::decibelsToGain<float>(-6);
486  static const float gain24dBneg = juce::Decibels::decibelsToGain<float>(-24);
487  static const float gain50dBneg = juce::Decibels::decibelsToGain<float>(-50);
488  static const float gain15dBInv = 1.0f / gain12dB;
489 
490  auto fnGainTodBString = [](float gain) { return juce::String(juce::Decibels::gainToDecibels(gain), 2) + " dB"; };
491 
492  /**
493  * Low-shelf filter
494  */
495  parameters.createAndAddParameter( PID_FILTER_PREFIX + std::to_string(0) + PID_FILTER_FREQ_SUFFIX,
496  "EQ: Low-shelf cut-off frequency", "<16-520 Hz>",
497  juce::Range<float>(16.0f, 520.0f),
498  100.0f,
499  nullptr, nullptr );
500 
501 
502  parameters.createAndAddParameter( PID_FILTER_PREFIX + std::to_string(0) + PID_FILTER_Q_SUFFIX,
503  "EQ: Low-shelf Q factor", "<0.71-1.41>",
504  juce::Range<float>(0.71f, 1.41f),
505  1.06f,
506  nullptr, nullptr );
507 
508  parameters.createAndAddParameter( PID_FILTER_PREFIX + std::to_string(0) + PID_FILTER_GAIN_SUFFIX,
509  "EQ: Low-shelf gain factor", "<" + juce::String(gain15dBInv) + " = no change>",
510  juce::Range<float>(gain24dBneg, gain12dB),
511  gain6dBneg,
512  fnGainTodBString, nullptr );
513 
514 
515  /**
516  * Peak filter 1
517  */
518  parameters.createAndAddParameter( PID_FILTER_PREFIX + std::to_string(1) + PID_FILTER_FREQ_SUFFIX,
519  "EQ: Peak filter 1 cut-off frequency", "<250-1500 Hz>",
520  juce::NormalisableRange<float>(250.0f, 1500.0f),
521  800.0f,
522  nullptr, nullptr );
523 
524  parameters.createAndAddParameter( PID_FILTER_PREFIX + std::to_string(1) + PID_FILTER_Q_SUFFIX,
525  "EQ: Peak filter 1 Q factor", "<0.71-6.5>",
526  juce::NormalisableRange<float>(0.71f, 6.5f),
527  3.38f,
528  nullptr, nullptr );
529 
530  parameters.createAndAddParameter( PID_FILTER_PREFIX + std::to_string(1) + PID_FILTER_GAIN_SUFFIX,
531  "EQ: Peak filter 1 gain factor", "<" + juce::String(gain15dBInv) + " = no change>",
532  juce::NormalisableRange<float>(gain24dBneg, gain12dB),
533  gain6dBneg,
534  fnGainTodBString, nullptr );
535 
536 
537  /**
538  * Peak filter 2
539  */
540  parameters.createAndAddParameter( PID_FILTER_PREFIX + std::to_string(2) + PID_FILTER_FREQ_SUFFIX,
541  "EQ: Peak filter 2 cut-off frequency", "<2600-15600 Hz>",
542  juce::NormalisableRange<float>(2600.0f, 15600.0f),
543  8000.0f,
544  nullptr, nullptr );
545 
546  parameters.createAndAddParameter( PID_FILTER_PREFIX + std::to_string(2) + PID_FILTER_Q_SUFFIX,
547  "EQ: Peak filter 2 Q factor", "<0.71-6.50>",
548  juce::NormalisableRange<float>(0.71f, 6.50f),
549  3.38f,
550  nullptr, nullptr );
551 
552  parameters.createAndAddParameter( PID_FILTER_PREFIX + std::to_string(2) + PID_FILTER_GAIN_SUFFIX,
553  "EQ: Peak filter 2 gain factor", "<" + juce::String(gain15dBInv) + " = no change>",
554  juce::NormalisableRange<float>(gain24dBneg, gain12dB),
555  gain6dBneg,
556  fnGainTodBString, nullptr );
557 
558 
559  /**
560  * High-shelf filter
561  */
562  parameters.createAndAddParameter( PID_FILTER_PREFIX + std::to_string(3) + PID_FILTER_FREQ_SUFFIX,
563  "EQ: High-shelf cut-off frequency", "<8000-21000 Hz>",
564  juce::NormalisableRange<float>(8000.0f, 21000.0f),
565  10000.0f,
566  nullptr, nullptr );
567 
568  parameters.createAndAddParameter( PID_FILTER_PREFIX + std::to_string(3) + PID_FILTER_Q_SUFFIX,
569  "EQ: High-shelf Q factor", "<0.71-1.41>",
570  juce::NormalisableRange<float>(0.71f, 1.41f),
571  1.06f,
572  nullptr, nullptr );
573 
574  parameters.createAndAddParameter( PID_FILTER_PREFIX + std::to_string(3) + PID_FILTER_GAIN_SUFFIX,
575  "EQ: High-shelf gain factor", "<" + juce::String(gain15dBInv) + " = no change>",
576  juce::NormalisableRange<float>(gain24dBneg, gain12dB),
577  gain6dBneg,
578  fnGainTodBString, nullptr );
579 
580 
581  /**
582  * IR gain
583  */
584  parameters.createAndAddParameter( PID_IR_GAIN,
585  "Impulse response gain", "<" + juce::String(1.0) + " = no change>",
586  juce::NormalisableRange<float>(0.0f, 1.0f),
587  0.5f,
588  fnGainTodBString, nullptr );
589 
590  /**
591  * IR length
592  */
593  parameters.createAndAddParameter( PID_IR_LENGTH,
594  "Reverb length", "<s>",
595  juce::NormalisableRange<float>(0.1f, 5.0f),
596  3.0f,
597  nullptr, nullptr);
598 
599  /**
600  * Pre-delay
601  */
602  parameters.createAndAddParameter( PID_PREDELAY,
603  "Pre-delay", "<0-1000 ms>",
604  juce::NormalisableRange<float>(0.0f, 1000.0f),
605  0.0f,
606  nullptr, nullptr );
607 
608 
609  /**
610  * Dry/wet mixer
611  */
612  parameters.createAndAddParameter( PID_WETRATIO,
613  "Dry/wet ratio", "<0 = dry, 1 = wet>",
614  juce::NormalisableRange<float>(0.0f, 1.0f),
615  0.5f,
616  nullptr, nullptr );
617 
618 
619  /**
620  * Output gain
621  */
622  parameters.createAndAddParameter( PID_AUDIO_OUT_GAIN,
623  "Output gain", "<" + juce::String(gain15dBInv) + "= no change>",
624  juce::NormalisableRange<float>(gain50dBneg, 1.0f),
625  1.0f,
626  fnGainTodBString, nullptr );
627 
628 
629  /**
630  * Meta: Bypass detection flag
631  */
632  parameters.createAndAddParameter( PID_ACTIVE, "Active", "<y/n>",
633  juce::NormalisableRange<float>(0.0f, 1.0f),
634  1.0f,
635  nullptr, nullptr,
636  false, true, true );
637 
638 
639  parameters.state = juce::ValueTree(juce::Identifier(JucePlugin_Name));
640 
641  /**
642  * Impulse responses
643  *
644  * IR file choice is given by a string that may be one of the following:
645  * 1) name of a valid audio resource in BinaryData.h
646  * 2) full path to user-provided IR file
647  */
648  juce::ValueTree irFile(PID_IR_FILE_CHOICE);
649 
650  auto& irBank = IRBank::getInstance();
651  if (irBank.buffers.begin() != irBank.buffers.end())
652  {
653  const juce::String& firstBankedIR = irBank.buffers.begin()->first;
654  irFile.setProperty("value", firstBankedIR, nullptr);
655  }
656 
657  parameters.state.addChild(irFile, -1, nullptr);
658 
659 
660  /**
661  * END: Processor is responsible for deleting parameters on destruction
662  */
663  paramsInitialised = true;
664  }
665 
666 }
667 
668 //==============================================================================
669 // This creates new instances of the plugin..
670 juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter()
671 {
672  return new reverb::AudioProcessor();
673 }
float getParam(const juce::AudioProcessorValueTreeState &params, const juce::String &blockId) const
Internal method used to get (and check) a parameter&#39;s value.
Definition: Task.h:111
void updateParams(double sampleRate)
Update parameters across all channels.
void processChannel(int channelIdx)
Applies reverb effect on single channel using preconfigured pipelines.
void updateParamsForChannel(int channelIdx, double sampleRate)
Update parameters for a given channel.
static const IRBank & getInstance()
Definition: IRBank.cpp:18
void prepareToPlay(double sampleRate, int samplesPerBlock) override
Prepare the processor before playback starts.
void releaseResources() override
Release resource when playback stops.
AudioProcessor()
Constructs a reverb audio processor & initialises its inner pipelines.
void processBlock(juce::AudioSampleBuffer &audio, juce::MidiBuffer &) override
Applies reverb effect on audio buffer using parameters given through the editor.