JavaScripting: The Buffer Object
Episode One
Talking with the editor (Gregory), we decided that putting together a series on JavaScript would be a great addition to the newsletter. First, it gives us a chance to talk through problems that might be difficult to solve with standard Max. Secondly, JavaScript is clearly going to become more important with the upcoming release of Max 8, since Node (an advanced JavaScript runtime) will be integrated as part of that release. Finally, the Max-JavaScript link is a fun intermediate-to-advanced subject, and we can do some pretty crazy stuff with it. So it was decided: let’s give it a try!
What’s Today’s Trick?
For this first article, we are going to dive into something that I touched on in an earlier newsletter article: the JavaScript audio buffer object. We used it in the ROBO Sampling series last year, and there was a lot of interest in the project. But one of the tools that made the project sing is the Buffer object provided by the js object.
Let’s start by creating a simple patch with a buffer~, a playback system and a bang->js combo:
Save the patch, then double-click on the js object and enter in the following JavaScript code:
// Get some info from a buffer~
//
var buff = new Buffer("loop");
function bang() {
post("Channels: " + buff.channelcount() + '\n');
post("Frames (samples): " + buff.framecount() + '\n');
post("Length (in ms): " + buff.length() + '\n');
}
When you are done, make sure you save it in the same location as the patch – this way, the patch will always be able to find the JavaScript file.
All we are doing here is querying the named buffer to find out the number of channels and length (in frames/samples and milliseconds). We use the post
command to display the results to the Max Window (along with the required newlines…), and that’s about it.
Where’s the Fun?
Finding out the number of channels is something the info~ object can do for us, so that isn’t much fun, is it? Let’s dig a little deeper by manipulating the contents of our buffer. In my case, I'll add the code to a new file (in this case, named reverseLoop.js), but you could add the code to your existing JavaScript file. Start by creating a function that listens for the “reverse” message, and reverses the buffer. :
function reverse() {
var j = 0, k = buff.framecount() - 1;
var tmp;
while (k > j) {
for (var i=1; i<=buff.channelcount(); i++) {
tmp = buff.peek(i, k, 1);
buff.poke(i, k, buff.peek(i, j, 1));
buff.poke(i, j, tmp);
}
j++;
k--;
}
post("Sample reversed!\n");
}
Then we modify our patch to send the reverse message:
Play the file, then reverse it, then reverse it again. You should be back to the original. Innocuous, but it starts implying some of the fun we can have. Let’s change this a little so that we have a new message “scramble”:
When we get the message, we will reverse a small portion of the file. To do this, we will make a generic reversing function, and a global function to respond to the scramble message:
function reverseSegment(start, length) {
var st = start;
var en = start + length;
if (en >= buff.framecount()) {
en = buff.framecount() - 1;
}
var tmp;
while (en > st) {
for (var i=1; i<=buff.channelcount(); i++) {
tmp = buff.peek(i, en, 1);
buff.poke(i, en, buff.peek(i, st, 1));
buff.poke(i, st, tmp);
}
st++;
en--;
}
}
reverseSegment.local = 1
function scramble(num) {
var v = (num || 1);
if (v < 1) return;
for (var i=0; i<v; i++) {
var st = Math.floor(Math.random() * buff.framecount());
var ln = Math.floor(Math.random() * (buff.framecount() * .25));
reverseSegment(st, ln);
}
post("Scrambled ... ");
if (v == 1) post("1 time.\n");
else post(v + " times.\n");
}
You’ll notice that I take some steps to make sure that the incoming message checks for arguments, and if none are available (or they are invalid), we properly handle the situation. With these additions, each time I scramble the buffer the contents will get weirder. And there’s no going back – which might be one of the hallmarks of a great audio effect!
Taking it Further
Since we are already tiptoeing into glitch territory, we might as well go all-in. Let’s respond to another message – glitch – by having it generate dropouts in the buffer:
function zeroSegment(start, factor) {
var en = start + (buff.framecount() * factor);
if (en >= buff.framecount()) {
en = buff.framecount() - 1;
}
for (var i=start; i<en; i++) {
for (var j=1; j<=buff.channelcount(); j++) {
buff.poke(j, i, 0);
}
}
}
zeroSegment.local = 1;
function glitch(num, fac) {
var v = (num || 1);
var f = (fac || .05);
if (v < 1 || f < 0.0 || f > 1.0) return;
for (var i=0; i<v; i++) {
var st = Math.floor(Math.random() * buff.framecount());
zeroSegment(st, f);
}
post("Glitched ... ");
if (v == 1) post("1 time.\n");
else post(v + " times.\n");
}
In this case, I’m looking for the number of dropouts to introduce, and a ‘factor’ that represents the amount of the file (from 0.0 to 1.0) of the file to affect. With a slight tweak to the patch, we are ready to go:
At this point, I’m sure you can imagine volume adjustments, re-mappings and other buffer-based applications that could tweak the daylights out of your buffers. And, when things get too messy, you can always just reload the buffer with the original file – returning you to a blank canvas!
Learning More
Virtually everything we’ve done is based on the JavaScript in Max Guide’s Buffer
object reference, available in the application documentation or online:
If you want to see how I used it for manipulating live sampled content, you can check out the ROBO series I did last year:
And, as always, there is a ton of information on programming in JavaScript on the Internet, at your library and your local book shop.
Conclusion
So, hopefully you can see how, with direct buffer manipulation, we are able to make some pretty serious changes to the file contents without having to touch the file itself. We can also mess things up in as creative of a way as we can imagine, so start imagining! Once you start coding, there is no end to the mess you can make.
by Darwin Grosse on August 28, 2018