Web MIDI API | MIDIメッセージの受信

WAVE TYPE
MIDIMessageEvent
MIDIMessageEvent
PropertyValuehasOwnProperty
(function() {

    var onDOMContentLoaded = function() {

        window.AudioContext = window.AudioContext || window.webkitAudioContext;

        try {
            // Create the instance of AudioContext
            var context = new AudioContext();
        } catch (error) {
            window.alert(error.message + ' : Please use Chrome or Safari.');
            return;
        }

        if (!navigator.requestMIDIAccess) {
            window.alert('Cannot use Web MIDI API.');
            return;
        }

        var displayProperties = function(node, tableid, caption) {
            var html = '<caption>' + caption + '</caption>';

            html += '<thead>';
            html += '<tr>';
            html += '<th scope="col">Property</th>';
            html += '<th scope="col">Value</th>';
            html += '<th scope="col">hasOwnProperty</th>';
            html += '</tr>';
            html += '</thead>';

            html += '<tbody>';

            for (var key in node) {
                html += '<tr>';
                html += '<td>' + key + '</td>';
                html += '<td>' + node[key] + '</td>';
                html += '<td>' + node.hasOwnProperty(key) + '</td>';
                html += '</tr>';
            }

            html += '</tbody>';

            document.getElementById(tableid).innerHTML = html;

            if (document.getElementById(tableid).parentNode.previousElementSibling !== null) {
                document.getElementById(tableid).parentNode.previousElementSibling.style.display = 'block';
            }
        };

        // for legacy browsers
        context.createGain = context.createGain || context.createGainNode;

        var NOTE_NUMBER_OFFSET = 21;

        var PIANO_88S = [
                        'A-4', 'A-4h', 'B-4',
                        'C-3', 'C-3h', 'D-3', 'D-3h', 'E-3', 'F-3', 'F-3h', 'G-3', 'G-3h', 'A-3', 'A-3h', 'B-3',
                        'C-2', 'C-2h', 'D-2', 'D-2h', 'E-2', 'F-2', 'F-2h', 'G-2', 'G-2h', 'A-2', 'A-2h', 'B-2',
                        'C-1', 'C-1h', 'D-1', 'D-1h', 'E-1', 'F-1', 'F-1h', 'G-1', 'G-1h', 'A-1', 'A-1h', 'B-1',
                        'C',   'Ch',   'D',   'Dh',   'E',   'F',   'Fh',   'G',   'Gh',   'A',   'Ah',   'B',
                        'C1',  'C1h',  'D1',  'D1h',  'E1',  'F1',  'F1h',  'G1',  'G1h',  'A1',  'A1h',  'B1',
                        'C2',  'C2h',  'D2',  'D2h',  'E2',  'F2',  'F2h',  'G2',  'G2h',  'A2',  'A2h',  'B2',
                        'C3',  'C3h',  'D3',  'D3h',  'E3',  'F3',  'F3h',  'G3',  'G3h',  'A3',  'A3h',  'B3',
                        'C4'
                        ];

        var oscillators        = [];
        var envelopegenerators = [];
        var intervalids        = [];

        for (var i = 0, len = PIANO_88S.length; i < len; i++) {
            oscillators[i]        = null;
            envelopegenerators[i] = context.createGain();
            intervalids[i]        = null;

            // for legacy browsers
            envelopegenerators[i].gain.setTargetAtTime = envelopegenerators[i].gain.setTargetAtTime || envelopegenerators[i].gain.setTargetValueAtTime;
        }

        var gain = context.createGain();

        // Parameters
        var type         = 'sine';
        var masterVolume = document.getElementById('range-volume').valueAsNumber;
        var glide        = document.getElementById('range-glide').valueAsNumber;
        var attack       = document.getElementById('range-attack').valueAsNumber;
        var decay        = document.getElementById('range-decay').valueAsNumber;
        var sustain      = document.getElementById('range-sustain').valueAsNumber;
        var release      = document.getElementById('range-release').valueAsNumber;

        var startFrequency = -1;

        var successCallback = function(midiAccess) {
            var inputs  = [];
            var outputs = [];

            if (typeof midiAccess === 'function') {
                // Legacy Chrome
                inputs  = midiAccess.inputs();
                outputs = midiAccess.outputs();
            } else {
                // Chrome 39 and later
                var inputIterator  = midiAccess.inputs.values();
                var outputIterator = midiAccess.outputs.values();

                for (var i = inputIterator.next(); !i.done; i = inputIterator.next()) {
                    inputs.push(i.value);
                }

                for (var o = outputIterator.next(); !o.done; o = outputIterator.next()) {
                    outputs.push(o.value);
                }
            }

            inputs.forEach(function(element, index) {
                var option = document.createElement('option');

                option.appendChild(document.createTextNode(element.name));
                option.setAttribute('value', index);

                document.getElementById('select-midi-input-device').appendChild(option);
            });

            document.getElementById('select-midi-input-device').onchange = function() {
                inputs.forEach(function(input) {
                    input.onmidimessage = null;
                });

                if (this.value === '') {
                    return;
                }

                inputs[this.value].onmidimessage = function(event) {
                    switch (event.data[0] & 0xf0) {
                        case 0x90 :
                            noteOn(event.data[1], event.data[2]);
                            break;
                        case 0x80 :
                            noteOff(event.data[1], event.data[2]);
                            break;
                        default :
                            break;
                    }

                    displayProperties(event, 'midimessageevent-properties', 'MIDIMessageEvent');
                };
            };
        };

        var errorCallback = function(error) {
            console.dir(error);
        };

        var noteOn = function(noteNumber, velocity) {
            if ((noteNumber < NOTE_NUMBER_OFFSET) || (noteNumber > (PIANO_88S.length + NOTE_NUMBER_OFFSET - 1))) {
                return;
            }

            if (oscillators[noteNumber - NOTE_NUMBER_OFFSET] instanceof OscillatorNode) {
                oscillators[noteNumber - NOTE_NUMBER_OFFSET].stop(0);
                oscillators[noteNumber - NOTE_NUMBER_OFFSET].disconnect(0);
                oscillators[noteNumber - NOTE_NUMBER_OFFSET] = null;
            }

            var FREQUENCY_RATIO = Math.pow(2, (1 / 12));  // about 1.059463
            var MIN_A           = 27.5;
            var MAX_VELOCITY    = 127;

            var oscillator = context.createOscillator();

            oscillator.start = oscillator.start || oscillator.noteOn;
            oscillator.stop  = oscillator.stop  || oscillator.noteOff;

            oscillator.frequency.setTargetAtTime = oscillator.frequency.setTargetAtTime || oscillator.frequency.setTargetValueAtTime;

            oscillator.type = type;

            gain.gain.value = (velocity / MAX_VELOCITY) * masterVolume;

            // Envelope Generator
            var envelopegenerator = envelopegenerators[noteNumber - NOTE_NUMBER_OFFSET];

            var t0 = context.currentTime;
            var t1 = t0 + attack;
            var t2 = decay;

            envelopegenerator.gain.cancelScheduledValues(t0);
            envelopegenerator.gain.setValueAtTime(0, t0);
            envelopegenerator.gain.linearRampToValueAtTime(1, t1);
            envelopegenerator.gain.setTargetAtTime(sustain, t1, t2);

            // Glide
            if (startFrequency === -1) {
                oscillator.frequency.value = MIN_A * Math.pow(FREQUENCY_RATIO, (noteNumber - NOTE_NUMBER_OFFSET));
                startFrequency = oscillator.frequency.value;
            } else {
                oscillator.frequency.cancelScheduledValues(t0);
                oscillator.frequency.setValueAtTime(startFrequency, t0);
                oscillator.frequency.linearRampToValueAtTime(MIN_A * Math.pow(FREQUENCY_RATIO, (noteNumber - NOTE_NUMBER_OFFSET)), (t0 + glide));
            }

            oscillator.connect(envelopegenerator);
            envelopegenerator.connect(gain);
            gain.connect(context.destination);

            oscillator.start(0);

            oscillators[noteNumber - NOTE_NUMBER_OFFSET]        = oscillator;
            envelopegenerators[noteNumber - NOTE_NUMBER_OFFSET] = envelopegenerator;

            var keyboard = document.getElementById(PIANO_88S[noteNumber - NOTE_NUMBER_OFFSET]);

            keyboard.classList.remove('key-off');
            keyboard.classList.add('key-on');
        };

        var noteOff = function(noteNumber, velocity) {
            if ((noteNumber < NOTE_NUMBER_OFFSET) || (noteNumber > (PIANO_88S.length + NOTE_NUMBER_OFFSET - 1))) {
                return;
            }

            if (!(oscillators[noteNumber - NOTE_NUMBER_OFFSET] instanceof OscillatorNode)) {
                return;
            }

            // Envelope Generator
            var envelopegenerator = envelopegenerators[noteNumber - NOTE_NUMBER_OFFSET];

            var t3 = context.currentTime;
            var t4 = release;

            envelopegenerator.gain.cancelScheduledValues(t3);
            envelopegenerator.gain.setValueAtTime(envelopegenerator.gain.value, t3);
            envelopegenerator.gain.setTargetAtTime(0, t3, t4);

            intervalids[noteNumber - NOTE_NUMBER_OFFSET] = window.setInterval(function() {
                var MIN_GAIN = 1e-3;

                if (envelopegenerator.gain.value < MIN_GAIN) {
                    // Stop sound
                    if (oscillators[noteNumber - NOTE_NUMBER_OFFSET] instanceof OscillatorNode) {
                        oscillators[noteNumber - NOTE_NUMBER_OFFSET].stop(0);
                        oscillators[noteNumber - NOTE_NUMBER_OFFSET].disconnect(0);
                        oscillators[noteNumber - NOTE_NUMBER_OFFSET] = null;
                    }

                    if (intervalids[noteNumber - NOTE_NUMBER_OFFSET] !== null) {
                        window.clearInterval(intervalids[noteNumber - NOTE_NUMBER_OFFSET]);
                        intervalids[noteNumber - NOTE_NUMBER_OFFSET] = null;
                    }
                }
            }, 0);

            var keyboard = document.getElementById(PIANO_88S[noteNumber - NOTE_NUMBER_OFFSET]);

            keyboard.classList.remove('key-on');
            keyboard.classList.add('key-off');
        };

        navigator.requestMIDIAccess({sysex : true}).then(successCallback, errorCallback);

        document.getElementById('form-type').addEventListener('change', function() {
            for (var i = 0, len = this.elements['radio-type'].length; i < len; i++) {
                if (this.elements['radio-type'][i].checked) {
                    type = this.elements['radio-type'][i].value;
                    break;
                }
            }
        }, false);

        document.getElementById('range-volume').addEventListener('input', function() {
            masterVolume = this.valueAsNumber;
            document.getElementById('output-volume').textContent = this.value;
        });

        document.getElementById('range-glide').addEventListener('input', function() {
            glide = this.valueAsNumber;
            document.getElementById('output-glide').textContent = this.value;
        });

        document.getElementById('range-attack').addEventListener('input', function() {
            attack = this.valueAsNumber;
            document.getElementById('output-attack').textContent = this.value;
        });

        document.getElementById('range-decay').addEventListener('input', function() {
            decay = this.valueAsNumber;
            document.getElementById('output-decay').textContent = this.value;
        });

        document.getElementById('range-sustain').addEventListener('input', function() {
            sustain = this.valueAsNumber;
            document.getElementById('output-sustain').textContent = this.value;
        });

        document.getElementById('range-release').addEventListener('input', function() {
            release = this.valueAsNumber;
            document.getElementById('output-release').textContent = this.value;
        });
    };

    if ((document.readyState === 'interactive') || (document.readyState === 'complete')) {
        onDOMContentLoaded();
    } else {
        document.addEventListener('DOMContentLoaded', onDOMContentLoaded, true);
    }

})();