Add APU-related debugger commands

This change includes making one of the APU functions public
This commit is contained in:
ISSOtm 2019-05-15 12:39:08 +02:00
parent 795823e372
commit 40f83c8f25
3 changed files with 315 additions and 144 deletions

View File

@ -21,34 +21,34 @@ static void refresh_channel(GB_gameboy_t *gb, unsigned index, unsigned cycles_of
gb->apu_output.last_update[index] = gb->apu_output.cycles_since_render + cycles_offset; gb->apu_output.last_update[index] = gb->apu_output.cycles_since_render + cycles_offset;
} }
static bool is_DAC_enabled(GB_gameboy_t *gb, unsigned index) bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index)
{ {
switch (index) { switch (index) {
case GB_SQUARE_1: case GB_SQUARE_1:
return gb->io_registers[GB_IO_NR12] & 0xF8; return gb->io_registers[GB_IO_NR12] & 0xF8;
case GB_SQUARE_2: case GB_SQUARE_2:
return gb->io_registers[GB_IO_NR22] & 0xF8; return gb->io_registers[GB_IO_NR22] & 0xF8;
case GB_WAVE: case GB_WAVE:
return gb->apu.wave_channel.enable; return gb->apu.wave_channel.enable;
case GB_NOISE: case GB_NOISE:
return gb->io_registers[GB_IO_NR42] & 0xF8; return gb->io_registers[GB_IO_NR42] & 0xF8;
} }
return 0; return 0;
} }
static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsigned cycles_offset) static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsigned cycles_offset)
{ {
if (!is_DAC_enabled(gb, index)) { if (!GB_apu_is_DAC_enabled(gb, index)) {
value = gb->apu.samples[index]; value = gb->apu.samples[index];
} }
else { else {
gb->apu.samples[index] = value; gb->apu.samples[index] = value;
} }
if (gb->apu_output.sample_rate) { if (gb->apu_output.sample_rate) {
unsigned right_volume = 0; unsigned right_volume = 0;
if (gb->io_registers[GB_IO_NR51] & (1 << index)) { if (gb->io_registers[GB_IO_NR51] & (1 << index)) {
@ -73,7 +73,7 @@ static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest)
UNROLL UNROLL
for (unsigned i = 0; i < GB_N_CHANNELS; i++) { for (unsigned i = 0; i < GB_N_CHANNELS; i++) {
double multiplier = CH_STEP; double multiplier = CH_STEP;
if (!is_DAC_enabled(gb, i)) { if (!GB_apu_is_DAC_enabled(gb, i)) {
gb->apu_output.dac_discharge[i] -= ((double) DAC_DECAY_SPEED) / gb->apu_output.sample_rate; gb->apu_output.dac_discharge[i] -= ((double) DAC_DECAY_SPEED) / gb->apu_output.sample_rate;
if (gb->apu_output.dac_discharge[i] < 0) { if (gb->apu_output.dac_discharge[i] < 0) {
multiplier = 0; multiplier = 0;
@ -113,7 +113,7 @@ static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest)
(GB_sample_t) {output.left - gb->apu_output.highpass_diff.left, (GB_sample_t) {output.left - gb->apu_output.highpass_diff.left,
output.right - gb->apu_output.highpass_diff.right} : output.right - gb->apu_output.highpass_diff.right} :
output; output;
switch (gb->apu_output.highpass_mode) { switch (gb->apu_output.highpass_mode) {
case GB_HIGHPASS_OFF: case GB_HIGHPASS_OFF:
gb->apu_output.highpass_diff = (GB_double_sample_t) {0, 0}; gb->apu_output.highpass_diff = (GB_double_sample_t) {0, 0};
@ -146,10 +146,10 @@ static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest)
gb->apu_output.highpass_diff = (GB_double_sample_t) gb->apu_output.highpass_diff = (GB_double_sample_t)
{left_volume * (1 - gb->apu_output.highpass_rate) + gb->apu_output.highpass_diff.left * gb->apu_output.highpass_rate, {left_volume * (1 - gb->apu_output.highpass_rate) + gb->apu_output.highpass_diff.left * gb->apu_output.highpass_rate,
right_volume * (1 - gb->apu_output.highpass_rate) + gb->apu_output.highpass_diff.right * gb->apu_output.highpass_rate}; right_volume * (1 - gb->apu_output.highpass_rate) + gb->apu_output.highpass_diff.right * gb->apu_output.highpass_rate};
case GB_HIGHPASS_MAX:; case GB_HIGHPASS_MAX:;
} }
} }
if (dest) { if (dest) {
*dest = filtered_output; *dest = filtered_output;
@ -176,7 +176,7 @@ static uint16_t new_sweep_sample_legnth(GB_gameboy_t *gb)
static void update_square_sample(GB_gameboy_t *gb, unsigned index) static void update_square_sample(GB_gameboy_t *gb, unsigned index)
{ {
if (gb->apu.square_channels[index].current_sample_index & 0x80) return; if (gb->apu.square_channels[index].current_sample_index & 0x80) return;
uint8_t duty = gb->io_registers[index == GB_SQUARE_1? GB_IO_NR11 :GB_IO_NR21] >> 6; uint8_t duty = gb->io_registers[index == GB_SQUARE_1? GB_IO_NR11 :GB_IO_NR21] >> 6;
update_sample(gb, index, update_sample(gb, index,
duties[gb->apu.square_channels[index].current_sample_index + duty * 8]? duties[gb->apu.square_channels[index].current_sample_index + duty * 8]?
@ -195,38 +195,38 @@ static void nrx2_glitch(uint8_t *volume, uint8_t value, uint8_t old_value)
if (value & 8) { if (value & 8) {
(*volume)++; (*volume)++;
} }
if (((value ^ old_value) & 8)) { if (((value ^ old_value) & 8)) {
*volume = 0x10 - *volume; *volume = 0x10 - *volume;
} }
if ((value & 7) && !(old_value & 7) && *volume && !(value & 8)) { if ((value & 7) && !(old_value & 7) && *volume && !(value & 8)) {
(*volume)--; (*volume)--;
} }
if ((old_value & 7) && (value & 8)) { if ((old_value & 7) && (value & 8)) {
(*volume)--; (*volume)--;
} }
(*volume) &= 0xF; (*volume) &= 0xF;
} }
static void tick_square_envelope(GB_gameboy_t *gb, enum GB_CHANNELS index) 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]; uint8_t nrx2 = gb->io_registers[index == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22];
if (gb->apu.square_channels[index].volume_countdown || (nrx2 & 7)) { if (gb->apu.square_channels[index].volume_countdown || (nrx2 & 7)) {
if (!gb->apu.square_channels[index].volume_countdown || !--gb->apu.square_channels[index].volume_countdown) { if (!gb->apu.square_channels[index].volume_countdown || !--gb->apu.square_channels[index].volume_countdown) {
if ((nrx2 & 8) && gb->apu.square_channels[index].current_volume < 0xF) { if ((nrx2 & 8) && gb->apu.square_channels[index].current_volume < 0xF) {
gb->apu.square_channels[index].current_volume++; gb->apu.square_channels[index].current_volume++;
} }
else if (!(nrx2 & 8) && gb->apu.square_channels[index].current_volume > 0) { else if (!(nrx2 & 8) && gb->apu.square_channels[index].current_volume > 0) {
gb->apu.square_channels[index].current_volume--; gb->apu.square_channels[index].current_volume--;
} }
gb->apu.square_channels[index].volume_countdown = nrx2 & 7; gb->apu.square_channels[index].volume_countdown = nrx2 & 7;
if (gb->apu.is_active[index]) { if (gb->apu.is_active[index]) {
update_square_sample(gb, index); update_square_sample(gb, index);
} }
@ -237,19 +237,19 @@ static void tick_square_envelope(GB_gameboy_t *gb, enum GB_CHANNELS index)
static void tick_noise_envelope(GB_gameboy_t *gb) static void tick_noise_envelope(GB_gameboy_t *gb)
{ {
uint8_t nr42 = gb->io_registers[GB_IO_NR42]; uint8_t nr42 = gb->io_registers[GB_IO_NR42];
if (gb->apu.noise_channel.volume_countdown || (nr42 & 7)) { if (gb->apu.noise_channel.volume_countdown || (nr42 & 7)) {
if (!--gb->apu.noise_channel.volume_countdown) { if (!--gb->apu.noise_channel.volume_countdown) {
if ((nr42 & 8) && gb->apu.noise_channel.current_volume < 0xF) { if ((nr42 & 8) && gb->apu.noise_channel.current_volume < 0xF) {
gb->apu.noise_channel.current_volume++; gb->apu.noise_channel.current_volume++;
} }
else if (!(nr42 & 8) && gb->apu.noise_channel.current_volume > 0) { else if (!(nr42 & 8) && gb->apu.noise_channel.current_volume > 0) {
gb->apu.noise_channel.current_volume--; gb->apu.noise_channel.current_volume--;
} }
gb->apu.noise_channel.volume_countdown = nr42 & 7; gb->apu.noise_channel.volume_countdown = nr42 & 7;
if (gb->apu.is_active[GB_NOISE]) { if (gb->apu.is_active[GB_NOISE]) {
update_sample(gb, GB_NOISE, update_sample(gb, GB_NOISE,
(gb->apu.noise_channel.lfsr & 1) ? (gb->apu.noise_channel.lfsr & 1) ?
@ -276,20 +276,20 @@ void GB_apu_div_event(GB_gameboy_t *gb)
tick_square_envelope(gb, i); tick_square_envelope(gb, i);
} }
} }
if (gb->apu.is_active[GB_NOISE] && gb->apu.noise_channel.volume_countdown == 0 && (gb->io_registers[GB_IO_NR42] & 7)) { if (gb->apu.is_active[GB_NOISE] && gb->apu.noise_channel.volume_countdown == 0 && (gb->io_registers[GB_IO_NR42] & 7)) {
tick_noise_envelope(gb); tick_noise_envelope(gb);
} }
} }
if ((gb->apu.div_divider & 7) == 0) { if ((gb->apu.div_divider & 7) == 0) {
for (unsigned i = GB_SQUARE_2 + 1; i--;) { for (unsigned i = GB_SQUARE_2 + 1; i--;) {
tick_square_envelope(gb, i); tick_square_envelope(gb, i);
} }
tick_noise_envelope(gb); tick_noise_envelope(gb);
} }
if ((gb->apu.div_divider & 1) == 1) { if ((gb->apu.div_divider & 1) == 1) {
for (unsigned i = GB_SQUARE_2 + 1; i--;) { for (unsigned i = GB_SQUARE_2 + 1; i--;) {
if (gb->apu.square_channels[i].length_enabled) { if (gb->apu.square_channels[i].length_enabled) {
@ -301,7 +301,7 @@ void GB_apu_div_event(GB_gameboy_t *gb)
} }
} }
} }
if (gb->apu.wave_channel.length_enabled) { if (gb->apu.wave_channel.length_enabled) {
if (gb->apu.wave_channel.pulse_length) { if (gb->apu.wave_channel.pulse_length) {
if (!--gb->apu.wave_channel.pulse_length) { if (!--gb->apu.wave_channel.pulse_length) {
@ -311,7 +311,7 @@ void GB_apu_div_event(GB_gameboy_t *gb)
} }
} }
} }
if (gb->apu.noise_channel.length_enabled) { if (gb->apu.noise_channel.length_enabled) {
if (gb->apu.noise_channel.pulse_length) { if (gb->apu.noise_channel.pulse_length) {
if (!--gb->apu.noise_channel.pulse_length) { if (!--gb->apu.noise_channel.pulse_length) {
@ -321,7 +321,7 @@ void GB_apu_div_event(GB_gameboy_t *gb)
} }
} }
} }
if ((gb->apu.div_divider & 3) == 3) { if ((gb->apu.div_divider & 3) == 3) {
if (!gb->apu.sweep_enabled) { if (!gb->apu.sweep_enabled) {
return; return;
@ -333,12 +333,12 @@ void GB_apu_div_event(GB_gameboy_t *gb)
gb->apu.shadow_sweep_sample_legnth = gb->apu.shadow_sweep_sample_legnth =
gb->apu.new_sweep_sample_legnth; gb->apu.new_sweep_sample_legnth;
} }
if (gb->io_registers[GB_IO_NR10] & 0x70) { if (gb->io_registers[GB_IO_NR10] & 0x70) {
/* Recalculation and overflow check only occurs after a delay */ /* Recalculation and overflow check only occurs after a delay */
gb->apu.square_sweep_calculate_countdown = 0x13 - gb->apu.lf_div; gb->apu.square_sweep_calculate_countdown = 0x13 - gb->apu.lf_div;
} }
gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7); gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7);
if (!gb->apu.square_sweep_countdown) gb->apu.square_sweep_countdown = 8; if (!gb->apu.square_sweep_countdown) gb->apu.square_sweep_countdown = 8;
} }
@ -353,11 +353,11 @@ void GB_apu_run(GB_gameboy_t *gb)
uint8_t cycles = gb->apu.apu_cycles >> 2; uint8_t cycles = gb->apu.apu_cycles >> 2;
gb->apu.apu_cycles = 0; gb->apu.apu_cycles = 0;
if (!cycles) return; if (!cycles) return;
/* To align the square signal to 1MHz */ /* To align the square signal to 1MHz */
gb->apu.lf_div ^= cycles & 1; gb->apu.lf_div ^= cycles & 1;
gb->apu.noise_channel.alignment += cycles; gb->apu.noise_channel.alignment += cycles;
if (gb->apu.square_sweep_calculate_countdown) { if (gb->apu.square_sweep_calculate_countdown) {
if (gb->apu.square_sweep_calculate_countdown > cycles) { if (gb->apu.square_sweep_calculate_countdown > cycles) {
gb->apu.square_sweep_calculate_countdown -= cycles; gb->apu.square_sweep_calculate_countdown -= cycles;
@ -374,7 +374,7 @@ void GB_apu_run(GB_gameboy_t *gb)
gb->apu.square_sweep_calculate_countdown = 0; gb->apu.square_sweep_calculate_countdown = 0;
} }
} }
UNROLL UNROLL
for (unsigned i = GB_SQUARE_1; i <= GB_SQUARE_2; i++) { for (unsigned i = GB_SQUARE_1; i <= GB_SQUARE_2; i++) {
if (gb->apu.is_active[i]) { if (gb->apu.is_active[i]) {
@ -384,7 +384,7 @@ void GB_apu_run(GB_gameboy_t *gb)
gb->apu.square_channels[i].sample_countdown = (gb->apu.square_channels[i].sample_length ^ 0x7FF) * 2 + 1; gb->apu.square_channels[i].sample_countdown = (gb->apu.square_channels[i].sample_length ^ 0x7FF) * 2 + 1;
gb->apu.square_channels[i].current_sample_index++; gb->apu.square_channels[i].current_sample_index++;
gb->apu.square_channels[i].current_sample_index &= 0x7; gb->apu.square_channels[i].current_sample_index &= 0x7;
update_square_sample(gb, i); update_square_sample(gb, i);
} }
if (cycles_left) { if (cycles_left) {
@ -392,7 +392,7 @@ void GB_apu_run(GB_gameboy_t *gb)
} }
} }
} }
gb->apu.wave_channel.wave_form_just_read = false; gb->apu.wave_channel.wave_form_just_read = false;
if (gb->apu.is_active[GB_WAVE]) { if (gb->apu.is_active[GB_WAVE]) {
uint8_t cycles_left = cycles; uint8_t cycles_left = cycles;
@ -413,19 +413,19 @@ void GB_apu_run(GB_gameboy_t *gb)
gb->apu.wave_channel.wave_form_just_read = false; gb->apu.wave_channel.wave_form_just_read = false;
} }
} }
if (gb->apu.is_active[GB_NOISE]) { if (gb->apu.is_active[GB_NOISE]) {
uint8_t cycles_left = cycles; uint8_t cycles_left = cycles;
while (unlikely(cycles_left > gb->apu.noise_channel.sample_countdown)) { while (unlikely(cycles_left > gb->apu.noise_channel.sample_countdown)) {
cycles_left -= gb->apu.noise_channel.sample_countdown + 1; cycles_left -= gb->apu.noise_channel.sample_countdown + 1;
gb->apu.noise_channel.sample_countdown = gb->apu.noise_channel.sample_length * 4 + 3; gb->apu.noise_channel.sample_countdown = gb->apu.noise_channel.sample_length * 4 + 3;
/* Step LFSR */ /* Step LFSR */
unsigned high_bit_mask = gb->apu.noise_channel.narrow ? 0x4040 : 0x4000; unsigned high_bit_mask = gb->apu.noise_channel.narrow ? 0x4040 : 0x4000;
/* Todo: is this formula is different on a GBA? */ /* Todo: is this formula is different on a GBA? */
bool new_high_bit = (gb->apu.noise_channel.lfsr ^ (gb->apu.noise_channel.lfsr >> 1) ^ 1) & 1; bool new_high_bit = (gb->apu.noise_channel.lfsr ^ (gb->apu.noise_channel.lfsr >> 1) ^ 1) & 1;
gb->apu.noise_channel.lfsr >>= 1; gb->apu.noise_channel.lfsr >>= 1;
if (new_high_bit) { if (new_high_bit) {
gb->apu.noise_channel.lfsr |= high_bit_mask; gb->apu.noise_channel.lfsr |= high_bit_mask;
} }
@ -433,19 +433,19 @@ void GB_apu_run(GB_gameboy_t *gb)
/* This code is not redundent, it's relevant when switching LFSR widths */ /* This code is not redundent, it's relevant when switching LFSR widths */
gb->apu.noise_channel.lfsr &= ~high_bit_mask; gb->apu.noise_channel.lfsr &= ~high_bit_mask;
} }
gb->apu.current_lfsr_sample = gb->apu.noise_channel.lfsr & 1; gb->apu.current_lfsr_sample = gb->apu.noise_channel.lfsr & 1;
if (gb->model == GB_MODEL_CGB_C) { if (gb->model == GB_MODEL_CGB_C) {
/* Todo: This was confirmed to happen on a CGB-C. This may or may not happen on pre-CGB models. /* Todo: This was confirmed to happen on a CGB-C. This may or may not happen on pre-CGB models.
Because this degrades audio quality, and testing this on a pre-CGB device requires audio records, Because this degrades audio quality, and testing this on a pre-CGB device requires audio records,
I'll assume these devices are innocent until proven guilty. I'll assume these devices are innocent until proven guilty.
Also happens on CGB-B, but not on CGB-D. Also happens on CGB-B, but not on CGB-D.
*/ */
gb->apu.current_lfsr_sample &= gb->apu.previous_lfsr_sample; gb->apu.current_lfsr_sample &= gb->apu.previous_lfsr_sample;
} }
gb->apu.previous_lfsr_sample = gb->apu.noise_channel.lfsr & 1; gb->apu.previous_lfsr_sample = gb->apu.noise_channel.lfsr & 1;
update_sample(gb, GB_NOISE, update_sample(gb, GB_NOISE,
gb->apu.current_lfsr_sample ? gb->apu.current_lfsr_sample ?
gb->apu.noise_channel.current_volume : 0, gb->apu.noise_channel.current_volume : 0,
@ -455,10 +455,10 @@ void GB_apu_run(GB_gameboy_t *gb)
gb->apu.noise_channel.sample_countdown -= cycles_left; gb->apu.noise_channel.sample_countdown -= cycles_left;
} }
} }
if (gb->apu_output.sample_rate) { if (gb->apu_output.sample_rate) {
gb->apu_output.cycles_since_render += cycles; gb->apu_output.cycles_since_render += cycles;
if (gb->apu_output.sample_cycles > gb->apu_output.cycles_per_sample) { if (gb->apu_output.sample_cycles > gb->apu_output.cycles_per_sample) {
gb->apu_output.sample_cycles -= gb->apu_output.cycles_per_sample; gb->apu_output.sample_cycles -= gb->apu_output.cycles_per_sample;
render(gb, false, NULL); render(gb, false, NULL);
@ -475,7 +475,7 @@ void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, size_t count)
/* TODO: Rewrite this as a proper cyclic buffer. This is a workaround to avoid a very rare crashing race condition */ /* TODO: Rewrite this as a proper cyclic buffer. This is a workaround to avoid a very rare crashing race condition */
size_t buffer_position = gb->apu_output.buffer_position; size_t buffer_position = gb->apu_output.buffer_position;
if (!gb->apu_output.stream_started) { if (!gb->apu_output.stream_started) {
// Intentionally fail the first copy to sync the stream with the Gameboy. // Intentionally fail the first copy to sync the stream with the Gameboy.
gb->apu_output.stream_started = true; gb->apu_output.stream_started = true;
@ -487,11 +487,11 @@ void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, size_t count)
// GB_log(gb, "Audio underflow: %d\n", count - gb->apu_output.buffer_position); // GB_log(gb, "Audio underflow: %d\n", count - gb->apu_output.buffer_position);
GB_sample_t output; GB_sample_t output;
render(gb, true, &output); render(gb, true, &output);
for (unsigned i = 0; i < count - buffer_position; i++) { for (unsigned i = 0; i < count - buffer_position; i++) {
dest[buffer_position + i] = output; dest[buffer_position + i] = output;
} }
if (buffer_position) { if (buffer_position) {
if (gb->apu_output.buffer_size + (count - buffer_position) < count * 3) { if (gb->apu_output.buffer_size + (count - buffer_position) < count * 3) {
gb->apu_output.buffer_size += count - buffer_position; gb->apu_output.buffer_size += count - buffer_position;
@ -579,7 +579,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
} }
reg = GB_IO_WAV_START + gb->apu.wave_channel.current_sample_index / 2; reg = GB_IO_WAV_START + gb->apu.wave_channel.current_sample_index / 2;
} }
/* Todo: this can and should be rewritten with a function table. */ /* Todo: this can and should be rewritten with a function table. */
switch (reg) { switch (reg) {
/* Globals */ /* Globals */
@ -593,7 +593,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
} }
break; break;
case GB_IO_NR52: { case GB_IO_NR52: {
uint8_t old_nrx1[] = { uint8_t old_nrx1[] = {
gb->io_registers[GB_IO_NR11], gb->io_registers[GB_IO_NR11],
gb->io_registers[GB_IO_NR21], gb->io_registers[GB_IO_NR21],
@ -612,10 +612,10 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
memset(gb->io_registers + GB_IO_NR10, 0, GB_IO_WAV_START - GB_IO_NR10); memset(gb->io_registers + GB_IO_NR10, 0, GB_IO_WAV_START - GB_IO_NR10);
old_nrx1[0] &= 0x3F; old_nrx1[0] &= 0x3F;
old_nrx1[1] &= 0x3F; old_nrx1[1] &= 0x3F;
gb->apu.global_enable = false; gb->apu.global_enable = false;
} }
if (!GB_is_cgb(gb) && (value & 0x80)) { if (!GB_is_cgb(gb) && (value & 0x80)) {
GB_apu_write(gb, GB_IO_NR11, old_nrx1[0]); GB_apu_write(gb, GB_IO_NR11, old_nrx1[0]);
GB_apu_write(gb, GB_IO_NR21, old_nrx1[1]); GB_apu_write(gb, GB_IO_NR21, old_nrx1[1]);
@ -624,7 +624,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
} }
} }
break; break;
/* Square channels */ /* Square channels */
case GB_IO_NR10: case GB_IO_NR10:
if (gb->apu.sweep_decreasing && !(value & 8)) { if (gb->apu.sweep_decreasing && !(value & 8)) {
@ -639,7 +639,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
gb->apu.square_sweep_calculate_countdown = 0; gb->apu.square_sweep_calculate_countdown = 0;
} }
break; break;
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;
@ -649,7 +649,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
} }
break; break;
} }
case GB_IO_NR12: case GB_IO_NR12:
case GB_IO_NR22: { case GB_IO_NR22: {
unsigned index = reg == GB_IO_NR22? GB_SQUARE_2: GB_SQUARE_1; unsigned index = reg == GB_IO_NR22? GB_SQUARE_2: GB_SQUARE_1;
@ -667,10 +667,10 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
nrx2_glitch(&gb->apu.square_channels[index].current_volume, value, gb->io_registers[reg]); nrx2_glitch(&gb->apu.square_channels[index].current_volume, value, gb->io_registers[reg]);
update_square_sample(gb, index); update_square_sample(gb, index);
} }
break; break;
} }
case GB_IO_NR13: case GB_IO_NR13:
case GB_IO_NR23: { case GB_IO_NR23: {
unsigned index = reg == GB_IO_NR23? GB_SQUARE_2: GB_SQUARE_1; unsigned index = reg == GB_IO_NR23? GB_SQUARE_2: GB_SQUARE_1;
@ -678,7 +678,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
gb->apu.square_channels[index].sample_length |= value & 0xFF; gb->apu.square_channels[index].sample_length |= value & 0xFF;
break; break;
} }
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;
@ -700,16 +700,16 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
gb->apu.square_channels[index].sample_countdown = (gb->apu.square_channels[index].sample_length ^ 0x7FF) * 2 + 4 - gb->apu.lf_div; gb->apu.square_channels[index].sample_countdown = (gb->apu.square_channels[index].sample_length ^ 0x7FF) * 2 + 4 - gb->apu.lf_div;
} }
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;
/* The volume changes caused by NRX4 sound start take effect instantly (i.e. the effect the previously /* 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 started sound). The playback itself is not instant which is why we don't update the sample for other
cases. */ cases. */
if (gb->apu.is_active[index]) { if (gb->apu.is_active[index]) {
update_square_sample(gb, index); update_square_sample(gb, index);
} }
gb->apu.square_channels[index].volume_countdown = gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] & 7; gb->apu.square_channels[index].volume_countdown = gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] & 7;
if ((gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] & 0xF8) != 0 && !gb->apu.is_active[index]) { if ((gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] & 0xF8) != 0 && !gb->apu.is_active[index]) {
gb->apu.is_active[index] = true; gb->apu.is_active[index] = true;
update_sample(gb, index, 0, 0); update_sample(gb, index, 0, 0);
@ -720,7 +720,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
gb->apu.square_channels[index].pulse_length = 0x40; gb->apu.square_channels[index].pulse_length = 0x40;
gb->apu.square_channels[index].length_enabled = false; gb->apu.square_channels[index].length_enabled = false;
} }
if (index == GB_SQUARE_1) { if (index == GB_SQUARE_1) {
gb->apu.sweep_decreasing = false; gb->apu.sweep_decreasing = false;
if (gb->io_registers[GB_IO_NR10] & 7) { if (gb->io_registers[GB_IO_NR10] & 7) {
@ -734,9 +734,9 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7); gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7);
if (!gb->apu.square_sweep_countdown) gb->apu.square_sweep_countdown = 8; if (!gb->apu.square_sweep_countdown) gb->apu.square_sweep_countdown = 8;
} }
} }
/* APU glitch - if length is enabled while the DIV-divider's LSB is 1, tick the length once. */ /* APU glitch - if length is enabled while the DIV-divider's LSB is 1, tick the length once. */
if ((value & 0x40) && if ((value & 0x40) &&
!gb->apu.square_channels[index].length_enabled && !gb->apu.square_channels[index].length_enabled &&
@ -756,7 +756,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
gb->apu.square_channels[index].length_enabled = value & 0x40; gb->apu.square_channels[index].length_enabled = value & 0x40;
break; break;
} }
/* Wave channel */ /* Wave channel */
case GB_IO_NR30: case GB_IO_NR30:
gb->apu.wave_channel.enable = value & 0x80; gb->apu.wave_channel.enable = value & 0x80;
@ -788,12 +788,12 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
gb->apu.wave_channel.sample_countdown == 0 && gb->apu.wave_channel.sample_countdown == 0 &&
gb->apu.wave_channel.enable) { 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;
/* This glitch varies between models and even specific instances: /* This glitch varies between models and even specific instances:
DMG-B: Most of them behave as emulated. A few behave differently. DMG-B: Most of them behave as emulated. A few behave differently.
SGB: As far as I know, all tested instances behave as emulated. SGB: As far as I know, all tested instances behave as emulated.
MGB, SGB2: Most instances behave non-deterministically, a few behave as emulated. MGB, SGB2: Most instances behave non-deterministically, a few behave as emulated.
Additionally, I believe DMGs, including those we behave differently than emulated, Additionally, I believe DMGs, including those we behave differently than emulated,
are all deterministic. */ are all deterministic. */
if (offset < 4) { if (offset < 4) {
@ -827,7 +827,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
mean differences in the DACs. */ mean differences in the DACs. */
/* Todo: Similar issues may apply to the other channels on the DMG/AGB, test, verify and fix if needed */ /* Todo: Similar issues may apply to the other channels on the DMG/AGB, test, verify and fix if needed */
} }
/* APU glitch - if length is enabled while the DIV-divider's LSB is 1, tick the length once. */ /* APU glitch - if length is enabled while the DIV-divider's LSB is 1, tick the length once. */
if ((value & 0x40) && if ((value & 0x40) &&
!gb->apu.wave_channel.length_enabled && !gb->apu.wave_channel.length_enabled &&
@ -851,14 +851,14 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
} }
break; break;
/* Noise Channel */ /* Noise Channel */
case GB_IO_NR41: { case GB_IO_NR41: {
gb->apu.noise_channel.pulse_length = (0x40 - (value & 0x3f)); gb->apu.noise_channel.pulse_length = (0x40 - (value & 0x3f));
break; break;
} }
case GB_IO_NR42: { case GB_IO_NR42: {
if (((value & 0x7) == 0) && ((gb->io_registers[reg] & 0x7) != 0)) { if (((value & 0x7) == 0) && ((gb->io_registers[reg] & 0x7) != 0)) {
/* Envelope disabled */ /* Envelope disabled */
@ -879,24 +879,24 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
} }
break; break;
} }
case GB_IO_NR43: { case GB_IO_NR43: {
gb->apu.noise_channel.narrow = value & 8; gb->apu.noise_channel.narrow = value & 8;
unsigned divisor = (value & 0x07) << 1; unsigned divisor = (value & 0x07) << 1;
if (!divisor) divisor = 1; if (!divisor) divisor = 1;
gb->apu.noise_channel.sample_length = (divisor << (value >> 4)) - 1; gb->apu.noise_channel.sample_length = (divisor << (value >> 4)) - 1;
/* Todo: changing the frequency sometimes delays the next sample. This is probably /* Todo: changing the frequency sometimes delays the next sample. This is probably
due to how the frequency is actually calculated in the noise channel, which is probably due to how the frequency is actually calculated in the noise channel, which is probably
not by calculating the effective sample length and counting simiarly to the other channels. not by calculating the effective sample length and counting simiarly to the other channels.
This is not emulated correctly. */ This is not emulated correctly. */
break; break;
} }
case GB_IO_NR44: { case GB_IO_NR44: {
if (value & 0x80) { if (value & 0x80) {
gb->apu.noise_channel.sample_countdown = (gb->apu.noise_channel.sample_length) * 2 + 6 - gb->apu.lf_div; gb->apu.noise_channel.sample_countdown = (gb->apu.noise_channel.sample_length) * 2 + 6 - gb->apu.lf_div;
/* I'm COMPLETELY unsure about this logic, but it passes all relevant tests. /* I'm COMPLETELY unsure about this logic, but it passes all relevant tests.
See comment in NR43. */ See comment in NR43. */
if ((gb->io_registers[GB_IO_NR43] & 7) && (gb->apu.noise_channel.alignment & 2) == 0) { if ((gb->io_registers[GB_IO_NR43] & 7) && (gb->apu.noise_channel.alignment & 2) == 0) {
@ -910,9 +910,9 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
if (gb->apu.is_active[GB_NOISE]) { if (gb->apu.is_active[GB_NOISE]) {
gb->apu.noise_channel.sample_countdown += 2; gb->apu.noise_channel.sample_countdown += 2;
} }
gb->apu.noise_channel.current_volume = gb->io_registers[GB_IO_NR42] >> 4; gb->apu.noise_channel.current_volume = gb->io_registers[GB_IO_NR42] >> 4;
/* The volume changes caused by NRX4 sound start take effect instantly (i.e. the effect the previously /* 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 started sound). The playback itself is not instant which is why we don't update the sample for other
cases. */ cases. */
@ -925,18 +925,18 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
gb->apu.noise_channel.lfsr = 0; gb->apu.noise_channel.lfsr = 0;
gb->apu.current_lfsr_sample = false; gb->apu.current_lfsr_sample = false;
gb->apu.noise_channel.volume_countdown = gb->io_registers[GB_IO_NR42] & 7; gb->apu.noise_channel.volume_countdown = gb->io_registers[GB_IO_NR42] & 7;
if (!gb->apu.is_active[GB_NOISE] && (gb->io_registers[GB_IO_NR42] & 0xF8) != 0) { if (!gb->apu.is_active[GB_NOISE] && (gb->io_registers[GB_IO_NR42] & 0xF8) != 0) {
gb->apu.is_active[GB_NOISE] = true; gb->apu.is_active[GB_NOISE] = true;
update_sample(gb, GB_NOISE, 0, 0); update_sample(gb, GB_NOISE, 0, 0);
} }
if (gb->apu.noise_channel.pulse_length == 0) { if (gb->apu.noise_channel.pulse_length == 0) {
gb->apu.noise_channel.pulse_length = 0x40; gb->apu.noise_channel.pulse_length = 0x40;
gb->apu.noise_channel.length_enabled = false; gb->apu.noise_channel.length_enabled = false;
} }
} }
/* APU glitch - if length is enabled while the DIV-divider's LSB is 1, tick the length once. */ /* APU glitch - if length is enabled while the DIV-divider's LSB is 1, tick the length once. */
if ((value & 0x40) && if ((value & 0x40) &&
!gb->apu.noise_channel.length_enabled && !gb->apu.noise_channel.length_enabled &&
@ -956,7 +956,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
gb->apu.noise_channel.length_enabled = value & 0x40; gb->apu.noise_channel.length_enabled = value & 0x40;
break; break;
} }
default: default:
if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END) { if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END) {
gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2] = value >> 4; gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2] = value >> 4;

View File

@ -54,23 +54,23 @@ typedef struct
{ {
bool global_enable; bool global_enable;
uint8_t apu_cycles; uint8_t apu_cycles;
uint8_t samples[GB_N_CHANNELS]; uint8_t samples[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 uint8_t div_divider; // The DIV register ticks the APU at 512Hz, but is then divided
// once more to generate 128Hz and 64Hz clocks // once more to generate 128Hz and 64Hz clocks
uint8_t lf_div; // The APU runs in 2MHz, but channels 1, 2 and 4 run in 1MHZ so we divide uint8_t lf_div; // The APU runs in 2MHz, but channels 1, 2 and 4 run in 1MHZ so we divide
// need to divide the signal. // need to divide the signal.
uint8_t square_sweep_countdown; // In 128Hz uint8_t square_sweep_countdown; // In 128Hz
uint8_t square_sweep_calculate_countdown; // In 2 MHz uint8_t square_sweep_calculate_countdown; // In 2 MHz
uint16_t new_sweep_sample_legnth; uint16_t new_sweep_sample_legnth;
uint16_t shadow_sweep_sample_legnth; uint16_t shadow_sweep_sample_legnth;
bool sweep_enabled; bool sweep_enabled;
bool sweep_decreasing; bool sweep_decreasing;
struct { struct {
uint16_t pulse_length; // Reloaded from NRX1 (xorred), in 256Hz 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
@ -78,44 +78,44 @@ typedef struct
uint8_t current_sample_index; /* For save state compatibility, uint8_t current_sample_index; /* For save state compatibility,
highest bit is reused (See NR14/NR24's highest bit is reused (See NR14/NR24's
write code)*/ write code)*/
uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length, xorred $7FF) uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length, xorred $7FF)
uint16_t sample_length; // 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 256Hz 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
uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length, xorred $7FF) uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length, xorred $7FF)
uint8_t current_sample_index; uint8_t current_sample_index;
uint8_t current_sample; // Current sample before shifting. uint8_t current_sample; // Current sample before shifting.
int8_t wave_form[32]; int8_t wave_form[32];
bool wave_form_just_read; bool wave_form_just_read;
} wave_channel; } wave_channel;
struct { struct {
uint16_t pulse_length; // Reloaded from NR41 (xorred), in 256Hz DIV ticks uint16_t pulse_length; // Reloaded from NR41 (xorred), in 256Hz DIV ticks
uint8_t current_volume; // Reloaded from NR42 uint8_t current_volume; // Reloaded from NR42
uint8_t volume_countdown; // Reloaded from NR42 uint8_t volume_countdown; // Reloaded from NR42
uint16_t lfsr; uint16_t lfsr;
bool narrow; bool narrow;
uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length) uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length)
uint16_t sample_length; // From NR43, in APU ticks uint16_t sample_length; // From NR43, in APU ticks
bool length_enabled; // NR44 bool length_enabled; // NR44
uint8_t alignment; // If (NR43 & 7) != 0, samples are aligned to 512KHz clock instead of uint8_t alignment; // If (NR43 & 7) != 0, samples are aligned to 512KHz clock instead of
// 1MHz. This variable keeps track of the alignment. // 1MHz. This variable keeps track of the alignment.
} noise_channel; } noise_channel;
bool skip_div_event; bool skip_div_event;
bool current_lfsr_sample; bool current_lfsr_sample;
bool previous_lfsr_sample; bool previous_lfsr_sample;
@ -130,25 +130,25 @@ typedef enum {
typedef struct { typedef struct {
unsigned sample_rate; unsigned sample_rate;
GB_sample_t *buffer; GB_sample_t *buffer;
size_t buffer_size; size_t buffer_size;
size_t buffer_position; size_t buffer_position;
bool stream_started; /* detects first copy request to minimize lag */ bool stream_started; /* detects first copy request to minimize lag */
volatile bool copy_in_progress; volatile bool copy_in_progress;
volatile bool lock; volatile bool lock;
double sample_cycles; // In 8 MHz units double sample_cycles; // In 8 MHz units
double cycles_per_sample; double cycles_per_sample;
// Samples are NOT normalized to MAX_CH_AMP * 4 at this stage! // Samples are NOT normalized to MAX_CH_AMP * 4 at this stage!
unsigned cycles_since_render; unsigned cycles_since_render;
unsigned last_update[GB_N_CHANNELS]; unsigned last_update[GB_N_CHANNELS];
GB_sample_t current_sample[GB_N_CHANNELS]; GB_sample_t current_sample[GB_N_CHANNELS];
GB_sample_t summed_samples[GB_N_CHANNELS]; GB_sample_t summed_samples[GB_N_CHANNELS];
double dac_discharge[GB_N_CHANNELS]; double dac_discharge[GB_N_CHANNELS];
GB_highpass_mode_t highpass_mode; GB_highpass_mode_t highpass_mode;
double highpass_rate; double highpass_rate;
GB_double_sample_t highpass_diff; GB_double_sample_t highpass_diff;
@ -160,6 +160,7 @@ size_t GB_apu_get_current_buffer_length(GB_gameboy_t *gb);
void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode); void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode);
#ifdef GB_INTERNAL #ifdef GB_INTERNAL
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); 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); 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_event(GB_gameboy_t *gb);

