Quick start

import { on, toNoteName, int7ToFloat } from './midi/module.js';

// Listen to incoming 'noteon' and 'noteoff' messages on channel 1
on({ channel: 1, type: 'note' }, (e) => {
    // Time, port and message are standard properties of the DOM event
    const time    = e.timeStamp;
    const port    = e.target;
    const message = e.data;

    // Get the note name
    const name = toNoteName(message[1]);

    // Normalise velocity to the range 0-1
    const velocity = int7ToFloat(message[2]);
});

Note that on this page functions have been imported to the object MIDI so that you may try them in the console.

Functions

availability

request()

Returns a promise that resolves to the midiAccess object where it is available. Where the underlying navigator.requestMIDIAccess() method is undefined, or where MIDI is unavailable for some reason, returns a rejected promise.

request().catch(function(error) {
    // Alert the user they don't have MIDI
});

receive

inputs()

Returns the map of MIDI input ports from the underlying MIDIAccess object.

on(selector, fn)

Registers a handler fn for incoming MIDI events that match object selector. A selector is either an array (or array-like) in the form of a MIDI message [status, data1, data2]:

// Call fn on CH1 NOTEON events
on([144], fn);

// Call fn on CH1 NOTEON C4 events
on([144, 60], fn);

// Call fn on CH1 NOTEON C4 127 events
on([144, 60, 127], fn);

or a bit more conveniently an object of interpretive data of the form {channel, type, name, value}:

// Call fn on CH2 NOTEON events
on({ channel: 2, type: 'noteon' }, fn);

// Call fn on CH2 NOTEOFF C4 events
on({ channel: 2, type: 'noteoff', name: 'C4' }, fn)

// Call fn on CH2 NOTEON and NOTEOFF C4 events
on({ channel: 2, type: 'note', name: 'C4' }, fn)

// Call fn on CH4 CONTROL 1 0 events
on({ channel: 4, type: 'control', name: 'modulation', value: 0 }, fn)

Note that these selector properties are progressive. A selector may not have a type if it has no channel, it may not have a name without a type, and may not have a value without a name property. Selectors pre-create paths in a distribution tree that is optimised for incoming events to flow through.

Finally, a selector may optionally have a property port, the id of an input port.

// Call fn on CH4 CC 64 events from port '0123'
on({ port: '0123', 0: 179, 1: 64 }}, fn);

// Call fn on CH4 CC 64 events from port '0123'
on({ port: '0123', channel: 4, type: 'control', name: 64 }}, fn);

off(selector, fn)

Removes an event listener ‘fn’ from MIDI events matching object ‘selector’. Where ‘fn’ is not given, removes all handlers from events matching the selector.

off({ channel: 1, type: 'note' }, fn);

trigger(port, message)

Simulates an incoming MIDI event and fires listeners with matching selectors. Useful for debugging.

trigger(null, [128, 69, 88]);

trigger(port, chan, type, name, value)

As trigger(port, message), where the last 4 parameters are passed to createMessage() to create the MIDI message before triggering.

trigger(null, 1, 'noteon', 'A4', 0.75);

send

outputs()

Returns the map of MIDI output ports from the underlying MIDIAccess object.

send(event)

Cues a message to be sent to an output port. The object event must have the same structure as an incoming MIDI event object:

send({
    target:    // a MIDI output port
    timeStamp: // a DOM timeStamp
    data:      // a MIDI message
});

If timeStamp is in the past the message is sent immediately.

messages

createMessage(channel, type, name, value)

Creates a MIDI message – a Uint8Array of three values – where channel is an integer in the range 1-16 and type is a string that determines the meaning of name and value

for type 'noteon' or 'noteoff':

  • name: an integer in the range 0-127, or a note name string eg. 'Eb4'.
  • value: a float in the range 0-1 representing velocity.
createMessage(1, 'noteon', 'C3', 0.75);

for type 'control':

  • name: an integer in the range 0-127, or a control name string eg. 'modulation'.
  • value: a float in the range 0-1 representing control value.
createMessage(1, 'control', 'modulation', 1);

for type 'pitch':

  • name: a bend range in semitones.
  • value: a positive or negative float within that range representing a pitch bend in semitones.
createMessage(1, 'pitch', 2, 0.25);

for type 'polytouch':

  • name: an integer in the range 0-127, or a note name string eg. 'Eb4'.
  • value: a float in the range 0-1 representing force.
createMessage(1, 'polytouch', 'C3', 0.25);

for type 'channeltouch':

  • name: an integer in the range 0-1.
  • value: unused.
