Arduinoで音楽演奏する

Arduino

音楽演奏

今回は、Arduinoで音楽演奏を行ってみます。まぁ指定した周波数の音声信号を出力するtone()関数があればなんとかなるでしょう…

音階を作る

Arduinoの標準ライブラリにあるtone()関数は、出力する音声信号の周波数を指定するようになっています。残念ながらドレミを指定する仕様の関数がないので、まずはドレミ…と周波数の関係を知らなければなりません。

音階と周波数(平均律)

音階と周波数の関係(音律)には、実は『純正律』『ピタゴラス音律』など何種類かあり、それぞれ微妙に周波数が異なります。どの音律を使うかによって和音の響きなどにも違いがあるのですが、この方面に話を広げるとややこしくなってしまうので別の機会に。

今回は、もっとも数学的に単純なルールで周波数が決まっている『平均律』を採用します。

平均律のルールは

『1半音上がるたびに周波数は \(2^{1/12}\) 倍になる』

です。つまり基準となる音の周波数を \(f_0\) として、それより\(n\)半音だけ高い音の周波数\(f_n\)は、

\[\large f_n=f_0\times 2^{(n/12)}\]

という等比数列になります。元の音から12半音上がると周波数がちょうど2倍となります。

ピアノの鍵盤で見ると、英語音名『C』音(固定ド式の『ド』の音)から順に半音上がっていくと『C→C#→D→D#→E→F→F#→G→G#→A→A#→B→C』となって12半音で『CDE…』が一周して再び『C』に至ります。このように周波数がちょうど2倍になると人間の耳には『同じ階名の音』に聞こえるのです。このような関係の音を『1オクターブ』といいます。

※なお、このブログでは原則として音名を『英語音名+オクターブを表す数字』で表記します。ただしsharpは『#』と記号で表記します。

上の式に基づき、A4=440Hzとして、C4~C6の2オクターブ分の周波数を表にすると以下のようになります。なお、tone()関数の引数はint型なので、周波数は小数点以下を四捨五入した整数値で表しています。

音名周波数(Hz)備考
C4262いわゆる『中央ハ』。88鍵ピアノの中央付近、鍵穴のすぐ左のハ音
C#4277
D4294
D#4311
E4330
F4349
F#4370
G4392
G#4415
A4440ラジオ時報の低い方の音
オーケストラのチューニングにもよく使用される
A#4466
B4494
C5523
C#5554
D5587
D#5622
E5659
F5698
F#5740
G5784
G#5831
A5880ラジオ時報の高い方の音
A#5932
B5988
C61047

この周波数を使えばメロディを奏でることも出来るはずです。

C4~C6という音域は、ピアノの鍵盤でいうと中央から右寄りのあたり、大雑把に言うと『右手でメロディを奏でる』のに使うことが多い音域です。一般的な女性歌手の声域でもあります。

制作&実験

使用部品、回路など

使用部品や回路などは、前回『Arduinoで音を出す』と同じです。

例1:まずは音階

まずは半音階を順に鳴らしてみます。

スケッチ

C++
#define SPPIN 2

int freq[]={262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, 1047};

void setup() {
  pinMode(SPPIN, OUTPUT);
}

void loop() {
  int count=sizeof(freq)/sizeof(int);
  for(int i=0; i<count; i++){
    tone(SPPIN, freq[i]);
    delay(500);
  }
  noTone(SPPIN);
  delay(5000);
}

プログラムの解説

定数の設定
C++
#define SPPIN 2

圧電スピーカーを接続した出力ピンの番号を定数SPPINとして設定しています。『Arduinoで音を出す』と同じ設定です。

周波数の配列を用意する
C++
int freq[]={262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, 1047};

先に計算したC4~C6の2オクターブ分の周波数をint型一次元配列 freq[] に格納しています。freq[0]がC4、freq[1]がC#4、freq[2]がD4、…と低い方から順になっています。

初期設定
C++
void setup() {
  pinMode(SPPIN, OUTPUT);
}

setup()関数の中では、pinMode()関数を使ってスピーカーを接続した端子を出力に設定しています。

