Using counter for a nested-loop type structure

James Harkins's icon

James Harkins

2月 01 2023 | 3:46 午前

In SuperCollider, I would do this, where it's clear, unambiguous, direct, no fuss.

```
5.do { |y|
5.do { |x|
... something with x and y...
}
}
```

In Max, I can use [int] boxes as registers and increment them by hand, rolling over at the right time. It works but is a bit of a puzzle to read.

2d counting by int boxes

Then I thought... wouldn't counter be an easier way? But I found it tricky... I did get it to work but I feel there is actually a loss of clarity by doing it this way.

2d counting with [counter]s and a rather irritating use of gate

Am I missing something that would make this nicer? I know what I want, just not sure of the best way to tell it to Max.

2nd patcher:

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

hjh

👽'tW∆s ∆lienz👽's icon

👽'tW∆s ∆lienz👽

2月 01 2023 | 4:58 午前

although your 'counter'-based patch might be further automated by 'uzi' using only max objects,
you can just use the non-signal version of 'gen' and write a regular nested for-loop within codebox these days:

here's that patch:

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

[Edit: looking back at it, i didn't write it exactly like you have... maybe the 'i' and 'j' would be hard-coded to count to 5, but hope it helps anyways 🍻]

Roman Thilenius's icon

Roman Thilenius

2月 01 2023 | 5:37 午前


using i, f, zl reg as registers to read from and write to is doable, but you can do it even more naive and "visual" when you create custom subpatchers containing the register and the logic required for a certain task.

for example this is how i perform and connect a for i<=5 loop abstraction.

this abstraction patch will work for any iterative process and any data type.

is it as CPU effective as in supercollider? no, of course not. but it is a joy to patch with it.



James Harkins's icon

James Harkins

2月 01 2023 | 6:26 午前

I was a little bit imprecise with the loops -- the counters in the Max example are being driven by something else, so I should have written it as a coroutine:

```
r = Routine {
    5.do { |y|
        5.do { |x|
            [x, y].yield;
        }
    }
};

r.next; // [0, 0]

// later...
r.next; // [1, 0]
```

That's why, incidentally, I was staying away from [Uzi] intentionally. In my specific use case, I could actually use [Uzi] and [zl.slice] (I guess? or is there a better option?) but what I'm not crazy about in that case is that I would also need zl.reg to hold the remainder of the list for next time. I had thought it would be easier to let zl.iter drive, but maybe it's not.

Incidentally it's a valid use case to move through a 2D data structure driven by a metro, in which case the Uzi approach would not be enough.

[gen] might be cool, if it can retain static variables between calls.

> using i, f, zl reg as registers to read from and write to is doable, but you can do it even more naive and "visual" when you create custom subpatchers containing the register and the logic required for a certain task.

Taking a walk after lunch, it occurred to me that I could replace the slower counter with an abstraction where input 1 = consume the current value and, next time, increment and input 0 = do not consume the current value (repeat it next time). I think that would work nicely with [counter]'s carryint output.

Part of my interest in this question is that programming environments, or programming cultures, do better when they have standard solutions for common problems. SC borrows a lot from imperative languages, so many of those solutions are ready-made. Pd and Max, I don't see quite as much concern for this.

hjh

James Harkins's icon

James Harkins

2月 01 2023 | 7:15 午前

This does produce the right sequence, with a lot less funny business between the counters.

hjh

👽'tW∆s ∆lienz👽's icon

👽'tW∆s ∆lienz👽

2月 01 2023 | 7:54 午前

[gen] might be cool, if it can retain static variables between calls

it can, just use the 'history' op(it exists in the non-signal domain too).
now that you've explained more fully, your exact supercollider example of

r = Routine { 5.do { |y| 5.do { |x| [x, y].yield; } } };
r.next;  // [0, 0] 
// later... 
r.next;  // [1, 0]

can be done very simply like so:

(i skipped putting in a limit of 5, but you could do that easily inside or outside the 'gen'/'codebox', if you do it inside, you could use a 'param' to set the limit with a message to the 'gen' patcher)

here's the patch:

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

James Harkins's icon

James Harkins

2月 01 2023 | 9:13 午前

> it can, just use the 'history' op(it exists in the non-signal domain too).

That's very nice! I might well use that a lot for things that are awkward to patch.

Thanks!

hjh

👽'tW∆s ∆lienz👽's icon

👽'tW∆s ∆lienz👽

2月 01 2023 | 3:34 午後

Thanks!
hjh

ahhh, relieved i could post something helpful here, my pleasure.
and Thank YOU for the dewdrop_lib and everything else you've done with SC(i'm a long-time lurker in the SC world... using a norns now, hoping to switch over more) 🙏
🍻