概要
M5StickC Plusに搭載されているブザーを、マルチタスクを利用してバックグラインド再生を試してみました。実際の音楽データはBLE-MIDIを使って、レコーディングしました。
BLE-MIDIでレコーディング
再生する元データを作るって結構面倒ですよね?
なので、今回はBLE-MIDIを使ってパソコンから音データをM5Stick Plusに流し込んで、そのデータを使って再生することにしました。
スケッチ
#include <M5StickCPlus.h> #include <BLEDevice.h> #include <BLEUtils.h> #include <BLEServer.h> #define MIDI_SERVICE_UUID "03b80e5a-ede8-4b33-a751-6ce34ec4c700" #define MIDI_CHARACTERISTIC_UUID "7772e5db-3868-4112-a1a9-f2669d106bf3" #define DEVIVE_NAME "M5StickC" BLEServer *pServer; BLEAdvertising *pAdvertising; BLECharacteristic *pCharacteristic; int pos = 0; char midi[5]; uint16_t frequency_list[] = { 9, // 0 9, // 1 9, // 2 10, // 3 10, // 4 11, // 5 12, // 6 12, // 7 13, // 8 14, // 9 15, // 10 15, // 11 16, // 12 17, // 13 18, // 14 19, // 15 21, // 16 22, // 17 23, // 18 24, // 19 26, // 20 28, // 21 29, // 22 31, // 23 33, // 24 35, // 25 37, // 26 39, // 27 41, // 28 44, // 29 46, // 30 49, // 31 52, // 32 55, // 33 58, // 34 62, // 35 65, // 36 69, // 37 73, // 38 78, // 39 82, // 40 87, // 41 92, // 42 98, // 43 104, // 44 110, // 45 117, // 46 123, // 47 131, // 48 139, // 49 147, // 50 156, // 51 165, // 52 175, // 53 185, // 54 196, // 55 208, // 56 220, // 57 233, // 58 247, // 59 262, // 60 277, // 61 294, // 62 311, // 63 330, // 64 349, // 65 370, // 66 392, // 67 415, // 68 440, // 69 466, // 70 494, // 71 523, // 72 554, // 73 587, // 74 622, // 75 659, // 76 698, // 77 740, // 78 784, // 79 831, // 80 880, // 81 932, // 82 988, // 83 1047, // 84 1109, // 85 1175, // 86 1245, // 87 1319, // 88 1397, // 89 1480, // 90 1568, // 91 1661, // 92 1760, // 93 1865, // 94 1976, // 95 2093, // 96 2217, // 97 2349, // 98 2489, // 99 2637, // 100 2794, // 101 2960, // 102 3136, // 103 3322, // 104 3520, // 105 3729, // 106 3951, // 107 4186, // 108 4435, // 109 4699, // 110 4978, // 111 5274, // 112 5588, // 113 5920, // 114 6272, // 115 6645, // 116 7040, // 117 7459, // 118 7902, // 119 8372, // 120 8870, // 121 9397, // 122 9956, // 123 10548, // 124 11175, // 125 11840, // 126 12544, // 127 }; int frequency = 0; int duration = 0; unsigned long startTime = 0; class MyServerCallbacks: public BLEServerCallbacks { void onConnect(BLEServer* pServer) { M5.Lcd.setTextSize(1); M5.Lcd.setCursor(10, 0); M5.Lcd.printf("BLE MIDI Connected. "); }; void onDisconnect(BLEServer* pServer) { M5.Lcd.setTextSize(1); M5.Lcd.setCursor(10, 0); M5.Lcd.printf("BLE MIDI Disconnect."); } }; class MyCallbacks: public BLECharacteristicCallbacks { void onWrite(BLECharacteristic *pCharacteristic) { std::string rxValue = pCharacteristic->getValue(); pos = 0; if (rxValue.length() > 0) { for (int i = 0; i < rxValue.length(); i++) { midi[pos] = rxValue[i]; pos++; if (pos == 5) { M5.Lcd.setTextSize(3); M5.Lcd.setCursor(10, 30); M5.Lcd.printf("%02x %02x %02x", midi[2], midi[3], midi[4]); pos = 0; if (midi[2] == 0x80) { // Note Off ledcWriteTone(1, 0); int duration = millis() - startTime; Serial.printf("{%5d, %5d},\n", frequency, duration); frequency = 0; startTime = millis(); } else if (midi[2] == 0x90) { // Note On if (startTime) { int duration = millis() - startTime; Serial.printf("{%5d, %5d},\n", frequency, duration); } frequency = frequency_list[midi[3]]; ledcWriteTone(1, frequency); startTime = millis(); } } } } } }; void setup() { M5.begin(); M5.Lcd.setRotation(3); M5.Lcd.fillScreen(BLACK); M5.Lcd.setTextColor(WHITE, BLACK); M5.Lcd.setTextSize(1); M5.Lcd.setCursor(10, 0); M5.Lcd.printf("BLE MIDI Disconnect."); BLEDevice::init(DEVIVE_NAME); pServer = BLEDevice::createServer(); pServer->setCallbacks(new MyServerCallbacks()); BLEService *pService = pServer->createService(BLEUUID(MIDI_SERVICE_UUID)); pCharacteristic = pService->createCharacteristic( BLEUUID(MIDI_CHARACTERISTIC_UUID), BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_WRITE_NR ); pCharacteristic->setCallbacks(new MyCallbacks()); pService->start(); BLEAdvertisementData oAdvertisementData = BLEAdvertisementData(); oAdvertisementData.setFlags(0x04); oAdvertisementData.setCompleteServices(BLEUUID(MIDI_SERVICE_UUID)); oAdvertisementData.setName(DEVIVE_NAME); pAdvertising = pServer->getAdvertising(); pAdvertising->setAdvertisementData(oAdvertisementData); pAdvertising->start(); pinMode(2, OUTPUT); ledcSetup(1, 12000, 8); ledcAttachPin(2, 1); } void loop() { delay(1); }
長いのは音に対する周波数一覧です。他の処理はほぼ昔のブログのままになります。
M5StickC Plusにスケッチを入れて動かして、パソコンからBLEの追加をすると「M5StickC」がいるので追加します。MIDIberryを使って、出力先を「M5StickC」を選択します。左上にあるボタンを押すと鍵盤がでますので、チャンネルを1してから鍵盤を叩くと音がでます。
{ 0, 3299}, { 349, 300}, { 0, 240}, { 392, 240}, { 0, 240}, { 440, 780}, { 0, 240}, { 392, 180}, { 0, 300}, { 349, 120}, { 0, 720}, { 349, 239}, { 0, 240}, { 392, 180}, { 0, 240}, { 440, 179}, { 0, 240}, { 392, 179}, { 0, 300}, { 349, 179}, { 0, 300}, { 392, 779},
シリアル出力に、上記のようなログがでますのでこちらを利用します。1つ目の数値が周波数で、2つ目が継続時間です。周波数0が無音になります。
最初の0が長いところは無音なのでカットして、2行目から下がレコーディングされた音楽となります。
バックグラインド再生
再生したいデータをキューに送ることで再生を実現しています。停止はキューの中身を空にしています。他にも方法はあると思いますが、キューを使うのが単純なわりに排他処理などが安全になります。
スケッチ
#include <M5StickCPlus.h> struct BeepData { uint16_t frequency; uint16_t duration; }; BeepData sound1[] = { { 349, 300}, { 0, 240}, { 392, 240}, { 0, 240}, { 440, 780}, { 0, 240}, { 392, 180}, { 0, 300}, { 349, 120}, { 0, 720}, { 349, 239}, { 0, 240}, { 392, 180}, { 0, 240}, { 440, 179}, { 0, 240}, { 392, 179}, { 0, 300}, { 349, 179}, { 0, 300}, { 392, 779}, }; #define QUEUE_BEEP_LENGTH 1000 QueueHandle_t xQueueBeep; // キュー TaskHandle_t taskBeepHandle; // タスク管理 // タスク void taskBeep(void *pvParameters) { while (1) { BeepData data; xQueueReceive(xQueueBeep, &data, portMAX_DELAY); ledcWriteTone(1, data.frequency); // Play delay(data.duration); ledcWriteTone(1, 0); // Mute } } void setup() { M5.begin(); M5.Lcd.setRotation(3); M5.Lcd.fillScreen(BLACK); M5.Lcd.setTextSize(3); M5.Lcd.setCursor(0, 0); M5.Lcd.println("Beep Test"); M5.Lcd.println(); M5.Lcd.println("BtnA : PLAY"); M5.Lcd.println("BtnB : STOP"); pinMode(2, OUTPUT); ledcSetup(1, 12000, 8); ledcAttachPin(2, 1); // キュー作成 xQueueBeep = xQueueCreate(QUEUE_BEEP_LENGTH, sizeof(BeepData)); // Core1の優先度3でタスク起動 xTaskCreateUniversal( taskBeep, // タスク関数 "taskBeep", // タスク名(あまり意味はない) 8192, // スタックサイズ NULL, // 引数 3, // 優先度(大きい方が高い) &taskBeepHandle, // タスクハンドル APP_CPU_NUM // 実行するCPU(PRO_CPU_NUM or APP_CPU_NUM) ); } void loop() { M5.update(); if (M5.BtnA.wasPressed()) { // Play for (int i = 0; i < sizeof(sound1) / sizeof(BeepData); i++) { xQueueSend(xQueueBeep, &sound1[i], 0); } } if (M5.BtnB.wasPressed()) { // Stop BeepData data; while (xQueueReceive(xQueueBeep, &data, 0)) { // 空にする } ledcWriteTone(1, 0); // Mute } M5.Lcd.setCursor(0, 108); M5.Lcd.println(millis()); delay(1); }
1000個まで入るキューを作成して、再生したい音のリストを登録すると再生します。マルチタスクで動いていますので、再生中に他の処理もできます。
ちなみに再生時はキューに追加だけしているので、どんどん後ろに追加されています。本当は今のを消してから追加したほうが好ましいと思います。
おまけ UIFlowでのブザー
こんな感じでPWMをGPIO2に鳴らしたい音を周波数にセットして50%で出力することで音ができます。ただし、PWM出力中に再起動をした場合になりっぱなしになります!
初期化処理でPWMを止める処理が入っていないんですよね、、、なのでアプリ起動直後に音を止める処理を入れないと意図せずなりっぱなしになりますので注意してください。
こんな感じで、起動直後にPWMの初期化をして、その直後に停止をすると安全です。音を鳴らす場合には関数化して呼び出すと便利だと思います。処理の途中で音を鳴らすと処理が止まってしまうので、ボタン系のイベントの中か、タイマー処理などを使って別スレッドで動かすようにしたほうがいいと思います。
この辺はUIFlowの標準ブロックで今後サポートしてくれるとは思います!
まとめ
音楽データ作成は本当はMIDIファイル(SMF)を解析しようとして途中まで作業していたのですが、そもそも単音のデータがほとんどないのと、結構変なデータがあったので諦めました。。。
サンプルの音は、へろへろですがあまりちゃんとした曲をいれると、いろいろあれなんで、、、
コメント