Visualizr: Fun with Web Audio and Canvas

I made a thing with Web Audio and Canvas. It's a music visualizr. You can play with the full thing here, but here's a quick demo of it (music by mrsjxn):

 

Crash-course

The rest of this blog post will be a sort of crash-course in how to get a basic visualization set up with Web Audio and Canvas.

The Web Audio API is well-supported across modern browsers, except for IE (big surprise). It allows you to not only play sounds but also manipulate them at a very low level.

First, an example of just getting a song to play with Web Audio:

var context = new AudioContext();

var request = new XMLHttpRequest();
request.open( 'GET', songUrl, true );
request.responseType = 'arraybuffer';
request.onload = function() {
    context.decodeAudioData( request.response, function( buffer ) {
        var source = context.createBufferSource();
        source.connect( context.destination );
        source.buffer = buffer;
        source.start( 0 );
    });
};

request.send();

Web Audio also contains a set of interfaces that allow you to analyze and manipulate frequency data. AnalyserNode is meant for reading frequency data only and it's perfect for the purposes of visualization.

You can create an AnalyserNode with context.createAnalyser(), and "hook it" into the audio source with source.connect( analyser ).

Once you have the analyser set up, you can read in frequency data synchronously by using anaylser.getByteFrequencyData(). To demonstrate, here's an example that uses setInterval to periodically log the average volume:

/*
 * This is inside decodeAudioData callback
 */

// Set up analyser
var analyser = context.createAnalyser();
analyser.smoothingTimeConstant = 0.3;
analyser.fftSize = 1024;
var bufferLength = analyser.fftSize;
var dataArray = new Uint8Array( bufferLength );

// This will periodically compute average volume and log it out
setInterval( function() {
    analyser.getByteFrequencyData( dataArray );

    // Sum frequency amplitudes
    var values = 0;
    for ( var i = 0; i < bufferLength; i++ ) {
         values += dataArray[ i ];
    }

    // Log average volume
    console.log( values / bufferLength );
}, 100 );

var source = context.createBufferSource();
// Hook up the source to the analyser and the destination
source.connect( analyser );
source.connect( context.destination );
source.buffer = buffer;
source.start( 0 );

Now, it's time to have fun with canvas. We've got some volume data. Let's do something with it.

First, a couple quick notes about canvas:

  1. We can call createContext( '2d' ) on the canvas DOM element to get the rendering context. This is the object that gives you access to all the drawing methods
  2. Use requestAnimationFrame, not setInterval for constant re-rendering. requestAnimationFrame is the browsers way of binding a callback to the next tick of the browser's internal render loop. It's much more performant, and simple to use.
  3. On each tick of the clock, wipe the canvas clean. Each frame will be drawn from scratch. We'll do this with clearReact( 0, 0, canvasWidth, canvasHeight ).

Here's a quick example using requestAnimationFrame, clearRect and fillRect to draw a rectangle whose height matches the average volume:

var context = new AudioContext();
var analyser;
var bufferLength = 1024;
var dataArray = new Uint8Array( bufferLength );

var request = new XMLHttpRequest();
request.open( 'GET', 'https://s3.amazonaws.com/tybenz.assets/visualizr/really_wanna.mp3', true );
request.responseType = 'arraybuffer';
request.onload = function() {
    // Audio stuff goes in here
    // analyser is initialized and its fftSize
    // is set to bufferLength (1024)
    // Also, draw will be kicked off after it's all set up
};

var canvas = document.getElementById( 'canvas' );
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
var rectWidth = 20;
var ctx = canvas.getContext( '2d' );

var draw = function() {
    analyser.getByteFrequencyData( dataArray );

    // Sum frequency amplitudes
    var values = 0;
    for ( var i = 0; i < bufferLength; i++ ) {
         values += dataArray[ i ];
    }

    var volume = values / bufferLength;

    ctx.clearRect( 0, 0, canvasWidth, canvasHeight );
    ctx.fillStyle = 'black';
    ctx.fillRect(
        canvasWidth / 2 - rectWidth / 2,
        canvasHeight / 2 - volume / 2,
        rectWidth,
        volume
    );
    requestAnimationFrame( draw );
};

And that's it. The visualizr I created wasn't much different from this example. After setting this up, I just worked on improving the animation. I created a collection of bars whose heights decrease as you go away from the center. I then added a delay to when the change to the smaller bars' heights gets applied.

