MIDI Utilities for JavaScript
This is a very simple collection of MIDI data utilities for JavaScript. They are meant to process raw MIDI data bytes and not to talk with MIDI devices themselves. This library does not depend on any 3rd-party libraries so far (with the exception of an EventEmitter when not used in Node).
For connecting to MIDI devices and receiving/sending MIDI data, I can suggest the very good node-midi Node library. Check it out!
To use any of the included classes you simply need to include the relevant JavaScript file into your HTML page (or 'require' it if using Node). For example:
<script src="midiparser.js"></script>
<script src="mididamperpedal.js"></script>
<script src="midisostenutopedal.js"></script>
<script src="midisoftpedal.js"></script>
OR you can use the combined minified bundle that includes everything (recommended!):
<script src="midiutils.min.js"></script>
Please note that all of the classes are event emitters and rely on an existing EventEmitter object implementation to inherit/use. For example if you want to use the MidiParser
class:
- If using Node, this object comes built-in and can be easily used like this:
var EventEmitter = require('events').EventEmitter;
var MidiParser = require('./midiparser');
var mp = new MidiParser();
- If using the browser or other environments, you can use the very good (EventEmitter) from Oliver Caldwell (Wolfy87) by simply loading it before any other class in this package:
<script src="eventemitter.min.js"></script>
<script src="midiutils.min.js"></script>
Minifying and Bundling
To create or update the combined minified bundle, you can use UglifyJS:
$ uglifyjs --comments --mangle sort=true --compress --output midiutils.min.js --stats \
copyright.js midiparser.js mididamperpedal.js midisoftpedal.js midisostenutopedal.js
MidiParser class
A very simple event-driven MIDI messages parser for JavaScript. This class parses raw MIDI bytes arrays (must be Uint8Array
objects) and generates relevant higher-level MIDI events. You must add your own event handlers for it to do anything useful. The following example illustrates how to handle some events:
var mp = new MidiParser();
mp.on('note-off', function (channel, note, velocity) {
console.log('Note-Off on channel %d. Note: %d with velocity %d',
channel, note, velocity);
});
mp.on('note-on', function (channel, note, velocity) {
console.log('Note-On on channel %d. Note %d with velocity %d',
channel, note, velocity);
});
mp.on('damper-off', function (channel) {
console.log('Damper Pedal released on channel %d', channel);
});
mp.on('damper-on', function (channel) {
console.log('Damper Pedal pressed on channel %d', channel);
});
// Test some MIDI events
mp.parse(new Uint8Array([144, 24, 100]));
mp.parse(new Uint8Array([177, 64, 127]));
mp.parse(new Uint8Array([145, 36, 0]));
Note: this parser handles the special case of a note-on
event with zero velocity as a note-off
event, this way you do not need to worry about semantics. See the output from the third example test.
The full list of supported MIDI events and handler arguments is below:
note-off
andnote-on
with (channel, note, velocity) arguments.key-pressure
with (channel, note, pressure) arguments. This event is commonly known as Poly Aftertouch in the MIDI world.program-change
with (channel, number) arguments.channel-pressure
with (channel, pressure) arguments. This event is commonly known as Aftertouch in the MIDI world.pitch-bend
with (channel, value) arguments. The value already merges the MSB and LSB bytes, providing the full 14 bits bend value.bank-select
,mod-wheel
,breath-controller
,foot-controller
,portamento-time
,volume
,balance
,pan
,expression-controller
,effect-control-1
,effect-control-2
,gp-controller-1
,gp-controller-2
,gp-controller-3
,gp-controller-4
,bank-select-fine
,mod-wheel-fine
,breath-controller-fine
,foot-controller-fine
,portamento-time-fine
,volume-fine
,balance-fine
,pan-fine
,expression-controller-fine
,effect-control-1-fine
,effect-control-2-fine
,gp-controller-1-fine
,gp-controller-2-fine
,gp-controller-3-fine
,gp-controller-4-fine
,sound-variation
,timbre-intensity
,release-time
,attack-time
,brightness
,decay-time
,vibrato-rate
,vibrato-depth
,vibrato-delay
,snd-controller-10
,gp-controller-5
,gp-controller-6
,gp-controller-7
,gp-controller-8
,portamento
,reverb-depth
,tremolo-depth
,chorus-depth
,detune-depth
andphaser-depth
with (channel, value) arguments. These are all Control Change (CC) MIDI events.damper-off
,damper-on
,portamento-off
,portamento-on
,sostenuto-off
,sostenuto-on
,soft-off
,soft-on
,legato-off
,legato-on
,hold2-off
andhold2-on
with (channel) argument. These are all binary valued Control Change (CC) MIDI events commonly associated with instrument pedals.all-sound-off
,reset-all-controllers
andall-notes-off
with (channel) argument. These are all Control Change (CC) MIDI events.unknown-control-change
with (channel, control, value) arguments. If a Control Change (CC) MIDI event is received but can not be parsed, it is passed cleanly using this event handler.unknown-message
with (bytes) argument. If the MIDI bytes array can not be parsed, it is passed cleanly using this event handler.
The MidiParser
class also provides convenient standard General MIDI 1 (GM1) note-to-name, drumNote-to-name, programNumber-to-name and programNumber-to-familyName static mappings. For example:
var mp = new MidiParser();
console.log(mp.getGM1NoteName(0x50)); // note
console.log(mp.getGM1DrumNoteName(0x40)); // note
console.log(mp.getGM1ProgramName(0x10)); // program number
console.log(mp.getGM1FamilyName(0x20)); // program number
MidiDamperPedal class
A very simple event-driven MIDI damper pedal emulator for JavaScript. This class emulates the behaviour of a damper pedal (sometimes called a sustain pedal) by controlling the passage of note-on
and note-off
messages. This pedal's working is detailed in this Wikipedia article.
When the pedal is pressed, note-off
messages are held until the pedal is released, then they are all replayed back. This class works like an event filter designed to easily work together with the MidiParser
or other pedal emulation classes, however you can use it standalone too. See usage example:
// First the MidiDamperPedal
var mdp = new MidiDamperPedal();
mdp.on('note-off', function (channel, note, velocity) {
console.log('Note-Off on channel %d. Note %d with velocity %d',
channel, note, velocity);
});
mdp.on('note-on', function (channel, note, velocity) {
console.log('Note-On on channel %d. Note %d with velocity %d',
channel, note, velocity);
});
mdp.on('damper-off', function (channel) {
console.log('Damper Pedal released on channel %d', channel);
});
mdp.on('damper-on', function (channel) {
console.log('Damper Pedal pressed on channel %d', channel);
});
// Now the MidiParser and redirect events to the damper pedal
var mp = new MidiParser();
mp.on('note-off', function (channel, note, velocity) {
mdp.noteOff(channel, note, velocity);
});
mp.on('note-on', function (channel, note, velocity) {
mdp.noteOn(channel, note, velocity);
});
mp.on('damper-off', function (channel) {
mdp.release(channel);
});
mp.on('damper-on', function (channel) {
mdp.press(channel);
});
mp.on('unknown', function (byte1, byte2, byte3) {
console.log('Unknown event with bytes [%d, %d, %d].',
byte1, byte2, byte3);
});
// Test some events, check the order of damper pedal emitted events
mp.parse(new Uint8Array([176, 64, 127]));
mp.parse(new Uint8Array([144, 24, 100]));
mp.parse(new Uint8Array([144, 24, 0]));
mp.parse(new Uint8Array([144, 36, 90]));
mp.parse(new Uint8Array([144, 36, 0]));
mp.parse(new Uint8Array([176, 64, 0]));
MidiSostenutoPedal class
A very simple event-driven MIDI sostenuto pedal emulator for JavaScript. This class emulates the behaviour of a sostenuto pedal by controlling the passage of note-on
and note-off
messages. This pedal is detailed in this Wikipedia article.
When the pedal is depressed, note-on
messages without a corresponding note-off
are remembered until the pedal is pressed (pre-pedal held notes). Then, all further note-off
messages are passed except for those of the pre-pedal notes, which are held. When the pedal is released, all the held note-off
messages are replayed back. This class works like an event filter designed to easily work together with the MidiParser
or other pedal emulation classes, however you can use it standalone too. See usage example:
// First the MidiSostenutoPedal
var msp = new MidiSostenutoPedal();
msp.on('note-on', function (channel, note, velocity) {
console.log('Note-On on channel %d. Note %d with velocity %d',
channel, note, velocity);
});
msp.off('note-on', function (channel, note, velocity) {
console.log('Note-Off on channel %d. Note %d with velocity %d',
channel, note, velocity);
});
msp.on('sostenuto-off', function (channel) {
console.log('Sostenuto Pedal released on channel %d', channel);
});
msp.on('sostenuto-on', function (channel) {
console.log('Sostenuto Pedal pressed on channel %d', channel);
});
// Now the MidiParser and redirect events to the sostenuto pedal
var mp = new MidiParser();
mp.on('note-off', function (channel, note, velocity) {
msp.noteOff(channel, note, velocity);
});
mp.on('note-on', function (channel, note, velocity) {
msp.noteOn(channel, note, velocity);
});
mp.on('soft-off', function (channel) {
msp.release(channel);
});
mp.on('soft-on', function (channel) {
msp.press(channel);
});
mp.on('unknown', function (byte1, byte2, byte3) {
console.log('Unknown event with bytes [%d, %d, %d].',
byte1, byte2, byte3);
});
// Test some events, check the order of sostenuto pedal emitted events
mp.parse(new Uint8Array([144, 24, 100]));
mp.parse(new Uint8Array([176, 66, 127]));
mp.parse(new Uint8Array([144, 24, 0]));
mp.parse(new Uint8Array([144, 36, 90]));
mp.parse(new Uint8Array([144, 36, 0]));
mp.parse(new Uint8Array([176, 66, 0]));
MidiSoftPedal class
A very simple event-driven MIDI soft pedal emulator for JavaScript. This class emulates the behaviour of a soft pedal by controlling the passage of note-on
messages. This pedal is detailed in this Wikipedia article.
When the pedal is pressed, all velocities of incoming note-on
messages are scaled using a softenFactor
setting (default to 2/3
). This class works like an event filter designed to easily work together with the MidiParser
or other pedal emulation classes, however you can use it standalone too. See usage example:
// First the MidiSoftPedal
var msp = new MidiSoftPedal();
msp.setSoftenFactor(0.5); // Optionally set a custom soften factor
msp.on('note-on', function (channel, note, velocity) {
console.log('Note-On on channel %d. Note %d with velocity %d',
channel, note, velocity);
});
msp.on('soft-off', function (channel) {
console.log('Soft Pedal released on channel %d', channel);
});
msp.on('soft-on', function (channel) {
console.log('Soft Pedal pressed on channel %d', channel);
});
// Now the MidiParser and redirect events to the soft pedal
var mp = new MidiParser();
mp.on('note-off', function (channel, note, velocity) {
console.log('Note-Off on channel %d. Note %d with velocity %d',
channel, note ,velocity);
});
mp.on('note-on', function (channel, note, velocity) {
msp.noteOn(channel, note, velocity);
});
mp.on('soft-off', function (channel) {
msp.release(channel);
});
mp.on('soft-on', function (channel) {
msp.press(channel);
});
mp.on('unknown', function (byte1, byte2, byte3) {
console.log('Unknown event with bytes [%d, %d, %d].',
byte1, byte2, byte3);
});
// Test some events, check the velocity values of soft pedal emitted events
mp.parse(new Uint8Array([144, 24, 100]));
mp.parse(new Uint8Array([144, 24, 0]));
mp.parse(new Uint8Array([176, 67, 127]));
mp.parse(new Uint8Array([144, 24, 100]));
mp.parse(new Uint8Array([144, 24, 0]));
mp.parse(new Uint8Array([176, 67, 0]));
mp.parse(new Uint8Array([144, 24, 100]));
mp.parse(new Uint8Array([144, 24, 0]));
Note: the note-on
event from the soft pedal will have the modified velocities if the pedal is pressed.
Acknowledgements
Many thanks go to my excellent beta tester friend and pianist Mario Arias (@2xMcK). You can see him performing in this video and also in this video. Kudos for all the feedback!