p5.js Web Editor上で「Web MIDI API」を使って USB MIDIコントローラー（nanoKONTROL2）の入力を JavaScript で処理する

タイトルの通りで、以下のツイートの動画の内容を試した話です。

https://twitter.com/youtoy/status/1547427464091439104

USB MIDIコントローラーは、以下の「[KORG の nanoKONTROL2](https://www.korg.com/jp/products/computergear/nanokontrol2/)」を使っています。

https://twitter.com/youtoy/status/1295197940181381120

## この記事で扱う内容
この記事では、Web MIDI API を利用しますが、その詳細については一部のみしか触れません。
また、Web MIDI API はブラウザとデバイスとの間で「双方向通信」が行えますが、今回の内容では「デバイス ⇒ ブラウザ」の片方向のみを利用します。

Web MIDI API の詳細が気になる方は、ご自身でググって調べていただくか、以下の記事などをご覧ください。

●MIDIデバイスの準備不要、Web MIDI APIの基礎 | HTML5Experts.jp
　https://html5experts.jp/ryoyakawai/16787/
●Web MIDI APIを扱うためのMIDI基礎知識
　https://zenn.dev/okunokentaro/articles/01f9reeb0d7mc8110knpfra4tk

それと、プログラムの実行環境には「[p5.js Web Editor](https://editor.p5js.org/)」を利用しますが、p5.js Web Editor の使い方・p5.js の詳細については触れません。

## Web MIDI API に対応しているブラウザ
Web MIDI API に対応しているブラウザは、以下の表（※ Can I use より抜粋）の通りです。

●"midi" | Can I use... Support tables for HTML5, CSS3, etc
　https://caniuse.com/?search=midi
![Web MIDI API の対応ブラウザ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/05f65545-e7ca-b309-6e0d-a2362c47b7d7.png)
デバイス系の API にありがちな、Chrome系メイン（※ 上記の表の Microsoft Edge も、Chromium ベースのバージョンが該当）という対応状況です。

この後に出てくる内容では、自分は Chrome を使っています。

## Web MIDI API を使う
### デバイスとの接続
以下の MDN のサイトのサンプルと、ググって出てきた「p5.js Web Editor上で Web MIDI API を動かした方のサンプル」の両方を見つつ、まずはデバイスとの接続を行う処理を書いてみます。

●Web MIDI API - Web APIs | MDN
　https://developer.mozilla.org/en-US/docs/Web/API/Web_MIDI_API
●p5.js Web Editor | web midi test
　https://editor.p5js.org/cdaein/sketches/01tCK67N_

今回の記事では、簡単のため、例えば「エラー絡みの処理（※ 一部、サンプルに掲載されているものを除く）」や「途中でデバイスとの接続が途切れた場合の処理」などは実装しません。

以下のプログラムは、利用する USB MIDIコントローラーを 1台だけ PC につないだ状態で動作させてください。なお、以下のプログラムの内容は、[MDN のページの「Gaining access to the MIDI port」の内容](https://developer.mozilla.org/en-US/docs/Web/API/Web_MIDI_API#gaining_access_to_the_midi_port)と、[上記の「p5.js Web Editor の web midi test」](https://editor.p5js.org/cdaein/sketches/01tCK67N_)の「onMIDISuccess()」の処理を元にしました。

```javascript
function onMIDISuccess(midiAccess) {
  console.log("MIDI ready!");
  const input = midiAccess.inputs.values().next(); // デバイスが 1台だけつながっている前提
  console.log(input.value.manufacturer);
  console.log(input.value.name);
}

function onMIDIFailure(msg) {
  console.log("Failed to get MIDI access - " + msg);
}

function setup() {
  createCanvas(400, 400);

  navigator.requestMIDIAccess().then(onMIDISuccess, onMIDIFailure);
}

function draw() {
  background(220);
}
```

上記の処理を実行すると、コンソールで以下の出力が得られます。
![コンソール出力（デバイス名など）](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/59aa70f3-7c34-bede-f4b9-6fb9210ae143.png)

「manufacturer」が「KORG INC.」、「name」が「nanoKONTROL2 SLIDER/KNOB」となっており、意図したデバイスの情報が得られていそうです。

### デバイスからの入力を受けとった時の挙動（ノブ、スライダーを使った時）
さらに、デバイスからの入力を受けとる処理を追加してみます。

#### 実際煮えられるデータを見て仕様を探る
USB MIDIコントローラーからデータを受けとる部分も、[上記の「p5.js Web Editor の web midi test」](https://editor.p5js.org/cdaein/sketches/01tCK67N_)の「onMIDIMessage()」の処理を元にして、作成してみます。

先ほどのプログラムの「onMIDISuccess()」に処理を 1行追加し、「onMIDIMessage()」の部分を新規に追加しました。

```javascript
function onMIDISuccess(midiAccess) {
  console.log("MIDI ready!");
  const input = midiAccess.inputs.values().next(); // デバイスが 1台だけつながっている前提
  console.log(input.value.manufacturer);
  console.log(input.value.name);

  input.value.onmidimessage = onMIDIMessage;
}

function onMIDIMessage(message) {
  const data = message.data;
  console.log("MIDI data: ", data);
}
```

これで、USB MIDIコントローラーのスライダーやノブをいくつか動かしてみて、出力される値を見てみます。
![動かすスライダー・ノブ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/88878bb0-c890-2dde-3cb8-ff23006105ac.png)
具体的に動かしてみたのは、上記の 1〜3 のスライダー・ノブで、得られたデータは以下のとおりです。

1： スライダー ⇒ Uint8Array {0: 176, 1: 0, 2: 【0〜127】}
2： スライダー ⇒ Uint8Array {0: 176, 1: 1, 2: 【0〜127】}
3： ノブ ⇒ Uint8Array {0: 176, 1: 16, 2: 【0〜127】}

データを比較してみると、スライダー・ノブを区別するのには 2バイト目を利用すれば良さそうで、スライダーやノブの操作結果で得られる値は 3バイト目を見ると良いようです。
とりあえず仕様の詳細が不明でも、「onMIDIMessage()」の中の処理で、以下の処理を行うことで USB MIDIコントローラーの入力を扱えそうな感じになりました。
 ・「data[1]」で、2つのスライダー・1つのノブを区別
 ・「data[2]」で、スライダー・ノブの操作結の値を得る

#### ドキュメントなどから仕様を探る
とりあえず、上記までで USB MIDIコントローラーの入力値を扱える状態になりました。
しかし、せっかくなので、今回利用している USB MIDIコントローラー「nanoKONTROL2」の、ノブ・スライダーの操作に関する仕様も確認してみます（※ 以下を見てみます）。

●ダウンロード | nanoKONTROL2 | KORG (Japan)
　https://www.korg.com/jp/support/download/product/0/159/
●コントロールチェンジ:Control Changeとは | 偏ったDTM用語辞典 - DTM / MIDI 用語の意味・解説 | g200kg Music & Software
　https://www.g200kg.com/jp/docs/dic/controlchange.html
●DTM技術情報 - 1.MIDIメッセージ一覧 | g200kg Music & Software
　https://www.g200kg.com/jp/docs/tech/midi.html

「nanoKONTROL2 パラメータ・ガイド」を見てみると、「ノブ」・「スライダー」の操作は、「コントロール・チェンジ・メッセージを送信」という動作になるようです。
![「ノブ」・「スライダー」の操作について](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/a56399a9-fac9-1052-7d19-5e6441a04dd9.png)

また、コントロール・チェンジ・メッセージの仕様について、以下のように書かれています。
![コントロール・チェンジ・メッセージの仕様](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/f7fce5aa-c1f1-6090-6849-889ec81da703.png)

上で「ノブ」を動かした時、得られる値が「Uint8Array {0: 176, 1: 16, 2: 【0〜127】}」となっていましたが、「1: 16」の部分はコントロール番号、「2: 【0〜127】」の部分はデータとなる、というのは仕様に合致しているようです。
「0: 176」の部分は、4バイトずつに分けて 10進数にすると「11」と「0」になりますが、「11」を 16進数にしたものが上記の画像の「B」にあたり、「0」のほうは上記の画像の「X」のチャンネルに該当するという理解で良さそうです。

ちなみに、他のスライダーやノブ、ボタンを一通り操作してみましたが、第1バイトの部分のデフォルト値は「全て176」となるようでした。
※ 購入時のデフォルトの状態で、設定変更などを行わず利用している状態での話です

### デバイスからの入力を描画に反映させる
それでは、USB MIDIコントローラーのスライダー・ノブの操作を、p5.js の描画に反映させてみます。

以下の画像で示したように、スライダー・ノブを背景色（※ HSB で指定）と対応させてみます。
![hsbとの対応](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/d8a4d258-3e51-f1f1-8509-4e51b0347c1a.png)

```javascript
let h = 0,
  s = 0,
  b = 0;

function onMIDISuccess(midiAccess) {
  console.log("MIDI ready!");
  const input = midiAccess.inputs.values().next(); // デバイスが 1台だけつながっている前提
  console.log(input.value.manufacturer);
  console.log(input.value.name);

  input.value.onmidimessage = onMIDIMessage;
}

function onMIDIMessage(message) {
  const data = message.data;

  if (data[1] === 0) {
    b = map(data[2], 0, 127, 0, 100);
  } else if (data[1] === 1) {
    s = map(data[2], 0, 127, 0, 100);
  } else if (data[1] === 16) {
    h = map(data[2], 0, 127, 0, 360);
  }
}

function onMIDIFailure(msg) {
  console.log("Failed to get MIDI access - " + msg);
}

function setup() {
  createCanvas(500, 400);
  colorMode(HSB, 360, 100, 100);
  background(0);

  navigator.requestMIDIAccess().then(onMIDISuccess, onMIDIFailure);
}

function draw() {
  background(h, s, b);
}
```

このプログラムが、冒頭にも掲載していた以下の動作をさせるためのプログラムになります。

https://twitter.com/youtoy/status/1547427464091439104

これで、p5.js Web Editor上で Web MIDI API を使った処理（USB MIDIコントローラーの入力を使った処理）を実現することができました。

## ライブラリ「WEBMIDI.js」について
[p5.js公式の libraries のページ](https://p5js.org/libraries/)を見ると、MIDI関連で以下のライブラリが掲載されています。
![p5.js公式の libraries のページ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/87a46c3d-d165-c528-6940-fb6427cae1db.png)

公式ページは以下です。

●WEBMIDI.js | WEBMIDI.js
　https://webmidijs.org/
![WEBMIDI.js の公式ページ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/c4974136-6acd-f3ef-91d3-e6ba630a2142.png)

最初はこの MIDI用のライブラリの仕様を見てみたり、少し試そうとしたりしたのですが、今回の内容であれば Web MIDI API をそのまま使ってもそれほど複雑にはならなそうだったので、上記のライブラリは使わずに実装してみました。

しかし、もっと Web MIDI API の仕組みをいろいろ使おうとすると、ライブラリを使ったほうが良さそうな場合が様々出てきそうなので、またあらためてこのライブラリの仕様・使い方は確認できればと思っています。