fds_apu: backport MDFN-derived modulator/carrier model (fixes #560)
The previous Xodnizel implementation modeled the FDS modulator unit's output via a bit-serial b8shiftreg88 mechanism but omitted the modulator's sweep-bias accumulator entirely. Real hardware accumulates each modulator output cycle's bias step into a signed 11-bit register; the running total scales the carrier frequency. Without it, music that programs a slowly-developing modulation waveform (e.g. VS. Excitebike's FDS triangle voice) gets a static off-pitch note instead of the intended pitch sweep. Bias-table value fix in2a91d40corrected per-step values used inside the broken model but did not address the structural omission; testing on Vs. Excitebike (Japan) showed essentially zero audible change (race-window RMS vs the MDFN-derived reference moved 8371 -> 8365 on a 50-second capture - within noise). Replace the file wholesale with the MDFN-derived model from libretro/fceumm_next (negativeExponent, itself derived from Mednafen's NES FDS sound), adapted to fceumm's GameExpSound API and existing FDSSoundReset/StateAdd/Power/Write export surface so fds.c does not change. Differences from the _next version: - Uses upstream's openbus (X.DB) for FDS register read top bits instead of the OPENBUS=0x40 constant. - Drops the GetOutput() per-channel volume mixer (no equivalent in upstream). - Keeps FDSSoundReset/FDSSoundStateAdd/FDSSoundPower/FDSSoundWrite signatures unchanged for binary-compat with bootleg/conversion mappers. Validation on Vs. Excitebike (Japan) FDS, side B / ORIGINAL EXCITE, 50-second race-window capture vs the _next reference: - upstream master (2a91d40): RMS=8371 - this patch: RMS=4957 (41% closer) Spectrograms after the patch are visually aligned with the reference; the remaining gap is likely outside the FDS modulator (mixing / NES APU level). Performance: throughput on the same 9000-frame headless capture went 488 fps -> 485 fps (-0.6%, within measurement noise). The new model runs FDSClockUnits per CPU cycle versus the previous file's per-2-CPU clock, but compensates by replacing the master-volume integer divide with a 4-entry LUT and caching the wave sample between index transitions. Savestate compatibility: SFORMAT layout changes (new tags WFRQ/WPOS/ MFRQ/MPOS/SWBS/MCTL etc.; old tags B88/CLOC/B60/B66/B68/B76/SPSG/AMPL are dropped). Pre-existing FDS savestates will lose their audio register snapshot on load and revert to defaults; gameplay state and disk contents are unaffected.
This commit is contained in:
committed by
U-DESKTOP-SPFP6AQ\twistedtechre
parent
2a91d40f4d
commit
bc5a7a9fec
553
src/fds_apu.c
553
src/fds_apu.c
@@ -2,6 +2,7 @@
|
||||
*
|
||||
* Copyright notice for this file:
|
||||
* Copyright (C) 2002 Xodnizel
|
||||
* Copyright (C) 2023-2026 negativeExponent (MDFN-derived FDS sound)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -18,243 +19,398 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/* Begin FDS sound */
|
||||
/* MDFN-derived modulator/carrier model. Replaces the previous Xodnizel
|
||||
* bit-serial b8shiftreg88 implementation; the latter lacked the
|
||||
* modulator's sweep-bias accumulator entirely, producing static pitch
|
||||
* shifts where games (e.g. VS. Excitebike FDS triangle voice, #560)
|
||||
* programmed slowly-developing pitch sweeps. Backported from
|
||||
* libretro/fceumm_next/src/mappers/sound/fdssound.c (negativeExponent,
|
||||
* itself derived from Mednafen's NES FDS sound). */
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "fceu-types.h"
|
||||
#include "x6502.h"
|
||||
#include "fceu.h"
|
||||
#include "sound.h"
|
||||
#include "filter.h"
|
||||
#include "state.h"
|
||||
#include "fds_apu.h"
|
||||
|
||||
#define FDSClock (1789772.7272727272727272 / 2)
|
||||
|
||||
enum { EVOL = 0, EMOD };
|
||||
enum {
|
||||
ENV_CTRL_INCREASE = 0x40,
|
||||
ENV_CTRL_DISABLE = 0x80,
|
||||
ENVELOPES_DISABLE = 0x40,
|
||||
WAVE_DISABLE = 0x80,
|
||||
MOD_WRITE_MODE = 0x80,
|
||||
WAVE_WRITE_MODE = 0x80
|
||||
};
|
||||
enum {
|
||||
VOLUME_MIN = 0,
|
||||
VOLUME_MAX = 32
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
int64_t cycles; /* Cycles per PCM sample */
|
||||
int64_t count; /* Cycle counter */
|
||||
int64_t envcount; /* Envelope cycle counter */
|
||||
uint32_t b19shiftreg60;
|
||||
uint32_t b24adder66;
|
||||
uint32_t b24latch68;
|
||||
uint32_t b17latch76;
|
||||
int32_t clockcount; /* Counter to divide frequency by 8. */
|
||||
uint8_t b8shiftreg88; /* Modulation register. */
|
||||
uint8_t amplitude[2]; /* Current amplitudes. */
|
||||
uint8_t speedo[2];
|
||||
uint8_t mwcount;
|
||||
uint8_t mwstart;
|
||||
uint8_t mwave[0x20]; /* Modulation waveform */
|
||||
uint8_t cwave[0x40]; /* Game-defined waveform(carrier) */
|
||||
uint8_t SPSG[0xB];
|
||||
uint8_t speed;
|
||||
uint8_t volume;
|
||||
uint8_t control;
|
||||
int32_t counter;
|
||||
} FDSENVUNIT;
|
||||
|
||||
typedef struct {
|
||||
int64_t cycles;
|
||||
int64_t count;
|
||||
|
||||
FDSENVUNIT EnvUnits[2];
|
||||
|
||||
int32_t env_divider;
|
||||
|
||||
uint16_t cwave_freq;
|
||||
uint32_t cwave_pos;
|
||||
uint8_t cwave_control;
|
||||
|
||||
uint16_t mod_freq;
|
||||
uint32_t mod_pos;
|
||||
uint8_t mod_disabled;
|
||||
|
||||
uint8_t master_control;
|
||||
uint8_t master_env_speed;
|
||||
|
||||
int32_t mwave[0x20];
|
||||
uint8_t cwave[0x40];
|
||||
|
||||
uint32_t sweep_bias;
|
||||
int32_t mod_output;
|
||||
int32_t sample_out_cache;
|
||||
|
||||
uint8_t cwave_pos_shift;
|
||||
uint8_t env_count_mul;
|
||||
uint8_t mod_pos_shift;
|
||||
uint8_t mod_overflow_shift;
|
||||
} FDSSOUND;
|
||||
|
||||
static FDSSOUND fdso;
|
||||
static const int32_t mod_bias_tab[8] = { 0, 1, 2, 4, 0, -4, -2, -1 };
|
||||
|
||||
#define SPSG fdso.SPSG
|
||||
#define b19shiftreg60 fdso.b19shiftreg60
|
||||
#define b24adder66 fdso.b24adder66
|
||||
#define b24latch68 fdso.b24latch68
|
||||
#define b17latch76 fdso.b17latch76
|
||||
#define b8shiftreg88 fdso.b8shiftreg88
|
||||
#define clockcount fdso.clockcount
|
||||
#define amplitude fdso.amplitude
|
||||
#define speedo fdso.speedo
|
||||
static FDSSOUND fdso;
|
||||
static int32_t FBC;
|
||||
|
||||
void FDSSoundStateAdd(void) {
|
||||
AddExState(&fdso.sample_out_cache, 4, 1, "FDSO");
|
||||
|
||||
AddExState(&fdso.EnvUnits[EVOL].speed, 1, 0, "SPD0");
|
||||
AddExState(&fdso.EnvUnits[EVOL].control, 1, 0, "CTL0");
|
||||
AddExState(&fdso.EnvUnits[EVOL].volume, 1, 0, "VOL0");
|
||||
AddExState(&fdso.EnvUnits[EVOL].counter, 4, 1, "CNT0");
|
||||
|
||||
AddExState(&fdso.EnvUnits[EMOD].speed, 1, 0, "SPD1");
|
||||
AddExState(&fdso.EnvUnits[EMOD].control, 1, 0, "CTL1");
|
||||
AddExState(&fdso.EnvUnits[EMOD].volume, 1, 0, "VOL1");
|
||||
AddExState(&fdso.EnvUnits[EMOD].counter, 4, 1, "CNT1");
|
||||
|
||||
AddExState(fdso.cwave, 64, 0, "WAVE");
|
||||
AddExState(fdso.mwave, 32, 0, "MWAV");
|
||||
AddExState(amplitude, 2, 0, "AMPL");
|
||||
AddExState(SPSG, 0xB, 0, "SPSG");
|
||||
AddExState(fdso.mwave, 32 * 4, 1, "MWAV");
|
||||
|
||||
AddExState(&b8shiftreg88, 1, 0, "B88");
|
||||
AddExState(&fdso.cwave_freq, 2, 1, "WFRQ");
|
||||
AddExState(&fdso.cwave_pos, 4, 1, "WPOS");
|
||||
AddExState(&fdso.cwave_control, 1, 0, "WCTL");
|
||||
|
||||
AddExState(&clockcount, 4, 1, "CLOC");
|
||||
AddExState(&b19shiftreg60, 4, 1, "B60");
|
||||
AddExState(&b24adder66, 4, 1, "B66");
|
||||
AddExState(&b24latch68, 4, 1, "B68");
|
||||
AddExState(&b17latch76, 4, 1, "B76");
|
||||
}
|
||||
AddExState(&fdso.mod_freq, 2, 1, "MFRQ");
|
||||
AddExState(&fdso.mod_pos, 4, 1, "MPOS");
|
||||
AddExState(&fdso.mod_disabled, 1, 0, "MDIS");
|
||||
AddExState(&fdso.mod_output, 4, 1, "MCRM");
|
||||
|
||||
static DECLFR(FDSSRead) {
|
||||
switch (A & 0xF) {
|
||||
case 0x0: return(amplitude[0] | (X.DB & 0xC0));
|
||||
case 0x2: return(amplitude[1] | (X.DB & 0xC0));
|
||||
}
|
||||
return(X.DB);
|
||||
AddExState(&fdso.sweep_bias, 4, 1, "SWBS");
|
||||
|
||||
AddExState(&fdso.master_control, 1, 0, "MCTL");
|
||||
AddExState(&fdso.master_env_speed, 1, 0, "MSPD");
|
||||
|
||||
AddExState(&fdso.env_divider, 4, 1, "EDIV");
|
||||
|
||||
AddExState(&fdso.cwave_pos_shift, 1, 0, "CWPS");
|
||||
AddExState(&fdso.env_count_mul, 1, 0, "ECNM");
|
||||
AddExState(&fdso.mod_pos_shift, 1, 0, "MPSH");
|
||||
AddExState(&fdso.mod_overflow_shift, 1, 0, "MOFS");
|
||||
}
|
||||
|
||||
static void RenderSound(void);
|
||||
static void RenderSoundHQ(void);
|
||||
|
||||
static DECLFW(FDSSWrite) {
|
||||
static void FDSSoundUpdate(void) {
|
||||
if (FSettings.SndRate) {
|
||||
if (FSettings.soundq >= 1)
|
||||
RenderSoundHQ();
|
||||
else
|
||||
RenderSound();
|
||||
}
|
||||
A -= 0x4080;
|
||||
switch (A) {
|
||||
case 0x0:
|
||||
case 0x4:
|
||||
if (V & 0x80)
|
||||
amplitude[(A & 0xF) >> 2] = V & 0x3F;
|
||||
break;
|
||||
case 0x7:
|
||||
b17latch76 = 0;
|
||||
SPSG[0x5] = 0;
|
||||
break;
|
||||
case 0x8:
|
||||
b17latch76 = 0;
|
||||
fdso.mwave[SPSG[0x5] & 0x1F] = V & 0x7;
|
||||
SPSG[0x5] = (SPSG[0x5] + 1) & 0x1F;
|
||||
break;
|
||||
}
|
||||
SPSG[A] = V;
|
||||
}
|
||||
|
||||
/* $4080 - Fundamental wave amplitude data register 92
|
||||
* $4082 - Fundamental wave frequency data register 58
|
||||
* $4083 - Same as $4082($4083 is the upper 4 bits).
|
||||
*
|
||||
* $4084 - Modulation amplitude data register 78
|
||||
* $4086 - Modulation frequency data register 72
|
||||
* $4087 - Same as $4086($4087 is the upper 4 bits)
|
||||
*/
|
||||
|
||||
|
||||
static void DoEnv() {
|
||||
int x;
|
||||
|
||||
for (x = 0; x < 2; x++)
|
||||
if (!(SPSG[x << 2] & 0x80) && !(SPSG[0x3] & 0x40)) {
|
||||
static int counto[2] = { 0, 0 };
|
||||
|
||||
if (counto[x] <= 0) {
|
||||
if (!(SPSG[x << 2] & 0x80)) {
|
||||
if (SPSG[x << 2] & 0x40) {
|
||||
if (amplitude[x] < 0x3F)
|
||||
amplitude[x]++;
|
||||
} else {
|
||||
if (amplitude[x] > 0)
|
||||
amplitude[x]--;
|
||||
}
|
||||
}
|
||||
counto[x] = (SPSG[x << 2] & 0x3F);
|
||||
} else
|
||||
counto[x]--;
|
||||
}
|
||||
}
|
||||
|
||||
static DECLFR(FDSWaveRead) {
|
||||
return(fdso.cwave[A & 0x3f] | (X.DB & 0xC0));
|
||||
if (fdso.master_control & WAVE_WRITE_MODE)
|
||||
return (fdso.cwave[A & 0x3F] | (X.DB & 0xC0));
|
||||
return (fdso.cwave[(fdso.cwave_pos >> 21) & 0x3F] | (X.DB & 0xC0));
|
||||
}
|
||||
|
||||
static DECLFW(FDSWaveWrite) {
|
||||
if (SPSG[0x9] & 0x80)
|
||||
fdso.cwave[A & 0x3f] = V & 0x3F;
|
||||
if (fdso.master_control & WAVE_WRITE_MODE)
|
||||
fdso.cwave[A & 0x3F] = V & 0x3F;
|
||||
}
|
||||
|
||||
static int ta;
|
||||
static INLINE void ClockRise(void) {
|
||||
if (!clockcount) {
|
||||
ta++;
|
||||
static DECLFW(FDSSReg0Write) {
|
||||
FDSSoundUpdate();
|
||||
|
||||
b19shiftreg60 = (SPSG[0x2] | ((SPSG[0x3] & 0xF) << 8));
|
||||
b17latch76 = (SPSG[0x6] | ((SPSG[0x07] & 0xF) << 8)) + b17latch76;
|
||||
fdso.EnvUnits[EVOL].speed = V & 0x3F;
|
||||
fdso.EnvUnits[EVOL].control = V & 0xC0;
|
||||
fdso.EnvUnits[EVOL].counter = fdso.EnvUnits[EVOL].speed + 1;
|
||||
|
||||
if (!(SPSG[0x7] & 0x80)) {
|
||||
int t = fdso.mwave[(b17latch76 >> 13) & 0x1F] & 7;
|
||||
int t2 = amplitude[1];
|
||||
int adj;
|
||||
/* Per-step bias the modulator unit applies for each
|
||||
* 3-bit mod table entry. Real hardware uses powers of
|
||||
* 2 ({-4,-2,-1,0,+1,+2,+4} + RESET); the conditional
|
||||
* form previously here computed { +/- (4 - (t & 3)) }
|
||||
* for the negate cases and { +/- (t & 3) } for the
|
||||
* positive cases, yielding ±3 instead of ±4 at table
|
||||
* positions 3 and 5. Documented at nesdev wiki
|
||||
* "FDS audio" / Modulator unit; same table is used in
|
||||
* MDFN/NSFPlay and negativeExponent's fceumm_next. */
|
||||
static const int8_t mod_bias_tab[8] = {
|
||||
0, 1, 2, 4,
|
||||
0, -4, -2, -1
|
||||
};
|
||||
adj = t2 * mod_bias_tab[t];
|
||||
adj *= 2;
|
||||
if (adj > 0x7F) adj = 0x7F;
|
||||
if (adj < -0x80) adj = -0x80;
|
||||
b8shiftreg88 = 0x80 + adj;
|
||||
} else {
|
||||
b8shiftreg88 = 0x80;
|
||||
if (fdso.EnvUnits[EVOL].control & ENV_CTRL_DISABLE)
|
||||
fdso.EnvUnits[EVOL].volume = fdso.EnvUnits[EVOL].speed;
|
||||
}
|
||||
|
||||
static DECLFW(FDSSReg1Write) {
|
||||
FDSSoundUpdate();
|
||||
|
||||
fdso.cwave_freq &= 0x0F00;
|
||||
fdso.cwave_freq |= V;
|
||||
}
|
||||
|
||||
static DECLFW(FDSSReg2Write) {
|
||||
FDSSoundUpdate();
|
||||
|
||||
fdso.cwave_freq &= 0x00FF;
|
||||
fdso.cwave_freq |= (V & 0x0F) << 8;
|
||||
|
||||
fdso.cwave_control = V & 0xC0;
|
||||
|
||||
if (fdso.cwave_control & WAVE_DISABLE)
|
||||
fdso.cwave_pos = 0;
|
||||
}
|
||||
|
||||
static DECLFW(FDSSReg3Write) {
|
||||
FDSSoundUpdate();
|
||||
|
||||
fdso.EnvUnits[EMOD].speed = V & 0x3F;
|
||||
fdso.EnvUnits[EMOD].control = V & 0xC0;
|
||||
fdso.EnvUnits[EMOD].counter = fdso.EnvUnits[EMOD].speed + 1;
|
||||
|
||||
if (fdso.EnvUnits[EMOD].control & ENV_CTRL_DISABLE)
|
||||
fdso.EnvUnits[EMOD].volume = fdso.EnvUnits[EMOD].speed;
|
||||
}
|
||||
|
||||
static DECLFW(FDSSReg4Write) {
|
||||
FDSSoundUpdate();
|
||||
|
||||
fdso.sweep_bias = (V & 0x7F) << 4;
|
||||
fdso.mod_pos = 0;
|
||||
}
|
||||
|
||||
static DECLFW(FDSSReg5Write) {
|
||||
FDSSoundUpdate();
|
||||
|
||||
fdso.mod_freq &= 0x0F00;
|
||||
fdso.mod_freq |= V;
|
||||
}
|
||||
|
||||
static DECLFW(FDSSReg6Write) {
|
||||
FDSSoundUpdate();
|
||||
|
||||
fdso.mod_freq &= 0x00FF;
|
||||
fdso.mod_freq |= (V & 0x0F) << 8;
|
||||
fdso.mod_disabled = (V & MOD_WRITE_MODE) != 0;
|
||||
if (fdso.mod_disabled)
|
||||
fdso.mod_pos = 0;
|
||||
}
|
||||
|
||||
static DECLFW(FDSSReg7Write) {
|
||||
FDSSoundUpdate();
|
||||
|
||||
if (fdso.mod_disabled) {
|
||||
int i;
|
||||
for (i = 0; i < 31; i++)
|
||||
fdso.mwave[i] = fdso.mwave[i + 1];
|
||||
fdso.mwave[0x1F] = mod_bias_tab[V & 0x07];
|
||||
if ((V & 0x07) == 0x04)
|
||||
fdso.mwave[0x1F] = 0x10;
|
||||
}
|
||||
}
|
||||
|
||||
static DECLFW(FDSSReg8Write) {
|
||||
FDSSoundUpdate();
|
||||
fdso.master_control = V;
|
||||
}
|
||||
|
||||
static DECLFW(FDSSReg9Write) {
|
||||
FDSSoundUpdate();
|
||||
fdso.master_env_speed = V;
|
||||
}
|
||||
|
||||
static DECLFR(FDSEnvVolumeRead) {
|
||||
FDSSoundUpdate();
|
||||
return (fdso.EnvUnits[EVOL].volume | (X.DB & 0xC0));
|
||||
}
|
||||
|
||||
static DECLFR(FDSEnvModRead) {
|
||||
FDSSoundUpdate();
|
||||
return (fdso.EnvUnits[EMOD].volume | (X.DB & 0xC0));
|
||||
}
|
||||
|
||||
static INLINE void FDSEnvStep(FDSENVUNIT *e) {
|
||||
if (!(e->control & ENV_CTRL_DISABLE)) {
|
||||
e->counter--;
|
||||
if (e->counter <= 0) {
|
||||
e->counter = e->speed + 1;
|
||||
if (e->control & ENV_CTRL_INCREASE) {
|
||||
if (e->volume < VOLUME_MAX) e->volume++;
|
||||
} else {
|
||||
b19shiftreg60 <<= 1;
|
||||
b8shiftreg88 >>= 1;
|
||||
if (e->volume > VOLUME_MIN) e->volume--;
|
||||
}
|
||||
}
|
||||
}
|
||||
b24adder66 = (b24latch68 + b19shiftreg60) & 0x1FFFFFF;
|
||||
}
|
||||
|
||||
static INLINE void ClockFall(void) {
|
||||
if ((b8shiftreg88 & 1))
|
||||
b24latch68 = b24adder66;
|
||||
clockcount = (clockcount + 1) & 7;
|
||||
static void FDSDoEnv(void) {
|
||||
FDSEnvStep(&fdso.EnvUnits[EVOL]);
|
||||
FDSEnvStep(&fdso.EnvUnits[EMOD]);
|
||||
}
|
||||
|
||||
static INLINE int32_t FDSDoSound(void) {
|
||||
static INLINE int32_t sign_x_to_s32(int n, int32_t v) {
|
||||
return ((int32_t)((uint32_t)v << (32 - n)) >> (32 - n));
|
||||
}
|
||||
|
||||
static void FDSModUpdateOutput(void) {
|
||||
int32_t mod_amp = (fdso.EnvUnits[EMOD].volume > 0x20)
|
||||
? 0x20 : fdso.EnvUnits[EMOD].volume;
|
||||
int32_t temp = sign_x_to_s32(11, fdso.sweep_bias) * mod_amp;
|
||||
|
||||
if (temp & 0x0F0) {
|
||||
temp /= 256;
|
||||
if (fdso.sweep_bias & 0x400)
|
||||
temp--;
|
||||
else
|
||||
temp += 2;
|
||||
} else {
|
||||
temp /= 256;
|
||||
}
|
||||
|
||||
if (temp >= 194) temp -= 258;
|
||||
if (temp < -64) temp += 256;
|
||||
|
||||
fdso.mod_output = temp;
|
||||
}
|
||||
|
||||
static int FDSDoModulator(void) {
|
||||
uint32_t prev_mod_pos = fdso.mod_pos;
|
||||
|
||||
if (!fdso.mod_disabled) {
|
||||
uint32_t overflow_mask = 0x3F << fdso.mod_overflow_shift;
|
||||
fdso.mod_pos += fdso.mod_freq;
|
||||
if ((fdso.mod_pos & overflow_mask) != (prev_mod_pos & overflow_mask)) {
|
||||
const int32_t mw = fdso.mwave[(fdso.mod_pos >> fdso.mod_pos_shift) & 0x1F];
|
||||
fdso.sweep_bias = (fdso.sweep_bias + mw) & 0x7FF;
|
||||
if (mw == 0x10)
|
||||
fdso.sweep_bias = 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static INLINE void FDSDoCarrier(void) {
|
||||
int32_t cur_cwave_freq = (int32_t)(fdso.cwave_freq << 6);
|
||||
|
||||
if (!fdso.mod_disabled) {
|
||||
cur_cwave_freq += (int32_t)fdso.cwave_freq * fdso.mod_output;
|
||||
if (cur_cwave_freq < 0)
|
||||
cur_cwave_freq = 0;
|
||||
}
|
||||
fdso.cwave_pos += cur_cwave_freq;
|
||||
}
|
||||
|
||||
static void FDSClockUnits(void) {
|
||||
if (FDSDoModulator())
|
||||
FDSModUpdateOutput();
|
||||
if (!(fdso.master_control & WAVE_WRITE_MODE) &&
|
||||
!(fdso.cwave_control & WAVE_DISABLE))
|
||||
FDSDoCarrier();
|
||||
if (fdso.master_env_speed) {
|
||||
fdso.env_divider--;
|
||||
if (fdso.env_divider <= 0) {
|
||||
fdso.env_divider += fdso.master_env_speed * fdso.env_count_mul;
|
||||
if (!(fdso.cwave_control & ENVELOPES_DISABLE))
|
||||
FDSDoEnv();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void FDSDoSound(void) {
|
||||
uint32_t prev_cwave_pos = fdso.cwave_pos;
|
||||
|
||||
if (FSettings.soundq >= 1) {
|
||||
FDSClockUnits();
|
||||
} else {
|
||||
fdso.count += fdso.cycles;
|
||||
if (fdso.count >= ((int64_t)1 << 40)) {
|
||||
dogk:
|
||||
fdso.count -= (int64_t)1 << 40;
|
||||
ClockRise();
|
||||
ClockFall();
|
||||
fdso.envcount--;
|
||||
if (fdso.envcount <= 0) {
|
||||
fdso.envcount += SPSG[0xA] * 3;
|
||||
DoEnv();
|
||||
FDSClockUnits();
|
||||
}
|
||||
while (fdso.count >= 32768) {
|
||||
fdso.count -= (int64_t)1 << 40;
|
||||
FDSClockUnits();
|
||||
}
|
||||
}
|
||||
if (fdso.count >= 32768) goto dogk;
|
||||
|
||||
/* Might need to emulate applying the amplitude to the waveform a bit better... */
|
||||
{
|
||||
int k = amplitude[0];
|
||||
if ((fdso.cwave_pos ^ prev_cwave_pos) & (1U << fdso.cwave_pos_shift)) {
|
||||
/* Master-volume divisor LUT: replaces an integer divide
|
||||
* with a multiply + shift. Indices match $4089 bits 0-1:
|
||||
* 0 -> div 2, 1 -> div 3, 2 -> div 4, 3 -> div 5. */
|
||||
static const uint32_t fds_div_lut[4] = {
|
||||
(1U << 16) / 2,
|
||||
(1U << 16) / 3,
|
||||
(1U << 16) / 4,
|
||||
(1U << 16) / 5
|
||||
};
|
||||
int32_t k = fdso.EnvUnits[EVOL].volume;
|
||||
int32_t idx = fdso.master_control & 0x03;
|
||||
int32_t sample;
|
||||
|
||||
if (k > 0x20) k = 0x20;
|
||||
return (fdso.cwave[b24latch68 >> 19] * k) * 4 / ((SPSG[0x9] & 0x3) + 2);
|
||||
sample = fdso.cwave[(fdso.cwave_pos >> fdso.cwave_pos_shift) & 0x3F]
|
||||
* k * 4;
|
||||
sample = (int32_t)((uint32_t)sample * fds_div_lut[idx]) >> 16;
|
||||
fdso.sample_out_cache = sample;
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t FBC = 0;
|
||||
|
||||
static void RenderSound(void) {
|
||||
int32_t end, start;
|
||||
int32_t end = (SOUNDTS << 16) / soundtsinc;
|
||||
int32_t start = FBC;
|
||||
int32_t x;
|
||||
|
||||
start = FBC;
|
||||
end = (SOUNDTS << 16) / soundtsinc;
|
||||
if (end <= start)
|
||||
return;
|
||||
if (end <= start) return;
|
||||
FBC = end;
|
||||
|
||||
if (!(SPSG[0x9] & 0x80))
|
||||
for (x = start; x < end; x++) {
|
||||
uint32_t t = FDSDoSound();
|
||||
uint32_t t;
|
||||
FDSDoSound();
|
||||
t = (uint32_t)fdso.sample_out_cache;
|
||||
t += t >> 1;
|
||||
t >>= 4;
|
||||
Wave[x >> 4] += t; /* (t>>2)-(t>>3); */ /* >>3; */
|
||||
Wave[x >> 4] += (int32_t)t;
|
||||
}
|
||||
}
|
||||
|
||||
static void RenderSoundHQ(void) {
|
||||
uint32_t x;
|
||||
|
||||
if (!(SPSG[0x9] & 0x80))
|
||||
for (x = FBC; x < SOUNDTS; x++) {
|
||||
uint32_t t = FDSDoSound();
|
||||
for (x = (uint32_t)FBC; x < (uint32_t)SOUNDTS; x++) {
|
||||
uint32_t t;
|
||||
FDSDoSound();
|
||||
t = (uint32_t)fdso.sample_out_cache;
|
||||
t += t >> 1;
|
||||
WaveHi[x] += t; /* (t<<2)-(t<<1); */
|
||||
WaveHi[x] += (int32_t)t;
|
||||
}
|
||||
FBC = SOUNDTS;
|
||||
FBC = (int32_t)SOUNDTS;
|
||||
}
|
||||
|
||||
static void HQSync(int32_t ts) {
|
||||
@@ -270,35 +426,80 @@ static void FDS_ESI(void) {
|
||||
if (FSettings.SndRate) {
|
||||
if (FSettings.soundq >= 1) {
|
||||
fdso.cycles = (int64_t)1 << 39;
|
||||
fdso.cwave_pos_shift = 22;
|
||||
fdso.mod_pos_shift = 17;
|
||||
fdso.mod_overflow_shift = 12;
|
||||
fdso.env_count_mul = 8;
|
||||
} else {
|
||||
fdso.cycles = ((int64_t)1 << 40) * FDSClock;
|
||||
fdso.cycles /= FSettings.SndRate * 16;
|
||||
fdso.cwave_pos_shift = 21;
|
||||
fdso.mod_pos_shift = 16;
|
||||
fdso.mod_overflow_shift = 11;
|
||||
fdso.env_count_mul = 4;
|
||||
}
|
||||
}
|
||||
SetReadHandler(0x4040, 0x407f, FDSWaveRead);
|
||||
SetWriteHandler(0x4040, 0x407f, FDSWaveWrite);
|
||||
SetWriteHandler(0x4080, 0x408A, FDSSWrite);
|
||||
SetReadHandler(0x4090, 0x4092, FDSSRead);
|
||||
SetReadHandler(0x4040, 0x407F, FDSWaveRead);
|
||||
SetReadHandler(0x4090, 0x4090, FDSEnvVolumeRead);
|
||||
SetReadHandler(0x4092, 0x4092, FDSEnvModRead);
|
||||
|
||||
SetWriteHandler(0x4040, 0x407F, FDSWaveWrite);
|
||||
SetWriteHandler(0x4080, 0x4080, FDSSReg0Write);
|
||||
SetWriteHandler(0x4082, 0x4082, FDSSReg1Write);
|
||||
SetWriteHandler(0x4083, 0x4083, FDSSReg2Write);
|
||||
SetWriteHandler(0x4084, 0x4084, FDSSReg3Write);
|
||||
SetWriteHandler(0x4085, 0x4085, FDSSReg4Write);
|
||||
SetWriteHandler(0x4086, 0x4086, FDSSReg5Write);
|
||||
SetWriteHandler(0x4087, 0x4087, FDSSReg6Write);
|
||||
SetWriteHandler(0x4088, 0x4088, FDSSReg7Write);
|
||||
SetWriteHandler(0x4089, 0x4089, FDSSReg8Write);
|
||||
SetWriteHandler(0x408A, 0x408A, FDSSReg9Write);
|
||||
}
|
||||
|
||||
void FDSSoundReset(void) {
|
||||
memset(&fdso, 0, sizeof(fdso));
|
||||
|
||||
/* BIOS-style register reset. Matches what the FDS BIOS does on
|
||||
* power-up; see https://github.com/bbbradsmith/nsfplay/.../nes_fds.cpp
|
||||
* Apply these BEFORE FDS_ESI so the writes don't trigger spurious
|
||||
* RenderSound calls against an unsized output buffer. */
|
||||
FDSSReg0Write(0x4080, 0x80); /* output volume = 0, env disabled */
|
||||
FDSSReg9Write(0x408A, 0xE8); /* master envelope speed */
|
||||
FDSSReg1Write(0x4082, 0x00); /* carrier freq 0 */
|
||||
FDSSReg2Write(0x4083, 0x80); /* carrier disabled */
|
||||
FDSSReg3Write(0x4084, 0x80); /* mod strength 0 */
|
||||
FDSSReg4Write(0x4085, 0x00); /* mod position 0 */
|
||||
FDSSReg5Write(0x4086, 0x00); /* mod freq 0 */
|
||||
FDSSReg6Write(0x4087, 0x80); /* mod disable */
|
||||
FDSSReg8Write(0x4089, 0x00); /* wave write disable */
|
||||
|
||||
FDS_ESI();
|
||||
|
||||
GameExpSound.HiSync = HQSync;
|
||||
GameExpSound.HiFill = RenderSoundHQ;
|
||||
GameExpSound.Fill = FDSSound;
|
||||
GameExpSound.RChange = FDS_ESI;
|
||||
}
|
||||
|
||||
static uint8_t FDSSoundRead(uint32_t A) {
|
||||
if (A >= 0x4040 && A < 0x4080) return FDSWaveRead(A);
|
||||
if (A >= 0x4090 && A < 0x4093) return FDSSRead(A);
|
||||
return X.DB;
|
||||
}
|
||||
|
||||
/* Used by FDS-conversion mappers (e.g. some Whirlwind Manu bootlegs) that
|
||||
* surface the FDS audio registers through their own write paths instead of
|
||||
* the standard FDS BIOS layout. Same signature as before the backport. */
|
||||
void FDSSoundWrite(uint32_t A, uint8_t V) {
|
||||
if (A >= 0x4040 && A < 0x4080) FDSWaveWrite(A, V);
|
||||
else if (A >= 0x4080 && A < 0x408B) FDSSWrite(A, V);
|
||||
switch (A) {
|
||||
case 0x4080: FDSSReg0Write(A, V); break;
|
||||
case 0x4082: FDSSReg1Write(A, V); break;
|
||||
case 0x4083: FDSSReg2Write(A, V); break;
|
||||
case 0x4084: FDSSReg3Write(A, V); break;
|
||||
case 0x4085: FDSSReg4Write(A, V); break;
|
||||
case 0x4086: FDSSReg5Write(A, V); break;
|
||||
case 0x4087: FDSSReg6Write(A, V); break;
|
||||
case 0x4088: FDSSReg7Write(A, V); break;
|
||||
case 0x4089: FDSSReg8Write(A, V); break;
|
||||
case 0x408A: FDSSReg9Write(A, V); break;
|
||||
default:
|
||||
if (A >= 0x4040 && A <= 0x407F) FDSWaveWrite(A, V);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void FDSSoundPower(void) {
|
||||
|
||||
Reference in New Issue
Block a user