How to keep old (pre-JUCE) automation data?


#1

I found a way to get our old (non-JUCE) VST3 sessions to load in Cubase, but the automation data doesn’t show up. After some digging, I found that if I set JUCE_FORCE_USE_LEGACY_PARAM_IDS=1, then the automation data does show up. However, when I do that, then my old AAX sessions lose their automation data. (I have not tested with VST2 or AU yet, or on the PC.) Is there some way to maintain support for old (non-JUCE) session automation data across all plugin types?


#2

That should fix things for all formats. Can you provide an example of a previous AAX parameter ID that is different from a new one with that flag enabled?


#3

Out of interest, how is JUCE_FORCE_USE_LEGACY_PARAM_IDS working? Will it just stick with the old ids forever, or might it even replace the old once with the newer ones, once the session is resaved? So after a couple of years could we just drop the flag, and users might not be affected if they opened the session during the transition time?


#4

Unfortunately I don’t think it’s possible; I’m unaware of any mechanism that a plug-in could use to find out if the host is attempting to use automation data that doesn’t correspond to your parameter IDs.


#5

All of my old AAX parameters work with that flag disabled, but with it enabled, only Master Bypass still appears. All the other automation lanes show a “–” for the parameter name.

I do not know how to see what IDs ProTools is trying to use, but below are the old AAX parameter definition, followed by the new JUCE parameter definition for one of my parameters. (All parameters are defined in the same order as they were in AAX. This is the fourth parameter.)

AAX_CParameter<int32_t>* keyParameter = new AAX_CParameter<int32_t>("kypu",
																AAX_CString("Key"), 
																defaultKey_Value,
																AAX_CLinearTaperDelegate<int32_t, 1000>(0.0f, 11.0f),
																AAX_CStringDisplayDelegate<int32_t>(mKeyParamStrings), 
																true);

	pKeyParam = parameters.createAndAddParameter ("kypu", // "Key_ID",       // parameterID
										"Key",       // parameter name
										{},           // parameter label (suffix)
										NormalisableRange<float> (0.0f, 11.0f, 1.0f),    // range, discrete list
										0.0f,         // default value
										[this](float value) // putting 'this' inside [] allows using instance pointer!
										{
											return this->getKeyNameBasedOnScale( value );
										},
										nullptr,
										true, // IS meta-parameter!
										true, // automatable
										true ); // discrete

#6

I am able to get things to work for VST3 without disturbing AAX if I redefine JUCE_FORCE_USE_LEGACY_PARAM_IDS as 1 at the top of the VST3 wrapper source code, and then redefine it to its previous value (if it had one) at the bottom of that source file. That allows VST3 to enable that flag, while AAX leaves it disabled. Doing that, both AAX and VST3 previous (non-JUCE) sessions restore their automation data correctly using the new JUCE-based plugins. But this is another edit to the JUCE code, which I am loathe to do.


#7

Ah, I see what’s going on now.

The different plug-in formats use different schemes for addressing parameters. To get some idea of how irritating this is have a look here:

It’s not exactly the same problem but it is related. To have precise control over ordering it’s easiest to use integers as parameter IDs - which is one of the things that JUCE_FORCE_USE_LEGACY_PARAM_IDS enforces. Since JUCE is cross-platform it applies the same logic to all formats.

In your old project you are using different schemes on each platform (or, at least, the same scheme as current JUCE on one platform, and the same scheme as legacy JUCE in another), so setting the flag is having mixed results. Unfortunately there’s no nice way around this, and the workaround you have in place is probably the best way to go.


#8

I seem to be getting AAX and VST3 parameters working correctly now, but AU is not. I tried defining TEMP_JUCE_FORCE_USE_LEGACY_PARAM_IDS as 1 for AU, but then only the Bypass parameter shows up at all in Logic and Studio One. Without it, the parameters are all in a completely different order, and the automation lanes are affecting the wrong parameters now. Is there some flag I can set that will allow AudioUnit parameters (pre-JUCE) to use my new parameter definitions? Do I have to actually create different parameter IDs for AU than I do for AAX or VST3 (or VST2, which I haven’t even looked at yet)?


#9

Has anyone out there updated to JUCE from using the auwrapper (from the VST3 SDK)? How did you get your automation data to match up with the old data in Logic? There has to be some way to do this!


#10

We did move from the auwrapper in the VST3 SDK and hit the same problems.
The problem here is that the JUCE_FORCE_USE_LEGACY_PARAM_IDS definition changes the JUCE wrapper code to behave as it did in previous versions of JUCE, which still weren’t compatible with what the auwrapper used to do.

The following steps fixed these issues for us:

  • set JUCE_FORCE_USE_LEGACY_PARAM_IDS=1
  • In juce_AU_wrapper.mm, on line 1970, change juceParamID.hashCode() to juceParamID.getIntValue() - this will use the parameter index as the ID.
  • In juce_AU_wrapper.mm, comment out the else on line 1897 - this will force the generation of parameter IDs from the indices.

Good luck!


#11

Hmm, that’s not working, either. I get a bunch of unmapped parameters. All the automation lanes say “(unused)” after the plugin name, and if I click on the drop-downs to look at what parameters are available, there are none (except the Bypass parameter). I’m using JUCE v5.3.2, so the line numbers may differ, but here are the changes I made, wrapping them new code in #if statements.

In generateAUParameterID():

	#if JUCE_FORCE_USE_LEGACY_PARAM_IDS
    	AudioUnitParameterID paramHash = static_cast<AudioUnitParameterID> (juceParamID.getIntValue());
	#else
    	AudioUnitParameterID paramHash = static_cast<AudioUnitParameterID> (juceParamID.hashCode());
  	#endif

