mirror of https://github.com/bsnes-emu/bsnes.git
Add APU-related debugger commands
This change includes making one of the APU functions public
This commit is contained in:
parent
795823e372
commit
40f83c8f25
164
Core/apu.c
164
Core/apu.c
|
@ -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;
|
||||||
|
|
39
Core/apu.h
39
Core/apu.h
|
@ -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);
|
||||||
|
|
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 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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue