Hi there!
I want to build an audio plugin which needs some sort of bandpass filtering. So with some research I found the DSP Framework from Vinnie Falco and tried to implement that into my project.
First of all: This is my really first audio plugin. It should become a multiband compressor, lets see if it can be published one day.
A short description of what I want to do:
I started using the audio plugin template from the projucer and implemented a litte GUI which offers me 3 sliders. Each one controls the cut off frequency of a highpass and lowpass filter (these are Dsp::SmoothedFilterDesign<Dsp::Butterworth::Design::XXXXpass<4>,2> (1024), where XXXX is ‘High’ or ‘Low’).
In the end there should be 4 Frequencybands: Sub, Low, Mid, High. To get each Band I don’t want to use bandpass filters, because later the filters will be Linkwitz-Riley ones.
For each band there should be a compressor applied (I found one on the net, if you are interessted I will copy the link for you).
In the end all bands are summed up.
That’s the short description of my project, now the issues:
My audio is kind of ringing
Each time I want to evaluate my compressor functions (regardless of any prior filterings) there is some kind of ringing or distortion in my audio. It does not matter where I apply the compressor (within some filtered bands or on the whole buffer). So my suggestion is some pointer / copy issues.
To explain my problem I will start with the PluginProcessor code and then going into detail of my compressor class.
So to initialise my buffers and filters I use the prepareToPlay function and apply these filters onto some bandpass buffers which are copies from the buffer in processBlock:
My whole code is available at: this link
void MultibandCompressorAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) { // Use this method as the place to do any pre-playback // initialisation that you need.. internal_sampleRate = sampleRate; // initialise the buffers we need for the later on filterings subBand = new AudioSampleBuffer(INIT_FILTER_CHANNELS,samplesPerBlock); lowBand = new AudioSampleBuffer(INIT_FILTER_CHANNELS,samplesPerBlock); lowerBands = new AudioSampleBuffer(INIT_FILTER_CHANNELS,samplesPerBlock); higherBands = new AudioSampleBuffer(INIT_FILTER_CHANNELS,samplesPerBlock); midBand = new AudioSampleBuffer(INIT_FILTER_CHANNELS,samplesPerBlock); highBand = new AudioSampleBuffer(INIT_FILTER_CHANNELS,samplesPerBlock); // Now initialise the filters I need for the bandpass filtering using butterworth designs subpass = new Dsp::SmoothedFilterDesign<Dsp::Butterworth::Design::LowPass, INIT_FILTER_CHANNELS> (INIT_FILTER_SMOOTH); lowpass = new Dsp::SmoothedFilterDesign<Dsp::Butterworth::Design::HighPass, INIT_FILTER_CHANNELS> (INIT_FILTER_SMOOTH); setSubpass((double)INIT_LOW_FREQ); setLowpass((double)INIT_LOW_FREQ); lowerpass = new Dsp::SmoothedFilterDesign<Dsp::Butterworth::Design::LowPass, INIT_FILTER_CHANNELS> (INIT_FILTER_SMOOTH); higherpass = new Dsp::SmoothedFilterDesign<Dsp::Butterworth::Design::HighPass, INIT_FILTER_CHANNELS> (INIT_FILTER_SMOOTH); setLowerpass((double)INIT_MID_FREQ); setHigherpass((double)INIT_MID_FREQ); midpass = new Dsp::SmoothedFilterDesign<Dsp::Butterworth::Design::LowPass, INIT_FILTER_CHANNELS> (INIT_FILTER_SMOOTH); highpass = new Dsp::SmoothedFilterDesign<Dsp::Butterworth::Design::HighPass, INIT_FILTER_CHANNELS> (INIT_FILTER_SMOOTH); setMidpass((double)INIT_HIGH_FREQ); setHighpass((double)INIT_HIGH_FREQ); SubMute = false; LowMute = false; MidMute = false; HighMute = false; Active = true; midCompressor = new Compressor(); midCompressor->setBuffer(midBand); } void MultibandCompressorAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) { if(Active) { // divide the buffer into sub, low, mid and high bands by copying the buffer // and then apply some highpass and lowpass filters to the data // start with the lower frequencies lowerBands->copyFrom(0, 0, buffer, 0, 0, buffer.getNumSamples()); lowerBands->copyFrom(1, 0, buffer, 1, 0, buffer.getNumSamples()); lowerpass->process(lowerBands->getNumSamples(), lowerBands->getArrayOfWritePointers()); subBand->copyFrom(0, 0, *lowerBands, 0, 0, lowerBands->getNumSamples()); subBand->copyFrom(1, 0, *lowerBands, 1, 0, lowerBands->getNumSamples()); subpass->process(subBand->getNumSamples(), subBand->getArrayOfWritePointers()); lowBand->copyFrom(0, 0, *lowerBands, 0, 0, lowerBands->getNumSamples()); lowBand->copyFrom(1, 0, *lowerBands, 1, 0, lowerBands->getNumSamples()); lowpass->process(lowBand->getNumSamples(), lowBand->getArrayOfWritePointers()); // now the higher frequencies higherBands->copyFrom(0, 0, buffer, 0, 0, buffer.getNumSamples()); higherBands->copyFrom(1, 0, buffer, 1, 0, buffer.getNumSamples()); higherpass->process(higherBands->getNumSamples(), higherBands->getArrayOfWritePointers()); midBand->copyFrom(0, 0, *higherBands, 0, 0, higherBands->getNumSamples()); midBand->copyFrom(1, 0, *higherBands, 1, 0, higherBands->getNumSamples()); midpass->process(midBand->getNumSamples(), midBand->getArrayOfWritePointers()); highBand->copyFrom(0, 0, *higherBands, 0, 0, higherBands->getNumSamples()); highBand->copyFrom(1, 0, *higherBands, 1, 0, higherBands->getNumSamples()); highpass->process(highBand->getNumSamples(), highBand->getArrayOfWritePointers()); // now the bandpassed data is within the buffers subBand, lowBand, midBand, highBand // so clear the original buffer and then sum it up from the individual bands for (int i = 0; i < 2; ++i) buffer.clear (i, 0, buffer.getNumSamples()); if (!SubMute) { buffer.addFrom(0, 0, *subBand, 0, 0, subBand->getNumSamples()); buffer.addFrom(1, 0, *subBand, 1, 0, subBand->getNumSamples()); } if (!LowMute) { buffer.addFrom(0, 0, *lowBand, 0, 0, lowBand->getNumSamples()); buffer.addFrom(1, 0, *lowBand, 1, 0, lowBand->getNumSamples()); } if (!MidMute) { // for debugging reasons I started using only one compressor. If you // comment the next line out, the ringing is gone midCompressor->process(); buffer.addFrom(0, 0, *midBand, 0, 0, midBand->getNumSamples()); buffer.addFrom(1, 0, *midBand, 1, 0, midBand->getNumSamples()); } if (!HighMute) { buffer.addFrom(0, 0, *highBand, 0, 0, highBand->getNumSamples()); buffer.addFrom(1, 0, *highBand, 1, 0, highBand->getNumSamples()); } } }
So I give the pointer of my midBand to the compressor class and then later on I compress my audio data using the method midCompressor->process(); Now here’s the code of my compressor class (neglecting most getter/setter)
Compressor.h
#include "../JuceLibraryCode/JuceHeader.h" class Compressor { public: //============================================================================== Compressor(); ~Compressor(); //============================================================================== void setBuffer(AudioSampleBuffer *newbuffer); void setAttack(double newTauAttack); void setAttack(double newTauAttack, double sampleRate); void setRelease(double newTauRelease); void setRelease(double newTauRelease,double sampleRate); void setThreshold(double newThreshold); void setRatio(double newRatio); void setWeighting(double newWeighting); void setMakeup(double newMakeup); void setActive(bool newActive); void toggleActive(); //============================================================================== AudioSampleBuffer* getBufferPointer(); float getAttackSEC(); float getAttackMSEC(); float getReleaseSEC(); float getReleaseMSEC(); float getThreshold(); float getRatio(); float getWeighting(); float getMakeup(); bool isActive(); //============================================================================== void process(); void process(AudioSampleBuffer &buffer, int m); void resetAll(); //============================================================================== private: // AudioBuffer for Compressor AudioSampleBuffer *buffer, data; // time constraints float alphaAttack, tauAttack, alphaRelease, tauRelease; double sampleRate; // compressor settings float thresh, ratio, weighting, halfWeighting, makeup; bool active; // compressor internal calculations float *xL, *xG, *yG, *yL, yLprev, dbMin, linMin, *appliedGain; int numChannels, numSamples; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Compressor); };
Compressor.cpp (some methods are not written here. They’re obvious)
#include "Compressor.h" Compressor::Compressor() { linMin = 0.000001; dbMin = -120; // makeup = 0.0; thresh = 0.0; ratio = 1.0; active = false; setAttack(0.0); setRelease(0.0); sampleRate = 48000; } void Compressor::setBuffer(AudioSampleBuffer *newbuffer) { buffer = newbuffer; data.setSize(buffer->getNumChannels(),buffer->getNumSamples()); data.clear(); xL = new float[buffer->getNumSamples() * buffer->getNumChannels()]; xG = new float[buffer->getNumSamples() * buffer->getNumChannels()]; yG = new float[buffer->getNumSamples() * buffer->getNumChannels()]; yL = new float[buffer->getNumSamples() * buffer->getNumChannels()]; for(int i = 0; i < buffer->getNumSamples()*buffer->getNumChannels(); i++) { xL[i] = 0; xG[i] = 0; yG[i] = 0; yL[i] = 0; } numChannels = buffer->getNumChannels(); numSamples = buffer->getNumSamples(); } void Compressor::setAttack(double newTauAttack) { tauAttack = newTauAttack; alphaAttack = exp(-1000 / (sampleRate * tauAttack)); } void Compressor::setRelease(double newTauRelease) { tauRelease = newTauRelease; alphaRelease = exp(-1000 / (sampleRate * tauRelease)); } void Compressor::toggleActive() { active = !active; if (active) { for(int i = 0; i < numSamples*numChannels; i++) { xL[i] = 0; xG[i] = 0; yG[i] = 0; yL[i] = 0; } } } void Compressor::process() { if(!active) return; if (fabs(thresh) < 0.00001) return; yLprev = 0.0; for (int m = 0; m < numChannels; ++m) { data.clear(); data.addFrom(m, 0, *buffer, m*2, 0, data.getNumSamples(), 0.5); data.addFrom(m, 0, *buffer, m*2+1, 0, data.getNumSamples(), 0.5); for(int i = 0; i < numSamples; ++i) { if(fabs(data.getWritePointer(m)[i]) < linMin) { xG[i + m*numChannels] = -120; } else { xG[i + m*numChannels] = 20*log10(fabs(data.getWritePointer(m)[i])); } // compressor curve if(xG[i + m*numChannels] >= thresh) { yG[i + m*numChannels] = thresh + (xG[i + m*numChannels] - thresh) / ratio; } else { yG[i + m*numChannels] = xG[i + m*numChannels]; } // attack and release xL[i + m*numChannels] = xG[i + m*numChannels] - yG[i + m*numChannels]; if(xL[i + m*numChannels] > yLprev) { yL[i + m*numChannels] = alphaAttack * yLprev + (1 - alphaAttack) * xL[i + m*numChannels]; } else { yL[i + m*numChannels] = alphaRelease * yLprev + (1 - alphaRelease) * xL[i + m*numChannels]; } // apply gain to the buffer (*data.getWritePointer(m,i)) *= pow(10,(makeup - yL[i + m*numChannels])/20); yLprev = yL[i + m*numChannels]; } } }
Do you have a clue why my data is ringing? Thanks for your help