What is the best way to update treestate (automation) for a synthesizer?

Hi, this is my first post on Juce forum, so excited!
I’m currently building a synthesizer that has around 150~200 sliders/knobs. When I load it in my DAW, I find the CPU load unreasonably high (almost 30% for a single instance). After I commented out everything in renderNextBlock in my SynthesiserVoice, I noticed that the CPU load is still around 20%, then I guess I know the reason.
The way I synchronize parameters among Editor, Processor and Synthesizer is to create treestates and attachments (that links editor and processor), but in order to link the processor with synthesizer, I put the update code in the processBlock of processor like this:

void SynthFrameworkAudioProcessor::processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages)
{
for (int i = 0; i < mySynth.getNumVoices(); i++) {
if ((myVoice_ptr = dynamic_cast<SynthVoice*>(mySynth.getVoice(i))))
{
myVoice_ptr->setAmpAEnvADSR(
(*treeState.getRawParameterValue(“ampAenv_A”)),
(*treeState.getRawParameterValue(“ampAenv_D”)),
(*treeState.getRawParameterValue(“ampAenv_S”)),
(*treeState.getRawParameterValue(“ampAenv_R”))
);

I guess update EVERYTHING in processBlock is definitely not good, but how can I sync treestate in processor with synthesizer’s own variables only when the treestate has been changed?

I guess maybe
A: I should use the Slider::Listener method
B: I should discard all variables (xyz) in synthesizer and use processor.xyz instead

Any suggestion? Thank you very much!

The Slider::Listener is not an option, since in the processor the Slider might or might not exist.

The best way is to have a member variable pointing to the instance returned by getRawParameterValue(paramID). Note that the return type recently changed, it returns now a std::atomic<float>*, which is better, since it takes care of potential thread issues (latest develop).

// member:
std::atomic<float>* ampAenv_A = nullptr;
// in constructor:
ampAenv_A = treeState.getRawParameterValue(“ampAenv_A”);
// in processBlock:
myVoice_ptr->setAmpAEnvADSR (ampAenv_A->load(), ...);

A word about CPU load:

  • only measure in release builds
  • use a profiler to see, where the time is lost
  • look for cache coherency (iterate samples inside the channel loop)
  • and many more

Hope that helps

1 Like

Thank you so much for the reply!
So I kind of solved the issue by making my processor public inherit AudioProcessorValueTreeState::Listener and update variables of synthesizer in the processor’s parameterChanged method. Pretty much like Slider::Listener but a VTS version! And ya, I totally removed any Slider::Listener stuff since it won’t be needed once I choose to make sliderattachment to vts. Thank you so much for the advice!

Hello Brandon, welcome to the forum

to ease reading your future posts, remember to format your code as such.
It is easily done by selecting the code snippet while editing and clicking the button with the symbol ‘</>’ on it.

That is the same as adding three backticks ` before and after your code, or also adding four spaces before every line of it.

1 Like