音楽を演奏したい - toneで和音編

AVRを直接いじらず、上級関数(?)のtone()を使って和音を出す。スピーカー1つで。



タイムスライスと言っていいのかわかりませんが、時間を細切れにして、一定の間隔でそれぞれのトラックに割り当てていくという感じです。
分割時間の間隔ですが、96分音符をトラックの数で分割してみたところ、間隔が短すぎてノイズっぽくなったので、倍の長さにしてみました。それ以上長くしようとすると、分割のポコポコとした感じが目立ったり、16分音符が使えなくなったりします。
あと、低い音は分割した時間内に、音として聞き取れるほどには十分に波形の数がそろわないようで、1~2オクターブほど上げると耳障りのよい音になりました。


// Arduino tone()関数でTime Sliceで和音再生
#include "notes.h"              // 音符の定義データ(音階と音価(長さ))
#define MAX_TRACK  4            // 最大トラック数
const uint16_t F_SCALE[] = { 33488, 35479, 37589, 39824, 42192, 44701, 47359, 50175, 53159, 56320, 59669, 63217 };
   // 基本の12音階(ド~シ:C11からB11まで)の周波数(Hz)(16ビット符号なし整数型に収まる最大が第11オクターブ)

const uint16_t Noritz[] = {
 112, 0,
 G5_8  , F5_8  , E5_4  , G5_8  , C6_8  , B5_4  , G5_8  , D6_8  ,
 C6_4  , E6_2  , C6_8  , B5_8  , A5_4  , F6_8  , D6_8  , C6_4  , B5_4  ,
 C6_8  , G5_8  , G5_8  , F5_8  , E5_2  ,
 0,
 RST_4 , RST_1 , RST_1 , RST_1 , RST_8 , E5_8  , E5_8  , D5_8  , C5_2  ,
 0,
 RST_4 , C4_8  , G4_8  , G4_8  , G4_8  , D4_8  , B4_8  , B4_8  , B4_8  ,
 E4_8  , C5_8  , C5_8  , C5_8  , E4_8  , C5_8  , C5_8  , C5_8  ,
 F4_8  , C5_8  , C5_8  , C5_8  , G4_8  , E5_8  , G4_8  , F5_8  , C5_2  , RST_2 ,
 0,
 RST_4 , RST_8 , E4_8  , E4_8  , E4_8  , RST_8 , G4_8  , G4_8  , G4_8  ,
 RST_8 , G4_8  , G4_8  , G4_8  , RST_8 , G4_8  , G4_8  , G4_8  ,
 RST_8 , A4_8  , A4_8  , A4_8  , RST_8 , C5_8  , RST_8 , D5_8  , G4_2  , RST_2 ,
 0, 0 };

void setup(){
 pinMode(   9, OUTPUT );
 pinMode(  A0, INPUT_PULLUP );
 pinMode(  A1, INPUT_PULLUP );
 pinMode(  A2, INPUT_PULLUP );
}

void loop(){
 playTS( Noritz );
 while(digitalRead(A1));
 delay(1000);
}

void playTS(const uint16_t *d){                 // time sliceで和音を
 uint8_t  t = 0, track = 0;                            // 楽譜のトラック数(track) とトラックのカウンタ(t)
 uint16_t *p[MAX_TRACK];                               // 楽譜のトラック毎のポインタ
 uint16_t note, freq[MAX_TRACK];                       // 音符(音階+音価)情報(16bit), 周波数(Hz)
 uint8_t  len[MAX_TRACK] = {};                         // 音符の長さ(96分音符の何個分の長さか)
 uint32_t usInt, usExp;                                // 音の分割間隔(interval), 音の終了(expire)時刻(usec)
 usInt = 1000000*60*4 / MIN_NOTE / *d++;               // テンポから基準96分音符の長さ(usec)を計算
 for( ; track < MAX_TRACK; ) {                         // 曲データからトラック数と各トラックの開始位置を取得
   if( *d++ != 0 ) continue;                           // 区切りが来るまで飛ばす
   if( *d   == 0 ) break;                              // 0が2つ続いたらデータの終了
   p[ track++ ] = d;                                   // メモリ上の開始位置を取得、track数をカウントアップ
 }
 usInt = ( usInt << 1 ) / track;                       // トラック数で分割、間隔が短いと音質が落ちるので2倍する
 usExp = micros();
 do {                                          // 基準96分音符/2=48分音符の長さ/トラック毎に処理
   if( !len[t]-- ) {                                   // 音符の長さを減算していき0になったら次の音符へ
     if( !(note = *p[t]++) ) break;                    // note = 0なら演奏終了
     len[t]  = ((note & 0x00ff) >> 1) - 1;             // 下位8bitが音の長さ(間隔を倍にしたので半分にする)
     freq[t] =  (note & 0xff00) ?                      // 上位4ビットがオクターブ、次の4ビットがピッチクラス(0-11)
     //F_SCALE[(note>>8)&0x0f] >> (11-(note>>12)) : 0; // 周波数を計算、休符の場合は0
       F_SCALE[(note>>8)&0x0f] >> ( 9-(note>>12)) : 0; // 周波数を計算、休符の場合は0 (2オクターブ上げてみた)
   }
   noTone( 9 );
   if( freq[t] ) tone( 9, freq[t] );                   // 休符でなければ、、                                       
   if( ++t == track ) t = 0;
   usExp += usInt;
   while( micros() < usExp );                          // 分割時間を過ぎたら次の音へ
 } while( 1 );
 noTone( 9 );
}

この記事へのコメント