View File

@ -214,7 +214,7 @@ static value_t read_lvalue(GB_gameboy_t *gb, lvalue_t lvalue)
return r; return r;
} }
return VALUE_16(GB_read_memory(gb, lvalue.memory_address.value)); return VALUE_16(GB_read_memory(gb, lvalue.memory_address.value));
case LVALUE_MEMORY16: case LVALUE_MEMORY16:
if (lvalue.memory_address.has_bank) { if (lvalue.memory_address.has_bank) {
banking_state_t state; banking_state_t state;
@ -254,7 +254,7 @@ static void write_lvalue(GB_gameboy_t *gb, lvalue_t lvalue, uint16_t value)
} }
GB_write_memory(gb, lvalue.memory_address.value, value); GB_write_memory(gb, lvalue.memory_address.value, value);
return; return;
case LVALUE_MEMORY16: case LVALUE_MEMORY16:
if (lvalue.memory_address.has_bank) { if (lvalue.memory_address.has_bank) {
banking_state_t state; banking_state_t state;
@ -288,7 +288,7 @@ static void write_lvalue(GB_gameboy_t *gb, lvalue_t lvalue, uint16_t value)
25 bit address <op> 16 bit value = 25 bit address 25 bit address <op> 16 bit value = 25 bit address
16 bit value <op> 25 bit address = 25 bit address 16 bit value <op> 25 bit address = 25 bit address
25 bit address <op> 25 bit address = 16 bit value (since adding pointers, for examples, makes no sense) 25 bit address <op> 25 bit address = 16 bit value (since adding pointers, for examples, makes no sense)
Boolean operators always return a 16-bit value Boolean operators always return a 16-bit value
*/ */
#define FIX_BANK(x) ((value_t){a.has_bank ^ b.has_bank, a.has_bank? a.bank : b.bank, (x)}) #define FIX_BANK(x) ((value_t){a.has_bank ^ b.has_bank, a.has_bank? a.bank : b.bank, (x)})
@ -473,9 +473,9 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string,
/* Disable watchpoints while evaulating expressions */ /* Disable watchpoints while evaulating expressions */
uint16_t n_watchpoints = gb->n_watchpoints; uint16_t n_watchpoints = gb->n_watchpoints;
gb->n_watchpoints = 0; gb->n_watchpoints = 0;
value_t ret = ERROR; value_t ret = ERROR;
*error = false; *error = false;
// Strip whitespace // Strip whitespace
while (length && (string[0] == ' ' || string[0] == '\n' || string[0] == '\r' || string[0] == '\t')) { while (length && (string[0] == ' ' || string[0] == '\n' || string[0] == '\r' || string[0] == '\t')) {
@ -547,7 +547,7 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string,
} }
if (string[i] == '}') depth--; if (string[i] == '}') depth--;
} }
if (depth == 0) { if (depth == 0) {
value_t addr = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value); value_t addr = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value);
banking_state_t state; banking_state_t state;
@ -751,7 +751,7 @@ static bool next(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug
print_usage(gb, command); print_usage(gb, command);
return true; return true;
} }
gb->debug_stopped = false; gb->debug_stopped = false;
gb->debug_next_command = true; gb->debug_next_command = true;
gb->debug_call_depth = 0; gb->debug_call_depth = 0;
@ -791,7 +791,7 @@ static bool stack_leak_detection(GB_gameboy_t *gb, char *arguments, char *modifi
{ {
NO_MODIFIERS NO_MODIFIERS
STOPPED_ONLY STOPPED_ONLY
if (strlen(lstrip(arguments))) { if (strlen(lstrip(arguments))) {
print_usage(gb, command); print_usage(gb, command);
return true; return true;
@ -854,7 +854,7 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const
print_usage(gb, command); print_usage(gb, command);
return true; return true;
} }
if (strlen(lstrip(arguments)) == 0) { if (strlen(lstrip(arguments)) == 0) {
print_usage(gb, command); print_usage(gb, command);
return true; return true;
@ -913,9 +913,9 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const
gb->breakpoints[index].condition = NULL; gb->breakpoints[index].condition = NULL;
} }
gb->n_breakpoints++; gb->n_breakpoints++;
gb->breakpoints[index].is_jump_to = is_jump_to; gb->breakpoints[index].is_jump_to = is_jump_to;
if (is_jump_to) { if (is_jump_to) {
gb->has_jump_to_breakpoints = true; gb->has_jump_to_breakpoints = true;
} }
@ -957,7 +957,7 @@ static bool delete(GB_gameboy_t *gb, char *arguments, char *modifiers, const deb
index = i; index = i;
} }
} }
if (index >= gb->n_breakpoints) { if (index >= gb->n_breakpoints) {
GB_log(gb, "No breakpoint set at %s\n", debugger_value_to_string(gb, result, true)); GB_log(gb, "No breakpoint set at %s\n", debugger_value_to_string(gb, result, true));
return true; return true;
@ -965,7 +965,7 @@ static bool delete(GB_gameboy_t *gb, char *arguments, char *modifiers, const deb
result.bank = gb->breakpoints[index].bank; result.bank = gb->breakpoints[index].bank;
result.has_bank = gb->breakpoints[index].bank != (uint16_t) -1; result.has_bank = gb->breakpoints[index].bank != (uint16_t) -1;
if (gb->breakpoints[index].condition) { if (gb->breakpoints[index].condition) {
free(gb->breakpoints[index].condition); free(gb->breakpoints[index].condition);
} }
@ -980,7 +980,7 @@ static bool delete(GB_gameboy_t *gb, char *arguments, char *modifiers, const deb
} }
} }
} }
memmove(&gb->breakpoints[index], &gb->breakpoints[index + 1], (gb->n_breakpoints - index - 1) * sizeof(gb->breakpoints[0])); memmove(&gb->breakpoints[index], &gb->breakpoints[index + 1], (gb->n_breakpoints - index - 1) * sizeof(gb->breakpoints[0]));
gb->n_breakpoints--; gb->n_breakpoints--;
gb->breakpoints = realloc(gb->breakpoints, gb->n_breakpoints * sizeof(gb->breakpoints[0])); gb->breakpoints = realloc(gb->breakpoints, gb->n_breakpoints * sizeof(gb->breakpoints[0]));
@ -1140,12 +1140,12 @@ static bool unwatch(GB_gameboy_t *gb, char *arguments, char *modifiers, const de
index = i; index = i;
} }
} }
if (index >= gb->n_watchpoints) { if (index >= gb->n_watchpoints) {
GB_log(gb, "No watchpoint set at %s\n", debugger_value_to_string(gb, result, true)); GB_log(gb, "No watchpoint set at %s\n", debugger_value_to_string(gb, result, true));
return true; return true;
} }
result.bank = gb->watchpoints[index].bank; result.bank = gb->watchpoints[index].bank;
result.has_bank = gb->watchpoints[index].bank != (uint16_t) -1; result.has_bank = gb->watchpoints[index].bank != (uint16_t) -1;
@ -1457,7 +1457,7 @@ static bool backtrace(GB_gameboy_t *gb, char *arguments, char *modifiers, const
print_usage(gb, command); print_usage(gb, command);
return true; return true;
} }
GB_log(gb, " 1. %s\n", debugger_value_to_string(gb, (value_t){true, bank_for_addr(gb, gb->pc), gb->pc}, true)); GB_log(gb, " 1. %s\n", debugger_value_to_string(gb, (value_t){true, bank_for_addr(gb, gb->pc), gb->pc}, true));
for (unsigned int i = gb->backtrace_size; i--;) { for (unsigned int i = gb->backtrace_size; i--;) {
GB_log(gb, "%3d. %s\n", gb->backtrace_size - i + 1, debugger_value_to_string(gb, (value_t){true, gb->backtrace_returns[i].bank, gb->backtrace_returns[i].addr}, true)); GB_log(gb, "%3d. %s\n", gb->backtrace_size - i + 1, debugger_value_to_string(gb, (value_t){true, gb->backtrace_returns[i].bank, gb->backtrace_returns[i].addr}, true));
@ -1532,7 +1532,7 @@ static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
GB_log(gb, " Background and Window Tileset: %s\n", (gb->io_registers[GB_IO_LCDC] & 16)? "$8000" : "$8800"); GB_log(gb, " Background and Window Tileset: %s\n", (gb->io_registers[GB_IO_LCDC] & 16)? "$8000" : "$8800");
GB_log(gb, " Window: %s\n", (gb->io_registers[GB_IO_LCDC] & 32)? "Enabled" : "Disabled"); GB_log(gb, " Window: %s\n", (gb->io_registers[GB_IO_LCDC] & 32)? "Enabled" : "Disabled");
GB_log(gb, " Window tilemap: %s\n", (gb->io_registers[GB_IO_LCDC] & 64)? "$9C00" : "$9800"); GB_log(gb, " Window tilemap: %s\n", (gb->io_registers[GB_IO_LCDC] & 64)? "$9C00" : "$9800");
GB_log(gb, "\nSTAT:\n"); GB_log(gb, "\nSTAT:\n");
static const char *modes[] = {"Mode 0, H-Blank", "Mode 1, V-Blank", "Mode 2, OAM", "Mode 3, Rendering"}; static const char *modes[] = {"Mode 0, H-Blank", "Mode 1, V-Blank", "Mode 2, OAM", "Mode 3, Rendering"};
GB_log(gb, " Current mode: %s\n", modes[gb->io_registers[GB_IO_STAT] & 3]); GB_log(gb, " Current mode: %s\n", modes[gb->io_registers[GB_IO_STAT] & 3]);
@ -1541,9 +1541,9 @@ static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
GB_log(gb, " V-Blank interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 16)? "Enabled" : "Disabled"); GB_log(gb, " V-Blank interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 16)? "Enabled" : "Disabled");
GB_log(gb, " OAM interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 32)? "Enabled" : "Disabled"); GB_log(gb, " OAM interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 32)? "Enabled" : "Disabled");
GB_log(gb, " LYC interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 64)? "Enabled" : "Disabled"); GB_log(gb, " LYC interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 64)? "Enabled" : "Disabled");
GB_log(gb, "\nCurrent line: %d\n", gb->current_line); GB_log(gb, "\nCurrent line: %d\n", gb->current_line);
GB_log(gb, "Current state: "); GB_log(gb, "Current state: ");
if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) {
@ -1566,7 +1566,173 @@ static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
GB_log(gb, "LYC: %d\n", gb->io_registers[GB_IO_LYC]); GB_log(gb, "LYC: %d\n", gb->io_registers[GB_IO_LYC]);
GB_log(gb, "Window position: %d, %d\n", (signed) gb->io_registers[GB_IO_WX] - 7 , gb->io_registers[GB_IO_WY]); GB_log(gb, "Window position: %d, %d\n", (signed) gb->io_registers[GB_IO_WX] - 7 , gb->io_registers[GB_IO_WY]);
GB_log(gb, "Interrupt line: %s\n", gb->stat_interrupt_line? "On" : "Off"); GB_log(gb, "Interrupt line: %s\n", gb->stat_interrupt_line? "On" : "Off");
return true;
}
static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
{
NO_MODIFIERS
if (strlen(lstrip(arguments))) {
print_usage(gb, command);
return true;
}
GB_log(gb, "Current state: ");
if(!gb->apu.global_enable) {
GB_log(gb, "Disabled\n");
}
else {
GB_log(gb, "Enabled\n");
for(uint8_t channel = 0; channel < GB_N_CHANNELS; channel++) {
GB_log(gb, "CH%u is %s, DAC %s; current sample = 0x%x\n", channel + 1,
gb->apu.is_active[channel] ? "active " : "inactive",
GB_apu_is_DAC_enabled(gb, channel) ? "active " : "inactive",
gb->apu.samples[channel]);
}
}
GB_log(gb, "SO1 (left output): volume %u,", gb->io_registers[GB_IO_NR50] & 0x07);
if(gb->io_registers[GB_IO_NR51] & 0x0f) {
for(uint8_t channel = 0, mask = 0x01; channel < GB_N_CHANNELS; channel++, mask <<= 1) {
if(gb->io_registers[GB_IO_NR51] & mask) {
GB_log(gb, " CH%u", channel + 1);
}
}
} else {
GB_log(gb, " no channels");
}
GB_log(gb, "%s\n", gb->io_registers[GB_IO_NR50] & 0x80 ? " VIN": "");
GB_log(gb, "SO2 (right output): volume %u,", gb->io_registers[GB_IO_NR50] & 0x70 >> 4);
if(gb->io_registers[GB_IO_NR51] & 0xf0) {
for(uint8_t channel = 0, mask = 0x10; channel < GB_N_CHANNELS; channel++, mask <<= 1) {
if(gb->io_registers[GB_IO_NR51] & mask) {
GB_log(gb, " CH%u", channel + 1);
}
}
} else {
GB_log(gb, " no channels");
}
GB_log(gb, "%s\n", gb->io_registers[GB_IO_NR50] & 0x80 ? " VIN": "");
for(uint8_t channel = GB_SQUARE_1; channel <= GB_SQUARE_2; channel++) {
GB_log(gb, "\nCH%u:\n", channel + 1);
GB_log(gb, " Current volume: %u, current sample length: %u APU ticks (next in %u ticks)\n",
gb->apu.square_channels[channel].current_volume,
gb->apu.square_channels[channel].sample_length,
gb->apu.square_channels[channel].sample_countdown);
uint8_t nrx2 = gb->io_registers[channel == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22];
GB_log(gb, " %u 256 Hz ticks till next volume %screase (out of %u)\n",
gb->apu.square_channels[channel].volume_countdown,
nrx2 & 8 ? "in" : "de",
nrx2 & 7);
uint8_t duty = gb->io_registers[channel == GB_SQUARE_1? GB_IO_NR11 :GB_IO_NR21] >> 6;
GB_log(gb, " Duty cycle %s%% (%s), current index %u/8%s\n",
(char*[]){"12.5", " 25", " 50", " 75"}[duty],
(char*[]){"_______-", "-______-", "-____---", "_------_"}[duty],
gb->apu.square_channels[channel].current_sample_index & 0x7f,
gb->apu.square_channels[channel].current_sample_index >> 7 ? " (suppressed)" : "");
if(channel == GB_SQUARE_1) {
GB_log(gb, " Frequency sweep %s and %s (next in %u APU ticks)\n",
gb->apu.sweep_enabled? "active" : "inactive",
gb->apu.sweep_decreasing? "decreasing" : "increasing",
gb->apu.square_sweep_calculate_countdown);
}
if(gb->apu.square_channels[channel].length_enabled) {
GB_log(gb, " Channel will end in %u 256 Hz ticks\n",
gb->apu.square_channels[channel].pulse_length);
}
}
GB_log(gb, "\nCH3:\n");
GB_log(gb, " Wave:");
for(uint8_t i = 0; i < 32; i++) {
GB_log(gb, "%s%X", i%4?"":" ", gb->apu.wave_channel.wave_form[i]);
}
GB_log(gb, ", has %sjust been read\n", gb->apu.wave_channel.wave_form_just_read? "": "not ");
GB_log(gb, " Current position: %u\n", gb->apu.wave_channel.current_sample_index);
GB_log(gb, " Volume %s (right-shifted %ux)\n",
(char*[]){"100%", "50%", "25%", NULL, "muted"}[gb->apu.wave_channel.shift],
gb->apu.wave_channel.shift);
GB_log(gb, " Current sample length: %u APU ticks (next in %u ticks)\n",
gb->apu.wave_channel.sample_length,
gb->apu.wave_channel.sample_countdown);
if(gb->apu.wave_channel.length_enabled) {
GB_log(gb, " Channel will end in %u 256 Hz ticks\n",
gb->apu.wave_channel.pulse_length);
}
GB_log(gb, "\nCH4:\n");
GB_log(gb, " Current volume: %u, current sample length: %u APU ticks (next in %u ticks)\n",
gb->apu.noise_channel.current_volume,
gb->apu.noise_channel.sample_length,
gb->apu.noise_channel.sample_countdown);
GB_log(gb, " %u 256 Hz ticks till next volume %screase (out of %u)\n",
gb->apu.noise_channel.volume_countdown,
gb->io_registers[GB_IO_NR42] & 8 ? "in" : "de",
gb->io_registers[GB_IO_NR42] & 7);
GB_log(gb, " LFSR in %u-step mode, current value %%",
gb->apu.noise_channel.narrow? 7 : 15);
for(uint16_t lfsr = gb->apu.noise_channel.lfsr, i = 15; i--; lfsr <<= 1) {
GB_log(gb, "%u%s", (lfsr >> 14) & 1, i%4 ? "" : " ");
}
if(gb->apu.noise_channel.length_enabled) {
GB_log(gb, " Channel will end in %u 256 Hz ticks\n",
gb->apu.noise_channel.pulse_length);
}
GB_log(gb, "\n\nReminder: APU ticks are @ 2 MiHz\n");
return true;
}
static bool wave(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
{
if (strlen(lstrip(arguments)) || (modifiers && !strchr("fcl", modifiers[0]))) {
print_usage(gb, command);
return true;
}
uint8_t shift_amount = 1, mask;
if(modifiers) {
switch(modifiers[0]) {
case 'c':
shift_amount = 2;
break;
case 'l':
shift_amount = 8;
break;
}
}
mask = (0xf << (shift_amount - 1)) & 0xf;
for(int8_t cur_val = 0xf & mask; cur_val >= 0; cur_val -= shift_amount) {
for(uint8_t i = 0; i < 32; i++) {
if((gb->apu.wave_channel.wave_form[i] & mask) == cur_val) {
GB_log(gb, "%X", gb->apu.wave_channel.wave_form[i]);
} else {
GB_log(gb, "%c", i%4 == 2 ? '-' : ' ');
}
}
GB_log(gb, "\n");
}
return true; return true;
} }
@ -1587,6 +1753,10 @@ static const debugger_command_t commands[] = {
{"registers", 1, registers, "Print values of processor registers and other important registers"}, {"registers", 1, registers, "Print values of processor registers and other important registers"},
{"cartridge", 2, mbc, "Displays information about the MBC and cartridge"}, {"cartridge", 2, mbc, "Displays information about the MBC and cartridge"},
{"mbc", 3, }, /* Alias */ {"mbc", 3, }, /* Alias */
{"apu", 3, apu, "Displays information about the current state of the audio chip"},
{"wave", 3, wave, "Prints a visual representation of the wave RAM" HELP_NEWLINE
"Modifiers can be used for a (f)ull print (the default)," HELP_NEWLINE
"a more (c)ompact one, or a one-(l)iner"},
{"lcd", 3, lcd, "Displays information about the current state of the LCD controller"}, {"lcd", 3, lcd, "Displays information about the current state of the LCD controller"},
{"palettes", 3, palettes, "Displays the current CGB palettes"}, {"palettes", 3, palettes, "Displays the current CGB palettes"},
{"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE
@ -1875,7 +2045,7 @@ static jump_to_return_t test_jump_to_breakpoints(GB_gameboy_t *gb, uint16_t *add
void GB_debugger_run(GB_gameboy_t *gb) void GB_debugger_run(GB_gameboy_t *gb)
{ {
if (gb->debug_disable) return; if (gb->debug_disable) return;
char *input = NULL; char *input = NULL;
if (gb->debug_next_command && gb->debug_call_depth <= 0) { if (gb->debug_next_command && gb->debug_call_depth <= 0) {
gb->debug_stopped = true; gb->debug_stopped = true;
@ -1895,11 +2065,11 @@ next_command:
GB_log(gb, "Breakpoint: PC = %s\n", value_to_string(gb, gb->pc, true)); GB_log(gb, "Breakpoint: PC = %s\n", value_to_string(gb, gb->pc, true));
GB_cpu_disassemble(gb, gb->pc, 5); GB_cpu_disassemble(gb, gb->pc, 5);
} }
if (gb->breakpoints && !gb->debug_stopped) { if (gb->breakpoints && !gb->debug_stopped) {
uint16_t address = 0; uint16_t address = 0;
jump_to_return_t jump_to_result = test_jump_to_breakpoints(gb, &address); jump_to_return_t jump_to_result = test_jump_to_breakpoints(gb, &address);
bool should_delete_state = true; bool should_delete_state = true;
if (gb->nontrivial_jump_state && should_break(gb, gb->pc, true)) { if (gb->nontrivial_jump_state && should_break(gb, gb->pc, true)) {
if (gb->non_trivial_jump_breakpoint_occured) { if (gb->non_trivial_jump_breakpoint_occured) {
@ -1930,7 +2100,7 @@ next_command:
else { else {
gb->non_trivial_jump_breakpoint_occured = false; gb->non_trivial_jump_breakpoint_occured = false;
} }
if (should_delete_state) { if (should_delete_state) {
if (gb->nontrivial_jump_state) { if (gb->nontrivial_jump_state) {
free(gb->nontrivial_jump_state); free(gb->nontrivial_jump_state);
@ -2077,17 +2247,17 @@ static bool is_in_trivial_memory(uint16_t addr)
if (addr < 0x8000) { if (addr < 0x8000) {
return true; return true;
} }
/* HRAM */ /* HRAM */
if (addr >= 0xFF80 && addr < 0xFFFF) { if (addr >= 0xFF80 && addr < 0xFFFF) {
return true; return true;
} }
/* RAM */ /* RAM */
if (addr >= 0xC000 && addr < 0xE000) { if (addr >= 0xC000 && addr < 0xE000) {
return true; return true;
} }
return false; return false;
} }
@ -2125,7 +2295,7 @@ static bool condition_code(GB_gameboy_t *gb, uint8_t opcode)
case 3: case 3:
return (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG); return (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG);
} }
return false; return false;
} }
@ -2134,7 +2304,7 @@ static uint16_t jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode)
if (!condition_code(gb, opcode)) { if (!condition_code(gb, opcode)) {
return gb->pc + 2; return gb->pc + 2;
} }
return gb->pc + 2 + (int8_t)GB_read_memory(gb, gb->pc + 1); return gb->pc + 2 + (int8_t)GB_read_memory(gb, gb->pc + 1);
} }
@ -2221,12 +2391,12 @@ static GB_opcode_address_getter_t *opcodes[256] = {
static jump_to_return_t test_jump_to_breakpoints(GB_gameboy_t *gb, uint16_t *address) static jump_to_return_t test_jump_to_breakpoints(GB_gameboy_t *gb, uint16_t *address)
{ {
if (!gb->has_jump_to_breakpoints) return JUMP_TO_NONE; if (!gb->has_jump_to_breakpoints) return JUMP_TO_NONE;
if (!is_in_trivial_memory(gb->pc) || !is_in_trivial_memory(gb->pc + 2) || if (!is_in_trivial_memory(gb->pc) || !is_in_trivial_memory(gb->pc + 2) ||
!is_in_trivial_memory(gb->registers[GB_REGISTER_SP]) || !is_in_trivial_memory(gb->registers[GB_REGISTER_SP] + 1)) { !is_in_trivial_memory(gb->registers[GB_REGISTER_SP]) || !is_in_trivial_memory(gb->registers[GB_REGISTER_SP] + 1)) {
return JUMP_TO_NONTRIVIAL; return JUMP_TO_NONTRIVIAL;
} }
/* Interrupts */ /* Interrupts */
if (gb->ime) { if (gb->ime) {
for (unsigned i = 0; i < 5; i++) { for (unsigned i = 0; i < 5; i++) {
@ -2240,38 +2410,38 @@ static jump_to_return_t test_jump_to_breakpoints(GB_gameboy_t *gb, uint16_t *add
} }
} }
} }
uint16_t n_watchpoints = gb->n_watchpoints; uint16_t n_watchpoints = gb->n_watchpoints;
gb->n_watchpoints = 0; gb->n_watchpoints = 0;
uint8_t opcode = GB_read_memory(gb, gb->pc); uint8_t opcode = GB_read_memory(gb, gb->pc);
if (opcode == 0x76) { if (opcode == 0x76) {
gb->n_watchpoints = n_watchpoints; gb->n_watchpoints = n_watchpoints;
if (gb->ime) { /* Already handled in above */ if (gb->ime) { /* Already handled in above */
return JUMP_TO_NONE; return JUMP_TO_NONE;
} }
if (gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F) { if (gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F) {
return JUMP_TO_NONTRIVIAL; /* HALT bug could occur */ return JUMP_TO_NONTRIVIAL; /* HALT bug could occur */
} }
return JUMP_TO_NONE; return JUMP_TO_NONE;
} }
GB_opcode_address_getter_t *getter = opcodes[opcode]; GB_opcode_address_getter_t *getter = opcodes[opcode];
if (!getter) { if (!getter) {
gb->n_watchpoints = n_watchpoints; gb->n_watchpoints = n_watchpoints;
return JUMP_TO_NONE; return JUMP_TO_NONE;
} }
uint16_t new_pc = getter(gb, opcode); uint16_t new_pc = getter(gb, opcode);
gb->n_watchpoints = n_watchpoints; gb->n_watchpoints = n_watchpoints;
if (address) { if (address) {
*address = new_pc; *address = new_pc;
} }
return should_break(gb, new_pc, true) ? JUMP_TO_BREAK : JUMP_TO_NONE; return should_break(gb, new_pc, true) ? JUMP_TO_BREAK : JUMP_TO_NONE;
} }