Assertion failure when Plugin Host scans for plugin


#1

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


#2

there are possible other bugs in it, but some are very obvious

[quote]//filter objects
FFTfilter f0_filter;[/quote]

you creating a stack object, and reserve the memory inside the constructor

now you create a second stack object and assign all its internal values to the old object, the new object will immediality destroyed and the memory reservations will be free. When you now delete the old object you get of cause a problem, because you already freed the memory inside, also you have memory leaks, because you never freed the memory of the old object. What you want is something like

or instead


#3

Thank you! That solved the problem. Just to make sure I understand things why does

also get created on the stack? Is the only way to get memory from the heap through either using new or malloc? Also, why is the new object immediately destroyed? Is it because the assignment operator only copies the values to the lvalue after calling the constructor, and afterwards since the constructor call wasn’t assigned any variable/memory to live in on it’s own it can’t exist? Is the object immediately destroyed or destroyed when the program execution leaves the scope of the constructor?