createMessage(1, 'channeltouch', 0.5);

for type 'program':

  • name: an integer in the range 0-127.
  • value: unused.
createMessage(1, 'program', 24);

isControl(message)

Returns true if message is a control change, otherwise false.

isControl([145,80,20]);       // false

isNote(message)

Returns true where message is a noteon or noteoff, otherwise false.

isNote([145,80,20]);           // true

isPitch(message)

Returns true message is a pitch bend, otherwise false.

isPitch([145,80,20]);          // false

normalise(message)

Many keyboards transmit 'noteon' with velocity 0 rather than 'noteoff' messages. This is because MIDI allows messages with the same type to be sent together, omitting the status byte and saving bandwidth. The MIDI spec requires that both forms are treated identically. normalise() mutates 'noteon' messages with velocity 0 to 'noteoff' messages.

normalise([145,80,0]);  // [129,80,0]

Note that the MIDI library automatically normalises incoming messages.

data

floatToFrequency(ref, n)

Given a note number n, returns the frequency of the fundamental tone of that note. ref is a reference frequency for middle A4/69 (usually 440).

floatToFrequency(440, 69);  // 440
floatToFrequency(440, 60);  // 261.625565
floatToFrequency(442, 69);  // 442
floatToFrequency(442, 60);  // 262.814772

frequencyToFloat(ref, frequency)

Returns frequency as a float on the note number scale. ref is a reference frequency for middle A4/69 (usually 440).

frequencyToFloat(440, 220);  // 57 (A3)
frequencyToFloat(440, 110);  // 45 (A2)

Output is rounded to 32 bits to mitigate floating point rounding errors.

normaliseNoteName(name)

Replaces the characters 'b' and '#' with the unicode musical characters '♭' and '♯' respectively.

normaliseNoteName('Eb6');      // 'E♭6'

toControlName(n)

Returns a shorthand controller name from a value in the range 0-127. Not all contollers have a standardised name, and this library implements only the more common ones. Where a name is not found, returns the controller number as a string.

toControlName(7);       // 'volume'
toControlName(64);      // 'sustain'
toControlName(98);      // '98'

Standardised controller names are defined at midi.org/specifications-old/.

toControlNumber(name)

Returns a value in the range 0-127 from a shorthand controller name.

toControlNumber('volume')   // 7
toControlNumber('sustain')  // 64
toControlNumber('98')       // 98

toNoteName(n)

Returns note name from a value in the range 0-127.

toNoteName(69);       // 'A4'

toNoteNumber(name)

Given a note name, returns a value in the range 0-127.

toNoteNumber('D6');     // 86

toNoteOctave(n)

Where n is a note number, returns the numerical octave.

toNoteOctave(69);     // 4

toStatus(channel, type)

Given a channel in the range 1-16 and type, returns the MIDI message status byte.

toStatus(1, 'noteon');      // 144
toStatus(7, 'control');     // 183

toType(status)

Returns message type as one of the strings 'noteoff', 'noteon', 'polytouch', 'control', 'program', 'channeltouch' or 'pitch'.

toType(145);          // 'noteon'.

maths

bytesToInt14(lsb, msb)

Given two 7-bit values for lsb (least significant byte) and msb (most significant byte), returns a 14-bit integer in the range 0-16383.

bytesToInt14(0, 64);   // 8192

bytesToFloat(lsb, msb)

Given two 7-bit values for lsb (least significant byte) and msb (most significant byte), returns a float in the range 0-1.

bytesToFloat(0, 0);     // 0
bytesToFloat(0, 64);    // 0.50003051944
bytesToFloat(127, 127); // 1

bytesToSignedFloat(lsb, msb)

Given two 7-bit values for lsb (least significant byte) and msb (most significant byte), returns a float in the range -1-1, weighted so that an input of (0, 64) maps to 0.

bytesToSignedFloat(0, 0);     // -1
bytesToSignedFloat(0, 64);    // 0
bytesToSignedFloat(127, 127); // 1

bytesToWeightedFloat(lsb, msb)

Given two 7-bit values for lsb (least significant byte) and msb (most significant byte), returns a float in the range 0-1, weighted so that an input of (0, 64) maps to 0.5.

bytesToWeightedFloat(0, 0);     // 0
bytesToWeightedFloat(0, 64);    // 0.5
bytesToWeightedFloat(127, 127); // 1

floatToInt7(n)

Returns an integer in the 7-bit range 0-127 for values of n between 0-1. Values lower than 0 return 0, while values greater than 1 return 127.

floatToInt7(0.5);      // 64

