Question about VST3Interface::vst2PluginId

Hi,

the vst2PluginId has windows specific byte-swapping enclosed between #if JUCE_WINDOWS.
I wonder if it is correct, are VST3 interface ids supposed to be platform dependent ?

I say that I’m using this function to define JUCE_VST3_COMPATIBLE_CLASSES, and the “Compatibility” entries in my generated moduleinfo.json look like this on Windows:

"Compatibility": [
    {
      "New": "56535450...,
      "Old": [
        "50545356...",
        "50545356...",
      ],
    },
  ],

instead of (on macOS):

"Compatibility": [
    {
      "New": "56535450...,
      "Old": [
        "56535450...",
        "56535450...",
      ],
    },
  ],

As far as I can tell it comes down to COM_COMPATIBLE being defined only for Windows in the VST3 SDK.

The ultimate test would be to try commenting out the byte swapping and seeing if your VST3 plugin still correctly replaces your VST2 plugin on all VST3 hosts. I suspect it doesn’t?

This is the code that was replaced

    // NB: Nasty old-fashioned code in here because it's copied from the Steinberg example code.
    static void getUUIDForVST2ID (bool forControllerUID, uint8 uuid[16])
    {
        #if JUCE_WINDOWS
         const auto juce_sprintf = [] (auto&& head, auto&&... tail) { sprintf_s (head, (size_t) numElementsInArray (head), tail...); };
         const auto juce_strcpy  = [] (auto&& head, auto&&... tail) { strcpy_s  (head, (size_t) numElementsInArray (head), tail...); };
         const auto juce_strcat  = [] (auto&& head, auto&&... tail) { strcat_s  (head, (size_t) numElementsInArray (head), tail...); };
         const auto juce_sscanf  = [] (auto&&... args)              { sscanf_s  (args...); };
        #else
         const auto juce_sprintf = [] (auto&& head, auto&&... tail) { snprintf  (head, (size_t) numElementsInArray (head), tail...); };
         const auto juce_strcpy  = [] (auto&&... args)              { strcpy    (args...); };
         const auto juce_strcat  = [] (auto&&... args)              { strcat    (args...); };
         const auto juce_sscanf  = [] (auto&&... args)              { sscanf    (args...); };
        #endif

        char uidString[33];

        const int vstfxid = (('V' << 16) | ('S' << 8) | (forControllerUID ? 'E' : 'T'));
        char vstfxidStr[7] = { 0 };
        juce_sprintf (vstfxidStr, "%06X", vstfxid);

        juce_strcpy (uidString, vstfxidStr);

        char uidStr[9] = { 0 };
        juce_sprintf (uidStr, "%08X", JucePlugin_VSTUniqueID);
        juce_strcat (uidString, uidStr);

        char nameidStr[3] = { 0 };
        const size_t len = strlen (JucePlugin_Name);

        for (size_t i = 0; i <= 8; ++i)
        {
            juce::uint8 c = i < len ? static_cast<juce::uint8> (JucePlugin_Name[i]) : 0;

            if (c >= 'A' && c <= 'Z')
                c += 'a' - 'A';

            juce_sprintf (nameidStr, "%02X", c);
            juce_strcat (uidString, nameidStr);
        }

        unsigned long p0;
        unsigned int p1, p2;
        unsigned int p3[8];

        juce_sscanf (uidString, "%08lX%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X",
                     &p0, &p1, &p2, &p3[0], &p3[1], &p3[2], &p3[3], &p3[4], &p3[5], &p3[6], &p3[7]);

        union q0_u {
            uint32 word;
            uint8 bytes[4];
        } q0;

        union q1_u {
            uint16 half;
            uint8 bytes[2];
        } q1, q2;

        q0.word = static_cast<uint32> (p0);
        q1.half = static_cast<uint16> (p1);
        q2.half = static_cast<uint16> (p2);

        // VST3 doesn't use COM compatible UUIDs on non windows platforms
       #if ! JUCE_WINDOWS
        q0.word = ByteOrder::swap (q0.word);
        q1.half = ByteOrder::swap (q1.half);
        q2.half = ByteOrder::swap (q2.half);
       #endif

        for (int i = 0; i < 4; ++i)
            uuid[i+0] = q0.bytes[i];

        for (int i = 0; i < 2; ++i)
            uuid[i+4] = q1.bytes[i];

        for (int i = 0; i < 2; ++i)
            uuid[i+6] = q2.bytes[i];

        for (int i = 0; i < 8; ++i)
            uuid[i+8] = static_cast<uint8> (p3[i]);
    }

Note as it says this code was basically lifted from the VST3 example, the last time I checked this I recall it being very similar, however looking at the latest documentation it appears like it’s changed.

I also found a related post on the forum that might add a little more information.

One thought is that maybe both the byte swapped and non-byte swapped variations could be stored in the list of old ID’s?

Ultimately I strongly suspect not swapping the bytes would result in issues with replacing VST2 instances on Windows.

I guess even if it turns out to be that we could have kept it the same between platforms, as far as I can tell it’s been like that since it was implemented and changing it now seems likely to cause issues. Happy to be proven wrong though.

Thanks for the suggestion, I have removed the byte-swapping in VST3Interface::vst2PluginId and nothing broke for me.

I have

  • a cubase project made with “Foo v1.vst3” on Windows
  • a cubase project made with “Foo v1.vst” , also on Windows
  • a cubase project made with “Foo v2.vst” , on Windows

and I can open any of them when only “Foo v2.vst3” is available, on both windows and macos.

The only thing that I have to patch in JUCE sourcecode is the getParameterMap function in juce_audio_plugin_client_VST3.cpp. It seems it is called with a byte-swapped pluginId on windows (that was also happening before I made the change to vst2PluginId), so I’m changing it this way:

    std::map<Vst::ParamID, AudioProcessorParameter*> getParameterMap (const VST3Interface::Id& pluginId) const
    {
        const auto iter = compatibleParameterIdMap.find (pluginId);

+        if (iter == compatibleParameterIdMap.end())
+        {
+            auto id2 = pluginId;
+            std::swap(id2[0], id2[3]); std::swap(id2[1], id2[2]); // first uint32
+            std::swap(id2[4], id2[5]); // uint16
+            std::swap(id2[6], id2[7]); // uint16
+            
+            auto iter2 = compatibleParameterIdMap.find (id2);
+            if (iter2 != compatibleParameterIdMap.end())
+            {
+                return iter2->second;
+            }
+        }

        return iter != compatibleParameterIdMap.end() ? iter->second
                                                      : std::map<Vst::ParamID, AudioProcessorParameter*>{};
    }