SSブログ
前の10件 | -

音楽を演奏したい - toneで和音編 [Arduino]

AVRを直接いじらず、上級関数(?)のtone()を使って和音を出す。スピーカー1つで。



タイムスライスと言っていいのかわかりませんが、時間を細切れにして、一定の間隔でそれぞれのトラックに割り当てていくという感じです。
分割時間の間隔ですが、96分音符をトラックの数で分割してみたところ、間隔が短すぎてノイズっぽくなったので、倍の長さにしてみました。それ以上長くしようとすると、分割のポコポコとした感じが目立ったり、16分音符が使えなくなったりします。
あと、低い音は分割した時間内に、音として聞き取れるほどには十分に波形の数がそろわないようで、1~2オクターブほど上げると耳障りのよい音になりました。

// Arduino tone()関数でTime Sliceで和音再生
#include "notes.h"              // 音符の定義データ(音階と音価(長さ))
#define MAX_TRACK  4            // 最大トラック数
const uint16_t F_SCALE[] = { 33488, 35479, 37589, 39824, 42192, 44701, 47359, 50175, 53159, 56320, 59669, 63217 };
    // 基本の12音階(ド~シ:C11からB11まで)の周波数(Hz)(16ビット符号なし整数型に収まる最大が第11オクターブ)

const uint16_t Noritz[] = {
  112, 0,
  G5_8  , F5_8  , E5_4  , G5_8  , C6_8  , B5_4  , G5_8  , D6_8  ,
  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  ,
  0,
  RST_4 , C4_8  , G4_8  , G4_8  , G4_8  , D4_8  , B4_8  , B4_8  , B4_8  ,
  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  ,
  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 );
  pinMode(  A0, INPUT_PULLUP );
  pinMode(  A1, INPUT_PULLUP );
  pinMode(  A2, INPUT_PULLUP );
}

void loop(){
  playTS( Noritz );
  while(digitalRead(A1));
  delay(1000);
}

void playTS(const uint16_t *d){                 // time sliceで和音を
  uint8_t  t = 0, track = 0;                            // 楽譜のトラック数(track) とトラックのカウンタ(t)
  uint16_t *p[MAX_TRACK];                               // 楽譜のトラック毎のポインタ
  uint16_t note, freq[MAX_TRACK];                       // 音符(音階+音価)情報(16bit), 周波数(Hz)
  uint8_t  len[MAX_TRACK] = {};                         // 音符の長さ(96分音符の何個分の長さか)
  uint32_t usInt, usExp;                                // 音の分割間隔(interval), 音の終了(expire)時刻(usec)
  usInt = 1000000*60*4 / MIN_NOTE / *d++;               // テンポから基準96分音符の長さ(usec)を計算 
  for( ; track < MAX_TRACK; ) {                         // 曲データからトラック数と各トラックの開始位置を取得
    if( *d++ != 0 ) continue;                           // 区切りが来るまで飛ばす
    if( *d   == 0 ) break;                              // 0が2つ続いたらデータの終了
    p[ track++ ] = d;                                   // メモリ上の開始位置を取得、track数をカウントアップ
  }
  usInt = ( usInt << 1 ) / track;                       // トラック数で分割、間隔が短いと音質が落ちるので2倍する
  usExp = micros();
  do {                                          // 基準96分音符/2=48分音符の長さ/トラック毎に処理
    if( !len[t]-- ) {                                   // 音符の長さを減算していき0になったら次の音符へ
      if( !(note = *p[t]++) ) break;                    // note = 0なら演奏終了
      len[t]  = ((note & 0x00ff) >> 1) - 1;             // 下位8bitが音の長さ(間隔を倍にしたので半分にする)
      freq[t] =  (note & 0xff00) ?                      // 上位4ビットがオクターブ、次の4ビットがピッチクラス(0-11)
      //F_SCALE[(note>>8)&0x0f] >> (11-(note>>12)) : 0; // 周波数を計算、休符の場合は0
        F_SCALE[(note>>8)&0x0f] >> ( 9-(note>>12)) : 0; // 周波数を計算、休符の場合は0 (2オクターブ上げてみた)
    }
    noTone( 9 );
    if( freq[t] ) tone( 9, freq[t] );                   // 休符でなければ、、                                       
    if( ++t == track ) t = 0;
    usExp += usInt;
    while( micros() < usExp );                          // 分割時間を過ぎたら次の音へ
  } while( 1 );
  noTone( 9 );
}

