2013年7月1日にChrome CanaryにMIDI InputがやってきたWeb MIDI API。そろそろOutputも来るはずですが、今のところJazz-PluginをインストールしてWeb MIDI API Shimを使って動かします^^ でも「MIDIって難しいんでしょ?」という声が聞こえて聞こえてきそうなので、聞こえてくる前にWrapperなるものを作ってみました。

Web MIDI API Wrapper

Wrapperの話に入る前に「MIDIってなんぞ?」ってところを少し書きます。一言で言うと 「メーカの枠を超え、電子楽器と電子楽器を接続する為のプロトコル」なんです。 歴史は30年前のNAMM Show(National Association of Music Merchants Show)という楽器の見本市でお披露目され (今年はちょうど30周年に加えテクニカルグラミー賞を受賞)、その後電子楽器の接続のみならず照明機器のコントロール、通信カラオケ、携帯電話の着信音の制作など幅広く利用、応用されています。有名なのは、HollywoodにあるUniversal StudiosのWaterworldのアトラクションで、ライトと演出の同期、キュー出しにMIDIが使われています。そのアトラクションがこれ▼です。(12分00秒辺りが同期とか必要そう)

Wrapperの説明に入ります。 今のところ、とりあえずMIDIを意識せずともアプリケーションを簡単に構築できるように、基本的な以下のメッセージに対応にしています。

  • NoteOn:音を鳴らす
  • NoteOff:音を止める
  • PitchBend:ピッチを上下させる
  • Sustain:ダンパーペダルの操作
  • Modulation:揺らぎ
  • AllSoundOff:残響、Sustainを含め即時に全ての音を止める
  • ResetAllController:PitchBend、After-Touchなどのコントローラを初期値に戻す
  • AllNoteOff:発音している音を全て止める

使い方はこんな感じです。 まずは WebMIDIAPI.js と WebMIDIAPIWrapper.js にリンクします。(今回は都合上、前準備だけになってしまいました。。。)

<script src="[PathToJS]/WebMIDIAPI.js"></script>
<script src="[PathToJS]/WebMIDIAPIWrapper.js"></script>

次にConstructorを作ります。

var wmaw = new WebMIDIAPIWrapper( false );

続いて MIDI Input 、 MIDI Output が指定されたときの EventHandler を指定します。

wmaw.setMidiInputSelect=function() {
  /* MIDI Input の選択リストを表示する EventHandler */
  /* 選択リストから MIDI Input が選択された時の EventHandler もここに書く*/
}
wmaw.setMidiOutputSelect=function() {
  /* MIDI Output の選択リストを表示する EventHandler */
  /* 選択リストから MIDI Output が選択された時の EventHandler もここに書く*/
}

最後に Web MIDI API を Fire する。

wmaw.init();

これで MIDI Input、Output デバイスのリストが表示され、デバイスを選択できる状態になります。デバイスを選択した状態でMIDIメッセージを送れば音がなったりします。例えばこんな感じ▼

wmaw.sendNoteOn(0, 0, 69, 60, 0);
wmaw.sendNoteOff(0, 0, 69, 60, 500);

これでA4の音が500msecの間、60の強さ(最大127)で鳴ります。

ここから!というところですが、この先を書くと長くなりそうなので今回はここまでにして、次回はMIDIメッセージの送信について書こうと思います!!

お楽しみに^^

7月19日に GoogleJapan で行われた DemoPartySpeechJammer+というアプリケーションを展示させていただきました。以前 SpeechJammer というのを作って公開していたのですが、何かが起こったみたいでアクセスがとても伸びていまして、ならばもう少し楽しくできないか??という発想で続編的なのをりました。

