Loading an abstraction and its params in a codebox

Lionel's icon

Hi

I've got a GEN patch with multiple instances of an abstraction, and i route several params to each instance using a bunch of setparam objects.
Now i would like to load the abstraction in a codebox, i already know i can do this by writing:
out1 = abstraction_name(in1, in2, in3, in4); for example.
But for some reason i can't connect the setparam objects to the codebox. I tried declaring the params in the codebox before calling the abstraction, which doesn't seem to work either.
Does anyone know the proper way to do this ?

Thx by advance

Graham Wakefield's icon

If your abstraction was called "addx.gendsp" and it had a params called "x", "y" then you can invoke it and set the params like this:

out1 = addx(in1, 3, x=in2, y=10);

Lionel's icon

Thx for answering
So if i understand correctly, instead of the setparam objects, i just need to create as many inputs to the codebox as there are params in the abstraction ?

Gregory Taylor's icon

One meta-question question here concerns what you expect to gain by loading the abstraction into the codebox. Except in certain limited circumstances, codeboxing won't be any faster or more efficient than a patch that uses operators.

The gen~ object compiles the contents of patcher into a language called GenExpr, which the object turns into target code necessary to perform its calculations (that GenExpr target code is what you see in any gen~ patcher’s Code tab). From this, the gen~ object then generates and compiles native CPU machine code on-the-fly. Everything – whether it be operator-based patching or codebox operators - gets merged into a single representation.

Unless you're doing procedural stuff [if(), for(), etc.] or you want explicit ordering of read/write operations (delays, working with data/buffer operators), or you're in love with the require message, it's not going to save you much of anything. If any of these conditions apply, then have at it. Otherwise, it won't buy you much.

Gregory Taylor's icon

Oh. Or unless you really like writing command-line code, too. :-)

Graham Wakefield's icon

@Lionel: no, your patcher can have params as normal. Attaching example here:

addx.gendsp
gendsp 2.10 KB
save as addx.gendsp

main.maxpat
Max Patch
save as main.maxpat

Graham Wakefield's icon

@Gregory: Yes.

Basically, abstractions in gen patching are like (and also can be used as) functions in gen codeboxing. The advantage of defining a common routine as a .gendsp abstraction, rather than a .genexpr function, however is that it can be used in both patching and in codeboxing. (Add that to my list of why patching in gen is often preferable to codeboxing...) It's actually a really powerful feature of gen, and one that is massively under-used!

For example: say you made a simple abstraction for an equal-power crossfader. You might want to drop that into a few places in a regular gen patch. You might also want to invoke that as a routine somewhere inside a if block or for loop in a codebox. Having it as an abstraction lets you do both.

Graham

Lionel's icon

Hi again

thx a lot, this is a very interesting debate.
I don't expect faster processing, there are 2 important reasons i see for trying this:
1 : Being able to save CPU by killing inactive instances on the fly.
2 : Being able to load different versions of the abstraction, by adressing them with a dedicated param.

@Graham
Is it possible to call the abstraction within an if/elseif structure ?
For example, using a param with positive integer values : 0 =no abstraction loaded or bypass (lower CPU consumption), 1 = abstraction_1 gets loaded, 2 = abstraction_2 gets loaded etc...
If it works, then I think there is interesting stuff to be done in combination with a matrix mixer inside the parent GEN patch...

@Gregory

I was pretty averse to the codebox in the beginning (that's why i bought MAX instead of diving head first into C++ or whatever :-) ), but the more it goes the more i use it.
Still prefer patching with cables over anything but when you start building complex patches with multiple instances, or very complicated chains of conditionals, you start to think differently.

One of my prefered features in GEN is that i can choose between the best of both worlds.

Regards
Lionel



Graham Wakefield's icon

There's no easy solution, simply because of the way CPUs work, but there are some possible options.

First, if you mean invoking different abstractions in if/elseif etc. blocks, it might not actually save you as much CPU as you expect, and in some cases won't save at all, because of the way modern CPU architectures work. In fact I've seen examples of huge if/elseif/elseif etc. statements in which each section has a different algorithm, that were actually slower than running them all continuously and just using a [selector] to choose the output, as the latter is more predictable to the CPU. You might want to look at your algorithms and see if that might work. Anyway, for a matrix router it is probably better to have all inputs running all the time and just use a data (or buffer) store to handle the matrix weights.

(A note on that too: "very complex chains of conditionals" are also the kinds of things CPUs hate... often finding a way to express the problem that reduces the ifs (especially to top-level ones) can make a worthy improvement in performance. Use math rather than if whereever possible :-))

Second, there's no way to programmatically dynamic load different abstractions in gen~ code in response to a param or any other way within the patcher. gen~ is intentionally designed to be relatively static with limited control flow for better audio performance. The patcher mostly runs at per-sample rate, meaning that the full patcher is traversed within each single sample (as opposed to operating in blocks of samples like most audio environments), and that's why it can be both faster and more flexible. Swapping out algorithms at this level would involve a dynamic instruction jump that would murder performance within the sample loop (and also be far more likely to cause a crash).

If this is just to make debugging easier, then I can suggest two alternatives. You can of course do the usual turning on and off algorithms at the MSP level -- i.e. having different top-level gen~ patchers that refer to different abstractions. Probably that isn't what you want either.