音の数を数える
C++
  int count=sizeof(freq)/sizeof(int);

loop()関数の中では、最初に『鳴らすべき音の数』を数えています。今回は周波数を登録した音すべてを順に出力したいので 配列freq[] の要素数を求めています。この程度の数なら要素数を数えて数値をそのまま書いてもいいのですが、このような式にしておけば後でfreq[]の要素数を増減したいときに書き換えるのがfreq[]の初期化部分だけで済みます。

sizeof()は引数となった変数が使用しているバイト数を求める演算子です。

要素数 = 配列freq[]が使用しているバイト数 ÷ 要素1個分(int)のバイト数

という原理です。

すべての音の数だけループする

C++
  for(int i=0; i<count; i++){
    tone(SPPIN, freq[i]);
    delay(500);
  }

このfor文では、先ほど求めた配列の要素数分ループをしています。

音を出す
C++
  for(int i=0; i<count; i++){
    tone(SPPIN, freq[i]);
    delay(500);
  }

tone()関数は、周波数freq[i]の音(C4から数えてi番目の音)を出力します。tone()関数は実行後直ちに次の命令に処理が移るので、delay()関数で500ms=0.5秒間その音を保つようにしています。待ち時間を入れないとすぐに次の音に移ってしまい、最後の音以外は聞き取れません。

一回りしたら音を止め、しばらく待つ
C++
  noTone(SPPIN);
  delay(5000);

tone()関数で第3引数を省略した場合はずっと音が出力されたままになってしまうので、forループ終了後にいったん noTone()関数で音声出力を停止しています。

delay(5000) で5000ms=5秒間、無音の状態を保ちます。

loop()関数は繰り返し実行されるので、5秒間の無音の後ふたたび最初から半音階を出力します。

実行結果

無事、2オクターブ分の音階を鳴らせるようになりました。

例2:簡単な曲を演奏する

ではいよいよ、簡単な曲を演奏してみましょう。今回は題材として『きらきら星』を演奏するプログラムを作ってみます。

きらきら星について

『きらきら星』は、いうまでもなく非常に有名な童謡です。もとはフランスの歌曲 “Ah! Vous dirais-je, Maman”(あのね、母さん)ですが、英語歌詞(フランス版とは無関係な内容の替え歌) “Twinkle, twinkle, little star” が世界中に広まっています。日本でも英語歌詞に近い内容の日本語歌詞を付けて親しまれています。

この『きらきら星』には、

  • 非常によく知られている(日本では知らない人はほとんどいないのでは?)
  • メロディの著作権保護期間が終了している(ブログで公開するには重要)
  • 単音の旋律で、伴奏パートがなくても曲が成立する(データを作るには重要)
  • ワンコーラスが短い(データを作るには重要)

という音楽演奏のテストデータにする好条件が揃っているので、今回の題材に選びました。

『きらきら星』のメロディ、原曲のフランス語歌詞、英語歌詞は著作権保護期間が過ぎているため、自由に使用することが出来ます。ただし何種類かある日本語歌詞のなかには著作権保護期間が終了していないものがあるため、日本語歌詞による歌唱をネットに上げる場合は注意が必要です。

一応、楽譜も載せておきましょうか。

スケッチ

C++
#define SPPIN 2

int freq[]={262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, 1047};

struct NOTE {
  int tone;
  float length;
};

NOTE sheet[]={
    {0, 1}, {0, 1}, {7, 1}, {7, 1}, {9, 1}, {9, 1}, {7, 2}, {5, 1}, {5, 1}, {4, 1}, {4, 1}, {2, 1}, {2, 1}, {0, 2},
    {7, 1}, {7, 1}, {5, 1}, {5, 1}, {4, 1}, {4, 1}, {2, 2}, {7, 1}, {7, 1}, {5, 1}, {5, 1}, {4, 1}, {4, 1}, {2, 2}, 
    {0, 1}, {0, 1}, {7, 1}, {7, 1}, {9, 1}, {9, 1}, {7, 2}, {5, 1}, {5, 1}, {4, 1}, {4, 1}, {2, 1}, {2, 1}, {0, 2}
};

