VST3->VST2 Wrapper Bug

My team has discovered a bug with the parameter mapping in the VST3->VST2 wrapper.

Our VST2 plugin makes parameter changes to the VST2 layer by calling into Vst2wrapper::performEdit() which uses the paramIndexMap map to find the parameter index where the change is requested. Shortly after the VST host (in our case- Ableton) calls Vst2Wrapper::setParameter() to add these parameter changes, and this is eventually propagated to our controller on vst2wrapper::onTimer().

Vst2Wrapper::setParameter() however uses the parameterMap vector to find the index to addParameterChange(). Since the parameterMap map excludes the master bypass parameter (unlike paramIndexMap) in the case when Vst2Wrapper::mBypassParameterID = 0, we are always off by 1 index every time Vst2Wrapper::setParameter() evaluates the new index.

For example if we make the following changes using Vst2Wrapper::performEdit():
ParamID = 1, value = 0.39
ParamID = 2, value = 0.316

in the next pass when Ableton calls into vst2Wrapper::setParameter(), it uses the parameterMap vector to evaluate the ids for addParameterChange() as the following:
ParamID = 2, value = 0.39
ParamID = 3, value = 0.316

This is eventually propagated to our plugin UI when Vst2Wrapper::onTimer() => controller->setParamNormalized() happens and our plugin UI starts exhibiting weird behavior as a result. This happens 100% of the time we use Vst2Wrapper::performEdit() to make parameter changes.

Our workaround is to do the following in Vst2Wrapper::setParameter(): instead of using the parameterMap vector, evaluate the id from the paramIndexMap map by finding the key in paramIndexMap which has the value equal to the index parameter and using that key as the index in addParameterChange(). We are not sure if these workarounds will cause other instabilities. Alternately, is there something that we can do during setupParameters() to fix this issue?

Attached is a modified version of the again sample plug-in that comes with the VST3 SDK that demonstrates this bug. I made the following changes to the sample project to demonstrate the bug:

  • Moved the bypass parameter to index 0 (this is the primary cause of the bug)
  • Added two additional parameters (Other and Dummy) after the Gain parameter and before the VuPPM parameter
  • Added two additional sliders corresponding to the additional parameters

Upon moving the Gain slider, the change will be propagated to the Other slider. Similarly, moving the Other slider results in changes to the Dummy slider. This is the same behavior we are seeing in our plug-in, where Bypass is also at index 0.
again-modified.zip (165 KB)

Not sure this is related, but I also got the mapping incorrect between the host and the editor controls when bypass is added as the first parameter. Resolved this by moving it by adding it after all other parameters. While this worked for most DAWs, still get mapping problems in VSTHOST when using the VST3 version. In Carla things look normal, beside one difference that when I load the VST2, the bypass and Programlist parameters don’t show up, in the VST3 they do. Waiting for the release of Cubase 9 to verify all this (Sorry can’t test in Cubase because the demo is expired & trying to avoid upgrade expenses).

yes, you´re right there is a misalignment between VST3 and VST2 parameters when bypass or program change are at the beginning of the parameter list.

We need to fix it in the setupParameters. mParamIndexMap is there to map VST3 Param ID to VST2 Param index.
Strange that we do not find it earlier… :open_mouth:

This will be fixed in next update coming soon.

Thanks for finding it :stuck_out_tongue:

You’re very welcome, and thank you! :smiley:

Would you happen to know what the correct fix is at this time? We are on a bit of a time crunch and would like to patch our plug-in until the next update is released so that we can regress some possibly-dependent bugs we are seeing.

Thank you for looking into this, Yvan. I work on the same project with Robert, and due to the urgency of our need for a fix to this bug, I have tried to dive deeper into the part of the VST2 Wrapper code and understand the uses of the two parameter maps and where the indexing is going astray. I think that I have determined the point of error, and that we were a little off with our first theory that the problem was related to which map was being used within Vst2Wrapper::setParameter.

From what I can tell of the intention in the code, it seems to me that mParameterMap is intended to map a VST2 parameter index to a VST3 parameter ID or index, whereas mParamIndexMap is intended to map from a VST3 parameter ID to a VST2 parameter index. However that is not actually the data stored in mParamIndexMap. During Vst2Wrapper::setupParameters, mParamIndexMap is actually assigned mappings of VST3 parameter IDs to VST3 parameter indices, not VST2 parameter indices. And so for this reason, I actually think that the bug manifests within Vst2Wrapper::beginEdit, Vst2Wrapper::performEdit, and Vst2Wrapper::endEdit which are the methods that use mParamIndexMap for mapping. The thing is, mParameterMap already stores the full relationship mapping for everything–VST3 parameter ID, VST3 parameter index, and VST2 parameter index–so I don’t think that there is actually any need for mParamIndexMap, but especially if it is not storing the data relationship that it is actually being used for.

Below is the patch that I am testing that I believe fixes the bug. I would love any feedback/confirmation that this fix looks correct?

I added this method:

VstInt32 Vst2Wrapper::getVst2ParamIndexFromVst3ParamID (ParamID paramID) const
{
	for (std::vector<ParamMapEntry>::const_iterator iter = mParameterMap.cbegin (); iter != mParameterMap.cend (); ++iter)
	{
		if (iter->vst3ID == paramID)
			return static_cast<VstInt32> (std::distance (mParameterMap.cbegin (), iter));
	}
	return -1;
}

and then I modified the following methods:

tresult PLUGIN_API Vst2Wrapper::beginEdit (ParamID tag)
{
	const VstInt32 vst2Index = getVst2ParamIndexFromVst3ParamID (tag);
	if (vst2Index >= 0)
		AudioEffectX::beginEdit (vst2Index);
	return kResultTrue;
}

//-------------------------------------------------------------------------------------------------------
tresult PLUGIN_API Vst2Wrapper::performEdit (ParamID tag, ParamValue valueNormalized)
{
	const VstInt32 vst2Index = getVst2ParamIndexFromVst3ParamID (tag);
	if (vst2Index >= 0)
		audioMaster (&cEffect, audioMasterAutomate, vst2Index, 0, 0,
					 (float)valueNormalized); // value is in opt

	mInputTransfer.addChange (tag, valueNormalized, 0);

	return kResultTrue;
}

//-------------------------------------------------------------------------------------------------------
tresult PLUGIN_API Vst2Wrapper::endEdit (ParamID tag)
{
	const VstInt32 vst2Index = getVst2ParamIndexFromVst3ParamID (tag);
	if (vst2Index >= 0)
		AudioEffectX::endEdit (vst2Index);
	return kResultTrue;
}