floatToInt14(n)

Returns an integer in the 14-bit range 0-16383 for values of n between 0-1. Values lower than 0 return 0, while values greater than 1 return 16383.

floatToInt14(0.5);      // 8192

int7ToFloat(n)

Returns a float in the range 0-1 for values of n in the range 0-127.

int7ToFloat(64);      // 0.503937

int7ToWeightedFloat(n)

Returns a float in the range 0-1 for values of n in the range 0-127. The input integer is mapped so that the value 64 returns exactly 0.5, the centre of the range, as per the MIDI spec for controller values and their ilk.

int7ToSignedFloat(0);    // 0
int7ToSignedFloat(64);   // 0.5
int7ToSignedFloat(127);  // 1

int7ToSignedFloat(n)

Returns a float in the range -1-1 for values of n in the range 0-127. The input integer is mapped so that the value 64 returns 0, the centre of the range, as per the MIDI spec for controller values and their ilk.

int7ToSignedFloat(0);    // -1
int7ToSignedFloat(64);   // 0
int7ToSignedFloat(127);  // 1

int14ToFloat(n)

Returns a float in the range 0-1 for values of n in the range 0-16383.

int14ToFloat(8192);   // 0.500031

int14ToWeightedFloat(n)

Returns a float in the range 0-1 for values of n in the range 0-16383. The input integer is mapped so that the value 8192 returns 0.5, the centre of the range, as per the MIDI spec for pitch bend values and their ilk.

int14ToWeightedFloat(0);      // 0
int14ToWeightedFloat(8192);   // 0.5
int14ToWeightedFloat(16383);  // 1

int14ToSignedFloat(n)

Returns a float in the range -1-1 for values of n in the range 0-16383. The input integer is mapped so that the value 8192 returns 0, the centre of the range, as per the MIDI spec for pitch bend values and their ilk.

int14ToSignedFloat(0);      // -1
int14ToSignedFloat(8192);   // 0
int14ToSignedFloat(16383);  // 1

int14ToLSB(n)

Returns the least significant 7-bit data byte of an unsigned 14-bit integer.

int14ToLSB(8192);      // 0

Out-of-range input values will return spurious results.

int14ToMSB(n)

Returns the most significant 7-bit data byte of an unsigned 14-bit integer in the range 0-16383

int14ToMSB(8192);      // 64

Out-of-range input values will return spurious results.

signedFloatToInt7(n)

Returns an integer in the 7-bit range 0-127 for values of n between -1-1. The input value 0 maps exactly to the value 64, as per the MIDI spec for modulation control values and their ilk.

signedFloatToInt7(-1); // 0
signedFloatToInt7(0);  // 64
signedFloatToInt7(1);  // 127

Values lower than -1 return 0, while values greater than 1 return 127.

signedFloatToInt14(n)

Returns an integer in the 14-bit range 0-16383 for values of n between -1-1. The input value 0 maps exactly to the value 8192, as per the MIDI spec for pitch bend values and their ilk.

signedFloatToInt14(-1); // 0
signedFloatToInt14(0);  // 8192
signedFloatToInt14(1);  // 16383

Values lower than -1 return 0, while values greater than 1 return 16383.

weightedFloatToInt7(n)

Returns an integer in the 7-bit range 0-127 for values of n between 0-1. The input value 0.5 maps exactly to the value 64, as per the MIDI spec for modulation control values and their ilk.

weightedFloatToInt7(0);   // 0
weightedFloatToInt7(0.5); // 64
weightedFloatToInt7(1);   // 127

Values lower than -1 return 0, while values greater than 1 return 127.

weightedFloatToInt14(n)

Returns an integer in the 14-bit range 0-16383 for values of n between -1-1. The input value 0 maps exactly to the value 8192, as per the MIDI spec for pitch bend values and their ilk.

weightedFloatToInt14(0);   // 0
weightedFloatToInt14(0.5); // 8192
weightedFloatToInt14(1);   // 16383

Values lower than -1 return 0, while values greater than 1 return 16383.

Custom elements

<midi-graph>

The <midi-graph> element plots incoming note, control change and pitch bend messages on a graph.

<script type="module" src="./elements/midi-graph/midi-graph.js"></script>
<midi-graph>

This module has external dependencies.

<midi-monitor>

The <midi-monitor> element displays all incoming messages in a rolling list.

<script type="module" src="//stephen.band/midi/elements/midi-monitor/midi-monitor.js"></script>
<midi-monitor>

This module has external depencies. There is a full-page MIDI monitor at stephen.band/midi/monitor/.