Hello, can you help a newbie.
I’m working on adding a filter(HPF/LPF) to a delay vst audio plugin I’ve made. I’m using juce::dsp::ProcessorChain<juce::dsp::LadderFilter> to do this. I’ve read the tutorial https://docs.juce.com/master/tutorial_dsp_introduction.html. I’ve managed to do a working implementation. My problem is that the full signal is filtered, and I only want it to filter the wet signal(feedback delayed signal). When I play a note, I want to hear the initial dry unfiltered signal, then followed by the filtered feedback delayed signal.
Problem also is that I don’t fully understand this code:
/* setting up the ProcesChain*/
auto block = juce::dsp::AudioBlock (buffer);
auto blockToUse = block.getSubBlock(0, buffer.getNumSamples());
auto contextToUse = juce::dsp::ProcessContextReplacing (blockToUse);
processorChain.process (contextToUse);
In my first implementation (full signal is filtered) I used the incoming buffer in the processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages). So, the idea was to create a second AudioBuffer(called wetBuffer), that should contain the feedback delay signal(wet), that is then summed with the dry signal from buffer. Here is my code,trying to do that. I would really appreciate if you could help me here.
#include “PluginProcessor.h”
#include “PluginEditor.h”
DelayAudioProcessor::DelayAudioProcessor()
#ifndef JucePlugin_PreferredChannelConfigurations
: AudioProcessor (BusesProperties()
#if ! JucePlugin_IsMidiEffect
#if ! JucePlugin_IsSynth
.withInput (“Input”, AudioChannelSet::stereo(), true)
#endif
.withOutput (“Output”, AudioChannelSet::stereo(), true)
#endif
)
#endif
{
addParameter (mDryWetParameter = new AudioParameterFloat ("drywet", // parameter ID
"Dry Wet", // parameter name
0.0f, // minimum value
1.0f, // maximum value
0.5f)); // default value
addParameter (mFeedbackParameter = new AudioParameterFloat ("feedback", // parameter ID
"Feedback", // parameter name
0.0f, // minimum value
0.98f, // maximum value
0.5f)); // default value
addParameter (mTimeParameter = new AudioParameterFloat ("delaytime", // parameter ID
"Delay Time", // parameter name
0.1f, // minimum value
MAX_DELAY_TIME, // maximum value
0.5f)); // default value
/*check constructor for parameter*/
addParameter (mNoteParameter = new AudioParameterInt ("delayNotetime", // parameter ID
"Delay NoteTime", // parameter name
1, // minimum value
7, // maximum value
2)); // default value
mTimeSmoothed = 0;
/* we set nullptr because we don't no the sample rate yet, and are not ready to instantiate audio data/ how big buffer is*/
mCircularBufferLeft = nullptr;
mCircularBufferRight = nullptr;
mCircularBufferWriteHead = 0;
mCircularBufferLength = 0;
mDelayTimesInSamples = 0;
mDelayReadHead = 0;
mFeedBackLeft = 0;
mFeedBackRight = 0;
mLFOPhase = 0;
/*For testing. Later to be removed, to do GUI linked to parameter*/
auto& filter = processorChain.get<filterIndex>();
filter.setMode(dsp::LadderFilter<float>::Mode::LPF24 );
filter.setCutoffFrequencyHz (100.0f);
filter.setResonance (0.0f);
}
DelayAudioProcessor::~DelayAudioProcessor()
{
}
const String DelayAudioProcessor::getName() const
{
return JucePlugin_Name;
}
bool DelayAudioProcessor::acceptsMidi() const
{
#if JucePlugin_WantsMidiInput
return true;
#else
return false;
#endif
}
bool DelayAudioProcessor::producesMidi() const
{
#if JucePlugin_ProducesMidiOutput
return true;
#else
return false;
#endif
}
bool DelayAudioProcessor::isMidiEffect() const
{
#if JucePlugin_IsMidiEffect
return true;
#else
return false;
#endif
}
double DelayAudioProcessor::getTailLengthSeconds() const
{
return 0.0;
}
int DelayAudioProcessor::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 DelayAudioProcessor::getCurrentProgram()
{
return 0;
}
void DelayAudioProcessor::setCurrentProgram (int index)
{
}
const String DelayAudioProcessor::getProgramName (int index)
{
return {};
}
void DelayAudioProcessor::changeProgramName (int index, const String& newName)
{
}
void DelayAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
/*our delayed audio in time samples we want to retrieve and add to our original audiobuffer signal*/
mDelayTimesInSamples = sampleRate * *mTimeParameter;
/* our time in samples we need to store*/
mCircularBufferLength = sampleRate * MAX_DELAY_TIME;
/* Here we check for nullptr, and create and set the size of our floating point array - the correct length of our buffers*/
if (mCircularBufferLeft == nullptr )
{
mCircularBufferLeft.reset( new float[(int)mCircularBufferLength]);
}
zeromem(mCircularBufferLeft.get(), mCircularBufferLength * sizeof(float));
if (mCircularBufferRight == nullptr )
{
mCircularBufferRight.reset(new float[(int)mCircularBufferLength]);
}
zeromem(mCircularBufferRight.get(), mCircularBufferLength * sizeof(float));
mCircularBufferWriteHead = 0;
mTimeSmoothed = *mTimeParameter; // Data from delay time (GUI slider)knob
mLFOPhase = 0;
/* we create a ProcessSpec */
dsp::ProcessSpec spec;
spec.sampleRate = sampleRate;
spec.maximumBlockSize = uint32 (samplesPerBlock);
spec.numChannels = uint32 (getTotalNumOutputChannels ());
/*need to call prepare() with above spec */
processorChain.prepare(spec);
}
void DelayAudioProcessor::releaseResources()
{
// When playback stops, you can use this as an opportunity to free up any
// spare memory, etc.
/*need to call reset */
processorChain.reset();
}
#ifndef JucePlugin_PreferredChannelConfigurations
bool DelayAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const
{
#if JucePlugin_IsMidiEffect
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.
if (layouts.getMainOutputChannelSet() != AudioChannelSet::mono()
&& layouts.getMainOutputChannelSet() != 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 DelayAudioProcessor::processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages)
{
/*We create a audiobuffer, containing the feedback filtered signal */
AudioBuffer<float> wetBuffer (2, buffer.getNumSamples());
wetBuffer.clear();
ScopedNoDenormals noDenormals;
auto totalNumInputChannels = getTotalNumInputChannels();
auto totalNumOutputChannels = getTotalNumOutputChannels();
for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
{
buffer.clear (i, 0, buffer.getNumSamples());
}
/* setting up the ProcesChain*/
auto block = juce::dsp::AudioBlock<float> (wetBuffer);
auto blockToUse = block.getSubBlock(0, wetBuffer.getNumSamples());
auto contextToUse = juce::dsp::ProcessContextReplacing<float> (blockToUse);
processorChain.process (contextToUse);
/*get a pointer, to access buffer*/
float* leftChannel = buffer.getWritePointer(0);
float* RightChannel = buffer.getWritePointer(1);
/*get a pointer, to DAW audioPlayHead*/
AudioPlayHead* phead = getPlayHead();
AudioPlayHead::CurrentPositionInfo playposinfo;
if (phead != nullptr)
{
phead->getCurrentPosition(playposinfo);
}
/*Iterate through all samples in audio buffer, and proces buffer data in loop*/
for (int i = 0; i < buffer.getNumSamples(); i++)
{
/*. Normal delay: we store data in circular buffer + adding the feedback data back, se below. Se diagram in notes */
//mCircularBufferLeft.get()[mCircularBufferWriteHead] = leftChannel[i] + mFeedBackLeft;
//mCircularBufferRight.get()[mCircularBufferWriteHead] = RightChannel[i] + mFeedBackRight;
/* add ping pong */
mCircularBufferLeft.get() [mCircularBufferWriteHead] = leftChannel[i] + RightChannel[i] + mFeedBackRight;
mCircularBufferRight.get()[mCircularBufferWriteHead] = mFeedBackLeft;
/*we track Bpm from DAW and use that to set mDelayTimesInSamples, based on note*/
float beatPrSecond = playposinfo.bpm / 60;
float sampleTimeOneBeat = getSampleRate() / beatPrSecond;
mDelayTimesInSamples = sampleTimeOneBeat / *mNoteParameter;
/* we find the (delayed)data at position/index in buffer that we want to read from buffer behind the writeHead(mCircularBufferWriteHead) and to sum with original buffer data*/
mDelayReadHead = mCircularBufferWriteHead - mDelayTimesInSamples;
/*we check if mDelayReadHead gets less than zero. If so, we are wrapping around the buffer in the opposite direction*/
if(mDelayReadHead < 0)
{
mDelayReadHead += mCircularBufferLength;
}
/* here we set our values to put in lin_interp() */
int readHead_x = (int)mDelayReadHead; // Int is needed for mCircularBuffer array index access
int readHead_x1 = readHead_x + 1;
/* the float remainder value, used for interval between x and x1, where we want to compute a interpolated value */
float readHeadFloat = mDelayReadHead - readHead_x;
/*If we exceed our buffer*/
if(readHead_x1 >= mCircularBufferLength)
{
readHead_x1 -= mCircularBufferLength;
}
/* our interpolated delayed output audio data we want to sum with original audio buffer. added to feedback, then into circularbuffer above, and finaly summed below with original audio buffer buffer.addSample() delayed output */
float delay_sample_left = lin_interp(mCircularBufferLeft.get()[readHead_x], mCircularBufferLeft.get()[readHead_x1], readHeadFloat);
float delay_sample_right = lin_interp(mCircularBufferRight.get()[readHead_x], mCircularBufferRight.get()[readHead_x1], readHeadFloat);
/* here we scale our feedback audio data (* mFeedbackParameter) avoiding overloaded volume in DAW */
mFeedBackLeft = delay_sample_left * *mFeedbackParameter;
mFeedBackRight = delay_sample_right * *mFeedbackParameter;
-
here we add the delay/feedback to the wetBuffer*/
wetBuffer.addSample(0, i, delay_sample_left);wetBuffer.addSample(1, i, delay_sample_right);
// blockToUse.addSample(0, i, mFeedBackLeft);
// blockToUse.addSample(1, i, mFeedBackRight);
/* here we sum delayed signal/data to the original buffer data.
If DryWet is 0 only dry (original audio data/signal is heard)*/
buffer.setSample(0, i, buffer.getSample(0, i) * (1 - *mDryWetParameter) +
wetBuffer.getSample(0, i) * *mDryWetParameter );
buffer.setSample(1, i, buffer.getSample(1, i) * (1 - *mDryWetParameter) +
wetBuffer.getSample(1, i) * *mDryWetParameter );
mCircularBufferWriteHead++; // we increment writeHead
/* if end of array, we set writeHead to begining - circularbuffer concept */
if(mCircularBufferWriteHead >= mCircularBufferLength)
{
mCircularBufferWriteHead = 0;
}
}
}
PluginProcessor.cpp (14.1 KB)