Lists within gen~?

joelsquare's icon

Hi All,

I'm returning to Max/MSP after a long hiatus and I'm excited to see how things have improved. I'm slowly remembering things and it's been super fun.

I'm trying to wrap my brain around gen~ and I'm making progress, but I'm stuck on something that is probably extremely basic and straightforward - I just don't know the words to use to search for an answer!

Is there a way to have a coll-like function in gen~? I want to input a number, and have it return a different number based on the first.

So, much like if you had a Coll with the internal list of:

1, 55;
2, 78;
3, 99;

Inputting "1" returns a "55", inputting "2" returns, a "78", and inputting "3" gives you a "99".

Is there a way to do this in a gen~ object?

Could some kind soul either explain this or even nudge me to the right tutorial/reference?

Thanks,
J

double_UG's icon

buffer~

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

joelsquare's icon

Hi there,

Thank you so much for this!

Is there a way to do this self-contained within gen~? It doesn't need to be dynamic - I'm hoping to create a reusable gen~ that consistently returns the same values in response to various numbers - no need to edit them.

I guess what I'm asking is "do the values in a buffer or data object need to come externally or can they be permanent from within the gen~ object itself?"

Cheers, and thanks again!

-j

double_UG's icon

you can write values to a Buffer in Gen~ with "poke"

double_UG's icon

or use "data" with peek and poke

joelsquare's icon

Thank you! I will look into this further. I greatly appreciate your responses!

-j

Graham Wakefield's icon

Yes -- if you want to access (read or write) external to gen~, use a [buffer~] (Max) and a [buffer] (gen~), giving both the same name.

If you want it to be entirely self-contained in gen~, use [data]. It behaves mostly the same as [buffer], but you declare the name, length in samples (and optionally, number of channels) in the object, e.g.

[data mydata 512] // creates a local data store of 512 samples
[data mydata samplerate*10 2] // creates a local data store long enough for 10 seconds of audio, and stereo.

You can read/write to buffer and data using the same operators. To write, normally use the [poke] operator. To read, use [peek] for sample-based indexing, [sample] for interpolated phase-based indexing. You can also change the interpolation, bound modes, indexing mode etc. using @attributes, as well as channel indexing using inlets/arguments.

A trick I often use to fill a [data] when a patch loads is to write a codebox script that runs only once. Here for example, it fills a data called "mydata" with a sine curve:

if (elapsed==0) {
    for (i=0; i<dim(mydata); i+=1) {
        phase = i / dim(mydata);
        s = sin(twopi * phase);
        poke(mydata, i, s);
    }
}

Of course you replace that with any function of `i` or `phase` you want.

joelsquare's icon

Thank you for this! One thing that clearly hasn't changed during my Max/MSP hiatus is the friendly and helpful user base.

Your suggestion was really good, but it actually lead me to another, more concise solution for my specific purpose. I was able to figure out enough of CODEBOX to get the results I needed!

Aside from Max, my personal coding experience stopped after learning BASIC on my Commodore 64, so I had to figure out some rudiments, but I was able to accomplish what I need with a series of:

if (in1 == 1) then out1 = 55;
if (in1 == 2) then out1 = 78;
if (in1 == 3) then out1 = 99;

The result behaves exactly as I'd want it to - is there any reasons why this isn't a good method?

Cheers
J

Graham Wakefield's icon

Mainly that the code is running on every sample frame of passing time! Like most things in gen~, the codebox's code runs at audio rate. If all you are doing is filling up some memory, it's probably better to do that once only.

The `elapsed` keyword counts the sample frames since a patch was loaded (or since the last edit, which is the same thing). On the first sample frame, elapsed=0; on the next elapsed=1, etc. So by wrapping your setup in `if (elapsed==0) { ... }` it ensures that it only ever runs once.

If the algorithm is simple, you could do this:

Data mydata(10); // 10 or however many "rows" your "coll" should have
if (elapsed==0) {
poke(mydata, 55, 0);
poke(mydata, 78, 1);
poke(mydata, 99, 2);
// etc.
}

Now in the patcher, you can create a [peek mydata] object, and if you send it a signal value of 0, it will output 55; if you send it a 1, it will output 78, etc.

But if the list is very short, then you might just as well do what you did (slightly refined):

if (in1 == 1) { out1 = 55; }
else if (in1 == 2) { out1 = 78; }
else if (in1 == 3) { out1 = 99; }

joelsquare's icon

Ohhhh thank you!! You just gave me an "a-ha!" moment...

So, if I understand you correctly (keeping in mind that the last bit of code I made involved a Commodore 64 and "20 GOTO 10") - doing it as I did is inefficient, because it forces those commands to be processed constantly. BUT, if I instead use Codebox to send the same data to a data object (using elapsed == 0 as the trigger so that it only happens once), then the data sits in the data object, not using up resources, until such time as it gets "poked"?

My table is indeed very short (21 items), however, for the sake of good practice, it's important to me to do things the most efficient way, so that when bigger ideas hit me, I've got good habits.

I really do appreciate this. Is there a document that you know of that I could read to learn how to use Codebox more succinctly?

Cheers,
Joel