まず、SpeechJammer の説明です。2012年のイグノーベル賞アコースティック部門を受賞したアルゴリズム、および物理ガジェットです。(注:アルゴリズムの発明者は産総研にいらっしゃる栗原一貴氏で私ではありません。)アルゴリズムは「集めた音を0.2秒後に再生をする」という極めてシンプルなものですが「集める音」を自分の会話の声として「0.2秒前の自分の会話が再生される」のを実際に体験すると、違和感を感じ会話をストップしたくなってしまいます。名前の由来もここにあると思います。ユースケースとしては「ちょっとおしゃべりな人に静かにしていただきたいときに使う」という感じになります。 ただ、「0.2秒前の自分の会話が再生される」というのに違和感はあるのですが、何か楽しさもあるんです、不思議と。そこで「もっと面白くできないかな〜??しかもブラウザ上で。」と考えたところ「0.2秒前の会話にサウンドエフェクトをかけて再生してまえ!あと0.2秒前の映像も流してしまえ!」と思いつき実装をしてみました。個人的には理由はわかりませんが、暇つぶしに遊んでしまう、おもちゃ的な楽しさがあると感じています。

技術的には、マイク入力・カメラの扱いで getUserMedia() (WebRTC)、サウンドエフェクトには WebAudioAPI、あと細かいところですがノブには polymer/WebComponents (@agektmr氏作)を使っています。(Web MIDI API も使いたかったのですがアイデア不足の為、今回は組み込めませんでした。。。orz)SpeechJammer+ のソースコードは github で公開しているので、気になる方はこちらからどうぞ!!
ソースコード>
そして、LiveDemoはこちらになります LiveDemo
ライブデモは Google Chrome(ブラウザ)での動作を、またハウリングを起こしやすいので、ヘッドホンをしてお試しください。映像のプレイバックは力技でやっていまして、遅延が時間とともに酷くなりますので、おまけ程度ということでお願いします。。。

DemoPartyについては、MAKERな方々が集まってワィワィやる的なイベントでした。 すっごく楽しかったです。そしてウダーの宇田さん、オタマトーンで明和電機さんもいらっしゃってデモ兼プレゼンテーションをされていました。 私もプレゼンテーションをさせていただきましたが、デモで音声を出せなくちょっと残念でした&準備不足ですみませんでした、、、orz
こんな感じでプレゼンテーション(@tyoneさん、写真ありがとうございました!!)▼

MAKER Conferenceには1度だけ参加経験はあるものの、展示する側としてMAKER系のイベントに参加するのは初めてでしたので、かなりドキドキでした。終わってみると展示する側として参加して、また違った楽しみ方があることを知りました。ホント、楽しかったです。こんな素敵な場を提供くださったGoogleの皆様本当にありがとうございました!!

ついにMIDI機器をブラウザで扱える日を迎えました!!そうです、Web MIDI APIです。W3C的にはもうすぐLC(Last Call:最終草案)の段階で、ここを迎えるには「2つ以上のブラウザに実装されている」ことが条件になってる1つをGoogleさんが90%以上(sendが未実装なので)超えてくださいました。感謝感謝です!!! オリジナルの記事はこちら!!

まずは仕様の変更点。

(0) 変更じゃないけど、まだflag付きです。

chrome://flags/#enable-web-midi
をenableにしてください。しかしながら、send() は未実装です。

(1) requestMIDIAccess の引数が変更されました。

  • 変更前 navigator.requestMIDIAccess( successCallback, failureCallback );
  • 変更後 navigator.requestMIDIAccess({sysex:true/false}).then( successCallback, failureCallback );

SysExで楽器の情報をDumpできちゃので、それを明示的に指定するのが目的だと思われます。今後、{sysex:true} になってる場合、ユーザに許可を求めるダイアログが出るかもしれないです。

(2) event名 message が midimessage に変更されました。

(3) getInputs() -> getInput(id)、getOutputs() -> getOutput(id)が、1段階になりました。

inputs()、outputs() するとMIDI機器のリストが配列が返ってきて、配列のIDを指定することでそのMIDI機器を扱えるようになります。
※ この変更で扱いが更に手軽になりました。

変更点は以上です。 Chris Wilson氏のPolifillもこれらの変更に対応済みでした。


