VST3 factory programs are missing


#1

I've got a plugin that I'm building as a VST2 and VST3. I've defined a list of factory programs that are exposed using AudioProcessor functions such as AudioProcessor::getNumPrograms(), etc.. When I run the plugin, the VST2 version shows the factory programs, and everything looks good. But when I run the plugin as a VST3, the factory programs do not appear at all.

After a bit of digging, I see that the VST3 SDK defines a virtual class called IUnitInfo which defines the interface used to get program information. In juce_VST3_Wrapper.cpp, I see that JUCE defines JuceVST3Component, which derives from IUnitInfo, but those functions never get called on JuceVST3Component.

I modified the JuceVST3EditController to also derive from IUnitInfo, adjusted the queryInterface accordingly, and now that class is getting calls from the host to get at program information. So now I'm attempting to forward IUnitInfo calls from JuceVST3EditController to the associated JuceVST3Component.

So is this a bug in the JUCE VST3 wrapper? Or have I missed something about how I'm supposed to expose factory programs in the VST3 format?

 

 


#2

So I finished up the task of passing program info calls from the JuceVST3EditController to the JuceVST3Component. All my factory-defined programs are showing up in the VST3 plug-in now. If anyone knows of a better way to make this work (that doesn't require Juce souce code modifications, for example), I'd be happpy to know it.

 


#3

The other way to do it is to create .vstpreset files and install them in the corresponding directory when you install the plugin files...

It would be great if you could share your wrapper code changes so that the Juce team can integrate them in their code.


#4

Here is some code that works (but not 100% tested) :


diff --git a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp
index 247f2b3..48487ea 100644
--- a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp
+++ b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp
@@ -98,6 +98,7 @@ private:
 //==============================================================================
 class JuceVST3EditController : public Vst::EditController,
                                public Vst::IMidiMapping,
+                               public Vst::IUnitInfo,
                                public AudioProcessorListener
 {
 public:
@@ -130,6 +131,7 @@ public:
         TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IEditController2)
         TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IConnectionPoint)
         TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IMidiMapping)
+        TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IUnitInfo)
         TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (targetIID, IPluginBase, Vst::IEditController)
         TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (targetIID, IDependent, Vst::IEditController)
         TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (targetIID, FUnknown, Vst::IEditController)
@@ -146,6 +148,69 @@ public:
     }
 
     //==============================================================================
+    Steinberg::int32 PLUGIN_API getUnitCount () override { return 1; }
+    tresult PLUGIN_API getUnitInfo (Steinberg::int32 unitIndex, Vst::UnitInfo& info /*out*/) override
+    {
+        if (unitIndex == 0)
+        {
+            info.id             = Vst::kRootUnitId;
+            info.parentUnitId   = Vst::kNoParentUnitId;
+            info.programListId  = 'prst';
+            
+            toString128 (info.name, TRANS("Root Unit"));
+            
+            return kResultTrue;
+        }
+        
+        zerostruct (info);
+        return kResultFalse;
+    }
+    Steinberg::int32 PLUGIN_API getProgramListCount () override
+    {
+        AudioProcessor* const pluginInstance = getPluginInstance();
+        if (pluginInstance && pluginInstance->getNumPrograms() > 0)
+            return 1;
+        else
+            return 0;
+    }
+    tresult PLUGIN_API getProgramListInfo (Steinberg::int32 listIndex, Vst::ProgramListInfo& info /*out*/) override
+    {
+        if (listIndex == 0)
+        {
+            info.id = 'prst';
+            info.programCount = (Steinberg::int32) getPluginInstance()->getNumPrograms();
+            
+            toString128 (info.name, TRANS("Factory Presets"));
+            
+            return kResultTrue;
+        }
+        
+        jassertfalse;
+        zerostruct (info);
+        return kResultFalse;
+    }
+    tresult PLUGIN_API getProgramName (Vst::ProgramListID listId, Steinberg::int32 programIndex, Vst::String128 name /*out*/) override
+    {
+        if (listId == 'prst'
+            && isPositiveAndBelow ((int) programIndex, getPluginInstance()->getNumPrograms()))
+        {
+            toString128 (name, getPluginInstance()->getProgramName ((int) programIndex));
+            return kResultTrue;
+        }
+        
+        jassertfalse;
+        toString128 (name, juce::String());
+        return kResultFalse;
+    }
+    tresult PLUGIN_API getProgramInfo (Vst::ProgramListID listId, Steinberg::int32 programIndex, Vst::CString attributeId /*in*/, Vst::String128 attributeValue /*out*/) override { return kNotImplemented; }
+    tresult PLUGIN_API hasProgramPitchNames (Vst::ProgramListID listId, Steinberg::int32 programIndex) override { return kNotImplemented; }
+    tresult PLUGIN_API getProgramPitchName (Vst::ProgramListID listId, Steinberg::int32 programIndex, Steinberg::int16 midiPitch, Vst::String128 name /*out*/) override { return kNotImplemented; }
+    Vst::UnitID PLUGIN_API getSelectedUnit () override { return kNotImplemented; }
+    tresult PLUGIN_API selectUnit (Vst::UnitID unitId) override { return kNotImplemented; }
+    tresult PLUGIN_API getUnitByBus (Vst::MediaType type, Vst::BusDirection dir, Steinberg::int32 busIndex, Steinberg::int32 channel, Vst::UnitID& unitId /*out*/) override { return kNotImplemented; }
+    tresult PLUGIN_API setUnitProgramData (Steinberg::int32 listOrUnitId, Steinberg::int32 programIndex, IBStream* data) override { return kNotImplemented; }
+    
+    //==============================================================================
     tresult PLUGIN_API initialize (FUnknown* context) override
     {
         if (hostContext != context)
@@ -242,6 +307,43 @@ public:
     };
 
     //==============================================================================
