ESP32を使って、昔ファミコンにあったビープ音を使った音楽を再生します。
楽譜はMML(Music Macro Language)で書かれている前提です。
スピーカーはブザーを使います。M5StickCPlusに標準でついているブザーか、外付けのブザーを使います。
https://www.switch-science.com/products/6470
https://www.switch-science.com/products/9350
https://www.switch-science.com/products/7629
いつもの通り、ESP32で動作するJavascriptで実行します。
以下を使わせていただきました。ありがとうございました。
Javascriptコード
MMLの楽譜を再生するJavascriptソースコードです。
内部的には定期的なタイマを使っていますので、並行で別の処理をしていても大丈夫なようにしています。
import * as ledc from "Ledc";
import * as mml from "Mml";
const BUZZER_PIN = 2;
const LEDC_RESOLUTION = 8;
const LEDC_CHANNEL = 0;
var text = "[MMLの楽譜]";
function setup(){
ledc.setup(LEDC_CHANNEL, 100, LEDC_RESOLUTION);
ledc.attachPin(BUZZER_PIN, 0);
mml.begin(LEDC_CHANNEL, 10, LEDC_RESOLUTION);
mml.play(text, false);
}
解説
まずは、BUZZERが接続されたPINのLEDCの設定をします。
ledc.setup(LEDC_CHANNEL, 100, LEDC_RESOLUTION);
ledc.attachPin(BEEP_PIN, 0);
M5StickCPlusの場合は、GPIO02にブザーがついています。外付けのブザーの場合は、接続したGPIOのpin番号を指定します。
次に、MMLを再生する前の準備をします。LEDCに指定したパラメータを教えてあげます。
mml.begin(LEDC_CHANNEL, 10, LEDC_RESOLUTION);
あとは、mml.playでMMLの楽譜を指定すれば、再生されます。もし、連続で延々と再生したければ、第二引数をtrueにします。
mml.play(text, false);
楽譜の準備
本当は、ドラクエのオープニングをMMLにしたのですが、著作権的にNGなので、フリーの「さくらさくら」を楽譜にします。
階名でいくと以下になります。
ララシ ララシ ラシドシラシラファ ミドミファミミドシ ラシドシラシラファ ミドミファミミドシ ララシ ララシ ミファシラファミ
オクターブやテンポがありませんが、ちゃんとMMLの楽譜に反映が必要です。それが面倒なのですが、結果として以下のようになります。
t120l4o5 a a b r a a b r a b > c < b a b8 a8 f r e c e f e e8 c8 < b r > a b > c < b a b8 a8 f r e c e f e e8 c8 < b r > a a b r a a b r e f b8 a8 f e2
さきほどのJavascriptでは以下のようにすればよいです。
var text = "t120l4o5 a a b r a a b r a b > c < b a b8 a8 f r e c e f e e8 c8 < b r > a b > c < b a b8 a8 f r e c e f e e8 c8 < b r > a a b r a a b r e f b8 a8 f e2";
先頭には、テンポやデフォルトの音の長さ最初のオクターブを指定しています。
aがラで、cがドで、ソがgですね。
| ド | レ | ミ | ファ | ソ | ラ | シ |
|---|---|---|---|---|---|---|
| c | d | e | f | g | a | b |
詳細は以下を参照してください。
内部実装
内部的には、以下を使わせていただき、C言語で実装しています。
FreeRTOSのタイマーを使っています。こんな感じの関数を用意します。
static void mmlTimerCallback(TimerHandle_t xTimer) {
if( timerRunning ){
if (mml.isBGMPlay()) {
if (mml.available())
mml.playTick();
}else{
xTimerStop(mmlTimer, 0);
timerRunning = false;
if( g_repeat ){
mml.playBGM();
startIntervalTimer(g_period);
}
// Serial.println("startIntervalTimer end");
}
}
}
static void stopIntervalTimer(void){
if( timerRunning ){
g_repeat = false;
mml.stop();
xTimerStop(mmlTimer, 0);
timerRunning = false;
if( gp_mml_text != NULL ){
free(gp_mml_text);
gp_mml_text = NULL;
}
}
}
static void startIntervalTimer(uint32_t period){
if( timerRunning ){
xTimerStop(mmlTimer, 0);
timerRunning = false;
}
// Serial.println("startIntervalTimer start");
xTimerChangePeriod(mmlTimer, pdMS_TO_TICKS(period), 0);
xTimerStart(mmlTimer, 0);
timerRunning = true;
}
long initialize_mml(void)
{
mml.init(nullptr, func_tone, func_notone, nullptr /* debug */);
mmlTimer = xTimerCreate(
"mmlTimer",
pdMS_TO_TICKS(g_period),
pdTRUE,
nullptr,
mmlTimerCallback
);
return 0;
}
あとはこうやって再生を開始しています。
mml.setText(gp_mml_text);
mml.playBGM();
startIntervalTimer(g_period);
(参考) ESP32で動作するJavascript実行環境
ESP32で動作するJavascript実行環境を公開しています。
Javascriptのコードを書き換えるのに、いちいち再コンパイル不要なので、らくちんです。
「電子書籍:M5StackとJavascriptではじめるIoTデバイス制御」
サポートサイト
ぜひ、手に取ってみてください。
以上