Questions regarding the new AAX Wrapper

Kudos for the new wrappers added in the 3.6.8 build of the SDK.

I’ve been testing the AAX wrapper recently and I have some questions/remarks:

  1. The global bypass parameter in my plug-in implementation has to be set to -1 - which isn’t accepted as valid param ID by validator - in order for the AAX Wrapper to actually expose it as the MasterBypass to ProTools. It seems to have to do with this line in AAXWrapper_Parameters::AAXWrapper_Parameters():
mSimBypass = mWrapper->mBypassParameterID == -1;

Is this intended? I see the mBypassParameterID is initialized to kNoParamId in the Vst2Wrapper constructor and later set to the actual bypass param ID in Vst2Wrapper::setupParameters() if applicable. So isn’t the mentioned line supposed to be:

mSimBypass = mWrapper->mBypassParameterID != -1;

in fact?

Patching the aaxwrapper code as suggested eventually takes me down the if(mBypass) path in the Steinberg::int32 AAXWrapper::Process() function. However, the code there is a little puzzling. It doesn’t actually apply a bypass but create a silent output when the bypass is engaged in ProTools. It also seems like a change in ProTools’ Master Bypass isn’t propagated to the underlying VST3 bypass parameter. Any hints on that one?

EDIT: It looks like the current code needs to change substantially if you’re going to support a synchronization between ProTools’ Master Bypass and a bypass parameter exposed by a given VST3 implementation, anyway. I reckon the mSimBypass mode is in fact supposed to emulate a Master Bypass if the VST3 doesn’t offer any bypass parameter. As pointed out, no synchronization between an available VST3 bypass and the Pro Tools one would be going on in that case.

  1. I observe sporadic CPU overloads, when calling into EditController::setParameterNormalized() during my audio process. I don’t need sample accurate automation and thus process my incoming parameter changes outside of the process call, which I guess is a good idea when some considerable calculus may be going on during parameter changes, anyway. However, I transfer my meters back using read only parameters, which are set during the process loop. Once I remove these, the CPU overloads go away. I can re-arrange things and use a polling mechanism so as to yield some framerate granularity in the UI regarding meters and bigger processing blocks, but I’d like to find about the actual root of this problem before.

  2. I admit this is a rather specific one, but it seems there’s a problem with properly setting the default value of StringListParameter params. Here’s an example. In my overrider of EditController::initialize() I do:

auto powerParam = new StringListParameter(USTRING("Power"), kPowerId);
powerParam->appendString(USTRING("Off"));
powerParam->appendString(USTRING("On"));
powerParam->getInfo().defaultNormalizedValue = 1.f;
parameters.addParameter(powerParam);

Which initializes the power parameter to On in VST2 and VST3 builds. However, in AAX this doesn’t work. Any hints?

EDIT: It seems this can be worked around by adding powerParam->setNormalized(powerParam->getInfo().defaultNormalizedValue). However, I don’t think this should be necessary.

  1. It seems that EditController::setActive() is never hit through the aaxwrapper which surely may cause some problems in certain implementations, e.g. pops due to uninitialized buffers which are reset in setActive(). I’d at least expect a call from AAXWrapper::ResetFieldData(). So would you maybe add a sequence of suspend(); resume(); there so my implementation can set itself active properly via the Vst2Wrapper?

  2. Do you plan to add some sort of post build tool that extracts the preset banks in the tfx format into the plug-in bundle in order to expose them in the format expected by ProTools?

Thanks!

thanks for the report:

concerning the point 1):

you can try these changes:

in AAXWrapper_Parameters::AAXWrapper_Parameters:

	extern const ParamID kNoParamId;

	mSimBypass = (mWrapper->mBypassParameterID != kNoParamId);

and in AAXWrapper_Parameters::SetParameterNormalizedRelative

		if (mSimBypass && strcmp (iParameterID, kBypassId) == 0)
		{
			mWrapper->mBypass = (mWrapper->mBypass + iValue >= 0.5);
			mWrapper->setBypass (mWrapper->mBypass);
			return AAX_SUCCESS;
		}

and in AAXWrapper_Parameters::UpdateParameterNormalizedValue

		if (mSimBypass && strcmp (iParameterID, kBypassId) == 0)
		{
			mWrapper->mBypass = iValue >= 0.5;
			mWrapper->setBypass (mWrapper->mBypass);
		}

and disable this code in aaxwrapper.cpp

