概要
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)を解析しようとして途中まで作業していたのですが、そもそも単音のデータがほとんどないのと、結構変なデータがあったので諦めました。。。
サンプルの音は、へろへろですがあまりちゃんとした曲をいれると、いろいろあれなんで、、、




コメント