Web MIDI APIを使ってブラウザからソフトウェア・シンセサイザーを鳴らす

UI開発者 泉口

Web MIDI APIとは

Web MIDI APIとは、W3C Audio Working Groupによって提唱された、MIDIプロトコルをサポートするAPIです。このAPIを使用する事で、Webアプリケーション上からMIDIデバイスを介して電子楽器、物理的なコントローラーなどにアクセスし、MIDIメッセージの入出力を行うことが可能になります。2015年3月の段階ではWorking Draftとなっていますが、W3C Editor's Draftや、Issues trackerでは2016年現在も更新が行われています。

今回はこのWeb MIDI APIを使って、ブラウザ上からMIDIメッセージを作成・出力し、仮想MIDIケーブルを介して、VSTiから音声として出力してみたいと思います。また、2016年9月現在においてWeb MIDI APIが実装されているブラウザはGoogle Chromeのみとなり、本記事はアプリケーションのサポート上、Windowsのみを対象としていますので、ご注意ください。

仮想MIDIケーブルの設定

一般的なPCではMIDIデバイスを持っていないため、ブラウザとソフトウェア・シンセサイザー間のMIDIメッセージの通信を行うことはできません。本来であればオーディオインターフェイス上のMIDIデバイスを使用するのが一般的ですが、今回はPC上で仮想MIDIケーブルを実現するソフトウェア、loopMIDIをインストールします。インストール・起動後はアプリケーションのSetupタブから、「+」ボタンを押下し、loopMIDI Portを追加するだけで仮想MIDIケーブルの設定は完了となります。

HOSTとVSTの準備

