quantumVERB  1.0.0
A FOSS convolution reverb plugin
IRPipeline.cpp
1 /*
2  ==============================================================================
3 
4  IRPipeline.cpp
5 
6  ==============================================================================
7 */
8 
9 #include "IRPipeline.h"
10 
11 #include "IRBank.h"
12 #include "Logger.h"
13 #include "PluginProcessor.h"
14 
15 #include <algorithm>
16 
17 namespace reverb
18 {
19 
20  //==============================================================================
21  /**
22  * @brief Constructs an IRPipeline object associated with an AudioProcessor
23  *
24  * Creates an IRPipeline and each of its steps. Also sets the default IR file path
25  * and ensures it can be found.
26  *
27  * @param [in] processor Pointer to main processor
28  */
29  IRPipeline::IRPipeline(juce::AudioProcessor * processor, int channelIdx)
30  : Task(processor),
31  channelIdx(channelIdx)
32  {
33  // Initialise pipeline steps
34  equalizer = std::make_shared<Equalizer>(processor);
35  timeStretch = std::make_shared<TimeStretch>(processor);
36  gain = std::make_shared<Gain>(processor);
37  preDelay = std::make_shared<PreDelay>(processor);
38  }
39 
40  /**
41  * @brief Updates parameters from processor parameter tree
42  *
43  * @param [in] params Processor parameter tree
44  * @param [in] blockId ID of block whose paramters should be checked
45  */
46  void IRPipeline::updateParams(const juce::AudioProcessorValueTreeState& params,
47  const juce::String&)
48  {
49  // Update pipeline parameters
50  auto paramIRChoice = params.state.getChildWithName(AudioProcessor::PID_IR_FILE_CHOICE);
51  if (paramIRChoice.getProperty("value") != irNameOrFilePath)
52  {
53  irNameOrFilePath = paramIRChoice.getProperty("value").toString().toStdString();
54  mustExec = true;
55  }
56 
57  // Update child parameters
58  for (int i = 0; i < equalizer->getNumFilters(); i++)
59  {
60  std::string filterId = AudioProcessor::PID_FILTER_PREFIX + std::to_string(i);
61  try
62  {
63  equalizer->updateParams(params, filterId);
64  }
65  catch (const std::exception& e)
66  {
67  std::string errMsg = "Could not update filters due to exception: ";
68  errMsg += e.what();
69 
70  logger.dualPrint(Logger::Level::Error, errMsg);
71  }
72  }
73 
74  gain->updateParams(params, AudioProcessor::PID_IR_GAIN);
75  preDelay->updateParams(params, AudioProcessor::PID_PREDELAY);
76  timeStretch->updateParams(params, AudioProcessor::PID_IR_LENGTH);
77  }
78 
79  //==============================================================================
80  /**
81  * @brief Update sample rate for pipeline and child tasks
82  *
83  * Compares new sample rate with previous value. If different, sets mustExec to
84  * true in order to re-run pipeline for new sample rate. Store new sample rate
85  * value in object.
86  *
87  * @param [in] sr Sample rate
88  */
89  void IRPipeline::updateSampleRate(double sr)
90  {
91  if (sr != sampleRate)
92  {
93  sampleRate = sr;
94  mustExec = true;
95 
96  equalizer->updateSampleRate(sr);
97  gain->updateSampleRate(sr);
98  preDelay->updateSampleRate(sr);
99  timeStretch->updateSampleRate(sr);
100  }
101  }
102 
103  //==============================================================================
104  /**
105  * @brief Check if this or any subtasks needs to be executed
106  *
107  * Pipeline must be executed if any IR-related parameters were changed since the
108  * last run.
109  *
110  * @returns True if IRPipeline must be executed
111  */
112  bool IRPipeline::needsToRun() const
113  {
114  if (mustExec)
115  {
116  return true;
117  }
118 
119  if (equalizer->needsToRun())
120  {
121  return true;
122  }
123 
124  if (timeStretch->needsToRun())
125  {
126  return true;
127  }
128 
129  if (gain->needsToRun())
130  {
131  return true;
132  }
133 
134  if (preDelay->needsToRun())
135  {
136  return true;
137  }
138 
139  return false;
140  }
141 
142  //==============================================================================
143  /**
144  * @brief Manipulate the input IR file and place it in the given buffer
145  *
146  * Applies filtering (EQ), time stretching (sample rate conversion) and gain to
147  * internal IR channel buffers to prepare it for main audio processing, then write
148  * channels to given output buffer.
149  *
150  * @param [out] irChannelOut Processed impulse response channel
151  *
152  * @throws std::runtime_error
153  */
154  AudioBlock IRPipeline::exec(AudioBlock)
155  {
157 
158  // Apply filters
160 
161  // Apply gain
162  gain->exec(irBlock);
163 
164  // Resize buffer and apply timestretch
166  irBlock = ir;
167 
169 
170  // Resize buffer and apply predelay
172  irBlock = ir;
173 
175 
176  // Reset mustExec flag
177  mustExec = false;
178 
179  // Return reference to processed IR channel
180  return irBlock;
181  }
182 
183  //==============================================================================
184  /**
185  * @brief Loads an impulse response from disk or IR bank
186  *
187  * If given string is the name of an IR in IR bank, load that buffer. Otherwise,
188  * look for IR on disk.
189  *
190  * @param [in] irName Name or path of IR file
191  *
192  * @throws std::invalid_argument
193  */
195  {
196  if (irNameOrFilePath.empty())
197  {
198  throw std::invalid_argument("Received invalid IR name/path");
199  }
200 
201  auto& irBank = IRBank::getInstance();
203  {
205  }
206  else
207  {
209  }
210 
213 
214  return irBlock;
215  }
216 
217  //==============================================================================
218  /**
219  * @brief Loads an impulse response from provided IR bank
220  *
221  * Loads the appropriate impulse response (IR) from binary data.
222  *
223  * @param [in] irName Name of banked IR file
224  *
225  * @throws std::invalid_argument
226  */
228  {
229  auto& irBank = IRBank::getInstance();
230 
231  // Find requested IR
232  const auto irIter = irBank.buffers.find(irName);
234 
235  if (irIter == irBank.buffers.end() ||
237  {
238  throw std::invalid_argument("Requested impulse response (" + irName + ") does not exist in IR bank");
239  }
240 
241  // Copy IR buffer to internal representation
243 
244  ir.copyFrom(0, 0,
247  }
248 
249  //==============================================================================
250  /**
251  * @brief Loads an impulse response from a file (.WAV or .AIFF) to internal representation
252  *
253  * Loads the selected impulse response (IR) from disk and splits it into individual buffers
254  * for each channel. This operation is potentially very heavy so processing is suspended
255  * until completion.
256  *
257  * @param [in] irFilePath Path to impulse response file
258  *
259  * @throws std::invalid_argument
260  */
262  {
263  // Load impulse response file
265 
268 
270 
271  if (!reader)
272  {
273  throw std::invalid_argument("Failed to create reader for IR file: " + irFilePath);
274  }
275 
276  // Read IR buffer into internal representation
278  reader->read(&ir, 0, (int)reader->lengthInSamples, 0, (channelIdx == 0), (channelIdx == 1));
279  }
280 
281 }
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
virtual void updateSampleRate(double sr) override
Update sample rate for pipeline and child tasks.
Definition: IRPipeline.cpp:89
IRPipeline(juce::AudioProcessor *processor, int channelIdx)
Constructs an IRPipeline object associated with an AudioProcessor.
Definition: IRPipeline.cpp:29
virtual void updateParams(const juce::AudioProcessorValueTreeState &params, const juce::String &="") override
Updates parameters from processor parameter tree.
Definition: IRPipeline.cpp:46
virtual bool needsToRun() const override
Check if this or any subtasks needs to be executed.
Definition: IRPipeline.cpp:112