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) 

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

digispark.jpg
Digispark(というかその互換品)でやってみたいと思います。
OC1A版をDigispark用に移植してみました。
割り込みルーチンを使わない方法したので、これをBGMとして別の作業はできませんが、逆にスイッチなどをピンチェンジ割り込みで使うようにすればいいのかな?

あと、しばらく触っていないうちに、ATTinyCoreが新しくなっていて、Digisparkにも対応してました。

GitHub - SpenceKonde/ATTinyCore: Arduino core for ATtiny 1634, 828, x313, x4, x41, x5, x61, x7 and x8
https://github.com/SpenceKonde/ATTinyCore

便利になりました。

//  Digispark版 減衰矩形波オルゴール
//    boards manager URL : http://drazzy.com/package_drazzy.com_index.json
//    Board : ATTinyCore > ATtiny85(Micronucleus / DigiSpark)
//    Clock : 16.5MHz (USB)
#include "notes.h"                      // 音符の定義データ(音階と音価(長さ))
#include "Gurenge.h"                    // 曲データ
#define MAX_TRACK       4               // 最大トラック数 (省RAMのため必要最低限で)
// 基本の12音階(ド~シ:C0からB0まで)の波長となるサイクル数 (16.5MHz, 512clock毎の割り込みのとき)
static const uint16_t CYC_SCALE[] = { 1971, 1860, 1756, 1657, 1564, 1476, 1394, 1315, 1242, 1172, 1106, 1044};

void setup(){
  pinMode( 1, OUTPUT);                  // 音声出力 (PB1:OC1A)
  pinMode( 2, INPUT_PULLUP);            // スイッチ (PB2)
  TCCR1 = B01100010;            // 8ビット タイマ/カウンタ1用レジスタ (PWM動作A許可,OC1A比較一致でLow,~OC1Aピン接続断,2分周)
  OCR1C = 255;                          // タイマ/カウンタ1 比較Cレジスタ
  TIMSK &= ~_BV(TOIE1);                 // タイマ/カウンタ1溢れ割り込み許可(TOIE1) 取り消し
}

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

void playDsOC1A(const int16_t *d){      // Digispark(ATtiny85, 16.5MHz用)
  uint8_t  Tracks;                      // 楽譜のトラック数
  uint16_t *NoteP[MAX_TRACK];           // 楽譜のトラック毎のポインタ
  uint16_t NoteCycle;                   // 基準音符(96,48分音符)の長さに必要な割り込みサイクル数
  uint8_t  t;                           // トラックのカウンタ(t)
  uint16_t n = 1;                       // 基準音符毎に処理に入るカウンタ(n)(減算カウンタ)
  uint16_t note;                        // PROGMEMから読み込んだ音符(音階+音価)情報
  uint8_t  len[MAX_TRACK];              // 音符の長さ(96分音符の何個分の長さか)(減算カウンタ)
  uint16_t c[MAX_TRACK], cyc[MAX_TRACK];// 音の波長→1周期に必要な割り込みサイクル数(cyc)とそのカウンタ(c)
  uint16_t env[MAX_TRACK];              // 音の振幅(envelope)
  uint16_t out[MAX_TRACK];              // 出力される値
  uint8_t  lap[MAX_TRACK];              // 音の経過サイクル(laptime),減衰の計算用
  // *** 楽譜の準備 ***
  NoteCycle = F_CPU / 512 *4*60 / pgm_read_word_near(d++) / MIN_NOTE; // 基準音符に必要なサイクル数
  for( Tracks = 0; Tracks < MAX_TRACK; ) {              // 曲データからトラック数と各トラックの開始位置を取得
    if( pgm_read_word_near(d++) != 0 ) continue;        // 区切りが来るまで飛ばす
    if( pgm_read_word_near(d)   == 0 ) break;           // 0が2つ続いたらデータの終了
    len[ Tracks ] = 1;                                  // 音の長さの減算カウンタを残1に初期化
    NoteP[ Tracks++ ] = d;                              // メモリ上の位置を取得、トラック数をカウントアップ    
  }
  do {
    // *** 楽譜の処理 ***
    if( !--n ) {                        // 基準音符長毎に楽譜の処理
      n = NoteCycle;
      for( t = 0; t < Tracks; t++ ) {
        if( !--len[t] ) {
          note   = pgm_read_word_near( NoteP[t]++ );
          len[t] =  note & 0x00ff;      // 下位8bitが音の長さ(96分音符の何倍の長さか)
          cyc[t] = (note & 0xff00) ? (CYC_SCALE[ (note>>8) & 0x0f ] >> (note>>12)) : 0;
                                        // 上位4bitがオクターブ、次の4bitがピッチクラス(0-11)
          c[t]   = 0;                   // 矩形波1周期をつくるためのサイクル数のカウンタを初期化
          lap[t] = 0;                   // 音の経過サイクル初期化
          env[t] = 0xffff;              // 初めは最大振幅
        }
      }
    }
    // *** 波形の処理・出力 ***
    for( t = 0; t < Tracks; t++ ) {
      if( ++c[t] == cyc[t] )  c[t] = 0;                 // 周期は16MHz時 31.03usec*cyc[]
      out[t] = ( c[t] < (cyc[t]>>1) ) ? env[t] : 0;     // duty比1:1の矩形波
      if(!(++lap[t] & 0x0f)) env[t] -= (env[t]>>9);     // 31.03usec*16≒496.5usec毎に振幅減衰
    }
    switch( Tracks ) {                  // OC1A(ATtiny85:PB1) 出力をトラック数毎で変える
      case 1:     OCR1A =  out[0]>>8;                                     break;
      case 2:     OCR1A = (out[0]>>9)  + (out[1]>>9);                     break;
      case 3:     OCR1A = (out[0]>>9)  + (out[1]>>10) + (out[2]>>10);     break;
      case 4:     OCR1A = (out[0]>>10) + (out[1]>>10) + (out[2]>>10) + (out[3]>>10);
    }
    while( !(TIFR & _BV(TOV1)) );       // タイマのオーバーフロー待ち (256x2cycle @16.5MHz≒31.03usec毎)
    TIFR |= _BV(TOV1);                  // Timer/Counter1 Overflow Flag をクリア
  } while( note );
  OCR1A = 0;
}

nice!(0)  コメント(0) 
共通テーマ:趣味・カルチャー