fill data array only once at gen~ init?

Ernest's icon

Currently the only thing I can think of is not very efficient. Any suggestions?

Data dat(65536);
if (change(samplerate)!=0){
    for(x = 0 ; x < 65535 ; x += 1){
       dat.poke(x * x, x);
    }
}
Graham Wakefield's icon

Roughly analogous, this is what I usually do. I use a history, so that I can trigger it via a message from outside gen~ if I want to:

History reset(1);
if (reset) {
    // do stuff that only happens once
    // e.g.
    for (i=0; i<dim(dat); i+=1) {
        dat.poke(i*i, i);
    }
}    
Ernest's icon

Thanks for the reply. I found an alternative: loading a buffer with stored data from an audio file, then sending gen~ a message to store the buffer in a data object, as illustrated in one of the gen~ help file tabs. I'll do some analysis on it.

Graham Wakefield's icon

Only thing to watch out for there is that the data object won't be resized to match the buffer size. But so long as your data and buffer are the same size, that works.

Ernest's icon

Yes, also Buffer and Data objects in functions must have size and channels declared, or Max doesn't compile. And if the function is called by another function, the Buffer and Data declarations must also be at the top level, or you get anomalous errors on compile attempts.

But I am running into a memory problem. Max crashes if I try to load 32 voices at launch. I have to load with one voice, then change it to 32 voices after load. Memory then increases to 5 gigabytes. So it won't work in 32-bit version at all.

If I have large buffer and data objects, is there anything I can do to reduce the memory usage? That would be very helpful.

Graham Wakefield's icon

Buffer doesn't need size/channels, only Data -- buffer inherits the size/channels from the buffer~ it refers to. Actually even Data shouldn't need them either, it defaults to 512 frames of 1 channels.

By the way you can also pass buffer/data into functions, rather than using global names. For example:

getfirstsample(src) {
    return src.peek(0);
}

Data d;
d.poke(74, 0);
out1 = getfirstsample(d);

I'm not sure what you mean about 32 voices. If you mean your gen~ inside a poly~ with 32 voices, and they are all using the same buffer~ data, then I strongly recommend you use Buffer rather than Data in your gen patcher. Data means a local copy for each gen~ instance, whereas Buffer uses the same memory as the underlying buffer~ object.

The only reason to use Data is if you want 64-bit data generated in gen~. Since you're loading from a buffer~, the data is 32 bit anyway.

Ernest's icon

Aha! That's just what I wanted to know about passing data/buffer in! Thank you!

My first tests of a single instance of gen~ indicate that data arrays, when declared inside a function, load another copy. I was able to get memory down from 465MB to 439MB after 4 hours unrolling functions. But for the work and and the difficulty of maintenance, it would be alot easier if there were another way to reduce memory, and it doesn't seem as significant as other factors. The memory goes up to 565MB if I close the patch and open it again. And the memory usage doesn't change if I am open the patch on 32-bit or 64-bit Max on Windows.

Would it make a difference if I somehow forced gen~ to work in runtime mode? Or are there global settings that make a significant difference to memory usage?

And yes, I am trying to embed gen~ in a poly~ again.

Graham Wakefield's icon

No idea about run-time mode (not likely), but absolutely use Buffer instead of Data.

Buffer is just a reference wrapper, should have negligible effect on memory.
Data makes a copy (and converts to 64-bit), definitely will eat up memory in a poly~.

Ernest's icon

Well as far as gen~ goes, my tests do indicate that each declaration still increases memory, whether buffer or data.

Can one resize buffer() or delay()after declaration in gen~? Some of my delays are very large to support 192khz samplerate, and people with slower machines don't need such a big delay. It would be nice to resize buffers and delays and things depending on sample rate

EDIT: maybe they are double buffered?

Ernest's icon

Alas, passing pointers to data and buffers with multiple channels doesn't work though. I'll file a bug.

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

Graham Wakefield's icon

Buffer is not sized in gen~. The size of a Buffer is exactly the size of the underlying buffer~. There really shouldn't be any additional memory usage caused by Buffer objects in gen~. They are not double-buffered, it is a direct pointer reference. There should be no significant memory increase from using Buffer objects in gen~. If you have an example that shows memory increase let me know -- that would indicate a strange bug!

You can size a delay according to samplerate by using the samplerate global, e.g. [delay samplerate*4] for a 4 second delay.

Graham Wakefield's icon

Passing and using multi-channel buffers in functions works fine:

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

Ernest's icon

Well, I just finished converting all my data buffers to single channels. Now I guess I can do some performance testing to see if the additional multiplication is computationally cheaper than using channel buffers, then I'll look at your sample, thanks.

You already helped me though.

Passing buffers by referenced reduced memory from 456MB to 419MB, That 37MB difference may not seem like a big proportion, but over 32 voices that's 1.2GB less! thank you very much!

Ernest's icon

Hi Graham,

it took me a very long time to figure out why your suggested change did not work in my (my own) code. I finally figured out gen~ can't have a peek method in the last argument of a function call. For example this works:

out1 = myfunc(d.peek(0, 0), in1);

but this doesn't work:

out1 = myfunc(in1, d.peek(0, 0));

On consideration, it appears when the peek method returns a value to any but the last argument of a function, the method invokes parsing of the next argument, But if the peek method is the last argument in the function, the return from the peek does not cause the function to be called. I would guess that is a pretty rare problem , but it had me stumped for a very long time. I had actually given up trying to explain the error, but much later tried rewriting the code from scratch and happened on a case where the error did not occur, and finally spotted the difference that the function call did not have the peek method as the last argument.

Graham Wakefield's icon

This is strange; placing peek in the final position works fine here:

// just a simple function to grab a specific argument:
myfunc(x, y, z) {
   return y;
}

// create a data object and set the first sample to 74:
Data d(1);
poke(d, 74, 0);

// call function test() with a peek in the 2nd and final argument position:
out1 = myfunc(in1, d.peek(0, 0));

I wonder if it is related to multiple returns. peek() has two return values (the peeked value, and the index) just like the [peek] object has two outputs, and maybe this additional value is changing the behaviour of your function? E.g. if in the code above you change myfunc to return z, it will output zero, as that is the sample index peeked.

kcoul's icon

Hi @Graham sorry to derail the thread but I've been trying to touch base and haven't been able, is graham@cycling74.com a good address for you? Looking to synchronize on some planned work for the vr package

Ernest's icon

The actual code in my case was:

History a1, s2, s3, s4, s5, s6, s7, s8;
....
if(in11 == vdraw){
...
if (mon == 9){    
y, z = ping1(y, z, dl1, dr1, fx.peek(0,x), fx.peek(8,x), fx.peek(6,x), fx.peek(10,x));
y, z = ping2(y, z, dl2, dr2, fx.peek(d2m,x), fx.peek(d2w,x), fx.peek(d2p,x), fx.peek(d2z,x), fx.peek(d2d,x), fx.peek(d2h,x));
y, z = chorus(y, z, fx.peek(ch1b,x), fx.peek(ch1b,x) * fx.peek(ch1d,x), fx.peek(ch1f,x) * fx.peek(ch1z,x), fx.peek(ch1m,x), s1, s2, s3, s4, s5, s6, s7, s8);
y, z = reverb(fx.peek(Rpre,x), fx.peek(Rcut,x), fx.peek(Rdmp,x), fx.peek(Rdec,x), fx.peek(Rmix,x));
y, z = limit(y, z, fx.peek(Limo,x), fx.peek(Limw,x), fx.peek(Liml,x), fx.peek(Lima,x), fx.peek(Limr,x), lahi, lrhi);
}
}
out1 = y;
out2 = z;

What I found was the limit function compilrd, but the other sigbnatures did not compile until I moved y and z to the end of the functions. I have no other explanation.

Arabrab's icon

Dreaming has a% 25 discount:

codebox

myArray [4] = {0., - 0.95,0.98,0.54} -> (t_sample) myData(4){ 0., - 0.95,0.98,0.54}