タグ:音楽 和音
nice!(0)  コメント(304) 
共通テーマ:趣味・カルチャー

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

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



波形が見えると気持ちいいです。

スケッチです。


タグ:音楽 波形
nice!(0)  コメント(0) 
共通テーマ:趣味・カルチャー

音楽を演奏したい - ライン入力編 [周辺機器]

前回のでライン出力/スピーカー出力まではできるようになりました。
これを取り込むのにマイクから取り込むのはさすがにナンセンスで、ライン入力で行きたいわけです。
ただ、うちのノートパソコンは、3.5mmの4極のジャック(TRRS; Tip Ring Ring Sleeve)が1つだけでイヤホンか、イヤホンとマイクが一体になったやつしかありません。
デスクトップでも、ヘッドホン端子・ライン出力(黄緑)、マイク端子(ピンク)はあっても、ライン入力(水色)がなかったり。

問題点
マイク端子(ピンク)問題
 基本的にコンデンサマイク用。
 電源用バイアス電圧(2.2V)がバイアス抵抗(数kΩ)を経由してかかっているよう。
 3極(TRS; Tip Ring Sleeve)でもL-CHのモノラルのものが多い(みたい)。
パソコン壊したくない問題
 Arduinoの自作回路側の失敗でPCに損傷がおこるのは避けたい。

解決のために
・マイク端子がライン入力にできるかも
 端子を共有している場合あり。
 Realtek Audio Control を入手 - Microsoft Store ja-JP
 https://www.microsoft.com/ja-jp/p/realtek-audio-control/9p2b8mcsvpln
 とか
 C:\Program Files\Realtek\Audio\HDA にある RAVCpl64.exe
 (コントロールパネルにあるならおそらく同一)
 で設定できる場合もあるらしい。
 Realtek Audio Control はリンクはあるものの実態がないのか、ダウンロードできず。
 うちのPCの場合、RAVCpl64.exeは入っていたけど、起動せず。
・USB接続
 「USB オーディオインターフェイス」など検索しても、安いやつは黄緑とピンクの端子のやつ。
 「USB オーディオキャプチャー」で検索すると吉。
・Bluetooth接続
 「Bluetooth オーディオトランスミッター」で検索すると使えそうなものあり。
 送信(Tx)・受信(Rx)に注意。両対応なら問題なし。

ということで、「デジ造 音楽版 PCA-ACU」(販売終了)の中古を購入しました。
pcaacu.jpg

デジ造音楽版 PCA-ACU | オーディオキャプチャーユニット | 製品案内 | 株式会社プリンストン
https://www.princeton.co.jp/product/pcaacu.html

nice!(0)  コメント(0) 

音楽を演奏したい - サウンドシールド編 [Arduino]

soundshield1.jpg
こんなものを作ってみました。
ステレオミニプラグを挿したら、ライン出力で、そうでないときはモノラルスピーカー出力します。
 D9 (OC1A)がステレオの左 (L)
 D10 (OC1B)がステレオの右 (R)
他にピンは、
 A0, A1, A2を操作ボタン。
   タクトスイッチとつなぎ、(普段はスケッチでPull-upしておき)押したらGND(LOW)。
 RESETボタンも。
になっています。

ステレオをモノラルにする回路というのは適当に調べた範囲では、あまりはっきり決まったものはないようですが、とりあえず両方のチャンネルとも抵抗を挟んで合成すればいいみたい。しかしその値はまちまち。なんとなく1kΩにしてみた。
アンプとか、アナログ回路は全くわからないので、以前にAliExpressで10個でいくらみたいに売っていたPAM8403を使用したモジュールを使いました。共立エレショップでも同じものが売っていました。

3W+3WステレオD級オーディオアンプモジュール / MOD602-PAM8403-15SP
https://eleshop.jp/shop/g/gF81125/

