

写真は改造後のもの。スイッチを後で追加している。
回路は、回路図そのものにした。回路図に現れないところの工夫では、丸ピンICソケット を割ったものを使って、出力の制限抵抗R2,R3 の交換とフィルター用 コイル L1,L2 の交換、それにフィルター用のコンデンサ C4,C6 の容量追加 が できるようにしている。
ちなみに、丸ピンICソケットは
シングルのもの が綺麗に仕上る...のだが、安く買い置きしてある 40pin のものをニッパで割って使っている。
フィルター用のコイルは、LAL02NA470Kにして、フィルター用のコンデンサ には、積層セラミックの 10uF (±10%) を使った。
あと、出力の制限抵抗 R2,R3 は、75 Ωでは音が大きすぎるので、150 Ωにしている。(これでもまだ若干大きい。自分的には、3.3V のとき 220 Ωが良いかも知れない。)
この回路、そのままでは、ISP で書き込みできないという問題点がある。
MISO の先に LC フィルタが付いているため、波形が歪んで ISP の通信ができない。まだステレオ対応はしていないので、R 側の コイルを取ってしまったが、スライドスイッチで LC フィルタを切り離せるようにすべきだ。
L 側は、周波数カウンタにつなげて、クロックのキャリブレーションができるようにしているが、こちらは 無音 (PWM は ON だが 一定値) か 音が小さいときならちゃんと動作するようだ。
(後述するが)CPU 使用率測定機能も 値がばらつくものの有用なことを確認できた。
はまった点
EG の ATTACK の上限処理で 次のようにしていた。
if ((uint32_t)eg.intval + eg.attack >= (255<<8))
これが、(Windows/Linux で動いても) AVR (gcc-3.4.0) では動かない。ちなみに
if ((uint16_t)eg.intval + eg.attack < eg.intval)
は、Windows/Linux で動かない。
if ((uint16_t)(eg.intval + eg.attack) < eg.intval)
これは動くのだが、コード効率も悪そうなので、今は、
uint16_t next = eg.intval + eg.attack;
if (next < eg.intval)
という風にしている。どこが動かないのか すぐにはわからずに、結構はまった。
乗算のコスト
さて、このはまった過程で たかが 8bitX8bit 乗算 が非常に重いことを目のあたりにした。
CPU使用率は、電圧として出てくるので、USB910A の機能で簡単に読み取ることができる。
fcnt result 244625 x4 978500
06:VCC AUTO : 0021772 0x550c 3401.875
02:AIN3 AUTO : 0002304 0x0900 360.000
fcnt result 244625 x4 978500
06:VCC AUTO : 0021772 0x550c 3401.875
02:AIN3 AUTO : 0002336 0x0920 365.000
fcnt result 244625 x4 978500
06:VCC AUTO : 0021772 0x550c 3401.875
02:AIN3 AUTO : 0002304 0x0900 360.000
fcnt result 244625 x4 978500
06:VCC AUTO : 0021772 0x550c 3401.875
02:AIN3 AUTO : 0002192 0x0890 342.500
244625 というのは、PWM 周波数で、AIN3 が CPU使用率をあらわす電圧。こんなふうに多少ばらつく。以下精度が 0.1% のように書いているが、自分の感覚で読み取ったものであり、厳密ではない。
EG なし LFO ありの状態だと CPU 使用率が 6.3 % 前後なのだが、サンプリングレート(8000 Hz) 毎に 8bitX8bit 乗算 fmusu8_8() を入れるだけで15.8 % にまでなるのだ。
元のコードは、
static inline int8_t fmulsu8_8(int8_t a, uint8_t b) {
int16_t v;
v = (int16_t)a * b;
return (int8_t)(v >> 8);
}
で、8bitX8bit をして、上位 8 bit だけ取り出すもの。固定小数点演算の意味で fmulxx としている 。C 的には、16bitX16bit にするしかないので重いわけだ。
アセンブラ化しようと思ったのだが、なかなか面倒。8bit×8bit 乗算(符号なし/符号あり)などを眺めていたのだが、ふと 思い付いて アセンブラでやっていることをC で書いてみた。
static inline int8_t fmulsu8_8(int8_t a, uint8_t b) {
uint8_t i;
int16_t __c , __a;
__c = 0;
__a = a;
for (i=0; i<8; i++) {
if (b & 1) {
__c += __a;
}
b>>=1;
__a <<= 1;
}
return __c>>8;
}
これで 15.8 % が 10.3 % 程度まで下がった。
サンプリングレート毎に行う 8bitX8bit 乗算のコスト が 9.5 % から 4.0 % にまで下がったことになる。乗数が 16bit から 8bit に半減したおかげだ。(ちなみに、16bitx8bit 乗算もよくつかっているが同じコスト)。
おなじようにして、C 版の 16bitX16bit 乗算 のコストを調べたところ 12.1 % になった。被乗数 の方も 32bit に増えているので、3 倍というのは妥当だろう。サンプリング毎のCPUクロック数 は、2000 なので、12.1 % は、242 クロック(平均)ということになる。
LFO/EG のコスト
EG を入れると、DECAY が効いている状態で 11.8 % になった。もとが 10.3 % なので、1.5 % という計算。16bitX16bit と 16bitx8bit 乗算が 1/16 の頻度だから (12% + 4% )/16 = 1.0% 。+それ以外の処理で 1.5 % は妥当なところか。
ステレオ化のコスト
結局 8bitX8bit 乗算 がもうひとつ増えるので、4.0 % 増える計算 。EG も乗算 2 つ余計に入る予定で 1.0 % ぐらい増えるはず。
フィルターのコスト
AVRマイコンによるMIDIシンセサイザーの製作 にある フィルターだと
y[i] = y[i-1] + m * (x[i] - y[i-1])
で サンプリングレート(8000 Hz) 毎に 16bitx16bit 乗算がはいりそうだ。この場合 12.1 % のコストで、16bitx8bitで済ませれば 4.0 % のコスト。
どのように拡張できそうか
ここまで合計すると、EG あり 11.8 % + ステレオ 5.0 % + フィルタ 12.1 % で 合計 28.9 % 。
このバランスだと サンプリング周波数を 倍の 16kHz にしてもいけそうだが ... フィルタを凝れば 8KHz でも厳しいか。もともとフィルターを凝るのが目的なので、サンプリングレートをあげるのは二の次にして、進めていきたい。
ところで、FM音源化について考察しておく。各オペレータには、EG が付く。EG あり LFO なしだと だいたい 10.5% ぐらいだろう。これに対して 16bitx8bit 乗算が1つ余分に付く見込みで、合計はだいたい15% 。あと最終的な出力のボリューム 8bitx8bit 乗算が 2 つほど必要なので 8% がさらに必要。計算上は 6オペレータ分はいるが、たぶん無理で 4 オペレータが妥当そうだ。
ホスト側のプログラム
ホストには、USB910A を使う。以前使った コードを改造してi2c_send()/i2c_recv()を作り、sndtest.c が使えるようにした。
USB910A の FW は、version 0.5 ではうまくない。ちゃんとつながるのだが、いったん CLOSE すると 再接続ができない。、version 0.4 を使えば ちゃんと動く。
0.5 は、機能的には 0.4 と変わらないのだが、ベースコードを最新にしていて コードが 小さくなっている。原因はボチボチ調べることにして、とりあえず、version 0.4 を使うことにする。
試聴
まずノイズについて、USB の 5V で動かすと たいへんひどい。本体 PC でエディットするときのカーソル移動がノイズになってあらわれるぐらい。3.3V もあまり期待していなかったのだが ... こちらは全然ノイズが聞こえない。これぐらいなら十分実用になりそうだ。
スプリアスについて
正弦波の場合、高調波が(あまり)ないので、結構高音までいける。
三角波も おなじような感じ。それに反して のこぎり波は、ちょっと高音になると スプリアスのために、変な音がまじってくる。
逆に 正弦波でかなり低音にすると、(時間軸の)量子化ノイズのために、へんな音がまじってくる。
サンプリングレートを倍にすれば、1 オクターブ上まで OK ということになるのだろう。ただし、低音の問題は解決しない。
PC(の 8bit 8Khz) と比較して
なんだか音がはっきりしている気がする。余計なものが一切なくダイレクトにヘッドホンを駆動しているせいかも知れない。同じコードで同じ波形を作っているので、それぐらいしか違いはわからなかった。
判断するだけの経験がないので、あまり具体的にはかけなかった。興味があるかたは、ぜひ自作してみてほしい。ここまでのコードは、→ i2csnd-0.2.tar.gz 。
おわりに
これで、実機の環境も Windows/Linux に追いついたことになる。これをベースに次は完成向けて機能を作りこんでいこうと思う。その際 個別機能の構造体 VCO とかは、ひとつの構造体(名前は OPERATOR ?)に押し込めて、整理しようと思う。こうしないと評価環境でのマルチチップシミュレーションが作りづらい。実機でのポリフォニックは(あまり)考えていないが、FM音源版の準備という要素もある。