YMF825のEQを使う
はじめに
今回は,備忘録的にYMF825のEQの使い方に関して解説します.
EQ以外のYMF825自体の使い方や,EQの正体である双2次フィルタについてはあまり深入りしないのであしからず…
環境はYMF825+ ArduinoUnoとなっています.
他のマイコンボードなどを利用する場合は適宜変更してください.
YMF825とは
YMF825(SD-1)はヤマハの4オペレータ16ポリのFM音源チップです.
この基板とArduinoなど各種マイコンボードを利用することで簡単にFM音源を利用できます.
このチップについてのヤマハのページには,3Band-EQ搭載のFMシンセサイザと説明がありEQがうりであるようです.
しかし,私が調べた範囲ではYMF825のEQについて解説している記事が見つからなかったため備忘録的に文章化しておこうと思います.
YMF825のEQ
何はともあれ,Githubにある資料を読んで仕様を調べましょう.
EQについての解説は『manual/fbd_spec2.md』の一番下にあります.
YMF825のEQは何者なのか
資料を見ると何やらややこしそうな図と数式が出てきました,
これは3つの双2次フィルタが直列につながれた図と,双2次フィルタの伝達関数の式です.
双2次フィルタ(Biquad-Filter)とは
BiquadFilterのダイアグラムは以下の通りです.
a1からb2までの係数を変えることでローパスフィルタ,ハイパスフィルタ,バンドパスフィルタ…などいろいろなフィルタを作ることができます.
YMF825のEQの図を見ると書き方が微妙に違うものの,これが3つ繋がっているのが分かります.
CEQx0 ⇔b0 , CEQx1 ⇔b1 , CEQx2 ⇔b2 , CEQx3 ⇔a1 , CEQx4 ⇔a1と対応しています.
YMF825のEQを利用するには,a0からb2までの係数を計算してそれをYMF825のEQに書き込めばよさそうです.
フィルタの設計
YMF825のEQが3つの双2次フィルタであることが分かりました.
ここからフィルタの設計をしていきましょう今回は3つフィルタのうち1つだけを使って単純なローパスフィルタを作ってみます.
ほかのフィルタを試したい人はCookbook formulae for audio equalizer biquad filter coefficientsなどを参考にしてください.
計算式
双2次フィルタを使ってローパスフィルタを作るためには
カットオフ周波数(fc)
Q(q)
サンプリング周波数(fs)
の3つの情報が必要です.
カットオフ周波数はフィルタがかり始める周波数です.
Q=レゾナンスと思って大丈夫です(厳密には異なる).
サンプリング周波数1秒あたりのサンプル数でYMF825では48000固定です.
これらの情報を使って実際に各係数を計算します式は以下の通りです.
$$
\omega = \frac{2\pi f_c}{f_s}\\
\alpha = \frac{\sin(\omega)}{2 \times q}\\
$$
$$
a_0=1+\alpha\\
a_1=\frac{2\cos(\omega)}{a_0}\\
a_2=\frac{-(1-\alpha)}{a_0}
$$
$$
b_0 = b_3=\frac{1-\cos(\omega)}{2a_0}\\
b_2= \frac{1-\cos(\omega)}{a_0}
$$
式の注意点
a1,a2の式はYMF825の仕様合わせたものになっています.
一般的なa1,a2は符号が逆であることが多いです.このあたりは資料によってもまちまちなので注意してください.
計算する
今回は,これらの計算をArduino上でリアルタイムにするのではなく,あらかじめ値を計算しておき配列として利用できるようにします.
具体的には,255段階のカットオフを持つローパスフィルタの係数を計算します.
(11Hzづつカットオフ周波数を変化させる.カットオフ周波数の値は100~2905Hz)
Q=4としました.
計算にはExcelを使いました.
参考までにエクセルのファイルを置いておきます.
もしかしたらここもプログラムを書いて再利用しやすいようにした方が良いかもしれません…
YMF825へ書き込める形式に変換
ここまででフィルタ係数を計算することができました.
ここからこの値をYMF825に書きこんでいきます.
書き込みの仕様は以下の通りです.
各フィルタの係数を書き込むアドレスはそれぞれ
#32,#33,#34各係数は符号1bit,整数部3bit,小数部20bitの3byteの2の補数表現で表す.
各アドレスにBurst Writeを利用してCEQx0から順にCEQx4までの3×5byteの情報を書き込む
補数表現への変換
前述の通り今回は先に係数は計算してしますのでpythonで行ってしまいましょう.
以下に小数であらわされた係数の配列をYMF825仕様の2の補数表現変換し2進数にするプログラムを用意しました.
(データ量などの観点から本当は16進数にした方がよいと思われます.)
変換方法について詳しく知りたい方はコードを参照したり,2の補数表現で検索してみてください.
Burst Writeで書き込む
Burst Writeとはその名の通りSPI通信において1度にデータを書き込んでしまうものです.EQを使う場合の具体的な手順は
SSpinをLowにする
アドレスにデータを書き込む(15byte)
SSPinをHIGHにする
となります.データが15byte未満だと無効となるので注意が必要です.
arduinoを使う場合,公式のソースの中にあるif_write()関数が利用可能です.
Arduinoへの実装
フィルタの係数,書き込むための形式変換ができました!
やっとArduinoへの実装です.公式ソースのymf825board_sample1.inoが使えることが前提となっています.
まずは,計算した係数を2の補数表現に変換したものの配列を作成します.
(2進数にしてしまったため24bitのデータに対してuint32を使っていて上位8bitを無駄にしています.16進数で書いた方がFlashの節約になりますね…)
const uint32_t b0[256] PROGMEM ={ 0b000000000001111101100100,………};
const uint32_t b1[256] PROGMEM ={ 0b000000000000000001011001,………};
const uint32_t a1[256] PROGMEM ={ 0b000111111111000111101010,………};
const uint32_t a2[256] PROGMEM ={ 0b111100000000110101100010,………};
今回はローパスフィルタのためLPFという名前の関数にします.
void LPF(int cutoff){
//カットオフを0~255に
if(cutoff >255){
cutoff = 255;
}
if(cutoff <= 0){
cutoff = 0;
}
// 5つの24ビットデータを8ビットに分解するので15byteのデータの配列を作成
uint8_t result[15];
// それぞれの係数読み出し
for (int i = 0; i < 5; i++) {
const uint32_t* baseAddress;
switch(i) {
case 0: baseAddress = b0; break;
case 1: baseAddress = b1; break;
case 2: baseAddress = b0; break;
case 3: baseAddress = a1; break;
case 4: baseAddress = a2; break;
}
uint32_t data = pgm_read_dword_near(baseAddress + cutoff);
//1byte事に分割
result[i * 3] = (data & 0x00FF0000) >> 16; // 上位8ビット
result[i * 3 + 1] = (data & 0x0000FF00) >> 8; // 中間8ビット
result[i * 3 + 2] = data & 0x000000FF; // 下位8ビット
}
//Burst Writeで書き込み
if_write(0x22, &result[0], 15);
delay(10);
}
動作
以上の関数を使ってフィルタを動作させることができました.
ただし,現状だと若干ぷつぷつするので,シンセのフィルタとしてウニウにさせたいのなら改良が必要ですね…
この記事が気に入ったらサポートをしてみませんか?