+    struct PresetParam  : public Vst::StringListParameter
+    {
+        PresetParam (AudioProcessor& p, int index)
+        : Vst::StringListParameter(::toString(String("Factory Presets")), index, 0, Vst::ParameterInfo::kCanAutomate|Vst::ParameterInfo::kIsList|Vst::ParameterInfo::kIsProgramChange, Vst::kRootUnitId)
+        , owner (p)
+        {
+            for (int i = 0; i < owner.getNumPrograms(); ++i)
+            {
+                Vst::String128 str;
+                toString128(str, owner.getProgramName(i));
+                this->appendString(str);
+            }
+        }
+        
+        virtual ~PresetParam() {}
+        
+        bool setNormalized (Vst::ParamValue v) override
+        {
+            v = jlimit (0.0, 1.0, v);
+            
+            if (v != valueNormalized)
+            {
+                valueNormalized = v;
+                changed();
+                owner.setCurrentProgram(v * (owner.getNumPrograms() - 1));
+                return true;
+            }
+            
+            return false;
+        }
+    private:
+        AudioProcessor& owner;
+        
+        JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PresetParam)
+    };
+    
+    //==============================================================================
     struct BypassParam  : public Vst::Parameter
     {
         BypassParam (AudioProcessor& p, int index)  : owner (p), paramIndex (index)
@@ -474,6 +576,9 @@ private:
                     parameters.addParameter (new Param (*pluginInstance, i));
 
                 parameters.addParameter (new BypassParam (*pluginInstance, numParameters));
+                
+                if (pluginInstance->getNumPrograms() > 0)
+                    parameters.addParameter (new PresetParam (*pluginInstance, numParameters + 1));
             }
 
             // We need to account for the bypass parameter in the numParameters passed to
@@ -741,7 +846,6 @@ private:
 //==============================================================================
 class JuceVST3Component : public Vst::IComponent,
                           public Vst::IAudioProcessor,
-                          public Vst::IUnitInfo,
                           public Vst::IConnectionPoint,
                           public AudioPlayHead
 {
@@ -795,7 +899,6 @@ public:
         TEST_FOR_AND_RETURN_IF_VALID (targetIID, JuceVST3Component)
         TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IComponent)
         TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IAudioProcessor)
-        TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IUnitInfo)
         TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IConnectionPoint)
         TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (targetIID, FUnknown, Vst::IComponent)
 
@@ -1249,83 +1352,6 @@ public:
     }
 
     //==============================================================================
