SSブログ

音楽を演奏したい - 波形確認編 [Arduino]

ひさしぶりにyoutubeに動画をアップしてみました。
ライン入力でAudacityというソフトで取り込んでみました。
このソフトで音声をキャプチャーしているところを、スクリーンキャプチャーして動画にまとめました。



波形が見えると気持ちいいです。
includeする "notes.h"は以前のままです。
// Arduino/ATmega328P,32U4 楽譜再生スケッチ
// Arduino Uno, Leonardo 両対応
#include "notes.h"                              // 音符の定義データ(音階と音価(長さ))
#define MAX_TRACK  4                            // 最大トラック数

static const uint16_t F_SCALE[]   = {           // 基本12音階のスケール:C11~B11の周波数(Hz)
  33488, 35479, 37589, 39824, 42192, 44701, 47359, 50175, 53159, 56320, 59669, 63217 };
static const uint16_t T_SCALE[]   = {           // 基本12音階のスケール:C0~B0の周期(usec) 
  61156, 57724, 54484, 51426, 48540, 45815, 43244, 40817, 38526, 36364, 34323, 32396 };
static const uint16_t CYC_SCALE[] = {           // 基本12音階のスケール:C0~B0のサイクル数(16MHz, 512clk)
  1911, 1804, 1703, 1607, 1517, 1432, 1351, 1276, 1204, 1136, 1073, 1012 }; 
static const uint16_t WT_SCALE[]  = {           // 基本12音階のスケール:C9~B9のWT添字増加量/cycの256倍
  17557, 18601, 19708, 20879, 22121, 23436, 24830, 26306, 27871, 29528, 31284, 33144 };

static const uint8_t SinWave[] = {              // 正弦波の波形(振幅0-255, 添え字0-255)
    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};

static const uint16_t PROGMEM Noritz[] = {      // テオドール・エステン作曲『人形の夢と目覚め』より
  112, 0,                                                                       // テンポ
  G5_8  , F5_8  , E5_4  , G5_8  , C6_8  , B5_4  , G5_8  , D6_8  ,               // トラック0
  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  ,       // トラック1
  0,
  RST_4 , C4_8  , G4_8  , G4_8  , G4_8  , D4_8  , B4_8  , B4_8  , B4_8  ,       // トラック2
  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  ,       // トラック3
  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 );                // OC1A(Uno:PB1, Leonardo PB5) Uno, Leonardoとも Digital  9番ピン
  pinMode( 10, OUTPUT );                // OC1B(Uno:PB2, Leonardo PB6) Uno, Leonardoとも Digital 10番ピン
  pinMode( A0, INPUT_PULLUP );          // タクトスイッチ(左側)
  pinMode( A1, INPUT_PULLUP );          // タクトスイッチ(中央)
  pinMode( A2, INPUT_PULLUP );          // タクトスイッチ(右側)
}

void loop(){
  static uint8_t playMode = 0;
  while( digitalRead( A0 ) && digitalRead( A1 ) && digitalRead( A2 ) ); 
  if( !digitalRead( A0 ) ) { delay(200); while( !digitalRead( A0 ) ); if( playMode > 0 ) playMode--; }  
  if( !digitalRead( A2 ) ) { delay(200); while( !digitalRead( A2 ) ); if( playMode < 3 ) playMode++; }  
  if( !digitalRead( A1 ) ) { delay(200); while( !digitalRead( A1 ) );
    switch( playMode ) {
      case 0: playTone( Noritz ); break;
      case 1: playDW( Noritz );   break;
      case 2: playOC1A( Noritz ); break;
      case 3: playWT( Noritz );
    }
  }
}

void playTone(const uint16_t *d){       // ★ tone()で再生 
  uint16_t note, freq;                  // PROGMEMから読み込んだ音符(音階+音価)情報(16bit), 周波数(Hz)
  uint32_t usExp, usDur, usRef;         // 音の切れる(expire)時刻, 音の持続時間(duration), 基準音符の長さ(reference)(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 = F_SCALE[(note>>8)&0x0f] >> (11-(note>>12));      // 上位4ビットがオクターブ、次の4ビットがピッチクラス(0-11)
        tone( 9, freq, usDur>>10 );                             // 10bitシフト=(1/1024):usからmsに変換し,やや音を短く
      }
    }
  } while( note );
}

void playDW(const uint16_t *d){         // ★ digitalWrite()で再生
  uint8_t  t, track;                                            // 楽譜のトラック数(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( track = 0; track < MAX_TRACK; ) {                        // 曲データからトラック数と各トラックの開始位置を取得
    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 );
}

void playOC1A(const int16_t *d){        // ★ OC1A 矩形波で再生
  uint8_t  Tracks, t;                   // 楽譜のトラック数(Tracks)とそのカウンタ(t)
  uint16_t *NoteP[MAX_TRACK];           // 楽譜のトラック毎のポインタ
  uint16_t NoteCycle;                   // 基準音符(96,48分音符)の長さに必要な割り込みサイクル数
  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),減衰の計算用
  // 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)
  // *** 楽譜の準備 ***
  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( !(TIFR1 & _BV(TOV1)) );      // タイマのオーバーフロー待ち
    TIFR1 |= _BV(TOV1);                 // Timer/Counter1 Overflow Flag をクリア
  } while( note );
  OCR1A = OCR1B = 0;                    // 出力を0にする
  TCCR1A &= B00001111;                  // 波形出力なし(COM1A1=0, COM1A0=0, COM1B1=0, COM1B0=0)
}

void playWT(const int16_t *d){          // ★ OC1A 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)カウンタ(減衰の計算用)(減算カウンタ)
  // 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)
  // *** 楽譜の準備 ***
  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ループ開始直後から行えるよう初期化
  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;    // 7bit
      case 3:   out[2]  = SinWave[ (c[2] += cyc[2]) >>8 ] * (env[2]>>8) >>9;    // 7bit
      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,OC1B出力をトラック数毎で変える
      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];       break;
      case 4:   OCR1A += (out[0]>>1) + (out[1]>>1) +  out[2];           // 4和音モノラル
    //case 4:   OCR1A +=  out[0]     + (out[1]>>1);                     // 4和音ステレオ(L)
    //          OCR1B  =  out[0]     + (out[1]>>1) +  out[2];           // 4和音ステレオ(R)
    }
    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:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

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