音楽を演奏したい - Digispark編 [Arduino]
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;
}
コメント 0