Repository

js/Mobilizing/serial/Midi.js

import Component from '../core/Component';
import * as debug from '../core/util/Debug';

export default class MIDI extends Component {
    /**
    Gives access to the MIDI devices. This class requires the Web MIDI API to operate properly.

    @param {Object} params Parameters object, given by the constructor.
    @param {String} params.inputName The MIDI IN interface name we want to open.
    @param {String} params.outputName The MIDI OUT interface name we want to open.
    @param {Function} params.onMIDIIn The MIDI IN callback we want to be called on a MIDI in event.

    @requires Web MIDI API
    */
    constructor({
        inputName = undefined,
        outputName = undefined,
        onMIDIIn = undefined,
    } = {}) {
        super(...arguments);

        this.inputName = inputName;
        this.outputName = outputName;
        this.onMIDIIn = onMIDIIn;

        debug.log(`this.onMIDIIn=${this.onMIDIIn}`);

        if (navigator.requestMIDIAccess) {
            navigator.requestMIDIAccess().then(this.onMIDIInit.bind(this), this.onMIDIReject.bind(this));
        }
        else {
            console.warn("No MIDI support present in your browser. Try in another browser.");
        }
    }

    /**
    Web MIDI API success callback.
    @private
    @param {Object} MIDI
    */
    onMIDIInit(access) {
        this.access = access;
        this.hookUpMIDI();
        this.access.onstatechange = this.hookUpMIDI;
    }

    /**
    Web MIDI API failure callback.
    @private
    @param {Object} err
    */
    onMIDIReject(err) {
        alert(`
        The MIDI system failed to start.
        You're gonna have a bad time.
        Error: ${err.message}
        `);
    }

    /**
    * hookUpMIDI
    * @private
    */
    hookUpMIDI() {
        if (this.inputName !== undefined) {
            const inputs = this.access.inputs.values();

            debug.log("MIDI inputs on your system : ");

            for (let input = inputs.next(); input && !input.done; input = inputs.next()) {
                debug.log("input=", input);

                if (input.value.name === this.inputName) {
                    debug.log(`found MIDI interface IN : ${input.value.name}`);
                    input.value.onMIDImessage = this.onMIDIMessage.bind(this);
                }
            }
        }

        if (this.outputName !== undefined) {
            const outputs = this.access.outputs.values();
            debug.log("MIDI outputs on your system : ");
            for (let output = outputs.next(); output && !output.done; output = outputs.next()) {
                debug.log("output=", output);

                if (output.value.name === this.outputName) {
                    debug.log(`found MIDI interface OUT : ${output.value.name}`);
                    this.output = output.value;
                }
            }
        }
    }

    /**
    * onMIDIMessage
    * @method onMIDIMessage
    * @param {Object} event MIDI event
    * @private
    */
    onMIDIMessage(event) {
        debug.log(`this.onMIDIIn=${this.onMIDIIn}`);

        if (this.onMIDIIn !== undefined) {
            //channel message data1 data2
            const message = event.data[0] & 0xf0;
            const channel = event.data[0] - message;
            const data1 = event.data[1];
            const data2 = event.data[2];
            this.onMIDIIn(channel, message, data1, data2);
        }
    }

    /**
    Sends a Control Change MIDI message.
    @method ControlChange
    @param {Number} channel MIDI channel [0-15]
    @param {Number} cc ControlChange number [0-127]
    @param {Number} val ControlChange value [0-127]
    @example
    MIDI.ControlChange(2,11,50);
    */
    ControlChange(channel, cc, val) {
        if (this.output === undefined) {
            return;
        }

        let ccMessage;
        const m = 0xB0 + channel;
        ccMessage = [m, cc, val];
        this.output.send(ccMessage);
    }

    /**
    Sends a NoteOn MIDI message.
    @method NoteOn
    @param {Number} channel MIDI channel [0-15]
    @param {Number} note MIDI Note number [0-127]
    @param {Number} vel note MIDI velocity [0-127]
    @example
    MIDI.NoteOn(0,60,127);
    */
    NoteOn(channel, note, vel) {
        if (this.output === undefined) {
            return;
        }

        let ccMessage;
        const m = 0x90 + channel;
        ccMessage = [m, note, vel];
        this.output.send(ccMessage);
    }

    /**
    Sends a NoteOff MIDI message.
    @method NoteOff
    @param {Number} channel MIDI channel [0-15]
    @param {Number} note MIDI Note number [0-127]
    @param {Number} vel note MIDI velocity [0-127]
    @example
    MIDI.NoteOff(0,60,0);
    */
    NoteOff(channel, note, vel) {
        if (this.output === undefined) {
            return;
        }

        let ccMessage;
        const m = 0x80 + channel;
        ccMessage = [m, note, vel];
        this.output.send(ccMessage);
    }

}
/**
* Note On
*
* @property NoteOn
* @type Number
* @static
* @final
*/
MIDI.NoteOn = 144;
/**
* Note Off
*
* @property NoteOff
* @type Number
* @static
* @final
*/
MIDI.NoteOff = 128;
/**
* After touch
*
* @property Aftertouch
* @type Number
* @static
* @final
*/
MIDI.Aftertouch = 160;
/**
* Control change
*
* @property ControlChange
* @type Number
* @static
* @final
*/
MIDI.ControlChange = 176;
/**
* Program change
*
* @property ProgramChange
* @type Number
* @static
* @final
*/
MIDI.ProgramChange = 192;