Arduino UNO R4用バックグラウンドミュージック再生ライブラリの紹介
BGM 再生といっても tone() のことです、悪しからず。
あまり実用的な使い道は思いつかないですが、折角作ったので PR しちゃいます。興味のある方は「Lチカしながら音楽再生・Arduino UNO R4のタイマと割り込みとクロック事情」に貼った動画を見てくれれば喜びます ![]()
BackgroundMusicR4 の特徴
仕組みは至って簡単。tone() とセットで使われる delay() の代わりに 前回紹介した CallbackTimerR4 で音符の長さだけ待機しながら、次々に音符を再生します。従来 チュートリアル に記載のスケッチ例では演奏出来なかった
や
といった音符も再現できるので、レパートリーの幅がグッと広がります!
インストール方法
CallbackTimerR4 のインストール
ベースとしてタイマ系ライブラリ CallbackTimerR4 が必要です。コチラ を参照の上、インストールしてください。
BackgroundMusicR4 のインストール
Github の BackgroundMusicR4 リポジトリ から画面右側にある Releases をたどり、移動先のページから最新版の
Source code (zip) をダウンロードします。
続いて Arduino IDE メニューの「スケッチ → ライブラリをインクルード → .ZIP形式のライブラリをインストール…」から、先にダウンロードした .zip ファイルを読み込ませれば、ライブラリフォルダにインストールされます。
使い方
スケッチのサンプル
ライブラリのインストール後、IDE メニューの「ファイル → スケッチ例 → カスタムライブラリのスケッチ例」から、曲を再生しながら Lチカする blink_and_music を開くことができます。添付の曲は イングランド民謡 の Greensleeves です。
Arduino.h に続けて BGMusic.h をインクルードすれば CBTimer.h も読み込みます。
#include "Arduino.h"
#include "BGMusic.h"
// pitch and duration pairs in the melody:
// duration: 4 = quarter note, 8 = eighth note, etc.
#include "pitches.h"
static int melody[] = {
// Special thanks for the memody data from: https://github.com/robsoncouto/arduino-songs
#include "Greensleeves.h"
};
#define BUZZER_PIN 9 // pin number connected to the buzzer
#define TEMPO 70 // change this to make the song slower or faster
#define REPEAT true // playback repeatedly
#define N_NOTES(s) (sizeof(s) / sizeof((s)[0]) / 2) // number of notes in musical score.
void setup() {
// initialize digital pin LED_BUILTIN as an output.
pinMode(BUZZER_PIN, OUTPUT);
pinMode(LED_BUILTIN, OUTPUT);
// initialize and start the BGM player
static BGMusic music;
music.begin(BUZZER_PIN, melody, N_NOTES(melody), TEMPO, REPEAT);
music.start();
}
void loop() {
// blink LED
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}公式の チュートリアル と少し違うのは、音符と音符長のデータを1つの int 型配列にフラットに格納しているところで、Robson Couto さん のアイディアです。音符を定義した Greensleeves.h の中身はザッとこんな感じです。
NOTE_G4,8,
NOTE_AS4,4, NOTE_C5,8, NOTE_D5,-8, NOTE_DS5,16, NOTE_D5,8,
NOTE_C5,4, NOTE_A4,8, NOTE_F4,-8, NOTE_G4,16, NOTE_A4,8,
NOTE_AS4,4, NOTE_G4,8, NOTE_G4,-8, NOTE_FS4,16, NOTE_G4,8,
NOTE_A4,4, NOTE_FS4,8, NOTE_D4,4, NOTE_G4,8,
...ヘッダファイルとしてはイレギュラーですが、Arduino IDE が .txt とか .dat とかの拡張子を許してくれず、仕方なく .h としています。当然、この楽譜データを差し替えれば色々な曲に対応できます。
メンバ関数の説明
コンストラクタ/デストラクタ
BGMusic 変数名;コンストラクタは特に何もしていません。一方、デストラクタは 依存するライブラリ CallbackTimerR4 の end() を呼び出して後始末をします。
例えば上記 スケッチのサンプル で、変数 music を static に確保するか、setup() の外でグローバル変数化しないと音が鳴らなくなるので要注意です。
また CallbackTimerR4 の都合により、残念ながら次のように複数の変数を宣言しても一方しか動作しません。
BGMusic 変数名1, 変数名2; // 変数名1と変数名2でFspTimerのインスタンスが共通となってしまう初期化
bool begin(int pin, const int notes[], int n_notes, int tempo, bool loop = false, bool start = false);-
int pin
ブザー(自励式、ピエゾタイプ)などtone()の出力ピンを指定します。 -
const int notes[]
音符の周波数と長さを並べたint型の配列を指定します。配列の中身は変更されることがない想定のため、const修飾子を付けて宣言してください。 -
int n_notes
音符の周波数と長さを1組と数えた時の総数を指定します。スケッチのサンプル にあるマクロN_NOTES()を使うと楽チンです。 -
int tempo
メトロノーム記号 で表される曲の速さを指定します。例えば
は「4分音符を1分間に 155 回刻む速さ」を表します。 -
bool loop
trueを指定すると曲の終了後、直ちに再生を繰り返します。falseが省略時のデフォルトです。 -
bool start
trueを指定すると初期化後、直ちに再生を開始します。省略時はfalseがデフォルトなので、別途start()で再生を開始する必要があります。
開始、停止、終了のメソッド
bool start();
bool stop();
void end();start() は曲の再生を開始、stop() は停止です。end() は確保していたリソースを解放します。
音符長計算関数の登録
void set_duration_function(int (*calc_duration_function)(int duration))音符長をミリ秒単位で返す関数を登録する関数ですが、少々ややこしいので 後述 することにします
。
曲のテンポと音符長の関係
このページ を参考にまとめると、テンポと音符長の関係は次の様になります。
- 曲のテンポは、1分間に刻むことができる4分音符の拍数で表す
- 例えば
= 60 は、1分間に4分音符を60回を数える速さを表す
このテンポと4分音符一拍分の長さ(時間)を式で表すと以下となります。

