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