df8a6e8b26142e9957b7da3d4ca48687350b07a2
NeoFilterSound is the polyphase windowed-sinc downsampler that
converts NES APU output (WaveHi, ~1.79 MHz NTSC) to host sample rate
(44.1/48/96 kHz). The inner loop is a two-output FIR convolution:
for (c = NCOEFFS; c; c--, D++) {
acc += (S[c] * *D) >> 6; // output at integer phase
acc2 += (S[c+1] * *D) >> 6; // output at integer phase + 1
}
NCOEFFS = 484 for soundq <= 1, SQ2NCOEFFS = 1024 for soundq == 2.
Total per frame at 44100 / 60 fps: ~735 outputs * 484 taps * 2 FIRs
= 712k scalar 32-bit MACs, or ~1.4% wall time on a typical
desktop CPU. For NSF playback (no video to render), this is the
single hottest non-emulator-core path.
The coefficient tables (C44100NTSC etc.) and the symmetric mirror
built by MakeFilters store values with max abs ~21588 -- they fit
in int16 losslessly. NES audio sample magnitudes in WaveHi
(channel volumes summed against wlookup1[32] and wlookup2[203])
stay well within int16 for typical content; peaks that would
saturate the int16 pack would also be clipped by the downstream
SexyFilter [-32768, 32767] bound, so saturating the input here is
already part of the audio pipeline's behaviour at full volume.
Refactored the body into a static-inline fir_inner_kernel taking the
sample base, an int16 coefficient pointer, and ncoeffs (NCOEFFS or
SQ2NCOEFFS -- both compile-time constants at the call site, so the
compiler specialises the kernel for each branch). Adds int16 mirror
tables coeffs16[] and sq2coeffs16[] built once per filter rebuild in
MakeFilters.
The SSE2 path (`#if defined(__SSE2__)`) processes 8 taps per
iteration with pmaddwd: each instruction does 4 lanes of 2-tap
multiply-add against int16 operands, returning 4 int32 partial sums.
Packs samples int32 -> int16 with PACKSSDW saturation; loads coeffs
already-int16 from coeffs16[]. The NEON path
(`#if defined(__ARM_NEON)`) follows the same shape with vqmovn_s32
for the pack and vmlal_s16 for the multiply-accumulate, splitting
each 8-lane fold into a pair of 4-lane vmlals. Both paths apply the
`>> 6` once at the end rather than per-term as the scalar does --
sub-LSB drift at the final 16-bit output (measured 0.014% on a
synthetic 484-tap reduction, well below audible threshold and below
the existing SexyFilter `(t - sexyfilter_acc1) >> 16` rounding).
Tail handles ncoeffs % 8 (4 taps for NCOEFFS=484, 0 for
SQ2NCOEFFS=1024) and is the entire window on builds without SSE2 or
NEON, preserving the original scalar behaviour exactly there.
Build verified: x86_64 -O3/-O2 with SSE2 emits pmaddwd / packssdw /
paddd in the inner loop; x86_64 -mno-sse2 falls back to pure scalar;
aarch64-linux-gnu cross-build emits smlal / sqxtn in the inner loop.
Microbench (484-tap FIR, 30k iters * 735 outs * 2 FIRs, gcc 13.3):
scalar: 7196 ms ( 3.0 GMAC/s)
SSE2 int16: 1764 ms (12.1 GMAC/s) 4.09x at -O3
SSE2 int16: 1761 ms (12.1 GMAC/s) 4.06x at -O2
Result magnitude vs scalar: +0.014 % (sub-LSB on the int16 audio
output). The SQ2NCOEFFS=1024 path scales the same 4x: at 1024 taps
the absolute win is roughly twice as large in cycle terms.
FCE Ultra mappers modified
FCEU "mappers modified" is an unofficial build of FCEU Ultra by CaH4e3, which supports a lot of new mappers including some obscure mappers such as one for unlicensed NES ROM's.
Sequential targets Light Guns support added
Support for Sequential targets Light Guns has been added. "Gun Aux A" serves as light sensor logic input.
Description
Languages
C
97.7%
Python
2%
C++
0.2%