Buffer~ access with SDK6.1.1, buffer_perform_begin? buffer_locksamples ?

    Apr 13 2013 | 12:22 pm
    Hi, I'm confused. In this page : https://cycling74.com/sdk/MaxSDK-6.0.4/html/chapter_appendix_d.html its explained about buffer_perform_begin/buffer_perform_end
    In this page : https://cycling74.com/sdk/MaxSDK-6.1.1/html/group__buffers.html its explained about buffer_locksamples/buffer_unlocksamples. Why this second page doesn't includes buffer_perform_begin/buffer_perform_end, but it includes all buffer related fonctions ?
    What is THE good method for accessing a buffer in READ, and in WRITE mode ? Are buffer_locksamples/buffer_unlocksamples sufficients ?
    Thanks for clarifiying.

    • Apr 13 2013 | 2:49 pm
      Hello folks,
      when somebody is new to the Max/MSP SDK, it's imperative that he/she checks out the header files of the Max framework. Many answers are to be found right in there. Don't mean to be rude, however lately the Max Dev Forum has been filled with many questions, the answers to which would have been easily found in the .h files or in the numerous code examples that come with the SDK. We have all gone through that process and it might not be always pleasant, but the Max/MSP API is constantly evolving, and therefore it's hard for the C74 team to keep the Dev docs always 100% updated. Check out the code and most likely you will find answers to all basic questions. If - after going through the header files and/or code examples relevant to your question(s) - something might still be unclear, we'll be happy to help you in the forum. Thanks for your understanding.
      ...now, to answer kawkhins' specific question:
      From ext_buffer.h:
      // Internal or low-level functions
      // buffer_perform functions to replace the direct use of
      // atomics and other buffer state flags from the perform method
      // wrapped by buffer_locksamples() and buffer_unlocksamples()
      t_max_err buffer_perform_begin(t_buffer_obj *buffer_object);
      t_max_err buffer_perform_end(t_buffer_obj *buffer_object);
      So buffer_locksamples() and buffer_unlocksamples() are new functions (MaxSDK 6.1, I believe) that take care of encapsulating buffer_perform_begin() and buffer_perform_end() for you (and probably manage other things under the hood), so that buffer~ access is made even easier and more straight forward than before.
      To learn the correct way of accessing a buffer check out the index~.c example.
      Hope this helps.
      - Luigi
    • Apr 17 2013 | 3:49 pm
      Thanks for this clear reply. Not to be rude, the doc is quite usefull, but with so many dark areas without any explanations! And more, when 6.1.1 SDK came out, no page with full summary of differences with previous SDK (something which is widely done by companies releasing SDK...). Instead, we have to open all pages, checking some info here, some other there, some in samples as you're saying, some in header files, ... That all but professionnal. When we have a set of running objects and we want to make then conform to the new SDK, its too much time lost and too much assumptions to make.
      For example, regarding buffer, I guess that now they are always valid ? before there was a flag for this. I guess that we can call buffer_getframecount, buffer_getchannelcount, ... even without locking samples ? I guess that in a jnilib, I can access a buffer by setting t_object to NULL when calling buffer_ref_new (because I don't have any maxmsp object here).
      And one strangeness, why there is no buffer_setdirty(buffer) function ? to have a uniform way of handling a buffer ?
      Whatever, it's working with lock/unlock samples, until I'll find one of my guess to be wrong.
    • Apr 17 2013 | 5:31 pm
      If you look at index~.c you will get answers to some of your questions.
      For example: buffers are NOT always valid. If buffer_locksamples() returns a NULL pointer then it means the buffer was not valid and you have to basically skip your perform routine and fill the output vector with zeros. If buffer_locksamples() finds a valid buffer it will lock the samples and return a pointer to the first sample in memory. After that you can proceed to calling buffer_getframecount(), buffer_getchannelcount(), etc... and then to executing your perform routine.
      here is the perform method from index~.c to better show you the process:
      void index_perform64(t_index *x, t_object *dsp64, double **ins, long numins, double **outs, long numouts, long sampleframes, long flags, void *userparam)
          t_double	*in = ins[0];
          t_double	*out = outs[0];
          int			n = sampleframes;
      	t_float		*tab;
      	double		temp;
      	double		f;
      	long		index, chan, frames, nc;
      	t_buffer_obj	*buffer = buffer_ref_getobject(x->l_buffer_reference);
      	tab = buffer_locksamples(buffer);
      	if (!tab)
      		goto zero;
      	chan = MIN(x->l_chan, 3);
      	frames = buffer_getframecount(buffer);
      	nc = buffer_getchannelcount(buffer);
      	while (n--) {
      		temp = *in++;
      		f = temp + 0.5;
      		index = f;
      		if (index < 0)
      			index = 0;
      		else if (index >= frames)
      			index = frames - 1;
      		if (nc > 1)
      			index = index * nc + chan;
      		*out++ = tab[index];
      	while (n--)
      		*out++ = 0.0;
      You are right that the documentation for accessing buffer~ objects could be better, but for now that's what it is. Hope I was able to make at least some of it clearer for you.
      - Luigi
    • Apr 18 2013 | 3:52 pm
      Thanks, I suggest to Cycling74 to copy/paste your explanations into the SDK documentation.
    • Apr 18 2013 | 3:56 pm
      So, I guess that if in a poly~, we have several buffer readers, then poly is useless... as operations will be serialized by lock/unlock ?
    • Apr 19 2013 | 5:45 pm
      Well, I don't work for Cycling74 so I don't have access to the source code. Without looking at the source code I cannot really answer your question. My guess is that if every buffer~ accessing object (wave~, groove~, lookup~, etc...) is following the same locking mechanism as index~.c, then yes, most likely buffer~ access will be serialized no matter what. But this is just a guess. I don't really know what's happening inside buffer_locksamples() so I can't really tell. As far as I know it could be detecting if the buffer~ accessing operation is performed from inside a poly~ and in that case decide not to lock at all. Sorry but for deeper and more technical questions only Cycling will be able to provide a definitive and reliable answer.
      - Luigi
    • Apr 20 2013 | 12:13 am
      locksamples is safe to parallelize, and there are no issues with poly~. Whether you are reading or writing from/to the sample values, you are only reading the samples pointer, and are considered a "reader".
      It is the things which reallocate the sample memory that modify the memory pointer of samples (not the sample values) which are considered a writer, and those use an exclusive lock (locking out all other readers and writers).
      While it's implemented differently, you can think of it like a pthread_rwlock, where locksamples is a rdlock, and buffer reallocation is a wrlock.
      We recommend avoiding being a writer, and instead use messages to the buffer object for such reallocation, but if you're really curious about this stuff, look at ext_buffer.h, we describe the lower level calls which you should generally avoid, copied below for your convenience, but left out of the top level SDK html to discourage their use.
      However the short answer is if you're not reallocating buffer memory, you can just use locksamples/unlocksamples.
      // Internal or low-level functions
      // buffer_perform functions to replace the direct use of
      // atomics and other buffer state flags from the perform method
      // wrapped by buffer_locksamples() and buffer_unlocksamples()
      t_max_err buffer_perform_begin(t_buffer_obj *buffer_object);
      t_max_err buffer_perform_end(t_buffer_obj *buffer_object);
      // utility function for getting buffer info in struct form
      // without needing to know entire buffer struct
      t_max_err buffer_getinfo(t_buffer_obj *buffer_object, t_buffer_info *info);
      // the following functions are not to be called in the perform method
      // please use the lightweight buffer_perform methods
      // use buffer_edit functions to collapse all operations of
      // locking heavy b_mutex, setting b_valid flag,
      // waiting on lightweight atomic b_inuse, etc.
      t_max_err buffer_edit_begin(t_buffer_obj *buffer_object);
      t_max_err buffer_edit_end(t_buffer_obj *buffer_object, long valid);  // valid 0=FALSE, positive=TRUE, negative=RESTORE_OLD_VALID (not common)