Web MIDI: Music and Show Control in the Browser


October 6, 2015 Jean-Philippe Côté
published in

Earlier this year, Google released Chrome 43. This release marked the official introduction of an amazing new feature: MIDI in the browser! The magnitude of this news for fans of physical computing cannot be overstated. This means you can a) control external MIDI devices from JavaScript and b) use MIDI devices to control what’s happening in your web browser. I almost cried… And the best part is: it actually works!

About MIDI

The MIDI (short for Musical Instrument Digital Interface) protocol is over 30 years old. It has stood the test of time and is now supported in nearly all electronic musical instruments known to mankind. If you want to interface with synths, samplers, wind controllers, sound modules and drums machines, MIDI is the way to go (there is also OSC but that’s for another article). It also works with various non-traditional controllers and various control surfaces. If you are curious, here are a few crazy-cool ones: AlphaSphere,(link down), Tenori-on, Pianocade, Da Fact,  Breath and bite controller.

MIDI is also supported in pretty much all music software and various development frameworks such as Pure Data, Max/MSP and openFrameworks. This means that it is an ideal protocol to exchange live or recorded data with other software on your machine.

MIDI is even used as a show control protocol designed to drive various stage and entertainment equipments such as lighting gear, smoke machines, pyrotechnic devices, etc. This is pretty powerful stuff… and, thanks to Google Chrome and the Web MIDI API,  it’s now available in your browser.

About the Web MIDI API

The original release of the Web MIDI API specification is actually 3 years old. It was first released in October 2012. However, without a browser vendor implementing the standard, it wasn’t of much concrete use. It took version 43 of Google Chrome to finally have an official widespread implementation. For a few versions before that, you could manually enable it with  chrome://flags/#enable-web-midi but is is now activated by default. Opera has had support since version 32. According to caniuse.com, there is nothing on the horizon for other browsers.

If you are using the Electron framework, you should be good to go. Users of version 0.12 of NW.js will need to include "chromium-args": "--enable-web-midi"  in their package.json  file for MIDI support to kick in.

Getting ready

Before we start, we urge you to verify your setup by trying out one of the few online synthesizers already available. The first thing to do is to plug in your MIDI keyboard (or other MIDI controller). Then you should start (or restart) your browser. Once this has been done, try HeisenbergWebSynthsViktor NV-1 or MIDI Synth. If everything went well, you should hear the notes you are playing on your physical keyboard.

Amazingly enough, you can even play more than one synths at the same time by opening them in different windows. Wow!

Now, before we start writing some code, it might be a good idea to decide if you should use the Web MIDI API directly or if it’s worth it to use a library to make things a bit simpler. While the Web MIDI API is relatively simple, it is also relatively low-level.

To give you an idea, here is an example of the code needed to play a 3rd octave C for 1 second on channel 3  of the first available device.

That’s quite a bit of code for what is a relatively simple thing. The worst part is having to figure out the byte values for a note on event on channel 3 (0x92) and the equivalent note off (0x82). Unfortunately, if you stick with the bare Web MIDI API, this is what you will have to do.

By the way, receiving MIDI messages is just as equally convoluted…

Faced with this situation, I decided to write a library to make all this simpler. The library is called WebMidi.js. Here is how you would do the same thing using the library:

For archival purposes, here is the same code for version 1.x of WebMidi.js (documentation for version 1.x will remain available) :

If you agree that it’s more fun this way, I invite you to download WebMidi.js from GitHub, or to install it from Bower or NPM. Check out the README file on GitHub for installation details.

As you may have guessed, from this point on, the code in this tutorial takes it for granted that you have WebMidi.js installed.

There are other alternatives to the Web MIDI API. If you are developing your project in NW.js, Electron or Node.js, you could use the midi NPM module. This module is a wrapper for the very powerful and cross-platform (Mac, Linux & Windows) RtMidi C++ library.

Sending Messages

What is so exciting about MIDI support is the amount of access it grants you. You can control any external hardware MIDI device but also any MIDI-compatible software endpoints. In terms of physical computing, what’s perhaps the most exciting is the ability to talk to other popular physical computing frameworks. For example, interfacing with Pure Data or Max is super easy.

So, how can we send commands to MIDI devices? Well, first you need to identify the target MIDI channel you want to send the command to. You can do that by printing out all the available output channels. 

