Watch for param value changes

What is the best / recommended way for watching param value changes?

up until recently, i have just started off each process() call by setting a param’s associated variable to the current param value. but i recently learned at least some devs only do so every X process() calls.

something along the lines of this:

int maxCount = 64;
int processCount = 0;
void updateParams() {
    // update params here
}

then at start of process():

processCount++;
if (processCount == maxCount) {
    updateParams();
    processCount = 0;
}

hope this helps :grinning:

1 Like

From the phrasing of the question, I suspect you’re looking for some event/method call that occurs when a parameter changes. There is no such event; as the answer from @alefnull implies, watching for parameter changes is your code’s responsibility.

@alefnull 's suggestion is a common way to lower the CPU load of doing that watching in a way that a user wouldn’t notice; a sub-millisecond delay in the effect of changing a knob is unnoticeable by humans.

2 Likes

Another trick:

If you want to call updateParam() custom function every 64 DSP frames, you can do it from process() method like this:

if ((args.frame % 64) == 0)
   updateParams();

args.frame is an 64-bit integer (int64_t) incremented by 1 on every DSP frame, provided by API.

5 Likes

This is really neat! I didn’t know that this was there. Super useful

Also I learned something. Forever and a day I’ve been using x & 63 rather than x % 64 since % is a more expensive operation but I went to godbolt to see if that’s worth it.

turns out that with an int64_t % 64 is two instructions more expensive than & 63 but with a uint64_t it is not. Neat.

Thanks @Ohmer - learned stuff!

3 Likes

turns out that with an int64_t % 64 is two instructions more expensive than & 63 but with a uint64_t it is not. Neat.

Interesting… Thank you Paul :+1:

1 Like

yeah so to give a bit more context; I am building a clock divider, and want to watch for changes in the division param, in order to reset the timer when a user changes the value.

1 Like

If you are developing, you can use the DEBUG() function to log parameter values to the log file, but those calls need to be removed before you release to the library. DEBUG() is invaluable to me during development. That does not answer your exact question, but it is a useful tool.

Don’t log per sample though!

1 Like

If I understand what you are asking, I usually define a flag variable that can be seen by both the clock and the divisor input handling such that the divisor process change sets the flag when needed and the clock process handling reads it, takes action and resets the flag to false.

1 Like

thanks, that might work :slight_smile:

I have a slightly different take for these situations. I define a module oldValue attribute that is initialized to an impossible value. In my process method I check if the current value is different than the oldValue, and if so, I update the oldValue and do whatever processing is needed.

So in this case, define oldDivisor, and within Process (pseudo code)

if (divisor != oldDivisor) {
  reset timer
  oldDivisor = divisor
}
1 Like

It’s an elegant one, and I also didn’t know of args.frame, but there could be a problem once many developers use the trick, in that all the extra code guarded by the %64 will occur on the same frame for all modules, which could result in a non-trivial CPU consumption spike.

In my own implementation of a process divider for Impromptu, I start the counters with a random value, so as to stagger the occurences of %N == 0, and thus balance out the extra computing load across frames.

6 Likes

oh, clever! i remember noticing the existence of args.frame at one point when i was first exploring the API, but had no idea what it might be useful for. this makes perfect sense! thanks for the tip :stuck_out_tongue:

EDIT: and @marc_boule’s tip about randomly offsetting the counter to avoid “colliding” with any other modules using the same trick - also super clever and useful!

3 Likes

Great observation to add jitter to the control rate computation to avoid spikes! I’m definitely going to implement that.

I’m thinking this trick (off the top of my head):

    if (0 == ((args.frame + this->id) % CONTROL_INTERVAL)) {
        ...
    }

Module ids are guaranteed to be unique (and happen to be well-distributed in the current implementation), so this gives a good heuristic for getting modules to do their control rate processing at different time steps. There can be collisions due to the reduced range of the modulus, but to do better would require a central broker to hand out unique offsets within the interval.

4 Likes

I also do this.

1 Like

Bit off topic. But: Does rack ever run without an end chain block? That is the audio drivers all have a block size to fill and that’s usually at least 32 if not 64. Not adding latency in your module is important but if everyone triggered on sample 64 of a 128 size block vs 63 64 and 65 the aggregate cpu per realized block would be the same right? And that block under fill is what causes xruns.

Honestly curious if any of the deployments have a sample 1 end of chain block size.

1 Like

I assume your question is general rather than to me. All of my modules are sequencers or CV generation or modification, with no audio generation or manipulation, so I am unfamiliar with the question.

1 Like

Don’t know if this is relevant, but the VCV Host modules works with blocks instead of individual samples.

1 Like

Yeah some modules internally have blocks including surge in some cases

My question was more whether the output block is ever sample 1.

And indeed it was meant for the thread not for kchaffin directly. The reply semantics on this forum are way harder to master than writing all of surge for rack apparently!

4 Likes

Good point, and definitely an interesting question!

1 Like