Second Loaded Plugin Not Responding to MIDI Messages {SOLVED}

I have a plugin that hosts exactly one other plugin instance (Vital, just for testing). When the plugin starts it loads an instance of Vital by default. When I pass MIDI messages to this default instance, it processes them correctly and I receive audio.

Recently, I’ve added functionality to load other arbitrary plugins. However, when I load a new plugin instance (replacing the default Vital instance with a new one), it does not respond to MIDI messages (no audio received, and the visuals of the Vital plugin make it obvious it’s not receiving/processing messages).

Here’s my (debugging riddled) function for loading plugins. Both the initial plugin load and later plugin load use this function:

void MyAudioProcessor::loadPlugin(juce::String path)
{
    juce::AudioPluginFormatManager& fm = *new juce::AudioPluginFormatManager();
    fm.addDefaultFormats();

    juce::OwnedArray<juce::PluginDescription>& typesFound = *new juce::OwnedArray<juce::PluginDescription>();

    juce::Array<juce::AudioPluginFormat*> formats = fm.getFormats();
    sptr<juce::AudioPluginFormat> format(nullptr);
    for (int i = 0; i < formats.size(); i++) {
        auto f = formats[i];
        juce::String name = f->getName();
        if (name.equalsIgnoreCase("VST3")) {
            format = sptr<juce::AudioPluginFormat>(f);
            break;
        }
    }

    pluginList.scanAndAddFile(
        path,
        true,
        typesFound,
        *format.get()
    );

    auto desc = typesFound[0];

    juce::String& errorMessage = *new juce::String();
    std::unique_ptr<juce::AudioPluginInstance> instance = formatManager.createPluginInstance(
        *desc,
        SAMPLE_RATE,
        HOSTED_PLUGIN_BLOCK_SIZE,
        errorMessage
    );
    hostedPlugin = std::move(instance);
    hostedPlugin->enableAllBuses();
}

My processBlock is just

hostedPlugin->processBlock(buffer, midiMessages);

Any ideas as to what’s causing this?

After messing around a little I tried calling prepareToPlay on the new plugin instances. This causes the new plugin to accept MIDI and produce audio… but the audio isn’t pitched correctly. It’s off by somewhere between 1-2 semitones.

Updated code:

hostedPlugin = std::move(instance);
hostedPlugin->enableAllBuses();
hostedPlugin->prepareToPlay(SAMPLE_RATE, HOSTED_PLUGIN_BLOCK_SIZE);

This could be totally wrong. However, I host plugins and I took a quick look at my code (been awhile since I was working on that part of the project), but after I replace a plugin, I call AudioProcessor::setRateAndBufferSizeDetails before prepareToPlay. You would think that would be kind of redundant to prepareToPlay but I can only assume it was necessary. Or maybe not…

https://docs.juce.com/master/classAudioProcessor.html#a562ba6fd88562fd42eddee5b95da489d

I dug through the AudioPluginHost example project and also saw setRateAndBufferSizeDetails as part of their initialization process (in addition to setPlayHead). But adding either or these really didn’t seem to make much of a difference. Thanks for the input though!

TLDR; I was able to get the correct pitch by changing references to my SAMPLE_RATE constant to getSampleRate().

Aha! Figured out what was going on.

It all stems from my use of a sample rate constant (44.1k) that did not match the ‘host’ or ‘plugin’ default rate.

The first plugin was loaded in the constructor of the audio processor. It’s created, just like the second instance, using a SAMPLE_RATE constant. However, for some reason when the plugin is loaded in the constructor it seems to not care what sample rate you give it, and somehow uses the ‘right’ one anyway.

During the second load the plugin load actually took into account my constant, which makes the pitch of the audio slightly off.

I’m guessing that JUCE at some point calls my plugin’s prepareToPlay after my audio processor is constructed, and my prepareToPlay calls prepareToPlay on the hosted plugin instances with the same (and correct) arguments. However, after the second plugin load JUCE doesn’t automatically call my prepareToPlay so the incorrect arguments I pass in the load plugin function never get overwritten.