SSブログ

音楽を演奏したい - Wave Table 4和音編 [Arduino]

waon4.jpg
なんとか、正弦波のwave tableを使って4和音出すことができました。

改善点
・10bit(1024clock)割込みで挑戦 → 音質が結構落ちるのでやめた
・ISRではなく関数内で処理
・トラック毎のfor loopを手動展開(添え字を変数にしない)
  ・それより楽譜処理も1サイクル1トラックと変更(音符開始がトラック毎で1サイクルずれても誤差範囲)
  ・同様に減衰処理も1サイクル1トラックと変更(減衰のタイミングが多少ずれても支障なし)
・WaveとEnvelopeの乗算をビット数を落として配列で持っておく → やめた
  (ATmegaは乗算ができることや、音質の低下のため)
・if文での分岐をswitch文に変更
・wave tableの添え字x128で32767を超えたら0にしていたが、x256で65535を超えたらオーバーフローでもとに戻るので処理の必要がなくなった
・出力値 out[]を16bit → 8bitで扱うことにした

正弦波と楽譜の相性
・高音のほうが、波形にエッジがでるのかどうかわからないけど、音量が大きくなるのと、音の終末のノイズ的な音が減ったような気がする。
・2~3オクターブ上げると、オルゴール的な音色になる。
・これ以上あげると、音程のずれが気になりだす。
・楽譜の作成時にオクターブを上げるか、プログラム側でオクターブを上げるか(cyc[n] = WT_SCALE[ ]の右シフトを減らす (9 → 7くらい))

いろいろプログラムの書き方も調べていくと、、
・if文の後に処理が1つだけでも中かっこ{ } をつける
・1行に1文まで
・中かっこ{ } だけで1行使う
・switch文は使わないようにする
など作法というか流派というか流行りのようなものがあるみたいで、、。
でも開発環境が古いノートパソコンなので、極力行数を減らさないと視認性が悪いし、と言い訳。

// Arduino/ATmega328P,32U4 減衰正弦波(Wave Table)オルゴール
// Arduino Uno, Leonardo 両対応

#include  "notes.h"                     // 音符の定義データ(音階と音価(長さ))
#include  "Gurenge.h"                   // 曲データ
#define   MAX_TRACK   4                 // 4和音まで対応できた 

// 基本の12音階の波長となるサイクル数とwave tableの換算
static const uint16_t WT_SCALE[] = {  // (第9オクターブ,1サイクルで1周期(Wave Tableで0-255)進むときの添字の256倍)
  17557, 18601, 19708, 20879, 22121, 23436, 24830, 26306, 27871, 29528, 31284, 33144 };
// 正弦波データ (音の出だしでノイズが出ないように0から開始)
static const uint8_t SinWave[] = {
    0,  0,  0,  0,  1,  1,  1,  2,  2,  3,  4,  5,  5,  6,  7,  9,  10, 11, 12, 14, 15, 17, 18, 20, 21, 23, 25, 27, 29, 31, 33, 35,
   37, 40, 42, 44, 47, 49, 52, 54, 57, 59, 62, 65, 67, 70, 73, 76,  79, 82, 85, 88, 90, 93, 97,100,103,106,109,112,115,118,121,124,
  128,131,134,137,140,143,146,149,152,155,158,162,165,167,170,173, 176,179,182,185,188,190,193,196,198,201,203,206,208,211,213,215,
  218,220,222,224,226,228,230,232,234,235,237,238,240,241,243,244, 245,246,248,249,250,250,251,252,253,253,254,254,254,255,255,255,
  255,255,255,255,254,254,254,253,253,252,251,250,250,249,248,246, 245,244,243,241,240,238,237,235,234,232,230,228,226,224,222,220,
  218,215,213,211,208,206,203,201,198,196,193,190,188,185,182,179, 176,173,170,167,165,162,158,155,152,149,146,143,140,137,134,131,
  128,124,121,118,115,112,109,106,103,100, 97, 93, 90, 88, 85, 82,  79, 76, 73, 70, 67, 65, 62, 59, 57, 54, 52, 49, 47, 44, 42, 40,
   37, 35, 33, 31, 29, 27, 25, 23, 21, 20, 18, 17, 15, 14, 12, 11,  10,  9,  7,  6,  5,  5,  4,  3,  2,  2,  1,  1,  1,  0,  0,  0};

void setup(){
  pinMode(  9, OUTPUT );        // OC1A(Uno:PB1, Leonardo PB5) Uno, Leonardoとも Digital  9番ピン
  pinMode( 10, OUTPUT );        // OC1B(Uno:PB2, Leonardo PB5) Uno, Leonardoとも Digital 10番ピン
    // 9bit高速PWM(TOP=0x01ff), 分周なし, コンペアマッチでLow (512clock毎に割り込み)
  TCCR1A = B10100010;           // コンペアマッチでLow(COM1A1=1,COM1A0=0,COM1B1=1,COM1B0=0),タイマモード設定(一部)(WGM11=1,WGM10=0)
  TCCR1B = B00001001;           // タイマモード設定(一部)(WGM13=0,WGM12=1), クロック分周の設定(CS12=0,CS11=0,CS10=1)
  pinMode( A0, INPUT_PULLUP );
  pinMode( A1, INPUT_PULLUP );
  pinMode( A2, INPUT_PULLUP );
}