これを裏面に実装しました。
一応、Duemilanove、Uno R1(DIP版)、Leonardoで干渉しない位置になっているはず。
soundshield2.jpg

・作成の経緯
作品の音をアップロードしたいわけですが、スマホで録音すると音のレベルが低くかったり、ノイズがのったり。
直接、ライン入力したいわけです。
あと、パソコンに入力するとなると、パソコンが壊れるようなことも避けたいのです。
あらためて、接続法の検討です。

音声合成LSI 「AquesTalk pico LSI」といのがありまして、、

音声合成LSI AquesTalk pico LSI データシート - ATP3012XX
https://www.a-quest.com/archive/manual/atp3012_datasheet.pdf

ATP3011XXではないほうです。
これが、ATmega328pを使用していて、そのDIP版の15番ピン(PB1 (OC1A/PCINT1))を出力(AOUT ; 音声出力端子(0-VCC[V] PWM出力))にしています。
さらに16MHzでの動作時は、PWM搬送波が30KHzとありますが、16MHz / 9bit高速PWM(=512Clock) = 31,250KHzということで、一致と考えていいのでしょうか?
だとすると、6ページの「5. 基本回路」の左の図か、Max 15ページの「13. 付録」の「13.1. アナログ・オーディオ回路(参考回路)」のところが参考になりそうです。
ただ、どっちがいいのかわからない。
さらに、

音声合成LSI用のオーディオアンプ(再考)
http://blog-yama.a-quest.com/?eid=970162

というのもあり、一番下の右に ATP3012用の回路図がありましたが、ますますわかりません。
よくわからないものの、、
aout1.png
これでいきました。(16MHzだけど省略なしにしてみた。)

ちなみに、この前つくったスピーカーのケースに組み込みのPro micro版は、
aout2.png
です。

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

音楽を演奏したい - 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) 
共通テーマ:趣味・カルチャー

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

今回は、正弦波を出してみたいと思います。

wavetable.png

・仕組み
こんな感じの添え字が0~255までで、値が0~255までの配列をつくりました。
正弦波というか、位相をずらして0から始まるようにしてあります。(音の鳴り始めのポップノイズをなくすためです。)
音の周波数によって、配列の添え字の進め方を変えて値を読み取ります。
たとえば、A4 = 440Hzだと 16MHz, 9bit割り込み(512clock)の場合、
256 / (16000000 / 512 / 440) = 3.60448
ということで、3.60448ずつ配列の添え字を進めていけばいいのですが、処理能力的にマイコンで少数を扱うのはやめたほうがいいと思います。
そこで仮に配列が大きく、添え字が0~32767まであったとしたら、128倍して、461.37344となり、小数点以下を切り捨てて461としても大きな問題がなさそうです。
ただ、配列を大きくするとメモリが足りなくなるので、配列に添え字を入れる段階で128で割る=7ビット右シフトすることで誤差を許容範囲にできるという寸法です。
256倍にしなかったのは、添え字を足していき65535を超えたたらまた添え字を0にするというようにしてしまうと、16ビット内で計算ができないためです。

・問題点
波形の性質なのか、音が減衰しきっていないときに新しい音がくるとポップノイズがでる。
波形の性質なのか、ハードウェアのせいなのか、音量が小さい。
波形の種類や減衰量などを選べるようにと機能を欲張ったら、2和音しか出せなくなった。

処理能力については、配列を使わないようにするとか、変数を使いまわすとか、いろいろテクニックはあるらしい。
とりあえず個人的にWave Tableに興味がなくなってしまったので、覚えとしての和音なしでのスケッチ。

// Arduino/ATmega328P,32U4 減衰正弦波オルゴール
// Arduino Uno, Leonardo 両対応
#include "notes.h"                  // 音符の定義データ(音階と音価(長さ))
#include "Gurenge.h"                // 曲データ

static uint16_t NoteCycle;          // 基準音符(96,48分音符)の長さに必要な割り込みサイクル数
static uint16_t *NoteP;             // 楽譜のポインタ
// 基本の12音階の波長となるサイクル数とwave tableの換算スケール
// 第10オクターブ, 1サイクル(@16MHz,9bit高速=512clock)で1周期(Wave Tableで0-255)進むときの添字の128倍)
static const uint16_t WT_SCALE[] = { 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 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)
}

