AAX_CParamID strlen 31 long only and duplicate map

Looking at the AAX documentation is says that AAX_CParamID can only have a strlen of 31 maximum, which is nowhere enforced in the AAX wrapper, which could lead to parameter collisions as this is stored as the key to a std::map<std::string, *> by the mParameterManager.

Also there is no point duplicating the storing of juce parameters in a possibly clashing hash table when an extra pointer can be stored directly to the juce parameter by inheriting from the AAX_CParameter class, and using the mechanism already provide, which already gets called when doing operations (ie a hash table on top of the existing std::map is just extra work):

struct Juce_AAX_CParameter : AAX_CParameter<float>
{
    Juce_AAX_CParameter(AudioProcessorParameter* juceParam_, AAX_CParamID identifier, const AAX_IString& name, float defaultValue, const AAX_ITaperDelegate<float>& taperDelegate, const AAX_IDisplayDelegate<float>& displayDelegate, bool automatable)
    : AAX_CParameter<float>(identifier, name, defaultValue, taperDelegate, displayDelegate, automatable)
    , juceParam(juceParam_)
    {
        jassert (juceParam);
    }
    AudioProcessorParameter* juceParam;
};

If you possibly object to doing a reinterpret_cast<Juce_AAX_CParameter*> on the mParameterManager fetching, then don’t have a look at all the other juce parameter code doing this all over the place in the plugin wrappers. Also the juceParam passed in can be a legacy wrapped param, so this logic is done in a single place at construction time instead of every time parameters are accessed.

edit: here is an example of where the lookup is already done, and then it is done again by juce:

AAX_Result SetParameterNormalizedValue (AAX_CParamID paramID, double newValue) override
{
    if (auto* p = mParameterManager.GetParameterByID (paramID))
        p->SetValueWithFloat ((float) newValue);

    setAudioProcessorParameter (paramID, (float) newValue);

    return AAX_SUCCESS;
}

void setAudioProcessorParameter (AAX_CParamID paramID, double value)
{
    if (auto* param = getParameterFromID (paramID))
    {
        auto newValue = static_cast<float> (value);

        if (newValue != param->getValue())
        {
            param->setValue (newValue);

            inParameterChangedCallback = true;
            param->sendValueChangedMessageToListeners (newValue);
        }
    }
}

AudioProcessorParameter* getParameterFromID (AAX_CParamID paramID) const noexcept
{
    return paramMap [AAXClasses::getAAXParamHash (paramID)];
}

And this is what it would look like if it was done just the once:

AAX_Result SetParameterNormalizedValue (AAX_CParamID paramID, double newValue) override
{
    if (auto* p = reinterpret_cast<Juce_AAX_CParameter*>(mParameterManager.GetParameterByID (paramID)))
    {
        p->SetValueWithFloat ((float) newValue);
        setAudioProcessorParameter (p->juceParam, (float) newValue);
    }

    return AAX_SUCCESS;
}

I think that’s asserted here:

I definitively remember running into this assertion once :wink:

Me too, after release, what a pain that caused.

Well it’s good it’s checked somewhere, even if it is just an assert.

And now, how about that useless hash table paramMap which duplicates the functionality of the mParameterManager? Is it possibly twice the fun to look something up twice - with the added danger of another clash due to the hash function result not being checked?

It looks like this jassert is present in all DEBUG builds. Would it makes sense not to bother people that aren’t doing AAX to even have this there? It would be easy enough to hide it:

void AudioProcessor::checkForDuplicateTrimmedParamID ([[maybe_unused]] AudioProcessorParameter* param)
{
   #if JUCE_DEBUG && ! JUCE_DISABLE_CAUTIOUS_PARAMETER_ID_CHECKING
    if (auto* withID = dynamic_cast<AudioProcessorParameterWithID*> (param))
    {
        if (wrapperType == AudioProcessor::wrapperType_AAX)
        {    
            constexpr auto maximumSafeAAXParameterIdLength = 31;

            const auto paramID = withID->paramID;

            // If you hit this assertion, a parameter name is too long to be supported
            // by the AAX plugin format.
            // If there's a chance that you'll release this plugin in AAX format, you
            // should consider reducing the length of this paramID.
            // If you need to retain backwards-compatibility and are unable to change
            // the paramID for this reason, you can add JUCE_DISABLE_CAUTIOUS_PARAMETER_ID_CHECKING
            // to your preprocessor definitions to silence this assertion.
            jassertquiet (paramID.length() <= maximumSafeAAXParameterIdLength);

            // If you hit this assertion, two or more parameters have duplicate paramIDs
            // after they have been truncated to support the AAX format.
            // This is a serious issue, and will prevent the duplicated parameters from
            // being automated when running as an AAX plugin.
            // If there's a chance that you'll release this plugin in AAX format, you
            // should reduce the length of this paramID.
            // If you need to retain backwards-compatibility and are unable to change
            // the paramID for this reason, you can add JUCE_DISABLE_CAUTIOUS_PARAMETER_ID_CHECKING
            // to your preprocessor definitions to silence this assertion.
            jassertquiet (trimmedParamIDs.insert (paramID.substring (0, maximumSafeAAXParameterIdLength)).second);
        }
    }
   #endif
}

Isn’t that exactly the purpose of JUCE_DISABLE_CAUTIOUS_PARAMETER_ID_CHECKING? I personally like it being defaulted to check it always, not only when building AAX, since releasing AAX versions of a plugin seems like a common thing but testing debug builds of the plugin during development in Pro Tools is rather inconvenient compared to other host and therefore at least for us something not done frequently during development. We rather use VST3 and AU during development and only do some Pro Tools tests once a Plug In is release ready, so being notified by this even in e.g. a VST3 build definitively prevented some headache.

In case you hit this and you are sure that you’ll never release an AAX version of your plugin and you don’t want to shorten your parameter ID it’s easy enough to silence it by just setting that preprocessor definition in my opinion.

1 Like

I’m all for a lowest common denominator of ids, but in that case there should be another flag checking if you’ve actually enabled AAX support in the first place.

I’m actually all for thorough checking even in the release version of the lowest common denominator of all currently compiled plugin client versions to assert and provide clean and detailed instructions on how to avoid clashes with the parameters, both plugin ids and hashes of the ids and bring all this directly into the AudioProcessor base classes to make sure no matter which version you test with you get an assert for troubles that will appear in the other versions as well.