and in addParameters():

	#if JUCE_FORCE_USE_LEGACY_PARAM_IDS
        Globals()->UseIndexedParameters (numParams);
	#else
    if (forceUseLegacyParamIDs)
    {
        Globals()->UseIndexedParameters (numParams);
    }
    else
    {
        for (auto* param : juceParameters.params)
        {
            const AudioUnitParameterID auParamID = generateAUParameterID (param);

            // Consider yourself very unlucky if you hit this assertion. The hash code of your
            // parameter ids are not unique.
            jassert (! paramMap.contains (static_cast<int32> (auParamID)));

            auParamIDs.add (auParamID);
            paramMap.set (static_cast<int32> (auParamID), param);
            Globals()->SetParameter (auParamID, param->getValue());
        }
    }
    #endif

If I set JUCE_FORCE_USE_LEGACY_PARAM_IDS=0, then my parameters appear as automatable in Logic, but they don’t map correctly to the old ones. Did I make a mistake in the code above?


#12

Try this:

    if (forceUseLegacyParamIDs)
    {
        Globals()->UseIndexedParameters (numParams);
    }
    //else
    {
        for (auto* param : juceParameters.params)
        {
            const AudioUnitParameterID auParamID = generateAUParameterID (param);

            // Consider yourself very unlucky if you hit this assertion. The hash code of your
            // parameter ids are not unique.
            jassert (! paramMap.contains (static_cast<int32> (auParamID)));

            auParamIDs.add (auParamID);
            paramMap.set (static_cast<int32> (auParamID), param);
            Globals()->SetParameter (auParamID, param->getValue());
        }
    }

#13

Oh, just comment out the “else”. Ok, I had removed the whole else block.

Unfortunately, that change fails auval:
FATAL ERROR: OpenAComponent: result: -10878,0xFFFFD582


#14

Oh, that’s because I had valid numeric IDs for a few of the parameters (as a test). I’ll try returning them to their expected IDs.


#15

Nope. Now they all return 0 as the auParamID. Do I have to define my parameters with ID strings that are their index, as in “0”, “1”, for parameter #0 and #1, etc.? Currently, all my parameters have names like “KeyID”, “ScaleID”. When I try to use strings like “1987011436”, which is a string that contains the numeric value for the old 4-character code (‘vocl’ in this case), then it crashes because getIntValue() returns 1987011436, which is greater than the parameter count. I thought it would be getting the index, not the numeric value of the ID string? Do I have to change getIntValue()?


#16

Returning the parameter’s getParameterIndex() instead of getIntValue() from generateAUParameterID() almost works, but I suspect other code elsewhere is using getIntValue(), because some of the parameter ranges and values are not what they should be. But I do get some of the parameters properly mapped.


#17

Ok, I reversed some logic that I had for defining JUCE_FORCE_USE_LEGACY_PARAM_IDS for AAX vs. AU vs. VST3, defaulting it to 1 except for AAX, and now if I return param->getParameterIndex() from generateAUParameterID() (instead of juceParamID.getIntValue() as your suggestion said), then I do get correct mappings of the automation lanes to the desired parameters. However, now all parameters show just the values 0…1 instead of the value ranges that my JUCE parameters specify. That includes the discrete parameters, which now only show 0 or 1. My plug-in still works as expected, but the automation data is interpreted incorrectly. Do you know how I can fix that?


#18

I’ve got it working, but I’m not sure if what I’ve done is appropriate. Here are the change I made:

in getMaximumParameterValue():

    // return param->isDiscrete() && (! forceUseLegacyParamIDs) ? (float) (param->getNumSteps() - 1) : 1.0f;
    return param->isDiscrete() ? (float) (param->getNumSteps() - 1) : 1.0f;

in addParameters():

        // juceParameters.update (*juceFilter, forceUseLegacyParamIDs);
    juceParameters.update (*juceFilter, false);

and:

            // if (param->isDiscrete() && (! forceUseLegacyParamIDs))
        if (param->isDiscrete())

But as I say, I don’t know if this is correct. All I know is that it seems to be working in Logic (and Studio One) now.


#19

OK, looking at our AU Wrapper changes again, it looks like what you might need to do is just turn off JUCE_FORCE_USE_LEGACY_PARAM_IDS for the AU Wrapper. We did that and added our own pre-processor definition for our legacy products like this:

void addParameters()
{
    juceParameters.update (*juceFilter, forceUseLegacyParamIDs);
    const int numParams = juceParameters.getNumParameters();

    if (forceUseLegacyParamIDs || OUR_OWN_FORCE_USE_LEGACY_PARAM_IDS)
    {
        Globals()->UseIndexedParameters (numParams);
    }
   #if ! OUR_OWN_FORCE_USE_LEGACY_PARAM_IDS
    else
   #endif
    {
        // leave the rest of the else as it is.
    }

    // ... etc.
}

AudioUnitParameterID generateAUParameterID (AudioProcessorParameter* param) const
{
    const String& juceParamID = LegacyAudioParameter::getParamID (param, forceUseLegacyParamIDs);
   #if OUR_OWN_FORCE_USE_LEGACY_PARAM_IDS
    AudioUnitParameterID paramHash = static_cast<AudioUnitParameterID> (juceParamID.getIntValue());
   #else
    AudioUnitParameterID paramHash = static_cast<AudioUnitParameterID> (juceParamID.hashCode());
   #endif

    // etc.
}

This is exactly how we’ve solved that part of the legacy compatibility problem, and it lets us force our own legacy handling for older products and use JUCE’s way for our new products.


#20

That almost works for us. I had to change juceParamID.getIntValue() to param->getParameterIndex(), because getIntValue() always returns 0, given my parameter definitions. But with that change, it’s loading my old sessions correctly now! Thanks for the help!