The other last mile: channel values → sound另一條最後一哩:聲道值 → 聲音

Deep dive #6 · the audio counterpart to the video chain — still a roadmap, not a build. Our switch-level 2A03 is bit-exact, but it stops at five channel values — two pulses, a triangle, noise, a DMC sample. A real NES doesn't output numbers; it drains a little current out of two pins, mixes them through a famously non-linear network, forces a logic inverter to act as an amplifier, and sends line-level audio to the TV's speaker. This maps that whole stretch — the on-die non-linear DAC, the board's resistor mix and 74HCU04 amp, the filters — and how we'd model it: tap the DAC gate nodes, run a 1.789 MHz behavioural sidecar, and write our own DSP (no borrowed audio libraries). One hardware-engineer consult; honest caveats throughout.

深入專文 #6 · 視訊鏈的音訊對應 —— 一樣是路線圖、不是實作。我們的開關級 2A03 是 bit-exact,但它停在五個聲道值 —— 兩個 pulse、一個 triangle、noise、一個 DMC 樣本。真的 NES 不輸出數字;它從兩支腳吸一點電流出去、經一個著名的非線性網路混音、逼一顆邏輯反相器當放大器,再把 line-level 音訊送到電視喇叭。這篇把那整段標出來 —— 晶片內非線性 DAC、主機板電阻混音與 74HCU04 放大器、濾波器 —— 以及我們會怎麼模擬:接出 DAC 閘極節點、跑一個 1.789MHz 行為 sidecar、然後自己寫 DSP(不借用音訊函式庫)。一次硬體工程師諮詢;誠實但書貫穿。

S1a · deep dive #6roadmap2026-07-20

The whole chain整條鏈Two pins, mixed, filtered, amplified兩支腳,混音、濾波、放大

The 2A03 exposes audio on two pins, and — unlike a normal DAC — they don't output a voltage. They are open-drain NMOS current sinks: they only pull current toward ground. The board turns that into a voltage, mixes the two, and amplifies it. As with video, the RF path is optional — the composite/AV output is plain line-level baseband, so we can tap it directly.

2A03 在兩支腳上輸出音訊,而且 —— 跟一般 DAC 不同 —— 它們不輸出電壓。它們是開漏極 NMOS 電流下拉:只把電流拉向地。主機板把它變成電壓、把兩支混起來、再放大。跟視訊一樣,RF 路徑是選配 —— composite/AV 輸出就是純 line-level baseband,可以直接接。

2A03 channel values → speaker (RF audio subcarrier skipped) ON-DIE BOARD 5 channels (digital) Pulse1·2 · Tri · Noise · DMC ← we stop here Pin 1 (Pulse) Pin 2 (TND) open-drain NMOS sinks 100Ω pull-ups R7·R8 → +5V R-mix + 1µF 20k·12k · C21 HPF 74HCU04 amp U7 + 100k fb (R12) RC filter 440Hz/14kHz → RCA tap the DAC gate nodes → 1.789 MHz sidecar (non-linear DAC + IIR filters) → our own decimator → 48 kHz
The audio chain. Two open-drain pins become voltages via 100 Ω pull-ups, mix through 20 k (pulse) + 12 k (TND), high-pass through a 1 µF cap, get amplified by a logic inverter abused as an op-amp, then filtered to line level.音訊鏈。兩支開漏極腳經 100Ω 上拉變成電壓、經 20k(pulse)+ 12k(TND)混音、經 1µF 高通、被一顆當 op-amp 用的邏輯反相器放大,再濾到 line level。

The famous non-linear mix著名的非線性混音Why louder isn't proportional為什麼越大聲不成正比

There is no R-2R DAC. Each channel's level opens a bank of parallel NMOS-to-ground switches, so a bigger digital value lowers the pin's effective resistance to ground. With the board's 100 Ω pull-up to 5 V that forms a voltage divider — and because conductances (not resistances) add in parallel, the output is a compressive, non-linear curve: the step from 0→1 is loud, the step from 14→15 barely moves. Blargg fit it precisely (output normalised 0…1):

這裡沒有 R-2R DAC。每個聲道的音量開一排並聯的對地 NMOS 開關,所以數位值越大,腳對地的等效電阻越小。配主機板 100Ω 上拉到 5V 就成了分壓 —— 而因為並聯相加的是電導(不是電阻),輸出是一條壓縮式、非線性曲線:0→1 那一步很大聲,14→15 那一步幾乎不動。Blargg 精確擬合(輸出正規化 0…1):

pulse_out = 95.88 / ( 8128 / (pulse1 + pulse2) + 100 ) // Pin 1
tnd_out = 159.79 / ( 1 / (tri/8227 + noise/12241 + dmc/22638) + 100 ) // Pin 2

The 100 in each denominator is that external 100 Ω pull-up; the other constants are the NMOS array's per-process on-resistances. Two consequences fall straight out: channels are not independent (two pulses at half volume ≠ one at full), and the whole thing is quieter and warmer than a naive linear sum.

