How to Identify Program Change Parameters in Hosted VST3 Plugins

Hi,

I’m working on a VST plugin that hosts VST3 plugins, and I’m looking into how I can trigger MIDI program changes in the hosted VST3 plugins. I understand the limitations around MIDI program changes in VST3s and that these plugins typically expose a parameter representing the program change. My current approach involves setting the value of this parameter to send a program change.

From my research, I understand that VST3s supporting program changes should set a “program change” flag (Vst::ParameterInfo::kIsProgramChange) on the corresponding parameter. My question is: How can I programmatically identify which parameter in my hosted VST3 plugin has this program change flag?

Here’s what I’ve done so far:

  1. I’ve managed to get program changes working for some plugins by manually inspecting parameter names (e.g., “Program Change” or similar). This works, but the naming varies between plugins, so it’s not a scalable solution.
  2. I’ve seen references to Vst::ParameterInfo::kIsProgramChange in the VST3 SDK documentation, but I’m unsure how to access this information within JUCE’s AudioPluginInstance or related classes.

Ideally, I’d like to implement a general solution that detects this flag dynamically for any supported VST3 plugin. Does anyone know if there’s a way to:

  • Access the parameter flags of a hosted VST3 plugin within JUCE, or
  • Extend JUCE’s VST3 support to expose these flags?

Any advice, examples, or guidance on how to approach this would be greatly appreciated. Thank you!

There’s no way to determine which parameter is the program parameter, but JUCE automatically exposes VST3 program functionality via AudioProcessor::setCurrentProgram and friends.

Thanks reuk!

I understand that this function allows me to set the current program for a plugin, but I’m hosting VST3 plugins that may require MIDI channel-specific program changes. Using setCurrentProgram seems to only apply the program change on MIDI channel 1. Is there a way to use setCurrentProgram (or any other JUCE API) to specify the MIDI channel for the program change?

If setCurrentProgram doesn’t support channel-specific changes, is there a recommended way to implement this functionality? For example, should I be looking into sending MIDI program change messages to the plugin directly via the MidiBuffer, or is there a mechanism in JUCE to identify and set program changes for a particular channel through parameters?

I’ve encountered VST3 plugins where program changes seem tied to specific parameters rather than direct MIDI events, so any guidance on handling such scenarios programmatically would also be greatly appreciated. Specifically, how would you recommend handling plugins with channel-specific program change requirements?

Thank you for your help!

Unfortunately this use-case is not currently covered by JUCE. If we were to add it, it would become part of the MIDI->VST3-event conversion, so you’d send MIDI messages as normal to the plugin.

Internally, JUCE’s hosting code would call the plugin’s IUnitInfo::getUnitByBus to find whether the plugin has a Unit associated with a particular MIDI channel, and then attempt to locate the program change parameter associated with that Unit. If a program change parameter is found, it can be sent a change request. Otherwise, we’d ignore the MIDI program-change message.

If adding this functionality allows VST3s built with JUCE to react to program change messages like VST2s do, I’m definitely in favor of it! This would simplify handling program changes significantly.

In the meantime, I’m working on a solution to trigger program changes on different MIDI channels in my VST3 host. I’ve been exploring how JUCE handles program changes, and I came across the syncProgramNames() function in juce_VST3PluginFormat.cpp. Here’s the relevant snippet I found:

programNames.clear();

if (processor == nullptr || editController == nullptr)
    return;

Vst::UnitID programUnitID;
Vst::ParameterInfo paramInfo{};

{
    int idx, num = editController->getParameterCount();

    for (idx = 0; idx < num; ++idx)
        if (editController->getParameterInfo(idx, paramInfo) == kResultOk
             && (paramInfo.flags & Vst::ParameterInfo::kIsProgramChange) != 0)
            break;

    if (idx >= num)
        return;

    programParameterID = paramInfo.id;
    programUnitID = paramInfo.unitId;
}

The loop exits after finding the first program change parameter. To handle program changes for multiple MIDI channels, I’m trying to adapt this to retrieve all program change parameters. Here’s what I’ve implemented so far:

  1. Created a Visitor struct to get the IComponent:
struct Visitor : public ExtensionsVisitor
{
    void visitVST3Client(const VST3Client& c) override { iComponent = c.getIComponentPtr(); }
    Steinberg::Vst::IComponent* iComponent = nullptr;
};
  1. Used it to query the IEditController and find program change parameters:
Visitor visitor;
inner->getExtensions(visitor);

if (visitor.iComponent != nullptr)
{
    Steinberg::Vst::IEditController* editController = nullptr;

    // Query the IEditController from the IComponent
    if (auto result = visitor.iComponent->queryInterface(Steinberg::Vst::IEditController::iid, (void**)&editController);
        result == Steinberg::kResultOk)
    {
        Steinberg::Vst::ParameterInfo paramInfo{};
        int numParams = editController->getParameterCount();

        for (int idx = 0; idx < numParams; ++idx)
        {
            if (editController->getParameterInfo(idx, paramInfo) == Steinberg::kResultOk &&
                (paramInfo.flags & Steinberg::Vst::ParameterInfo::kIsProgramChange) != 0)
            {
                auto programParameterID = paramInfo.id;
                auto programUnitID = paramInfo.unitId;

                DBG("Found program change parameter: ID = " << programParameterID
                    << ", UnitID = " << programUnitID);
            }
        }
    }
    else
    {
        DBG("EditController not found!");
    }
}

However, the editController is not being found. I suspect there’s a better way to ensure the IEditController is available or a step I might be missing. Could this be related to how JUCE initializes or accesses VST3 components?

Is there a more robust approach to loop through the parameters of a hosted VST3 plugin and identify those with the kIsProgramChange flag?

Hi @reuk,

I hope you don’t mind me tagging you directly. I’m following up on my earlier post regarding handling program changes in a VST3 host. I’m trying to identify parameters with the kIsProgramChange flag so I can support triggering program changes on different MIDI channels.

To summarize, I’ve been exploring the syncProgramNames() function in juce_VST3PluginFormat.cpp, which uses the IEditController::getParameterInfo() function to find the first program change parameter. My goal is to adapt this so I can retrieve all program change parameters instead of stopping at the first one.

I’ve attempted to query the IEditController from the IComponent as follows:

if (auto result = visitor.iComponent->queryInterface(Steinberg::Vst::IEditController::iid, (void**)&editController);
    result == Steinberg::kResultOk)
{
    // Process editController to find parameters
}
else
{
    DBG("EditController not found!");
}

Unfortunately, this always fails, and the queryInterface() call returns kNoInterface. I’ve double-checked that the hosted plugin should have an IEditController, as JUCE itself accesses it in the same way when setting the current program.

Could you provide any guidance on:

  1. Why queryInterface() might be failing here when IEditController is expected to be available?
  2. Whether there’s an alternative way to enumerate parameters and check for the kIsProgramChange flag in a hosted VST3?

I’m also open to other approaches for achieving MIDI channel-specific program changes if this path isn’t viable.

Thanks in advance for your time and insights!

Bump!! @reuk can you please advise?

Could JUCE provide a way for us to access IEditController?