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).


52 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 !

  • Dom

    Hi

    I am currently trying to use your library in a project to build a browser based randomised arp sequencer in chrome

    At the moment however I am having a problem with the note messages:

    • when I playNote with duration the notes are always extremely short (dont hold)
    • I have ‘recorded’ this in ableton to confirm
    • The same with using stopNote (the original start note has already stopped!)
    • I have added a console log in webmidi.js line 2185 to log the message and I see this for a 1000ms duration:
      • [144, 65, 102]
      • [128, 65, 64]
      • More or less at the same time at the start of the note
    • The version is 2.0.0-rc.4

    I don’t want to raise a bug, I’d like to confirm I am not being a dunce first!

    Please let me know if you can help. Thanks for the great work.

    Dom

     

     

    • Jean-Philippe Côté

      That’s weird. I’ll take a look this afternoon.

    • Jean-Philippe Côté

      Dom,

      Indeed, there was an issue with time parsing. Thanks a lot for reporting it. I have now fixed it. Can you try the latest release (2.0.0-rc.5) and let me know if it works for you.

      Thanks.

  • Dom

    Wow. Great stuff!

    I will do so tonight. Has it been updated on npm?

    • Jean-Philippe Côté

      Yep.

      • Dom

        That fixed it!

         

        Many thanks for this great API, this will really give me time and the power to get really creative.

         

        Many thanks

      • Jean-Philippe Côté

        Excellent!

        If you encounter other issues, do not hesitate to file them on GitHub. If you have comments or suggestions, you can always contact me on Twitter.

        Cheers!

  • Marc Lacroix

    Salut Jean-Philippe,

    I have a Small question before I dive into using MIDI in the brower or Electron.
    I tried to work it out once, but all I got to was receiving midi, and crashing Chrome. :/

    What I’d like to achieve is this:

    • Get a midi signal from an external device
    • intercept this message in my app (probably Program Change)
    • Send a different MIDI message (CC) to an other program (most likely Logic or Mainstage).

    Do you think it’s possible to do that? When I tryed to send midi from Chrome, I couldn’t see it in my DAW…

    And how would I go about to send midi to an other software?

    • Jean-Philippe Côté

      Yep, you could definitely do that. To confirm, you can plug in your hardware devices and start the applications you want to communicate with. Then, simply use the following code to check if your devices are properly recognized by WebMidi.js:

      Obviously, you first need to link the library to your page.

      • Marc Lacroix

        hmmm…

        I’m gonna have to try again then. Thanks for the answer and for the great library!

        I’ll post here when I have something working 🙂

      • Marc Lacroix

        Alright, So I managed to make it work. It’s actually quite simple. But what I missed last time I tried, was that the app (in the browser) is not a visible device to other softwares. So on Mac OS I had to use the internal IAC manager to get the new midi message to an other software..

        My question now is, do you know if there’s a way to make a virtual device directly in JS using the MIDI API to avoid having to setup the IAC ports?

         

        Is there anyway to do something like this:

        var device = new MidiDevice(‘virtualDeviceName’); WebMidi.getOutputByName(‘virtualDeviceName’ );
        device.output.sendControlChange(20, 127, 11);

        Because if I was to make a utility app for this kind of use, it would be great if it works out of the box, without having to fiddle with other midi routing.

         

      • Jean-Philippe Côté

        The ability to create virtual MIDI devices with the Web MIDI API has been left out of version 1 of the spec. However, this is a very common feature request, even from big players like Korg and Yamaha. There is an ongoing discussion on the subject that dates back to 2013.

      • Marc Lacroix

        Interesting. It looks more complicated than we would think I suppose.

        Anyways, I thought I’d try something different and find a few node modules that interact with native midi. So setup Electron and installed a couple of node modules, and voilà, I got my virtual devices. If I end up with something nice, I’ll share it here!

        Cheers

      • Jean-Philippe Côté

        That’s the spirit! Do share. 🙂

  • Vassilis

    A_M_A_Z_I_N_G !
    Thank you very much JP!
    I’ve already build an excellent (for my needs) controller for Yamaha Motif XS.
    Even if it is by its own a great instrument, i gave to it endless capabilities and awsome new features…using wma and your library on chrome, through sending sysex  messages.
    It works PERFECTLY  for me without any issues.
    The only thing (of those i need) that i didn’t get to work yet, is to receive sysex back to chrome from my instrument.
    This is how i try…

    (when i’m replacing ‘sysex ‘ with ‘pitchbend’ for testing and set undefined midi channels to “all” the corresponding function is responding very normally.)
    which is the correct syntax of recieving sysex?
    An example would be great!
    Thank you one more time

    • Jean-Philippe Côté

      Hello Vassilis,

      Thank you for your nice comments. I added an example to the documentation. Hopefully, it will help you out.

      Cheers!

  • Vassilis

    Hmm…
    Unfortunately, still can’t understand the way to assign a specific incoming sysex message to a function.
    In the documentation about  Output/sendSysex  it is pretty clear to understand the
    structure of an outgoing sysex msg.
    As i mentioned earlier, my function is being completed if i choose ‘pitchbend’ to trigger it and i tried the same with “noteon” and its working perfectly too, but what if i want to specify exactly witch note to trigger the function? Any syntax i tried i get to the same error as with sysex. (Uncaught (in promise) TypeError:)
    p.s i had already enabled sysex (true) for the second time
    -Sorry for long commenting-
    Regards from Greece!

     

Post a comment

Your email address will not be published.