每個分母裡的 100 就是那顆外部 100Ω 上拉;其餘常數是 NMOS 陣列的製程導通電阻。兩個結果直接掉出來:聲道不獨立(兩個半音量 pulse ≠ 一個全音量),而且整體比天真的線性相加更小聲、更溫暖。

Pulse mix: non-linear (real) vs linear (naive) pulse1 + pulse2 (0 … 30) output (0 … ~0.26) 0102030 linear (naive) non-linear (real) 0→1 is a big step… …14→15 barely moves
The pulse mix compresses: early steps are loud, later steps flatten. A naive linear DAC (dashed) would be wrong at every point but the endpoints.Pulse 混音會壓縮:前面的步階大聲、後面的步階變平。天真的線性 DAC(虛線)除了端點之外每一點都錯。

The board circuit主機板電路A logic gate pretending to be an op-amp一顆假裝成 op-amp 的邏輯閘

On an NES-CPU-11 board, after the two pins:

在 NES-CPU-11 板上,兩支腳之後:

Doing it ourselves自己做A 1.789 MHz sidecar, our own DSP1.789MHz sidecar,自己寫的 DSP

As with video, we'll write the DSP ourselves — not blargg's Nes_Snd_Emu, not a copy of Mesen's filters. The whole analog chain reduces to a handful of first-order IIR filters at the CPU clock:

跟視訊一樣,我們會自己寫 DSP —— 不用 blargg 的 Nes_Snd_Emu、不抄 Mesen 的濾波器。整條類比鏈化簡成 CPU 時脈上的幾個一階 IIR 濾波器:

// per CPU cycle (Fs = 1.789773 MHz) — read the 5 channel values, mix non-linearly
s = 95.88/(8128/(p1+p2)+100) + 159.79/(1/(tri/8227.+noise/12241.+dmc/22638.)+100);

// analog board = cascaded first-order IIR:  y = a0·x + a1·x1 + b1·y1
s = hpf(s, 90);      // 1µF coupling  (DC block)
s = hpf(s, 440);     // NES amp/output net (Famicom: a single ~37 Hz)
s = lpf(s, 14000);   // RC output low-pass

// down-sample 1.789 MHz → 48 kHz (ratio 37.286, NON-integer). Never decimate raw —
// a square wave has infinite harmonics and would alias horribly. Band-limit first:
out48k = polyphase_FIR_decimate(s);   // FIR LPF (cut ~20 kHz) then fractional resample

Build order, each step audible: non-linear DAC + naive resample (you already hear SMB, a bit harsh) → add the three IIR filters (it warms up) → replace the resampler with a proper band-limited polyphase decimator (the aliasing hiss goes away) → optional expansion-audio mix.

建置順序,每步都能聽:非線性 DAC + 天真重採樣(已能聽到瑪利歐,有點刺) → 加三個 IIR 濾波器(暖起來) → 把重採樣換成正規帶限 polyphase decimator(混疊嘶聲消失) → 選配擴充音訊混音。

Where it plugs in接在哪The AprVisual integration — a planAprVisual 整合 —— 一個計畫

  1. Tap the DAC gate nodes. In the switch-level 2A03, find the digital lines from the five channel output latches to their NMOS DAC gates; each CPU cycle, read the five integers (0–15, or 0–127 for DMC). That is the clean cut between "bit-exact digital" and "analog behavioural" — no SPICE for the 100 Ω divider.
  2. 接出 DAC 閘極節點。在開關級 2A03 裡找五個聲道輸出 latch 到 NMOS DAC 閘極的數位線;每 CPU cycle 讀五個整數(0–15,DMC 0–127)。那就是「bit-exact 數位」與「類比行為」的乾淨切點 —— 100Ω 分壓不跑 SPICE。
  3. A 1.789773 MHz audio sidecar — the non-linear DAC + the IIR chain + our own polyphase decimator to 48 kHz, exactly as above.
  4. 1.789773MHz 音訊 sidecar —— 非線性 DAC + IIR 鏈 + 我們自己的 polyphase decimator 到 48kHz,如上。

Honest status. A roadmap, not built — nothing here exists in the switch-level engine yet, and like the video chain it's a presentation layer that changes what you hear, not a bit of the golden-checksum logic. The AprNes reference emulator already has a working APU + filter path (APU.cs, WaveOutPlayer) to compare against; what's new here is generating the analog audio from the transistor-level APU and writing the DSP ourselves. Pairs with the video chain to complete the mixed-signal picture.誠實狀態。路線圖、還沒做 —— 開關級引擎裡一點都還沒有,而且跟視訊鏈一樣是呈現層:改變你聽到什麼,不動金 checksum 的邏輯。AprNes 參考模擬器已有能用的 APU + 濾波路徑(APU.csWaveOutPlayer)可比對;這裡新的是從電晶體級 APU 產生類比音訊、並自己寫 DSP。與視訊鏈成對,補完整個混合訊號圖。

← back to deep dives · the other half: the video chain← 回深入專文 · 另一半:視訊鏈