Mercurial > hg > audio_effects_textbook_code
view effects/pingpongdelay/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 |
line wrap: on
line source
/* This code accompanies the textbook: Digital Audio Effects: Theory, Implementation and Application Joshua D. Reiss and Andrew P. McPherson --- Ping-Pong Delay: stereo delay alternating between channels See textbook Chapter 2: Delay Line Effects Code by Andrew McPherson, Brecht De Man and Joshua Reiss --- This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "PluginProcessor.h" #include "PluginEditor.h" //============================================================================== PingPongDelayAudioProcessor::PingPongDelayAudioProcessor() : delayBuffer_ (2, 1) { // Set default values: delayLengthLeft_ = delayLengthRight_ = 0.5; wetMix_ = 0.5; feedback_ = 0.75; delayBufferLength_ = 1; reverseChannels_ = false; // Start the circular buffer pointers at the beginning delayReadPositionLeft_ = delayReadPositionRight_ = 0; delayWritePosition_ = 0; lastUIWidth_ = 500; lastUIHeight_ = 140; } PingPongDelayAudioProcessor::~PingPongDelayAudioProcessor() { } //============================================================================== const String PingPongDelayAudioProcessor::getName() const { return JucePlugin_Name; } int PingPongDelayAudioProcessor::getNumParameters() { return kNumParameters; } float PingPongDelayAudioProcessor::getParameter (int index) { // This method will be called by the host, probably on the audio thread, so // it's absolutely time-critical. Don't use critical sections or anything // UI-related, or anything at all that may block in any way! switch (index) { case kWetMixParam: return wetMix_; case kFeedbackParam: return feedback_; case kDelayLengthLeftParam: return delayLengthLeft_; case kDelayLengthRightParam: return delayLengthRight_; case kReverseChannelsParam: return (reverseChannels_ ? 1.0f : 0.0f); default: return 0.0f; } } void PingPongDelayAudioProcessor::setParameter (int index, float newValue) { // This method will be called by the host, probably on the audio thread, so // it's absolutely time-critical. Don't use critical sections or anything // UI-related, or anything at all that may block in any way! switch (index) { case kWetMixParam: wetMix_ = newValue; break; case kFeedbackParam: feedback_ = newValue; break; case kDelayLengthLeftParam: delayLengthLeft_ = newValue; delayReadPositionLeft_ = (int)(delayWritePosition_ - (delayLengthLeft_ * getSampleRate()) + delayBufferLength_) % delayBufferLength_; break; case kDelayLengthRightParam: delayLengthRight_ = newValue; delayReadPositionRight_ = (int)(delayWritePosition_ - (delayLengthRight_ * getSampleRate()) + delayBufferLength_) % delayBufferLength_; break; case kReverseChannelsParam: reverseChannels_ = (newValue != 0.0f); break; default: break; } } const String PingPongDelayAudioProcessor::getParameterName (int index) { switch (index) { case kWetMixParam: return "wet mix"; case kFeedbackParam: return "feedback"; case kDelayLengthLeftParam: return "delay left"; case kDelayLengthRightParam: return "delay right"; case kReverseChannelsParam: return "reverse channels"; default: break; } return String::empty; } const String PingPongDelayAudioProcessor::getParameterText (int index) { return String (getParameter (index), 2); } const String PingPongDelayAudioProcessor::getInputChannelName (int channelIndex) const { return String (channelIndex + 1); } const String PingPongDelayAudioProcessor::getOutputChannelName (int channelIndex) const { return String (channelIndex + 1); } bool PingPongDelayAudioProcessor::isInputChannelStereoPair (int index) const { return true; } bool PingPongDelayAudioProcessor::isOutputChannelStereoPair (int index) const { return true; } bool PingPongDelayAudioProcessor::silenceInProducesSilenceOut() const { #if JucePlugin_SilenceInProducesSilenceOut return true; #else return false; #endif } double PingPongDelayAudioProcessor::getTailLengthSeconds() const { return 0.0; } bool PingPongDelayAudioProcessor::acceptsMidi() const { #if JucePlugin_WantsMidiInput return true; #else return false; #endif } bool PingPongDelayAudioProcessor::producesMidi() const { #if JucePlugin_ProducesMidiOutput return true; #else return false; #endif } int PingPongDelayAudioProcessor::getNumPrograms() { return 0; } int PingPongDelayAudioProcessor::getCurrentProgram() { return 0; } void PingPongDelayAudioProcessor::setCurrentProgram (int index) { } const String PingPongDelayAudioProcessor::getProgramName (int index) { return String::empty; } void PingPongDelayAudioProcessor::changeProgramName (int index, const String& newName) { } //============================================================================== void PingPongDelayAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) { // Allocate and zero the delay buffer (size will depend on current sample rate) // Sanity check the result so we don't end up with any zero-length calculations delayBufferLength_ = (int)(2.0*sampleRate); if(delayBufferLength_ < 1) delayBufferLength_ = 1; delayBuffer_.setSize(2, delayBufferLength_); delayBuffer_.clear(); // This method gives us the sample rate. Use this to figure out what the delay position // offset should be (since it is specified in seconds, and we need to convert it to a number // of samples) delayReadPositionLeft_ = (int)(delayWritePosition_ - (delayLengthLeft_ * getSampleRate()) + delayBufferLength_) % delayBufferLength_; delayReadPositionRight_ = (int)(delayWritePosition_ - (delayLengthRight_ * getSampleRate()) + delayBufferLength_) % delayBufferLength_; } void PingPongDelayAudioProcessor::releaseResources() { // When playback stops, you can use this as an opportunity to free up any // spare memory, etc. // The delay buffer will stay in memory until the effect is unloaded. } void PingPongDelayAudioProcessor::reset() { // Use this method as the place to clear any delay lines, buffers, etc, as it // means there's been a break in the audio's continuity. delayBuffer_.clear(); } void PingPongDelayAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) { // Helpful information about this block of samples: const int numInputChannels = getNumInputChannels(); // How many input channels for our effect? const int numOutputChannels = getNumOutputChannels(); // How many output channels for our effect? const int numSamples = buffer.getNumSamples(); // How many samples in the buffer for this block? // This shouldn't happen, but we need a sanity check: this effect only makes sense // if there are at least 2 channels to work with (and in this case only 2...) if(buffer.getNumChannels() < 2) return; // If there is one input only, the second channel may not contain anything useful. // start with a blank buffer in this case if(numInputChannels < 2) buffer.clear(1, 0, numSamples); // channelDataL and channelDataR are arrays of length numSamples which contain // the audio for one channel float *channelDataL = buffer.getSampleData(0); float *channelDataR = buffer.getSampleData(1); // delayDataL and delayDataR are the circular buffers for implementing delay float* delayDataL = delayBuffer_.getSampleData(0); float* delayDataR = delayBuffer_.getSampleData(1); for (int i = 0; i < numSamples; ++i) { const float inL = channelDataL[i]; const float inR = channelDataR[i]; float outL, outR; if(reverseChannels_) { outL = (inL + wetMix_ * delayDataR[delayReadPositionLeft_]); outR = (inR + wetMix_ * delayDataL[delayReadPositionRight_]); } else { outL = (inL + wetMix_ * delayDataL[delayReadPositionLeft_]); outR = (inR + wetMix_ * delayDataR[delayReadPositionRight_]); } // Store the output of one delay buffer into the other, producing // the ping-pong effect delayDataR[delayWritePosition_] = inR + (delayDataL[delayReadPositionLeft_] * feedback_); delayDataL[delayWritePosition_] = inL + (delayDataR[delayReadPositionRight_] * feedback_); if (++delayReadPositionLeft_ >= delayBufferLength_) delayReadPositionLeft_ = 0; if (++delayReadPositionRight_ >= delayBufferLength_) delayReadPositionRight_ = 0; if (++delayWritePosition_ >= delayBufferLength_) delayWritePosition_ = 0; // Store the output samples in the buffer, replacing the input channelDataL[i] = outL; channelDataR[i] = outR; } // Clear any channels above 2 (stereo) for (int i = 2; i < numOutputChannels; ++i) { buffer.clear (i, 0, buffer.getNumSamples()); } } //============================================================================== bool PingPongDelayAudioProcessor::hasEditor() const { return true; // (change this to false if you choose to not supply an editor) } AudioProcessorEditor* PingPongDelayAudioProcessor::createEditor() { return new PingPongDelayAudioProcessorEditor (this); } //============================================================================== void PingPongDelayAudioProcessor::getStateInformation (MemoryBlock& destData) { // You should use this method to store your parameters in the memory block. // You could do that either as raw data, or use the XML or ValueTree classes // as intermediaries to make it easy to save and load complex data. // Create an outer XML element.. XmlElement xml("C4DMPLUGINSETTINGS"); // add some attributes to it.. xml.setAttribute("uiWidth", lastUIWidth_); xml.setAttribute("uiHeight", lastUIHeight_); xml.setAttribute("delayLengthLeft", delayLengthLeft_); xml.setAttribute("delayLengthRight", delayLengthRight_); xml.setAttribute("feedback", feedback_); xml.setAttribute("wetMix", wetMix_); // then use this helper function to stuff it into the binary blob and return it.. copyXmlToBinary(xml, destData); } void PingPongDelayAudioProcessor::setStateInformation (const void* data, int sizeInBytes) { // You should use this method to restore your parameters from this memory block, // whose contents will have been created by the getStateInformation() call. // This getXmlFromBinary() helper function retrieves our XML from the binary blob.. ScopedPointer<XmlElement> xmlState (getXmlFromBinary (data, sizeInBytes)); if(xmlState != 0) { // make sure that it's actually our type of XML object.. if(xmlState->hasTagName("C4DMPLUGINSETTINGS")) { // ok, now pull out our parameters.. lastUIWidth_ = xmlState->getIntAttribute("uiWidth", lastUIWidth_); lastUIHeight_ = xmlState->getIntAttribute("uiHeight", lastUIHeight_); delayLengthLeft_ = (float)xmlState->getDoubleAttribute("delayLengthLeft", delayLengthLeft_); delayLengthRight_ = (float)xmlState->getDoubleAttribute("delayLengthRight", delayLengthRight_); feedback_ = (float)xmlState->getDoubleAttribute("feedback", feedback_); wetMix_ = (float)xmlState->getDoubleAttribute("wetMix", wetMix_); } } } //============================================================================== // This creates new instances of the plugin.. AudioProcessor* JUCE_CALLTYPE createPluginFilter() { return new PingPongDelayAudioProcessor(); }