If you look at Chrome’s debugging console, you should see an array of all available outputs. Obviously, you need to plug in your devices first. In my case, I have 5 available output devices (0, 1, 2, 3 and 4) and the device at position 3 is my Axiom Pro 25.

Now that you know how to identify the outputs available on your system, the second thing you need to know is the type of data you wish to send. MIDI messages can be broken down into two groups: messages sent to a single channel and system messages sent globally. The first group is the most common. Here is a list of channel-related MIDI commands:

  • noteon: play a note;
  • noteoff: stop playing a note;
  • keyaftertouch: alter the pressure value associated with a specific note;
  • controlchange: send control information (modulation, volume, pan, etc.);
  • channelmode: send channel status (all sounds off, reset, all notes off, etc.);
  • programchange: change to a specific patch/program number;
  • channelaftertouch: alter the global pressure associated with a channel;
  • pitchbend: bend the pitch of the sound by a certain amount.

Let’s look at how to send common messages: note on, note off, controlchange, channelaftertouch and pitchbend. As you will see, all these commands have their own dedicated function:

As you can see, it’s very straightforward. Also, it works in the exact same way for the other channel-related send commands (channel mode, program change, etc.) which all have equivalent functions ( sendChannelMode() , sendProgramChange() , etc.).

There is a second group of commands made up of system-global commands (sysex, timecode, clock, etc.). Advanced users might need them but if you are starting with MIDI, you probably will not use them. Having said that, they also work in the exact same way. The only difference is that you do not need to specify a channel for them.

Receiving Messages

Perhaps even more exciting than sending MIDI commands is the ability to let your web page be controlled by an external hardware or software MIDI device. To allow that, you simply need to register a function to be fired when specific MIDI events occur. Here are examples of doing that for common messages:

Obviously, wherever an anonymous function is used in the above code ( function(e){ console.log(e); }), you could call your own function or do whatever you please.

Surely, if you can add event listeners, you should also be able to check for their presence and remove them if needed. You can do that as such:

If you need to check for the presence of event listeners or be able to remove them later, you need to keep a reference to the callback function that has been assigned. Therefore, you shouldn’t use an anonymous function in such cases. In the example above, we simply assign the test()  function which we are later able to use to with hasListener()  and removeListener() .

Conclusion

This pretty much rounds it up. While we have looked at the most popular scenarios, you should be aware that the WebMidi.js library offers quite a bit more. For all details, check out the full API documentation which is available online or the in the download package (in the docs folder).

I hope this tutorial has proven useful and, as always, I invite you to participate by leaving comments below. Cheers!


The synthesizer image is from Bernd Sieker and is used in accordance with the Creative Commons license (CC BY-SA 2.0).