void setup() {
  pinMode(SPPIN, OUTPUT);
}

void loop() {
  int count = sizeof(sheet)/sizeof(NOTE);
  for(int i=0; i<count; i++){
    int length = 500*sheet[i].length;
    tone(SPPIN, freq[sheet[i].tone], length*0.95);
    delay(length);
  }
  delay(5000);
}

プログラム解説

例1と違う部分について解説します。

音符を表す構造体の定義
C++
struct NOTE {
  int tone;
  float length;
};

音符1つ1つの情報を格納する構造体NOTE(note は『音符』を表す英単語です)を定義しています。メンバは、

名前意味
toneint音の高さ(配列freqの要素番号。C4=0、D4=2、E4=4…)
lengthfloat音の長さ(1拍の何倍の長さかを表す。四分音符が1拍なら八分音符=0.5、二分音符=2)
楽譜データ
C++
NOTE sheet[]={
    {0, 1}, {0, 1}, {7, 1}, {7, 1}, {9, 1}, {9, 1}, {7, 2}, {5, 1}, {5, 1}, {4, 1}, {4, 1}, {2, 1}, {2, 1}, {0, 2},
    {7, 1}, {7, 1}, {5, 1}, {5, 1}, {4, 1}, {4, 1}, {2, 2}, {7, 1}, {7, 1}, {5, 1}, {5, 1}, {4, 1}, {4, 1}, {2, 2}, 
    {0, 1}, {0, 1}, {7, 1}, {7, 1}, {9, 1}, {9, 1}, {7, 2}, {5, 1}, {5, 1}, {4, 1}, {4, 1}, {2, 1}, {2, 1}, {0, 2}
};

楽譜データは、先ほど定義したNOTEを要素とする配列として表現します。変数名sheet[]は『楽譜』を表す英単語”music sheet”からきています。

NOTE.tone が判りやすいように、楽譜にfreqの要素番号を記入すると以下のようになります。

NOTE.lengthは四分音符が1、二分音符は2です。配列sheet[]は音符の順にデータが並んでいます。

音符の個数を求め、その数だけ繰り返す
C++
  int count = sizeof(sheet)/sizeof(NOTE);
  for(int i=0; i<count; i++){
    int length = 500*sheet[i].length;
    tone(SPPIN, freq[sheet[i].tone], length*0.95);
    delay(length);
  }

まず、音符(配列sheet[]の要素)の個数を求めています。

音符の個数 = 配列sheetのバイト数 ÷ 構造体NOTEのバイト数

です。結果は変数countに格納しています。

次に、for文で音符の数だけ繰り返し処理を行います。

音符1つ分の音を出す
C++
  int count = sizeof(sheet)/sizeof(NOTE);
  for(int i=0; i<count; i++){
    int length = 500*sheet[i].length;
    tone(SPPIN, freq[sheet[i].tone], length*0.95);
    delay(length);
  }

ここでは、i番目の音符(sheet[i]で表される音譜)の音を発生しています。

音の高さ(freq[]の要素番号)は sheet[i].tone
音の長さ(何拍分か)は sheet[i].length

で表されます。

まず、音符の音の長さをms単位で求めて変数lengthに格納します。1分間に120拍と定義されていますので、1拍あたりの時間は 60000[ms]÷120=500[ms] です。

次に、tone()関数sheet[i].tone の高さ(周波数は freq[sheet[i].tone] Hz)の音を発します。このとき、音の長さを指定する第3引数はlengthではなくlength*0.95となっていますが、これはlengthいっぱいに音を伸ばしてしまうと同じ高さの音が2つ続いたときにつながった1つの音のように聞こえてしまうためです。次のdelay()関数と併せて、length*0.05(音符の長さの5%)だけ無音の時間を作って音を区切っています。

tone()関数は実行後すぐに次の処理に移るので、delay(length)で音符の長さ分処理を停止します。

実行結果

まぁ電子オルゴールICくらいの音は出ていますね。

まとめ

・音楽演奏をするには、音階と周波数のデータをあらかじめ作っておくと便利

コメント

タイトルとURLをコピーしました