今回はプログラミングで基本的なフィルターを作ってみます。
【前回までの記事はこちら】
第1回 デジタルエフェクターを作ってみる
第2回 何を作るか決めました の巻
第3回 オーディオ処理の心臓部はコイツ の巻
第4回 ソフトウェア内部構成を決める の巻
第5回 回路図を描いてみる の巻
第6回 基板ができたよ。プログラムで息を吹き込む準備 の巻
第7回 基板をケースに仮組みしてみる の巻
ちょっと専門的な話になってしましますが、デジタルエフェクターを作る際、オーディオを加工するための信号処理はソフトウェアで実装しますが、どのプログラミング言語で実現するのかが重要になります。
最近のスペックの高いDSP(デジタルシグナルプロセッサ)であればC言語でリアルタイムに信号処理することもできますが、ローコストなdsPICでは、C言語で沢山の処理を詰め込むのは難しいです。
そこで、ここでは、アセンブラで全部の処理をプログラミングしようと思います。
アセンブラとは、機械語とかマシン語とか呼ばれているものです。
私は25年くらい前からMSXのCPUであるザイログ社のZ80のマシン語に目覚めてしまったので、高級言語よりもアセンブラで組むほうが好きだったりします(^^)
dsPICのDSPエンジン
dsPICには、掛け算と足し算、つまり積和演算を1サイクルで行うための命令が搭載されています。
積和演算とは
アキュムレータ ← (16ビット×16ビット)+アキュムレータ
という積和演算を1サイクルで完了することができます。
演算結果を格納するアキュムレータは、40ビット幅なので、オーバーフローを起こさずにダイナミックレンジの広い演算が可能です。
また、dsPIC30にアキュムレータはACCA、ACCBの2個あります。
最終的な出力を16ビットのレジスタに格納する時に、「固定小数点位置をシフトさせてレジスタに格納」までの動作を1サイクルで行えます。
代表的なDSP命令
・MAC命令
acc = acc + x * y
acc = acc + x * x
・MPY命令
acc = x * y
acc = x * x
・MSC命令
acc = acc - x * y
acc = acc - x * x
x、yはレジスタ。accはアキュムレータ。
デジタルフィルタの設計
さて、今回のメインですが、デジタルフィルタを作ります。
歪み系のエフェクターの基本構成として、フィルターは欠かせません。
ただ、デジタルフィルタといっても身構える必要はなく、簡単なレシピがあります。
RBJ Audio-EQ-CookbookというRobert B Johnson氏が作成した、オーディオ用2次フィルタの設計時に使う公式集を使うと、面倒な計算は不要でサクッとデジタルフィルタを設計することができます。
RBJ Audio-EQ-Cookbook を使ってIIRフィルタを設計
RBJ Audio-EQ-Cookbook
http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt
Robert B Johnson氏が作成した、オーディオ用2次フィルタの設計時に使う公式集。
EQクックブックで使用する信号処理実行部のフィルタの式は下記の通りです。
つまり、今回の入力音(AD値)と前回の入力音、前々回の入力音、前回の出力音、前々回の出力音さえあれば、あとは適切な係数knをそれぞれに掛け算してやり、合計を求めたものが今回の出音になるという式です。
重要なのは係数の求め方です。
係数の求め方
実は、これもさほど面倒ではありません。
基本的に、5つの係数を求めるための式は、下記の通り。
5つの係数:
k0 = b0/a0
k1 = b1/a0
k2 = b2/a0
k3 = -(a1/a0)
k4 = -(a2/a0)
いきなり、axとかbxとか、出てきちゃいましたが、これらは6つの中間係数で、フィルタータイプ毎に異なります。
詳しくはEQクックブックに、公式がそのものズバリで掲載されていますので、ここでは割愛しますが、例えば低音を通して高音を落とすローパスフィルタの場合、
b0 = (1 – cos(ω0))/2
b1 = 1 – cos(ω0)
b2 = (1 – cos(ω0))/2
a0 = 1 + alpha
a1 = -2*cos(ω0)
a2 = 1 – alpha
ω0 = 2π×周波数/サンプリング周波数
alpha = sin(ω0)/(2*Q)
これで、求めることができます。
フィルタータイプを決めたあと、フィルターを作用させる周波数とフィルターのQ値を決めれば係数を求めることができます。
ちなみに、HPFの場合は、
b0 = (1 + cos(ω0))/2
b1 = -(1 + cos(ω0))
b2 = (1 + cos(ω0))/2
a0 = 1 + alpha
a1 = -2*cos(ω0)
a2 = 1 – alpha
LPFとはちょっと式が異なります。
けど、実行部で必要な信号処理の式は変わらずに、係数だけを入れ替えるだけで、LPFとHPFが切り替わるなんて、面白いですよね。
アセンブラで実際に記述
dsPICで実際にコーディングしてみるとどんな感じかご覧ください。
もちろん、お手製のアセンブラのシートを手元にコーディングします。
係数メモリ確保
式で使っている入力音xと、出力音y、係数kのエリアを確保します。
.section .xbss, bss, xmemory
XY: .space 10 ; x0, x1, x2, y1, y2の順に5個格納
.section .ybss, bss, ymemory
K: .space 10 ; k0, k1, k2, k3, k4の順に係数5個格納
信号処理実行部
フィルタの信号処理はMPYから始まる5行です。
.section .text
MOV W0, XY ; x0格納
MOV #XY, W8
MOV #K, W10
CLR A, [W8]+=2, W4, [W10]+=2, W5 ; アキュムレータをクリア
; アドレスW8が指すメモリの内容x0値をW4に格納
; アドレスW10が指すメモリの内容k0値をW5に格納
; アドレスW8をインクリメント
; アドレスW10をインクリメント
MPY W4*W5, A, [W8]+=2, W4, [W10]+=2, W5
MAC W4*W5, A, [W8]+=2, W4, [W10]+=2, W5
MAC W4*W5, A, [W8]+=2, W4, [W10]+=2, W5
MAC W4*W5, A, [W8]+=2, W4, [W10]+=2, W5
MAC W4*W5, A, [W8]+=2, W4, [W10]+=2, W5
最後の5行が実際の実行部の数式です。
<出力データの取り出しとXYメモリのシフト>
MOV [W8-#10], W1 ; X[1] -> X[2]
MOV W1, [W8-#8]
MOV W0, [W8-#10] ; X[0] -> X[1]
MOV [W8-#6], W0 ; Y[1] -> Y[2]
MOV W0, [W8-#4]
; Qフォーマットに応じてシフト
SAC.R A, #-1, W0
MOV W0, [W8-#6] ; Y[0] -> Y[1]
おわりに
如何でしたか?
デジタルフィルタと言っても係数を設計するレシピがあるので、意外と簡単に作ることができると思います。
もちろんCで組めばもっと簡単に実現できると思います。
私は、フィルター以外にも様々な信号処理を組み込みたいと思っているからアセンブラでやっていますが、まずは作ってみることが何よりも重要と思いますので、Cでも別の言語でも試せるもので是非、フィルターで遊んでみてください。
面白いですよ!
Comment On Facebook