コンソールの音
N7 の報告書。作業の後に書いた。
走行スタンプ: 2026-09-06、rustc 1.97.1。ピン留め: 2a03 78a1f6e
(v2a03-micro と、新しい v2a03-dac をパスで)、2c02 0b25e83、6502
8b9e0b5、ntsc-crt v0.2.4、nes-bus v0.1.1。cargo test -p nes-console: 11
テスト (N5 の 6 個、N6 の 2 個、ここの 3 個)。計画は docs/n7-plan.md、
先に書いた。
コンソールに音が付いた: 2A03 のコードを二つの DAC に通し、回路図が与える とおりの NES-001 の音声段に通し、48 kHz へのリサンプラに通す。blargg の 四つのミキサー ROM が打ち消し合うことで保たれ、同じ ROM を実機で録った彼の 録音と並べて測った。AUDIO_OUT のスコープ記録は、これまでどおりベンチの項目の ままだ。
回路図から読んだ段
RetroTechCollection の NES-001.pdf (2026-09-06 に読んだ)、U6 のそばの音声の 一角: AD1 (ピン 1) と AD2 (ピン 2) はそれぞれ 100 オームの R4 と R3 で プルダウンされている。AD1 は R7 20K を通り、AD2 は R8 12K を通り、 カートリッジの AUX_AUDIO_IN は R9 20K を通って、一つのノードで出会う。 C23 1 uF がそのノードを、74HC04 インバータである U9E のピン 11 に結合し、 その出力 (ピン 10) から入力へ R6 47K が戻り、R6 に並列に C21 220 pF が入る。 C20 220 pF は出力から接地へ。出力は AUX_AUDIO_OUT であり、FC1 39 uH と 接地への C4 0.01 uF を通って、ジャックの AUDIO_OUT になる。
wiki のミキサーの表をこれに照らして読むと、二つのことが落ちてくる。表の
どちらのグループにもある「+100」は R3/R4 だ: どちらの DAC も、基板の
プルダウンへ流し込む電流源になっている。そして二つのグループの分子 95.88 と
159.79 は 12/20 の比、つまり基板の加算抵抗の比なので、表の
pulse_out + tnd_out は表の単位で見た加算ノードの電流であって、二本のピンの
ことではない。だから DAC の表は元のままだ (nesdev のページ、書き下ろし。
いまは v2a03-dac なので、コンソールはスイッチレベルのクレートを通らずに
そこへ届く。v2a03-sim は A3 のためにそれを mixer として再エクスポート
する)。そしてここでの段は、そのノードの電流に基板がすることだ:
1/(2 pi (R7 || R8) C23) の一次ハイパス、21.2 Hz (音声を持たない
カートリッジではカートリッジ入力は開放)、インバータの符号を伴う
R6/R7 = 2.35 のゲイン、そして 1/(2 pi R6 C21) の一次ローパス、15.4 kHz。
ジャックの LC は 255 kHz にあり、入れていない。
模型に入れず、記録した: インバータの有限な開ループ利得 (閉ループは理想と して扱う)、そのレール、出力抵抗に対する C20、そして表の絶対的なボルト値。 最後のものは、ベンチの記録が与えるスケール係数が一つある、ということだ。 wiki の 90 Hz と 440 Hz というハイパスの角は、この回路図ではピンとジャックの 間に無い。それを作っているものは何であれジャックの先にあり、スコープは ジャックまでを見る。
経路
nes_console::sound::Sound は、CPU の半サイクルごとに五つのコードを
与えられる (APU は半サイクルに一度進む。サンプルは 39,375,000/11 Hz に
一つ、つまりサブキャリアそのもので、その数は CPU の半サイクル数と等しく
保たれる)。段はそのレートでの f64 の一次セクション二つ。リサンプラは
20 kHz で切った Blackman 窓の sinc で、両側に四周期、サンプルの 1/64 刻みで
表にしてあり、厳密な有理数の出力時刻で評価する。run-rom は WAV=out.wav
でそれを書く。音を入れると、コンソールは 1 コアで毎秒およそ 80 フレーム
(実時間の 1.3 倍) で走る。音なしでは 125 から 140 だ。
ゲート
tests/sound.rs、3 テスト。ダイデータもゴールデンも読まない。ROM と録音は
nes-test-roms のチェックアウトから来る (無ければ名指しで SKIP、ffmpeg が
無ければ録音のぶんも)。
段は回路図の算術をやる。 ステップは反転して出てきて、その大きさは ゲインに 1 パーセントの精度で合い、(R7 || R8) C23 (7.50 ms) ごとに e^-1 倍に 減衰して 2 パーセントまで落ちる。200 Hz の音に対して 10 kHz の音は、二つの 角の一次の積のぶん減衰する: 実測 0.8401、素子値からは 0.8433 (差は 10 kHz でのリサンプラ自身のロールオフ)。これはコードを上に書いた値に照らして保つ もので、何か本物に照らすものではない。
blargg のミキサー ROM が打ち消し合う。 四つそれぞれをコンソールで 1,200 フレーム走らせる。ビープはその形 (998.8 Hz の音が 300 ms、その両側は 無音) で見つけ、ビープの間にあるテスト区間を、ビープに照らして 100 ms の窓で 測る。許容差は計画から: どの窓でもビープの RMS の 5 パーセント以下 (DMC の一段はおよそ 2 パーセント)、noise の ROM は自分の音だけで保つ。
| ROM | コンソール: 最悪の窓、ビープの RMS 比 | 音 | 録音 (実機): RMS | 音 |
|---|---|---|---|---|
| square | 2.4% | 1.5% | 6.1% | 6.0% |
| triangle | 2.7% | 1.3% | 3.0% | 1.7% |
| noise | 21.1% (設計上フェードする) | 3.7% | 19.0% | 3.3% |
| dmc | 3.1% | 1.4% | 5.6% | 5.4% |
四つとも保った。MUTATE_SOUND=1 は wiki 自身の線形近似でミックスし、
square で 34.4 パーセント、dmc で 33.6 を読む: 残差で赤だ。(これに専用の
変数がある理由はこうだ: 2A03 のラングは MUTATE を読んで当てはめた APU の
位相を差し替え、コンソールレベルの MUTATE=1 はチップを変異させるので、
その殻はビープを一度も鳴らさなくなる。ここでの最初の赤はミキサーのものでは
なくビープ探しのもので、それは何も証明しない類の赤そのものだった。)
録音を並べて。 各 ROM を実機で録った blargg の mp3 を 48 kHz に復号し、 同じコードで測った (ビープはファイルのフルスケール 0.359 に座っているので、 ファイルは正規化されている。比のほうはされていない)。triangle と noise の 数字はコンソールのものと 1 パーセントの端数まで一致する。表の triangle と noise の重みは、DMC の一段が分解できる範囲で、実物のチップがやっていること と同じだ、ということだ。square と dmc の録音はコンソールの残差の二倍、 3 に対して 6 パーセントを抱えていて、その残差は音の形をしている (音の数字が RMS の数字と等しい): 実物のパルス DAC と実物の DMC DAC は、表が blargg の 逆関数から離れている以上に、表の曲線から離れている。彼はその逆関数を自分の ハードウェアで較正した。記録はしたが、保っていない: 録音は実物の段、実物の 部屋、そしてコーデックを抱えており、実物のパルスと DMC の曲線が何であるかは、 下にあるスコープの問いだ。
ベンチに残るもの
- 実機のコンソールの AUDIO_OUT を、映像のときと同じように終端して、四つの ミキサー ROM と blargg の apu_test の下で、1 MSa/s 以上で。同じ リサンプリングの後、スケール係数を一つ当てはめて波形として比べる。その 比較の許容差は、記録が存在してから、かつ採点する前に述べる: スケール後の ビープの波形はビープの RMS の 5 パーセント以内、打ち消しの残差は録音自身の ものからビープの 2 パーセント以内。
- 記録があの 6 パーセントを彼らのものだと示すなら、パルスと DMC の DAC の 曲線: コードごとの実物のピンのレベルで、2C02 の DAC レベルを転記したときの ように、wiki の表を実測で置き換えることになる。
- N6 と N5 のベンチの項目は、そのまま。
ビルド時に nes/docs/n7-report.md から取り込んでいる。写しはリポジトリの一つだけだ。報告には私たちの間の言葉がいくつか残っている: 報告書で使われる言葉。