But there's also another way you might consider, so long as you don't expect to be switching out algorithms very often. A gen patcher is amenable to patcher scripting like any other Max patcher, which is very cool. So you could use that to e.g. rename specific objects in a gen patcher to refer to a different abstraction. The caveat is that this will actually trigger a recompile of the entire patcher, because it is just the same as if you had manually edited it. That is, there may be a brief audio dropout (unless you have sufficient mixer crossfade) and there might be some state discontinuity (e.g. oscillator phase). But invoking a script is a lot easier than going through and editing a ton of abstraction names every time you want to A/B something. Check out the gen~.patcherscripting example in Max.

Finally, I might add that despite being one the main developers of gen~, I honestly prefer to limit my use of codebox as much as possible. I know quite a few folks whose gen~ patchers are usually one giant codebox, but I prefer to use visual patching for the most part, possibly with a handful of tiny codeboxes interspersed within them when those are really needed. Visual patching lets you arrange things spatially in a very useful way, including comments and fragments of work in progress, which is very helpful when you come back to a patch weeks/months/years later ("code is more often read than written etc."). It is nearly impossible to generate a syntax error, which means you are spending more time thinking about the sonic/musical/etc. problem than thinking about language. I believe there is something very elegant in prioritizing the expression of the flow of data rather than the flow of control, and it seems to coincide with the nature of the machine better than text does. We usually get introduced to algorithms as being "like step by step recipes" but the nature of the beast, especially in audio, is far more like a modular synth or a system of waterways. And it just so happens that CPUs prefer to do lots and lots of tiny bits of math and logic than big conditionals.

Graham

Lionel's icon

@Graham

a few quotes & answers below

if you mean invoking different abstractions in if/elseif etc. blocks, it might not actually save you as much CPU as you expect
As you may have noticed i’m not a professionnal programmer at all, so thanks for all these explanations. I don't think this topic is covered in the existing GenExpr documentation.

for a matrix router it is probably better to have all inputs running all the time and just use a data (or buffer) store to handle the matrix weights.
I’ve been doing this for complex patches, using buffers and peek/poke to transfer stuff from MSP into GEN, so i see some pros & cons here :
Pros : no limitation to input number ; super flexible (if more slots needed in GEN, create new buffer or increase channel number ; if more inputs needed for each slot, increase buffer size) ; very accurate with MSP signals, fast lfos etc… a LOT more accurate than adressing params through message box

Cons : higher CPU consumption; patching complexity in- and outside of GEN if you have lots of data at the same time.

As a developer, do you see possibilities to improve & simplify communication between MSP and complex GEN patches in future versions (mainly thinking of MSP objects here rather than number boxes sliders attrui etc…) ?

"very complex chains of conditionals" are also the kinds of things CPUs hate...
I was not thinking so much in terms of algo. complexity, more in terms of patching ergonomics : i know how you can achieve the equivalent of multiple if/then branching with selectors and >=0 and whatever, but i think a codebox is conducive to cleaner patching, and also in this case easier debugging.

You can of course do the usual turning on and off algorithms at the MSP level
I really prefer to keep audio processing in GEN as much as possible

A gen patcher is amenable to patcher scripting
I’ve been thinking about this already but i’m a lot more averse to javascript than i am to a GEN codebox :-) Wouldn’t mind taking time to figure it out though, if it’s worth it.
If i had a set of gendsp abstractions with say, 4 inputs, 2 outputs each and a bunch of identical setparams, can i target the object to make it switch between 2 abstractions without breaking existing connexions ?

there may be a brief audio dropout (unless you have sufficient mixer crossfade) and there might be some state discontinuity
Correct me if i’m wrong, but this is no different from what a poly~ does when loading an MSP abstraction. I could live with that; keeping all the connexions intact in the process is what matters most.

I know quite a few folks whose gen~ patchers are usually one giant codebox,
not my cup of tea. As i said earlier, you’d be better off writing C++ externals at this point, then why use MAX in the first place ?

but I prefer to use visual patching for the most part, possibly with a handful of tiny codeboxes interspersed within them when those are really needed.
Smaller codeboxes connected together or a mix between visual patching and a few codeboxes, only when needed. Abstractions for all reusable subprocesses, though for some reason i experience a lot of crashes when moding my abstractions inside the GEN patches. Kind of irritating

the nature of the beast, especially in audio, is far more like a modular synth or a system of waterways.
Totally agree, though i don’t own any hardware and no interest in it (cost, lack of space, lack of flexibility etc…). One of the possibilities i see with such a mixing matrix in GEN is to build a flexible modular effects patch bay. Have your effects saved as abstractions, maybe put simple limiters after the audio inputs to tame feedback loops, load the abstractions in free slots of the matrix and you’d have quite a nice playground for experimentation. Was thinking about this the other day while messing around with various reverb and delay based patches, involving a set of simpler abstractions that can be moved around and connected in various ways (multi tap delays, modulated allpass chains, pitch shifters, limiters etc…)

And it just so happens that CPUs prefer to do lots and lots of tiny bits of math than big conditionals.
I guess the ultimate answer to my CPU woes is « buy a new computer now and dump this 10 year old piece of crap you've been keeping for no reason».

Thax again for taking time to answer and sorry for the lengthy post.