From 3f23e2b98f883be9c62a3fdb65c015d376dcd135 Mon Sep 17 00:00:00 2001 From: libretroadmin Date: Sun, 14 Jun 2026 16:41:37 +0000 Subject: [PATCH] sound: fix noise LFSR power-on state to match real hardware (fixes #466) The internal noise shift register in this file uses a bit-reversed layout vs the canonical nesdev wiki model: output is read from bit 14 (not bit 0), the feedback taps are at 13/14 in long mode and 8/14 in short mode (mirrors of 0/1 and 0/6), and the register shifts left rather than right. The algorithm is algebraically equivalent to the wiki form, but the power-on state has to be mirrored as well. Real hardware initialises the LFSR to $0001 with bit 0 set. Under this file's mirroring, the equivalent state has bit 14 set - i.e. nreg = 0x4000, not nreg = 1. Starting at 1 placed the LFSR 14 steps off-phase from real hardware in long mode and at an entirely different position in the 93-cycle short-mode loop. Reported by NSFPlay author Brad Smith (rainwarrior) comparing moon8.nes audio against Mesen. Reproduced via headless libretro harness with per-channel solo bisection against negativeExponent's fceumm_next branch (which uses the un-mirrored nesdev layout): channel RMS diff vs _next (noise solo, 22-60s window) ------- ---------------------------------------------- square1 1 ( 0.4%) - bit-identical square2 1 ( 0.4%) - bit-identical triangle 178 (14.0%) - minor difference noise 1914 (125.8%) - completely off dmc 2 ( 0.4%) - bit-identical After this patch the noise-solo RMS drops to 6 (0.4%), bringing the noise channel into bit-exact agreement with _next/Mesen/NSFPlay. Full-mix moon8 audio RMS vs _next falls from 1135 to 126 across the 60-second test. --- src/sound.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/sound.c b/src/sound.c index a0c15e7..4544dd4 100644 --- a/src/sound.c +++ b/src/sound.c @@ -1128,7 +1128,30 @@ void FCEUSND_Reset(void) { fhcnt = fhinc; fcnt = 0; - nreg = 1; + /* Power-on noise shift register state. + * + * Real hardware initializes the 15-bit noise LFSR to $0001 with bit + * 0 set (the output bit, muting the channel until the first feedback + * cycle). This file stores the LFSR with the bit order reversed - + * the output is read from bit 14, the feedback taps are at 13/14 + * (long mode) or 8/14 (short mode), and the shift goes left rather + * than right (see RDoNoise / NoLQNoise). Under that mirroring, the + * real-hardware $0001 state corresponds to nreg = 0x4000 here, not + * nreg = 1. + * + * Initialising to 1 left the LFSR running 14 long-mode steps ahead + * of every other accurate emulator (Mesen, NSFPlay, _next), and made + * short-mode output diverge entirely - the 93-cycle period is short + * enough that the position offset is audible as "rougher" or + * subtly wrong percussion. Reported as libretro-fceumm issue #466 + * (Moon8 audio inaccuracy, by NSFPlay author Brad Smith). + * + * Per-channel bisection of moon8.nes against negativeExponent's + * _next branch (which uses the un-mirrored layout from nesdev wiki) + * shows the noise channel as the only meaningful divergence after + * the music kicks in at ~22 s; squares and DMC are bit-identical. + */ + nreg = 0x4000; for (x = 0; x < 2; x++) { wlcount[x] = 2048;