JUCE 9.0.1. A VST3 host delivers a parameter’s automation as a queue of (sample offset, value) points per block. JuceVST3Component::processParameterChanges takes getPointFromQueue (paramQueue, numPoints - 1) - the last point - and sets the parameter once for the block, so a plugin that could render the curve at sample resolution (the host delivered it) steps it once per block, and a ramp becomes a staircase at the block rate.
Proposal (patch attached):
AudioProcessorParameter::setValueAt (int offsetSamples, float value), virtual, defaultsetValue (value), so nothing changes for a parameter that does not override it.- The VST3 wrapper walks the whole queue and calls
setValueAtfor every point, in order; the last point still ends insetValueas before, so the parameter’s value after the block is what it was.
The AU and other formats keep their behaviour (AU’s kAudioUnitProperty_ScheduleParameters / AUScheduledParameterEvent would be the counterpart - ScheduleParameter in the AU wrapper could feed setValueAt with the event’s eventValues.immediate.bufferOffset, which the patch does not cover).
The two hunks are small and backward compatible; the plugin side decides per parameter whether offsets mean anything to it.
The patch against 9.0.1:
diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp
index f758dc36ed..fc3967afdf 100644
--- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp
+++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp
@@ -3531,9 +3531,15 @@ public:
}
else
#endif
- if (const auto change = getPointFromQueue (paramQueue, numPoints - 1))
+ if (auto* param = comPluginInstance->getParamForVSTParamID (vstParamID))
{
- if (auto* param = comPluginInstance->getParamForVSTParamID (vstParamID))
+ // the whole queue, not only where it ends: a parameter that
+ // reads the offsets renders the curve within the block
+ for (Steinberg::int32 point = 0; point < numPoints; ++point)
+ if (const auto change = getPointFromQueue (paramQueue, point))
+ param->setValueAt ((int) change->offsetSamples, (float) change->value);
+
+ if (const auto change = getPointFromQueue (paramQueue, numPoints - 1))
setValueAndNotifyIfChanged (*param, (float) change->value);
}
}
diff --git a/modules/juce_audio_processors_headless/processors/juce_AudioProcessorParameter.h b/modules/juce_audio_processors_headless/processors/juce_AudioProcessorParameter.h
index 3007ea7cd3..9df5a4d083 100644
--- a/modules/juce_audio_processors_headless/processors/juce_AudioProcessorParameter.h
+++ b/modules/juce_audio_processors_headless/processors/juce_AudioProcessorParameter.h
@@ -128,6 +128,16 @@ public:
*/
virtual void setValue (float newValue) = 0;
+ /** Called once per automation point of the block about to be rendered, on the
+ audio thread and before processBlock, by a format and wrapper that carry the
+ offset of a change within the block (VST3 parameter queues, AU scheduled
+ events, AAX). A parameter that needs only one value per block ignores it and
+ reads setValue as before.
+
+ The value passed will be between 0 and 1.0.
+ */
+ virtual void setValueAt (int /*offsetSamples*/, float /*newValue*/) {}
+
/** A processor should call this when it needs to change one of its parameters.
This could happen when the editor or some other internal operation changes