/* // apply bypass
	static const float kDiffGain = 0.001f;
	if (mBypass)
	{
		int32 bufPos = 0;
		while (mBypassGain > 0 && bufPos < bufferSize)
		{
			for (int32 i = 0; i < cntOut; i++)
				outputs[i][bufPos] *= mBypassGain;
			mBypassGain -= kDiffGain;
			bufPos++;
		}
		for (int32 i = 0; i < cntOut; i++)
			memset (&outputs[i][bufPos], 0, (bufferSize - bufPos) * sizeof (float));
	}
	else if (mBypassGain < 1)
	{
		int32 bufPos = 0;
		while (mBypassGain < 1 && bufPos < bufferSize)
		{
			for (int32 i = 0; i < cntOut; i++)
				outputs[i][bufPos] *= mBypassGain;
			mBypassGain += kDiffGain;
			bufPos++;
		}
	}*/

and keep me inform, i will check the other points…

point 3)

the constructor:

 StringListParameter::StringListParameter (const TChar* title,..

will set the normalizedValue to 0…not 1…
you have to call setNormalized for initializing it to the wanted value
or you have to use the another constructor :

StringListParameter::StringListParameter (const ParameterInfo& paramInfo)

which sets the normalized Value to the default one (defined in the paramInfo)

Thanks for the quick response, Yvan! Your suggested fix works fine, exept for some linker issues in resolving

extern const ParamID kNoParamId;

but that’s not really an issue.

I see, thanks! Any info on the other points, yet?

Here’s one more detail:

you expose Pro Tools meters according to the settings provided in the effPlugins[].mMeters member in AAXWrapper::DescribeAlgorithmComponent(). None of the meter fields added to the algorithm context are filled in the AAXWrapper::Process() in order for Pro Tools to read them out. Can we expect this feature to be added in a future version, e.g. via read-only parameters exposed by the VST3 implementation?

Hi

if you want you can contribute for this feature… we can review it together…

Yes, sure. I’ll provide a sketch asap.

Any insights on questions 4. and 5. from my original post, though?

Here’s my sketch for supporting Pro Tools meters:

  1. Change the last member in AAX_Meter_Desc struct in aaxwrapper_description.h to allow for e.g. CL meters
struct AAX_Meter_Desc
{
	const char* mName;
	uint32 mID;
	uint32 mOrientation; // see AAX_EMeterOrientation
	uint32 mType; // see AAX_EMeterType
};
  1. Implement and update meters as read-only parameters in your VST3 implementation.

  2. Patch aaxwrapper.h as follows

...
#include <memory>
...
class AAXWrapper : ... {
...
private:
...
     // Add these two as static members
     static Steinberg::int32 cntMeters;
     static std::unique_ptr<Steinberg::int32[]> meterIds;
...
};
  1. Patch aaxwrapper.cpp as follows so the meters and their values are properly exposed to Pro Tools
...
//------------------------------------------------------------------------

Steinberg::int32 AAXWrapper::cntMeters = 0;
std::unique_ptr<Steinberg::int32[]> AAXWrapper::meterIds;

//------------------------------------------------------------------------
AAXWrapper::AAXWrapper (IAudioProcessor* processor, IEditController* controller,
{
	...
}
//------------------------------------------------------------------------
Steinberg::int32 AAXWrapper::ResetFieldData (Steinberg::int32 index, void* inData,
                                             Steinberg::uint32 inDataSize)
{
	// Make sure setActive() is called, doesn't have to do with the meters feature directly,
	// but addresses issue 4.) from my initial post
	suspend ();
	resume ();
	...
}
	
//------------------------------------------------------------------------
Steinberg::int32 AAXWrapper::Process (AAXWrapper_Context* instance)
{
	//--- ------ Retrieve instance-specific information ---------//
	// Memory blocks
	const int32_t bufferSize = *static_cast<int32_t*> (instance->ptr[idxBufferSize]);
	AAX_ASSERT (bufferSize <= 1024);

	...
	processReplacing (pdI, outputs, bufferSize);
        
	// Meter readout
	if(cntMeters > 0)
	{
		float* meters = *static_cast<float**>(instance->ptr[idxMeters]);
		for(int32 m = 0; m < cntMeters; m++)
		{
			// Retrieve computed meter values from VST3 implementation
			meters[m] = mController->getParamNormalized(meterIds[m]);
		}
	}
	...
}

//------------------------------------------------------------------------
void AAXWrapper::DescribeAlgorithmComponent (AAX_IComponentDescriptor* outDesc,
                                             const AAX_Effect_Desc* desc,
                                             const AAX_Plugin_Desc* pdesc)
{
	...
	if (pdesc->mMeters)
	{
		cntMeters = 0;
		for (AAX_Meter_Desc* mdesc = pdesc->mMeters; mdesc->mName; mdesc++)
			cntMeters++;
		meterIds.reset(new Steinberg::int32(cntMeters));
		cntMeters = 0;
		for (AAX_Meter_Desc* mdesc = pdesc->mMeters; mdesc->mName; mdesc++)
			meterIds[cntMeters++] = mdesc->mID;

		err = outDesc->AddMeters (idx++, (AAX_CTypeID*) meterIds.get(), cntMeters);
		AAX_ASSERT (err == AAX_SUCCESS);
	}
	...
}

//------------------------------------------------------------------------
static AAX_Result GetPlugInDescription (AAX_IEffectDescriptor* outDescriptor,
                                        const AAX_Effect_Desc* desc, const AAX_Plugin_Desc* pdesc)
{	
	...
	// Effect's meter display properties
	if (pdesc->mMeters)
	{
		for (AAX_Meter_Desc* mdesc = pdesc->mMeters; mdesc->mName; mdesc++)
		{
			AAX_IPropertyMap* meterProperties = outDescriptor->NewPropertyMap ();
			if (!meterProperties)
				return AAX_ERROR_NULL_OBJECT;

			// Support different meter types offered by AAX here
			meterProperties->AddProperty (AAX_eProperty_Meter_Type, mdesc->mType);
		}
	}
	...
}
  1. Populate and expose effMeters[] array in your aaxentry (see e.g. againaax.cpp), note that the mName and mID members should parallel your VST3 meter parameters’ properties.
AAX_Meter_Desc effMeters[] = {
	{ "GRMeter", kGRMeterId, 0/*AAX_eMeterOrientation_Default*/, 2/*AAX_eMeterType_CLGain*/ },
	{ nullptr }
};

Let me know what you think.

And one more thing: foo21345 has found that the current wrapper implementation does not report any signal latency to Pro Tools.
Here’s my suggestion for a fix:

Add a

uint32 mLatency;

field to the AAX_Effect_Desc struct in aaxwrapper_description.h that, depending on your aaxentry implementation, can be auto-populated.

Then report that latency to Pro Tools by adding

properties->AddProperty (AAX_eProperty_LatencyContribution, desc->mLatency);

in line 1026 of aaxwrapper.cpp, right below

properties->AddProperty (AAX_eProperty_CanBypass, true);

I see some of these suggestions were merged/reworked into the new SDK release. I’ll check out the new version’s aaxwrapper asap, but I assume this means that this thread can be marked as solved.

EDIT: I see that latency reporting has not been added. I’ll try to provide a suggestion for a patch asap.

Yes please submit for each issue a different post or use Github PR

Thanks for clearing that up, Yvan. I was just about to create a PR for the latency issue from a fork, but then found that all the subfolders of the VST3SDK are empty after cloning. It’s the same on GitHub when trying to browse the code. Am I missing something here? The same worked fine in the VSTGUI repo. I am under the impression that the subfolders’ contents haven’t been added correctly.

you have to clone each folder you need…

https://github.com/steinbergmedia/vst3sdk.git
https://github.com/steinbergmedia/vst3_public_sdk.git
https://github.com/steinbergmedia/vst3_pluginterfaces.git
https://github.com/steinbergmedia/vst3_cmake.git
https://github.com/steinbergmedia/vst3_doc.git

Cheers

Thanks. How can I fork a submodule, e.g. the public.sdk repo though?

I have added some more issues I found in the AAX Wrapper on github.

Goto: https://github.com/steinbergmedia/vst3_public_sdk

Click the [Fork] (top right corner) button and follow the instructions.

Ah thanks, I wasn’t aware that it also forks the submodules.

Maybe I got you wrong, but forking the whole vst3sdk module including all submodules is not possible (as far as I know).

Sorry for mixing things up, I wasn’t aware that there’s a vst3_public_sdk repo, I only saw the master repo vst3sdk and wondered why the subfolders (acually being submodules) were “empty”. It’s all clear now. Thanks again!