Performance Question: Processing an array of buttons?

Hello!

I have an array of buttons. Let’s say…

  enum ParamIds
  {
    ENUMS(BUTTONS, NUMBER_OF_BUTTONS)
    NUM_PARAMS
  };

And I have an array of Schmitt triggers, like:

  dsp::SchmittTrigger button_schmitt_triggers[NUMBER_OF_BUTTONS];

In my main process loop, I iterate over the buttons to see if any have been clicked on using:

for (unsigned int button_id = 0; button_id < NUMBER_OF_BUTTONS; button_id++)
{
  if (button_schmitt_triggers[button_id].process(params[BUTTONS + button_id].getValue()))
  {
    // do stuff
  }
}

It’s not vitally important that button processing happens immediately. Users wouldn’t notice a very small delay.

:grey_question: Should I consider creating a custom button class which overrides onClick, and handling the “stuff” that happens within the onClick event instead of polling it from within the module’s main process() loop?

The Schmitt trigger logic is extremely efficient. Unless you have hundreds of buttons, I doubt this matters much. Is your goal to cut down on CPU usage in your module? I would start by commenting out different parts of your process function (even if it breaks functionality) and measure CPU usage each time. Use experimental data to identify your bottleneck. Even the best experts are often surprised when they find out what’s really slowing down their code.

Yes, exactly.

This is excellent advice. :+1:

Possibly the easiest (and probably laziest) thing to do, without knowing anything about the “stuff” that gets handled, why don’t you just start by polling the buttons less regularly and seeing how this affects CPU usage? I would start with something “extreme”, like polling them only once per second, and see if that makes any difference with regards to your CPU usage. Then you could increase the frequency to see if you can get it to a point where you’re happy with the CPU usage and polling frequency.

2 Likes

I have always done this kind of polling at every four process calls, sometime less. It’s the single easiest thing you can do to make your stuff faster. Usually.

1 Like

I was able to shave off a few CPU percentages just now by changing

return(this->step_amount * exp2(pitch_cv_input));

to…

return(this->step_amount * rack::dsp::approxExp2_taylor5(pitch_cv_input));

This was a nice find, since that code is shared amongst most of my modules. I’m going to see if I can find any other expensive math computations!

yeah don’t call exp every sample

but really don’t call anything every sample except things which need to be sample accurate. let me echo the advice above that if your user won’t mind a 0.01 second delay in the button responding you can process or at least respond every 100 samples easily.

1 Like

Yes, but sooooo many plugins do everything on every sample! That one of the reasons there is an LFO in the library that clocks in at 10% CPU!

2 Likes

There is a short article on this that I wrote a few years ago. Most ppl seem to like it. Unfortunately my VCV stuff is no longer public, but there’s a copy here: SquinkyVCV-main/efficient-plugins.md at master · kockie69/SquinkyVCV-main · GitHub

2 Likes

Thanks @Squinky! I’ll read through that a little later tonight.

Good to know. If I need to, I’ll temper my module’s responses to user input. I’ll give it a shot and see if it would save any CPU.

one trick i’ve used is to run the schmidt trigger every sample but || it onto a bool

that way if your sample rate is > the open/close time you won’t miss (which is the risk if, say, you sample every second and only run the trigger every second)

so if you have something super low frequency you may want to cache the trigger or what not.

all sorts of strategies. But basically as squinty’s doc and lots of the performing modules show: don’t do stuff every sample if you don’t have to.

2 Likes

One thing to note - there is dsp::BooleanTrigger for detecting bool changes of state, slightly simpler and appropriate for this use case (I think!).

1 Like

Like others have said, don’t do everything every sample. A long time ago (in a vst synth) I did stuff every 8 or 16 samples… now I’ve changed it to be based on time (instead of samples).

So I’ll have some stuff fill up a buffer and deal with it when buffer is full. I’ve tried using a whole millisecond or a quarter of a millisecond for the buffer.

For triggers they are expected to be on for a whole ms. (from VCV Manual - Voltage Standards) " Trigger sources should produce 10 V with a duration of 1 ms ." So you don’t have to sample triggers every frame…

Thanks a ton for this! I had no idea that this existed. :slight_smile: