CLASS_ATTR_FLOAT_ARRAY question

Eric Lyon's icon

Does the array for this macro need to be defined as static memory? It appears so, since my attempt to use dynamic memory (with or without sysmem allocation calls) results in crashes, but static allocation seems to work. However, that makes me nervous about running up against the size limit for Max object structures, since I might want a larger array.

e.g.

// inside object structure
t_float a_addsyn_weights[128]; // OK

t_float *a_addsyn_weights; // Not OK

// (with or without the following in the new instance method)

x->a_addsyn_weights = (t_float *)sysmem_newptrclear(128 * sizeof(t_float));

// attribute definition in main()
CLASS_ATTR_FLOAT_ARRAY(c,"adsyn_weights",0,t_oscil,a_addsyn_weights, 128);

Any advice would be appreciated.

Thanks,
Eric

Luigi Castelli's icon

Hi Eric,

I too, had exactly the same issue and question not long ago.

since nobody replied yet, I'll share my experience, however it would be nice to know from a more authoritative source than myself...

What I found out is that the only way to have an attribute refer to a dynamically allocated array is to define custom setter and getter methods for that attribute and implement the get/set operations manually.

Now, as far as I am concerned, it would be much more convenient if the attribute itself could automatically work with dynamically allocated memory as well, but I never found that to be possible. I would love to be proved wrong.

It should be possible to do, as long as you feed the attribute a pointer to memory and the size of the memory chunk, regardless of the type of allocation used (static or dynamic).

My feeling is that it might be a shortcoming of the whole attribute architecture.

To C74 the final verdict...

Cheers.

- Luigi

Joshua Kit Clayton's icon

This is true that you need to have a custom setter. No custom getter is necessary. Personally, I don't think it's such a big deal for a relatively uncommon case. You copy a few lines of code (or write a generic attr setter, and reuse it).

Timothy Place's icon

If the number of items in the array can change, I also recommend using CLASS_ATTR_FLOAT_VARSIZE instead of CLASS_ATTR_FLOAT_ARRAY.

best

Eric Lyon's icon

Thanks everyone who wrote back. I now have a functional external using a custom getter/setter. However I observed a surprising behavior. Following the documentation model, the getter allocates memory if both *ac and **av are zero. Otherwise it uses existing memory. In my test, existing memory is never used. Rather, any time the attribute is accessed through the inspector I see multiple calls for memory allocation. If I access the attribute by mousing on the left inlet, I see a single memory allocation call.

This surprises me, since I would have thought that memory would only be allocated once. As is, I'm concerned about a possible memory leak, or more likely that my code is written incorrectly. I can send the project off list to anyone who is interested.

TiA for any advice,

Eric

#define OSCIL_MAX_HARMS 1024

// the object component

t_float *a_amplitudes; // user spec harmonic weightings

// memory alloc in new instance routine

x->amplitude_bytes = OSCIL_MAX_HARMS*sizeof(t_float);
x->a_amplitudes = (t_float *)sysmem_newptr(x->amplitude_bytes);

// here are the macros in main()

    CLASS_ATTR_FLOAT_ARRAY(c,"amplitudes",0,t_oscil,a_amplitudes, OSCIL_MAX_HARMS);
    CLASS_ATTR_ACCESSORS(c, "amplitudes", a_amplitudes_get, a_amplitudes_set);
    CLASS_ATTR_CATEGORY(c, "amplitudes", 0, "Waveform");
    CLASS_ATTR_LABEL(c, "amplitudes", 0, "Harmonic Amplitudes");
    CLASS_ATTR_SAVE(c,"amplitudes",0);

// now the getter

t_max_err a_amplitudes_get(t_oscil *x, void *attr, long *ac, t_atom **av)
{
    int i;

    if ((*ac)&&(*av)) {
        post("memory passed to getter: %ld", *ac);
        //memory passed in, use it
    } else {
        //otherwise allocate memory
        *ac = OSCIL_MAX_HARMS;
        if (!(*av = (t_atom *)sysmem_newptr(sizeof(t_atom) * (*ac)))) {
            *ac = 0;
            return MAX_ERR_OUT_OF_MEM;
        }
        post("memory alloced for getter: %ld", *ac);
    }
    for(i = 0; i < OSCIL_MAX_HARMS; i++){
        atom_setfloat(*av + i,x->a_amplitudes[i]);
    }
    return MAX_ERR_NONE;
}

Joshua Kit Clayton's icon

You are doing the right thing, but as I mentioned, you shouldn't need a custom getter (or even a custom getter if your memory is a fixed size, allocated once in your object constructor and freed only in the destructor). If the size is variable, you should use CLASS_ATTR_FLOAT_VARSIZE as Tim suggests, specifying a location in your struct that keeps track of the array count.

The memory you allocate is freed by the caller. There are various situations which present the potential for optimization to take place with passing memory in, so you should always handle such a case properly when writing attribute getters.

Anyway. I hope this helps.

Eric Lyon's icon

Thanks, Joshua.

I'll have a look at CLASS_ATTR_FLOAT_VARSIZE now. But when I try the above code without the custom getter, the attribute comes in filled with garbage, and is impossible to change either from the inspector or with an "amplitude" message. In any case, it is helpful to know that the memory is freed by the caller, as I had hoped.

Eric

Joshua Kit Clayton's icon

Interesting. Yes, perhaps this is in fact the case. I'd need to look back at the implementation to know if it always assumed the array memory was in the struct memory, but it sounds like that is the case. Sorry for the mis-information.

Eric Lyon's icon

Hi Joshua,

No problem. I can report the same behavior when using CLASS_ATTR_FLOAT_VARSIZE. The good news is that the advertised functionality is in place as long as both getter and setter functions are supplied.

Thanks again,
Eric

Luigi Castelli's icon

@Joshua
Thanks for the clarification.
Yes, both getter and setter need to be supplied for attributes to work properly with dynamically allocated memory.

@Eric
In your getter methods I strongly suggest you use two great little functions:
atom_alloc() and atom_alloc_array()

So, you'd write something like:

t_max_err a_amplitudes_get(t_oscil *x, void *attr, long *ac, t_atom **av)
{
    if (ac && av)
    {
        char alloc;
        if (atom_alloc_array(OSCIL_MAX_HARMS, ac, av, &alloc)) {
            return MAX_ERR_OUT_OF_MEM;
        }

        for (int i = 0; i < OSCIL_MAX_HARMS; i++) {
            atom_setfloat(*av + i, x->a_amplitudes[i]);
        }
    }
    return MAX_ERR_NONE;
}

It makes for more streamlined and easy to read code.
It is also less error-prone than having to use sysmem_newptr()

Cheers.

- Luigi