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;
}
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) {
case GB_SQUARE_1:
return gb->io_registers[GB_IO_NR12] & 0xF8;
case GB_SQUARE_2:
return gb->io_registers[GB_IO_NR22] & 0xF8;
case GB_WAVE:
return gb->apu.wave_channel.enable;
case GB_NOISE:
return gb->io_registers[GB_IO_NR42] & 0xF8;
}
return 0;
}
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];
}
else {
gb->apu.samples[index] = value;
}
if (gb->apu_output.sample_rate) {
unsigned right_volume = 0;
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
for (unsigned i = 0; i < GB_N_CHANNELS; i++) {
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;
if (gb->apu_output.dac_discharge[i] < 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,
output.right - gb->apu_output.highpass_diff.right} :
output;
switch (gb->apu_output.highpass_mode) {
case GB_HIGHPASS_OFF:
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)
{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};
case GB_HIGHPASS_MAX:;
}
}
if (dest) {
*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)
{
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;
update_sample(gb, index,
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) {
(*volume)++;
}
if (((value ^ old_value) & 8)) {
*volume = 0x10 - *volume;
}
if ((value & 7) && !(old_value & 7) && *volume && !(value & 8)) {
(*volume)--;
}
if ((old_value & 7) && (value & 8)) {
(*volume)--;
}
(*volume) &= 0xF;
}
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.square_channels[index].volume_countdown || (nrx2 & 7)) {
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) {
gb->apu.square_channels[index].current_volume++;
}
else if (!(nrx2 & 8) && gb->apu.square_channels[index].current_volume > 0) {
gb->apu.square_channels[index].current_volume--;
}
gb->apu.square_channels[index].volume_countdown = nrx2 & 7;
if (gb->apu.is_active[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)
{
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) {
if ((nr42 & 8) && gb->apu.noise_channel.current_volume < 0xF) {
gb->apu.noise_channel.current_volume++;
}
else if (!(nr42 & 8) && gb->apu.noise_channel.current_volume > 0) {
gb->apu.noise_channel.current_volume--;
}
gb->apu.noise_channel.volume_countdown = nr42 & 7;
if (gb->apu.is_active[GB_NOISE]) {
update_sample(gb, GB_NOISE,
(gb->apu.noise_channel.lfsr & 1) ?
@ -276,20 +276,20 @@ void GB_apu_div_event(GB_gameboy_t *gb)
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)) {
tick_noise_envelope(gb);
}
}
if ((gb->apu.div_divider & 7) == 0) {
for (unsigned i = GB_SQUARE_2 + 1; i--;) {
tick_square_envelope(gb, i);
}
tick_noise_envelope(gb);
}
if ((gb->apu.div_divider & 1) == 1) {
for (unsigned i = GB_SQUARE_2 + 1; i--;) {
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.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.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.sweep_enabled) {
return;
@ -333,12 +333,12 @@ void GB_apu_div_event(GB_gameboy_t *gb)
gb->apu.shadow_sweep_sample_legnth =
gb->apu.new_sweep_sample_legnth;
}
if (gb->io_registers[GB_IO_NR10] & 0x70) {
/* Recalculation and overflow check only occurs after a delay */
gb->apu.square_sweep_calculate_countdown = 0x13 - gb->apu.lf_div;
}
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;
}
@ -353,11 +353,11 @@ void GB_apu_run(GB_gameboy_t *gb)
uint8_t cycles = gb->apu.apu_cycles >> 2;
gb->apu.apu_cycles = 0;
if (!cycles) return;
/* To align the square signal to 1MHz */
gb->apu.lf_div ^= cycles & 1;
gb->apu.noise_channel.alignment += cycles;
if (gb->apu.square_sweep_calculate_countdown) {
if (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;
}
}
UNROLL
for (unsigned i = GB_SQUARE_1; i <= GB_SQUARE_2; 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].current_sample_index++;
gb->apu.square_channels[i].current_sample_index &= 0x7;
update_square_sample(gb, i);
}
if (cycles_left) {
@ -392,7 +392,7 @@ void GB_apu_run(GB_gameboy_t *gb)
}
}
}
gb->apu.wave_channel.wave_form_just_read = false;
if (gb->apu.is_active[GB_WAVE]) {
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;
}
}
if (gb->apu.is_active[GB_NOISE]) {
uint8_t cycles_left = cycles;
while (unlikely(cycles_left > gb->apu.noise_channel.sample_countdown)) {
cycles_left -= gb->apu.noise_channel.sample_countdown + 1;
gb->apu.noise_channel.sample_countdown = gb->apu.noise_channel.sample_length * 4 + 3;
/* Step LFSR */
unsigned high_bit_mask = gb->apu.noise_channel.narrow ? 0x4040 : 0x4000;
/* 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;
gb->apu.noise_channel.lfsr >>= 1;
if (new_high_bit) {
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 */
gb->apu.noise_channel.lfsr &= ~high_bit_mask;
}
gb->apu.current_lfsr_sample = gb->apu.noise_channel.lfsr & 1;
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.
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.
Also happens on CGB-B, but not on CGB-D.
*/
gb->apu.current_lfsr_sample &= gb->apu.previous_lfsr_sample;
}
gb->apu.previous_lfsr_sample = gb->apu.noise_channel.lfsr & 1;
update_sample(gb, GB_NOISE,
gb->apu.current_lfsr_sample ?
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;
}
}
if (gb->apu_output.sample_rate) {
gb->apu_output.cycles_since_render += cycles;
if (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);
@ -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 */
size_t buffer_position = gb->apu_output.buffer_position;
if (!gb->apu_output.stream_started) {
// Intentionally fail the first copy to sync the stream with the Gameboy.
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_sample_t output;
render(gb, true, &output);
for (unsigned i = 0; i < count - buffer_position; i++) {
dest[buffer_position + i] = output;
}
if (buffer_position) {
if (gb->apu_output.buffer_size + (count - buffer_position) < count * 3) {
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;
}
/* Todo: this can and should be rewritten with a function table. */
switch (reg) {
/* Globals */
@ -593,7 +593,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
}
break;
case GB_IO_NR52: {
uint8_t old_nrx1[] = {
gb->io_registers[GB_IO_NR11],
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);
old_nrx1[0] &= 0x3F;
old_nrx1[1] &= 0x3F;
gb->apu.global_enable = false;
}
if (!GB_is_cgb(gb) && (value & 0x80)) {
GB_apu_write(gb, GB_IO_NR11, old_nrx1[0]);
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;
/* Square channels */
case GB_IO_NR10:
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;
}
break;
case GB_IO_NR11:
case GB_IO_NR21: {
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;
}
case GB_IO_NR12:
case GB_IO_NR22: {
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]);
update_square_sample(gb, index);
}
break;
}
case GB_IO_NR13:
case GB_IO_NR23: {
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;
break;
}
case GB_IO_NR14:
case GB_IO_NR24: {
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].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. */
if (gb->apu.is_active[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;
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;
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].length_enabled = false;
}
if (index == GB_SQUARE_1) {
gb->apu.sweep_decreasing = false;
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);
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. */
if ((value & 0x40) &&
!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;
break;
}
/* Wave channel */
case GB_IO_NR30:
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.enable) {
unsigned offset = ((gb->apu.wave_channel.current_sample_index + 1) >> 1) & 0xF;
/* This glitch varies between models and even specific instances:
DMG-B: Most of them behave as emulated. A few behave differently.
SGB: As far as I know, all tested instances 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,
are all deterministic. */
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. */
/* 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. */
if ((value & 0x40) &&
!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;
/* Noise Channel */
case GB_IO_NR41: {
gb->apu.noise_channel.pulse_length = (0x40 - (value & 0x3f));
break;
}
case GB_IO_NR42: {
if (((value & 0x7) == 0) && ((gb->io_registers[reg] & 0x7) != 0)) {
/* Envelope disabled */
@ -879,24 +879,24 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
}
break;
}
case GB_IO_NR43: {
gb->apu.noise_channel.narrow = value & 8;
unsigned divisor = (value & 0x07) << 1;
if (!divisor) divisor = 1;
gb->apu.noise_channel.sample_length = (divisor << (value >> 4)) - 1;
/* 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
not by calculating the effective sample length and counting simiarly to the other channels.
This is not emulated correctly. */
break;
}
case GB_IO_NR44: {
if (value & 0x80) {
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.
See comment in NR43. */
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]) {
gb->apu.noise_channel.sample_countdown += 2;
}
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
started sound). The playback itself is not instant which is why we don't update the sample for other
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.current_lfsr_sample = false;
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) {
gb->apu.is_active[GB_NOISE] = true;
update_sample(gb, GB_NOISE, 0, 0);
}
if (gb->apu.noise_channel.pulse_length == 0) {
gb->apu.noise_channel.pulse_length = 0x40;
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. */
if ((value & 0x40) &&
!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;
break;
}
default:
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;

View File

@ -54,23 +54,23 @@ typedef struct
{
bool global_enable;
uint8_t apu_cycles;
uint8_t samples[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 lf_div; // The APU runs in 2MHz, but channels 1, 2 and 4 run in 1MHZ so we divide
// need to divide the signal.
uint8_t square_sweep_countdown; // In 128Hz
uint8_t square_sweep_calculate_countdown; // In 2 MHz
uint16_t new_sweep_sample_legnth;
uint16_t shadow_sweep_sample_legnth;
bool sweep_enabled;
bool sweep_decreasing;
struct {
uint16_t pulse_length; // Reloaded from NRX1 (xorred), in 256Hz DIV ticks
uint8_t current_volume; // Reloaded from NRX2
@ -78,44 +78,44 @@ typedef struct
uint8_t current_sample_index; /* For save state compatibility,
highest bit is reused (See NR14/NR24's
write code)*/
uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length, xorred $7FF)
uint16_t sample_length; // From NRX3, NRX4, in APU ticks
bool length_enabled; // NRX4
} square_channels[2];
struct {
bool enable; // NR30
uint16_t pulse_length; // Reloaded from NR31 (xorred), in 256Hz DIV ticks
uint8_t shift; // NR32
uint16_t sample_length; // NR33, NR34, in APU ticks
bool length_enabled; // NR34
uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length, xorred $7FF)
uint8_t current_sample_index;
uint8_t current_sample; // Current sample before shifting.
int8_t wave_form[32];
bool wave_form_just_read;
} wave_channel;
struct {
uint16_t pulse_length; // Reloaded from NR41 (xorred), in 256Hz DIV ticks
uint8_t current_volume; // Reloaded from NR42
uint8_t volume_countdown; // Reloaded from NR42
uint16_t lfsr;
bool narrow;
uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length)
uint16_t sample_length; // From NR43, in APU ticks
bool length_enabled; // NR44
uint8_t alignment; // If (NR43 & 7) != 0, samples are aligned to 512KHz clock instead of
// 1MHz. This variable keeps track of the alignment.
} noise_channel;
bool skip_div_event;
bool current_lfsr_sample;
bool previous_lfsr_sample;
@ -130,25 +130,25 @@ typedef enum {
typedef struct {
unsigned sample_rate;
GB_sample_t *buffer;
size_t buffer_size;
size_t buffer_position;
bool stream_started; /* detects first copy request to minimize lag */
volatile bool copy_in_progress;
volatile bool lock;
double sample_cycles; // In 8 MHz units
double cycles_per_sample;
// Samples are NOT normalized to MAX_CH_AMP * 4 at this stage!
unsigned cycles_since_render;
unsigned last_update[GB_N_CHANNELS];
GB_sample_t current_sample[GB_N_CHANNELS];
GB_sample_t summed_samples[GB_N_CHANNELS];
double dac_discharge[GB_N_CHANNELS];
GB_highpass_mode_t highpass_mode;
double highpass_rate;
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);
#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);
uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg);
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 VALUE_16(GB_read_memory(gb, lvalue.memory_address.value));
case LVALUE_MEMORY16:
if (lvalue.memory_address.has_bank) {
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);
return;
case LVALUE_MEMORY16:
if (lvalue.memory_address.has_bank) {
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
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)
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)})
@ -473,9 +473,9 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string,
/* Disable watchpoints while evaulating expressions */
uint16_t n_watchpoints = gb->n_watchpoints;
gb->n_watchpoints = 0;
value_t ret = ERROR;
*error = false;
// Strip whitespace
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 (depth == 0) {
value_t addr = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value);
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);
return true;
}
gb->debug_stopped = false;
gb->debug_next_command = true;
gb->debug_call_depth = 0;
@ -791,7 +791,7 @@ static bool stack_leak_detection(GB_gameboy_t *gb, char *arguments, char *modifi
{
NO_MODIFIERS
STOPPED_ONLY
if (strlen(lstrip(arguments))) {
print_usage(gb, command);
return true;
@ -854,7 +854,7 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const
print_usage(gb, command);
return true;
}
if (strlen(lstrip(arguments)) == 0) {
print_usage(gb, command);
return true;
@ -913,9 +913,9 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const
gb->breakpoints[index].condition = NULL;
}
gb->n_breakpoints++;
gb->breakpoints[index].is_jump_to = is_jump_to;
if (is_jump_to) {
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;
}
}
if (index >= gb->n_breakpoints) {
GB_log(gb, "No breakpoint set at %s\n", debugger_value_to_string(gb, result, 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.has_bank = gb->breakpoints[index].bank != (uint16_t) -1;
if (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]));
gb->n_breakpoints--;
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;
}
}
if (index >= gb->n_watchpoints) {
GB_log(gb, "No watchpoint set at %s\n", debugger_value_to_string(gb, result, true));
return true;
}
result.bank = gb->watchpoints[index].bank;
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);
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));
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));
@ -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, " 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, "\nSTAT:\n");
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]);
@ -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, " 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, "\nCurrent line: %d\n", gb->current_line);
GB_log(gb, "Current state: ");
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, "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");
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;
}
@ -1587,6 +1753,10 @@ static const debugger_command_t commands[] = {
{"registers", 1, registers, "Print values of processor registers and other important registers"},
{"cartridge", 2, mbc, "Displays information about the MBC and cartridge"},
{"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"},
{"palettes", 3, palettes, "Displays the current CGB palettes"},
{"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)
{
if (gb->debug_disable) return;
char *input = NULL;
if (gb->debug_next_command && gb->debug_call_depth <= 0) {
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_cpu_disassemble(gb, gb->pc, 5);
}
if (gb->breakpoints && !gb->debug_stopped) {
uint16_t address = 0;
jump_to_return_t jump_to_result = test_jump_to_breakpoints(gb, &address);
bool should_delete_state = true;
if (gb->nontrivial_jump_state && should_break(gb, gb->pc, true)) {
if (gb->non_trivial_jump_breakpoint_occured) {
@ -1930,7 +2100,7 @@ next_command:
else {
gb->non_trivial_jump_breakpoint_occured = false;
}
if (should_delete_state) {
if (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) {
return true;
}
/* HRAM */
if (addr >= 0xFF80 && addr < 0xFFFF) {
return true;
}
/* RAM */
if (addr >= 0xC000 && addr < 0xE000) {
return true;
}
return false;
}
@ -2125,7 +2295,7 @@ static bool condition_code(GB_gameboy_t *gb, uint8_t opcode)
case 3:
return (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG);
}
return false;
}
@ -2134,7 +2304,7 @@ static uint16_t jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode)
if (!condition_code(gb, opcode)) {
return gb->pc + 2;
}
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)
{
if (!gb->has_jump_to_breakpoints) return JUMP_TO_NONE;
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)) {
return JUMP_TO_NONTRIVIAL;
}
/* Interrupts */
if (gb->ime) {
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;
gb->n_watchpoints = 0;
uint8_t opcode = GB_read_memory(gb, gb->pc);
if (opcode == 0x76) {
gb->n_watchpoints = n_watchpoints;
if (gb->ime) { /* Already handled in above */
return JUMP_TO_NONE;
}
if (gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F) {
return JUMP_TO_NONTRIVIAL; /* HALT bug could occur */
}
return JUMP_TO_NONE;
}
GB_opcode_address_getter_t *getter = opcodes[opcode];
if (!getter) {
gb->n_watchpoints = n_watchpoints;
return JUMP_TO_NONE;
}
uint16_t new_pc = getter(gb, opcode);
gb->n_watchpoints = n_watchpoints;
if (address) {
*address = new_pc;
}
return should_break(gb, new_pc, true) ? JUMP_TO_BREAK : JUMP_TO_NONE;
}