Dynamically generating MIDI in JavaScript

Last weekend I open-sourced a small side project consisting of a JavaScript library that generates MIDI in a simple manner.

For example, the code to generate a MIDI file that plays 3 notes (C, E and G) would look like the following:

// We pass some notes to |MidiWriter.createNote| to create the MIDI
// events that define the notes that will be in the final MIDI stream. If
// no other parameters are specified to |createNote|, a NoteOff event
// will be inserted automatically, instead of letting the note ring forever.

// Disregard the |push.apply|, it is used here simply to flatten the
// resulting array, since |createNote| returns an array of events.

var noteEvents = [];
["C4", "E4", "G4"].forEach(function(note) {
    Array.prototype.push.apply(noteEvents, MidiEvent.createNote(note));
});

// Create a track that contains the events to play the notes above
var track = new MidiTrack({ events: noteEvents });

// Creates an object that contains the final MIDI track in base64 and some
// useful methods.
var song  = MidiWriter({ tracks: [track] });

// Alert the base64 representation of the MIDI file
alert(song.b64);

// Play the song
song.play();

// Play/save the song (depending of MIDI plugins in the browser). It opens
// a new window and loads the generated MIDI file with the proper MIME type
song.save();

The library is work in progress, but it already outputs standard MIDI files (SMF encoded in base64), and can play them in any browser that has a plugin that can reproduce MIDI installed. It is usually Quicktime in most machines.

And now, please allow me to rant a little bit…

The surprisingly bad state of MIDI in the browser

While developing this small library I was shocked at how inconsistent the state of MIDI reproducibility is in the browser. I might have been very naive, but I expected that a MIDI file would just sound right away inside a browser. Boy was I wrong.

Disclaimer: Any time I talk about reproducing MIDI, I am trying to do so using a data URI encoded in base64. Although all the browsers mentioned should be compatible with it, there are some bugs related to how data URI is supported when using it along with external plugins.

The new and shiny HTML5 <audio> element doesn’t support MIDI in any browser, so I had to revert to the good ol’ <embed> element, which supports it ONLY if the user has a proper plugin installed. All the browsers are using the Quicktime 7 plugin (I was very surprised to see that the mighty VLC can’t reproduce MIDI out of the box).

In some cases, even with the plugin installed, things didn’t work. That is the case for Webkit in Mac, which can’t reproduce the MIDI file generated by the library, while Webkit on Windows and Firefox 4b6 have no problems with it. I suspect this is due to bad interaction of the plugins with data URIs.

Of course you can always try to force a file download, but that’s not the point. It would be very nice if something as basic as cross-browser MIDI reproducibility would exist, and it is surprising that it is not possible.

But its inconsistent behavior isn’t actually the worse part. The worse part is that the user has to manually install a plug-in if she wants to be able to reproduce MIDI inside the browser. Of course iTunes and Media Player can play MIDI, but that’s not the experience I am aiming for. MIDI is still a strong standard nowadays (apparently the new iOS 4.2 is betting hard for it), specially in the world of professional musicians. For some reason this format has been ditched from HTML5 audio, or was never considered for it, leaving MIDI reproduction in the browser in the hands of mediocre plugins and careless browser vendors.

Try it!

Go and try it! Find bugs, suggest improvements to the API or features you would like to see there. And if you find solutions or have ideas on how to improve MIDI reproducibility in the browser, please don’t hesitate to tell me!

You can find the library here Remember that it is not complete and there is heavy work in progress going on, so expect that new versions will break compatibility (certainly) and keep updating it because I will improve it during the following weeks.