If you visit the full visualizr here: http://tybenz.com/visualizr, you can play with various parameters and see how it effects the animation. If you'd like to see more details, the source is up on GitHub and CodePen.

As always, if you have questions/comments, you can find me on Twitter.

Paper API Client

Don’t put URL string literals in your code. Use my latest “paper” module instead.

It turns this:

request({
    method: 'post',
    url: 'https://host.com/v1/items?api_key=ApiKey',
    json: {
        name: 'foo',
        description: 'bar'
    }
})
.on( 'response', function( res ) {
    console.log( JSON.parse( res.body ).id );
})
.on( 'error', function( err ) {
    console.error( err );
});

Into this:

apiClient.createItem({
    name: 'foo',
    description: 'bar'
}, itemCreated );

function itemCreated( err, data ) {
    if ( err ) {
        console.error( err );
        return;
    }

    console.log( data.id );
};

You can read more about how it works on the GitHub page: https://github.com/tybenz/paper-api-client.

As always, any feedback is appreciated. Give me a shoutout on Twitter.

Paper Profiler

I wrote a small profiling helper for NodeJS apps today.

It's called paper-profiler and you can read more about it here: https://github.com/tybenz/paper-profiler

Example

If you're app looks like this:

var express = require( 'express' );
var profile = require( 'paper-profile' ).create();
var fs = require( 'fs' );
var path = require( 'path' );
var service = require( './service' );

var app = express();

app.get( '/', function( req, res, next ) {
    var reqId = req.get( 'X-request-id' );
    profile( reqId, 'getInfo' );

    server.getInfo( function( err, data ) {
        if ( err ) {
            res.send( 500 );
            return;
        }
        profile( reqId, 'getInfo' );

        profile( reqId, 'writeToFile' );
        fs.writeFileSync( path.join( __dirname, 'path', 'to', 'file' ), JSON.stringify( data ), 'utf8' );
        profile( reqId, 'writeToFile' );

        console.log( profile.getTimes( reqId ) );
        profile.done( reqId );
        res.send( data );
    });

});

app.listen( 8080, function() {
    console.log( 'Server started up' );
});

Profiling might look like:

[
  {
    "name": "getInfo",
    "elapsedTime": "0.51s"
  },
  {
    "name": "writeToFile",
    "elapsedTime": "1.32s"
  }
]

Better ctrl-z for Vim

I stumbled onto “How to boost your Vim productivity” the other day and it’s full of awesome Vim tips. One of them involved improving ctrl-z.

For those of you who don’t know. You can hit ctrl-z from within Vim and it puts Vim in the background and returns you to your shell. You can type in the command fg into your shell to get back into Vim. I use this workflow all the time. It’s much nicer to use an actual console to do file system stuff then many of the other Vim-specific workarounds out there (although I will say that vim-eunuch is awesome).

One huge problem with this approach is typing fg<CR> is annoying. And I never knew it until I read this article. The author has managed to map ctrl-z to not only send Vim into background mode but also return to Vim when you’re ready.

He achieves this through ZSH scripting. I’m a dedicated iTerm2 and BASH user, so I was immediately disappointed that this awesome tip didn’t apply to me.

So I figured out how to do it in BASH and iTerm2. You can take a look here: https://github.com/tybenz/ctrl-z.

It uses iTerm2’s custom key bindings with something called a coprocess. The coprocess itself is just a BASH script. Feel free to check it out. If you follow the steps, you’ll be up-and-running in no time and using ctrl-z up and down the street. Good luck nerds!

Presentation: JS Legos

I've been doing this talk on Reusable UI Components in JavaScript for a while now. I gave the last on at the HTML5 Developer Conference in May (hopefully will have a video soon). The talk is an adaptation from a previous blog post about reusable UI components in JavaScript. I've also given versions of the talk at jQuery Europe, jQuery San Diego and SSDG.

Slides are posted here:

http://tybenz.com/presentation-js-legos

Also, I'm releasing this small open-source UI component library as part of the talk, called lego.js. It's really just meant to be a repository of more in-depth code samples of the kind of components presented in the talk. Feel free to check it out:

http://tybenz.com/legojs

Here's a recording of the talk from jQuery Europe:

Here's a recording of the talk from jQuery San Diego: