Mercurial > hg > audio_effects_textbook_code
comparison effects/autowah/Source/PluginProcessor.cpp @ 0:e32fe563e124
First commit
author | Andrew McPherson <andrewm@eecs.qmul.ac.uk> |
---|---|
date | Fri, 10 Oct 2014 15:41:23 +0100 |
parents | |
children | 04e171d2a747 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:e32fe563e124 |
---|---|
1 /* | |
2 This code accompanies the textbook: | |
3 | |
4 Digital Audio Effects: Theory, Implementation and Application | |
5 Joshua D. Reiss and Andrew P. McPherson | |
6 | |
7 --- | |
8 | |
9 Auto-Wah: LFO or envelope-operated wah effect | |
10 See textbook Chapter 4: Filter Effects | |
11 | |
12 Code by Andrew McPherson, Brecht de Man and Joshua Reiss | |
13 | |
14 --- | |
15 | |
16 This program is free software: you can redistribute it and/or modify | |
17 it under the terms of the GNU General Public License as published by | |
18 the Free Software Foundation, either version 3 of the License, or | |
19 (at your option) any later version. | |
20 | |
21 This program is distributed in the hope that it will be useful, | |
22 but WITHOUT ANY WARRANTY; without even the implied warranty of | |
23 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
24 GNU General Public License for more details. | |
25 | |
26 You should have received a copy of the GNU General Public License | |
27 along with this program. If not, see <http://www.gnu.org/licenses/>. | |
28 */ | |
29 | |
30 #include "PluginProcessor.h" | |
31 #include "PluginEditor.h" | |
32 | |
33 // The filter will produce a resonant peak of amplitude Q; bring everything | |
34 // down somewhat to compensate, though try to maintain some perceptual balance | |
35 // of being similar loudness. (This factor has been chosen somewhat arbitrarily.) | |
36 const double kWahwahFilterGain = 0.5; | |
37 | |
38 //============================================================================== | |
39 AutoWahAudioProcessor::AutoWahAudioProcessor() | |
40 { | |
41 // Set default values: | |
42 baseFrequency_ = 350.0; | |
43 q_ = 5.0; | |
44 lfoFrequency_ = 2.0; | |
45 lfoWidth_ = 1000.0; | |
46 envelopeWidth_ = 0.0; | |
47 envelopeAttack_ = 0.005; | |
48 envelopeDecay_ = 0.1; | |
49 | |
50 // Initialise the filters later when we know how many channels | |
51 wahFilters_ = 0; | |
52 numWahFilters_ = 0; | |
53 envelopes_ = 0; | |
54 numEnvelopes_ = 0; | |
55 attackMultiplier_ = 1.0; | |
56 decayMultiplier_ = 0.0; | |
57 | |
58 inverseSampleRate_ = 1.0/44100.0; // start with a sensible default | |
59 | |
60 lastUIWidth_ = 550; | |
61 lastUIHeight_ = 200; | |
62 } | |
63 | |
64 AutoWahAudioProcessor::~AutoWahAudioProcessor() | |
65 { | |
66 deallocateFilters(); | |
67 } | |
68 | |
69 //============================================================================== | |
70 const String AutoWahAudioProcessor::getName() const | |
71 { | |
72 return JucePlugin_Name; | |
73 } | |
74 | |
75 int AutoWahAudioProcessor::getNumParameters() | |
76 { | |
77 return kNumParameters; | |
78 } | |
79 | |
80 float AutoWahAudioProcessor::getParameter (int index) | |
81 { | |
82 // This method will be called by the host, probably on the audio thread, so | |
83 // it's absolutely time-critical. Don't use critical sections or anything | |
84 // UI-related, or anything at all that may block in any way! | |
85 switch (index) | |
86 { | |
87 case kBaseFrequencyParam: return baseFrequency_; | |
88 case kQParam: return q_; | |
89 case kLFOFrequencyParam: return lfoFrequency_; | |
90 case kLFOWidthParam: return lfoWidth_; | |
91 case kEnvelopeWidthParam: return envelopeWidth_; | |
92 case kEnvelopeAttackParam: return envelopeAttack_; | |
93 case kEnvelopeDecayParam: return envelopeDecay_; | |
94 default: return 0.0f; | |
95 } | |
96 } | |
97 | |
98 void AutoWahAudioProcessor::setParameter (int index, float newValue) | |
99 { | |
100 // This method will be called by the host, probably on the audio thread, so | |
101 // it's absolutely time-critical. Don't use critical sections or anything | |
102 // UI-related, or anything at all that may block in any way! | |
103 | |
104 switch (index) | |
105 { | |
106 case kBaseFrequencyParam: | |
107 baseFrequency_ = newValue; | |
108 break; | |
109 case kQParam: | |
110 q_ = newValue; | |
111 break; | |
112 case kLFOFrequencyParam: | |
113 lfoFrequency_ = newValue; | |
114 break; | |
115 case kLFOWidthParam: | |
116 lfoWidth_ = newValue; | |
117 break; | |
118 case kEnvelopeWidthParam: | |
119 envelopeWidth_ = newValue; | |
120 break; | |
121 case kEnvelopeAttackParam: | |
122 envelopeAttack_ = newValue; | |
123 // See comment below for justification | |
124 if(envelopeAttack_ == 0.0) | |
125 attackMultiplier_ = 0.0; | |
126 else | |
127 attackMultiplier_ = pow(1.0 / M_E, inverseSampleRate_ / envelopeAttack_); | |
128 break; | |
129 case kEnvelopeDecayParam: | |
130 envelopeDecay_ = newValue; | |
131 // envelopeDecay_ sets the time constant tau. The decay is | |
132 // given as e^-(t/tau) so after tau seconds, it will have | |
133 // decayed to 1/e of its original value. tau*sampleRate samples | |
134 // will have passed by then, each of which multiplies the signal | |
135 // by decayMultiplier_. | |
136 if(envelopeDecay_ == 0.0) | |
137 decayMultiplier_ = 0.0; | |
138 else | |
139 decayMultiplier_ = pow(1.0 / M_E, inverseSampleRate_ / envelopeDecay_); | |
140 break; | |
141 default: | |
142 break; | |
143 } | |
144 } | |
145 | |
146 const String AutoWahAudioProcessor::getParameterName (int index) | |
147 { | |
148 switch (index) | |
149 { | |
150 case kBaseFrequencyParam: return "base frequency"; | |
151 case kQParam: return "Q"; | |
152 case kLFOFrequencyParam: return "LFO frequency"; | |
153 case kLFOWidthParam: return "LFO width"; | |
154 case kEnvelopeWidthParam: return "envelope width"; | |
155 case kEnvelopeAttackParam: return "envelope attack"; | |
156 case kEnvelopeDecayParam: return "envelope decay"; | |
157 default: break; | |
158 } | |
159 | |
160 return String::empty; | |
161 } | |
162 | |
163 const String AutoWahAudioProcessor::getParameterText (int index) | |
164 { | |
165 return String (getParameter (index), 2); | |
166 } | |
167 | |
168 const String AutoWahAudioProcessor::getInputChannelName (int channelIndex) const | |
169 { | |
170 return String (channelIndex + 1); | |
171 } | |
172 | |
173 const String AutoWahAudioProcessor::getOutputChannelName (int channelIndex) const | |
174 { | |
175 return String (channelIndex + 1); | |
176 } | |
177 | |
178 bool AutoWahAudioProcessor::isInputChannelStereoPair (int index) const | |
179 { | |
180 return true; | |
181 } | |
182 | |
183 bool AutoWahAudioProcessor::isOutputChannelStereoPair (int index) const | |
184 { | |
185 return true; | |
186 } | |
187 | |
188 bool AutoWahAudioProcessor::silenceInProducesSilenceOut() const | |
189 { | |
190 #if JucePlugin_SilenceInProducesSilenceOut | |
191 return true; | |
192 #else | |
193 return false; | |
194 #endif | |
195 } | |
196 | |
197 double AutoWahAudioProcessor::getTailLengthSeconds() const | |
198 { | |
199 return 0.0; | |
200 } | |
201 | |
202 bool AutoWahAudioProcessor::acceptsMidi() const | |
203 { | |
204 #if JucePlugin_WantsMidiInput | |
205 return true; | |
206 #else | |
207 return false; | |
208 #endif | |
209 } | |
210 | |
211 bool AutoWahAudioProcessor::producesMidi() const | |
212 { | |
213 #if JucePlugin_ProducesMidiOutput | |
214 return true; | |
215 #else | |
216 return false; | |
217 #endif | |
218 } | |
219 | |
220 int AutoWahAudioProcessor::getNumPrograms() | |
221 { | |
222 return 0; | |
223 } | |
224 | |
225 int AutoWahAudioProcessor::getCurrentProgram() | |
226 { | |
227 return 0; | |
228 } | |
229 | |
230 void AutoWahAudioProcessor::setCurrentProgram (int index) | |
231 { | |
232 } | |
233 | |
234 const String AutoWahAudioProcessor::getProgramName (int index) | |
235 { | |
236 return String::empty; | |
237 } | |
238 | |
239 void AutoWahAudioProcessor::changeProgramName (int index, const String& newName) | |
240 { | |
241 } | |
242 | |
243 //============================================================================== | |
244 void AutoWahAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) | |
245 { | |
246 // Use this method as the place to do any pre-playback | |
247 // initialisation that you need.. | |
248 | |
249 allocateFilters(); | |
250 inverseSampleRate_ = 1.0 / sampleRate; | |
251 if(envelopeDecay_ == 0.0) | |
252 decayMultiplier_ = 0.0; | |
253 else | |
254 decayMultiplier_ = pow(1.0 / M_E, inverseSampleRate_ / envelopeDecay_); | |
255 if(envelopeAttack_ == 0.0) | |
256 attackMultiplier_ = 0.0; | |
257 else | |
258 attackMultiplier_ = pow(1.0 / M_E, inverseSampleRate_ / envelopeAttack_); | |
259 } | |
260 | |
261 void AutoWahAudioProcessor::releaseResources() | |
262 { | |
263 // When playback stops, you can use this as an opportunity to free up any | |
264 // spare memory, etc. | |
265 | |
266 deallocateFilters(); | |
267 } | |
268 | |
269 void AutoWahAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) | |
270 { | |
271 // Helpful information about this block of samples: | |
272 const int numInputChannels = getNumInputChannels(); // How many input channels for our effect? | |
273 const int numOutputChannels = getNumOutputChannels(); // How many output channels for our effect? | |
274 const int numSamples = buffer.getNumSamples(); // How many samples in the buffer for this block? | |
275 int channel; | |
276 float ph; | |
277 | |
278 // Go through each channel and put it through the resonant lowpass filter, updating | |
279 // the coefficients as we go along. Each channel is processed identically in this effect. | |
280 | |
281 for(channel = 0; channel < jmin(numInputChannels, numWahFilters_); ++channel) | |
282 { | |
283 // channelData is an array of length numSamples which contains the audio for one channel | |
284 float* channelData = buffer.getSampleData(channel); | |
285 ph = lfoPhase_; | |
286 | |
287 for (int sample = 0; sample < numSamples; ++sample) | |
288 { | |
289 const float in = channelData[sample]; | |
290 float centreFrequency = baseFrequency_; | |
291 | |
292 // Calculate the envelope of the signal. Do this even if we're not currently | |
293 // changing the frequeny based on it, since it involves maintaining a history | |
294 // of the signal's behaviour. | |
295 | |
296 if(channel < numEnvelopes_) { // Safety check | |
297 if(fabs(in) > envelopes_[channel]) { | |
298 envelopes_[channel] += (1.0 - attackMultiplier_) * (fabs(in) - (double)envelopes_[channel]); | |
299 } | |
300 else | |
301 envelopes_[channel] *= decayMultiplier_; | |
302 } | |
303 | |
304 // Calculate the centre frequency of the filter based on the LFO and the | |
305 // signal envelope | |
306 if(lfoWidth_ > 0.0) { | |
307 centreFrequency += lfoWidth_ * (0.5f + 0.5f*sinf(2.0 * M_PI * ph)); | |
308 } | |
309 if(envelopeWidth_ > 0.0 && channel < numEnvelopes_) { | |
310 centreFrequency += envelopeWidth_ * envelopes_[channel]; | |
311 } | |
312 | |
313 // Update filter coefficients (see ResonantLowpassFilter.cpp for calculation) | |
314 wahFilters_[channel]->makeResonantLowpass(inverseSampleRate_, | |
315 centreFrequency, | |
316 q_, | |
317 kWahwahFilterGain); | |
318 | |
319 // Process one sample and store it back in place. See juce_IIRFilter.cpp for the | |
320 // application of the IIR filter. | |
321 channelData[sample] = wahFilters_[channel]->processSingleSampleRaw(in); | |
322 | |
323 // Update the LFO phase, keeping it in the range 0-1 | |
324 ph += lfoFrequency_*inverseSampleRate_; | |
325 if(ph >= 1.0) | |
326 ph -= 1.0; | |
327 } | |
328 } | |
329 | |
330 lfoPhase_ = ph; | |
331 | |
332 // Go through the remaining channels. In case we have more outputs | |
333 // than inputs, or there aren't enough filters, we'll clear any | |
334 // remaining output channels (which could otherwise contain garbage) | |
335 while(channel < numOutputChannels) | |
336 { | |
337 buffer.clear (channel++, 0, buffer.getNumSamples()); | |
338 } | |
339 } | |
340 | |
341 //============================================================================== | |
342 bool AutoWahAudioProcessor::hasEditor() const | |
343 { | |
344 return true; // (change this to false if you choose to not supply an editor) | |
345 } | |
346 | |
347 AudioProcessorEditor* AutoWahAudioProcessor::createEditor() | |
348 { | |
349 return new AutoWahAudioProcessorEditor (this); | |
350 } | |
351 | |
352 //============================================================================== | |
353 void AutoWahAudioProcessor::getStateInformation (MemoryBlock& destData) | |
354 { | |
355 // You should use this method to store your parameters in the memory block. | |
356 // You could do that either as raw data, or use the XML or ValueTree classes | |
357 // as intermediaries to make it easy to save and load complex data. | |
358 | |
359 // Create an outer XML element.. | |
360 XmlElement xml("C4DMPLUGINSETTINGS"); | |
361 | |
362 // add some attributes to it.. | |
363 xml.setAttribute("uiWidth", lastUIWidth_); | |
364 xml.setAttribute("uiHeight", lastUIHeight_); | |
365 xml.setAttribute("baseFrequency", baseFrequency_); | |
366 xml.setAttribute("q", q_); | |
367 xml.setAttribute("lfoFrequency", lfoFrequency_); | |
368 xml.setAttribute("lfoWidth", lfoWidth_); | |
369 xml.setAttribute("envelopeWidth", envelopeWidth_); | |
370 xml.setAttribute("envelopeAttack", envelopeAttack_); | |
371 xml.setAttribute("envelopeDecay", envelopeDecay_); | |
372 | |
373 // then use this helper function to stuff it into the binary blob and return it.. | |
374 copyXmlToBinary(xml, destData); | |
375 } | |
376 | |
377 void AutoWahAudioProcessor::setStateInformation (const void* data, int sizeInBytes) | |
378 { | |
379 // You should use this method to restore your parameters from this memory block, | |
380 // whose contents will have been created by the getStateInformation() call. | |
381 | |
382 // This getXmlFromBinary() helper function retrieves our XML from the binary blob.. | |
383 ScopedPointer<XmlElement> xmlState (getXmlFromBinary (data, sizeInBytes)); | |
384 | |
385 if(xmlState != 0) | |
386 { | |
387 // make sure that it's actually our type of XML object.. | |
388 if(xmlState->hasTagName("C4DMPLUGINSETTINGS")) | |
389 { | |
390 // ok, now pull out our parameters.. | |
391 lastUIWidth_ = xmlState->getIntAttribute("uiWidth", lastUIWidth_); | |
392 lastUIHeight_ = xmlState->getIntAttribute("uiHeight", lastUIHeight_); | |
393 | |
394 q_ = (float)xmlState->getDoubleAttribute("q", q_); | |
395 baseFrequency_ = (float)xmlState->getDoubleAttribute("baseFrequency", baseFrequency_); | |
396 lfoFrequency_ = (float)xmlState->getDoubleAttribute("lfoFrequency", lfoFrequency_); | |
397 lfoWidth_ = (float)xmlState->getDoubleAttribute("lfoWidth", lfoWidth_); | |
398 envelopeWidth_ = (float)xmlState->getDoubleAttribute("envelopeWidth", envelopeWidth_); | |
399 envelopeAttack_ = (float)xmlState->getDoubleAttribute("envelopeAttack", envelopeAttack_); | |
400 envelopeDecay_ = (float)xmlState->getDoubleAttribute("envelopeDecay", envelopeDecay_); | |
401 inverseSampleRate_ = 1.0 / getSampleRate(); | |
402 if(envelopeDecay_ == 0.0) | |
403 decayMultiplier_ = 0.0; | |
404 else | |
405 decayMultiplier_ = pow(1.0 / M_E, inverseSampleRate_ / envelopeDecay_); | |
406 if(envelopeAttack_ == 0.0) | |
407 attackMultiplier_ = 0.0; | |
408 else | |
409 attackMultiplier_ = pow(1.0 / M_E, inverseSampleRate_ / envelopeAttack_); | |
410 } | |
411 } | |
412 } | |
413 | |
414 void AutoWahAudioProcessor::allocateFilters() | |
415 { | |
416 // Prevent leaks from reallocation | |
417 if(wahFilters_ != 0 || envelopes_ != 0) | |
418 deallocateFilters(); | |
419 | |
420 // Create as many filters as we have input channels | |
421 numWahFilters_ = getNumInputChannels(); | |
422 wahFilters_ = (ResonantLowpassFilter**)malloc(numWahFilters_ * sizeof(ResonantLowpassFilter*)); | |
423 if(wahFilters_ == 0) | |
424 numWahFilters_ = 0; | |
425 else { | |
426 for(int i = 0; i < numWahFilters_; i++) | |
427 wahFilters_[i] = new ResonantLowpassFilter; | |
428 } | |
429 | |
430 numEnvelopes_ = getNumInputChannels(); | |
431 envelopes_ = (double *)malloc(numEnvelopes_ * sizeof(double)); | |
432 if(envelopes_ == 0) | |
433 numEnvelopes_ = 0; | |
434 else { | |
435 for(int i = 0; i < numEnvelopes_; i++) | |
436 envelopes_[i] = 0.0; | |
437 } | |
438 } | |
439 | |
440 void AutoWahAudioProcessor::deallocateFilters() | |
441 { | |
442 for(int i = 0; i < numWahFilters_; i++) | |
443 delete wahFilters_[i]; | |
444 if(numWahFilters_ != 0) | |
445 free(wahFilters_); | |
446 numWahFilters_ = 0; | |
447 wahFilters_ = 0; | |
448 if(envelopes_ != 0) | |
449 free(envelopes_); | |
450 envelopes_ = 0; | |
451 numEnvelopes_ = 0; | |
452 } | |
453 | |
454 //============================================================================== | |
455 // This creates new instances of the plugin.. | |
456 AudioProcessor* JUCE_CALLTYPE createPluginFilter() | |
457 { | |
458 return new AutoWahAudioProcessor(); | |
459 } |