音楽を演奏したい - 波形確認編 [Arduino]
ひさしぶりにyoutubeに動画をアップしてみました。
ライン入力でAudacityというソフトで取り込んでみました。
このソフトで音声をキャプチャーしているところを、スクリーンキャプチャーして動画にまとめました。
波形が見えると気持ちいいです。
includeする "notes.h"は以前のままです。
ライン入力で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)
}
コメント 0