音楽を演奏したい - tone編 [Arduino]
作った楽譜が再生できるかの確認です。
とりあえず、和音なし、Arduino標準のtone()関数を使った再生です。
tone関数は、周波数で音の高さを指定します。
基本の12音階(ド~シ)の周波数(Hz)をスケールとして配列で持っておき、1オクターブは上がると周波数は倍に、1オクターブ下がると周波数は半分になるので、シフト演算でいけます。16ビット符号なし整数型に収まる最大が第11オクターブになるので、ここから希望のオクターブまで右シフトすることで希望のオクターブの12音階(ド~シ)のそれぞれの周波数が得られるという仕組みにしました。
右ビットシフトは切り捨てになりますが、R_BITRSHIFT という四捨五入できるビットシフトマクロを作ってみましたが、それを聞き分けられるほどの耳ではないし、そもそもtone()関数の周波数は整数指定なので気にしなくてもよさそう。
#include "notes.h" // 音符の定義データ(音階と音価(長さ))
#include "Gurenge.h" // 曲データ
#define R_BITRSHIFT(X,S) ((((X)>>((S)-1))+1)>>1) // 四捨五入(ROUND)する右シフト(BITRSHIFT)マクロ
const uint16_t F_SCALE[] = { 33488, 35479, 37589, 39824, 42192, 44701, 47359, 50175, 53159, 56320, 59669, 63217 };
// 基本の12音階(ド~シ:C11からB11まで)の周波数(Hz)(16ビット符号なし整数型に収まる最大が第11オクターブ)
void setup(){
pinMode( 9, OUTPUT ); // tone()の中から呼び出されるのでなくていいが、明示的に
pinMode( A1, INPUT_PULLUP ); // タクトスイッチ(PULLUPしてあり、押すとGND=LOWに)
}
void loop(){
playTone( Gurenge );
delay(1000);
while( digitalRead(A1) ); // 演奏が終わったらスイッチが押される(LOWになる)まで待つ
delay(1000);
}
void playTone(const uint16_t *d){
uint16_t note, freq; // PROGMEMから読み込んだ音符(音階+音価)情報(16bit), 周波数(Hz)
uint32_t usExp, usDur, usRef; // 音の切れる(expire)時刻, 音の持続時間(duration), 基準音符の長さ(usec)
usRef = 1000000 * 60 * 4 / MIN_NOTE / pgm_read_word_near(d++); // テンポから基準96,48分音符の長さ(usec)を計算
usExp = micros(); // 音の終了時刻の初期化
do { // 曲データを読み込みながら再生
if( micros() > usExp ) { // 1音の出力時間を過ぎたら次の1音の準備
if( !(note = pgm_read_word_near(++d)) ) continue; // note = 0なら演奏終了
usDur = (note & 0x00ff) * usRef; // 下位8ビットが音の長さ(基準音符の何倍の長さか)
usExp += usDur; // 音の切れる時間を更新
if(note & 0xff00) { // 休符でなければ、、
freq = R_BITRSHIFT( F_SCALE[(note>>8)&0x0f], 11-(note>>12) ); // 上位4ビットがオクターブ、次の4ビットがピッチクラス(0-11)
tone( 9, freq, usDur>>10 ); // 10bitシフト=(1/1024):usからmsに変換し,やや音を短く
}
}
if( !digitalRead(A1) ) break; // タクトスイッチが押されたら演奏終了
} while( note );
}
マクロ使わない場合。
freq = F_SCALE[(note>>8)&0x0f] >> (11-(note>>12)); // 上位4ビットがオクターブ、次の4ビットがピッチクラス(0-11)