第3回 デジタル制御2:音階を使って、音楽をつくる
音階と周波数
音は空気の振動です。この振動は空気を伝達し、鼓膜まで到達します。空気の振動の周波数が、大きければ大きいほど、高い音に聞こえます。音楽は、ドレミファ・・の音階がベースになっていますが、これは人間が任意に選んだ音の周波数です。ドからドまで、周波数が10 倍ちがいます。この周波数の範囲を12 に分割することで、音階ができます。
Figure:周波数と音の高低
AVRマイコンからブザーへの出力
ブザーはPORD の4 番目のピンに接続されています。PD4 を「出力」に設定してから、この端子に[1] と [0] の信号を交互に送ることで、ブザーの膜が振動し、音が鳴ります。
Figure:AVR マイコンとブザーとの接続
AVR マイコンとブザーとの接続
#include <avr/io.h> int main() { long i; DDRD= 0x10; //PORTD の4 番ピンを出力に使う for(;;){ for(i=0;i<200;i++){ PORTD= 0x10; } for(i=0;i<200;i++){ PORTD= 0x00; } } return 0; }
音階と周波数
音階と周波数のテーブルに従って、音階をつくりましょう。周波数を、波ひとつあたりにどれだけの時間がかかるかの周期にかえて、
1周期の時間をつくります。for ループを10 回繰り返すのにどれぐらいの時間がかかるのかを調べます。
for ループの繰り返しの回数を調節することで、音階をつくります。
例えば、ドの音を作ろうと思えば、ドの周波数は261H zなので、1秒間に空気が261 回振動すれば、ドの音に聞こえます。つまり、マイコンから1秒間に261 回、振動する波を出せばよいことになります。これは時間で言うと、一つの波の周期が3.8 ミリ秒になります。マイコンから「1」を出力する時間を1.9 ミリ秒、マイコンから「0」を出力する時間を1.9 ミリ秒にし、[1] と[0] の出力を繰り返せば、1 秒間に261回振動する波がつくれます。この[1] と[0] でつくられた波の信号が、ブザーに送られ金属板が振動します。この振動が空気を振動させ、最終的には、ヒトの耳の鼓膜が振動し、音が聞こえます。
- 課題1 ドの音をつくり、3 秒間鳴らすプログラムを書こう
- 課題2 12 音階をプログラミングしよう
- 課題3 自分の好きな音楽を鳴らそう
音階コード | 周波数 | 周期(msec) |
---|---|---|
ドC | 261 | 3.8 |
レD | 293 | 3.4 |
ミE | 329 | 3.0 |
ファF | 349 | 2.9 |
ソG | 391 | 2.6 |
ラA | 440 | 2.3 |
シB | 494 | 2.0 |
ドC | 523 | 1.9 |
音楽は、ドレミファ・・の音階をもとに、それぞれの音をどれほどの長さで鳴らすかでつくられます。1章節という区切りに、全音符、2 分音符(図15.3)というように、1 章節の中の音の長さを分割し、音を鳴らす長さを表します。マイコンで音楽をつくる場合は、単純にそれぞれの音を鳴らす長さを指定します。例えば、ドの音を0.5 秒間鳴らし、次にレの音を1 秒間鳴らすというようにして、音楽をつくっていきます。
Figure:章節と音符
Figure:ドの音を鳴らす時間を調節する
音階と関数
音階をつくったあとに、音楽を鳴らしたいわけですが、ドレミドレ・・という順番で「ドのfor loop」の次に「レのfor loop」というように並べていっては、大変です。そこでドの関数、レの関数というようにそれぞれの音の関数をつくり、ドレミファソラシドの音階をつくります。次に楽譜に合わせて、それぞれの関数を呼び出す方法がよい。具体的な関数のつくり方ですが、ドの音をC という名前の関数で表し、レの音をD という名前の関数で表していきます。そして、main 関数の中でC(200); D(100); E(50); というように音階の関数を順番に呼び出し、音楽をつくることを考えます。() の中の引数は、ある波長を何回、繰り返すのか、つまり各音符を鳴らす時間の長さに相当します。プログラムの構造を15.5 に表します。
Figure:音階関数の作り方
ドという関数をつくる
#include <avr/io.h> void C(int k); //ドの関数の宣言を行なう int main() //main 関数の中で音階を並べる { DDRD= 0x10; //PORTD の4 番ピンを出力に使う C(200); //200 という値を関数C に出力し、ドの音をある時間間隔で出力する return 0; } //ドの関数をつくる void C(int k){ //kを読み込む int m,i; //関数内で使う変数を宣言する for(m=0;m<k;m++){ //k (この場合、k=200) 回、ドの波長を繰り返し、ある時間間隔で音を鳴らす for(i=0;i<200;i++){ //ドの一波長をつくる PORTD= 0x10; } for(i=0;i<200;i++){ PORTD= 0x00; } } }
音には、高い音、低い音があり、それぞれで周期が違います。したがって同じ時間間隔で、低い音、高い音を鳴らそうとすると、それぞれの波長に合わせて、何回、特定の波長を繰り返すかを計算しないといけません。そこで例えば、上のドをつくる関数内に「出力したい音の時間間隔」を「ドの1波長を何回繰り返すか」に変換する式をつくり、計算された値で、for loop をまわす回数を設定し、ドの1波長を何回繰り返すかを設定します。こうして音の波長に合わせて、音の鳴る時間を設定することができます。
Figure:音を鳴らす長さのそろえ方
参考に音階の関数を使った「崖の上のポニョ」(O&S 作成) のプログラムをのせておきます。
「崖の上のポニョ」のmain 関数部分
#include<avr/io.h> void KARA(int k); void DO(int k); void RE(int k); void MI(int k); void FA(int k); void SO(int k); void RA(int k); void SI1(int k); void SI(int k); void DO2(int k); void RE2(int k); int main(){ long i,j; DDRD =0x10; for(;;){ DO2(360); RA(160); FA(300); DO(90); KARA(5); DO(90); KARA(5); DO(90); RE(110); FA(130); SI1(190); RE(200); DO2(400); RA(170); SI1(170); SO(150); KARA(5); SO(150); SI1(150); RA(170); FA(300); RA(150); SO(150); RE(130); MI(150); FA(150); SO(620); DO2(360); RA(160); FA(300); DO(90); KARA(5); DO(90); KARA(5); DO(90); RE(110); FA(130); SI1(190); RE2(200); DO2(400); RA(180); SI1(170); SO(160); KARA(5); SO(160); SI1(160); RA(180); FA(160); KARA(40); RA(180); SO(160); MI(160); KARA(40); FA(300); KARA(80); } return 0; } void KARA(int k){ int i,m; for(m=0;m<k;m++){ for(i=0;i<248;i++){ PORTD=0x00; } for(i=0;i<248;i++){ PORTD=0x00; } } } void DO(int k){ int i,m; for(m=0;m<k;m++){ for(i=0;i<248;i++){ PORTD=0x10; } for(i=0;i<248;i++){ PORTD=0x00; } } } void RE(int k){ int i,m; for(m=0;m<k;m++){ for(i=0;i<217;i++){ PORTD=0x10; } for(i=0;i<217;i++){ PORTD=0x00; } } } void MI(int k){ int i,m; for(m=0;m<k;m++){ for(i=0;i<195;i++){ PORTD=0x10; } for(i=0;i<195;i++){ PORTD=0x00; } } } void FA(int k){ int i,m; for(m=0;m<k;m++){ for(i=0;i<180;i++){ PORTD=0x10; } for(i=0;i<180;i++){ PORTD=0x00; } } } void SO(int k){ int i,m; for(m=0;m<k;m++){ for(i=0;i<160;i++){ PORTD=0x10; } for(i=0;i<160;i++){ PORTD=0x00; } } } void RA(int k){ int i,m; for(m=0;m<k;m++){ for(i=0;i<143;i++){ PORTD=0x10; } for(i=0;i<143;i++){ PORTD=0x00; } } } void SI1(int k){ int i,m; for(m=0;m<k;m++){ for(i=0;i<134;i++){ PORTD=0x10; } for(i=0;i<134;i++){ PORTD=0x00; } } } void SI(int k){ int i,m; for(m=0;m<k;m++){ for(i=0;i<130;i++){ PORTD=0x10; } for(i=0;i<130;i++){ PORTD=0x00; } } } void DO2(int k){ int i,m; for(m=0;m<k;m++){ for(i=0;i<120;i++){ PORTD=0x10; } for(i=0;i<120;i++){ PORTD=0x00; } } } void RE2(int k){ int i,m; for(m=0;m<k;m++){ for(i=0;i<110;i++){ PORTD=0x10; } for(i=0;i<110;i++){ PORTD=0x00; } } }