M5StickCでBLE MIDIコントローラーを作ってみよう@2023

# 概要
2019年に[M5StickCでMIDIコントローラーを作ってみよう](https://qiita.com/wdice02136/items/2de784ccf752d12923de)という記事を書き、色々、機器を作って遊んでいこうと思っていた矢先、Micro:bitがV2になり、BLEの利用が制限されると同時に、BLE MIDIの熱が冷めてしまいました。

が、今年になって、熱が再燃したので、再度、挑戦してみます。

# デモ
[M5StickC x KORG Gadget Phoenix ](https://youtu.be/ayZHkzRIYqw)
(YouTube)

前と同じです。

- ボタンを押すとNote On、離すとNote Off
(たまにチャタリングでかNote Offが送信できないのはご愛嬌)
- 縦に傾けると音程変更(Pitch Bend)
- 横に動かすと音量変更(CC: 7)

# 開発環境構築
最近は、VSCodeでの開発も盛んですが、使いやすさから、前回と同じく**Arduino IDE**で開発していきます。ArudinoIDEも、メジャーバージョンアップも行われ、こちら統合Platformとして順当に進化してますね。

PC(というかMac)も変わったので改めて環境構築しました。

[公式を参考](https://docs.m5stack.com/en/quick_start/m5stickc/arduino)に、M5StickC開発環境を整えていきます。

3年前と大きく変わったことは、**BLEライブラリを別途入れる必要が無くなった**ことです。Arudino本家からESP32搭載のArduino「Arduino nano 33 IoT」が出たためでしょう。かなり開発のハードルが低くなりました。

# 構成
- M5StickC
- KORG Gadget2 for iPad(iPhoneでも可)

#ソース

```c:Theramin.ino
#include <M5StickC.h>
#include <BLEDevice.h>
#include <BLEServer.h>

//Note
const int NOTE_NUM = 60;

//BLE関連
//デバイス名
const char *DEVICE_NAME = "m5sC";

//BLE MIDIのサービスとキャラクタラスティック
const char *SERVICE_UUID = "03B80E5A-EDE8-4B33-A751-6CE34EC4C700";
const char *CHAR_UUID = "7772E5DB-3868-4112-A1A9-F2669D106BF3";

//BLEデータ
BLEServer *pServer;
BLECharacteristic *pCharacteristic;
bool isConnected = false;

//MIDIデータ関連
//ccデータバッファ
int cc[128];
int pitch;

//IMUバッファ
float x, y, z;
int intX, intY;

//Buttonバッファ
bool pressed[2];

//Display関連
int cnt;
int circleSize;
int dspX, dspY;

//BLE MIDIデータ
unsigned char buff[] = {0x80, 0x80, 0xB0, 0x01, 0x64};

//BLE関連関数
// サーバーのコールバック関数
class cbServer: public BLEServerCallbacks {
  void onConnect(BLEServer *pServer) {
    isConnected = true;
    M5.Lcd.fillScreen(BLACK); 
      M5.Lcd.drawCentreString("Connected", 40, 80, 1);
  };

  void onDisconnect(BLEServer *pServer) {
    isConnected = false;
    M5.Lcd.fillScreen(BLACK); 
    M5.Lcd.drawCentreString(DEVICE_NAME, 40, 80, 1);
  }
};

//マッピングと制限を行う関数
int mapAndLimit(int value, int fromLow, int fromHigh, int toLow, int toHigh) {
  int tmp, maxValue, minValue;

  if(toLow < toHigh)
  {
    minValue = toLow;
    maxValue = toHigh;
  }
  else
  {
    minValue = toHigh;
    maxValue = toLow;
  }

  tmp = map(value, fromLow , fromHigh, toLow, toHigh);
  tmp = tmp > maxValue ? maxValue : tmp;
  tmp = tmp < minValue ? minValue : tmp;
  return tmp;
}

//MIDI関連関数
//BLE Noteを送信
void notifyNote(int note, int velocity)
{    
  //noteをvelcityの強さで送信
  buff[2] = 0x90;
  buff[3] = note;      
  buff[4] = velocity;

  //MIDIデータをNotify
  pCharacteristic->setValue(buff, 5);
  pCharacteristic->notify();
}

//BLE MIDIのCCデータを送信
void notifyCC(int ccNum, int value, int sensitivity)
{
  if(abs(cc[ccNum] - value) > sensitivity)
  {
    //元情報にデータを入れておく
    cc[ccNum] = value;

    //valueをCC(ccNum)のデータにする
    buff[2] = 0xb0;
    buff[3] = ccNum;      
    buff[4] = value;

    //MIDIデータをNotify
    pCharacteristic->setValue(buff, 5);  
    pCharacteristic->notify();    
  }
}

//BLE MIDIのピッチベンドを送信
void notifyPitch(int value, int sensitivity)
{
  if(abs(pitch - value) > sensitivity)
  {
    //元情報にデータを入れておく
    pitch = value;

    //valueをCC(ccNum)のデータにする
    buff[2] = 0xe0;
    buff[3] = value & 127;      
    buff[4] = value >> 7;

    //MIDIデータをNotify
    pCharacteristic->setValue(buff, 5);  
    pCharacteristic->notify();    
  }
}

void setup(){
  int i;

  // M5Stackの初期化
  M5.begin();
  // MPU6886の初期化
  M5.MPU6886.Init();

  //CCバッファの初期化
  for(i = 0; i < 128; i++)
  {
    cc[i] = 64;
  }

  //Pitchバッファの初期化
  pitch = 8192;

  //Display関連の初期化
  cnt = 0;
  circleSize = 2;
  dspX = 40;
  dspY = 80;

  //Btnの初期化
  for(i = 0; i < 2; i++)
  {
    pressed[i] = false;
  }

  // BLE初期化
  BLEDevice::init(DEVICE_NAME);

  // サーバーの作成
  BLEServer *pServer = BLEDevice::createServer();
  // コールバック関数の設定
  pServer->setCallbacks(new cbServer());

  // サービスの作成
  BLEService *pService = pServer->createService(SERVICE_UUID);

  // キャラクタリスティックの作成
  pCharacteristic = pService->createCharacteristic(
    CHAR_UUID,
    BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY
  );

  // サービスの開始
  pService->start();

  //アドバタイジングの開始
  BLEAdvertising *pAdvertising = pServer->getAdvertising();    
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->start();

  //ディスプレイにデバイスネームを表示
  M5.Lcd.fillScreen(BLACK); 
  M5.Lcd.drawCentreString(DEVICE_NAME, 40, 80, 1);
}

void loop() {
  if (isConnected)
  {
    //BtnAでNote On
    M5.update();    
    if(M5.BtnA.read() && !pressed[0])
    {
      pressed[0] = true;
      notifyNote(NOTE_NUM, 100);
    }
    //BtnAを離したらNote Off
    else if(!M5.BtnA.read() && pressed[0])
    {
      pressed[0] = false;
      notifyNote(NOTE_NUM, 0);      
    }

    // 加速度を取得する
    // X、Y、Zの値を得る
    M5.MPU6886.getAccelData(&x, &y, &z);

    x = x * 128.0;
    y = y * 16383.0;

    intX = mapAndLimit((int)x, 64, -64, 0, 127);
    intY = mapAndLimit((int)y, 8192, -8192, 0, 16383);

    //NoteOnの状態のときのみMIDI情報を送る
    if(pressed[0])
    {
      //IMUのデータをCC7, Pitchに送信
      notifyCC(7, intX, 1);
      notifyPitch(intY, 1);
    }

    //100msごとDisplay更新
    if(cnt == 50)
    {
      M5.Lcd.fillScreen(BLACK); 

      if(pressed[0])
      {
        circleSize = 7;
      }
      else
      {
        circleSize = 2;
      }

      dspX = mapAndLimit((int)x, 64, -64, 0, 80);
      dspY = mapAndLimit((int)y, -8192, 8192, 0, 160);
      M5.Lcd.drawCircle(dspX, dspY, circleSize, RED);
    }
    cnt = (cnt+1) % 100;
    
    // 少し休ませる
    delay(1);
  }
}
```
M4StickCの後継種、M5StickCPlusでもライブラリとヘッダ、ディスプレイ周りをCPlusに合わせれば動くと思います。

# 3年前からの進捗
**BLEライブラリがかなり進化**してました。前作ったときは、delayを10ms取らないとまともに動かなかったのですが、今回は、1msでも問題なく動作しました。

また、PCが早くなったのか、公式も入ったことでライブラリの最適化が進んだのか、**コンパイル時間も大幅に速く**なっていました。

ただ、BLEの限界でしょうね。相変わらず、ピッチベンドのデジタル臭さは抜けませんでした。

# まとめ
仕事柄、無線業界のウワサは色々入ってきていて、ESP32がIoT業界で覇権を取った背景に、チップの価格の安さもさることながら、**ソフトウェア改善の速さ**があると聞いていましたが、今回、身をもって体験することができました。

ようやく電波認証の通ったArudino nanoの無線シリーズの方でも色々遊んでみようと思ってますが、今回の実験で、WLANついているのになぜか安いESP32が乗っている方([Arduino nano 33 IoT](https://store.arduino.cc/products/arduino-nano-33-iot))で十分、と判断できそうです。

# 参考
前貼った、BLE MIDIの規格のリンクが切れていたので、貼り直しておきます。
全体公開になってますね。

[MIDI over Bluetooth Low Energy(BLE-MIDI)](https://www.midi.org/specifications/midi-transports-specifications/midi-over-bluetooth-low-energy-ble-midi)