equivalent of poly~ for gen (multiplying gen subpatches)

Diemo Schwarz's icon

Hi and sorry for the probably naive question (just getting curious about gen):
Is there a way to programmatically multiply parts of a gen patch, similar to what poly~ does, while staying inside one gen instance?
Use case: a cross-modulating oscillator bank.

Cheers...
...Diemo

Ernest's icon

It's actually not so simple, because when you copy a gen subpatch, you have to connect all the params with setparams to each one. Also, it does not do multithreading. Unless you are really serious, you probably want to stick with poly!

Diemo Schwarz's icon

Thanks for your answer, Ernest! Sure, I wouldn't expect this to do multi-tasking, neither dynamic voice allocation.
Ideally, such a hypothetical multigen would generate the code in the subpatcher n times, and somehow expose params with indices or settable as a list, and make the current instance number accessible as a variable inside each instance.

I did read up on gen~ now, and find the lack of arguments for subpatchers and their substitution (#1) and generation and setting of the label for send/receive a real hindrance for this kind of genericity.

Graham Wakefield's icon

As of Max 7.3.1, you *can* add arguments to subpatchers (and abstractions) in gen~:

Max Patch
Copy patch and select New From Clipboard in Max.

Graham Wakefield's icon

Oh, and this also works on the gen~ itself:

Max Patch
Copy patch and select New From Clipboard in Max.

Graham Wakefield's icon

Another way of repeating sections is to define them as functions in genexpr and then make multiple calls to them.

Max Patch
Copy patch and select New From Clipboard in Max.

(But note: this won't work if placed within a for loop, for complicated reasons)

jvkr's icon

A while back I shared something using gendsp based on a single definition / multiple instantiation. It is not the easiest or most intuitive approach, but actually I use it quite regularly.

Diemo Schwarz's icon

Thanks Graham, that's totally awesome and perfectly on time! I must have missed it in the doc or release notes.
And, yes, the codebox eases that kind of reuse a bit. The dream now would be to be able to address in/out vars as an array like so

for (i = 0; i < numoutlets; i += 1)
  out[i] = subprocess(i, ...);

or what if outlets would be mirrored as a Data array?:

 
  out.poke(i, subprocess(i, ...));

stkr's icon

Yes, Data is the way to go for this (and yes, I wish it too).

Graham Wakefield's icon

The dream of using a for loop to do this is unfortunately not likely to come true.

The main problem is that subprocess() can be stateful, by having History objects or other stateful operators such as phasor(), cycle(), etc, which require memory allocation up front to support each iteration distinctly. (Otherwise, all your filters and oscillators etc. will be the same one). The generic form of a for() or while() loop is unpredictable how many iterations are involved, and thus how much memory needs to be allocated. We don't want to allocate memory in the audio loop, especially if suddenly many hundreds of iterations are invoked. Only in the specific case of a constant, as in "for(i=0; i

BTW My own approach to solving this kind of problem is to use another language generate repetitive genexpr code.

Ernest's icon

Thank you for the explanation.

That's much easier to declare Params as arguments over wiring up Setparams, great improvement. Currently I have >200 params in SynthCore. . Is there a limit to the number of Params that can be declared as arguments?

Graham Wakefield's icon

No limit that I know of. I wonder if there's a limit to how much text you can write in a max box?

Max Gardener's icon

Thanks, Graham. Although I'm sure it may not be the answer that some folks wanted, I always enjoy those explanations that clarify WHY someone is challenging or impossible, since it pulls the curtain aside a little bit and lets us marvel at the pinions, escapements, and tourbillons operating within.

Ernest's icon

So far, I have not had any limits on what I can write in a *codebox". Don't know about text box. in gen~.

I also wanted to thank for increasing number of inputs. I can use 43 now (which is still not actually enough for synthcore, but it is a big improvement). I just wanted to note, Graham, when hovering over the last inputs on a codebox with 43 inputs, the numbers that show up in the hover box are corrupted. Also the inputs do not turn red in the editor over something like 20. I just mention it in case you want to have it fixed. The extra inputs do work though, so thank you.

Diemo Schwarz's icon

Thanks again, Graham, for the clear reminder of the underpinnings of the translation model of gen.

I agree that this could be more clearly achieved by an explicit poly-like operator, but I think an even more easy way could be a resonably powerful macro expansion language (a bit more powerful than the C preprocessor, esp. adding recursion to expand a block of code n times, without going into the full and ugly complexity of C++ templates).

You hint at doing that externally, with what language?

BTW My own approach to solving this kind of problem is to use another language generate repetitive genexpr code.

Graham Wakefield's icon

Yes in fact I have a feature ticket explicitly for a macro language in genexpr. One smart enough to be hygeinic etc.

But in practice I have been having fun using parsing expression grammars to generate genexpr. It helps that a gen object handles expr message so it can be remote coded. Mostly I do this in a browser-based editor. For the grammars PEG.js is not bad. Actually I used it for a workshop at last year's live coding conference.

Graham Wakefield's icon

Hoping to run this again at NIME this year.

Ernest's icon

That looks fun )

If there were a way to multithread in gen~, then it would be incredibly more useful.

Graham Wakefield's icon

Actually it's unlikely it would be much different to gen in poly in practice.

Ernest's icon

Well, that's understandable.

Please can you help me understand how functions work in FOR loops? It seems normally a function works more like a method in an object-oriented language, with a new stateful instance created each time it is invoked. But inside FOR loops, mayb eit is not stateful, but does the same code then invoked, so it works more like a function as known in non OOPs worlds, where there is more a desire to reduce code size with functions?

Are there any changes in statefulness of Max's built-in objects inside FOR loops? For example, SAH and Latch,? I guess accum does not increment on loop iterations, but one can still write new values to history, and the most recent values will be available on the next sample clock?

Diemo Schwarz's icon

You might have seen my application of what I've asked and learned about in this thread with a matrix~ like gen patch :
https://cycling74.com/forums/sharing-is-matrixing-obviously-matrix-in-gen/

It shows how cumbersome it is to write code requiring multiple instances of some subprocesses. Imagine the matrix32 version of this...
That's where a macro language could go a long way.
Also, the need to copy the audio inputs into a Data array in order to pass them into the subprocesses is suboptimal and could be so much easier: I'd suggest a global option for a gen patch to make in and out accessible as a Data array instead of in1, in2, out1, out2, etc. with additional attributes giving their number.

Best...
...Diemo

Graham Wakefield's icon

It's "lexical instancing". That is, if you defined a function "foo", then each instance of foo() in your code implies an instance of any state foo requires (including History, latch, sah, phasor, accum, etc.). So calling foo(foo(foo(foo(x)))) creates four independent instances, but below is only one instance, because the text "foo" appears only once:

for (i=0; i

In this situation a macro language would be fantastic (there are many others, as Diemo notes). I noted the idea of a macro language as a feature request a long time ago, but it seemed a bit esoteric for most people. I'm happy to give it a shot if it seems low overhead. I could sketch up a grammar for it, but what kinds of things would you want to see/be able to do? Before/after examples would be great. Thinking hygeinic would be great. I was thinking something LISP/Julia inspired.

Ernest's icon

I guess my concern is, when compiling a gen~ function, does Max really make a 'real' binary-level function, that is, does it create a stack frame and push the variables onto the stack? Or does it expand the function in place sort of like a macro as you say?

larme's icon

Sorry to bump this thread. Does that mean we cannot use function calls in for loops?

Graham Wakefield's icon

Yes you can use functions in for loops, but you have to bear in mind the 'lexical instancing' issue regarding state. That is, a function that declares its own state (like History) or which uses stateful operators (like cycle, phasor, etc.) will share that state over each iteration of the for loop. Which might or might not be what you wanted.

I guess I didn't reply to Ernest's question. The answer is yes, it make a 'real' binary-level function. Send an 'exportcode' message to gen~ and take a look ;-)

larme's icon

Thanks for your reply, Graham.

It's a little bit tricky because a function may not use History or phasor explicitly but instead call functions that do so. Hence you need to make sure all functions in the chain of invocation is "clean" of state, am I right?

Graham Wakefield's icon

Correct, yes.