Accessing buffer~ Objects in Max5
One thing that has always been a bit tricky, and perhaps a bit under-documented, has been writing good code for accessing the contents of a buffer~ object in Max. What has made the situation a bit more confusing is that the API has changed slowly over a number of versions of Max to make the system more robust and easier to use. This is certainly true of Max 5, and the most recent version of the Max 5 Software Developer Kit makes these new facilities available.
I’ll be showing the favored way to access buffer~ objects for Max 5 in the context of a real object: tap.buffer.peak~ from Tap.Tools. I’ll show how it should be done now, and in some places I’ll show how it was done in the past for reference.
Getting a Pointer
The first thing we need to do is get a pointer to the buffer~ bound to a given name. If you know that there is a buffer~ object with the name “foo” then you could simply do this:
t_symbol* s = gensym("foo"); t_buffer* b = s->s_thing;
However, there are some problems here. What if “foo” is the name of a table and not a buffer~? What if there is a buffer~ named foo in the patcher, but when the patcher is loaded the buffer~ is instantiated after your object. What if you execute the above code and then the user delete’s the buffer~ from their patch? These are a few of the scenarios that happen regularly.
A new header in Max 5 includes a facility for eleganty handling these scenarios:
#include "ext_globalsymbol.h"
Having included that header, you can now implement a ‘set’ method for your buffer~-accessing object like so:
// Set Buffer Method void peak_set(t_peak *x, t_symbol *s) { if(s != x->sym){ x->buf = (t_buffer*)globalsymbol_reference((t_object*)x, s->s_name, "buffer~"); if(x->sym) globalsymbol_dereference((t_object*)x, x->sym->s_name, "buffer~"); x->sym = s; x->changed = true; } }
By calling globalsymbol_reference(), we will bind to the named buffer~ when it gets created or otherwise we will attach to an existing buffer. And when I say attached, I mean it. Internally this function calls object_attach() and our object, in this case tap.buffer.peak~, will receive notifications from the buffer~ object. To respond to these notifications we need to setup a message binding:
class_addmethod(c, (method)peak_notify, "notify", A_CANT, 0);
And then we need to implement the notify method:
t_max_err peak_notify(t_peak *x, t_symbol *s, t_symbol *msg, void *sender, void *data) { if (msg == ps_globalsymbol_binding) x->buf = (t_buffer*)x->sym->s_thing; else if (msg == ps_globalsymbol_unbinding) x->buf = NULL; else if (msg == ps_buffer_modified) x->changed = true; return MAX_ERR_NONE; }
As you may have deduced, the notify method is called any time a buffer~ is bound to the symbol we specified, unbound from the symbol, or any time the contents of the buffer~ are modified. For example, this is how the waveform~ object in MSP knows to update its display when the buffer~ contents change.
Accessing the Contents
Now that you have a pointer to a buffer~ object (the t_buffer*), you want to access the contents. Having the pointer to the buffer~ is not enough, because if you simply start reading or writing to the buffer’s b_samples member you will not be guaranteed of thread-safety, meaning that all matter of subtle (and sometimes not so subtle) problems may ensue at the most inopportune moment.
In Max 4 you might have used code that looked like the following before and after you accessed a buffer~’s contents:
saveinuse = b->b_inuse; b->b_inuse = true; // access buffer contents here b->b_inuse = saveinuse; object_method((t_object*)b, gensym("dirty"));
The problem is that the above code is not entirely up to the task. There’s a new sheriff in town, and in Max 5 the above code will be rewritten as:
ATOMIC_INCREMENT((int32_t*)&b->b_inuse); // access buffer contents here ATOMIC_DECREMENT((int32_t*)&b->b_inuse);
This is truly threadsafe. And as a bonus you no longer need to call the dirty method on the buffer to tell that it changed.
Here is the code from tap.buffer.peak~ that access the buffer~’s contents to find the hottest sample in the buffer:
{ t_buffer *b = x->buf; // Our Buffer float *tab; // Will point to our buffer's values long i, chan; double current_samp = 0.0; // current sample value ATOMIC_INCREMENT((int32_t*)&b->b_inuse); if (!x->buf->b_valid) { ATOMIC_DECREMENT((int32_t*)&b->b_inuse); return; } // FIND PEAK VALUE tab = b->b_samples; // point tab to our sample values for(chan=0; chan < b->b_nchans; chan++){ for(i=0; i < b->b_frames; i++){ if(fabs(tab[(chan * b->b_nchans) + i]) > current_samp){ current_samp = fabs(tab[(chan * b->b_nchans) + i]); x->index = (chan * b->b_nchans) + i; } } } ATOMIC_DECREMENT((int32_t*)&b->b_inuse); }
About this entry
You’re currently reading “Accessing buffer~ Objects in Max5,” an entry on 74Objects
- Published:
- March 22, 2009 / 8:57 pm
- Category:
- The 74Objects Blog
- Tags:
- coding, externals, Max, multithreading
10 Comments
Jump to comment form | comment rss [?] | trackback uri [?]