void loop(){
  playOC1A( Gurenge );              // 演奏開始
  while( TIMSK1 & _BV(TOIE1) );     // 演奏終了(割り込み停止)まで待機(ループ内delay禁止(誤作動の原因))
  delay(5000);
}

void playOC1A(const uint16_t *d){
  NoteCycle = F_CPU / 512 *4*60 / pgm_read_word_near( d ) / MIN_NOTE;  // 基準音符に必要なサイクル数
  NoteP = d + 2;                    // 楽譜の開始位置を取得
  TCCR1A |= B10100000;              // 波形出力(OCR1A(D9), OCR1B(D10)ともコンペアマッチでLow)
  TIMSK1 |= _BV(TOIE1);             // タイマ1のオーバーフロー割り込み(TOIE1)
}

ISR(TIMER1_OVF_vect){               // タイマ1オーバーフロー:カウンタ最大値を超えたとき割り込み(F_CPU/9bit(Hz)→32usec毎)
  static uint16_t n = 1;            // 基準音符毎に処理に入るカウンタ
  static uint16_t note;             // PROGMEMから読み込んだ音符(音階+音価)情報
  static uint8_t  len = 1;          // 音符の長さ(96,48分音符の何個分の長さか)
  static uint16_t cyc, c;           // 音の波長 → 1サイクルでWaveTableの添字を進める量x128とそのカウンタ
  static uint16_t env;              // 音の振幅(envelope)
  static uint8_t  lap;              // 1音の経過サイクルカウンタ(減衰計算用), 32μs*16=480μsごとに減衰させる
  
  // *** 楽譜の処理 ***
  if( !--n ) {                      // 基準音符長毎に楽譜の処理
    n = NoteCycle;
    if( !--len ) {                  // 1音終わったら次の音の処理
      note = pgm_read_word_near( NoteP++ );
      len  = note & 0x00ff;         // 下位8ビットが音の長さ(96,48分音符の何倍の長さか)
      if( note & 0xff00 ) {         // 休符でなければ
        cyc = WT_SCALE[ (note>>8) & 0x0f ] >> (10 - (note>>12)); // 1サイクルで進めるWave Tabele添字の数の128倍
        c   = 0;                    // 正弦波1周期をつくるためのサイクル数のカウンタを初期化
        lap = 0;                    // 音の経過サイクル(laptime)
        env = 0xffff;               // 初めは最大振幅
      }
      if( !note ) {                 // 0なら演奏終了
        OCR1A = OCR1B = 0;          // 出力を0にする
        TCCR1A &= B00001111;        // 波形出力なし(COM1A1=0, COM1A0=0, COM1B1=0, COM1B0=0)
        TIMSK1 &= ~_BV(TOIE1);      // タイマ1のオーバーフロー割り込み(TOIE1)=0
        n = len = 1;                // カウンタを初期化して終了
        return;
      }
    }
  }
  // *** 波形の処理・出力 ***
  if( c > 32767 ) c = 0;            // 添字x128が、添字の範囲[0-255]を超えたらリセット
  OCR1A = OCR1B = (SinWave[ c>>7 ] * (env>>8)) >> 7;
  c += cyc;
  if( !(lap++ & 0x0f) ) env -= (env>>8);  // 32us*16=480us毎に振幅減衰(調整可)
}


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

音楽を演奏したい - 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)  コメント(18) 
共通テーマ:趣味・カルチャー

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

ard_dw.jpg
今回は、tone()ではなく、digitalWrite()を使って波形をつくろうと思います。
tone()では、和音が出せないこともないようですが、特殊なやり方のようで、スピーカーも和音の数だけ必要らしい。

・仕組み
和音での演奏をするのにあたり、tone()を使わないで、digitalWrite()のOn/Offで矩形波をで作ることにしました。
tone()では周波数を指定して矩形波の音を出していましたが、digitalWrite()の場合、周波数よりも、矩形波の長さ=1波形の時間=周期、で指定するのが簡便かと考えました。
ちょうど基本の12音階(ド~シ)で第0オクターブの周期(usec; μ秒)が、以下のように16ビット符号なし整数型にいい感じで収まります。
const uint16_t T_SCALE[] = { 61156, 57724, 54484, 51426, 48540, 45815, 43244, 40817, 38526, 36364, 34323, 32396};   

