PWMDAC_Synthライブラリにおける音程を決める式

PWMDAC_Synthライブラリのソースには、さまざまな計算式が、主にマクロで記述されています。

ここでは、音の高さを決めるマクロ、PHASE_SPEED_OF() について解説します。

#define PHASE_SPEED_OF(note_number) ( \
pow( 2, ((double)note_number - 69)/12 + BitSizeOf(unsigned long) ) \
* PWMDAC_NOTE_A_FREQUENCY * 0xFF * 2 / F_CPU )

これは「位相速度」、すなわち単位時間あたり音波の位相をどれくらい進めるかを計算するマクロです。この式では、位相は最終的には unsigned long の整数値で表わされます。整数値がオーバーフローして一周すると 0 == 2π [rad] の位相ということになります。

単位時間

ここで「単位時間」は、PWMのパルス幅を更新するための割り込みサービスルーチン ISR() が呼ばれる周期です。最高速の位相基準PWM(Phase-correct PWM)の設定にすることにより、カウンタがCPUクロックごとに 0, 1, 2, … ,254, 255, 254, … 2, 1, のように往復しながら繰り返されることで、0 → 254の255個(0xFF個)、255 → 1 の255個、計510個ごとに割り込みがかかります。つまり、

255 * 2 / CPUクロック周波数

ということになります。CPUクロック周波数は F_CPU としてすでに定義済みで、16MHzになっています。これにより

0xFF * 2 / F_CPU

という、割り込み周期(単位時間)を表す式ができました。

音階の周波数

PHASE_SPEED_OF(note_number) の引数 note_number は MIDI のノート番号(0 ~ 127)ですが、これをもとに周波数を決める式が MIDI Tuning Standard として次のように決められています。

2(note_number - 69)/12・440Hz

これは、オクターブが周波数2倍、平均律に基づく半音がその1/12乗(12乗根)であることに由来します。69 は、A=440Hz の音階に対応するノート番号です。

440Hz を PWMDAC_NOTE_A_FREQUENCY として別途 #define し、これを使ってマクロで書くと

pow( 2, ((double)note_number - 69)/12 ) * PWMDAC_NOTE_A_FREQUENCY

のようになります。指数部分は12で割る段階で実数でなければならないので(double)でキャストしています。

周波数を位相速度へ

周波数は1秒当たりのサイクル数なので、これに単位時間を掛けると、その単位時間あたりのサイクル数に変わります。

pow( 2, ((double)note_number - 69)/12 ) * PWMDAC_NOTE_A_FREQUENCY * 0xFF * 2 / F_CPU

単位時間は音波の周期よりもはるかに短いため、サイクル数は1以下の小数になるはずです。これを位相速度にするには unsigned long の最大値+1を掛ける必要があります。unsigned long は 4 バイトなので、4 * 8 = 32 ビット。つまり unsigned long の最大値+1は、232 です。

が、当然のことながらこの数値は大きすぎて unsigned long で表すことはできません。ではどうするか?

ここで音階の周波数計算で使っている pow(2, …) に注目してください。

232 を掛けるということは、pow(2, n) の n に 32 を足すことと同じであることに気づきましたか?

そうです。
指数 n のところにビット数 32 を足すだけでよいのです!

これで、以下のように計算式ができあがりました。

#define BitSizeOf(type) (8 * sizeof(type))
#define PHASE_SPEED_OF(note_number) (pow( 2, ((double)note_number - 69)/12 + BitSizeOf(unsigned long) ) * PWMDAC_NOTE_A_FREQUENCY * 0xFF * 2 / F_CPU )

位相速度の活用

ではこの「位相速度」はどう活用されるか?

位相速度は unsigned long(32ビット)ですが、波形テーブルのインデックスはbyte(8ビット)です。これを変換するには 24 ビット右にシフトする必要があります。

変換できれば、あとはインデックスをもとに波形テーブル(値もインデックスも0~255の範囲)から現在あるべきパルス幅を読み出し、パルス幅を決めるレジスタを設定するだけです。この処理を割り込みサービスルーチンの中で行うことで、一定の単位時間でパルス幅を更新し続けることができるのです。
6重和音の場合は、その数だけこのパルス幅を合算します。
エンベロープでボリュームがコントロールされる場合は、そのボリュームを掛けたりします。

割り込みサービスルーチンでは、あまり時間のかかる処理を行うとそれ以外の処理を行う暇がなくなってしまうので、割り算などクロック数を食うような処理はできません。しかし、クロック数を最小限に抑えれば、6重和音も平気で出せるようになるのです。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です