mirror of https://github.com/bsnes-emu/bsnes.git
2MHz audio downscaling support. Implemented NR50 and NR51.
This commit is contained in:
parent
baccf336d7
commit
a19ee1e5e0
146
Core/apu.c
146
Core/apu.c
|
@ -3,12 +3,64 @@
|
|||
#include <string.h>
|
||||
#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;
|
||||
}
|
||||
|
|
38
Core/apu.h
38
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);
|
||||
|
|
16
Core/gb.c
16
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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue