Redo the volume envelope with better timings, locking emulation and zombie mode edge cases. Fixes #344

This commit is contained in:
Lior Halphon 2021-02-25 15:43:38 +02:00
parent 8809d8ac2f
commit 4c05ebcea6
3 changed files with 127 additions and 53 deletions

View File

@ -295,35 +295,73 @@ static void update_square_sample(GB_gameboy_t *gb, unsigned index)
requires the PCM12 register. The behavior implemented here was verified on *my*
CGB, which might behave differently from other CGB revisions, as well as from the
DMG, MGB or SGB/2 */
static void _nrx2_glitch(uint8_t *volume, uint8_t value, uint8_t old_value)
static void _nrx2_glitch(uint8_t *volume, uint8_t value, uint8_t old_value, uint8_t *countdown, GB_envelope_clock_t *lock)
{
if (lock->clock) {
*countdown = value & 7;
}
bool should_tick = (value & 7) && !(old_value & 7) && !lock->locked;
bool should_invert = (value & 8) ^ (old_value & 8);
if ((value & 0xF) == 8 && (old_value & 0xF) == 8 && !lock->locked) {
should_tick = true;
}
if (should_invert) {
// The weird way and over-the-top way clocks for this counter are connected cause
// some weird ways for it to invert
if (value & 8) {
if (!(old_value & 7) && !lock->locked) {
*volume ^= 0xF;
}
else {
*volume = 0xE - *volume;
*volume &= 0xF;
}
should_tick = false; // Somehow prevents ticking?
}
else {
*volume = 0x10 - *volume;
*volume &= 0xF;
}
}
if (should_tick) {
if (value & 8) {
(*volume)++;
}
if (((value ^ old_value) & 8)) {
*volume = 0x10 - *volume;
}
if ((value & 7) && !(old_value & 7) && *volume && !(value & 8)) {
else {
(*volume)--;
}
if ((old_value & 7) && (value & 8)) {
(*volume)--;
*volume &= 0xF;
}
else if (!(value & 7) && lock->clock) {
// *lock->locked = false; // Excepted from the schematics, but doesn't actually happen on any model?
if (!should_invert) {
if (*volume == 0xF && (value & 8)) {
lock->locked = true;
}
else if (*volume == 0 && !(value & 8)) {
lock->locked = true;
}
}
else if (*volume == 1 && !(value & 8)) {
lock->locked = true;
}
else if (*volume == 0xE && (value & 8)) {
lock->locked = true;
}
lock->clock = false;
}
}
(*volume) &= 0xF;
}
static void nrx2_glitch(GB_gameboy_t *gb, uint8_t *volume, uint8_t value, uint8_t old_value)
static void nrx2_glitch(GB_gameboy_t *gb, uint8_t *volume, uint8_t value, uint8_t old_value, uint8_t *countdown, GB_envelope_clock_t *lock)
{
if (gb->model <= GB_MODEL_CGB_C) {
_nrx2_glitch(volume, 0xFF, old_value);
_nrx2_glitch(volume, value, 0xFF);
_nrx2_glitch(volume, 0xFF, old_value, countdown, lock);
_nrx2_glitch(volume, value, 0xFF, countdown, lock);
}
else {
_nrx2_glitch(volume, value, old_value);
_nrx2_glitch(volume, value, old_value, countdown, lock);
}
}
@ -331,7 +369,7 @@ static void tick_square_envelope(GB_gameboy_t *gb, enum GB_CHANNELS index)
{
uint8_t nrx2 = gb->io_registers[index == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22];
if (gb->apu.is_square_envelope_locked[index]) return;
if (gb->apu.square_envelope_clock[index].locked) return;
if (!(nrx2 & 7)) return;
if (gb->cgb_double_speed) {
if (index == GB_SQUARE_1) {
@ -347,7 +385,7 @@ static void tick_square_envelope(GB_gameboy_t *gb, enum GB_CHANNELS index)
gb->apu.square_channels[index].current_volume++;
}
else {
gb->apu.is_square_envelope_locked[index] = true;
gb->apu.square_envelope_clock[index].locked = true;
}
}
else {
@ -355,7 +393,7 @@ static void tick_square_envelope(GB_gameboy_t *gb, enum GB_CHANNELS index)
gb->apu.square_channels[index].current_volume--;
}
else {
gb->apu.is_square_envelope_locked[index] = true;
gb->apu.square_envelope_clock[index].locked = true;
}
}
@ -368,7 +406,7 @@ static void tick_noise_envelope(GB_gameboy_t *gb)
{
uint8_t nr42 = gb->io_registers[GB_IO_NR42];
if (gb->apu.is_noise_envelope_locked) return;
if (gb->apu.noise_envelope_clock.locked) return;
if (!(nr42 & 7)) return;
if (gb->cgb_double_speed) {
@ -380,7 +418,7 @@ static void tick_noise_envelope(GB_gameboy_t *gb)
gb->apu.noise_channel.current_volume++;
}
else {
gb->apu.is_noise_envelope_locked = true;
gb->apu.noise_envelope_clock.locked = true;
}
}
else {
@ -388,7 +426,7 @@ static void tick_noise_envelope(GB_gameboy_t *gb)
gb->apu.noise_channel.current_volume--;
}
else {
gb->apu.is_noise_envelope_locked = true;
gb->apu.noise_envelope_clock.locked = true;
}
}
@ -438,29 +476,30 @@ void GB_apu_div_event(GB_gameboy_t *gb)
gb->apu.div_divider++;
}
if ((gb->apu.div_divider & 1) == 0) {
unrolled for (unsigned i = GB_SQUARE_2 + 1; i--;) {
uint8_t nrx2 = gb->io_registers[i == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22];
if (gb->apu.is_active[i] && gb->apu.square_channels[i].volume_countdown == 0) {
tick_square_envelope(gb, i);
gb->apu.square_channels[i].volume_countdown = nrx2 & 7;
}
}
if (gb->apu.is_active[GB_NOISE] && gb->apu.noise_channel.volume_countdown == 0) {
tick_noise_envelope(gb);
gb->apu.noise_channel.volume_countdown = gb->io_registers[GB_IO_NR42] & 7;
}
}
if ((gb->apu.div_divider & 7) == 7) {
unrolled for (unsigned i = GB_SQUARE_2 + 1; i--;) {
if (!gb->apu.square_envelope_clock[i].clock) {
gb->apu.square_channels[i].volume_countdown--;
gb->apu.square_channels[i].volume_countdown &= 7;
}
}
if (!gb->apu.noise_envelope_clock.clock) {
gb->apu.noise_channel.volume_countdown--;
gb->apu.noise_channel.volume_countdown &= 7;
}
}
unrolled for (unsigned i = GB_SQUARE_2 + 1; i--;) {
if (gb->apu.square_envelope_clock[i].clock) {
tick_square_envelope(gb, i);
gb->apu.square_envelope_clock[i].clock = false;
}
}
if (gb->apu.noise_envelope_clock.clock) {
tick_noise_envelope(gb);
gb->apu.noise_envelope_clock.clock = false;
}
if ((gb->apu.div_divider & 1) == 1) {
unrolled for (unsigned i = GB_SQUARE_2 + 1; i--;) {
@ -500,6 +539,20 @@ void GB_apu_div_event(GB_gameboy_t *gb)
}
}
void GB_apu_div_secondary_event(GB_gameboy_t *gb)
{
unrolled for (unsigned i = GB_SQUARE_2 + 1; i--;) {
uint8_t nrx2 = gb->io_registers[i == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22];
if (gb->apu.is_active[i] && gb->apu.square_channels[i].volume_countdown == 0) {
gb->apu.square_envelope_clock[i].clock = gb->apu.square_channels[i].volume_countdown = nrx2 & 7;
}
}
if (gb->apu.is_active[GB_NOISE] && gb->apu.noise_channel.volume_countdown == 0) {
gb->apu.noise_envelope_clock.clock = gb->apu.noise_channel.volume_countdown = gb->io_registers[GB_IO_NR42] & 7;
}
}
static void step_lfsr(GB_gameboy_t *gb, unsigned cycles_offset)
{
unsigned high_bit_mask = gb->apu.noise_channel.narrow ? 0x4040 : 0x4000;
@ -950,7 +1003,9 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
update_sample(gb, index, 0, 0);
}
else if (gb->apu.is_active[index]) {
nrx2_glitch(gb, &gb->apu.square_channels[index].current_volume, value, gb->io_registers[reg]);
nrx2_glitch(gb, &gb->apu.square_channels[index].current_volume,
value, gb->io_registers[reg], &gb->apu.square_channels[index].volume_countdown,
&gb->apu.square_envelope_clock[index]);
update_square_sample(gb, index);
}
@ -990,7 +1045,8 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
if (value & 0x80) {
/* Current sample index remains unchanged when restarting channels 1 or 2. It is only reset by
turning the APU off. */
gb->apu.is_square_envelope_locked[index] = false;
gb->apu.square_envelope_clock[index].locked = false;
gb->apu.square_envelope_clock[index].clock = false;
if (!gb->apu.is_active[index]) {
gb->apu.square_channels[index].sample_countdown = (gb->apu.square_channels[index].sample_length ^ 0x7FF) * 2 + 6 - gb->apu.lf_div;
if (gb->model <= GB_MODEL_CGB_C && gb->apu.lf_div) {
@ -1019,7 +1075,6 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
}
}
gb->apu.square_channels[index].current_volume = gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] >> 4;
/* The volume changes caused by NRX4 sound start take effect instantly (i.e. the effect the previously
started sound). The playback itself is not instant which is why we don't update the sample for other
cases. */
@ -1196,7 +1251,9 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
update_sample(gb, GB_NOISE, 0, 0);
}
else if (gb->apu.is_active[GB_NOISE]) {
nrx2_glitch(gb, &gb->apu.noise_channel.current_volume, value, gb->io_registers[reg]);
nrx2_glitch(gb, &gb->apu.noise_channel.current_volume,
value, gb->io_registers[reg], &gb->apu.noise_channel.volume_countdown,
&gb->apu.noise_envelope_clock);
update_sample(gb, GB_NOISE,
gb->apu.current_lfsr_sample ?
gb->apu.noise_channel.current_volume : 0,
@ -1241,7 +1298,8 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
case GB_IO_NR44: {
if (value & 0x80) {
gb->apu.is_noise_envelope_locked = false;
gb->apu.noise_envelope_clock.locked = false;
gb->apu.noise_envelope_clock.clock = false;
if (!GB_is_cgb(gb) && (gb->apu.noise_channel.alignment & 3) != 0) {
gb->apu.channel_4_dmg_delayed_start = 6;
}

View File

@ -46,6 +46,13 @@ enum GB_CHANNELS {
GB_N_CHANNELS
};
typedef struct
{
bool locked:1;
bool clock:1; // Represents FOSY on channel 4
unsigned padding:6;
} GB_envelope_clock_t;
typedef void (*GB_sample_callback_t)(GB_gameboy_t *gb, GB_sample_t *sample);
typedef struct
@ -127,8 +134,8 @@ typedef struct
uint8_t channel_4_dmg_delayed_start;
uint16_t channel1_completed_addend;
bool is_square_envelope_locked[2];
bool is_noise_envelope_locked;
GB_envelope_clock_t square_envelope_clock[2];
GB_envelope_clock_t noise_envelope_clock;
} GB_apu_t;
typedef enum {
@ -173,6 +180,7 @@ bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index);
void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value);
uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg);
void GB_apu_div_event(GB_gameboy_t *gb);
void GB_apu_div_secondary_event(GB_gameboy_t *gb);
void GB_apu_init(GB_gameboy_t *gb);
void GB_apu_run(GB_gameboy_t *gb);
void GB_apu_update_cycles_per_sample(GB_gameboy_t *gb);

View File

@ -137,20 +137,28 @@ static void increase_tima(GB_gameboy_t *gb)
}
}
static void GB_set_internal_div_counter(GB_gameboy_t *gb, uint32_t value)
static void GB_set_internal_div_counter(GB_gameboy_t *gb, uint16_t value)
{
/* TIMA increases when a specific high-bit becomes a low-bit. */
value &= INTERNAL_DIV_CYCLES - 1;
uint32_t triggers = gb->div_counter & ~value;
uint16_t triggers = gb->div_counter & ~value;
if ((gb->io_registers[GB_IO_TAC] & 4) && (triggers & GB_TAC_TRIGGER_BITS[gb->io_registers[GB_IO_TAC] & 3])) {
increase_tima(gb);
}
/* TODO: Can switching to double speed mode trigger an event? */
if (triggers & (gb->cgb_double_speed? 0x2000 : 0x1000)) {
uint16_t apu_bit = gb->cgb_double_speed? 0x2000 : 0x1000;
if (triggers & apu_bit) {
GB_apu_run(gb);
GB_apu_div_event(gb);
}
else {
uint16_t secondary_triggers = ~gb->div_counter & value;
if (secondary_triggers & apu_bit) {
GB_apu_run(gb);
GB_apu_div_secondary_event(gb);
}
}
gb->div_counter = value;
}