Sample accurate parameter automation?

So, If I have 100 parameters, will I need to call getNumCurvePoints() 100 times per processBlock? (once per parameter). even when no parameters have changed?

Because the alternative I’m suggesting is a list of events. Where you make one quick check (is the list empty) and if so you have no further checking of the parameters.

Hope that question makes sense. (re the efficiency of polling 100’s of parameters over and over - vs having a small list that requires work only when a parameter has actually changed)

5 Likes

I understand, but a quick check if you have something to do is the same as a quick check if there is nothing to do.

By the way, I was reading a bit further in the VST3 docs, and in case the value didn’t change would even return 0 curve points, not one as I thought it would. This way you will have the exact same number of operations.

The only difference I could see is the overhead of copying the curve points when the user didn’t intend to use sample accurate automation.

Since the Plugin API in case of VST3 already uses this format, it makes sense to me simply exposing that data to the user. It might be different in other formats, I haven’t checked CLAP for instance, but JUCE doesn’t support it yet anyway…

FYI, Audio Units pass a list of parameter change events (in the same list as the MIDI events). Each of these events says: at sample time T in this buffer, parameter X should ramp to value Y over Z samples (or: parameter X should change immediately).

3 Likes

That’s great, thanks for that information. Should be easy enough to convert into a common format then. The information if is should ramp is given in the parameter’s isContinuous() flag already.

In the case of AU, I think that’s up to the host to determine. It probably won’t tell a non-rampable parameter to ramp, but it could tell a rampable parameter to jump to the new value immediately. It’s two different events, see AUParameterEventType. (When I experimented with this a while back, Logic didn’t seem to bother sending these events at all, but REAPER does.) Anyway, it seems like VST3 handles this in a very similar manner. Also, the AU ramp is assumed to be linear.

I’m not sure if we should take the VST3 standard. I also see some problems when we have to check the parameters for changes. There are also questions about how this works when you are using the ValueTreeListener to update your parameters also for the DSP code.
We have a plugin with >10000 parameters. I don’t think I can check them all when the buffer size is 32 samples and I like to have the DSP code decoupled from parameter value classes.
At the moment we have to use the listeners to update also the DSP parameters. This is really annoying because it is called from different threads at any time.

The solution that the JUCE CLAP extension implements is a callback that is also called in the processing thread for every event just before the audio processing starts. It looks like this:

void handleDirectEvent(const clap_event_header_t *event, int sampleOffset)

This callback handles all event types in one place. This means we get parameter changes, midi and note expressions all in one single place. This makes things super simple and it does not add CPU load when no parameter is automated. I like this idea, especially if we want to go for sample accurate events. We already have this for MIDI, why can we not use the same list also for automation?

This are the types the event can have:

For JUCE it would be a good solution to pass them into the process-block function directly. We can still call the old process method with the midi-only events and run them parallel. We could mark it as deprecated for the next 20 years.

I’m sure we could also simplify the MPE implementation and don’t have to find out by ourselves what parameters have changed or listen to changes also for parameters used in DSP code.

The current API does not fit the needs of the current time anymore. We have much more than MIDI events and audio data and we want to be sample accurate. I think we should make some bigger changes…

Edit: I think we should also have a look at the CLAP features and implementation when we add support for sample-accurate automation. Would be great to see a JUCE CLAP wrapper in the future. Other companies using CLAP as a basis have extensions for the other formats.

1 Like

Hi, keeping this topic alive.

Another important case is if you have a cyclic parameter such as azimuth in object-based audio, which goes from 180° to -180° and which will jump from 0 to 1 or 1 to 0 in a rotational movement. If you smooth that out, you wont get smooth rotations, especially with long buffer sizes.

And, well, for sample accurate automation, we might also need a concept for sending automation off to the host from the plugin, not only receiving from the host.

Does anyone know how this is solved in native plugin format SDKs?

Best,

Thomas

1 Like

The most straightforward method is to pass the automation events including the timestamps also into the processing method like the MIDI events.
Sending automation to the host could be done as we do with the MIDI data today. We could replace the incoming events in the processing method with the events we want to send to the host. The JUCE wrapper picks them up after processing and sends them to the host.

2 Likes

in GMPI the host (DAW) provides a function to set a parameter. And one of the arguments is the timestamp (relative to the current ‘block’). So the plugin does something like…

getHost()->setParameter( blockPosition, paramId, value );

in AU it’s very similar, you can also pass the ‘sendingObject’ (the plugin processor) as additional information for ‘listeners’ (e.g. the GUI).

OSStatus AUParameterSet(inSendingListener, void *inSendingObject, const AudioUnitParameter *inParameter, double inValue, UInt32 inBufferOffsetInFrames);

in VST3 it’s a little more elaborate. The plugin assembles a list of all the parameters that need to be updated, and passes that back to the DAW at the end of the processBlock.
You put one entry in the list for each parameter that changed, and each parameter entry has a nested list of ‘points’ that represent sample values/timestamps. So pretty much similar concept to the others, only implemented differently.

tresult addPoint (int32 sampleOffset, ParamValue value, int32& index /*out*/);

So all cases boil down to informing the DAW:

  • the new parameter value
  • the sampleOffset at which time it changed
3 Likes

Great, how is it with AAX?

OT but filtering angles is actually an interesting math topic on its own (circular statistics) :upside_down_face:

@reuk Is there something in the pipeline regarding sample accurate automation? Would be good to know… Or some way to get it through the wrappers going around the juce abstraction?

No concrete plans. When I get time between bugfixes my current longer-term project is investigating MIDI 2.0 support for the framework. That will probably necessitate changes to AudioProcessor to expose messages in Universal Midi Packet (UMP) format. My current plan is to investigate adding single ‘event’ entry-point for both UMP and sample-accurate parameter events as part of that work.

15 Likes

Hi @reuk ,

i read on the juce homepage that one of the features for Juce 7.1 will be MIDI 2.0. Will sample accurate parameter events be part of that, as you said in your last post here?

Best,

Thomas

The JUCE 7 announcement states that MIDI 2.0 support was scheduled for JUCE 7.1, but we have been at the mercy of the release schedules of the operating systems we support. Microsoft is only just releasing details of their implementation, and as such this has pushed our schedule back dramatically. The possibility of including sample-accurate parameter changes as part of this work will likely not be evaluated before JUCE 8.

And Juce 8 will come when? :smile:

We’ll announce some of our roadmap with the results of our user survey

2 Likes

@t0m Are there any news regarding the plans with sample accurate automation?

I’m afraid not. We’ve not yet put MIDI 2.0 support into AudioProcessor.

Hi all, any news on this one?