void loop(){
  playWT( Gurenge );                    // 再生
  while( digitalRead(A1) );             // スイッチが押されるまで待つ
}

void playWT(const int16_t *d){      // wave tableでの再生 
  uint8_t  Tracks;                      // 楽譜のトラック数
  uint16_t *NoteP[MAX_TRACK];           // 楽譜のトラック毎のポインタ
  uint16_t NoteCycle, n;                // 基準(96分)音符に必要な割り込みサイクル数(NoteCycle)とそのカウンタ(n)(減算カウンタ)
  uint16_t note;                        // PROGMEMから読み込んだ音符(音階+音価)情報
  uint8_t  len[MAX_TRACK];              // 音符の長さ(96分音符の何個分の長さか)(減算カウンタ)
  uint16_t cyc[MAX_TRACK], c[MAX_TRACK];// 音の波長→1周期に必要な割り込みサイクル数(cyc)とそのカウンタ(c)
  uint16_t env[MAX_TRACK] = {};         // 音の振幅(envelope) (すべて0で初期化)
  uint8_t  out[MAX_TRACK];              // 出力される値
  uint8_t  lap = 16;                    // 音の経過サイクル(laptime)カウンタ(減衰の計算用)(減算カウンタ)
  // *** 楽譜の準備 ***
  NoteCycle = F_CPU / 512 *4*60 / pgm_read_word(d++) / MIN_NOTE - 1;   // 基準音符に必要なサイクル数
  for( Tracks = 0; Tracks < MAX_TRACK; ) {      // 曲データからトラック数と各トラックの開始位置を取得
    if( pgm_read_word(d++) != 0 ) continue;     // 区切りが来るまで飛ばす
    if( pgm_read_word(d)   == 0 ) break;        // 0が2つ続いたらデータの終了
    len[ Tracks ] = 1;                          // ついでに初期化処理:音の長さの減算カウンタを残1に初期化
    NoteP[ Tracks++ ] = d;                      // メモリ上の位置を取得
  }
  n = Tracks;                           // 楽譜処理をdoループ開始直後から行えるよう初期化
  TCCR1A |= B10100000;                  // 波形出力(OCR1A(PB1), OCR1B(PB2)ともコンペアマッチでLow)
  do {
    // *** 楽譜の処理 ***
    if( --n < Tracks ) {                // トラック毎に処理をずらして基準音符長毎に楽譜の処理
      if( !--len[n] ) {                 // 1音終わったら次の音の処理
        note = pgm_read_word( NoteP[n]++ );
        len[n]  = note & 0x00ff;        // 下位8ビットが音の長さ(96分音符の何倍の長さか)
        if( note & 0xff00 ) {           // 休符でなければ、、 (休符の場合でもオルゴールのように音の中断はせず余韻は残す)
          cyc[n] = WT_SCALE[ (note>>8) & 0x0f ] >> (9 - (note>>12));  // 1サイクルで進めるWave Tabele添字の256倍
          c[n]   = 0;                   // 正弦波1周期をつくるためのサイクル数のカウンタを初期化
          env[n] = 0xffff;              // 初めは最大振幅
        }
      }
      if( !n ) n = NoteCycle;
    }
    // *** 波形の処理 ***                    sinの波形(同時に添字x256を進める(オーバーフローで戻る)) x 振幅
    switch( Tracks ) {                  // トラック数に応じて波形の処理(switch文のfall through対応)
      case 4:   OCR1A   = SinWave[ (c[3] += cyc[3]) >>8 ] * (env[3]>>8) >>9;
      case 3:   out[2]  = SinWave[ (c[2] += cyc[2]) >>8 ] * (env[2]>>8) >>8;
      case 2:   out[1]  = SinWave[ (c[1] += cyc[1]) >>8 ] * (env[1]>>8) >>8;
      case 1:   out[0]  = SinWave[ (c[0] += cyc[0]) >>8 ] * (env[0]>>8) >>8;
    }
    // *** 振幅の減衰 ***
    switch( --lap ) {                   // 処理が同時にならないようループ毎にずらす
      case 4:   env[3] -= (env[3]>>8);    break;
      case 3:   env[2] -= (env[2]>>8);    break;
      case 2:   env[1] -= (env[1]>>8);    break;
      case 1:   env[0] -= (env[0]>>8);    break;
      case 0:   lap = 16;               // 32us*16=480us毎に振幅減衰(調整可)
    }    
    // *** 波形の出力 ***
    switch( Tracks ) {                  // OC1A出力をトラック数毎で変える
      case 1:   OCR1A  =  out[0]<<1;                                  break;
      case 2:   OCR1A  =  out[0]     +  out[1];                       break;
      case 3:   OCR1A  =  out[0]     + (out[1]>>1) + (out[2]>>1);     break;
      case 4:   OCR1A += (out[0]>>1) + (out[1]>>1) + (out[2]>>1);
    }
    while( !(TIFR1 & _BV(TOV1)) );      // タイマのオーバーフロー待ち
    TIFR1 |= _BV(TOV1);                 // Timer/Counter1 Overflow Flag をクリア
  } while( note );              // 音符データが0なら終了
  OCR1A = OCR1B = 0;            // 出力を0にする
  TCCR1A &= B00001111;          // 波形出力なし(COM1A1=0, COM1A0=0, COM1B1=0, COM1B0=0)
}

タグ: 和音
nice!(0)  コメント(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

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