Accuracy improvements, especially to the length control
This commit is contained in:
parent
d65c2247e5
commit
ab5611119a
140
Core/apu.c
140
Core/apu.c
@ -73,42 +73,54 @@ static void render(GB_gameboy_t *gb)
|
|||||||
void GB_apu_div_event(GB_gameboy_t *gb)
|
void GB_apu_div_event(GB_gameboy_t *gb)
|
||||||
{
|
{
|
||||||
if (!gb->apu.global_enable) return;
|
if (!gb->apu.global_enable) return;
|
||||||
for (unsigned i = GB_SQUARE_2 + 1; i--;) {
|
gb->apu.div_divider++;
|
||||||
if (gb->apu.square_channels[i].length_enabled) {
|
|
||||||
if (gb->apu.square_channels[i].pulse_length) {
|
if ((gb->apu.div_divider & 1) == 1) {
|
||||||
if (!--gb->apu.square_channels[i].pulse_length) {
|
for (unsigned i = GB_SQUARE_2 + 1; i--;) {
|
||||||
gb->apu.is_active[i] = false;
|
if (gb->apu.square_channels[i].length_enabled) {
|
||||||
update_sample(gb, i, 0, 0);
|
if (gb->apu.square_channels[i].pulse_length) {
|
||||||
|
if (!--gb->apu.square_channels[i].pulse_length) {
|
||||||
|
gb->apu.is_active[i] = false;
|
||||||
|
update_sample(gb, i, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t nrx2 = gb->io_registers[i == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22];
|
||||||
|
|
||||||
|
if (gb->apu.square_channels[i].volume_countdown) {
|
||||||
|
if (!--gb->apu.square_channels[i].volume_countdown) {
|
||||||
|
if ((nrx2 & 8) && gb->apu.square_channels[i].current_volume < 0xF) {
|
||||||
|
gb->apu.square_channels[i].current_volume++;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (!(nrx2 & 8) && gb->apu.square_channels[i].current_volume > 0) {
|
||||||
|
gb->apu.square_channels[i].current_volume--;
|
||||||
|
}
|
||||||
|
|
||||||
|
gb->apu.square_channels[i].volume_countdown = (nrx2 & 7) * 4;
|
||||||
|
|
||||||
|
uint8_t duty = gb->io_registers[i == GB_SQUARE_1? GB_IO_NR11 :GB_IO_NR21] >> 6;
|
||||||
|
update_sample(gb, i,
|
||||||
|
duties[gb->apu.square_channels[i].current_sample_index + duty * 8]?
|
||||||
|
gb->apu.square_channels[i].current_volume : 0,
|
||||||
|
0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t nrx2 = gb->io_registers[i == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22];
|
if (gb->apu.wave_channel.length_enabled) {
|
||||||
|
if (gb->apu.wave_channel.pulse_length) {
|
||||||
if (gb->apu.square_channels[i].volume_countdown) {
|
if (!--gb->apu.wave_channel.pulse_length) {
|
||||||
if (!--gb->apu.square_channels[i].volume_countdown) {
|
gb->apu.is_active[GB_WAVE] = false;
|
||||||
if ((nrx2 & 8) && gb->apu.square_channels[i].current_volume < 0xF) {
|
gb->apu.wave_channel.current_sample = 0;
|
||||||
gb->apu.square_channels[i].current_volume++;
|
update_sample(gb, GB_WAVE, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (!(nrx2 & 8) && gb->apu.square_channels[i].current_volume > 0) {
|
|
||||||
gb->apu.square_channels[i].current_volume--;
|
|
||||||
}
|
|
||||||
|
|
||||||
gb->apu.square_channels[i].volume_countdown = (nrx2 & 7) * 8;
|
|
||||||
|
|
||||||
uint8_t duty = gb->io_registers[i == GB_SQUARE_1? GB_IO_NR11 :GB_IO_NR21] >> 6;
|
|
||||||
update_sample(gb, i,
|
|
||||||
duties[gb->apu.square_channels[i].current_sample_index + duty * 8]?
|
|
||||||
gb->apu.square_channels[i].current_volume : 0,
|
|
||||||
0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gb->apu.square_sweep_div++;
|
if ((gb->apu.div_divider & 3) == 3) {
|
||||||
|
|
||||||
if ((gb->apu.square_sweep_div & 3) == 3) {
|
|
||||||
if (gb->apu.square_sweep_countdown) {
|
if (gb->apu.square_sweep_countdown) {
|
||||||
if (!--gb->apu.square_sweep_countdown) {
|
if (!--gb->apu.square_sweep_countdown) {
|
||||||
gb->apu.square_channels[GB_SQUARE_1].sample_length ^= 0x7FF;
|
gb->apu.square_channels[GB_SQUARE_1].sample_length ^= 0x7FF;
|
||||||
@ -130,16 +142,6 @@ void GB_apu_div_event(GB_gameboy_t *gb)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gb->apu.wave_channel.length_enabled) {
|
|
||||||
if (gb->apu.wave_channel.pulse_length) {
|
|
||||||
if (!--gb->apu.wave_channel.pulse_length) {
|
|
||||||
gb->apu.is_active[GB_WAVE] = false;
|
|
||||||
gb->apu.wave_channel.current_sample = 0;
|
|
||||||
update_sample(gb, GB_WAVE, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -252,10 +254,11 @@ void GB_apu_init(GB_gameboy_t *gb)
|
|||||||
memset(&gb->apu, 0, sizeof(gb->apu));
|
memset(&gb->apu, 0, sizeof(gb->apu));
|
||||||
// gb->apu.wave_channels[0].duty = gb->apu.wave_channels[1].duty = 4;
|
// gb->apu.wave_channels[0].duty = gb->apu.wave_channels[1].duty = 4;
|
||||||
// gb->apu.lfsr = 0x7FFF;
|
// gb->apu.lfsr = 0x7FFF;
|
||||||
gb->io_registers[GB_IO_NR50] = 0x77;
|
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
gb->apu.left_enabled[i] = gb->apu.right_enabled[i] = true;
|
gb->apu.left_enabled[i] = gb->apu.right_enabled[i] = true;
|
||||||
}
|
}
|
||||||
|
gb->apu.square_channels[GB_SQUARE_1].sample_length = 0x7FF;
|
||||||
|
gb->apu.square_channels[GB_SQUARE_2].sample_length = 0x7FF;
|
||||||
gb->apu.wave_channel.sample_length = 0x7FF;
|
gb->apu.wave_channel.sample_length = 0x7FF;
|
||||||
gb->apu.square_carry = 1;
|
gb->apu.square_carry = 1;
|
||||||
}
|
}
|
||||||
@ -314,7 +317,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
|||||||
}
|
}
|
||||||
|
|
||||||
gb->io_registers[reg] = value;
|
gb->io_registers[reg] = value;
|
||||||
|
|
||||||
switch (reg) {
|
switch (reg) {
|
||||||
/* Globals */
|
/* Globals */
|
||||||
case GB_IO_NR50:
|
case GB_IO_NR50:
|
||||||
@ -347,7 +350,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
|||||||
case GB_IO_NR11:
|
case GB_IO_NR11:
|
||||||
case GB_IO_NR21: {
|
case GB_IO_NR21: {
|
||||||
unsigned index = reg == GB_IO_NR21? GB_SQUARE_2: GB_SQUARE_1;
|
unsigned index = reg == GB_IO_NR21? GB_SQUARE_2: GB_SQUARE_1;
|
||||||
gb->apu.square_channels[index].pulse_length = (0x40 - (value & 0x3f)) * 2 - 1;
|
gb->apu.square_channels[index].pulse_length = (0x40 - (value & 0x3f));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -376,7 +379,6 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
|||||||
case GB_IO_NR14:
|
case GB_IO_NR14:
|
||||||
case GB_IO_NR24: {
|
case GB_IO_NR24: {
|
||||||
unsigned index = reg == GB_IO_NR24? GB_SQUARE_2: GB_SQUARE_1;
|
unsigned index = reg == GB_IO_NR24? GB_SQUARE_2: GB_SQUARE_1;
|
||||||
gb->apu.square_channels[index].length_enabled = value & 0x40;
|
|
||||||
gb->apu.square_channels[index].sample_length &= 0xFF;
|
gb->apu.square_channels[index].sample_length &= 0xFF;
|
||||||
gb->apu.square_channels[index].sample_length |= ((~value) & 7) << 8;
|
gb->apu.square_channels[index].sample_length |= ((~value) & 7) << 8;
|
||||||
if (value & 0x80) {
|
if (value & 0x80) {
|
||||||
@ -398,16 +400,35 @@ 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;
|
gb->apu.square_channels[index].current_volume = gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] >> 4;
|
||||||
gb->apu.square_channels[index].volume_countdown = (gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] & 7) * 8;
|
gb->apu.square_channels[index].volume_countdown = (gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] & 7) * 4;
|
||||||
|
|
||||||
if ((gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] & 0xF8) != 0) {
|
if ((gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] & 0xF8) != 0) {
|
||||||
gb->apu.is_active[index] = true;
|
gb->apu.is_active[index] = true;
|
||||||
}
|
}
|
||||||
if (gb->apu.square_channels[index].pulse_length == 0) {
|
if (gb->apu.square_channels[index].pulse_length == 0) {
|
||||||
gb->apu.square_channels[index].pulse_length = 0x7F;
|
gb->apu.square_channels[index].pulse_length = 0x40;
|
||||||
|
gb->apu.square_channels[index].length_enabled = false;
|
||||||
}
|
}
|
||||||
/* Note that we don't change the sample just yet! This was verified on hardware. */
|
/* Note that we don't change the sample just yet! This was verified on hardware. */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* APU glitch - if length is enabled while the DIV-divider's LSB is 1, tick the length once. */
|
||||||
|
if ((value & 0x40) &&
|
||||||
|
!gb->apu.square_channels[index].length_enabled &&
|
||||||
|
(gb->apu.div_divider & 1) &&
|
||||||
|
gb->apu.square_channels[index].pulse_length) {
|
||||||
|
gb->apu.square_channels[index].pulse_length--;
|
||||||
|
if (gb->apu.square_channels[index].pulse_length == 0) {
|
||||||
|
if (value & 0x80) {
|
||||||
|
gb->apu.square_channels[index].pulse_length = 0x3F;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
update_sample(gb, index, 0, 0);
|
||||||
|
gb->apu.is_active[index] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gb->apu.square_channels[index].length_enabled = value & 0x40;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -421,7 +442,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case GB_IO_NR31:
|
case GB_IO_NR31:
|
||||||
gb->apu.wave_channel.pulse_length = (0x100 - value) * 2 - 1;
|
gb->apu.wave_channel.pulse_length = (0x100 - value);
|
||||||
break;
|
break;
|
||||||
case GB_IO_NR32:
|
case GB_IO_NR32:
|
||||||
gb->apu.wave_channel.shift = (uint8_t[]){4, 0, 1, 2}[(value >> 5) & 3];
|
gb->apu.wave_channel.shift = (uint8_t[]){4, 0, 1, 2}[(value >> 5) & 3];
|
||||||
@ -432,13 +453,15 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
|||||||
gb->apu.wave_channel.sample_length |= (~value) & 0xFF;
|
gb->apu.wave_channel.sample_length |= (~value) & 0xFF;
|
||||||
break;
|
break;
|
||||||
case GB_IO_NR34:
|
case GB_IO_NR34:
|
||||||
gb->apu.wave_channel.length_enabled = value & 0x40;
|
|
||||||
gb->apu.wave_channel.sample_length &= 0xFF;
|
gb->apu.wave_channel.sample_length &= 0xFF;
|
||||||
gb->apu.wave_channel.sample_length |= ((~value) & 7) << 8;
|
gb->apu.wave_channel.sample_length |= ((~value) & 7) << 8;
|
||||||
if ((value & 0x80) && gb->apu.wave_channel.enable) {
|
if ((value & 0x80)) {
|
||||||
/* DMG bug: wave RAM gets corrupted if the channel is retriggerred 1 cycle before the APU
|
/* DMG bug: wave RAM gets corrupted if the channel is retriggerred 1 cycle before the APU
|
||||||
reads from it. */
|
reads from it. */
|
||||||
if (!gb->is_cgb && gb->apu.is_active[GB_WAVE] && gb->apu.wave_channel.sample_countdown == 0) {
|
if (!gb->is_cgb &&
|
||||||
|
gb->apu.is_active[GB_WAVE] &&
|
||||||
|
gb->apu.wave_channel.sample_countdown == 0 &&
|
||||||
|
gb->apu.wave_channel.enable) {
|
||||||
unsigned offset = ((gb->apu.wave_channel.current_sample_index + 1) >> 1) & 0xF;
|
unsigned offset = ((gb->apu.wave_channel.current_sample_index + 1) >> 1) & 0xF;
|
||||||
|
|
||||||
/* On SGB2 (and probably SGB1 and MGB as well) this behavior is not accurate,
|
/* On SGB2 (and probably SGB1 and MGB as well) this behavior is not accurate,
|
||||||
@ -461,10 +484,31 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
|||||||
gb->apu.wave_channel.sample_countdown = gb->apu.wave_channel.sample_length + 3;
|
gb->apu.wave_channel.sample_countdown = gb->apu.wave_channel.sample_length + 3;
|
||||||
gb->apu.wave_channel.current_sample_index = 0;
|
gb->apu.wave_channel.current_sample_index = 0;
|
||||||
if (gb->apu.wave_channel.pulse_length == 0) {
|
if (gb->apu.wave_channel.pulse_length == 0) {
|
||||||
gb->apu.wave_channel.pulse_length = 0x1FF;
|
gb->apu.wave_channel.pulse_length = 0x100;
|
||||||
|
gb->apu.wave_channel.length_enabled = false;
|
||||||
}
|
}
|
||||||
/* Note that we don't change the sample just yet! This was verified on hardware. */
|
/* Note that we don't change the sample just yet! This was verified on hardware. */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* APU glitch - if length is enabled while the DIV-divider's LSB is 1, tick the length once. */
|
||||||
|
if ((value & 0x40) &&
|
||||||
|
!gb->apu.wave_channel.length_enabled &&
|
||||||
|
(gb->apu.div_divider & 1) &&
|
||||||
|
gb->apu.wave_channel.pulse_length) {
|
||||||
|
gb->apu.wave_channel.pulse_length--;
|
||||||
|
if (gb->apu.wave_channel.pulse_length == 0) {
|
||||||
|
if (value & 0x80) {
|
||||||
|
gb->apu.wave_channel.pulse_length = 0xFF;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
update_sample(gb, GB_WAVE, 0, 0);
|
||||||
|
gb->apu.is_active[GB_WAVE] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gb->apu.wave_channel.length_enabled = value & 0x40;
|
||||||
|
gb->apu.is_active[GB_WAVE] &= gb->apu.wave_channel.enable;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
13
Core/apu.h
13
Core/apu.h
@ -11,8 +11,7 @@
|
|||||||
#define CH_STEP (MAX_CH_AMP/0xF/7)
|
#define CH_STEP (MAX_CH_AMP/0xF/7)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Lengths are in either DIV ticks (512Hz, triggered by the DIV register) or
|
/* APU ticks are 2MHz, triggered by an internal APU clock. */
|
||||||
APU ticks (2MHz, triggered by an internal APU clock) */
|
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
@ -38,29 +37,31 @@ typedef struct
|
|||||||
bool right_enabled[GB_N_CHANNELS];
|
bool right_enabled[GB_N_CHANNELS];
|
||||||
bool is_active[GB_N_CHANNELS];
|
bool is_active[GB_N_CHANNELS];
|
||||||
|
|
||||||
|
uint8_t div_divider; // The DIV register ticks the APU at 512Hz, but is then divided
|
||||||
|
// once more to generate 128Hz and 64Hz clocks
|
||||||
|
|
||||||
uint8_t square_carry; // The square channels tick at 1MHz instead of 2,
|
uint8_t square_carry; // The square channels tick at 1MHz instead of 2,
|
||||||
// so we need a carry to divide the signal
|
// so we need a carry to divide the signal
|
||||||
|
|
||||||
uint8_t square_sweep_div; // The DIV-APU ticks are divided by 4 to handle tone sweeping
|
|
||||||
uint8_t square_sweep_countdown; // In 128Hz
|
uint8_t square_sweep_countdown; // In 128Hz
|
||||||
uint8_t square_sweep_stop_countdown; // In 2 MHz
|
uint8_t square_sweep_stop_countdown; // In 2 MHz
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
uint16_t pulse_length; // Reloaded from NRX1 (xorred), in DIV ticks
|
uint16_t pulse_length; // Reloaded from NRX1 (xorred), in 256Hz DIV ticks
|
||||||
uint8_t current_volume; // Reloaded from NRX2
|
uint8_t current_volume; // Reloaded from NRX2
|
||||||
uint8_t volume_countdown; // Reloaded from NRX2
|
uint8_t volume_countdown; // Reloaded from NRX2
|
||||||
uint8_t current_sample_index;
|
uint8_t current_sample_index;
|
||||||
bool sample_emitted;
|
bool sample_emitted;
|
||||||
|
|
||||||
uint16_t sample_countdown; // in APU ticks
|
uint16_t sample_countdown; // in APU ticks
|
||||||
uint16_t sample_length; // Reloaded from NRX3, NRX4, in APU ticks
|
uint16_t sample_length; // From NRX3, NRX4, in APU ticks
|
||||||
bool length_enabled; // NRX4
|
bool length_enabled; // NRX4
|
||||||
|
|
||||||
} square_channels[2];
|
} square_channels[2];
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
bool enable; // NR30
|
bool enable; // NR30
|
||||||
uint16_t pulse_length; // Reloaded from NR31 (xorred), in DIV ticks
|
uint16_t pulse_length; // Reloaded from NR31 (xorred), in 256Hz DIV ticks
|
||||||
uint8_t shift; // NR32
|
uint8_t shift; // NR32
|
||||||
uint16_t sample_length; // NR33, NR34, in APU ticks
|
uint16_t sample_length; // NR33, NR34, in APU ticks
|
||||||
bool length_enabled; // NR34
|
bool length_enabled; // NR34
|
||||||
|
Loading…
Reference in New Issue
Block a user