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) 
共通テーマ:趣味・カルチャー

nice! 0

コメント 2

うろ

曲データ?(Gurenge.h)はどうやって作成しましたか?また、notes.hの詳細も知りたいです。
by うろ (2022-02-27 21:13) 

お名前(必須)

ブログみていただきありがとうございます。
notes.h は、
・音楽を演奏したい - 楽譜編 [Arduino]
にあります。データの作り方もあります。

曲データのサンプルは、
・音楽を演奏したい - 波形確認編 [Arduino]
にあり、「static const uint16_t PROGMEM Noritz[] = { // テオドール・エステン作曲『人形の夢と目覚め』より」のところです。
紅蓮華は(テキストにすると)データが大きいので別ファイル(Gurenge.h)にしたのと、著作権的にブログに載せていいのかわからなかったので、載せていないだけです。

今思うと、楽譜データを、C4q (C(ドの音)で、オクターブは4で、q(四分音符;Quarter))とか、A5h(A(ラの音)のオクターブは5で、h(二分音符;Half))にしておけば、すっきりして入力もしやすかったと後悔。
by お名前(必須) (2022-03-03 12:24) 

コメントを書く

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