音楽を演奏したい - Wave Table編 [Arduino]
今回は、正弦波を出してみたいと思います。

・仕組み
こんな感じの添え字が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に興味がなくなってしまったので、覚えとしての和音なしでのスケッチ。

・仕組み
こんな感じの添え字が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毎に振幅減衰(調整可)
}
2020-11-28 06:33
nice!(0)
コメント(0)
コメント 0