蛇腹楽器型MIDIコントローラーV3 ～ M5 AtomS3 & BMP390への換装

[前回V2となった蛇腹楽器型MIDIコントローラー("Jabara MIDI")](https://qiita.com/tomoto335/items/d20aa668a62ad49cda36)をさらに進化させた。なんじゃそれは？と経緯を知らない読者は[V1の記事](https://qiita.com/tomoto335/items/92580af26b5c171dc1c0)を見てみて欲しい。

きっかけは[M5StackのAtomS3](https://docs.m5stack.com/en/core/AtomS3)の登場である。アプリからUSBコントローラーを使えるESP32-S3を搭載し、128x128ピクセルの小さい液晶ディスプレイも付いているので、前回のLolin S2 Mini(USB MIDIデバイス本体担当)とM5StickC(操作盤担当)のMPU 2台使いという大袈裟な構成を解消できる。結果的には御覧の通りすっかりシンプルになり、「いつ崩壊するかわからない仮組みの試作品」から「準実用の試作品」に引き上げることができた。

<img src="https://lh3.googleusercontent.com/pw/AJFCJaXbTeMDvDdFkilCfs4A2GrqEZOGTCpgqLi7_7uCBqNQ82nfDtcnq4G_CsacyhyPYm00yMkgPnKHqS0Vc0sQQA9kgd0XJHS216iHX56EQaM3SDAGZGaKGWYlTTqQnqgEczY_aKtb24mUOap5Jg9OcQ6npQ=w1148-h864-s-no?authuser=0"/>

せっかくなのでついでに気圧センサーをBME280から[BMP390](https://www.adafruit.com/product/4816)という新世代の高精度モデルにアップグレードした。V2と同じく内圧用と外気圧用に2個のBMP390を贅沢に(?)用いて差動動作させる。これで気圧変化の検出精度が上がり、より楽に、より豊かな表現が可能となった[^1]。

次の動画はV1のときと同じ音列を用いたV3によるデモ演奏である。V1に比較して音量変化がリニアであり、蛇腹の小さな動きに敏感に反応していることが見て取れると思う。また感度が高い＝空気の利用効率が高いため、蛇腹の全体的な動きも小さく、無駄が減っている。(音楽的にはそれほどいい演奏でもないが、そこは突っ込まないで頂きたい。)

<iframe width="560" height="315" src="https://www.youtube.com/embed/PyreB7TNCz0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

[^1]: 科学的に評価したわけではなくプラシーボの可能性は排除できない。あくまで筆者の主観である。

# AtomS3によるUSB MIDIデバイスの作り方

AtomS3をMIDIデバイスとして動作させる方法は、本質的には[V2でLolin S2 Miniを使ったとき](https://qiita.com/tomoto335/items/d20aa668a62ad49cda36#usb-midi%E3%83%87%E3%83%90%E3%82%A4%E3%82%B9%E3%81%AE%E4%BD%9C%E3%82%8A%E6%96%B9)と同じである。以下にAtomS3固有の気付き事項や注意点だけ挙げる。

## ボードマネージャの設定

* ArduinoのESP32ボードマネージャから `M5Stack-ATOMS3` を選ぶ。
* 設定項目の `USB Mode` を `USB-OTG (TinyUSB)` にする。
  * デフォルト(`Hardware CDC and JTAG`)のままだと、USBはプログラム書き込み＆シリアルポートに固定され、アプリで自由に使うことができない。
* `USB CDC On Boot` も `Enabled` にしてシリアルポートを使えるようにしておく。
* 他はデフォルト設定でOK。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/465152/1bd102e9-96fd-ee0c-73be-da57cf0c5d56.png)

## アプリ実行モードとプログラム書き込みモード

* `USB Mode` がデフォルト(`Hardware CDC and JTAG`)の場合、モードの区別はない。
* `USB Mode` を `USB-OTG (TinyUSB)` にしてプログラムを書き込み、リセットするとアプリ実行モードに移行する。
  * プログラム書き込み後自動リセットはかからないので、手でリセットボタンを押す必要がある。
  * `USB CDC On Boot` を `Enabled` にしていれば、この時点でプログラム書き込み用とは別のCOMポートが見えるようになる。
* リセットボタンを長押し(緑のLEDが光るまで)すると、アプリ実行モードからプログラム書き込みモードに入る。

ちなみに筆者環境ではアプリ実行モードでは左画像、プログラム書き込みモードでは右画像のようなCOMポートが見えるので、どちらのモードにあるかはこれで区別できる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/465152/6eb4e61e-7e56-75f4-6503-2eb77f3dd519.png)

## ライブラリ関連

* ここまで来れば、V2でも紹介したAdafruit TinyUSB LibraryのMIDIのサンプルがコンパイル＆実行できるはずで、AtomS3のMIDIデバイスとしての動作が確認できるはず。
  * Adafruit TinyUSB Libraryには最近ESP32-S3対応の修正が入っているので更新しておくこと。
  * 送信/受信の片方しか動かないことがあったので、動作テストは両方向とも試すとよい。
* [M5StackのAtomS3用ライブラリ](https://github.com/m5stack/M5AtomS3)は使わない。
  * `USB Mode` を `USB-OTG (TinyUSB)` にするとコンパイルが通らなかった。実は、以前[こんな記事](https://tomoto335.hatenablog.com/entry/m5atom-dont-use-library)で書いたように、そもそもM5Stackのライブラリは価値よりも多くの問題を持ち込むことがあり、必要なければ使わない方がよい。
  * 画面描画にはLovyanさんがメンテしている[M5GFX](https://github.com/m5stack/M5GFX)を使う。

# 蛇腹楽器部分の実装

V2で説明を保留した蛇腹楽器部分の実装について、今回は概略に触れてみたい。

## ハードウェア

JabaraMIDI V3のハードウェアブロック図:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/465152/9ebdce2b-611b-a4cd-d146-b3b803f0fe2a.png)

* AtomS3のGroveコネクタにBMP390 2個と[エンコーダーユニット](https://shop.m5stack.com/products/encoder-unit)をI2Cでぶら下げる。接続にはGroveやSTEMMA QTコネクタを用いて配線もすっきり。
* Groveコネクタのピン番号がSDA=2, SCL=1なので、I2Cの初期化コードは `Wire.begin(2, 1)` で。
* 2個のBMP390に異なるアドレスを振るため、1個はアドレス設定ジャンパーをはんだでショートする。

## ソフトウェア

JabaraMIDI V3のソフトウェアブロック図:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/465152/9be09ea9-b838-7a87-abda-131ab1a8851c.png)

蛇腹楽器としてのコア部分、つまりどのデバイスでアプリを書いても変わらない部分は JabaraMIDI Library (緑の箱)として分離してある。これには以下のような機能が含まれる。

* 気圧センサーの適切な設定とデータ読み取り(Sensor)
* センサーのキャリブレーション(Calibrator)
* 気圧データのMIDIデータへの変換(Processor & Config)
* USB MIDIの初期化

この上にアプリケーション JabaraMIDI App (黄色の箱)が乗る。メインループはもちろんのこと、各種ハードウェアの初期化と接続、設定UIといったデバイス依存の処理をこちらで実装する。

リポジトリはこちら:

https://github.com/tomoto/JabaraMIDI_Library

https://github.com/tomoto/JabaraMIDI_App

以下主な処理を簡単に説明する。興味があればコード本体を見て欲しい。

### BMP390の設定(Sensor)

JabaraMIDI Libraryに含まれるSensorコンポーネント(ファイルは JabaraMIDI_Sensor_XXX.h シリーズ)は、異なるセンサーライブラリを同じ顔に見せるラッパーであるが、同時に蛇腹楽器向けに適したセンサーの設定を行うのも仕事である。今回追加したBMP390については[データシート](https://www.bosch-sensortec.com/products/environmental-sensors/pressure-sensors/pressure-sensors-bmp390.html)を見て次のような設定にしてみた。
* 消費電力は度外視して全力で計測
* 精度と測定速度のバランスを取るため、オーバーサンプリング4倍(計測時間約12ms = 毎秒約80個のデータポイント)
* IIRフィルタを軽くかける

```cpp:JabaraMIDI_Sensor_BMP3XX.h
// Measure at the maximum speed and power
bmp.setOutputDataRate(BMP3_ODR_200_HZ);
// Use 4x oversampling (~12ms = ~80Hz) with a light filtering to balance
// the accuracy, responsiveness, and the number of data points
bmp.setPressureOversampling(BMP3_OVERSAMPLING_4X);
bmp.setIIRFilterCoeff(BMP3_IIR_FILTER_COEFF_3);
```

### 気圧データのMIDIデータへの変換([Processor](https://github.com/tomoto/JabaraMIDI_Library/blob/main/src/JabaraMIDI_Processor.h) & [Calibrator](https://github.com/tomoto/JabaraMIDI_Library/blob/main/src/JabaraMIDI_Calibrator.h))

[V1で作ったProcessing版JabaraMIDIアプリ](https://qiita.com/tomoto335/items/92580af26b5c171dc1c0#jabaramidi)をC++に移植したものである。ただしV3はセンサーが2個あるので、気圧の絶対値の代わりに、[2個のセンサーの値の差をデータとして用いる](https://github.com/tomoto/JabaraMIDI_App/blob/78d618cc9c7533f34f34f76b89946e0465f75961/V3/JabaraMIDI_V3/Engine.cpp#L29-L34)。

キャリブレーションもV1と同等のアルゴリズム(c.f. 値が一定時間安定したら静止状態と見なしてその値を基準値として採用する)により行うが、やはり気圧の絶対値ではなく2個のセンサーの値の差を用いる。これにより、センサー1個のときは外気圧を推定するキャリブレーションであったのに対し、センサー2個のときは両センサーの個体差等によるオフセットの補正を行うキャリブレーションとなる。

### 気圧データ取得＆MIDIデータ送信ループ([Engine](https://github.com/tomoto/JabaraMIDI_App/blob/feat/v3/V3/JabaraMIDI_V3/Engine.cpp))

以下の処理をFreeRTOSの高優先タスクとして約80Hzで回す。
1. 2つのSensorの値を読み、差を取る
2. Calibratorに突っ込んで基準値を取得
3. 基準値に基づき補正した値をProcessorに突っ込む
4. ProcessorからのコールバックでUSB MIDIを叩いてMIDIイベントを送出する

### 設定UI([Controller](https://github.com/tomoto/JabaraMIDI_App/blob/feat/v3/V3/JabaraMIDI_V3/Controller.cpp))

M5StickCで作ったV2の操作盤と同等のものをAtomS3の128x128の画面に移植したものである。動作についてはこちらのV2版のデモ動画を見て頂きたい。

<iframe width="560" height="315" src="https://www.youtube.com/embed/DyZMNsaLk7w" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

このUIで変更できるパラメタは、CC#、CCの最小値(蛇腹静止時の値)、感度、感度カーブの形である[^2]。[V1の説明で詳細に突っ込んでいる](https://qiita.com/tomoto335/items/92580af26b5c171dc1c0#jabaramidi)のでそちらも参照のこと。またミュート・アンミュートの切り替えもできる。

こういうUIや設定永続化を毎度毎度作るのは面倒なのだが、フレームワーク化したり手を抜いたりするうまい方法がなかなかない。下手にサボるとユーザエクスペリエンスの低下として跳ね返って来るので、まぁ致し方ない。

[^2]: MIDIチャンネルは1に決め打ちで、この設定UIからは変更できない。最近のDAWは入力先をMIDIチャンネルではなくデバイスで指定できるのが普通なので、まぁ不要だろうと判断。

# 評価(V2方式との比較)

* MPUの統合とI2C配線の見直しによりハードウェアが簡略化できたのは大きな進歩。その場限りの実験的なV2とは異なり、恒久的にメンテできる実用品になった。
* 気圧センサーをBMP390にアップグレードしたことで演奏性能が上がったのも大きな進歩。
* イベント送出性能は、実際にDAWに記録されたMIDIデータから、設計通り約80Hz出ていることがわかった。(c.f. MPUの統合によりCPUやI2Cバスが忙しくなり性能が出ないのではないかという懸念があったが、特に目につく問題はなかった。) なお、Engineのタスク呼び出し周期を100Hzに上げてみたところ、イベント送出性能は逆に下がってしまった。一回の処理に10msちょっとかかっている様子で、これ以上の最適化なしには80Hzがギリギリのようだ。
* 操作用エンコーダーは左手親指で操作できるのが理想。→後日ノブの位置と形を工夫して改善した。
* V2を作った際に「そもそもスタンドアロンのMIDIデバイスにする意味は(PC上で追加のソフトウェアを動かすのが苦ではない自分にとっては)特にない、単なる技術チャレンジ」という評価を述べたが、これはV3でも本質的には変わらない。ただ、実用的なハードウェア構成が見いだせたことにより、自分以外の人がこれを作って使う可能性が上がったとするなら、あながち意味がないとは言えないかもしれない。

# リファレンス一覧

* [M5Stack AtomS3ドキュメント](https://docs.m5stack.com/en/core/AtomS3)
  このサイズでESP32-S3と液晶ディスプレイ付き、coolですよ。
* [AdafruitのBMP390ボード](https://www.adafruit.com/product/4816)
  珍しくAmazonの怪しいパチ物より安かった。STEMMA QTコネクタ付き、アドレス選択ジャンパーが出ているのもプラス。ライブラリは[こちら](https://github.com/adafruit/Adafruit_BMP3XX)。
* [BMP390データシート](https://www.bosch-sensortec.com/products/environmental-sensors/pressure-sensors/pressure-sensors-bmp390.html) (Bosch)
  オーバーサンプリング等のパラメタを選ぶのに理解が必要。
* [M5Stackのエンコーダーユニット](https://shop.m5stack.com/products/encoder-unit)
  設定操作用のロータリーエンコーダー＆プッシュボタン。ライブラリは[こちら](https://github.com/m5stack/M5Unit-Encoder)。
* [M5GFXライブラリ](https://github.com/m5stack/M5GFX)
  M5Stackのデバイス共通の画面描画ライブラリ！日本語のガイドは[こちら](https://docs.m5stack.com/ja/quick_start/m5gfx/m5gfx)。
* [M5AtomライブラリのLED機能は使うべからず](https://tomoto335.hatenablog.com/entry/m5atom-dont-use-library)
  M5StackのAtom用ライブラリのdis。優しい意図は感じるし、彼らのライブラリすべてがダメなわけでも決してないのだが、「あーこれは適当に作ってんな」という臭いがするものは確かにあって、そういうのは自分で嗅ぎ分けて避ける必要がある。