Samplerate expression as constant
I have a counter that I want to increment by 1/samplerate to prevent multiplying the count limit by samplerate, but if it's not constant it'd be a more expensive operation.
I think I saw somewhere that a parameter without any inputs is treated as a constant value. Does that extend to expressions as well, like my example?
You can do it in one step by dividing the amount to add to the counter by samplerate costant. Did I understand your problem ?
Btw, to have a number costant you can just create a new operator box and put the value inside. No need for parameter
Hi Paolo. Yeah, instead of multiplying my count limit by samplerate, I am currently sending 1/samplerate into the amount to add inlet on the counter. My question is: is that calculated once and kept at as a constant, or does it calculate 1/samplerate every sample?
I think at every sample. In case you want to calculate it once, just copy the result of the operation from somewhere else as a costant.
Or do it in Max
Oh okay, unfortunately this is for my Owl pedal, so I'm constrained to doing everything within Gen. I do want to calculate it only once rather than every sample, so how do I go about copying the result as a constant like you suggested?
here is a way to go, using codebox. whenever the initialisation is done, it will never recalculate it unless you change the samplerate as it recompile the gen patcher.
Yo. This is awesome! Thanks bertrandfraysse!
So if I'm reading this right, would it make sense to initialize all of my constants in this if block? Why is the History class best suited for this task? I've never worked with the History class before, and with a cursory search I'm not finding much (maybe the name is too general). Is there more documentation on it?
it's strange you overlooked History, it's almost everywhere, it allows you to produce feedback loops !
(with only one sample delay) this is actually what made me dive into gen~ so History is allmost the first object I discovered. History allows you to store a value too.
and yes, I think it's a good idea to initialise all you complex constants.
Ah! So the History class and the history object are one and the same. I knew that it delayed your signal by one sample, but I didn't know you could use it as data storage as well. Thanks!
I'm going through and trying to make old patches of mine more efficient since a lot of them were getting right up to the 100% CPU marker on the Owl pedal. I made a gen subpatch that takes in an abs(noise()) and outputs a random number within a range at random times within a range. I can chain the subpatch using a history object as well, and since I use it four times in my main patch, I wound up with like 12+ history objects. Not knowing how expensive the history operation is under the hood, I was trying to make the subpatch as efficient as possible.
so, are you meaning the history object is expensive ?
since I program with codebox, a lot of expensive but of short period tasks have been optimised using if loops.
as an example, if you need to generate a random number, you generate and store it with an History, this way the abs (noise()) function is only computed once.
It's a bit like a sample and hold but removing all the calculations when it's not active.
I'm meaning that I have no idea how expensive the history object is, so using it 12+ times is probably not the wisest.
This has been very helpful, though I'm not sure how much I can use it, since it seems subpatches don't have the scope to access constants in their parent gen patch. I'm trying to make a subpatch (included below) more efficient. Even if it had the scope of it's parent's state, it still seems tricky since I need two random numbers each trigger. Or, at least, I'm lost on a good way of going about it.
I think you can get rid of all history objects, except the one after the counter carry flag. If they are not in feedback loops they don't add any delay, so they are like transparent.
I don't think history objects are expensive, it's more on the memory side than the computer cycles I think...
The most expensive here are / and * but I'm totally not an expert.
I made a codebox version which uses way more history objects but only compute the output and countLimit on every carry count flags. It's around 1.7 time faster, but you need to get used to coding with codebox which at first sight is a bit scary, but in the end, you can program faster, cleaner and use less CPU
This is really nice, and I'm actually excited to be coding more in CodeBox. I work with C# all day at work, so I'm pumped to get into more of a C++ frame of mind. Thanks for spending time on my problem.
The only issue I'm seeing is your codebox example doesn't give the same result as mine. My example outputs random numbers in a specified range at random times in a specified range, whereas the codebox example outputs random numbers corresponding to the time range. Ex: the higher/lower the number output the higher/lower the count limit, or longer/shorter between triggers. I'm pretty sure that's where I got hung up too with all the history objects.
nice to hear that and wish you a good journey into codebox, it's deep.
Maybe here is the intended behavior ? It seem to work similarly now... but I'm not sure.
oh, I made a mistake,
hRdmPrev = hRdm needs to be the first thing to do in the if (trig) {}
otherwise, it gets the same value as the noise generator.
if (trig)
{
hRdmPrev = hRdm;
if (modeNoiseInput) {hRdm = noiseInput;} else {hRdm = abs (noise());}
output = hRdm * (maxOut - minOut) + minOut;
countLimit = hRdmPrev * (maxTime - minTime) + minTime;
trig = 0;
}
by the way, I like this random generator.
Thanks for all your help! I can think of some improvements, but the fact that I don't need to have two random number generators split into 12 expressions all running constantly can only be a HUGE boost.
Also, just gotta say that CPU measure thing is badass. Where'd you get it from?
as an example, if you need to generate a random number, you generate and store it with an History, this way the abs (noise()) function is only computed once. It's a bit like a sample and hold but removing all the calculations when it's not active.
@ bertrandfraysse I can't thank you enough. I didn't know about this possibility - now, thanks to you, I think I have resolved a huge problem in one of my patches. I thought about a if/latch method, but without success.
Hi didn't see this thread earlier. A few points:
1. gen~ tries its best to optimize code out of the sample loop if possible. Simple operations whose inputs are constants will also be constants. Simple operations whose inputs are 'param' objects will only update a block rate. For example, it totally makes sense to compute 1/samplerate and pass that into 2nd inlet of a multiply object, because division is expensive while multiplication is cheap. But you probably don't need the codebox/history stuff for this, a simple patcher will already do it for you.
If you are curious, send 'exportcode' message to the gen~ and look at the C++ it generates:
// block-rate calculation of inverse samplerate
t_sample rdiv_3 = safediv(((int)1), samplerate);
// the main sample loop;
while ((__n--)) {
const t_sample in1 = (*(__in1++));
t_sample mul_2 = (in1 * rdiv_3);
t_sample out1 = mul_2;
// assign results to output buffer;
(*(__out1++)) = out1;
};
2. Note that, counter-intuitively, quite often, adding an if() block can make performance worse, rather than better; CPUs tend not to like "branchy" code.
3. History is pretty cheap -- it's just a float in persistent memory.
4. There's a performance measurement example in the gen~ examples folder, worth looking into if you want to optimize.
Graham
BTW that example will look slightly different when embedded in Max, where we can optimize even more, since we can just regenerate code if the samplerate changes:
// pre-computed 1/samplerate as a constant:
t_sample rdiv_3 = (((int)1) * ((t_sample)2.2675736961451e-05));
// the main sample loop;
while ((__n--)) {
const t_sample in1 = (*(__in1++));
t_sample mul_2 = (in1 * rdiv_3);
t_sample out1 = mul_2;
// assign results to output buffer;
(*(__out1++)) = out1;
};
Thanks for the tips, Graham!
Regarding your assertion on if statements: If that's so, would that mean a Selector object is doing heavy lifting under the hood? I tried a few different C language-based syntaxes for a switch statement, and it seems GenExpr doesn't support them.Good to know about History, though.
As for your second post: I'm unfortunately limited to Gen for the the purpose of running the code on an Owl pedal, but certainly interesting to think about from a plain coding perspective!
C-style switch statements also work in codebox (and in the [expr] object too, it's the same language). Syntax looks like this example:
out1 = in1 ? 1 : 0;
PS. If you want to look 'under the hood' to see what operators are doing, use the 'exportcode' message and have a look at the code!
Will do! Thanks for helping! So it seems that it works the same as a ternary operation when you only have two cases. How do you handle multiple cases?
Chain `em!
out1 = a ? x : b ? y : c ? z : 0;
That syntax is so weird, but I'm digging it! I'm glad it's a thing. Thanks, Graham!
same syntax works in C, Javascript, etc. etc
I never knew! It's more a one-liner else-if chain than a switch statement, since you have to restate the condition every time, but it does make it more terse, for sure. I guess I just prefer switch statements for the annoying situation when I have 10+ cases and realize I need to change the variable name.
Fair enough. It all ends up compiled down to the same machine code in the end.