そして今回は「Web MIDI API!!」と騒いでる自分も何かできないか??と考えた結果、需要があるか分かりませんが事始めにテストでも書いてみることにしました。というのも、先月 Test the Web Forward という「Web標準の仕様書を見て実際にテストを書いてContributeしちゃおう!!」というイベントに参加してきまして、その応用でもあります。このイベントで重要なのは「みんなでテストを書いてContributeすることで、ブラウザ間のInteroperabilityを高めよう」というところだと私は理解しました。


そしてWeb MIDI APIのテストはこれ Web MIDI API Test
※スゴイモノではありませんのでびっくりしないでください(><)
OSX の Chrome Canary以外で動作させるには、http://jazz-soft.net/ の Jazz-Plugin のインストールが必須です。

このテストを使って、以下の組み合わせで動作することを確認をしました。(全てPolyfillを使ってのテストになります)ここに書いてないのは試していないだけなので、情報をいただけるとありがたいです(・ω<)

  • OSX: Chrome(27.0.1453.116)、Firefox(22)、Safafi(6.0.5 (8536.30.1))、Opera(15.0.1147.132)
  • Windows8: IE10、Firefox(22)、Chrome(27.0.1453.110)

Slideshareに「Chrome Packaged Apps」なんて資料を上げちゃった訳ですけど、、、その経緯などを書いてみます。

まず、Chrome Packaged Appsって何??ってところです。

ど〜ん!!これです http://developer.chrome.com/trunk/apps/about_apps.html

