mirror of https://github.com/bsnes-emu/bsnes.git
Merge pull request #178 from ISSOtm/master
Add APU-related debugger commands
This commit is contained in:
commit
8d9149b020
164
Core/apu.c
164
Core/apu.c
|
@ -21,7 +21,7 @@ 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)
|
||||
{
|
||||
if (gb->model >= GB_MODEL_AGB) {
|
||||
/* On the AGB, mixing is done digitally, so there are no per-channel
|
||||
|
@ -33,17 +33,17 @@ static bool 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 false;
|
||||
}
|
||||
|
||||
|
@ -83,13 +83,13 @@ static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsign
|
|||
return;
|
||||
}
|
||||
|
||||
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)) {
|
||||
|
@ -116,7 +116,7 @@ static void render(GB_gameboy_t *gb, bool no_downsampling, GB_sample_t *dest)
|
|||
double multiplier = CH_STEP;
|
||||
|
||||
if (gb->model < GB_MODEL_AGB) {
|
||||
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;
|
||||
|
@ -157,7 +157,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};
|
||||
|
@ -190,10 +190,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;
|
||||
|
@ -220,7 +220,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]?
|
||||
|
@ -239,38 +239,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);
|
||||
}
|
||||
|
@ -281,19 +281,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) ?
|
||||
|
@ -320,20 +320,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) {
|
||||
|
@ -345,7 +345,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) {
|
||||
|
@ -355,7 +355,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) {
|
||||
|
@ -365,7 +365,7 @@ void GB_apu_div_event(GB_gameboy_t *gb)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ((gb->apu.div_divider & 3) == 3) {
|
||||
if (!gb->apu.sweep_enabled) {
|
||||
return;
|
||||
|
@ -377,12 +377,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;
|
||||
}
|
||||
|
@ -397,11 +397,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;
|
||||
|
@ -418,7 +418,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]) {
|
||||
|
@ -428,7 +428,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) {
|
||||
|
@ -436,7 +436,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;
|
||||
|
@ -457,19 +457,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;
|
||||
}
|
||||
|
@ -477,19 +477,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,
|
||||
|
@ -499,10 +499,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);
|
||||
|
@ -519,7 +519,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;
|
||||
|
@ -531,11 +531,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;
|
||||
|
@ -623,7 +623,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 */
|
||||
|
@ -637,7 +637,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],
|
||||
|
@ -656,10 +656,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]);
|
||||
|
@ -668,7 +668,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)) {
|
||||
|
@ -683,7 +683,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;
|
||||
|
@ -693,7 +693,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;
|
||||
|
@ -711,10 +711,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;
|
||||
|
@ -722,7 +722,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;
|
||||
|
@ -744,16 +744,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);
|
||||
|
@ -764,7 +764,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) {
|
||||
|
@ -778,9 +778,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 &&
|
||||
|
@ -800,7 +800,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;
|
||||
|
@ -832,12 +832,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) {
|
||||
|
@ -871,7 +871,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 &&
|
||||
|
@ -895,14 +895,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 */
|
||||
|
@ -923,24 +923,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) {
|
||||
|
@ -954,9 +954,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. */
|
||||
|
@ -969,18 +969,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 &&
|
||||
|
@ -1000,7 +1000,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;
|
||||
|
|
39
Core/apu.h
39
Core/apu.h
|
@ -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);
|
||||
|
|
256
Core/debugger.c
256
Core/debugger.c
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue