- 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.
This commit is contained in:
parent
b858f17425
commit
00623d4eea
60
Core/apu.c
60
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)
|
void GB_apu_run(GB_gameboy_t *gb)
|
||||||
{
|
{
|
||||||
if (gb->sample_rate == 0) return;
|
if (gb->sample_rate == 0) return;
|
||||||
static bool should_log_overflow = true;
|
|
||||||
while (gb->audio_copy_in_progress);
|
while (gb->audio_copy_in_progress);
|
||||||
double ticks_per_sample = (double) CPU_FREQUENCY / gb->sample_rate;
|
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) {
|
if (gb->apu_sample_cycles > ticks_per_sample) {
|
||||||
gb->apu_sample_cycles -= ticks_per_sample;
|
gb->apu_sample_cycles -= ticks_per_sample;
|
||||||
if (gb->audio_position == gb->buffer_size) {
|
if (gb->audio_position == gb->buffer_size) {
|
||||||
/*
|
/*
|
||||||
if (should_log_overflow && !gb->turbo) {
|
if (!gb->turbo) {
|
||||||
GB_log(gb, "Audio overflow\n");
|
GB_log(gb, "Audio overflow\n");
|
||||||
should_log_overflow = false;
|
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
GB_apu_get_samples_and_update_pcm_regs(gb, &gb->audio_buffer[gb->audio_position++]);
|
if (gb->audio_quality == 1) {
|
||||||
should_log_overflow = true;
|
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) {
|
if (count > gb->audio_position) {
|
||||||
// GB_log(gb, "Audio underflow: %d\n", 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;
|
count = gb->audio_position;
|
||||||
}
|
}
|
||||||
memcpy(dest, gb->audio_buffer, count * sizeof(*gb->audio_buffer));
|
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;
|
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;
|
||||||
|
}
|
||||||
|
12
Core/apu.h
12
Core/apu.h
@ -14,6 +14,12 @@ typedef struct
|
|||||||
int16_t right;
|
int16_t right;
|
||||||
} GB_sample_t;
|
} GB_sample_t;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
double left;
|
||||||
|
double right;
|
||||||
|
} GB_double_sample_t;
|
||||||
|
|
||||||
/* Not all used on all channels */
|
/* Not all used on all channels */
|
||||||
/* All lengths are in APU ticks */
|
/* All lengths are in APU ticks */
|
||||||
typedef struct
|
typedef struct
|
||||||
@ -40,7 +46,7 @@ typedef struct
|
|||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
uint8_t apu_cycles;
|
uint16_t apu_cycles;
|
||||||
bool global_enable;
|
bool global_enable;
|
||||||
uint32_t envelope_step_timer;
|
uint32_t envelope_step_timer;
|
||||||
uint32_t sweep_step_timer;
|
uint32_t sweep_step_timer;
|
||||||
@ -55,7 +61,11 @@ typedef struct
|
|||||||
} GB_apu_t;
|
} GB_apu_t;
|
||||||
|
|
||||||
void GB_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate);
|
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);
|
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
|
#ifdef GB_INTERNAL
|
||||||
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);
|
||||||
|
@ -91,6 +91,7 @@ void GB_init(GB_gameboy_t *gb)
|
|||||||
gb->input_callback = default_input_callback;
|
gb->input_callback = default_input_callback;
|
||||||
gb->async_input_callback = default_async_input_callback;
|
gb->async_input_callback = default_async_input_callback;
|
||||||
gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type
|
gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type
|
||||||
|
gb->audio_quality = 4;
|
||||||
|
|
||||||
GB_reset(gb);
|
GB_reset(gb);
|
||||||
}
|
}
|
||||||
@ -105,6 +106,7 @@ void GB_init_cgb(GB_gameboy_t *gb)
|
|||||||
gb->input_callback = default_input_callback;
|
gb->input_callback = default_input_callback;
|
||||||
gb->async_input_callback = default_async_input_callback;
|
gb->async_input_callback = default_async_input_callback;
|
||||||
gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type
|
gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type
|
||||||
|
gb->audio_quality = 4;
|
||||||
|
|
||||||
GB_reset(gb);
|
GB_reset(gb);
|
||||||
}
|
}
|
||||||
|
19
Core/gb.h
19
Core/gb.h
@ -20,7 +20,7 @@
|
|||||||
#include "z80_cpu.h"
|
#include "z80_cpu.h"
|
||||||
#include "symbol_hash.h"
|
#include "symbol_hash.h"
|
||||||
|
|
||||||
#define GB_STRUCT_VERSION 10
|
#define GB_STRUCT_VERSION 11
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
GB_REGISTER_AF,
|
GB_REGISTER_AF,
|
||||||
@ -325,12 +325,8 @@ struct GB_gameboy_internal_s {
|
|||||||
|
|
||||||
/* Timing */
|
/* Timing */
|
||||||
GB_SECTION(timing,
|
GB_SECTION(timing,
|
||||||
GB_PADDING(int64_t, last_vblank);
|
|
||||||
uint32_t display_cycles;
|
uint32_t display_cycles;
|
||||||
uint32_t div_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. */
|
uint8_t tima_reload_state; /* After TIMA overflows, it becomes 0 for 4 cycles before actually reloading. */
|
||||||
uint16_t serial_cycles;
|
uint16_t serial_cycles;
|
||||||
);
|
);
|
||||||
@ -366,7 +362,6 @@ struct GB_gameboy_internal_s {
|
|||||||
uint32_t background_palettes_rgb[0x20];
|
uint32_t background_palettes_rgb[0x20];
|
||||||
uint32_t sprite_palettes_rgb[0x20];
|
uint32_t sprite_palettes_rgb[0x20];
|
||||||
int16_t previous_lcdc_x;
|
int16_t previous_lcdc_x;
|
||||||
GB_PADDING(uint8_t, padding);
|
|
||||||
bool effective_window_enabled;
|
bool effective_window_enabled;
|
||||||
uint8_t effective_window_y;
|
uint8_t effective_window_y;
|
||||||
bool stat_interrupt_line;
|
bool stat_interrupt_line;
|
||||||
@ -400,12 +395,18 @@ struct GB_gameboy_internal_s {
|
|||||||
uint64_t cycles_since_last_sync;
|
uint64_t cycles_since_last_sync;
|
||||||
|
|
||||||
/* Audio */
|
/* Audio */
|
||||||
unsigned int buffer_size;
|
unsigned buffer_size;
|
||||||
unsigned int sample_rate;
|
unsigned sample_rate;
|
||||||
unsigned int audio_position;
|
unsigned audio_position;
|
||||||
bool audio_stream_started; /* detects first copy request to minimize lag */
|
bool audio_stream_started; /* detects first copy request to minimize lag */
|
||||||
volatile bool audio_copy_in_progress;
|
volatile bool audio_copy_in_progress;
|
||||||
volatile bool apu_lock;
|
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 */
|
/* Callbacks */
|
||||||
void *user_data;
|
void *user_data;
|
||||||
|
@ -132,6 +132,7 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles)
|
|||||||
// Not affected by speed boost
|
// Not affected by speed boost
|
||||||
gb->hdma_cycles += cycles;
|
gb->hdma_cycles += cycles;
|
||||||
gb->apu_sample_cycles += cycles;
|
gb->apu_sample_cycles += cycles;
|
||||||
|
gb->apu_subsample_cycles += cycles;
|
||||||
gb->apu.apu_cycles += cycles;
|
gb->apu.apu_cycles += cycles;
|
||||||
gb->cycles_since_ir_change += cycles;
|
gb->cycles_since_ir_change += cycles;
|
||||||
gb->cycles_since_input_ir_change += cycles;
|
gb->cycles_since_input_ir_change += cycles;
|
||||||
|
Loading…
Reference in New Issue
Block a user