How to do continuous parameter automation with JUCE?


#1

Currently I’m working on some sort of plugin host in order to get more familiar with JUCE. Right now I’m looking into the next feature I’m going to implement and parameter automation looked interesting. However, I’m still not sure if “continuous” parameter automation is possible and if so how.

Of course I could use AudioProcessorParameter::setValue, but I can only call this before rendering the block, meaning that, with a sampling rate of 44000 Hz and a block size of 512, I can update automation with a frequency of ~86 Hz. However, this would be 0th-order interpolation, meaning discontinuous jumps at every block. What’s worse though, is that the parameter curve (and thereby the produced sound) differs significantly when the block size or sampling rate is changed.

A second option would of course be to take a (much) smaller block size whenever a plugin is currently automated, at the cost of higher CPU loads and the risk of some plugins not responding well to differing block sizes.

A final option would be some sort of MIDI solution, where automation is done through MIDI messages, but this seems far from ideal too, as I’m not sure all plugins support parameter automation through MIDI.

Does any of you have experience with the automation of plugins and how they solved this problem or am I missing something?


#2

I’m not an expert, but I’ve done some investigation of automation in JUCE plugins; see http://getdunne.net/wiki/doku.php?id=juce_and_parameter_automation, where you’ll find links to two GPL 3.0 examples on GitHub.

Your question is mainly about smoothing transitions between discontinuous parameter values. This is what the LinearSmoothedValue template class is for. The basic idea is that you use e.g. LinearSmoothedValue<float> to go in tiny steps from a start value to a target value. The question then becomes “how frequently should you step?” The answer depends on the CPU impact of changing the parameter’s value.

The template at https://www.juce.com/doc/tutorial_audio_parameter shows one end of the scale: build the stepping into your processBlock() loop and step one per sample. This obviously should only be used for parameters like gain, where the impact of changes is minimal.

The next obvious option (and probably the easiest) is to step once per call to processBlock(). This is appropriate when the cost of changing the value is medium, and when it won’t matter much how often it happens (because how often processBlock() gets called will vary depending on the block size, which is usually set by the DAW host).

A final choice can be seen in my synthesizer example SARAH (look at the function SynthVoice::renderNextBlock()). In SARAH, modulating harmonic shaping (“filter”) parameters is very expensive, so although I step the parameter values once per sample, I only apply changes every 441 samples (typical sampling rate 44.1 kHz / 441 = 100 times per second).

I hope these links will give you some ideas, and that others with commercial plugin development experience will weigh in also.

-shane


#3

The original poster is developing a host, not a plugin, so he doesn’t have control over how the hosted plugins deal with the parameter changes.


#4

With the JUCE plugin hosting, it is not. While some plugin formats like VST3 support “sample accurate automation”, JUCE does not have that implemented when making plugins or hosts. (Additionally, even if the JUCE plugin hosting did support the sample accurate automation systems, it’s still up to the hosted plugins to also implement that properly and many don’t.)

There is no generally applicable solution to the problem. Using small processing buffers on the plugins probably becomes closest, but like you suspected, some plugins might increase their CPU usage when doing that. So you probably should make the plugin processing buffer size configurable by the user. If they have plugins that work fine with tiny buffers, then they can allow that and if they have plugins that do not deal with small buffers nicely, they can choose to use a larger buffer.

Sending MIDI CC messages into the plugins would work with just a tiny tiny fraction of available effects plugins. Properly implemented virtual instrument plugins should support them, but all probably don’t.


#5

The variable block size technique seems to be becoming more popular with a number of DAWs, off the top of my head I believe Cubase does this for VST 3 plugins, Final Cut Pro certainly does this, as does SADiE, Reaper likely has an option for it (like it does for everything!). The general rule is they will never give a buffer size greater than buffer size but they could give any size smaller than the buffer size. So if you go with this method you wouldn’t be the only ones.


#6

Thanks everyone for your replies so far, a bummer that it’s not supported by JUCE.

Currently I’m looking into JUCE’s source to see if I might be able to implement it myself, although I’m afraid that this might be a bit over my head, so I might focus on some other things first. Still, does anyone have any resources regarding this? Google doesn’t really offer much information, but from one I gather that with VST3 it is implemented in some sort of list (stating at which sample the parameter should change) that is sent while rendering the audio (https://www.kvraudio.com/forum/viewtopic.php?t=490162). The other is an old JUCE post (from June 2007 (!)), where Jules states that it is not supported, but that he should look into it at some point (How does sample accurate host->plugin automation work?). Apparently, Jules likes to plan ahead very far :wink: (or, more likely, too busy implementing all the other awesome stuff).

Or better yet, is there anyone who is also interested and would like to offer some help with this?

@the JUCE team, are there any plans to implement sample-accurate automation in the foreseeable future?


#7

Just don’t forget that your efforts may be wasted due to the hosted plugins themselves not supporting the sample accurate automation system. (For example in VST3 it’s trivial for plugins to just ignore properly handling the automation data from the host.) So be prepared to be disappointed with the results…


#8

I had a read into that as well, but decided against it, because I think I would have needed to change the signature of processBlock, adding a list of parameter changes. And I think this change has to come from juce, as it affects all plugin’s code.

I think the matter is, how many users will take profit of that, and for 90% of the plugins one value per block is good enough (sure, at 512 samples it is quite audible).
With MPE they took the lead for new features, because they have the hardware for it. Hope, that they take the lead on that topic as well…


#9

I had a read into that as well, but decided against it, because I think I would have needed to change the signature of processBlock, adding a list of parameter changes. And I think this change has to come from juce, as it affects all plugin’s code.

Yeah, that was another reason which made me wonder whether or not this would be feasible. However, optional parameters giving something like processBlock(AudioSampleBuffer& buffer, MidiBuffer& messages, ParameterAutomationList& automation = Some sort of empty list ) or even simply overloading the processBlock method seem like solutions that shouldn’t break too much, if anything at all. Or might there be some other adverse effects which I’m missing at the moment?