Plugin state not restored in Cubase 8 for newer plugin version?

Summary

I have a new version of a VST2 plugin and am making sure plugin state is correctly restored when loading projects saved with an older version of the plugin when the new version is installed, and vice versa.
This seems to work fine in all hosts I tested, but not in Cubase 8.

Both versions of the plugin were built with Juce, and the version numbers are 1.1.1 and 1.1.2.
It seems like Cubase refuses to call setStateInformation in the case the older version is installed and a project saved with the newer version is opened (note: my code can handle that, and this works fine in other hosts).

Details / debug info

This is what I’m observing (Cubase 8 64-bit on Windows 10):

CASE 1: debugging with version 1.1.1 of our plugin installed
(plugin version shows up as 0.1.1.1 in Cubase)

loading project saved with version 1.1.1 of plugin
–> setStateInformation gets called

loading project saved with version 1.1.2b1 of plugin
–> neither setStateInformation nor setCurrentProgramStateInformation gets called…

CASE 2: debugging with version 1.1.2b1 of our plugin installed
(plugin version shows up as 1.1.2.0 in Cubase)

loading project saved with version 1.1.1 of plugin
–> setStateInformation gets called

loading project saved with version 1.1.2b1 of plugin
–> setStateInformation gets called

I am also seeing this in the plugin manager (this is in Cubase Pro 8.5.30 32-bit on Windows 10 64-bit):

  • new plugin (Juce version 1.1.2): version shows up as 1.1.2.0 in plugin manager
  • old plugin (Juce version 1.1.1): version shows up as 0.1.1.1 in plugin manager

Has anyone experienced this before?
Is there anything I can do about this?

For the Roli team: is there a plugin example in the Juce examples that uses programs? That would be easier, as it probably also happens with other plugins (I’m not doing anything special other than declaring a number of programs and keeping a 2-D array of floats for the parameter values of the programs, and then using the program number as index in the 2D array; as said this all works fine in other hosts).

Thanks!

Just discovered this long thread started by @yfede and followed-up by @fabian:

I’m not sure if I understood all the great detective work + details I read in there, but did I pick up correctly that Cubase will simply refuse to supply a plugin chunk saved with plugin version 1.1.2 to a currently installed plugin with version 1.1.1? (talking about plugin version, as I don’t think we ever explicitly set a chunk version)

That would mean that if a user upgrades a plugin to a later version, saves his project, and then decides to go back to a previous version of the plugin, he is basically screwed, as his project won’t load the plugin’s settings any longer. And is this then for any version number change, including minor or maintenance version number changes?

It’s strange that the old plugin version 1.1.1 shows as 0.1.1.1, and the new plugin version shows as 1.1.2.0 (even though only the maintenance version number part changed).

Perhaps relevant: the old version was built with Juce 2.1.1 (back in July 2013…) and the new one with Juce 5.3.1.

Exactly

And what’s displayed as version number in Cubase does not really matter, what counts is the “number” returned by the plug-in as the chunk version.

If you haven’t set a chunk version explicitly, JUCE provides one based upon the version of the plug-in.

The plain numeric value of said code is what counts for Cubase: if the plug-in has a lower value then the one of the chunk saved in the song, Cubase will not pass the chunk to the plug-in, end of the story.

Totally separate topic is how Cubase does its guesswork to obtain from said number a “displayable” version number. That’s what my colorful table in the topic is all about.

But again, w.r.t. the issue that you are seeing, only the plain numeric value of what ends in vstEffect.pluginVersion counts in the eyes of Cubase, as shown in this table also present in the topic you linked:

See that, when the number in the first column is lower than the value that is stored in the saved chunk (0x040506), then Cubase (9 in this case, but I believe all versions behave the same), will refuse to feed the plug-in the chunk stored in the session (‘no’ appears in the Cubase column)

Thanks a lot for the feedback! I must have missed that table going through the thread, sorry about that, as it’s really clearly explained. Hadn’t tested in Studio One, but at least I now know to expect a similar issue there…

So, if I could set the chunk version in the new plugin build (plugin version 1.1.2) to the previous version of the plugin (which was 1.1.1, and since I didn’t explicitly set a chunk version then, also chunk version 1.1.1), then the older plugin should get a saved session chunk even it it was saved with the later plugin.