これを tone() の チュートリアル に当てはめると、4分音符の長さを数値 4 で表し、その時間を 1000 ÷ 4 = 250[msec] としているので、1分間のテンポ数に換算すると 60000 ÷ 250 = 240、つまり
= 240 ということになります。
そこで音の長さを表す数値を
、テンポを
=
とすると、音符の長さ(時間)を求める式は
となります。
BackgroundMusicR4 では互換性を考慮し、この式を元に音符長を決めています。
音符長計算関数について
一方、上記方式の問題点は、「・」のついた付点音符(元の音符の1.5倍の長さ)や、4分音符+16分音符を表す
などの音符長が整数で表せないことです。
float を使えばデータサイズこそ int と同じになりますが、浮動小数点演算が必要になります。UNO R4 のマイコンは FPU を持っているので実際にはそれほど問題にならないかも知れませんが、できれば整数演算で済ませたいところです。
ということで「音符長計算関数」の登場です。
音符長の計算式
まずは チュートリアル の音符長表現のおさらいです。
| N分音符 | 16分音符 | 8分音符 | 4分音符 | 2分音符 | 全音符 |
|---|---|---|---|---|---|
| 音符長 | 16 | 8 | 4 | 2 | 1 |
直感的な分かり易さを優先させたのだと思いますが、短い音符ほど音符長を表す数値が大きくなってます。
これに対し、4分音符一拍分の長さ 4 を基準に(ここまでは(チュートリアル と同じ)、各音符の長さの比と、それを表すシンボルを割り当ててみます。直感的な分かり易さはそのままに、数値ではなく N4 などのシンボルを使う様にするワケです。
| N分音符 | 16分音符 | 8分音符 | 4分音符 | 2分音符 | 全音符 |
|---|---|---|---|---|---|
| 音符長比 |
1 | 2 | 4 | 8 | 16 |
| シンボル | N16 | N8 | N4 | N2 | N1 |
ここで N 分音符の音符長比を表す数値を
、曲の速さを
とすれば、音の長さ(時間)を求める式は
となります。
この方式であれば、4分音符と16分音符を タイ で1つに繋げた
の音符長は整数演算が可能な N4 + N16、即ち 5 で表せることになります。
また4分音符一拍中に3音を鳴らす3連符(Triplet)
も怖くありません。音符長比をそれぞれ3倍したシンボルを定義すれば良いだけです。
| N分音符 | 16分音符 | 3連符 | 8分音符 | 4分音符 | 2分音符 | 全音符 |
|---|---|---|---|---|---|---|
| 音符長比 |
1 × 3 = 3 | 4 | 2 × 3 = 6 | 4 × 3 = 12 | 8 × 3 = 24 | 16 × 3 = 48 |
| シンボル | N16 | NT | N8 | N4 | N2 | N1 |
これで再現できる曲がグッと増えること間違いなしです!
音符長計算関数の登録
ということで、前述のメンバ関数 set_duration_function() は以下の様に使います。
#define BUZZER_PIN 9 // pin number connected to the buzzer
#define TEMPO 155 // change this to make the song slower or faster
#define REPEAT true // playback repeatedly
#define N_NOTES(s) (sizeof(s) / sizeof((s)[0]) / 2) // number of notes in musical score.
#define QUARTER_NOTE ((60000 / N4) / TEMPO) // ratio of the duration for one quarter note per beat
// Calculate note length from note length symbol
int calc_duration(int duration) {
return QUARTER_NOTE * duration;
}
void setup() {
// initialize digital pin LED_BUILTIN as an output.
pinMode(BUZZER_PIN, OUTPUT);
pinMode(LED_BUILTIN, OUTPUT);
// initialize and start the BGM player
static BGMusic music;
music.set_duration_function(calc_duration);
music.begin(BUZZER_PIN, score, N_NOTES(score), TEMPO, REPEAT);
music.start();
}QUARTER_NOTE は「4分音符一拍分当たりの長さの比率」です。calc_duration() に渡される int duration は N4 とか N8 とか N4 + N16 などの値となります。
上記のコードは background_music からの抜粋です。大好きな T-SQUARE の TRUTH を収録してあるので、よかったら聞いてみてください。
あとがき
バックグラウンドで音を鳴らすだけで、tone() で1個、CallbackTimerR4 ライブラリで1個、合計2個のタイマを使っちゃてるんですよね。
今回はかつて Cortex-M3 の LPC1343 ボードで作った機能(ご参考:play.c)の移植を目指してきましたが、移植に際し当初2つの案がありました。
前者であればタイマは1個で済んだハズですが、勉強も兼ねて 後者を選びました。
その上での感想ですが、まず第1に Cortex-M3 のレジスタに比べ Cortex-M4 のそれは何倍も難しくなっていること、そして第2に Arduino のプログラミングはライブラリのお陰で楽な反面、うまく折り合いを付けなきゃならない(コメントの少ないコードをリーディングするという)苦労があり、なかなかに手強いぞ!と感じてます。
ともあれ、豊富な情報と関連モジュール(H/W、S/W)との組み合わせが Arduino を楽しむコツと分かってきた今日この頃です ![]()