音を出すソフトウェア・シンセサイザーについて、Web MIDI APIと同じくWeb Audio APIを使って音をゼロから作ることも可能ですが、今回はより実用的で汎用性のあるVST(Steinberg's Virtual Studio Technology)を使用します。VSTとは、主にデジタル・オーディオ・ワークステーション(DAW)で使用される規格で、おおまかに、音を加工するVSTe(VSTエフェクト)と、音そのものを出力するVSTi(VSTインストゥルメント)、MIDIメッセージを加工するMIDIエフェクトの3種類が存在し、このVST規格を使用したサードパーティー製のプラグインは有料/無料問わず数えきれない程多く存在しているため、音声編集や、音楽制作におけるDTM(デスクトップミュージック)では標準的に使用することができると言っても過言ではありません。

VSTiを使用するにあたって、VSTiをプラグインとして起動できる状況にする必要がありますが、この場合ホストアプリケーションが必要になります。今回は2013年度製と、当時のDTMを知っている方には少し懐かしい感覚がしますが、VSTのホストアプリケーションそのままであるVSTHostと言うソフトウェアを使用します。ZIPファイルのダウンロード・解凍後、インストールの必要は無く、vsthost.exeを起動することでソフトウェア使用することができます。

次にVSTiを用意します。VSTiに関してはVSTHost上で起動するものであれば何でも構いませんが、今回はテスト用に4Font Pianoを使用します。4fpiano-win.zipをダウンロード・解凍し、任意のディレクトリに配置します。

VSTHostの起動後、DevicesメニューからMIDIを選択し、先ほど作成した仮想MIDIケーブルのloopMIDI Portを選択状態にし、OKボタンを押下します。次にFileメニューからNew Plugin...を選択し、先ほど解凍した4Font Pianoのdll(VSTi)を選択して開くことで、ソフトウェア・シンセサイザーの入出力設定は完了です。

HTMLとJSファイルを作成

最後に、MIDIメッセージを作成し、VSTHostに送信する仕組みをHTML/JavaScriptで作成します。核となるrequestMIDIAccessによって認識されたデバイス(今回はloopMIDI Port)を選択し、キーボードやマウスの入力に応じた各button要素のdata-sound-key属性値を選択したデバイスに送信します。

index.html

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Document</title>
<link rel="stylesheet" href="common.css">
</head>
<body>

<div class="midi-output">
<label>MIDI OUTPUT:<select class="output"></select></label>
<!-- /.midi-output --></div>

<div class="midi-input">
<ul class="keybord">
<li><button data-pc-key="90" data-sound-key="0x3C">Z</button></li>
<li class="black left-05"><button data-pc-key="83" data-sound-key="0x3D">S</button></li>
<li><button data-pc-key="88" data-sound-key="0x3E">X</button></li>
<li class="black left-15"><button data-pc-key="68" data-sound-key="0x3F">D</button></li>
<li><button data-pc-key="67" data-sound-key="0x40">C</button></li>
<li><button data-pc-key="86" data-sound-key="0x41">V</button></li>
<li class="black left-35"><button data-pc-key="71" data-sound-key="0x42">G</button></li>
<li><button data-pc-key="66" data-sound-key="0x43">B</button></li>
<li class="black left-45"><button data-pc-key="72" data-sound-key="0x44">H</button></li>
<li><button data-pc-key="78" data-sound-key="0x45">N</button></li>
<li class="black left-55"><button data-pc-key="74" data-sound-key="0x46">J</button></li>
<li><button data-pc-key="77" data-sound-key="0x47">M</button></li>
<li><button data-pc-key="188" data-sound-key="0x48">,</button></li>
<li class="black left-75"><button data-pc-key="76" data-sound-key="0x49">L</button></li>
<li><button data-pc-key="190" data-sound-key="0x4A">.</button></li>
<li class="black left-85"><button data-pc-key="187" data-sound-key="0x4B">;</button></li>
<li><button data-pc-key="191" data-sound-key="0x4C">/</button></li>
<!-- /.keybord --></ul>

<script src="jquery-3.1.0.min.js"></script>
<script src="run.js"></script>
</body>
</html>

run.js

MIDIデバイス一覧の取得と表示、キーボード・クリックで発音までの内容を記述しています。

(function () {
    'use strict';

    var $win = $(window);
    var $keybordBtn = $('.keybord').find('button');
    var $output = $('.output');
    var listener = {};
    var currentKey;
    var midi = {
        output: []
    };

    $win.on({
        'keydown': function (event) {
            if (listener[event.keyCode]) return;

            listener[event.keyCode] = true;

            $keybordBtn.each(function () {
                if (event.keyCode + '' === $(this).attr('data-pc-key')) {
                    $(this).trigger('focus').trigger('mousedown');
                }
            });
        },
        'keyup': function (event) {
            listener[event.keyCode] = false;

            $keybordBtn.each(function () {
                if (event.keyCode + '' === $(this).attr('data-pc-key')) {
                    $(this).trigger('focus').trigger('mouseup');
                }
            });
        }
    });
    $keybordBtn.on({
        'mousedown': function () {
            currentKey = $(this).attr('data-sound-key');
            midi.output[$output.val()].send(['0x90', currentKey, '127']);

        },
        'mouseup': function () {
            currentKey = $(this).attr('data-sound-key');
            midi.output[$output.val()].send(['0x80', currentKey, '0']);
        }
    });

    navigator.requestMIDIAccess({
        sysex: false
    }).then(function (access) {
        var outputs = access.outputs;

        for (outputs of outputs.values()) {
            midi.output.push(outputs);
            $output.append(new Option(outputs.name, outputs.id));
        }
    }, function (err) {
        console.dir(err);
    });
}());

common.css

最低限のビジュアル的なオマケです。無くても機能します。

@charset "UTF-8";

body {
  font-family: Arial;
  font-size: 16px;
  color: #333;
  min-width: 320px;
}
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.midi-output {
  color: #fff;
  background: #222;
  padding: 4px 8px;
}
.output {
  font-size: 14px;
  margin: 0 0 0 8px;
  padding: 2px 4px;
}
.midi-input {
  perspective: 400px;
}
.keybord {
  position: relative;
  display: flex;
  overflow: hidden;
  width: 75%;
  margin: 0 auto;
  list-style: none;
  transform: rotateX(45deg);
}
.keybord li {
  flex-basis: 10%;
}
.keybord li button {
  font-weight: bold;
  background: #fff;
  display: block;
  width: 100%;
  padding: 300% 0 20%;
  cursor: pointer;
  border: 1px solid #ccc;
  border-color: #ccc #999 #999 #ccc;
  border-radius: 0 0 3px 3px;
}
.keybord li button:focus,
.keybord li button:hover {
  padding: 298% 0 21%;
  border-color: #999 #ccc #ccc #999;
  box-shadow: inset 0 1px 3px rgba(0,0,0,.2);
}
.keybord li.black {
  position: absolute;
  z-index: 1;
  top: 0;
  width: 10%;
  margin: -5% 0 0;
  padding: 0 1%;
}
.keybord li.black button {
  color: #fff;
  background: #222;
}
.left-05 {left: 5%;}
.left-15 {left: 15%;}
.left-35 {left: 35%;}
.left-45 {left: 45%;}
.left-55 {left: 55%;}
.left-75 {left: 75%;}
.left-85 {left: 85%;}

作成したHTMLファイルをブラウザで開き、MIDI OUTPUTからloopMIDI Portする事でVSTiの音声が出力されます(PCの音量/サウンドミュートにご注意ください)。

ブラウザから仮想MIDIケーブルを介してソフトウェア・シンセサイザーが発音する

Web MIDI APIの応用

今回はブラウザからMIDI出力を用いてVSTiを鳴らすまでの簡単な処理ですが、実際はMIDIメッセージで細かい音階、音量の調節をしたり、チャンネル変更を行うことによる音色を変更、VSTiの持つ独自コントローラーの変更なども可能です。外部から送信されたMIDIメッセージを受け取り、Web Audio APIで音を出力する、またはブラウザ内で入出力を完結するなど、標準的なMIDIとしての扱い方に加え、requestAnimationFrameを使用したシーケンス機能の実装なども現段階で実現が可能です。

今後、Web MIDI APIが標準化された際には、より音楽・音声と親和性のあるWebの実現も可能になるかもしれません。