Google I/O 2012でアナウンスされ、Chromium Blogで 2012/08/09 に正式に告知されったぽいです。( http://goo.gl/R1yVp ) 簡単にいうと「Chrome Packaged AppsとはWebAppsをNative Appsのように見せることができちゃう&WebAppsでは使えないRawSocketを送受信できたり、BluetoothとかのデバイスにアクセスできるAPIが用意されたPlatform(?)」という感じです。個人的にはLaunch当初にちょっとお触りしたきり、気になりつつも放置しちゃっていました。

が、1月末に仕事(NAMM Show)でアメリカに行った際、Chromebookを購入してみて、「なるほどChrome Packaged AppsはChromeOS(WebOS)上でのNativeAppsか!」と気がつき(今更。。。)「やっぱりおもしろいじゃん。」ということで現状をまとめた、結果がこの(あの?)資料▼だということになります。特に勉強会等で発表をした訳ではありませんf(^-^;)

Chrome Packaged Apps from Ryoya Kawai

Chrome Packaged Appsは基本WebAppsですので、その言語はHTML、JavaScript、CSSなので「開発言語はHTML5です!」と言えますね。資料の中でも書いてますが、一度の覚えてしまえばNativeApps的なものまで書けちゃうHTML5。NTTコミュニケーションズさん主催のWeb of Things カンファレンスにお邪魔させていただいたときに株式会社ニューフォリア 取締役 最高技術責任者 の 羽田野氏 が「HTML5の最大の魅力は言語を複数勉強する必要がないんですよ」とおっしゃっていてものすごく納得し、「Build Once, Run Everywhere」という夢のような言葉に対抗するこんな言葉を思いつきました。

「Learn Once, Build Everywhere」

そのままですが「一度学習(Learn)したら、どこでもアプリを作れます(Build)よ」という意味で、自分ではかなりお気に入りの言葉ですf(^-^;) ちょっと言い過ぎですかね。

ということで、何が言いたかったのかよくわからないPOSTになってしまいましたが、今回はこのへんで。

12月5日にもレビューしましたが、アップデートされていますので再度レビューします。
仕様はここを参照しています。(hogeは前回のレビューからの変更点です。漏れがあったらごめんなさい><)

最初に全体的な構成です。
(i)がinterface、(m)がmethodを示しています。また「->」の意味ですが、例えば「A -> return B」だとすると「Aを実行すると戻り値としてBがくる」という意味で使っています。

(i)Navigator
   (m)requestMIDIAccess -> return void;
  (i)MIDIAccess
   (m)getInputs -> return sequence<(i)MIDIPort>
   (m)getOutputs -> return sequence<(i)MIDIPort>
   (m)getInput -> return (i)MIDIInput
   (m)getOutput -> return (i)MIDIOutput
  (i)MIDIPort
  (i)MIDIInput
  (i)MIDIOutput
   (m)send -> return void
  (i)MIDIEvent

次に(i)のついているinterfaceを説明します。

Navigator
    interface Navigator {
      void requestMIDIAccess(successCallback, optional errorCallback)
    }
  MIDIAccess
    (UserAgentに接続されているMIDI機器のリストアップ、またアクセスを可能にします)
    interface MIDIAccess {
      sequence<MIDIPort> getInputs();
      sequence<MIDIPort> getOutputs();
      MIDIInput          getInput(MIDIPort or DOMString or short target);
      MIDIOutput         getOutput(MIDIPort or DOMString or short target);
    }
  MIDIPort
     (MIDIのInput/Outputポートで、名前、製造会社、MIDIポートのタイプ(input/output)、ユニークIDを提供します)
    interface MIDIPort {
      DOMString    id;
      DOMString?   manufacturer;
      DOMString?   name;
      MIDIPortType type; // input or output
      DOMString?   version;
    }
  MIDIInput
     (onmessageはMIDIメッセージを取得した時のEventHandlerを定義します)
    interface MIDIInput : MIDIPort {
      attribute EventHandler onmessage;
    }
  MIDIOutput
    (MIDIPortに組み込まれていて、またMIDIメッセージをOutputポートへ送信するメソッドを提供します)
    interface MIDIOutput {
      void send(sequence<octet> data, optional double timestamp);
    }
    (※octetとはココで定義されている通りで「[0, 255]の値をとる符号なしInteger」です)
    (※SysExのような長いMIDIデータを効率的に渡す手段としてUint8Arrayも使用可能です)
  MIDIEvent
       MIDIInputのonmessage handlerに渡されたEventオブジェクトで、
       MIDI data byteに加えてtimestampも含まれています。
    interface MIDIEvent: Event {
      attribute double      receivedTime;
      attribute Uint8Array  data;
    }
    (※Uint8ArrayのdataにはMIDIメッセージが1つずつ入ってきます)

最後に(m)のついているmethodの説明です。

  requestMIDIAccess(successCallback, optional errorCallback)
        (ユーザーシステムにあるMIDI機器のリスト、アクセスを取得します)
        successCallback: MIDI機器が取得できた場合のCallback
        errorCallback: なんらかの理由でMIDI機器を取得できなった場合のCallback
  getInputs()
        引数なし
        利用可能なMIDI input port[(i)MIDIInput]のリストを配列で返す
  getOutputs()
        引数なし
        利用可能なMIDI output port[(i)MIDIOutput]のリストを配列で返す
  getInput(target)
        target: (i)MIDIPort、または、(i)MIDIPortのid[DOMString]、
                         または、 getInputs()で取得したindex[short]
  getOutput(target)
        target: (i)MIDIPort、または、(i)MIDIPortのid[DOMString]、
                          または、getOutputs()で取得したindex[short]
  send(sequence<octet> data, optional double timestamp)
        data: sequence<octet> 
        timestamp: double

といった仕様になっています。

前回のレビューとの差分をリストアップすると以下の通りです。

Method名の変更

  • getMIDIAccess() → requestMIDIAccess()
  • enumerate{Inputs, Outputs}() → get{Inputs, Outputs}()

Attribute名の変更

  • (i)MIDIPort: fingerprint → Id

型の変更

  • data(@(i)MIDIOutput)): Uint8Array → sequence
  • timestamp: DOMHighResTimeStamp → double (millisec order)
  • onmessage(@(i)MIDIInput): callback → EventHandler

以下のようなOpen Issueもありますので、今後も仕様の更新は続くはずです。