/* ============================================================================== This file contains the basic framework code for a JUCE plugin processor. ============================================================================== */ #include "PluginProcessor.h" #include "PluginEditor.h" //============================================================================== DelayPluginAudioProcessor::DelayPluginAudioProcessor() #ifndef JucePlugin_PreferredChannelConfigurations : AudioProcessor (BusesProperties() #if ! JucePlugin_IsMidiEffect #if ! JucePlugin_IsSynth .withInput ("Input", juce::AudioChannelSet::stereo(), true) #endif .withOutput ("Output", juce::AudioChannelSet::stereo(), true) #endif ), lowPassFilter(juce::dsp::IIR::Coefficients::makeLowPass(44100, 15000.0, 1.0)) #endif { for (int i = 0; i < 2; i++) fDelayTime[i] = 1000.0; } DelayPluginAudioProcessor::~DelayPluginAudioProcessor() { } //============================================================================== const juce::String DelayPluginAudioProcessor::getName() const { return JucePlugin_Name; } bool DelayPluginAudioProcessor::acceptsMidi() const { #if JucePlugin_WantsMidiInput return true; #else return false; #endif } bool DelayPluginAudioProcessor::producesMidi() const { #if JucePlugin_ProducesMidiOutput return true; #else return false; #endif } bool DelayPluginAudioProcessor::isMidiEffect() const { #if JucePlugin_IsMidiEffect return true; #else return false; #endif } double DelayPluginAudioProcessor::getTailLengthSeconds() const { return 0.0; } int DelayPluginAudioProcessor::getNumPrograms() { return 1; // NB: some hosts don't cope very well if you tell them there are 0 programs, // so this should be at least 1, even if you're not really implementing programs. } int DelayPluginAudioProcessor::getCurrentProgram() { return 0; } void DelayPluginAudioProcessor::setCurrentProgram (int index) { } const juce::String DelayPluginAudioProcessor::getProgramName (int index) { return {}; } void DelayPluginAudioProcessor::changeProgramName (int index, const juce::String& newName) { } //============================================================================== void DelayPluginAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) { const int iNumInputChannels = getTotalNumInputChannels(); const int iDelayBufferSize = 2 * sampleRate; iSampleRate = sampleRate; delayBuffer.setSize(iNumInputChannels, iDelayBufferSize); // Filter juce::dsp::ProcessSpec spec; spec.sampleRate = sampleRate; spec.maximumBlockSize = samplesPerBlock; spec.numChannels = getTotalNumOutputChannels(); lowPassFilter.prepare(spec); lowPassFilter.reset(); } void DelayPluginAudioProcessor::releaseResources() { // When playback stops, you can use this as an opportunity to free up any // spare memory, etc. } #ifndef JucePlugin_PreferredChannelConfigurations bool DelayPluginAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const { #if JucePlugin_IsMidiEffect juce::ignoreUnused (layouts); return true; #else // This is the place where you check if the layout is supported. // In this template code we only support mono or stereo. // Some plugin hosts, such as certain GarageBand versions, will only // load plugins that support stereo bus layouts. if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono() && layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo()) return false; // This checks if the input layout matches the output layout #if ! JucePlugin_IsSynth if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet()) return false; #endif return true; #endif } #endif void DelayPluginAudioProcessor::updateFilter(float cutoff, float res) { *lowPassFilter.state = *juce::dsp::IIR::Coefficients::makeLowPass(getSampleRate(), cutoff, res); } void DelayPluginAudioProcessor::processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages) { juce::ScopedNoDenormals noDenormals; auto totalNumInputChannels = getTotalNumInputChannels(); auto totalNumOutputChannels = getTotalNumOutputChannels(); for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i) { buffer.clear (i, 0, buffer.getNumSamples()); } filterBuffer.makeCopyOf(buffer); const int iBufferLength = buffer.getNumSamples(); const int iDelayBufferLength = delayBuffer.getNumSamples(); for (int channel = 0; channel < totalNumInputChannels; ++channel) { const float* fBufferData = buffer.getReadPointer(channel); const float* fDelayBufferData = delayBuffer.getReadPointer(channel); float* fDryBuffer = filterBuffer.getWritePointer(channel); // Copy data from audio buffer to delay buffer fillDelayBuffer(channel, iBufferLength, iDelayBufferLength, fBufferData, fDelayBufferData); // Add delay buffer audio into filtered buffer getFromDelayBuffer(filterBuffer, channel, iBufferLength, iDelayBufferLength, fBufferData, fDelayBufferData); feedbackDelay(channel, iBufferLength, iDelayBufferLength, fDryBuffer); } // Move through delay buffer the length of the audio buffer iWritePosition += iBufferLength; iWritePosition %= iDelayBufferLength; juce::dsp::AudioBlock block (filterBuffer); lowPassFilter.process(juce::dsp::ProcessContextReplacing (block)); // Sum filterBuffer into buffer for (int channel = 0; channel < totalNumInputChannels; ++channel) { buffer.addFromWithRamp(channel, 0, filterBuffer.getWritePointer(channel), filterBuffer.getNumSamples(), 0.5, 0.5); } } void DelayPluginAudioProcessor::fillDelayBuffer(int channel, const int iBufferLength, const int iDelayBufferLength, const float* fBufferData, const float* fDelayBufferData) { // Is delayBuffer big enough to fit buffer in from write position if (iDelayBufferLength > iBufferLength + iWritePosition) delayBuffer.copyFromWithRamp(channel, iWritePosition, fBufferData, iBufferLength, 0.8, 0.8); // else { // Calculate remaining samples to be implemented const int iBufferRemaining = iDelayBufferLength - iWritePosition; delayBuffer.copyFromWithRamp(channel, iWritePosition, fBufferData, iBufferLength, 0.8, 0.8); // Fill delay til end // Fill delay from start to remaining samples left delayBuffer.copyFromWithRamp(channel, 0, fBufferData, iBufferLength - iBufferRemaining, 0.8, 0.8); } } void DelayPluginAudioProcessor::getFromDelayBuffer(juce::AudioBuffer& buffer, int channel, const int iBufferLength, const int iDelayBufferLength, const float* fBufferData, const float* fDelayBufferData) { // Calculate read position from delay buffer based on delay time const int iReadPosition = static_cast(iDelayBufferLength + iWritePosition - (iSampleRate * fDelayTime[channel] / 1000)) % iDelayBufferLength; if (iDelayBufferLength > iBufferLength + iReadPosition ) buffer.addFromWithRamp(channel, 0, fDelayBufferData + iReadPosition, iBufferLength, fFeedbackGain, fFeedbackGain); else { const int iBufferRemaining = iDelayBufferLength - iReadPosition; buffer.addFromWithRamp(channel, 0, fDelayBufferData + iReadPosition, iBufferRemaining, fFeedbackGain, fFeedbackGain); buffer.addFromWithRamp(channel, iBufferRemaining, fDelayBufferData + iReadPosition, iBufferLength - iBufferRemaining, fFeedbackGain, fFeedbackGain); } } void DelayPluginAudioProcessor::feedbackDelay(int channel, const int iBufferLength, const int iDelayBufferLength, float* fDryBuffer) { if (iDelayBufferLength > iBufferLength + iWritePosition) delayBuffer.addFromWithRamp(channel, iWritePosition, fDryBuffer, iBufferLength, fFeedbackGain, fFeedbackGain); else { const int iBufferRemaining = iDelayBufferLength - iWritePosition; delayBuffer.addFromWithRamp(channel, iBufferRemaining, fDryBuffer, iBufferRemaining, fFeedbackGain, fFeedbackGain); delayBuffer.addFromWithRamp(channel, 0, fDryBuffer, iBufferLength - iBufferRemaining, fFeedbackGain, fFeedbackGain); } } //============================================================================== bool DelayPluginAudioProcessor::hasEditor() const { return true; // (change this to false if you choose to not supply an editor) } juce::AudioProcessorEditor* DelayPluginAudioProcessor::createEditor() { return new DelayPluginAudioProcessorEditor (*this); } //============================================================================== void DelayPluginAudioProcessor::getStateInformation (juce::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. } void DelayPluginAudioProcessor::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 creates new instances of the plugin.. juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() { return new DelayPluginAudioProcessor(); }