[SOLVED] Leak Issues after loading Sample Files

Hi all,

I load WAV Samples into my Plugin with this Function:

void EightAudioProcessor::loadFile()
{
    FileChooser chooser{ "Please load a File" };

    if (chooser.browseForFileToOpen())
    {
        auto file = chooser.getResult();
        mFormatReader = mFormatManager.createReaderFor(file);
    }
    
    BigInteger range;
    range.setRange(0, 128, true);

    mSampler.addSound(new SamplerSound("Sample", *mFormatReader, range, 60, 0.0, 0.001, 10.0));
}

When I then quit the Standalone Version of my Plugin I get the following Leaks:

*** Leaked objects detected: 1 instance(s) of class WavAudioFormatReader
JUCE Assertion failure in juce_LeakedObjectDetector.h:90
Eight.exe hat einen Haltepunkt ausgelöst.

*** Leaked objects detected: 1 instance(s) of class AudioFormatReader
JUCE Assertion failure in juce_LeakedObjectDetector.h:90
Eight.exe hat einen Haltepunkt ausgelöst.

*** Leaked objects detected: 1 instance(s) of class BigInteger
JUCE Assertion failure in juce_LeakedObjectDetector.h:90
Eight.exe hat einen Haltepunkt ausgelöst.

*** Leaked objects detected: 1 instance(s) of class FileInputStream
JUCE Assertion failure in juce_LeakedObjectDetector.h:90
Eight.exe hat einen Haltepunkt ausgelöst.

*** Leaked objects detected: 1 instance(s) of class InputStream
JUCE Assertion failure in juce_LeakedObjectDetector.h:90
Eight.exe hat einen Haltepunkt ausgelöst.

*** Leaked objects detected: 1 instance(s) of class StringPairArray
JUCE Assertion failure in juce_LeakedObjectDetector.h:90
Eight.exe hat einen Haltepunkt ausgelöst.

*** Leaked objects detected: 2 instance(s) of class StringArray
JUCE Assertion failure in juce_LeakedObjectDetector.h:90
Eight.exe hat einen Haltepunkt ausgelöst.

I tought I need to destruct the Objects I use in the loadFile Function but it seems they are not disposable / deletable. What could I do to fix the Leak Issues ?

Thank’s a lot!

I tried:

void EightAudioProcessor::loadFile()
{
   ...
   range = NULL;
}

But that throw also the *** Leaked objects detected: 1 instance(s) of class BigInteger Leak.

From the docs :

It’s the caller’s responsibility to delete the reader that is returned.

when you’re done reading the file you must either delete mFormatReader; or use unique_ptr to manage the lifetime for you (better practice) and the leak will go away.

Thank’s a lot Richie!

I used:

EightAudioProcessor::~EightAudioProcessor()
{
    mFormatReader = nullptr;
}

I changed it to:

EightAudioProcessor::~EightAudioProcessor()
{
    delete mFormatReader;
}

Now all Leaks are gone.

1 Like

No problem.

You should really test that mFormatReader is not nullptr before trying to delete it in case the AudioFormatReader could not be created and createReaderFor had returned nullptr (in fact you should also not add your sound in that case too).

Or use std::unique_ptr<AudioFormatReader> to automatically delete it if it’s not a nullptr

1 Like

I debated about mentioning best practice, but my own laziness won the argument :joy:

Thank’s Richie. Changed it to:

EightAudioProcessor::~EightAudioProcessor()
{
    if (mFormatReader != nullptr) delete mFormatReader;
}

I also tried your Approach:

Changed from:

// PluginProcessor.h

...
private:
    Synthesiser mSampler;
    const int mNumVoices { 16 };

    AudioFormatManager mFormatManager;
    AudioFormatReader* mFormatReader{ nullptr };
    ...

To:

// PluginProcessor.h

...
private:
    Synthesiser mSampler;
    const int mNumVoices { 16 };

    AudioFormatManager mFormatManager;
    std::unique_ptr<AudioFormatReader*> mFormatReader{ nullptr };
    ...

But that doesn’t start the Plugin at all.

Finally moved the delete Statement for the mFormatReader to the loadFile function directly:

void EightAudioProcessor::loadFile()
{
    FileChooser chooser{ "Please load a File" };

    if (chooser.browseForFileToOpen())
    {
        auto file = chooser.getResult();
        mFormatReader = mFormatManager.createReaderFor(file);
    }
    
    BigInteger range;
    range.setRange(0, 128, true);

    mSampler.addSound(new SamplerSound("Sample", *mFormatReader, range, 60, 0.0, 0.001, 10.0));

    if (mFormatReader != nullptr) delete mFormatReader;
}

and removed if from the Destructor:

EightAudioProcessor::~EightAudioProcessor()
{
    
}

Thank’s a lot to Richie and Daniel!

That is the right approach when using the format reader with the basic Juce SamplerSound, since the sound class uses the audio format reader only during its constructor. (It uses the format reader to read the audio file into an AudioBuffer and the format reader is not used for anything after that.) A further improvement would be to remove the format reader from your member variables and just declare it locally in the loadFile method.

1 Like

+1 for keepoing the reader only in the loadFile method (Xenakios)

The error was std::unique_ptr needs the type, not a pointer.

+1 for using unique_ptr regardless (danielruderich)

If you need to call delete, there is most likely a better solution.
It is worth using unique_ptr even within that limited scope, since even with an early termination the code will not leak (e.g. exception or adding a return in a later refactor).

It would look like this:

void EightAudioProcessor::loadFile()
{
    FileChooser chooser{ "Please load a File" };

    if (chooser.browseForFileToOpen())
    {
        auto file = chooser.getResult();
        std::unique_ptr<AudioFormatReader> reader (mFormatManager.createReaderFor(file));
        if (reader)
        {
            BigInteger range;
            range.setRange(0, 128, true);
            mSampler.addSound(new SamplerSound("Sample", *reader, range, 60, 0.0, 0.001, 10.0));
        }
    }
}

This solution also avoids adding disfunctional SamplerSounds

2 Likes

Thank’s Daniel and Xenakios!

I changed the Header and removed the Member Variable:

// PluginProcessor.h

private:
    Synthesiser mSampler;
    const int mNumVoices { 16 };

    AudioFormatManager mFormatManager;
    // AudioFormatReader* mFormatReader{ nullptr };

And I moved it to the loadFile Function as unique_ptr:

// PluginProcessor.cpp

void EightAudioProcessor::loadFile()
{
    FileChooser chooser{ "Please load a File" };

    if (chooser.browseForFileToOpen())
    {
        auto file = chooser.getResult();
        std::unique_ptr<AudioFormatReader> formatReader(mFormatManager.createReaderFor(file));
        if (formatReader)
        {
            BigInteger range;
            range.setRange(0, 128, true);
            mSampler.addSound(new SamplerSound("Sample", *formatReader, range, 60, 0.0, 0.001, 10.0));
        }
    }
}

Actually I gave bad advice here, and you don’t need to worry about trying to delete a nullptr, as I’ve just learned that my assumption about trying to delete a nullptr was completely false. However the advice to not addSound with a nullptr was at least correct (as demonstrated with better practice code by Xenakios and Daniel).

Thank’s Richie!