-    Steinberg::int32 PLUGIN_API getUnitCount() override
-    {
-        return 1;
-    }
-
-    tresult PLUGIN_API getUnitInfo (Steinberg::int32 unitIndex, Vst::UnitInfo& info) override
-    {
-        if (unitIndex == 0)
-        {
-            info.id             = Vst::kRootUnitId;
-            info.parentUnitId   = Vst::kNoParentUnitId;
-            info.programListId  = Vst::kNoProgramListId;
-
-            toString128 (info.name, TRANS("Root Unit"));
-
-            return kResultTrue;
-        }
-
-        zerostruct (info);
-        return kResultFalse;
-    }
-
-    Steinberg::int32 PLUGIN_API getProgramListCount() override
-    {
-        if (getPluginInstance().getNumPrograms() > 0)
-            return 1;
-
-        return 0;
-    }
-
-    tresult PLUGIN_API getProgramListInfo (Steinberg::int32 listIndex, Vst::ProgramListInfo& info) override
-    {
-        if (listIndex == 0)
-        {
-            info.id = paramPreset;
-            info.programCount = (Steinberg::int32) getPluginInstance().getNumPrograms();
-
-            toString128 (info.name, TRANS("Factory Presets"));
-
-            return kResultTrue;
-        }
-
-        jassertfalse;
-        zerostruct (info);
-        return kResultFalse;
-    }
-
-    tresult PLUGIN_API getProgramName (Vst::ProgramListID listId, Steinberg::int32 programIndex, Vst::String128 name) override
-    {
-        if (listId == paramPreset
-            && isPositiveAndBelow ((int) programIndex, getPluginInstance().getNumPrograms()))
-        {
-            toString128 (name, getPluginInstance().getProgramName ((int) programIndex));
-            return kResultTrue;
-        }
-
-        jassertfalse;
-        toString128 (name, juce::String());
-        return kResultFalse;
-    }
-
-    tresult PLUGIN_API getProgramInfo (Vst::ProgramListID, Steinberg::int32, Vst::CString, Vst::String128) override             { return kNotImplemented; }
-    tresult PLUGIN_API hasProgramPitchNames (Vst::ProgramListID, Steinberg::int32) override                                     { return kNotImplemented; }
-    tresult PLUGIN_API getProgramPitchName (Vst::ProgramListID, Steinberg::int32, Steinberg::int16, Vst::String128) override    { return kNotImplemented; }
-    tresult PLUGIN_API selectUnit (Vst::UnitID) override                                                                        { return kNotImplemented; }
-    tresult PLUGIN_API setUnitProgramData (Steinberg::int32, Steinberg::int32, IBStream*) override                              { return kNotImplemented; }
-    Vst::UnitID PLUGIN_API getSelectedUnit() override                                                                           { return Vst::kRootUnitId; }
-
-    tresult PLUGIN_API getUnitByBus (Vst::MediaType, Vst::BusDirection,
-                                     Steinberg::int32, Steinberg::int32,
-                                     Vst::UnitID& unitId) override
-    {
-        zerostruct (unitId);
-        return kNotImplemented;
-    }
-
-    //==============================================================================
     bool getCurrentPosition (CurrentPositionInfo& info) override
     {
         info.timeInSamples              = jmax ((juce::int64) 0, processContext.projectTimeSamples);


#5

Hi _Manu_, 

I'm currently working on the VST3 code and will look at your issue. I'll get back to you once I've worked in a fix.

Fabian


#6

Quick update: I'm currently working on VST3 factory preset support. It still has some rough edges and needs some testing. I should have something for you to test by the end of next week.


#7

OK, thanks for the feedback.


#8

Hi,

i was wondering this (if my current issue) is related:

'getProgramName()' seems to be ignored ... whatever it returns, all programs are shown as "unnamed" when i load my (VST3) plugin to the plugin host (using the JUCE audio plugin host to debug). I can see a list of programs, so the number seems to be considered by the host, but not the names ...

Furthermore, from the "Show all programs" menu, programs aren't selectable at all. Is it intended to be that way ?


#9

No updates on this issue ?

 

Checked out the latest version of JUCE, and seemingly, this problem remains ...


#10

Didn't quite make it in, but we know! Will do it very soon.


#11

It's been 2.5 months since "will do it very soon" - can ROLI provide an update on the status of VST3 Factory Presets?  I don't want to roll my own solution if you're going to push one next week, but also can't wait indefinitely for an official fix.

Is it just a matter of testing/validating _Manu_'s proposed fix or did you find problems with that approach?

Thank you


#12

Hi there,

Yep, VST3 Factory presets is still in our backlog, we haven't forgotten about it!

Unfortunately the main problem right now is that in the JUCE team we are severely understaffed (and are hiring additional developers!) so there is a huge pile of things that need doing.

Therefore most likely it won't be happening within the next couple weeks.


#13

Does anyone have VST3 Factory Preset support working in Studio One? I’ve successfully implemented it starting from _Manu’s proposed code, and it works well in Cubase, Nuendo, and Reaper, but NOT in Studio One. AU and VST2 Factory Presets work great in Studio One, but VST3 shows nothing.

Would greatly appreciate tips from anyone who has it working (or even just confirmation that someone did get it working.

ROLI, any word on an official implementation?


#14

This still seems to be an issue.


#15

I contacted Presonus directly and was told by their CTO that StudioOne does NOT support the VST3 program list mechanism and is not going to. They only support VST3 presets via external files stored on disk.


#16

Any updates?

It seems most of the code by _Manu is already in the VST3 wrapper. What is missing to make this work, finally?