LoginSignup
5
4

More than 3 years have passed since last update.

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

Posted at

概要

これまでは、M5StackでBLE MIDIコントローラを作ってきましたが、今回は、M5StickCでBLE MIDIコントローラを作ってみます。

デモ

M5StickC x KORG Gadget Phoenix
(YouTube)

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

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

開発環境構築

「M5StickCを動かしてみる(itiB_S144様)」を参考にしてみてください。
BLEライブラリの追加は、前回の記事を参考にしてみてください。

構成

  • M5StickC
  • KORG Gadget2 for iPad(iPhoneでも可)

ソース

note_cc_pitch_imu_ble_midi_stickC_sample.zip

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が多い
  • リセットボタン搭載
5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4