Hey,
So I’m just getting started with JUCE and C++ (I come from a C background though so memory management isn’t entirely new to me) and while developing a VST plugin I’ve come across a problem I can’t seem to figure out. I’ve been able to build my plugin but the Plugin Host that comes with JUCE crashes every time I scan for new plugins and it locates it. I get a debug assertion failed error saying:
[quote]Program:…\Plugin Host.exe
File: f:\dd\vctools\crt_bld\self_x86\crt\src\dbgdel.cpp
Line: 52
Expression: _BLOCK_TYPE_IS_VALID(pHead->nBlockUse)[/quote]
From this I’ve been able to deduce it’s a memory related error.
When it crashes the last bit of the call stack lists:
[quote]> SpectralDelayPlugin.dll!operator delete(void * pUserData) Line 52 + 0x51 bytes C++
SpectralDelayPlugin.dll!operator delete[](void * p) Line 21 + 0x9 bytes C++
SpectralDelayPlugin.dll!FFTfilter::~FFTfilter() Line 58 + 0x12 bytes C++
SpectralDelayPlugin.dll!SpectralDelayPluginAudioProcessor::~SpectralDelayPluginAudioProcessor() Line 38 + 0x7a bytes C++
SpectralDelayPlugin.dll!SpectralDelayPluginAudioProcessor::scalar deleting destructor'() + 0x14 bytes C++ SpectralDelayPlugin.dll!JuceVSTWrapper::~JuceVSTWrapper() Line 297 + 0x26 bytes C++ SpectralDelayPlugin.dll!JuceVSTWrapper::
scalar deleting destructor’() + 0x14 bytes C++
SpectralDelayPlugin.dll!AudioEffect::dispatchEffectClass(AEffect * e, int opCode, int index, int value, void * ptr, float opt) Line 28 + 0x20 bytes C++
[/quote]
So I’m pretty sure it has to do when the destructor for the FFTfilter is called (FFTfilter is a class I wrote that preforms convolution via the frequency domain). I’m not sure exactly what the Plugin Host is doing at this point but I think once it finds a plugin it creates an instance of it to make sure it works and then destroys it. I’ve written the DSP algorithm in C++ in another program and after testing it I was reasonably sure my FFTfilter code didn’t have any bugs. At first I thought it was because I provided an empty default constructor so it might have been called and the destructor would try to delete memory that wasn’t assigned but upon further investigation I don’t think this is the case.
Here’s the code. I didn’t edit the PluginEditor stuff at all yet so I didn’t include it.
FFTfilter.h
[code]#ifndef FFTFILTER_H_INCLUDED
#define FFTFILTER_H_INCLUDED
#include “fftw3.h”
class FFTfilter
{
public:
//unused default constructor to statisfy compiler
FFTfilter();
//limits are posed on the size of the filter
//filter coefficents should be derived from an FFT the same length as the value passed
FFTfilter(const double* coeff_real, const double* coeff_imag, const int filter_order, const int FFT_size);
~FFTfilter();
//should this method overwrite the input if the output buffer is the same? sure, why not
void filter(double* input, double* output, const int length);
//flush over buffer incase signial isn’t processed continuously
void clearBuffer();
private:
int P;
int L;
int N;
const double *real_coeff;
const double *imag_coeff;
double *overlap;
double *in_fwd;
double *out_bwd;
fftw_complex *out_fwd;
fftw_complex in_bwd;
fftw_plan forward;
fftw_plan backward;
void fft_convolve(const double input, const int length);
};
class Bad_length{};
#endif // FFTFILTER_H_INCLUDED
[/code]
FFTfilter.cpp
[code]#include
#include “FFTfilter.h”
//unused default constructor to satisfy compiler, stuff is allocated in case it is called for some reason and the destructor is called later
FFTfilter::FFTfilter()
{
int num = 32;
overlap = new double[num];
in_fwd = new double[num];
out_bwd = new double[num];
out_fwd = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * (num/2 + 1));
in_bwd = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * (num/2 + 1));
//setup FFTw plans
forward = fftw_plan_dft_r2c_1d(num, in_fwd, out_fwd, FFTW_MEASURE);
backward = fftw_plan_dft_c2r_1d(num, in_bwd, out_bwd, FFTW_MEASURE);
}
FFTfilter::FFTfilter(const double* coeff_real, const double* coeff_imag, const int filter_order, const int FFT_size)
{
//copy and setup filter parameters
real_coeff = coeff_real;
imag_coeff = coeff_imag;
P = filter_order;
N = FFT_size;
L = N - P + 1;
//filter_order might not be the correct size here when delay is built into the stored offsets
overlap = new double[N];
in_fwd = new double[N];
out_bwd = new double[N];
out_fwd = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * (N/2 + 1));
in_bwd = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * (N/2 + 1));
//setup FFTw plans
forward = fftw_plan_dft_r2c_1d(N, in_fwd, out_fwd, FFTW_MEASURE);
backward = fftw_plan_dft_c2r_1d(N, in_bwd, out_bwd, FFTW_MEASURE);
//clear buffers
int i;
for(i = 0; i < N/2 + 1; ++i)
{
overlap[i] = 0.0;
in_fwd[i] = 0.0;
out_bwd[i] = 0.0;
in_bwd[i][0] = 0.0;
in_bwd[i][1] = 0.0;
out_fwd[i][0] = 0.0;
out_fwd[i][1] = 0.0;
}
for(; i < N; ++i)
{
overlap[i] = 0.0;
in_fwd[i] = 0.0;
out_bwd[i] = 0.0;
}
}
FFTfilter::~FFTfilter()
{
delete[] overlap;
delete[] in_fwd;
delete[] out_bwd;
fftw_free(out_fwd);
fftw_free(in_bwd);
fftw_destroy_plan(forward);
fftw_destroy_plan(backward);
}
void FFTfilter::filter(double* input, double* output, const int length)
{
int frames = length / L;
int remainder = length % L;
//j indexes which value from the input buffer should be copied, it should be incremented by the length passes to fft_convolve
//l indexes which spot in the output buffer the next value is going to be copied (or added too)
int j = 0, l = 0;
if(frames == 0 || (frames == 1 && remainder == 0))
{
//convolve the frame
fft_convolve(input, length);
//copy data into output buffer
int i;
for(i = 0; i < length; ++i)
{
//add data to delay line which increases write pointer by 1
output[i] = ((out_bwd[i] + overlap[i]) / N);
}
//copy overlapping output for next call
for(int k = 0; k < P - 1; ++i, ++k)
{
overlap[k] = out_bwd[i];
}
}
else if(frames == 1 && remainder > 0) //if there's only one subframe and remainder to process
{
//filter the only subframe
fft_convolve(input, L);
j += L;
//clear the output buffer as it will be used in calculations
for(int i = 0; i < length; ++i)
{
output[i] = 0.0;
}
//copy the entire processed frame into the output buffer if the remainder is large enough
if(remainder - (P - 1) >= 0)
{
for(int k = 0; k < N; ++k, ++l)
{
output[l] = (out_bwd[k] + overlap[k]) / N;
//clear the overlap buffer so last subframe and remainder can use it
overlap[k] = 0.0;
}
l -= (P - 1);
}
else //the extension from the frame will overlap with the "overlap" that needs to be stored for the next processed frame
{
int m, k;
for(k = 0; k < L + remainder; ++k, ++l)
{
output[l] = (out_bwd[k] + overlap[k] )/ N;
//clear the overlap buffer so it can be used right after
overlap[k] = 0.0;
}
l -= remainder;
//store the overlap
for(m = 0; m < (P - 1) - remainder; ++m, ++k)
{
overlap[m] = out_bwd[k];
}
}
//process the remainder
//filter that sumbitch
fft_convolve(&(input[j]), remainder);
j += remainder;
int i;
//copy the convolved remainder into the output buffer
for(i = 0; i < remainder; ++i, ++l)
{
output[l] += out_bwd[i] / N;
}
//copy the extension into the overlap buffer for the next call to processReplacing
for(int k = 0; i < remainder + (P - 1); ++i, ++k)
{
overlap[k] += out_bwd[i];
}
}
else //frame is greater than 2L and large enough to cause time-domain aliasing so split it into subframes and overlap-add them
{
//clear the output buffer as it will be used in calculations
for(int i = 0; i < length; ++i)
{
output[i] = 0.0;
}
for(int i = 0; i < frames; ++i)
{
fft_convolve(&(input[j]), L);
j += L;
//copy data into output buffer taking overlap into account
if(i == 0)
{
for(int k = 0; k < N; ++k, ++l)
{
output[l] = (out_bwd[k] + overlap[k]) / N;
//clear the overlap buffer so last subframe and remainder can use it
overlap[k] = 0.0;
}
l -= (P - 1);
}
//check to see if there's any overlap between the remainder's extension and the last subframes extension from convolution
//if there is overlap then ensure the overlap buffer has the correct data for the next call to processReplacing
else if(i == frames - 1)
{
if(remainder - (P - 1) >= 0)
{
for(int k = 0; k < N; ++k, ++l)
{
output[l] += out_bwd[k] / N;
}
l -= (P - 1);
}
else
{
int m, k;
for(k = 0; k < L + remainder; ++k, ++l)
{
output[l] += out_bwd[k] / N;
}
l -= remainder;
//store the overlap
for(m = 0; m < (P - 1) - remainder; ++m, ++k)
{
overlap[m] = out_bwd[k];
}
}
}
else
{
for(int k = 0; k < N; ++k, ++l)
{
output[l] += out_bwd[k] / N;
}
l -= (P - 1);
}
}
//handle the remainder of the subframe if it exists
if(remainder > 0)
{
//copy the data into the input buffer making sure to clear the old values to ensure proper zero padding
fft_convolve(&(input[j]), remainder);
j += remainder;
int i;
//copy the remainder into the output buffer
for(i = 0; i < remainder; ++i, ++l)
{
output[l] += out_bwd[i] / N;
}
//copy the extension into the overlap buffer for the next call to processReplacing
for(int k = 0; i < remainder + (P - 1); ++i, ++k)
{
overlap[k] += out_bwd[i];
}
}
}
}
void FFTfilter::fft_convolve(const double* input, const int length)
{
if(length > L)
{
std::cerr << “Buffer to big for convolution… Aborting” << std::endl;
throw Bad_length();
}
int i;
//copy the input into the fftw buffer
for(i = 0; i < length; ++i)
{
in_fwd[i] = input[i];
}
//ensure the signal is padded with zeros
for(; i < N; ++i)
{
in_fwd[i] = 0.0;
}
//FFT the zero-padded signal
fftw_execute(forward);
//multiply the signal FFT by the previously computed filter response FFT
for(int k = 0; k < N/2 + 1; ++k)
{
//(a+bi)(c+di) = (ac-bd) + (ad+bc)i
in_bwd[k][0] = (out_fwd[k][0] * real_coeff[k]) - (out_fwd[k][1] * imag_coeff[k]);
in_bwd[k][1] = (out_fwd[k][0] * imag_coeff[k]) + (out_fwd[k][1] * real_coeff[k]);
}
//IFFT the convolved signal (don't foget it's unnormalized)
fftw_execute(backward);
}
void FFTfilter::clearBuffer(void)
{
for(int i = 0; i < N; ++i)
{
overlap[i] = 0.0;
}
}
[/code]
PluginProcessor.h
[code]/*
This file was auto-generated by the Jucer!
It contains the basic startup code for a Juce application.
==============================================================================
*/
#ifndef PLUGINPROCESSOR_H_C0FD7140
#define PLUGINPROCESSOR_H_C0FD7140
#include
#include “…/JuceLibraryCode/JuceHeader.h”
#include “CircularBuffer.h”
#include “FFTfilter.h”
//==============================================================================
/**
*/
class SpectralDelayPluginAudioProcessor : public AudioProcessor
{
public:
//==============================================================================
SpectralDelayPluginAudioProcessor();
~SpectralDelayPluginAudioProcessor();
//==============================================================================
void prepareToPlay (double sampleRate, int samplesPerBlock);
void releaseResources();
void processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages);
//==============================================================================
AudioProcessorEditor* createEditor();
bool hasEditor() const;
//==============================================================================
const String getName() const;
int getNumParameters();
float getParameter (int index);
void setParameter (int index, float newValue);
const String getParameterName (int index);
const String getParameterText (int index);
const String getInputChannelName (int channelIndex) const;
const String getOutputChannelName (int channelIndex) const;
bool isInputChannelStereoPair (int index) const;
bool isOutputChannelStereoPair (int index) const;
bool acceptsMidi() const;
bool producesMidi() const;
//==============================================================================
int getNumPrograms();
int getCurrentProgram();
void setCurrentProgram (int index);
const String getProgramName (int index);
void changeProgramName (int index, const String& newName);
//==============================================================================
void getStateInformation (MemoryBlock& destData);
void setStateInformation (const void* data, int sizeInBytes);
private:
//==============================================================================
int N;
//int Fs = 44100;
//delay line length/max delay in samples
int maxDelay;
int numFilters;
//filter objects
FFTfilter f0_filter;
FFTfilter f1_filter;
FFTfilter f2_filter;
FFTfilter f3_filter;
FFTfilter f4_filter;
//delay lines for filter outputs
CircularBuffer<double> f0_delayLine;
CircularBuffer<double> f1_delayLine;
CircularBuffer<double> f2_delayLine;
CircularBuffer<double> f3_delayLine;
CircularBuffer<double> f4_delayLine;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SpectralDelayPluginAudioProcessor);
};
#endif // PLUGINPROCESSOR_H_C0FD7140
[/code]
PluginProcessor.cpp
[code]/*
This file was auto-generated by the Jucer!
It contains the basic startup code for a Juce application.
==============================================================================
*/
#include “PluginProcessor.h”
#include “PluginEditor.h”
#include “FilterCoeffs.h”
//==============================================================================
SpectralDelayPluginAudioProcessor::SpectralDelayPluginAudioProcessor()
{
N = 4096;
numFilters = 5;
//create filter objects
f0_filter = FFTfilter(filters_real[0], filters_imag[0], filter_orders[0], N);
f1_filter = FFTfilter(filters_real[1], filters_imag[1], filter_orders[1], N);
f2_filter = FFTfilter(filters_real[2], filters_imag[2], filter_orders[2], N);
f3_filter = FFTfilter(filters_real[3], filters_imag[3], filter_orders[3], N);
f4_filter = FFTfilter(filters_real[4], filters_imag[4], filter_orders[4], N);
//create delay lines
f0_delayLine = CircularBuffer<double>();
f1_delayLine = CircularBuffer<double>();
f2_delayLine = CircularBuffer<double>();
f3_delayLine = CircularBuffer<double>();
f4_delayLine = CircularBuffer<double>();
}
SpectralDelayPluginAudioProcessor::~SpectralDelayPluginAudioProcessor()
{
}
//==============================================================================
const String SpectralDelayPluginAudioProcessor::getName() const
{
return JucePlugin_Name;
}
int SpectralDelayPluginAudioProcessor::getNumParameters()
{
return 0;
}
float SpectralDelayPluginAudioProcessor::getParameter (int index)
{
return 0.0f;
}
void SpectralDelayPluginAudioProcessor::setParameter (int index, float newValue)
{
}
const String SpectralDelayPluginAudioProcessor::getParameterName (int index)
{
return String::empty;
}
const String SpectralDelayPluginAudioProcessor::getParameterText (int index)
{
return String::empty;
}
const String SpectralDelayPluginAudioProcessor::getInputChannelName (int channelIndex) const
{
return String (channelIndex + 1);
}
const String SpectralDelayPluginAudioProcessor::getOutputChannelName (int channelIndex) const
{
return String (channelIndex + 1);
}
bool SpectralDelayPluginAudioProcessor::isInputChannelStereoPair (int index) const
{
return true;
}
bool SpectralDelayPluginAudioProcessor::isOutputChannelStereoPair (int index) const
{
return true;
}
bool SpectralDelayPluginAudioProcessor::acceptsMidi() const
{
#if JucePlugin_WantsMidiInput
return true;
#else
return false;
#endif
}
bool SpectralDelayPluginAudioProcessor::producesMidi() const
{
#if JucePlugin_ProducesMidiOutput
return true;
#else
return false;
#endif
}
int SpectralDelayPluginAudioProcessor::getNumPrograms()
{
return 0;
}
int SpectralDelayPluginAudioProcessor::getCurrentProgram()
{
return 0;
}
void SpectralDelayPluginAudioProcessor::setCurrentProgram (int index)
{
}
const String SpectralDelayPluginAudioProcessor::getProgramName (int index)
{
return String::empty;
}
void SpectralDelayPluginAudioProcessor::changeProgramName (int index, const String& newName)
{
}
//==============================================================================
void SpectralDelayPluginAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
// Use this method as the place to do any pre-playback
// initialisation that you need…
maxDelay = sampleRate*2;
//resize and clear delay lines
f0_delayLine.changeSize(maxDelay + phase_offsets[0]);
f1_delayLine.changeSize(maxDelay + phase_offsets[1]);
f2_delayLine.changeSize(maxDelay + phase_offsets[2]);
f3_delayLine.changeSize(maxDelay + phase_offsets[3]);
f4_delayLine.changeSize(maxDelay + phase_offsets[4]);
for(int i = 0; i < maxDelay; ++i)
{
f0_delayLine[i] = 0.0;
f1_delayLine[i] = 0.0;
f2_delayLine[i] = 0.0;
f3_delayLine[i] = 0.0;
f4_delayLine[i] = 0.0;
}
}
void SpectralDelayPluginAudioProcessor::releaseResources()
{
// When playback stops, you can use this as an opportunity to free up any
// spare memory, etc.
}
//this plugin can only handle one channel of input and has only one channel of output
void SpectralDelayPluginAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages)
{
// This is the place where you’d normally do the guts of your plugin’s
// audio processing…
int numSamples = buffer.getNumSamples();
ScopedPointer doubleInput = new double[numSamples];
ScopedPointer doubleOutput_f0 = new double[numSamples];
ScopedPointer doubleOutput_f1 = new double[numSamples];
ScopedPointer doubleOutput_f2 = new double[numSamples];
ScopedPointer doubleOutput_f3 = new double[numSamples];
ScopedPointer doubleOutput_f4 = new double[numSamples];
ScopedPointer functionOutput = new float[numSamples];
// …do something to the data…
float* channelData = buffer.getSampleData (0);
//convert input data to doubles for now, ask on forums if it’s possible use use doubles instead w/ AudioSampleBuffer otherwise rewrite filter’s to use floats instead (maybe make a template for different numeric types)
for(int i = 0; i < numSamples; ++i)
{
doubleInput[i] = channelData[i];
functionOutput[i] = 0.0;
}
f0_filter.filter(doubleInput, doubleOutput_f0, numSamples);
f1_filter.filter(doubleInput, doubleOutput_f1, numSamples);
f2_filter.filter(doubleInput, doubleOutput_f2, numSamples);
f3_filter.filter(doubleInput, doubleOutput_f3, numSamples);
f4_filter.filter(doubleInput, doubleOutput_f4, numSamples);
//copy filter output to the correct delay line and copy to the function’s output at the same time
for(int i = 0; i < numSamples; ++i)
{
f0_delayLine.addData(doubleOutput_f0[i]);
f1_delayLine.addData(doubleOutput_f1[i]);
f2_delayLine.addData(doubleOutput_f2[i]);
f3_delayLine.addData(doubleOutput_f3[i]);
f4_delayLine.addData(doubleOutput_f4[i]);
functionOutput[i] = float(f0_delayLine[f0_delay_amt] + f1_delayLine[f1_delay_amt] + f2_delayLine[f2_delay_amt] + f3_delayLine[f3_delay_amt] + f4_delayLine[f4_delay_amt]);
}
//clear the buffer and copy the output data to it
buffer.clear();
buffer.copyFrom(0, 0, functionOutput, numSamples);
}
//==============================================================================
bool SpectralDelayPluginAudioProcessor::hasEditor() const
{
return true; // (change this to false if you choose to not supply an editor)
}
AudioProcessorEditor* SpectralDelayPluginAudioProcessor::createEditor()
{
return new SpectralDelayPluginAudioProcessorEditor (this);
}
//==============================================================================
void SpectralDelayPluginAudioProcessor::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.
}
void SpectralDelayPluginAudioProcessor::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…
AudioProcessor* JUCE_CALLTYPE createPluginFilter()
{
return new SpectralDelayPluginAudioProcessor();
}
[/code]
The Plugin Host throws the assertion at the first line of ~FFTfilter() where it says delete[] overlap. Any help would be greatly appreciated as this has consumed a lot of my time over the past few days.
Thanks,
Graham