クロスプラットフォーム対応！ MIDI経由のハードウエア制御システムをサっと作る

### Webからハードを操作する環境を素早くつくりたい！

Webエンジニアです！

普段仕事でプロトタイピングすることも多く、雑にWebからハードを操作するシステムを試作することも多いです。
今回紹介するのは、つい先日のお話です。

下記のような要件のシステム（プロトタイプ）を作りました。

- 操作用のPCやスマホから別の場所にあるPCとそのPCに接続されたハードを制御する
- ハードが接続されたPCはWindows PCの上で動作させる想定だがOSはLinuxにするかもしれない。さらに開発はMacで行いたい
- PCとハードの接続にはUSBケーブルを用いる
- ハードは制御コマンドに基づいて動く。コマンド体系はいくつかのコマンド＋ON/OFF程度で単純
- 数台作れれば良い。さくっと明日までにプロトタイプを1台つくれ。的なスピード感（実際は2-3日猶予がある）

![sc1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31758/3cd4c188-081c-ac3a-097c-6c8b11c7def3.png)


まー、よくある話ですよね。
速度優先のプロトタイピングなので、下記のように作りました。

|No.|機能|実装方法|
|:---:|:---|:---|
|1|操作用PC用アプリ|Webアプリケーション。UIを提供。普通にHTML/CSS/JSで書く|
|2|ハードウエア|USB-MIDIで制御コマンドを受信後ハードを制御する|
|3|HTTP API|HTTPリクエストはnode.js+expressで処理する|
|4|MIDI Client|APIが叩かれたらMIDIメッセージに変換してデバイスに送信する|

![sc2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31758/5b946b4c-c54e-008a-f73c-2d52cad20420.png)

> 最終的には、4. の実装方法が少し上記図から変わりました。

### 1. 操作用PC用アプリ

これは普通のWebアプリです。UIを提供してボタン等を押すとAPIを呼び出します。
UIも単純なのでHTML/CSS/JSで普通に書くだけでしたので、説明は割愛。

### 2. ハードウエア

ハードウエアのIF部分をUSB MIDIに対応させるため、Arduino環境で開発可能なマイコンを利用しました。

ArduinoでUSB MIDIデバイスを作るというのも、豊富なOSSのおかげで簡単です。
主要なマイコンとライブラリの組み合わせを書いておきます。

