概要
2019年にM5StickCでMIDIコントローラーを作ってみようという記事を書き、色々、機器を作って遊んでいこうと思っていた矢先、Micro:bitがV2になり、BLEの利用が制限されると同時に、BLE MIDIの熱が冷めてしまいました。
が、今年になって、熱が再燃したので、再度、挑戦してみます。
デモ
M5StickC x KORG Gadget Phoenix
(YouTube)
前と同じです。
- ボタンを押すとNote On、離すとNote Off
(たまにチャタリングでかNote Offが送信できないのはご愛嬌) - 縦に傾けると音程変更(Pitch Bend)
- 横に動かすと音量変更(CC: 7)
開発環境構築
最近は、VSCodeでの開発も盛んですが、使いやすさから、前回と同じくArduino IDEで開発していきます。ArudinoIDEも、メジャーバージョンアップも行われ、こちら統合Platformとして順当に進化してますね。
PC(というかMac)も変わったので改めて環境構築しました。
公式を参考に、M5StickC開発環境を整えていきます。
3年前と大きく変わったことは、BLEライブラリを別途入れる必要が無くなったことです。Arudino本家からESP32搭載のArduino「Arduino nano 33 IoT」が出たためでしょう。かなり開発のハードルが低くなりました。
構成
- M5StickC
- KORG Gadget2 for iPad(iPhoneでも可)
#ソース
#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)で十分、と判断できそうです。
参考
前貼った、BLE MIDIの規格のリンクが切れていたので、貼り直しておきます。
全体公開になってますね。