あとは、ループ内で各パート(トラック)ごとに、音が終わる時刻(usExp)が現在時刻(usCur)を過ぎたら新しい音の読み込み。新しい音の読み込みのときには、その音の終了時刻(usExp)と、周期(T)と、周期のうち出力Onにする時間(Ton)を指定する。また、矩形波を作るため、ループ内で現在時刻(usCur)を周期(T)で割った余り(0→(T-1)で変化を繰り返す)が、出力On時間(Ton)内なら出力フラグを立てる。ということにしました。

#include "notes.h"              // 音符の定義データ(音階と音価(長さ))
#include "Gurenge.h"            // 曲データ
#define MAX_TRACK  4            // 最大トラック数
const uint16_t T_SCALE[] = { 61156, 57724, 54484, 51426, 48540, 45815, 43244, 40817, 38526, 36364, 34323, 32396};   
    // 基本の12音階(ド~シ:C0からB0まで)の波長となる周期(usec)

void setup(){
  pinMode(   9, OUTPUT );
  pinMode(  A1, INPUT_PULLUP );
}

void loop(){
  playDW( Gurenge );
  while(digitalRead(A1));
  delay(1000);
}

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

無駄だと思うのは、曲の再生時にトラック数と、各トラックの開始位置を探る処理です。曲データはリアルタイムで変わるものではないので事前に定数として持っていればいいのですが、、、でも、楽譜データ作成の簡便さを優先ということで。
nice!(0)  コメント(0) 
共通テーマ:趣味・カルチャー

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

ard_tone.jpg

作った楽譜が再生できるかの確認です。
とりあえず、和音なし、Arduino標準のtone()関数を使った再生です。
tone関数は、周波数で音の高さを指定します。
基本の12音階(ド~シ)の周波数(Hz)をスケールとして配列で持っておき、1オクターブは上がると周波数は倍に、1オクターブ下がると周波数は半分になるので、シフト演算でいけます。16ビット符号なし整数型に収まる最大が第11オクターブになるので、ここから希望のオクターブまで右シフトすることで希望のオクターブの12音階(ド~シ)のそれぞれの周波数が得られるという仕組みにしました。
右ビットシフトは切り捨てになりますが、R_BITRSHIFT という四捨五入できるビットシフトマクロを作ってみましたが、それを聞き分けられるほどの耳ではないし、そもそもtone()関数の周波数は整数指定なので気にしなくてもよさそう。

#include "notes.h"              // 音符の定義データ(音階と音価(長さ))
#include "Gurenge.h"            // 曲データ
#define R_BITRSHIFT(X,S)  ((((X)>>((S)-1))+1)>>1)   // 四捨五入(ROUND)する右シフト(BITRSHIFT)マクロ
const uint16_t F_SCALE[] = { 33488, 35479, 37589, 39824, 42192, 44701, 47359, 50175, 53159, 56320, 59669, 63217 };
    // 基本の12音階(ド~シ:C11からB11まで)の周波数(Hz)(16ビット符号なし整数型に収まる最大が第11オクターブ)

void setup(){
  pinMode(  9, OUTPUT );              // tone()の中から呼び出されるのでなくていいが、明示的に
  pinMode( A1, INPUT_PULLUP );        // タクトスイッチ(PULLUPしてあり、押すとGND=LOWに)
}

void loop(){
  playTone( Gurenge );
  delay(1000);
  while( digitalRead(A1) );           // 演奏が終わったらスイッチが押される(LOWになる)まで待つ
  delay(1000);
}

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

マクロ使わない場合。
        freq = F_SCALE[(note>>8)&0x0f] >> (11-(note>>12));        // 上位4ビットがオクターブ、次の4ビットがピッチクラス(0-11)


タグ:音楽 楽譜 Tone
nice!(0)  コメント(0) 
共通テーマ:趣味・カルチャー
前の10件 | -

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