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.
This commit is contained in:
libretroadmin
2026-06-14 16:41:37 +00:00
committed by U-DESKTOP-SPFP6AQ\twistedtechre
parent 71bbd10ad0
commit 3f23e2b98f

View File

@@ -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;