SSブログ

音楽を演奏したい - digitalWrite編 [Arduino]

ard_dw.jpg
今回は、tone()ではなく、digitalWrite()を使って波形をつくろうと思います。
tone()では、和音が出せないこともないようですが、特殊なやり方のようで、スピーカーも和音の数だけ必要らしい。

・仕組み
和音での演奏をするのにあたり、tone()を使わないで、digitalWrite()のOn/Offで矩形波をで作ることにしました。
tone()では周波数を指定して矩形波の音を出していましたが、digitalWrite()の場合、周波数よりも、矩形波の長さ=1波形の時間=周期、で指定するのが簡便かと考えました。
ちょうど基本の12音階(ド~シ)で第0オクターブの周期(usec; μ秒)が、以下のように16ビット符号なし整数型にいい感じで収まります。
const uint16_t T_SCALE[] = { 61156, 57724, 54484, 51426, 48540, 45815, 43244, 40817, 38526, 36364, 34323, 32396};   

あとは、ループ内で各パート(トラック)ごとに、音が終わる時刻(usExp)が現在時刻(usCur)を過ぎたら新しい音の読み込み。新しい音の読み込みのときには、その音の終了時刻(usExp)と、周期(T)と、周期のうち出力Onにする時間(Ton)を指定する。また、矩形波を作るため、ループ内で現在時刻(usCur)を周期(T)で割った余り(0→(T-1)で変化を繰り返す)が、出力On時間(Ton)内なら出力フラグを立てる。ということにしました。

#include "notes.h"              // 音符の定義データ(音階と音価(長さ))
#include "Gurenge.h"            // 曲データ
#define MAX_TRACK  4            // 最大トラック数
const uint16_t T_SCALE[] = { 61156, 57724, 54484, 51426, 48540, 45815, 43244, 40817, 38526, 36364, 34323, 32396};   
    // 基本の12音階(ド~シ:C0からB0まで)の波長となる周期(usec)

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

void loop(){
  playDW( Gurenge );
  while(digitalRead(A1));
  delay(1000);
}

void playDW(const uint16_t *d){                         // digitalWriteで
  uint8_t  t, track = 0;                                        // 楽譜のトラック数(track) とトラックのカウンタ(t)
  uint8_t  output;                                              // digitalWriteの出力
  uint16_t *p[MAX_TRACK];                                       // 楽譜のトラック毎のポインタ(メモリ上の絶対的な位置)
  uint16_t note;                                                // PROGMEMから読み込んだ音符(音階+音価)情報
  uint32_t usRef, usCur, usExp[MAX_TRACK];                      // 基準音符の長さ, 現時刻, 音の終了時刻(usec)
  uint32_t T[MAX_TRACK], Ton[MAX_TRACK];                        // 周期, 周期のうちONの時間(usec)
  usRef = 1000000*60*4 / MIN_NOTE / pgm_read_word_near(d++);    // テンポから基準96分音符の長さ(usec)を計算 
  for(;;) {                                             // 曲データからトラック数と各トラックの開始位置を取得
    if( pgm_read_word_near(d++) != 0 ) continue;                // 区切りが来るまで飛ばす
    if( pgm_read_word_near(d)   == 0 ) break;                   // 0が2つ続いたらデータの終了
    p[ track++ ] = d;                                           // メモリ上の開始位置を取得、track数をカウントアップ
  }
  for( t = 0; t < track; t++ ) usExp[t] = micros();             // 音の終了時刻の初期化
  do {                                                  // 曲データを読み込みながら再生
    output = LOW;
    usCur  = micros();
    for( t = 0; t < track; t++ ) {
      if( usCur > usExp[t] ) {                                  // 1音の出力時間を過ぎたら次の1音の準備
        if( !(note = pgm_read_word_near(p[t])) ) continue;      // note = 0なら演奏終了
        p[t]++;                                                 // 音符データのポインタを進める
        usExp[t] += (note & 0x00ff) * usRef;                    // 下位8ビットが音の長さ(96分音符の何倍の長さか)
        if( note & 0xff00 ) {                                   // 休符でなければ、、
          T[t] = T_SCALE[ (note>>8) & 0x0f ] >> (note>>12);     // 上位4ビットがオクターブ、次の4ビットがピッチクラス(0-11)
          Ton[t] = T[t] >> 3;                                   // duty比を1/8とした
        } else T[t] = 0;                                        // 休符であれば∞にしたいとことだが、0で。
      }
      if( T[t] && usCur % T[t] < Ton[t] ) output = HIGH;        // 休符でなく波形のON部分なら出力
    }
    digitalWrite( 9, output );
  } while( note );
}

無駄だと思うのは、曲の再生時にトラック数と、各トラックの開始位置を探る処理です。曲データはリアルタイムで変わるものではないので事前に定数として持っていればいいのですが、、、でも、楽譜データ作成の簡便さを優先ということで。
nice!(0)  コメント(0) 
共通テーマ:趣味・カルチャー

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。