prepareToPlay() runs before MainComponent constructor?

Trying to understand the JUCE lifecycle.

I have an audio source declared as:

class Player : public juce::AudioSource

Then, in MainComponent.h:

std::vector<std::unique_ptr<Player>> players;

And in MainComponent.cpp:

    for(int i=0; i < max_players; ++i)
    {
        players.emplace_back(std::make_unique<Player>(format_manager));
    }

Compiles fine. The issue is that MainComponent::prepareToPlay() runs before MainComponent::MainComponent() has had the opportunity to set things up.

What’s going on? Why doesn’t prepareToPlay() wait for the constructor to run?

Assuming your code is using the Juce AudioAppComponent, the setAudioChannels call starts the playback. Do you have that in the constructor before you have created your player objects?

It appears that something in the boilerplate generated by Projucer causes prepareToPlay() to run. I placed debug messages before and after the boilerplate. The one before runs. Then prepareToPlay() runs. So, yes, the constructor is running before prepareToPlay(), as expected.

It appears that I have to initialize my audio components before this boilerplate. It’d be interesting to understand which part of it causes this issue. Here it is:

    // Some platforms require permissions to open input channels so request that here
    if (juce::RuntimePermissions::isRequired (juce::RuntimePermissions::recordAudio)
        && ! juce::RuntimePermissions::isGranted (juce::RuntimePermissions::recordAudio))
    {
        juce::RuntimePermissions::request (juce::RuntimePermissions::recordAudio,
                                           [&] (bool granted) { setAudioChannels (granted ? 2 : 0, 2); });
    }
    else
    {
        setAudioChannels (0, 2);
    }

I’ll continue to play with this and see if moving everything above that code makes the application work.

I am not familiar enough with JUCE yet to understand just how much of this boilerplate I actually need.

prepareToPlay can even run again in the middle of an instance’ lifetime. for example if the sampleRate changes. so you have to design your gui to react to those changes

Yes, I’ve been moving code around trying to understand. I think I am starting to get the idea. That bit of Projucer code that somehow triggers prepareToPlay() confused me. I am just accepting it for what it is and doing all my initialization before it.

Thanks.

The general point is that in a DAW host, your MainComponent may never be created. If the user loads a project containing your plugin, but the user never clicks to open the UI, the host will probably just not call createEditor().

So you need to write your plugin in such a way that it runs the same without an editor as it does with one.

In this case it’s an app.

Like Xenakios pointed out, the setAudioChannels will open the audio device, add the device callback and run prepareToPlay as part of the constructor.

Every C++ object construction runs as follows:

  • construct base class
  • construct the members in order of appearance
  • run the constructor body

If you want to make sure that everything even asynchronous stuff has finished, you can delay the starting of the audio:

juce::Component::SafePointer<MainComponent> safeThis (this);
juce::MessageManager::callAsync([safeThis]
{
    if (!safeThis)
        return;

    // Some platforms require permissions to open input channels so request that here
    if (juce::RuntimePermissions::isRequired (juce::RuntimePermissions::recordAudio)
        && ! juce::RuntimePermissions::isGranted (juce::RuntimePermissions::recordAudio))
    {
        juce::RuntimePermissions::request (juce::RuntimePermissions::recordAudio,
                                           [&] (bool granted) { safeThis->setAudioChannels (granted ? 2 : 0, 2); });
    }
    else
    {
        safeThis->setAudioChannels (0, 2);
    }
});

But that shouldn’t be necessary