WebMIDI + knockout.js

by sfpgmr

HTML

<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap-theme.min.css">
<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/knockout/3.1.0/knockout-min.js"></script>
<script src="http://sfpgmr.github.io/0002/scripts/undoredo.js"></script>
<h3>
   MIDI入力データをモニタリングし、MIDI出力にスルーする。
</h3>
<div class="row">
    <div class="col-md-4 col-sm-4">
         <h4>Input Port Select</h4>
        <select data-bind="options:inputs,optionsText:'name',value:input"></select>
    </div>
    <div class="col-md-4 col-sm-4">
         <h4>Output Port Select</h4>

        <select data-bind="options:outputs,optionsText:'name',value:output"></select>
    </div>
</div>
<div>
     <h4>Input Monitor</h4>

    <table class="table table-condensed">
        <thead>
            <tr>
                <th>time</th>
                <th>event</th>
            </tr>
        </thead>
        <!-- foreach バインディングを使用して MIDI イベントを表示 -->
        <tbody data-bind="foreach:inputEvents" >
            <tr>
                <td data-bind="text:timeStamp"></td>
                <!-- イベントデータはネストして表示 -->
                <td data-bind="foreach:{data:$data.events,as:'ev'}">
                    <span><span data-bind="text:ev"></span>&nbsp;</span>
                </td>
            </tr>
        </tbody> 
    </table>
</div>

JavaScript

// Modelデータ
function MIDI(access) {
    var self = this;
    self.access = access;
    self.inputs = access.inputs();
    self.outputs = access.outputs();
    self.input = null;
    self.output = null;
}

MIDI.prototype = {
    selectInput: function (value) {
        if(this.input !== value){
            if (this.input) {
                this.input.onmidimessage = null;
            }
            this.input = value;
            var self = this;
            this.input.onmidimessage = 
                function(ev){
                    if(ev.data[0] != 0xfe){
                        if(self.output){
                            self.output.send(ev.data,0);
                        }
                        $(self).trigger('midiEvent',[ev]);
                    }
                };
        }
    },
    selectOutput: function(value){
        if(this.output !== value){
            this.output = value;
        }
    }
};

// View Model
function ViewModel(midi) {
    var self = this;
    self.inputs = midi.inputs;
    // 入力値の変化に応じてMIDI Modelをいじりたいのでcomputedを使用
    self.input = ko.computed({
    read: function () {
        return midi.input;
    },
    write: function (value) {
        midi.selectInput(value);
    },
        owner: this});
    self.outputs = midi.outputs;
    // 出力値の変化に応じてMIDI Modelをいじりたいのでcomputedを使用
    self.output = ko.computed(
        {
            read: function(){return midi.output;},
            write: function(value){ midi.selectOutput(value);},
            owner:this
        });

    // MIDIイベントを保管する配列(10イベントまで)
    self.inputEvents = ko.observableArray();
    // MIDI Modelのイベント処理
    $(midi).on('midiEvent',function(e,ev){
        // イベントを文字列に変換
        var evs = [];
        for(var i = 0,end = ev.data.length;i < end;++i){
            evs.push(('00' + ev.data[i].toString(16)).slice(-2));
        }
        // データ保存
        self.inputEvents.push(
            {
                timeStamp: ev.receivedTime,
                events:evs
            }
      ...