From a19ee1e5e0c35141ea221526227685c5601ae3bd Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 21 Jul 2017 23:06:02 +0300 Subject: [PATCH] 2MHz audio downscaling support. Implemented NR50 and NR51. --- Core/apu.c | 146 ++++++++++++++++++++++++++++++++++---------------- Core/apu.h | 38 ++++++++----- Core/gb.c | 16 +++--- Core/gb.h | 9 +--- Core/timing.c | 2 +- 5 files changed, 135 insertions(+), 76 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 1cd038a..4ff7664 100755 --- a/Core/apu.c +++ b/Core/apu.c @@ -3,12 +3,64 @@ #include #include "gb.h" -#define likely(x) __builtin_expect((x),1) -#define unlikely(x) __builtin_expect((x),0) +#define likely(x) __builtin_expect((x), 1) +#define unlikely(x) __builtin_expect((x), 0) -static void update_sample(GB_gameboy_t *gb, unsigned index, uint8_t value) +static void refresh_channel(GB_gameboy_t *gb, unsigned index, unsigned cycles_offset) +{ + unsigned multiplier = gb->apu_output.cycles_since_render + cycles_offset - gb->apu_output.last_update[index]; + gb->apu_output.summed_samples[index].left += gb->apu_output.current_sample[index].left * multiplier; + gb->apu_output.summed_samples[index].right += gb->apu_output.current_sample[index].right * multiplier; + gb->apu_output.last_update[index] = gb->apu_output.cycles_since_render + cycles_offset; +} + +static void update_sample(GB_gameboy_t *gb, unsigned index, uint8_t value, unsigned cycles_offset) { gb->apu.samples[index] = value; + if (gb->apu_output.sample_rate) { + unsigned left_volume = 0; + if (gb->io_registers[GB_IO_NR51] & (1 << index)) { + left_volume = gb->io_registers[GB_IO_NR50] & 7; + } + unsigned right_volume = 0; + if (gb->io_registers[GB_IO_NR51] & (0x10 << index)) { + right_volume = (gb->io_registers[GB_IO_NR50] >> 4) & 7;; + } + GB_sample_t output = {value * left_volume, value * right_volume}; + if (*(uint32_t *)&(gb->apu_output.current_sample[index]) != *(uint32_t *)&output) { + refresh_channel(gb, index, cycles_offset); + gb->apu_output.current_sample[index] = output; + } + } +} + +static void render(GB_gameboy_t *gb) +{ + GB_sample_t output = {0,0}; + for (unsigned i = GB_N_CHANNELS; i--;) { + if (likely(gb->apu_output.last_update[i] == 0)) { + output.left += gb->apu_output.current_sample[i].left * CH_STEP; + output.right += gb->apu_output.current_sample[i].right * CH_STEP; + } + else { + refresh_channel(gb, i, 0); + output.left += (unsigned) gb->apu_output.summed_samples[i].left * CH_STEP + / gb->apu_output.cycles_since_render; + output.right += (unsigned) gb->apu_output.summed_samples[i].right * CH_STEP + / gb->apu_output.cycles_since_render; + gb->apu_output.summed_samples[i] = (GB_sample_t){0, 0}; + } + gb->apu_output.last_update[i] = 0; + } + gb->apu_output.cycles_since_render = 0; + + + while (gb->apu_output.copy_in_progress); + while (!__sync_bool_compare_and_swap(&gb->apu_output.lock, false, true)); + if (gb->apu_output.buffer_position < gb->apu_output.buffer_size) { + gb->apu_output.buffer[gb->apu_output.buffer_position++] = output; + } + gb->apu_output.lock = false; } void GB_apu_div_event(GB_gameboy_t *gb) @@ -20,36 +72,17 @@ void GB_apu_div_event(GB_gameboy_t *gb) else { gb->apu.is_active[GB_WAVE] = false; gb->apu.wave_channel.current_sample = 0; - update_sample(gb, GB_WAVE, 0); + update_sample(gb, GB_WAVE, 0, 0); } } } -static void render(GB_gameboy_t *gb) -{ - while (gb->audio_copy_in_progress); - while (!__sync_bool_compare_and_swap(&gb->apu_lock, false, true)); - if (gb->audio_position >= gb->buffer_size) { - gb->apu_lock = false; - return; - } - gb->audio_buffer[gb->audio_position++] = (GB_sample_t) {gb->apu.samples[GB_WAVE] * CH_STEP, - gb->apu.samples[GB_WAVE] * CH_STEP}; - gb->apu_lock = false; -} void GB_apu_run(GB_gameboy_t *gb, uint8_t cycles) { /* Convert 4MHZ to 2MHz. cycles is always even. */ cycles >>= 1; - double cycles_per_sample = gb->sample_rate ? CPU_FREQUENCY / (double)gb->sample_rate : 0; // TODO: this should be cached! - - if (gb->sample_rate && gb->apu_sample_cycles > cycles_per_sample) { - gb->apu_sample_cycles -= cycles_per_sample; - render(gb); - } - gb->apu.wave_channel.wave_form_just_read = false; if (gb->apu.is_active[GB_WAVE]) { uint8_t cycles_left = cycles; @@ -60,7 +93,9 @@ void GB_apu_run(GB_gameboy_t *gb, uint8_t cycles) gb->apu.wave_channel.current_sample_index &= 0x1F; gb->apu.wave_channel.current_sample = gb->apu.wave_channel.wave_form[gb->apu.wave_channel.current_sample_index]; - update_sample(gb, GB_WAVE, gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift); + update_sample(gb, GB_WAVE, + gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift, + cycles - cycles_left); gb->apu.wave_channel.wave_form_just_read = true; } if (cycles_left) { @@ -69,35 +104,44 @@ void GB_apu_run(GB_gameboy_t *gb, uint8_t cycles) } } + if (gb->apu_output.sample_rate) { + gb->apu_output.cycles_since_render += cycles; + double cycles_per_sample = CPU_FREQUENCY / (double)gb->apu_output.sample_rate; // TODO: this should be cached! + + if (gb->apu_output.sample_cycles > cycles_per_sample) { + gb->apu_output.sample_cycles -= cycles_per_sample; + render(gb); + } + } } -void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, unsigned int count) +void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, size_t count) { - gb->audio_copy_in_progress = true; + gb->apu_output.copy_in_progress = true; - if (!gb->audio_stream_started) { + if (!gb->apu_output.stream_started) { // Intentionally fail the first copy to sync the stream with the Gameboy. - gb->audio_stream_started = true; - gb->audio_position = 0; + gb->apu_output.stream_started = true; + gb->apu_output.buffer_position = 0; } - if (count > gb->audio_position) { - // GB_log(gb, "Audio underflow: %d\n", count - gb->audio_position); - if (gb->audio_position != 0) { - for (unsigned i = 0; i < count - gb->audio_position; i++) { - dest[gb->audio_position + i] = gb->audio_buffer[gb->audio_position - 1]; + if (count > gb->apu_output.buffer_position) { + // GB_log(gb, "Audio underflow: %d\n", count - gb->apu_output.buffer_position); + if (gb->apu_output.buffer_position != 0) { + for (unsigned i = 0; i < count - gb->apu_output.buffer_position; i++) { + dest[gb->apu_output.buffer_position + i] = gb->apu_output.buffer[gb->apu_output.buffer_position - 1]; } } else { - memset(dest + gb->audio_position, 0, (count - gb->audio_position) * sizeof(*gb->audio_buffer)); + memset(dest + gb->apu_output.buffer_position, 0, (count - gb->apu_output.buffer_position) * sizeof(*gb->apu_output.buffer)); } - count = gb->audio_position; + count = gb->apu_output.buffer_position; } - memcpy(dest, gb->audio_buffer, count * sizeof(*gb->audio_buffer)); - memmove(gb->audio_buffer, gb->audio_buffer + count, (gb->audio_position - count) * sizeof(*gb->audio_buffer)); - gb->audio_position -= count; + memcpy(dest, gb->apu_output.buffer, count * sizeof(*gb->apu_output.buffer)); + memmove(gb->apu_output.buffer, gb->apu_output.buffer + count, (gb->apu_output.buffer_position - count) * sizeof(*gb->apu_output.buffer)); + gb->apu_output.buffer_position -= count; - gb->audio_copy_in_progress = false; + gb->apu_output.copy_in_progress = false; } void GB_apu_init(GB_gameboy_t *gb) @@ -105,8 +149,7 @@ void GB_apu_init(GB_gameboy_t *gb) memset(&gb->apu, 0, sizeof(gb->apu)); // gb->apu.wave_channels[0].duty = gb->apu.wave_channels[1].duty = 4; // gb->apu.lfsr = 0x7FFF; - gb->apu.left_volume = 7; - gb->apu.right_volume = 7; + gb->io_registers[GB_IO_NR50] = 0x77; for (int i = 0; i < 4; i++) { gb->apu.left_enabled[i] = gb->apu.right_enabled[i] = true; } @@ -171,12 +214,23 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) switch (reg) { /* Globals */ + case GB_IO_NR50: + case GB_IO_NR51: + /* These registers affect the output of all 3 channels (but not the output of the PCM registers).*/ + /* We call update_samples with the current value so the APU output is updated with the new outputs */ + for (unsigned i = GB_N_CHANNELS; i--;) { + update_sample(gb, i, gb->apu.samples[i], 0); + } + break; case GB_IO_NR52: if ((value & 0x80) && !gb->apu.global_enable) { GB_apu_init(gb); gb->apu.global_enable = true; } else if (!(value & 0x80) && gb->apu.global_enable) { + for (unsigned i = GB_N_CHANNELS; i--;) { + update_sample(gb, i, 0, 0); + } memset(&gb->apu, 0, sizeof(gb->apu)); memset(gb->io_registers + GB_IO_NR10, 0, GB_IO_WAV_START - GB_IO_NR10); gb->apu.global_enable = false; @@ -189,14 +243,14 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) if (!gb->apu.wave_channel.enable) { gb->apu.is_active[GB_WAVE] = false; gb->apu.wave_channel.current_sample = 0; - update_sample(gb, GB_WAVE, 0); + update_sample(gb, GB_WAVE, 0, 0); } break; case GB_IO_NR31: break; case GB_IO_NR32: gb->apu.wave_channel.shift = (uint8_t[]){4, 0, 1, 2}[(value >> 5) & 3]; - update_sample(gb, GB_WAVE, gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift); + update_sample(gb, GB_WAVE, gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift, 0); break; case GB_IO_NR33: gb->apu.wave_channel.sample_length &= ~0xFF; @@ -246,7 +300,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } -unsigned GB_apu_get_current_buffer_length(GB_gameboy_t *gb) +size_t GB_apu_get_current_buffer_length(GB_gameboy_t *gb) { - return gb->audio_position; + return gb->apu_output.buffer_position; } diff --git a/Core/apu.h b/Core/apu.h index beec027..6690a58 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -6,9 +6,9 @@ #ifdef GB_INTERNAL -/* Divides nicely and never overflows with 4 channels */ -#define MAX_CH_AMP 0x1E00 -#define CH_STEP (MAX_CH_AMP/0xF) +/* Divides nicely and never overflows with 4 channels and 8 volume levels */ +#define MAX_CH_AMP 0x1FFE +#define CH_STEP (MAX_CH_AMP/0xF/7) #endif /* Lengths are in either DIV ticks (256Hz, triggered by the DIV register) or @@ -20,12 +20,6 @@ typedef struct int16_t right; } GB_sample_t; -typedef struct -{ - double left; - double right; -} GB_double_sample_t; - enum GB_CHANNELS { GB_SQUARE_1, GB_SQUARE_2, @@ -37,8 +31,6 @@ enum GB_CHANNELS { typedef struct { bool global_enable; - uint8_t left_volume; - uint8_t right_volume; uint8_t samples[GB_N_CHANNELS]; bool left_enabled[GB_N_CHANNELS]; @@ -61,9 +53,29 @@ typedef struct } wave_channel; } GB_apu_t; +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; + + // 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]; +} GB_apu_output_t; + void GB_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate); -void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, unsigned int count); -unsigned GB_apu_get_current_buffer_length(GB_gameboy_t *gb); +void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, size_t count); +size_t GB_apu_get_current_buffer_length(GB_gameboy_t *gb); #ifdef GB_INTERNAL void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value); diff --git a/Core/gb.c b/Core/gb.c index 88099b1..8a2e9e2 100755 --- a/Core/gb.c +++ b/Core/gb.c @@ -125,8 +125,8 @@ void GB_free(GB_gameboy_t *gb) if (gb->rom) { free(gb->rom); } - if (gb->audio_buffer) { - free(gb->audio_buffer); + if (gb->apu_output.buffer) { + free(gb->apu_output.buffer); } if (gb->breakpoints) { free(gb->breakpoints); @@ -388,13 +388,13 @@ void GB_serial_set_data(GB_gameboy_t *gb, uint8_t data) void GB_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate) { - if (gb->audio_buffer) { - free(gb->audio_buffer); + if (gb->apu_output.buffer) { + free(gb->apu_output.buffer); } - gb->buffer_size = sample_rate / 25; // 40ms delay - gb->audio_buffer = malloc(gb->buffer_size * sizeof(*gb->audio_buffer)); - gb->sample_rate = sample_rate; - gb->audio_position = 0; + gb->apu_output.buffer_size = sample_rate / 25; // 40ms delay + gb->apu_output.buffer = malloc(gb->apu_output.buffer_size * sizeof(*gb->apu_output.buffer)); + gb->apu_output.sample_rate = sample_rate; + gb->apu_output.buffer_position = 0; } void GB_disconnect_serial(GB_gameboy_t *gb) diff --git a/Core/gb.h b/Core/gb.h index fd7f4e0..852d1a9 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -404,7 +404,6 @@ struct GB_gameboy_internal_s { /* I/O */ uint32_t *screen; - GB_sample_t *audio_buffer; bool keys[GB_KEY_MAX]; /* Timing */ @@ -412,13 +411,7 @@ struct GB_gameboy_internal_s { uint64_t cycles_since_last_sync; /* Audio */ - unsigned buffer_size; - unsigned sample_rate; - unsigned audio_position; - bool audio_stream_started; /* detects first copy request to minimize lag */ - volatile bool audio_copy_in_progress; - volatile bool apu_lock; - double apu_sample_cycles; + GB_apu_output_t apu_output; /* Callbacks */ void *user_data; diff --git a/Core/timing.c b/Core/timing.c index 0d4d215..72fe503 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -133,7 +133,7 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) // Not affected by speed boost gb->hdma_cycles += cycles; - gb->apu_sample_cycles += cycles; + gb->apu_output.sample_cycles += cycles; gb->cycles_since_ir_change += cycles; gb->cycles_since_input_ir_change += cycles; gb->cycles_since_last_sync += cycles;