Migrating from 2.5.x to 3.x
This document is a work in progress. Your feedback is critical in identifying and, hopefully, mitigating migration pitfalls. 🙏🏻 Please share your observations, suggestions and issues in the Migration section of the forum so they can benefit others!
Highlights of the New Version
Version 3.x is a major update. It moves WEBMIDI.js from a smallish hobby project to a more serious long-term endeavour. Here are some key highlights:
- Modern architecture with ESM (ECMAScript module), IIFE (Immediately Invoked Function Expression) and CJS (CommonJS) flavours
- Full Node.js support
- TypeScript definitions files
- Various new objects:
InputChannel
andOutputChannel
objects to communicate with a single MIDI channelNote
object to store and pass around note informationMessage
object to better encapsulate MIDI messagesForwarder
object to allow message forwarding from an input to an output- and others...
- More and better unit tests to prevent regression issues
- Support for promises where appropriate (such as
WebMidi.enable()
) - More granular events. For example:
midiaccessgranted
to know when a user clicked the MIDI authorization promptcontrolchange-controllerXXX
to listen to a single type of control change message- and various others...
- Ability to query current note state (currently playing or not) with
InputChannel.getNoteState()
andInputChannel.notesState
array - Ability to unplug and replug a device while retaining its state
- Better sysex (system exclusive) message support
- Octave transposition can be performed at the global, input/output or channel level
- and so much more!
Backwards Compatibility
Backwards compatibility was a major concern while developing the new version. While every effort has been made to ease the transition, the new version might break a few things, mostly in edge cases.
Whenever it was possible, we deprecated old practices instead of completely dropping support. This means that your code should continue to work but you may see warnings in the console about the new ways to do things.
If you expected certain things to keep working after upgrade and they don't, please reach out in the Migration section of the forum so whe can assess if the behaviour is expected or not and troubleshoot from there.
Architecture Change
In v2, there was a top-level WebMidi
object that provided access to a list of inputs and outputs.
Most of the activity happened at the Input
and Output
level. This is where you would listen for
inbound messages and send outbound messages.
For example, if you wanted to listen for a message on a single MIDI channel, you would have
specified it in the call to addListener()
in this manner:
// In WebMidi.js version 2.5.x
WebMidi.inputs[0].addListener("noteon", 7, someFunction);
This would listen for noteon events on MIDI channel 7 of input 0. While this still works in v3, the preferred syntax would be:
WebMidi.inputs[0].addListener("noteon", someFunction, {channels: [7]});
You may think (rightly so!) that this syntax is more cumbersome. The reasoning is that, if you want
to listen to events on a single channel, you should do so on the channel itself
(the new InputChannel
object):
WebMidi.inputs[0].channels[7].addListener("noteon", someFunction);
Here, WebMidi.inputs[0].channels[7]
refers to an
InputChannel
object that has, for the most part, the same methods
as the Input
object you were used to in v2.5.x.
So, the idea is to use the Input
object if you need to listen to events on
more than one channel and to use the InputChannel
object to listen
for events dispatched by a single channel.
The exact same logic applies to the Output
and
OutputChannel
objects. For example, if you want to send a
controlchange message to all channels of an output, you can use:
WebMidi.outputs[0].sendControlChange("resonance", 123);
To send to multiple, but specific, channels, you can use:
WebMidi.outputs[0].sendControlChange("resonance", 123, {channels: [1, 2, 3]});
To send the message to a single channel (e.g. 7), you would use:
WebMidi.outputs[0].channels[7].sendControlChange("resonance", 123);
In the end, the idea is to target specifically what is appropriate. Therefore, a more idiomatic snippet would be something like this:
const output = WebMidi.getOutputByName("My Awesome Midi Device");
const synth = output.channels[10];
synth.playNote("A4");
Having said all that, let me reiterate that the previous way of doing things will still work in v3. This will give you a chance to smoothly transition to the new version.
Let's recap. In v3, there is a top-level WebMidi
object which has both
an inputs
and an outputs
array. These arrays contain, respectively, a list Input
and
Output
objects. The Input
and
Output
objects have a channels
array that contains a list of
InputChannel
or OutputChannel
objects.
You can also check the CHANGELOG.md file for more hints of what has changed in version 3.
Things to Watch Out For
The WebMidi.enable()
method now returns a promise
You can still use a callback with WebMidi.enable()
and it
will work just like before. However, you are now welcome to use the promise-based approach:
WebMidi.enable().then(() => {
console.log("WebMidi.js has been enabled!");
})