Master the fundamental concepts of emulation through this focused micro-challenge.
You have read the whole brief, and the concepts above stay free on every task. Writing and running the code needs a plan.
Three hints are available for this task, revealed one at a time inside the code workspace so you can struggle productively before seeing them.
Every task includes starter code, theory, and hidden tests so you can implement and verify locally in the browser.
How it worksThe Audio Processing Unit mixes two pulse channels, triangle, noise, and DMC sample playback. Even silent emulation must advance APU timers in sync with CPU cycles or some games hang waiting for length counters.
Pulse 1 and 2:
Triangle, noise, DMC occupy \`0x4008-0x4013\` and \`0x4010-0x4013\`.
Frame sequencer clocks envelopes and sweeps at 240 Hz (4-step mode).
For example, setting pulse 1 timer low registers plays a square wave near 440 Hz when the game writes note values each frame.
\`\`\`c apu_tick(cpu_cycles); // must run every CPU cycle batch \`\`\`
Length counters silence channels even when the timer still runs. Games depend on automatic note cutoff; skipping length clocking produces notes that never end.
This exercise asks you to model APU register writes and timer stepping. You will advance sound channels in lockstep with the CPU so ROMs that synchronize on APU state do not deadlock.
You will use the same mental model here when reading production interpreter source later in the track. Sketch one concrete input on paper, predict the outcome, then confirm with code. That discipline catches logic errors early and makes debugging far faster when you extend the implementation in follow-on tasks.
Implement the building blocks of the NES APU (audio processing unit) as small, exact simulations:
Each command exercises one unit and prints what it produces. The CPU clock is 1789773 Hz.
One command per line (; starts a comment):
cLoading…
01000000, 01100000, 01111000 and 10011111. Print STEPS outputs, repeating the sequence, after pulse duty 12.5%: (or 25%, 50%, 75 (negated 25)%). Then print F Hz with F = 1789773 / (16 (TIMER + 1)) to 1 decimal, or timer T < 8: silenced.triangle:. Then F Hz with a divisor of 32 (TIMER + 1).feedback = bit0 XOR (MODE ? bit6 : bit1), shift right, and put the feedback in bit 14. Then print 1 if bit 0 is now 0 (the channel sounds), else 0. Follow with period N steps: the steps until the register returns to 1.10 254 20 2 40 4 80 6 160 8 60 10 14 12 26 14 12 16 24 18 48 20 96 22 192 24 72 26 16 28 32 30. Print length index I = L half-frames (S s at 120 Hz), with S to 3 decimals.change = T >> SHIFT. The target is T + change, or when negating T - change - 1 for pulse 1 and T - change for pulse 2. If T < 8 or the target > $7FF, print muted and stop. Otherwise, if SHIFT > 0, T becomes the target (never below 0). Print T.1789773 / 29830 or / 37282 Hz, to 2 decimals.pulse = 95.88 / (8128 / (P1+P2) + 100) and tnd = 159.79 / (1 / (T/8227 + N/12241 + DMC/22638) + 100). Each is 0 when its inputs are all 0. Print pulse A + tnd B = SUM with 4 decimals.cLoading…
cycle %5d: followed by envelopes+linear, lengths+sweeps and IRQ as they apply, or - for none.pulse: DUTY (0-3) TIMER (0-2047) STEPS (1-64)), and unknown commands print unknown command X.Input:
cLoading…
Output:
cLoading…
Hidden tests cover the noise short mode's 93-step period, looping and constant-volume envelopes, sweep muting in both directions (including the timer < 8 case), the 5-step and inhibited frame sequences, mixer edge values, and argument errors.