第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;
		}
	}
}
					
					
					
 
 
					  