35 Comments

  • Alex

    Hi,

    Thanks very much for this article- very helpful!

    I’ve been trying to get your WebMidi library to work. I have the Jazz Midi plugin installed, and am using the WebMIDIAPIShim. I can’t get notes to play, unfortunately.

    It looks like on a Mac, at least (OS X Yosemite/ El Capitan, tested in FireFox/Safari), it’s necessary to open the MIDI output port before you can send messages to it. I’ve been poking around in your WebMidi library, but I can’t see anywhere where you’ve done this. I’m sure it would be an easy fix to add it, though. Could you possibly have a look, if you get a chance?

    Incidentally, sorry if this isn’t the right place to post this. I was trying to work out how to post a comment on the project GitHub page, but am new to GitHub (just started using it myself a couple of weeks ago), and couldn’t work out how to do  it, unfortunately.

    Thanks very much!

    Incidentally, I’m working on an acid-style bassline-generator web-app, if you’re interested. I only have a local copy of it at the moment, but will upload a live version soon. It’s been a really fun project to work on!

    • Jean-Philippe Côté

      The library has only been tested within Chrome using their native implementation. I have not tested it with WebMIDIAPIShim or external plugins. With browser plugins currently being phased out, I’m not sure I ever will… 😉

      Keep me posted about your project. Cheers!

       

    • Jean-Philippe Côté

      I just tested v2.0.0a of WebMidi with the Jazz-Plugin and the WebMIDIAPIShim polyfill. As far as my limited tests go, it seems to be working flawlessly. I added a section to the GitHub README file that explains how to do it.

  • Alex

    Hi,

    thanks for getting back to me so quickly!

    I will try WebMidi in Chrome when I get home. At the moment, though, I’m rolling my own much more limited library, so I can get MIDI working in other browsers (using Jazz, obviously).

    I do share your distaste for browser plugins, by the way. I look after a large number of Mac here at work, and Flash Player and its constant demands to be updated is one of the banes of my working life.

  • Alex

    Here’s my Bassline Explorer, as it stands. Needs to be connected to a synth with mono legato mode, and something like filter cutoff (and preferably filter envelope decay) mapped to velocity.

    • Jean-Philippe Côté

      Sounds cool! When I get a minute I’m going to check it out. Cheers!

  • alx

    The current version doesn’t work in Chrome, ironically, as it relies on Jazz. I’m redoing it to use the WebMIDIAPIShim, so it should then work in Chrome, AND other browsers, with Jazz installed.

    You’re right of course about plugins being phased out- and I don’t have a problem with that, if only so I can *finally* see the back of Flash…!

    The idea of my project was that it was a test of the firmware for a Eurorack module, specifically for use with the Open Music Labs x0x Heart module. Of course, once I started messing around with JavaScript, it turned into something else. But, if the page stops working in a couple of years, it’s no big deal for me, to be honest. Also, with the shim, I can write standard-compliant Web MIDI code, anyway, so the extra work shouldn’t be wasted, assuming the current WM spec does become standard, and is taken up by other browser vendors.

    a|x

  • Tyler

    Using webmidi with my Roland SP-404SX to great effect – thanks for sharing this!

    -Tyler from Rochester NY USA

  • Bret

    Great article.  How I can send a noteon event to the GM device of the client instead of playing samples? I tried sending to the the outputs[0].id  with no luck. Can you point me in the right direction? Thanks!

    • Jean-Philippe Côté

      I’m not sure I understand your question. If you have a MIDI device that has support for GM, you simply set that device so it uses GM and send a noteon event to it. Maybe if you provide me with more details I can be of more help.

  • Jean-Jacques Ebanga

    Jean-Philippe, i have just tested https://jsfiddle.net/KeithMcMillenInstruments/zma6pzt9/?utm_source=website&utm_medium=embed&utm_campaign=zma6pzt9, on Chrome, but it doesn’t run with my device. I going to follow your path.

    • Jean-Philippe Côté

      Cool. Let me know how it turns out!

      • Jean-Jacques Ebanga

        Finally it works, i just turned on, on chrome, parameter that allows website to interact with MIDI device.

      • Jean-Philippe Côté

        You probably have an older version of Chrome because, in newer versions, this is no longer necessary.

  • Vibeke Bertelsen

    I am a bit confused here. Does this API only work with the jazz plugin installed? Does it work in Chrome? I am having problems ‘MidiOutput not defined’ when I try to send a msg. I am able to get an array of the output devices though so some things are working

  • Xulio

    I just figured out there is no way to send sysex arbitrary messages like 0xF0 0xAA 0xF7 because of the manufacturer approach. Do you think it makes sense to have one more sendSYSEX function for arbitrary messages or do you discourage such thing?

     

    • Jean-Philippe Côté

      Are you allowed to do that according to the MIDI spec? If you are, can you point me in the direction of a document explaining how this works?

      In any case, you can always use the send() method to send arbitrary sysex data: send([0xF0, 0xAA, 0xF7]);. Because of that, I don’t really see a reason to add another function.

      Hope this helps.

  • GoodGrief

    Hello

    Is it possible to use WebMidi library with TypeScript? Are there any type definitions available?

    Thanks

    • Jean-Philippe Côté

      I do not use TypeScript but I believe it is possible to use WebMidi.js in TypeScript by simply forgoing the type checking for that object. I think this is how it’s done:

      declare var WebMidi:any;

      But again, I do not use TypeScript so I may be completely off here.

  • mw

    Is there any method to get active/current values for cc(s) without manually sending sysex from device? i.e.
    // with cc# and chan#
    var curVolume = output.getCurrentValue(‘7’, 1); // 120

    For reasons of building a UI that will render to current settings when connection is enabled.  Thanks!

    • Jean-Philippe Côté

      As far as I know, the current state of a device cannot be retrieved using regular MIDI. As you said, some devices will allow you to read a device’s current settings via sysex but, obviously, this will vary from one manufacturer to another (if at all supported). This may or may not be suitable for you scenario.

      I guess you will have to implement that on your end. You could initialize all controllers to a know value with Output.sendControlChange() and then store those values locally. Anytime, you send a new value, you update the local value. You would also have to update the local values when receiving messages from the device.

      I don’t see any reason why this couldn’t be done. Hope this helps!

      P.S. At one point maybe WebMidi.js could do that but I’m afraid it would add a lot of overhead…

      • mw

        Thanks for the reply Jean-Philippe!  I agree could just set defaults but from it is ideal to see the state of the patch.  BTW, here is some vendor doc I located related to sysex:

        http://l6c-acdn2.line6.net/data/6/0a060b316ac34f0593ef290ff/application/pdf/POD%20Pro%20Sysex.pdf

        00 SYSEX DATA DUMP REQUEST:

        Type:

        00: Program Patch Dump Request

        0xF0 0x00 0x01 0x0C 0x01 0x00 0x00 <program #> 0xF7

        <program #> = 0x00 ~ 0x23 (1A ~ 9D internal programs)

        POD Pro responds with Program Dump (01 00)

        Now I’m versed in general web JS and been a midi user for years but still learning the actual low level midi api.  Does the above look usable with the scenario I described?  Thanks for your work on the project and your responses!

  • Jean-Philippe Côté

    I don’t know this specific device but, from what you referenced, I think you are on the right track. You probably want to request a data dump only at start and then track the changes yourself. Data dumps are usually large and might impede performance if you were to repeatedly request them.

    MIDI is a great protocol but it does have some shortcomings. Not being able to query a device for a specific controller value is one of them…

    Let me know how it goes!

    • mw

      So some decent success but not 100% there.  I’ve been able to easily communicate cc changes with device’s input/output and I’ve been able to receive sysex byte array when I manuallly dump patch sysex from the device.  I have not been able to get the device to respond to a sysex dump request as yet.  My code may be wrong but another thing is that the doc posted above is from the original version of the device, I have v3 so perhaps the spec has been altered.  I do not think they have exposed this spec on the web that i can find.  Here is the code I’m using:

      WebMidi.enable(function(err) {

      if (err) console.log(“Could not enable WebMidi.”);

      var podOut = WebMidi.getOutputByName(‘PODxt’);

      var podIn = WebMidi.getInputByName(‘PODxt’);

       

      podIn.addListener(‘sysex’, undefined, function() {

      console.log(arguments)

      });

      $(‘button’).on(‘click’, function() {

      podOut.sendSysex([0, 1, 12], [1, 0, 0, 0]);

      });

      }, true);

      Again the sysex handler will fire but only with manual dump, not on sendSysex().  See anything incorrect?  Thanks!

      • Jean-Philippe Côté

        As far as I can tell, this looks fine.

        By the way, you can use hex numbers in the sendSysex() function. Even if it’s a bit longer, it tends to make it easier to follow the specifications:

        podOut.sendSysex([0x00, 0x01, 0x0C], [0x01, 0x00, 0x00, 0x00]);

      • mw

        Ok thanks for the review.  Yeah using hex may be good for consistency and visual parsing.  I have a request into the manufacturer for the updated spec sheet without much expectation since it is an old product.  Either way great job on WebMidi and I look forward to continued use and perhaps contributing.  Thanks!

      • Jean-Philippe Côté

        Cool, thanks!

  • Brock

    This is really awesome man, great work on this! Ive been looking for something like this. Im pretty new to code, I can get it this up and running and receive midi from my keyboard, I would like to be able to trigger different javascript functions with the different notes I play. Do you have any sample code on how to get the notes of the midi signal your recieving in “noteon”?

    • Jean-Philippe Côté

      When you listen on an input, the callback function receives an event object. This object contains all the information you want. For example, it has a note property containing the note’s name, number and octave. So, if you wanted something to be triggered when C2 is received, you could do the following:

      The documentation contains all that information. For example, check out the page on the “noteon” event.

      Cheers!

      • Brock

        Awesome. Thanks so much for the timely response! That little piece of code helps me more than you know! I better understand the documentation and can use your syntax to help me figure out more. You need to have a workshop in L.A!!

        cheers!

      • Jean-Philippe Côté

        That’s funny you should say that because I’m actually developing a workshop on the Web MIDI API and WebMidi.js. I’d love to present it in L.A !

Post a comment

Your email address will not be published.