SSブログ

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

avr_oc1a.jpg

digitalWrite()編では、ピンの位置だけ変更すれば、Arduino IDEを使ったArduinoの派生品ほぼすべてで移植可能ではないかと思います。
しかし、1音の音量が一定というか、音のエンベロープが長方形で、減衰しないのが難点。
そこで、ATmega328p, ATmega32u4の2つのAVRマイコンに依存した方法で音を出してみます。

私だけの電子オルゴール ♪ MyMelo2 ♪
http://siva.cc.hirosaki-u.ac.jp/usr/koyama/mymelo/

大いに参考というか、ISR内の考え方はそのままいただきました。
D/Aコンバータ(DAC)がないので、超高速なPWMで疑似的に作り出しています。
16MHz / 512clock = 31.250 kHz なので人間にはわからない速さです。
OCR1A, OCR1Bに0~511の範囲で値をいれるDACにします。
ISR内のプログラムは、
・基準の音符(96分音符)ごとに楽譜の評価
・矩形波の作成、減衰処理、出力
の2つの役割があります。
512clock内にとりあえず収まるようで、4和音でつくった「紅蓮華」がちゃんと再生できました。

// Arduino/ATmega328P,32U4 減衰矩形波オルゴール
// Arduino Uno, Leonardo 両対応
#include "notes.h"                      // 音符の定義データ(音階と音価(長さ))
#include "Gurenge.h"                    // 曲データ
#define MAX_TRACK  4                    // 最大トラック数 (省RAMのため必要最低限で)

// 基本の12音階(ド~シ:C0からB0まで)の波長となるサイクル数 (16MHz, 512clock毎の割り込みのとき)
static const uint16_t CYC_SCALE[] = { 1911, 1804, 1703, 1607, 1517, 1432, 1351, 1276, 1204, 1136, 1073, 1012 };
// ISRでのやり取りのためのグローバル変数
static uint8_t  Tracks;                 // 楽譜のトラック数
static uint16_t *NoteP[MAX_TRACK];      // 楽譜のトラック毎のポインタ
static uint16_t NoteCycle;              // 基準音符(96,48分音符)の長さに必要な割り込みサイクル数
static uint8_t  Reverb = 9;             // 残響のレベル(0-16, 小さいと短い音、大きいと長い音)

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番ピン
        // 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(){
  playOC1A( Gurenge );                  // 演奏開始
  while( TIMSK1 & _BV(TOIE1) ){         // 演奏終了(割り込み停止)まで待つ 注意:ループ内delay()禁止(誤作動の原因)
    if( !digitalRead(A0) ) Reverb =  8;         // 減衰幅を大きく残響が短い音
    if( !digitalRead(A1) ) Reverb =  9;         // 減衰幅中間
    if( !digitalRead(A2) ) Reverb = 10;         // 減衰幅を小さく残響が長い音
  }
  while( digitalRead(A1) );             // ボタンが押される(LOWになる)まで待つ
}

void playOC1A(const uint16_t *d){       // 初期化作業のみ、本体はタイマ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つ続いたらデータの終了
    NoteP[ Tracks++ ] = d;                      // メモリ上の位置を取得、トラック数をカウントアップ
  }
  TCCR1A |= B10100000;                  // 波形出力(OCR1A(D9), OCR1B(D10)ともコンペアマッチでLow)
  TIMSK1 |= _BV(TOIE1);                 // タイマ1のオーバーフロー割り込み(TOIE1)
}

ISR(TIMER1_OVF_vect){           // タイマ1オーバーフロー:カウンタ最大値を超えたとき割り込み(F_CPU/9bit(Hz)→32usec毎)
  static uint8_t  t;                            // トラックのカウンタ(t)
  static uint16_t n;                            // 基準音符毎に処理に入るカウンタ(n)
  static uint16_t note;                         // PROGMEMから読み込んだ音符(音階+音価)情報
  static uint8_t  len[MAX_TRACK] = {};          // 音符の長さ(96分音符の何個分の長さか)
  static uint16_t c[MAX_TRACK], cyc[MAX_TRACK]; // 音の波長→1周期に必要な割り込みサイクル数(cyc)とそのカウンタ(c)
  static uint16_t env[MAX_TRACK];               // 音の振幅(envelope)
  static uint16_t out[MAX_TRACK];               // 出力される値
  static uint8_t  lap[MAX_TRACK];               // 音の経過サイクル(laptime)、32μs*16=480μsごとに減衰させる
  // *** 楽譜の処理 ***
  if( !n ) {                    // 基準音符長毎に楽譜の処理
    n = NoteCycle;
    for( t = 0; t < Tracks; t++ ) {
      if( !len[t] ) {
        note = pgm_read_word_near( NoteP[t]++ );
        if( !note ) {                   // 音の開始時の初期化 0なら今回の演奏終了
          OCR1A = OCR1B = 0;            // 出力を0にする          
          TCCR1A &= B00001111;          // 波形出力なし(COM1A1=0, COM1A0=0, COM1B1=0, COM1B0=0)
          TIMSK1 &= ~_BV(TOIE1);        // タイマ1のオーバーフロー割り込み(TOIE1)=0
          return;
        }
        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;                // 初めは最大振幅
      }
      len[t]--;
    }
  }
  n--;
  // *** 波形の処理・出力 ***
  for( t = 0; t < Tracks; t++ ) {
    if( ++c[t] == cyc[t] )  c[t] = 0;                       // 周期は16MHz時 32usec*cyc[]
    out[t] = ( c[t] < (cyc[t]>>1) ) ? env[t] : 0;           // duty比1:1の矩形波
    if(!(++lap[t] & 0x0f)) env[t] -= (env[t]>>Reverb);      // 32us*16=480us毎に振幅減衰
  }
  switch( Tracks ) {                    // OC1A(D9), OC1B(D10) 出力をトラック数毎で変える
    case 1:     OCR1A = OCR1B =  out[0]>>7;                                 break;
    case 2:     OCR1A = OCR1B = (out[0]>>8) + (out[1]>>8);                  break;
    case 3:     OCR1A = OCR1B = (out[0]>>8) + (out[1]>>9) + (out[2]>>9);    break;
    case 4:     OCR1A = OCR1B = (out[0]>>9) + (out[1]>>9) + (out[2]>>9) + (out[3]>>9);
  }
}

ちょっと手直ししました。(2020/12/05)
nice!(0)  コメント(2) 
共通テーマ:趣味・カルチャー

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