[BR] juce_vst3_helper and juce_AudioTransportSource

I copied some old code from a Projucer project to a fresh CMake project and noticed something weird in juce::AudioTransportSource.

This only happens when building VST3. AU and SA build fine.

VST3 will fail to build with some Segfault message once I call transportSource.setSource(...);

I took a deep dive into juce_AudioTransportSource.cpp and started commenting everything out, until the segfault message went away and the VST3 was built.

The problem was in line 95:
masterSource = newMasterSource;
Which is an assignment of one AudioSource* to another.

Looks like that juce_vst3_helper is having an issue with this.

JUCE VERSION 7.0.8

JUCE now loads VST3 plugins at the end of the build process in order to generate a manifest data file that is stored inside the VST3 bundle.

It sounds like the plugin is crashing when it is loaded. This is most likely to be an issue with the plugin itself.

To debug, you could:

  • Debug the standalone or AU products as normal, or
  • Manually copy the VST3 to the install location (~/Library/Audio/Plug-Ins/VST3) after it is built and debug that.

actually the plugin wasn’t crashed when loading, but just refused to build the VST3 (AU and SA build fine, and the same code built VST3 fine on an old Xcode project).

I was able to solve it by switching the raw pointer. So this:

std::unique_ptr<juce::AudioFormatReaderSource> newSource (new juce::AudioFormatReaderSource (reader, true));
ransportSource.setSource (newSource.get(), 0, nullptr, reader->sampleRate);

became this:

auto newSource (new juce::AudioFormatReaderSource (reader, true));
transportSource.setSource (newSource, 0, nullptr, reader->sampleRate);

Are you really sure that there was no crash? Because the code snippet you show looks a bit suspicious. How was the lifetime of the unique pointer managed before?

Given that a std::unique_ptr will delete the held object once it goes out of scope, it’s basic C++ knowledge that every object allocated with new has to be deallocated with delete (or better be stored in a unique ptr that does that for you once it goes out of scope) in order to prevent a memory leak and the documentation of AudioTransportSource::setSource states “The source passed in will not be deleted by this object, so must be managed by the caller” I wonder if the situation was like this before:

The newly allocated AudioFormatReaderSource instance was stored in a local unique ptr, the address of the allocated object was passed to setSource and at some point the unique pointer got out of scope and the reader source was deleted. Later the transport source attempted to access it but ran into an access violation since the object no longer existed. Your solution was then to just allocate an instance and let it leak, which of course solves the problem of accessing the object later but also creates a memory leak.

The right way would be to store the AudioFormatReaderSource instance as class member along the transport source. Important here is to ensure that the transport source is deleted first, so it should be located after the audio format reader source as members are deleted from bottom to top according to their declaration in the class.

So in your class declare the members like

private:

std::unique_ptr<juce::AudioFormatReaderSource> audioFormatReaderSource;
juce::AudioTransportSource transportSource;

Then initialise them like

audioFormatReaderSource = std::make_unique<juce::AudioFormatReaderSource> (new juce::AudioFormatReaderSource (reader, true));
ransportSource.setSource (newSource.get(), 0, nullptr, reader->sampleRate);

Does this also solve it? In that case you probably really had a hidden crash in the first place and solved it via a memory leak :wink:

As I said, only guesswork since I don’t know the surrounding code, so I might also be wrong with my assumptions here.

1 Like

Sorry - I realize now I wasn’t clear with my update. You are correct - using a raw pointer does create a memory leak, but I wasn’t trying to show the correct way of setting up a source. I was just trying to understand what would make the VST project build in CLion, and turns out that replacing std::unique_ptr with a raw pointer did the trick. So I’ll summarize again my finding about this issue:

  • in an old Projucer project (not that old, just from a few months ago) I was using std::unique_ptr to set the source and all was fine.
  • in a fresh CLion project from today, when I copied the same code and I couldn’t build VST3, but I could build Standalone and AudioUnit.
  • I took a dive into juce_AudioTransportSource.cpp and found that if I comment out line 95:
    masterSource = newMasterSource; then the VST will at least finish the build.
  • likewise, if I switch the raw pointer instead of std::unique_ptr the VST will agree to build.

So both of my solution are not to make the plugin work, but just build the VST target.

I will read your post more thoroughly and see if I can connect some dots.

Just to help you connecting the dots a bit better, those two things are maybe a lot more related than you assume :wink:

The build process is actually a sequence of single steps executed one after another. It’s roughly like this for a VST3

  1. Compile all format unspecific code (e.g. all the code that you have written)
  2. Compile the code only needed for VST3 (completely written by JUCE)
  3. Link together the parts of 1 and 2 to create a plugin binary that can be loaded by a plugin host application
  4. Load the plugin by a tiny host application which inspects some metadata of the plugin and writes the result of that inspection to the moduleinfo.json file
  5. Copy the generated json file into the VST3 bundle

Steps 4 and 5 are something specific to VST3. What is important here is, that step 4, which is the juce_vst3_helper target actually runs the plugin. This again means if your code contains a programming error which can make the plugin crash under certain conditions this can make this step fail. So while it seems that the compilation failed, your plugin might have built fine in a traditional sense, the problem is that the plugin actually does not work after the compilation finished. Running something that has just been built during the build process to extract data that is then again added to the artefact during build is a big unusual in general, so it’s understandable that it might be a bit confusing. In the end, the crash is caused by a slight misuse of the AudioTransportSource class, so it’s not surprising that all solutions that you found had to do with disabling or working around parts of that code, but there is no inherent bug in AudioTransportSource that generally causes builds to fail. Does that help you understanding better what has happened here and why the solution is indeed to write correct code to make the build process succeed?

Usually you’d track down crashes by running the plugin with a debugger attached, which will likely lead you to the point where your code crashes. This is why @reuk suggested copying the VST3 in the state as it has been generated after step 3 to the install location, launch a host under the debugger and search for the problem using normal debugging techniques.

1 Like

Thanks for the detailed info! I wasn’t aware of all these steps. In which school do they actually teach that?

Problem was solved by moving the std::unique_ptr to the header file with proper initialization order as you suggested.

Glad I could help and glad you got it sorted!

I haven’t been to any such school, not sure if it exists, I guess for me the answer is working as a professional C++ developer for some years, using JUCE on a daily basis, reading the JUCE forum and constantly trying to find out what goes on behind the scenes of the code teaches you stuff like that :smiley: So better don’t worry if something like that is not immediately clear to you, it’s simply so much stuff to find out, I guess I’m learning new stuff all the time myself :man_shrugging:

1 Like