Hosting: juce::AudioPluginInstance->Process() VST3 Midi not working correctly

Hi Guys,

I’m banging my head against the wall here, nearly all VST3s I have tried are having difficulty with midi messages that are not notes.

It is pretty easy to test, load up AudioPluginHost, add a VST3 plugin and attach up midi. Try to send some CCs and midi map in the plugin. I have found one that works correctly and that is Diva, others have different problems, for example the Arturia ones get no messages, Spectrasonics always gets a CC7. It seems quite broken.

If I find the relevant automation parameter and use that via SetParameter() everything works fine, the plugins “see” the “midi messages”.

So associateWith (data, midiMessages); doesn’t seem to be working correctly for all plugins, I have debugged into it and the IParameterChanges calls via the ParameterChanges object seem to be working fine.

Has anyone got any ideas why using SetParameter() with the correct midi CC parameter works, while the associate stuff in processAudio doesn’t work? I have even for a quick test commented out the inputParameterChanges->set (cachedParamValues.getParamID (index), value); call to stop a queue entry being created and ‘SetParameter()’ still works.

I don’t know much about VST3 but I thought all param changes were meant to go through ‘IParameterChanges’ but this doesn’t seem to be the case here!

Any pointers would be really appreciated!

So making sure the parameter is updated from the editController fixes the issue I am seeing with VST3 plugins, obviously this code isn’t oven ready but why is the normal juce code not doing something along these lines?

        associateWith (data, buffer);
        associateWith (data, midiMessages);
      
        if (editController != nullptr)
        {
            uint32_t uParamCount = inputParameterChanges->getParameterCount();
            while(uParamCount--)
            {
                auto p = inputParameterChanges->getParameterData(uParamCount);
                editController->setParamNormalized(p->getParameterId(), p->get());
            }
        }

Another issue is that updateMidiMappings() is not guaranteed to be called as it is dependent on getting a restart with Vst::kMidiCCAssignmentChanged set in the flags.

Changing setComponentStateAndResetParameters() to call it fixes this issue:

    void setComponentStateAndResetParameters (Steinberg::MemoryStream& stream)
    {
        jassert (editController != nullptr);

        warnOnFailureIfImplemented (editController->setComponentState (&stream));
        resetParameters();
        updateMidiMappings();
    }

The host needs to notify the plugin twice about each parameter change, once to the editor and once to the processor. The inputParameterChanges are used to send changed parameter values to the processor, and IEditController::setParamNormalized is used to update the editor.

It looks like the JUCE implementation is failing to send updated parameter values to the editor, although it is sending them correctly to the processor. I’ll look at updating the JUCE implementation to do this properly.

Even with this change in place, it’s worth noting that very few plugins seem to implement IMidiMapping in the way that Steinberg apparently intended. I think the intent is that whenever a MIDI mapping is changed, the plugin requests a restart and lets the host know that the MIDI mappings need rescanning. The host then rescans the mappings, and updates parameters appropriately when their mapped CC values change. e.g. if a CC is mapped to ‘filter cutoff’, sending a new CC should change the filter cutoff parameter for the plugin and the host. The host may also choose not to send parameter changes, e.g. if they would conflict with recorded automation.

What a lot of plugins actually appear to do (I tested Arturia Pigments/Jun-6V, U-He Diva, BlueCat Patchwork, and NI Kontakt) is to create a parameter for each CC number, and then to forward parameter changes from these parameters to any mapped parameters. The main downside of this approach is that when the controller is moved, the host only knows to update the “controller 74” parameter (or whatever), but doesn’t know that the filter cutoff parameter will also change. As a result, the plugin will process as though the filter cutoff has changed, and the UI will update to reflect the new cutoff, but the host’s “filter cutoff” parameter won’t be updated. The host and the client will then be out of sync.

Edit: it may be possible for plugins to improve on the above approach by adding any changed parameters to the outputParameterChanges list during processing. That way, if moving the hidden “CC 74” parameter also causes “Filter Cutoff” to change, the plugin can let the host know about the new “Filter Cutoff” value, and the host can update its own sliders. None of the plugins I tested do this, although BlueCat Patchwork does send an update back to the host via HostContext::performEdit. This lets the host know to update its view of the mapped parameter to reflect the plugin’s state, but recorded CCs may be a bit inaccurate because they’ll be communicated asynchronously on the main thread, rather than synchronously on the audio thread.

Hi Reuk,

Thanks for the reply.

I think the developers do it like this because of wanting the ability to midi map from incoming midi messages, I’m guessing the Steinberg approach wouldn’t allow this unless you pre-map every midi message.

I’m not sure quite why Steinberg seemed to want to get rid of midi messages in VST3, must have made sense when they came up with it!

Thanks for reporting this issue. I’ve pushed a fix to the juce7 branch here:

Please test it out and let me know if it resolves the problem for you, or if you discover any new issues.

Thanks Reuk, I will have a look on Monday and get back to you…