|No.|マイコン|ライブラリ|参考情報|
|:---:|:---|:---|:---|
|1|[Arduino UNO](https://www.askul.co.jp/p/KW34333/)|[Moco LUFA](https://github.com/kuwatay/mocolufa)|[導入方法](https://webmusicdevelopers.appspot.com/codelabs/arduino-mocolufa/index.html?ja-jp#1)|
|2|[Arduino micro](https://www.askul.co.jp/p/PU81984/)/[Arduino Leonardo](https://www.askul.co.jp/p/PU78629/)|[MIDIUSB](https://github.com/arduino-libraries/MIDIUSB)|[使い方](https://qiita.com/tadfmac/items/8b532134247492427a43)|
|3|[Raspberry Pi Pico](https://www.switch-science.com/products/6900)/[Seeed XIAO RP2040](https://www.seeedstudio.com/XIAO-RP2040-v1-0-p-5026.html)|[Adafruit Tiny USB](https://github.com/adafruit/Adafruit_TinyUSB_Arduino)|←`/examples` にMIDIの例がある|
|4|ESP32|---|USB機能が無いのでUSB-MIDIデバイスには向かない。Bluetooth MIDI Deviceであれば作れる|
|5|Arduino Pro mini/Arduino Nano|---|USB機能が無いのでUSB-MIDIデバイスには向かない|

プロジェクトでは、上記2と3を利用しました。

> ボード入手できれば（[ATmega32u4](https://www.microchip.com/en-us/product/ATmega32U4)の開発ボードであれば概ね同じように使えます）、一番簡単なのはたぶん 2. ですが、2022年12月現在 半導体不足や円安の影響でATmegaの価格が高騰しており、互換ボードの入手性もかなり悪くなっています。多少環境にクセはありますが、現時点で入手性の良い[RP2040](https://www.raspberrypi.com/documentation/microcontrollers/rp2040.html)搭載ボードにも保険で対応しておくことにしました。

MIDIメッセージで伝えられたコマンドに基づいて、ハードウエア（例えばサーボモーターなどのアクチュエータ）を制御することになりますが、ここでは詳細は割愛します。

### 3. HTTP API

最近は [FastAPI](https://fastapi.tiangolo.com/ja/) に押され気味な気もしますが、慣れてるから！という理由で、node.js + express を採用しました。こちらも特に書くことはないので、リンクだけ置いておきます。

- [node.js](https://nodejs.org/ja/)
- [express](https://expressjs.com/ja/)
- [超初心者向け使い方](https://qiita.com/tadfmac/items/e61c1b915d047905ce7f)

### 4. MIDI Client（node.js から MIDIを呼ぶ方法）

node.js から MIDI を扱うライブラリはいくつかありますが、今回実は採用しませんでした。
その理由を共有するのが本記事の目的です。

> 早くいろいろ治れば嬉しいな！

#### 4-1. [node-midi](https://www.npmjs.com/package/midi)

node.js の npm モジュールで一番最初に見つけたのがコレです。
[RtMidi](https://github.com/thestk/rtmidi)というクロスプラットフォームMIDI実装のnode.jsラッパーです。

OSX / Windows / Linux に対応していて素晴らしいのですが、ちょっと依存環境が多くインストールになかなかに苦労します。

https://www.npmjs.com/package/midi
にさらりと記載されている依存関係は下記の通りです。（そのまま引用）

```
OSX
- Some version of Xcode (or Command Line Tools)
- Python (for node-gyp)

Windows
- Microsoft Visual C++ (the Express edition works fine)
- Python (for node-gyp)

Linux
- A C++ compiler
- You must have installed and configured ALSA. Without it this module will NOT build.
- Install the libasound2-dev package.
- Python (for node-gyp)
```

開発環境だけに入れれば良いわけではなく、実行環境でもこれらの環境を作る必要があります。
`npm i midi` 一発で環境構築できないのは辛いので、Windows環境でのインストールに手こずった段階で、今回は見送りました。
（これ普通にLinuxでも苦労するだろ。やってないけど。というのもあり。）

https://github.com/justinlatimer/node-midi/issues/208

> ようするに、各プラットフォーム用のビルド環境が必要なので Winodwsの場合は `npm install --global --production windows-build-tools` でビルドツールを事前に入れておけばOKのようです。

#### 4-2. [webmidi](https://www.npmjs.com/package/webmidi)

まさかの [node-midi](https://www.npmjs.com/package/midi) 不採用でもう一度 npm を探して次に見つけたのがこちら [webmidi](https://www.npmjs.com/package/webmidi)
 です。

- [Supported Environments](https://webmidijs.org/docs/getting-started)

各種ブラウザとnode.jsもマルチプラットフォームに対応しています。

```
- GNU/Linux
- macOS
- Windows
- Raspberry Pi
```

ブラウザの対応状況からブラウザ版は[Web MIDI API](https://www.w3.org/TR/webmidi/)のラッパーかな？と予想しますが、node.js 版は下記記載の通り、[JZZ](https://www.npmjs.com/package/jzz)に依存しているようです。

```
Support for the Node.js environment has been made possible in large part by the good folks of Jazz-Soft via their JZZ module.
```

JZZがどうやら Web MIDI API の node.js 版 Polyfillのようです。なるほど。そっち使ってもいいな。

で、これを使おうとしたんですが、なぜか動かない！（Macbook Air 2021 M1 Ver.で開発しています）
JZZの方も動かない！

いろいろ調べたら原因はコレ。

https://github.com/jazz-soft/jazz-midi/blob/master/node/index.js

```
var path='./bin/';
var v=process.versions.node.split('.');
if (v[0]<=10) path+='10_15/';
else if (v[0]<=11) path+='11_15/';
if(process.platform=="win32"&&process.arch=="ia32") path+='win32/jazz';
else if(process.platform=="win32"&&process.arch=="x64") path+='win64/jazz';
else if(process.platform=="darwin"&&process.arch=="x64") path+='macos64/jazz'; // <- ? arm版がない!
else if(process.platform=="linux"&&process.arch=="x64") path+='linux64/jazz';
else if(process.platform=="linux"&&process.arch=="arm") path+='linuxa7/jazz';
module.exports=require(path);
module.exports.package=require(__dirname + '/package.json');
```

JZZやwebmidiが依存してる [jazz-midi](https://github.com/jazz-soft/jazz-midi)がまだ Appleシリコンに対応しておらず、Appleシリコン版のバイナリがないようです。残念！

> 2025.05.21 追記
> 改めてJZZの mac appleシリコンのサポートが追加されていないか? 確認してみたところ、現在では追加されているようです！今なら無理やりブラウザ使わなくても良さそうですね！

開発者の開発環境で動かないモノは非採用！ということで。

#### 4-3. 最終手段！ブラウザを使う。

今回残念ながら、[node-midi](https://www.npmjs.com/package/midi)、[webmidi](https://www.npmjs.com/package/webmidi)や[JZZ](https://www.npmjs.com/package/jzz)を見送ることになりました。

大丈夫か？ node.js！（少し不安に。。。）

あまりこの手は使いたくなかったのですが、非常に簡単に導入できて、クロスプラットフォームなMIDI環境があります。そう。それはChrome（と共通のコアを使ったブラウザ群）。

下記のように対応しました。

1. WebSocketメッセージを受信したらWeb MIDI API を使ってMIDIメッセージを送信するWebページを作る。
2. node.js の API 起動時に WebSocketサーバーも起動しつつ、その後 [puppeteer](https://pptr.dev) を使い、1. の Webページを表示しておく。
3. expressでHTTPリクエストが来たら、WebSocketで 1.のページにWebSocketメッセージを送る。

![sc3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31758/d31722bd-99ea-566f-65f1-2ab6c00f0fd2.png)

APIの先でWebページに連携という面白アーキテクチャですが、`npm i` 一発で環境構築できます。最高。

##### 注意点 1. ヘッドレスモードでの Web MIDI API の制限

Web MIDI APIは Chromeのheadlessモードだとそのままだと動作しません。
`puppeteer` を用いたヘッドレスブラウザ起動時に対策が必要です。

動かないコード
```.js
  browser = await puppeteer.launch({headless: true, args: ['--no-sandbox']});
  page = await browser.newPage();
  await page.goto('http://yourdomain/sample/sample.html');
```

このコードは、`navigator.requestMIDIAccess()`に失敗するようです。

下記のようにヘッドレスモードでなく普通にブラウザを起動すると動作します。
```.js
  browser = await puppeteer.launch({headless: false, args: ['--no-sandbox']});
  page = await browser.newPage();
  await page.goto('http://yourdomain/sample/sample.html');
```

が、ブラウザが常に起動/表示されているのはかっこ悪いです。

MIDIが動かないのはヘッドレスモードでの動作を意図的に制限していることが原因とわかりました。

- [navigator.requestMIDIAccess() throws "DOMException: An attempt was made to break through the security policy of the user agent." in headless mode](https://github.com/puppeteer/puppeteer/issues/2973)

ドメインにpermissionをつけてあげれば動くようです。
改良したコードは下記のようになりました。

動くコード
```.js
  browser = await puppeteer.launch({headless: true, args: ['--no-sandbox']});
  await browser.defaultBrowserContext().overridePermissions('http://yourdomain', ['midi']); // <-追加
  page = await browser.newPage();
  await page.goto('http://yourdomain/sample/sample.html');
```

##### 注意点 2. Windowsは 複数のアプリから同時にVirtual MIDIポートがオープンできない

これ、ユースケースによっては結構致命的だと思うんですけど、Windowsの場合そういうもののようです。

例えば、Chromeで Web MIDI API を使うサイトを開いてる状態で、他のブラウザでも Web MIDI API を利用しようとしたら失敗するようです。

`RtMidi` などブラウザ以外のMIDIポートオープンでも同様の問題が発生するので、OSの仕様? なんでしょうか？

```
MidiOutWinMM::openPort: error creating Windows MM MIDI output port.
```

上記のようなエラーメッセージ（上記は RtMidiの例）を見つけたら、他にMIDIポートをつかんでるアプリケーションが無いか？チェックしてみる必要がありそうです。


### まとめ

MIDIを使ってWebからハードを操作する環境を作りました。
ただ、今回最初想定した実装とは少し違う結果になりました。

- MIDIを使えば手軽にUSBを使ったPC→ハードウエア制御が実現できる（ただし簡単なコマンド体系のみ。大容量データの伝送等には向かないので、ユースケース次第）
- 2022/11/30現在、MIDIのクロスプラットフォーム環境で一番手軽なのはブラウザ（chrome）
- ブラウザはサーバーのようなヘッドレス環境でも利用可能

### おまけ

Web MIDI API を使ったWebアプリを超手抜きで作ってみたい人向けラッパー（ブロードキャストがデフォルト動作という曲者）
- [pomidi](https://github.com/tadfmac/poormidi/blob/master/release/pomidi.js)