core: APU channel disable - fix bitmasks and LQ-mode volume application
Two related bugs in the user-facing fceumm_apu_1..5 channel disable
toggles. Pre-existing since commit 448a231 (set_apu_channels) and at
least as far back as the LQ rewrite for the second one.
set_apu_channels (libretro.c)
-----------------------------
The bitmask built by check_variables uses bit i for fceumm_apu_(i+1)
enabled, ordered SQ1, SQ2, Triangle, Noise, DMC. The old code:
FSettings.SquareVolume[1] = (chan & 1) ? 256 : 0;
FSettings.SquareVolume[0] = (chan & 2) ? 256 : 0;
FSettings.TriangleVolume = (chan & 3) ? 256 : 0;
FSettings.NoiseVolume = (chan & 4) ? 256 : 0;
FSettings.PCMVolume = (chan & 5) ? 256 : 0;
crossed SQ1/SQ2 and used non-power-of-two masks for the remaining
three. Practical effect: apu_1 muted SQ2 and apu_2 muted SQ1; apu_3
through apu_5 left their channels audible in any normal config (the
masks happened to be true any time another bit was set).
Fixed to read each channel's own bit:
FSettings.SquareVolume[0] = (chan & 0x01) ? 256 : 0; /* SQ1 */
FSettings.SquareVolume[1] = (chan & 0x02) ? 256 : 0; /* SQ2 */
FSettings.TriangleVolume = (chan & 0x04) ? 256 : 0; /* Tri */
FSettings.NoiseVolume = (chan & 0x08) ? 256 : 0; /* Nse */
FSettings.PCMVolume = (chan & 0x10) ? 256 : 0; /* DMC */
RDoTriangleNoisePCMLQ (sound.c)
-------------------------------
The combined Triangle/Noise/DMC LQ mix uses a non-linear lookup
table:
totalout = wlookup2[lq_tcout + noiseout + RawDALatch];
The old code took the noise envelope (EnvUnits[2], not Triangle -
Triangle has no envelope; EnvUnits[0]=SQ1, [1]=SQ2, [2]=Noise) and
scaled it by FSettings.TriangleVolume, despite the comment claiming
to modify Triangle. Meanwhile lq_tcout (Triangle) and RawDALatch
(DMC) went into wlookup2 unscaled, so neither TriangleVolume nor
PCMVolume had any effect in LQ mode.
Fixed:
* amptab[0] (noise envelope) is now scaled by FSettings.NoiseVolume.
* lq_tcout is scaled by FSettings.TriangleVolume into a local
scaled_tcout fed to wlookup2.
* RawDALatch is scaled by FSettings.PCMVolume into scaled_dmc.
The volume scalars are loop-invariant within this function, so the
Triangle volume is cached once in tri_vol at function entry to keep
the inner loops tight; the != 256 fast paths preserve byte-exact
behavior at full volume.
Verification
------------
Built per-channel-only test ROMs (test_sq1_only, test_sq2_only,
test_tri_only, test_nse_only, test_dmc_only) and an apu_test
runner that measures output RMS with each fceumm_apu_N toggle.
After the fix, in both LQ and HQ modes, each apu_N toggle mutes
exactly the channel whose ROM is the only sound source, and no
other toggle has any effect on it.
Default (all-on) audio output is bit-exact identical to the
baseline across the existing test_silent / test_idle / test_active /
test_stress ROMs (1500 frames each), so games and homebrew that
never touch the apu_N options see no change. No performance
regression in the default path; the != 256 guards skip the multiplies.
This commit is contained in:
@@ -1975,11 +1975,23 @@ void retro_reset(void)
|
|||||||
|
|
||||||
static void set_apu_channels(int chan)
|
static void set_apu_channels(int chan)
|
||||||
{
|
{
|
||||||
FSettings.SquareVolume[1] = (chan & 1) ? 256 : 0;
|
/* Bitmask layout (set up by check_variables): bit i = fceumm_apu_(i+1)
|
||||||
FSettings.SquareVolume[0] = (chan & 2) ? 256 : 0;
|
* is enabled. Channel order matches the libretro core options:
|
||||||
FSettings.TriangleVolume = (chan & 3) ? 256 : 0;
|
* bit 0 = Square 1 (apu_1)
|
||||||
FSettings.NoiseVolume = (chan & 4) ? 256 : 0;
|
* bit 1 = Square 2 (apu_2)
|
||||||
FSettings.PCMVolume = (chan & 5) ? 256 : 0;
|
* bit 2 = Triangle (apu_3)
|
||||||
|
* bit 3 = Noise (apu_4)
|
||||||
|
* bit 4 = PCM / DMC (apu_5)
|
||||||
|
*
|
||||||
|
* The previous masks crossed the SQ1/SQ2 indices and used non-power-of-
|
||||||
|
* two values for the remaining channels, which silently broke the
|
||||||
|
* apu_3..apu_5 toggles entirely (any combination of bits 0/1/2 left
|
||||||
|
* those channels audible) and made apu_1 mute SQ2 and vice versa. */
|
||||||
|
FSettings.SquareVolume[0] = (chan & 0x01) ? 256 : 0;
|
||||||
|
FSettings.SquareVolume[1] = (chan & 0x02) ? 256 : 0;
|
||||||
|
FSettings.TriangleVolume = (chan & 0x04) ? 256 : 0;
|
||||||
|
FSettings.NoiseVolume = (chan & 0x08) ? 256 : 0;
|
||||||
|
FSettings.PCMVolume = (chan & 0x10) ? 256 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void check_variables(bool startup)
|
static void check_variables(bool startup)
|
||||||
|
|||||||
45
src/sound.c
45
src/sound.c
@@ -733,6 +733,9 @@ static void RDoTriangleNoisePCMLQ(void) {
|
|||||||
uint32_t amptab[2];
|
uint32_t amptab[2];
|
||||||
uint32_t noiseout;
|
uint32_t noiseout;
|
||||||
int nshift;
|
int nshift;
|
||||||
|
uint32_t scaled_tcout;
|
||||||
|
uint32_t scaled_dmc;
|
||||||
|
const uint32_t tri_vol = FSettings.TriangleVolume;
|
||||||
|
|
||||||
int32_t totalout;
|
int32_t totalout;
|
||||||
|
|
||||||
@@ -754,13 +757,18 @@ static void RDoTriangleNoisePCMLQ(void) {
|
|||||||
else
|
else
|
||||||
amptab[0] = EnvUnits[2].decvolume;
|
amptab[0] = EnvUnits[2].decvolume;
|
||||||
|
|
||||||
/* Modify Triangle wave volume based on channel volume modifiers
|
/* Apply per-channel volume modifiers (set via fceumm_apu_N options).
|
||||||
* Note: the formulat x = x * y /100 does not yield exact results,
|
*
|
||||||
* but is "close enough" and avoids the need for using double vales
|
* EnvUnits[2] is the Noise envelope (not Triangle - Triangle has no
|
||||||
* or implicit cohersion which are slower (we need speed here)
|
* envelope; EnvUnits[0]=SQ1, [1]=SQ2, [2]=Noise). The previous code
|
||||||
* TODO: Optimize this. */
|
* scaled amptab[0] by FSettings.TriangleVolume, which crossed the
|
||||||
if (FSettings.TriangleVolume != 256)
|
* Triangle and Noise mute toggles in LQ mode and left Triangle
|
||||||
amptab[0] = (amptab[0] * FSettings.TriangleVolume) / 256;
|
* itself never muted. Triangle's contribution enters wlookup2 via
|
||||||
|
* lq_tcout below; PCM enters via RawDALatch. Scale each input
|
||||||
|
* channel by its own volume before the non-linear DAC mix - 0 in
|
||||||
|
* still produces 0 out, and 256 leaves the value unchanged. */
|
||||||
|
if (FSettings.NoiseVolume != 256)
|
||||||
|
amptab[0] = (amptab[0] * FSettings.NoiseVolume) / 256;
|
||||||
|
|
||||||
amptab[1] = 0;
|
amptab[1] = 0;
|
||||||
amptab[0] <<= 1;
|
amptab[0] <<= 1;
|
||||||
@@ -775,7 +783,14 @@ static void RDoTriangleNoisePCMLQ(void) {
|
|||||||
else
|
else
|
||||||
nshift = 13;
|
nshift = 13;
|
||||||
|
|
||||||
totalout = wlookup2[lq_tcout + noiseout + RawDALatch];
|
scaled_tcout = (tri_vol != 256)
|
||||||
|
? ((lq_tcout * tri_vol) / 256)
|
||||||
|
: lq_tcout;
|
||||||
|
scaled_dmc = (FSettings.PCMVolume != 256)
|
||||||
|
? ((RawDALatch * FSettings.PCMVolume) / 256)
|
||||||
|
: RawDALatch;
|
||||||
|
|
||||||
|
totalout = wlookup2[scaled_tcout + noiseout + scaled_dmc];
|
||||||
|
|
||||||
if (inie[0] && inie[1]) {
|
if (inie[0] && inie[1]) {
|
||||||
for (V = start; V < end; V++) {
|
for (V = start; V < end; V++) {
|
||||||
@@ -792,7 +807,10 @@ static void RDoTriangleNoisePCMLQ(void) {
|
|||||||
lq_tcout = (tristep & 0xF);
|
lq_tcout = (tristep & 0xF);
|
||||||
if (!(tristep & 0x10)) lq_tcout ^= 0xF;
|
if (!(tristep & 0x10)) lq_tcout ^= 0xF;
|
||||||
lq_tcout = lq_tcout * 3;
|
lq_tcout = lq_tcout * 3;
|
||||||
totalout = wlookup2[lq_tcout + noiseout + RawDALatch];
|
scaled_tcout = (tri_vol != 256)
|
||||||
|
? ((lq_tcout * tri_vol) / 256)
|
||||||
|
: lq_tcout;
|
||||||
|
totalout = wlookup2[scaled_tcout + noiseout + scaled_dmc];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lq_noiseacc <= 0) {
|
if (lq_noiseacc <= 0) {
|
||||||
@@ -808,7 +826,7 @@ static void RDoTriangleNoisePCMLQ(void) {
|
|||||||
nreg &= 0x7fff;
|
nreg &= 0x7fff;
|
||||||
noiseout = amptab[(nreg >> 0xe) & 1];
|
noiseout = amptab[(nreg >> 0xe) & 1];
|
||||||
if (lq_noiseacc <= 0) goto rea2;
|
if (lq_noiseacc <= 0) goto rea2;
|
||||||
totalout = wlookup2[lq_tcout + noiseout + RawDALatch];
|
totalout = wlookup2[scaled_tcout + noiseout + scaled_dmc];
|
||||||
} /* noiseacc<=0 */
|
} /* noiseacc<=0 */
|
||||||
} /* for(V=... */
|
} /* for(V=... */
|
||||||
} else if (inie[0]) {
|
} else if (inie[0]) {
|
||||||
@@ -825,7 +843,10 @@ static void RDoTriangleNoisePCMLQ(void) {
|
|||||||
lq_tcout = (tristep & 0xF);
|
lq_tcout = (tristep & 0xF);
|
||||||
if (!(tristep & 0x10)) lq_tcout ^= 0xF;
|
if (!(tristep & 0x10)) lq_tcout ^= 0xF;
|
||||||
lq_tcout = lq_tcout * 3;
|
lq_tcout = lq_tcout * 3;
|
||||||
totalout = wlookup2[lq_tcout + noiseout + RawDALatch];
|
scaled_tcout = (tri_vol != 256)
|
||||||
|
? ((lq_tcout * tri_vol) / 256)
|
||||||
|
: lq_tcout;
|
||||||
|
totalout = wlookup2[scaled_tcout + noiseout + scaled_dmc];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (inie[1]) {
|
} else if (inie[1]) {
|
||||||
@@ -845,7 +866,7 @@ static void RDoTriangleNoisePCMLQ(void) {
|
|||||||
nreg &= 0x7fff;
|
nreg &= 0x7fff;
|
||||||
noiseout = amptab[(nreg >> 0xe) & 1];
|
noiseout = amptab[(nreg >> 0xe) & 1];
|
||||||
if (lq_noiseacc <= 0) goto area2;
|
if (lq_noiseacc <= 0) goto area2;
|
||||||
totalout = wlookup2[lq_tcout + noiseout + RawDALatch];
|
totalout = wlookup2[scaled_tcout + noiseout + scaled_dmc];
|
||||||
} /* noiseacc<=0 */
|
} /* noiseacc<=0 */
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user