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.

    • Aug 28 2018 | 4:22 pm
      Darwin, Everything works perfectly until here "Then we modify our patch to send the reverse message:" You make a new object called "js reverseLoop.js" When I do that it's "not found" by the Console Then if I ignore that message and go back to "js checkLoop.js" it works anyway. But when I add scramble and scramble 5, scramble doesn't glitch the rhythm, but scramble 5 does. And I can't stop it from scrambling. I say this as a former professional drummer - so I think I know what it should sound like :) Can you explain? Do I need 2 objects in the patch - one called "js checkLoop.js" and another called "js reverseLoop.js"? Or what? Thanks!
    • Aug 28 2018 | 6:20 pm
      Sorry - I just renamed checkLoop.js to reverseLoop.js so that I could keep each version around. You can either keep modifying the checkLoop.js file, or create new js entries with new file names, and copy in the old contents...
      Here are the three Javascript files I created - see how they compare to yours!
      [ddg]
    • Aug 28 2018 | 6:22 pm
      thank you
    • Aug 29 2018 | 3:33 am
      As someone who does a lot of JS for work, I haven't applied it to Max yet. So thank you, Darwin, for putting this out there! This is going to be fun!
    • Sep 04 2018 | 12:19 am
      I'm already looking forward to episode 2!
      edit: I wonder why "The Buffer Object" entry never appeared under the "See also" section on the "buffer~" reference page...
    • Sep 27 2018 | 4:43 pm
      Thanks, nice tutorial.