Implementing buffer~ editing operations in Max 6 (lengthy post)
Aug 24, 2012 at 3:47pm
Implementing buffer~ editing operations in Max 6 (lengthy post)
I would like to definetely understand how to implement methods that correctly read and write from/to buffer~ objects.
I would like to start with a conceptually simple example that will go through most of the problems one might encounter.
Let’s say I want to write a method that copies a portion of a source buffer~ to another destination buffer~.
So far the simplified version of my code looks something like this:
Now we need to check in our notify method when the buffer~ is done resizing, and then proceed to copy the selected samples at low priority.
Now in this example, for the sake of simplicity, I am not concerned about the copying operation per se, but more on how to structure the locking mechanism and the resizing of the destination buffer~ around it. We are reading from one buffer~ and writing into another. I understand there are more cases to be handled than what I am showing in my code, but I only want to understand the right structure to adopt.
The first thing I am concerned about is whether I want my method to be deferred to low priority or not.
In any case, here are the options I can think of:
I will immediately discard defer() since the deferral is performed by putting the method at the front of the low priority queue, with the potential risk of changing order of execution. defer_low() could be a pretty good option, however still not ideal in my mind because even though it always defers, it doesn’t prevent backlogging of the low priority queue if the method was called at a fast pace. This could happen for example if someone were to make a metro object bang my copysrctodst() method at a fast rate (I am not sure why anybody would want to do that, but it’s a possible scenario nevertheless…)
So, I would do something like this:
Of course, this code unforgivingly crashes Max.
1 – I stay bound to my destination buffer for as little as possible, however is it still possible that while I am bound to it
2 – Theoretically speaking I need to lock both buffers, the buffer~ I am reading from as well as the one I am writing into.
3 – I don’t know if buffer~ resizing in response to the ‘size’ attribute/message is performed at low priority or not.
4 – Would it be possible to have a look at code of the ‘crop’ method of the buffer~ object? That should do pretty much
5 – What is the exact meaning of the “valid” parameter in the
Hopefully this will also be helpful to others who are struggling with similar issues related to buffer~.
Aug 24, 2012 at 7:49pm
I have likely missed some of your concerns, but I’ll try to get most of them!
0. Just as background for those not familiar… Calling things with defer() isn’t so bad. The benefit is that if you are called from the main thread then the method you call is executed immediately with no introduction of asynchrony.
1. If you currently have the lease on the buffer (by calling buffer_perform_begin()) then this notification can not be generated while you are operating on the buffer. Note, however, that it could be possible that a defer’d notification would still be in the queue and might be processed at the same time as you are operating on the buffer (assuming that you are not operating the main thread).
2. You can call buffer_perform_begin() on both buffers. It is possible, depending on what your extern is doing, that you will want the entirety of the operation protected. In this case you may want to create your own mutex and then wrap the entire operation with that. But as a minimum and without any further context, it looks like buffer_perform_begin() and buffer_perform_end() on both buffers will be adequate.
3. Buffer is a resized in main thread. If you call it from another thread then it is internally defer()’d to the main thread. So if your method is itself called via defer() then the buffer resize is, in fact, synchronous. That means you don’t need to worry about notifications which could be ambiguos in nature.
4. The “crop” method for buffer~ is deferred and happens entirely in the main thread. It does a bunch of things that you won’t be able to do in code external to the buffer~ object itself, so it’s not a good example. Here’s how it works though:
a. allocate a temporary vector of floats the size to which the buffer is being cropped with sysmem_newptr()
5. Good question. I think you don’t actually want to call buffer_edit_end(). Instead, the functions you want to use are buffer_perform_begin() and buffer_perform_end(). The comments in the headers will say that these are for perform methods, but it’s okay to use them in non-perform methods. We only use buffer_edit_begin() and buffer_edit_end() in gen~ code and not for normal MSP objects.
Hope this helps!
Aug 25, 2012 at 1:47pm
thanks for your comments. They helped a great deal.
After your remarks, now I have something like this:
So… something like this:
Did I understand you correctly ?
In any case, thank you so much for your help, Tim.
Aug 27, 2012 at 5:00pm
1. “you are advising to defer the whole myobj_copysrctodst() method, not only myobj_doit(), so that the whole composite operation will happen in the main thread and everything will be totally synchronous with no risk of reversing execution. Also no notifications will be necessary.”
2. Changing the size of the buffer will itself trigger a “dirty” message to buffer. Without checking I’m not sure if it will do this when the buffer size stays the same.
3. I would *not* put your potential large copying operation on the scheduler thread, as this will degrade the performance of the scheduler! You could, if you wanted, do something like you are talking about on the main thread by using a qelem. Then from inside of the qelem routine call qelem_set() again so that method will be revisited the next time the low-priority queue is serviced.
4. As long as you are using the buffer’s accessor methods and not directly accessing the struct you should not need to be paranoid ;-)
Aug 27, 2012 at 10:18pm
Tim, thanks again for replying.
One last thing:
2. I checked and if the buffer size stays the same there is no ‘dirty’ message sent to buffer~.
Aug 28, 2012 at 12:43am
There is nothing wrong with calling the dirty method an extra time. That’s best way to do this. Any direct member access of the t_buffer is subject to potential breakage in the future.
Aug 28, 2012 at 4:39pm
Ok, thanks so much for your help, Tim.
Sep 12, 2012 at 10:11am
It might be I am missing something obvious, but today a new issue came up regarding buffer~ resizing.
I have a ‘copy’ method as follows:
There are instances where the number of channels between the source and destination buffers is not equal.
So, is there a way to programmatically tell buffer~ to resize itself AND change the number of channels as well ?
Sep 24, 2012 at 12:21pm
There is currently no way to do that, unfortunately. I’ve just created a new feature request ticket for this.
You must be logged in to reply to this topic.