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);
    object_method((t_object*)b, gensym("dirty"));

This is truly threadsafe. (Note that you only need to call the dirty method on the buffer to tell that it changed if you wrote to the buffer).

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);
}

17 thoughts on “Accessing buffer~ Objects in Max5

  1. A question about this blog post popped up on the Cycling ’74 forum @ http://www.cycling74.com/forums/index.php?t=rview&goto=168701&th=38616#msg_168701

    “Following that you have an access loop without checking at each sample to see if the buffer is still valid. Does the “sheriff” make that safe to do?

    What I’ve tried recently (seems to work, but I don’t do a lot of active buffer switching) is to check before each sample access if b_valid is still true. If not, I bail. Is that hideously expensive, still not thread-safe, or otherwise sub-optimal?”

    The answer here is that you really don’t know when threads are going to switch. So checking the flag before hand for every sample does not guarantee thread safety. It might work some or most of the time, but then you have the potential for ‘unexplainable’ and unreproducible behavior later.

    It also happens to be true that branching (using the aforementioned ‘if’ statement on every sample) is slow. I don’t know if I would classify it as ‘hideously expensive’, but given the opportunity to avoid it I definitely would. The best way to know the actual expense of it is to profile it with a tool like Apple’s Shark.

    And finally, it is correct that it is not necessary to check the validity for every sample. Once you have bumped the ‘in use’ flag then the buffer is yours alone. This is also demonstrated in the index~.c example in the latest revision (rev2) of the Max 5 SDK.

  2. Hi Tim – thanks for the quick response! It appears that all my suggestions are “old school.” One thing that seems to work well for getting waveform~ to update itself is to send the buffer a “dirty” message:

    object_method( (t_object *)x->wavebuf, gensym(“dirty”) );

    That seems to be a bit less coding than the notify routine you mentioned above, but will it continue to work?

    Thanks again,
    Eric

  3. Tim, thanks for your private message pointing me here. I was having trouble with a waveform UI object updating based on a buffer my object is recording into. Looking back over the code, I was not using the dirty method that Eric describes, but directly changing the modtime.

    So in the perform method…

    /**/
    
    t_buffer *s_ptr = x->snd_buf_ptr;
    r_pos = x->rec_position;
    
    // track r_pos to see if we wrote anything
    saverpos = r_pos;
    
    // record - process - etc
    
    // update modtime
    if (r_pos > saverpos) s_ptr->b_modtime = systime_ticks();
    
    /**/
    

    I am guessing this method is deprecated and I need to switch to the “dirty” method. Any input?

    Thanks in advance.
    –Nathan

    • Hi Nathan — Yes, you should use the dirty method. As a general rule all direct struct access has been deprecated in Max 5 and you should use methods exposed in the API. There will be exceptions, but if you are directly accessing struct members your code either won’t work (e.g. with patchers, windows, etc.) or isn’t guaranteed to work in the future.

  4. Dear Tim

    Thanks for pointing this out. Is the ATOMIC_INCREMENT method is sufficient as the only protection if I want to write data to the buffer? In all the examples it seem we are only reading and I am worried writing needs more care.

    thanks

    pa

    • You are correct that the example I gave is only reading (I was trying to keep the example as simple as possible). However, the ATOMIC_INCREMENT is sufficient protection for both reading and writing. This is what is done in Tap.Tools and also what we do for the stock MSP objects.

  5. Pingback: Custom Data-Types in Max Part 3: Binding to Symbols « 74Objects

  6. These ps_ symbols I defined myself in the code as globals, like this:

    t_symbol* ps_globalsymbol_binding;
    

    Then in my main() function I set the symbol pointer using gensym(). The reason to do this instead of just calling gensym() everywhere is because it is faster to use the cached symbol pointer instead of doing the lookup in the symbol table each time.

    Then it all works!

  7. Thanks for the article(s) Tim.
    I still have a question, and that is about the b_valid flag:
    I understand that you need to check it before you access a buffer~, but for what operations do you need to set the state of this flag?
    You say “Once you have bumped the ‘in use’ flag then the buffer is yours alone.”, why is there still a need to set/check this state? Does that mean I can safely resize the buffer~ in an external as well?

    Timo

    • Hi Timo,

      The buffer~ object’s ‘in use’ flag and ‘valid’ flags are indeed different. The ‘in use’ flag indicates whether or not an object accessing a buffer’s contents while the ‘valid’ indicates whether or not there is memory correctly allocated by the buffer~ for reading/writing. Hope that makes sense!

      Tim

      • thanks Tim,
        that’s what I thought, but does this imply that when I want to resize/fill/clear the buffer in an external I will have to set the b_valid flag to false while doing that?

        Timo

        • The buffer~ object should manage the b_valid flag internally. Calling the resize method on the buffer~, or reading a file into the buffer~, etc. will all manage this flag for you.

          Cheers

  8. Could you give me some pointers towards accessing buffer~ in a non-MSP context? I am using the buffer~ to store data and need to get it into my external as quickly as possible, real-time is not cutting it ;)

Leave a Reply