And, also, if a plugin handles its own compatibility checking/handling internally, the chunk version it uses should actually not change between plugin versions (unless there is a substantial change in the way the chunk content is stored in the binary blob that makes it impossible to process it).
In my case, I use XML with 2 special fields: version of the plugin that saved the chunk + “lowest version that can load the chunk”. I then interpret the contents myself inside the plugin and handle things on a best-effort basis.

I looked back into the other forum thread, and it appears that I should be able to explicitly set the chunk version using JucePlugin_VSTChunkStructureVersion, right?
If I define that in the Projucer under preprocessor definitions and define it as the value it was in my older plugin version, this ought to fix the issue with Cubase and Studio One, right?

OK. So, I tried this:

  1. Went back to the code of the old version, and verified the version used then:

     #ifndef  JucePlugin_VersionCode
      #define JucePlugin_VersionCode            0x10101
     #endif
    
  2. Re-built my new version, defining JucePlugin_VSTChunkStructureVersion=0x10101 (+ checked in the code editor that in this piece of code in juce_VST_Wrapper.cpp the line between #ifdef and #else became activated and the value was indeed 0x10101:

    #ifdef JucePlugin_VSTChunkStructureVersion
     vstEffect.plugInVersion = JucePlugin_VSTChunkStructureVersion;
    #else
     vstEffect.plugInVersion = JucePlugin_VersionCode;
    #endif
    
  3. Saved a project in Cubase 8.5 while the new version was installed (version 1.1.2.0 in Cubase) + verified that the tweaked parameter settings were effectively restored when re-opening the project.

  4. Re-installed the old version of the plugin (version 0.1.1.1 in Cubase) + opened the project saved in step 3.

Result: plugin state still not restored…
I debugged and could see verify that a breakpoint in for example getNumPrograms was being hit, but NOT a breakpoint in setCurrentProgramStateInformation nor setStateInformation…
I also tried in an older Cubase 5 version, and same result.

Any other ideas I could try?

In my old version, the code in step 2. above looked like this:

    cEffect.version = convertHexVersionToDecimal (JucePlugin_VersionCode);

with:

static inline VstInt32 convertHexVersionToDecimal (const unsigned int hexVersion)
{
   #if JUCE_VST_RETURN_HEX_VERSION_NUMBER_DIRECTLY
    return (VstInt32) hexVersion;
   #else
    return (VstInt32) (((hexVersion >> 24) & 0xff) * 1000
                     + ((hexVersion >> 16) & 0xff) * 100
                     + ((hexVersion >> 8)  & 0xff) * 10
                     + (hexVersion & 0xff));
   #endif
}

Now that method looks like this:

static inline int32 convertHexVersionToDecimal (const unsigned int hexVersion)
{
   #if JUCE_VST_RETURN_HEX_VERSION_NUMBER_DIRECTLY
    return (int32) hexVersion;
   #else
    // Currently, only Cubase displays the version number to the user
    // We are hoping here that when other DAWs start to display the version
    // number, that they do so according to yfede's encoding table in the link
    // below. If not, then this code will need an if (isSteinberg()) in the
    // future.
    int major = (hexVersion >> 16) & 0xff;
    int minor = (hexVersion >> 8) & 0xff;
    int bugfix = hexVersion & 0xff;

    // for details, see: https://forum.juce.com/t/issues-with-version-integer-reported-by-vst2/23867

    // Encoding B
    if (major < 1)
        return major * 1000 + minor * 100 + bugfix * 10;

    // Encoding E
    if (major > 100)
        return major * 10000000 + minor * 100000 + bugfix * 1000;

    // Encoding D
    return static_cast<int32> (hexVersion);
   #endif
}

I’m not using JUCE_VST_RETURN_HEX_VERSION_NUMBER_DIRECTLY, not in the old version and not in the new version.

Looking at the debugger, what is convertHexVersionToDecimal returning in the new and old version of your plug-in?

Thanks for the follow-up Fabian.
Here is the answer to your question (all of this tested with Cubase 8.5.30 32-bit):

Old plugin version (1.1.1)

JuceVSTWrapper

static inline VstInt32 convertHexVersionToDecimal (const unsigned int hexVersion)
{
   #if JUCE_VST_RETURN_HEX_VERSION_NUMBER_DIRECTLY
    return (VstInt32) hexVersion;
   #else
    return (VstInt32) (((hexVersion >> 24) & 0xff) * 1000
                     + ((hexVersion >> 16) & 0xff) * 100
                     + ((hexVersion >> 8)  & 0xff) * 10
                     + (hexVersion & 0xff));
   #endif
}

and in the constructor:

    cEffect.version = convertHexVersionToDecimal (JucePlugin_VersionCode);

hexVersion is: 0x00010101
convertHexVersionToDecimal returns: 111 (0x0000006f)
JUCE_VST_RETURN_HEX_VERSION_NUMBER_DIRECTLY: not defined
plugin manager in Cubase displays as plugin version: 0.1.1.1

New plugin version (1.1.2b1)

(with JucePlugin_VSTChunkStructureVersion NOT defined)

JuceVSTWrapper

static inline int32 convertHexVersionToDecimal (const unsigned int hexVersion)
{
   #if JUCE_VST_RETURN_HEX_VERSION_NUMBER_DIRECTLY
    return (int32) hexVersion;
   #else
    // Currently, only Cubase displays the version number to the user
    // We are hoping here that when other DAWs start to display the version
    // number, that they do so according to yfede's encoding table in the link
    // below. If not, then this code will need an if (isSteinberg()) in the
    // future.
    int major = (hexVersion >> 16) & 0xff;
    int minor = (hexVersion >> 8) & 0xff;
    int bugfix = hexVersion & 0xff;

    // for details, see: https://forum.juce.com/t/issues-with-version-integer-reported-by-vst2/23867

    // Encoding B
    if (major < 1)
        return major * 1000 + minor * 100 + bugfix * 10;

    // Encoding E
    if (major > 100)
        return major * 10000000 + minor * 100000 + bugfix * 1000;

    // Encoding D
    return static_cast<int32> (hexVersion);
   #endif
}

–> this never gets called (not at plugin instantiation, not at project saving / loading)

pointer_sized_int handleGetManufacturerVersion (VstOpCodeArguments)
{
    return convertHexVersionToDecimal (JucePlugin_VersionCode);
}

–> this never gets called (not at plugin instantiation, not at project saving / loading)
–> note: this is the only place where convertHexVersionToDecimal is used

and in the constructor:

   #ifdef JucePlugin_VSTChunkStructureVersion
    vstEffect.plugInVersion = JucePlugin_VSTChunkStructureVersion;
   #else
    vstEffect.plugInVersion = JucePlugin_VersionCode;
   #endif

–> this does get called, and vstEffect.plugInVersion = 0x00010102 (JucePlugin_VersionCode)

JucePlugin_VSTChunkStructureVersion: not defined
JUCE_VST_RETURN_HEX_VERSION_NUMBER_DIRECTLY: not defined
plugin manager in Cubase displays as plugin version: 1.1.2.0

I also tried this with the AudioPluginDemo in the current Juce repository, with the same result:

  • convertHexVersionToDecimal never gets called
  • vstEffect.plugInVersion = JucePlugin_VersionCode is used in the constructor

Other combination and working solution

However, I then tried this:
In contrast to what I did for my previously reported test, I now defined JucePlugin_VSTChunkStructureVersion=0x0000006f (so: not the hexVersion value of my old plugin version, but the convertHexVersionToDecimal value of my old plugin version instead) and now the old version did load the state saved by my new version!

So, I guess this is the takeway:
Define JucePlugin_VSTChunkStructureVersion to the value your older plugin received from convertHexVersionToDecimal (with the implementation for that function present at that moment, which is not necessarily the same as the current implementation).

I now hope this won’t break in other hosts I had already tested without doing this…


PS About the “beta” in the version 1.1.2b1
We use alpha (a), beta (b), release candidate (rc) and final stages for our products, and that’s also what we specify in the Projucer as version. This doesn’t seem to give any problems, as this translates in the AppConfig.h to the folowing:

#ifndef  JucePlugin_Version
 #define JucePlugin_Version                1.1.2b1
#endif
#ifndef  JucePlugin_VersionCode
 #define JucePlugin_VersionCode            0x10102
#endif
#ifndef  JucePlugin_VersionString
 #define JucePlugin_VersionString          "1.1.2b1"