M5StickCでBLE MIDIコントローラーを作ってみよう(1)

#概要
これまでは、M5StackでBLE MIDIコントローラを作ってきましたが、今回は、[M5StickC](https://www.switch-science.com/catalog/5517/)でBLE MIDIコントローラを作ってみます。

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

これまでの集大成のようなデモです。

- ボタンを押すとNote On、離すとNote Off
- 縦に傾けると音程変更(Pitch Bend)
- 横に動かすと音量変更(CC: 7)

#開発環境構築
[「M5StickCを動かしてみる(itiB_S144様)」](https://qiita.com/itiB_S144/items/7a232c1a31151fcb5e5b)を参考にしてみてください。
BLEライブラリの追加は、[前回の記事](https://qiita.com/wdice02136/items/65d77ec690b137e3a900)を参考にしてみてください。

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

#ソース
[note_cc_pitch_imu_ble_midi_stickC_sample.zip](http://dwtweb.html.xdomain.jp/make/m5stack/note_cc_pitch_imu_ble_midi_stickC_sample.zip)

```c:note_cc_pitch_imu_ble_midi_stickC_sample.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, 0, 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();

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

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

void loop() {
  if (isConnected)
  {
    //BtnAでNote On
    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);      
    }
    M5.update();    

    // 加速度を取得する
    // 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のデータをCC1, CC71に送信
      notifyCC(7, intX, 1);
      notifyPitch(intY, 1);
    }

    //100msごとDisplay更新
    if(cnt == 0)
    {
      M5.Lcd.fillScreen(BLACK); 
      M5.Lcd.drawCentreString("Connected", 40, 0, 1);

      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) % 10;       
  
    // 少し休ませる
    delay(10);
  }
}
```
#解説
今後、行いたいと思います。

#課題
どことなく音の変化がデジタル臭いんですよね。delayを5msにすると、音の変化がリアルタイムで動かなくなってしまってダメでした。

解消には、もう少しMIDIの規格、BLE MIDIの規格を読み込んで、MIDIのランニングステータスの利用、BLE MIDIのタイムスタンプの利用、一度のBLEパケットに複数のMIDI信号を乗せる、などの工夫で必要ですね。

---

#M5Stackとの差分

プログラムを組む上では、大きな差分は無かったですね。

**M5StickCの良いところ**

- 安い(WLAN/BT, IMU付いてArduino UNOより安いのは異常)
- IMU標準搭載 & M5Stack Grayのものよりライブラリが使いやすい
- 電源ボタンが素直
- USBケーブルを抜いてもリセットされない
- 電池の持ちも良さそう

**M5Stackの良いところ**

- ディスプレイが大きい
- IOが多い
- リセットボタン搭載