cwilso / WebMIDIAPIShim

Polyfill using the Jazz NPAPI MIDI plugin to implement the Web MIDI API on Mac and Windows.
380 stars 53 forks source link

Broken with Jazz-Plugin Version 1.3 on Safari and FireFox (Mac) #45

Closed pingdynasty closed 9 years ago

pingdynasty commented 10 years ago

I get errors in Safari running this example: http://cwilso.github.io/WebMIDIAPIShim/tests/index.html

[Error] TypeError: 'undefined' is not an object (evaluating 'this._jazzInstance.MidiOutOpen')
    then (WebMIDIAPI.js, line 345)
    (anonymous function)
    MIDIOutput (WebMIDIAPI.js, line 358)
    outputs (WebMIDIAPI.js, line 156)
    success (index.html, line 48)
    succeed (WebMIDIAPI.js, line 32)
    _onReady (WebMIDIAPI.js, line 129)
    (anonymous function)

This also happens with my own code when I call midiAccess.outputs(). MidiAccess.inputs() works fine.

On FireFox I get TypeError: this._jazzInstance is undefined, at WebMIDIAPI.js, line 345

pingdynasty commented 10 years ago

It works if I comment out line 349: // if (!midiAccess._jazzInstances[i].outputInUse)

abudaan commented 9 years ago

It happens when there is more than 1 input and more than 1 output.

To solve this: create enough _JazzInstance instances for all in- and outputs in the MIDIAccess constructor and wait for _delayedInit() calls to complete before calling _onready()

    MIDIAccess = function() {
        var numInputs,
            numOutputs,
            numInstances,
            instance;

        this._jazzInstances = new Array();
        instance = new _JazzInstance();
        this._jazzInstances.push( instance );
        this._promise = new Promise;

        instance._delayedInit(function() {
            if(instance._Jazz){
                this._Jazz = instance._Jazz;
                numInputs = this._Jazz.MidiInList().length;
                numOutputs = this._Jazz.MidiOutList().length;
                /*
                    Get the number of _JazzInstances that is needed, because 1 input
                    and 1 output can share a _JazzInstance, we check how much inputs
                    and outputs are available and the largest number is the number
                    of _JazzInstances that we need. Then we deduct one because we have
                    already created a _JazzInstance.
                */
                numInstances = Math.max(numInputs, numOutputs) - 1;
                if(numInstances > 0){
                    _createJazzInstance.bind(this)(0, numInstances);
                }
            } else {
                window.setTimeout(_onNotReady.bind(this), 3);
            }
        }.bind(this));
    };

    _createJazzInstance = function(i, max){
        var instance = new _JazzInstance();
        this._jazzInstances.push(instance);

        instance._delayedInit(function() {
            i++;
            if(i < max) {
                _createJazzInstance.bind(this)(i, max);
            } else {
                /*
                    All necessary _JazzInstances are created, now call _onReady
                */
                window.setTimeout(_onReady.bind(this), 3);
            }
        }.bind(this));
    };

Alternately, a quick and dirty workaround for the example is:

function success(midiAccess) {
    log.innerHTML += "MIDI ready!\n";
    midi = midiAccess;

    inputs = midi.inputs();
    log.innerHTML += inputs.length + " inputs:\n";
    for (var i = 0; i < inputs.length; i++)
        log.innerHTML += i + ": " + inputs[i].name + "\n";

    if (inputs.length > 0) {
        input = inputs[0];
        input.addEventListener("midimessage", handleMIDIMessage);
        log.innerHTML += "Hooked up first input.\n";
    }

    // add a delay of 100 ms, the same delay as _JazzInstance._delayedInit
    setTimeout(function() {
        outputs = midi.outputs();
        log.innerHTML += outputs.length + " outputs:\n";
        for (var i = 0; i < outputs.length; i++)
            log.innerHTML += i + ": " + outputs[i].name + "\n";

        if (outputs.length) {
            output = outputs[0];
            output.send([0xb0, 0x00, 0x7f]); // If the first device is a Novation Launchpad, this will light it up!
        }
    }, 100);
}