From 00623d4eea49eb3d8e1e69bf9774b691e9bcd349 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 12 May 2017 17:11:55 +0300 Subject: [PATCH] - Added audio supersampling support to greatly improve audio quality. - Fixed a bug where low sampling rate or disabled sound resulted in wrong APU behavior. - Added API to get the current number of pending samples. - This change broke save state compatibility with v0.8 and older Closes #8. --- Core/apu.c | 60 +++++++++++++++++++++++++++++++++++++++++++++------ Core/apu.h | 12 ++++++++++- Core/gb.c | 2 ++ Core/gb.h | 19 ++++++++-------- Core/timing.c | 1 + 5 files changed, 77 insertions(+), 17 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 49b4f216..f493ff98 100755 --- a/Core/apu.c +++ b/Core/apu.c @@ -186,23 +186,52 @@ void GB_apu_get_samples_and_update_pcm_regs(GB_gameboy_t *gb, GB_sample_t *sampl void GB_apu_run(GB_gameboy_t *gb) { if (gb->sample_rate == 0) return; - static bool should_log_overflow = true; while (gb->audio_copy_in_progress); double ticks_per_sample = (double) CPU_FREQUENCY / gb->sample_rate; + if (gb->audio_quality == 0) { + GB_sample_t sample; + GB_apu_get_samples_and_update_pcm_regs(gb, &sample); + gb->current_supersample.left += sample.left; + gb->current_supersample.right += sample.right; + gb->n_subsamples++; + } + else if (gb->audio_quality != 1) { + double ticks_per_subsample = ticks_per_sample / gb->audio_quality; + if (ticks_per_subsample < 1) { + ticks_per_subsample = 1; + } + if (gb->apu_subsample_cycles > ticks_per_subsample) { + gb->apu_subsample_cycles -= ticks_per_subsample; + } + + GB_sample_t sample; + GB_apu_get_samples_and_update_pcm_regs(gb, &sample); + gb->current_supersample.left += sample.left; + gb->current_supersample.right += sample.right; + gb->n_subsamples++; + } + if (gb->apu_sample_cycles > ticks_per_sample) { gb->apu_sample_cycles -= ticks_per_sample; if (gb->audio_position == gb->buffer_size) { /* - if (should_log_overflow && !gb->turbo) { - GB_log(gb, "Audio overflow\n"); - should_log_overflow = false; + if (!gb->turbo) { + GB_log(gb, "Audio overflow\n"); } */ } else { - GB_apu_get_samples_and_update_pcm_regs(gb, &gb->audio_buffer[gb->audio_position++]); - should_log_overflow = true; + if (gb->audio_quality == 1) { + GB_apu_get_samples_and_update_pcm_regs(gb, &gb->audio_buffer[gb->audio_position++]); + } + else { + gb->audio_buffer[gb->audio_position].left = round(gb->current_supersample.left / gb->n_subsamples); + gb->audio_buffer[gb->audio_position].right = round(gb->current_supersample.right / gb->n_subsamples); + gb->n_subsamples = 0; + gb->current_supersample = (GB_double_sample_t){0, }; + gb->audio_position++; + } } } } @@ -219,7 +248,14 @@ void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, unsigned int count) if (count > gb->audio_position) { // GB_log(gb, "Audio underflow: %d\n", count - gb->audio_position); - memset(dest + gb->audio_position, 0, (count - gb->audio_position) * sizeof(*gb->audio_buffer)); + 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]; + } + } + else { + memset(dest + gb->audio_position, 0, (count - gb->audio_position) * sizeof(*gb->audio_buffer)); + } count = gb->audio_position; } memcpy(dest, gb->audio_buffer, count * sizeof(*gb->audio_buffer)); @@ -443,3 +479,13 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) break; } } + +void GB_set_audio_quality(GB_gameboy_t *gb, unsigned quality) +{ + gb->audio_quality = quality; +} + +unsigned GB_apu_get_current_buffer_length(GB_gameboy_t *gb) +{ + return gb->audio_position; +} diff --git a/Core/apu.h b/Core/apu.h index ecb8a8b9..ffc530e6 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -14,6 +14,12 @@ typedef struct int16_t right; } GB_sample_t; +typedef struct +{ + double left; + double right; +} GB_double_sample_t; + /* Not all used on all channels */ /* All lengths are in APU ticks */ typedef struct @@ -40,7 +46,7 @@ typedef struct typedef struct { - uint8_t apu_cycles; + uint16_t apu_cycles; bool global_enable; uint32_t envelope_step_timer; uint32_t sweep_step_timer; @@ -55,7 +61,11 @@ typedef struct } GB_apu_t; void GB_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate); +/* Quality is the number of subsamples per sampling, for the sake of resampling. + 1 means on resampling at all, 0 is maximum quality. Default is 4. */ +void GB_set_audio_quality(GB_gameboy_t *gb, unsigned quality); 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); #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 372830f8..214cbcbf 100755 --- a/Core/gb.c +++ b/Core/gb.c @@ -91,6 +91,7 @@ void GB_init(GB_gameboy_t *gb) gb->input_callback = default_input_callback; gb->async_input_callback = default_async_input_callback; gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type + gb->audio_quality = 4; GB_reset(gb); } @@ -105,6 +106,7 @@ void GB_init_cgb(GB_gameboy_t *gb) gb->input_callback = default_input_callback; gb->async_input_callback = default_async_input_callback; gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type + gb->audio_quality = 4; GB_reset(gb); } diff --git a/Core/gb.h b/Core/gb.h index a978e1a4..48c4a724 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -20,7 +20,7 @@ #include "z80_cpu.h" #include "symbol_hash.h" -#define GB_STRUCT_VERSION 10 +#define GB_STRUCT_VERSION 11 enum { GB_REGISTER_AF, @@ -325,12 +325,8 @@ struct GB_gameboy_internal_s { /* Timing */ GB_SECTION(timing, - GB_PADDING(int64_t, last_vblank); uint32_t display_cycles; uint32_t div_cycles; - GB_PADDING(uint32_t, tima_cycles); - GB_PADDING(uint32_t, dma_cycles); - GB_aligned_double apu_sample_cycles; uint8_t tima_reload_state; /* After TIMA overflows, it becomes 0 for 4 cycles before actually reloading. */ uint16_t serial_cycles; ); @@ -366,7 +362,6 @@ struct GB_gameboy_internal_s { uint32_t background_palettes_rgb[0x20]; uint32_t sprite_palettes_rgb[0x20]; int16_t previous_lcdc_x; - GB_PADDING(uint8_t, padding); bool effective_window_enabled; uint8_t effective_window_y; bool stat_interrupt_line; @@ -400,12 +395,18 @@ struct GB_gameboy_internal_s { uint64_t cycles_since_last_sync; /* Audio */ - unsigned int buffer_size; - unsigned int sample_rate; - unsigned int audio_position; + 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; + double apu_subsample_cycles; + GB_double_sample_t current_supersample; + unsigned n_subsamples; + unsigned audio_quality; + /* Callbacks */ void *user_data; diff --git a/Core/timing.c b/Core/timing.c index 6ace186a..477263e6 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -132,6 +132,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_subsample_cycles += cycles; gb->apu.apu_cycles += cycles; gb->cycles_since_ir_change += cycles; gb->cycles_since_input_ir_change += cycles;