Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GBA: Send audio samples to frontend with correct pacing #244

Merged
merged 1 commit into from
Oct 26, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 97 additions & 18 deletions src/platform/libretro/libretro.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,12 @@ FS_Archive sdmcArchive;

#include "libretro_core_options.h"

#define SAMPLES 512
#define GB_SAMPLES 512
#define SAMPLE_RATE 32768
/* An alpha factor of 1/180 is *somewhat* equivalent
* to calculating the average for the last 180
* frames, or 3 seconds of runtime... */
#define SAMPLES_PER_FRAME_MOVING_AVG_ALPHA (1.0f / 180.0f)
#define RUMBLE_PWM 35
#define EVENT_RATE 60

Expand Down Expand Up @@ -79,6 +84,9 @@ static int32_t _readGyroZ(struct mRotationSource* source);

static struct mCore* core;
static color_t* outputBuffer = NULL;
static int16_t *audioSampleBuffer = NULL;
static size_t audioSampleBufferSize;
static float audioSamplesPerFrameAvg;
static void* data;
static size_t dataSize;
static void* savedata;
Expand Down Expand Up @@ -1282,7 +1290,7 @@ void retro_get_system_av_info(struct retro_system_av_info* info) {

info->geometry.aspect_ratio = width / (double) height;
info->timing.fps = core->frequency(core) / (float) core->frameCycles(core);
info->timing.sample_rate = 32768;
info->timing.sample_rate = SAMPLE_RATE;
}

void retro_init(void) {
Expand Down Expand Up @@ -1393,6 +1401,13 @@ void retro_deinit(void) {
_deinitPostProcessing();
#endif

if (audioSampleBuffer) {
free(audioSampleBuffer);
audioSampleBuffer = NULL;
}
audioSampleBufferSize = 0;
audioSamplesPerFrameAvg = 0.0f;

if (sensorStateCallback) {
sensorStateCallback(0, RETRO_SENSOR_ACCELEROMETER_DISABLE, EVENT_RATE);
sensorStateCallback(0, RETRO_SENSOR_GYROSCOPE_DISABLE, EVENT_RATE);
Expand Down Expand Up @@ -1614,13 +1629,35 @@ void retro_run(void) {
videoCallback(NULL, width, height, VIDEO_WIDTH_MAX * sizeof(color_t));
}

// This was from aliaspider patch (4539a0e), game boy audio is buggy with it (adapted for this refactored core)
/*
int16_t samples[SAMPLES * 2];
int produced = blip_read_samples(core->getAudioChannel(core, 0), samples, SAMPLES, true);
blip_read_samples(core->getAudioChannel(core, 1), samples + 1, SAMPLES, true);
audioCallback(samples, produced);
*/
#ifdef M_CORE_GBA
if (core->platform(core) == mPLATFORM_GBA) {
blip_t *audioChannelLeft = core->getAudioChannel(core, 0);
blip_t *audioChannelRight = core->getAudioChannel(core, 1);
int samplesAvail = blip_samples_avail(audioChannelLeft);
if (samplesAvail > 0) {
/* Update 'running average' of number of
* samples per frame.
* Note that this is not a true running
* average, but just a leaky-integrator/
* exponential moving average, used because
* it is simple and fast (i.e. requires no
* window of samples). */
audioSamplesPerFrameAvg = (SAMPLES_PER_FRAME_MOVING_AVG_ALPHA * (float)samplesAvail) +
((1.0f - SAMPLES_PER_FRAME_MOVING_AVG_ALPHA) * audioSamplesPerFrameAvg);
size_t samplesToRead = (size_t)(audioSamplesPerFrameAvg);
/* Resize audio output buffer, if required */
if (audioSampleBufferSize < (samplesToRead * 2)) {
audioSampleBufferSize = (samplesToRead * 2);
audioSampleBuffer = realloc(audioSampleBuffer, audioSampleBufferSize * sizeof(int16_t));
}
int produced = blip_read_samples(audioChannelLeft, audioSampleBuffer, samplesToRead, true);
blip_read_samples(audioChannelRight, audioSampleBuffer + 1, samplesToRead, true);
if (produced > 0) {
audioCallback(audioSampleBuffer, (size_t)produced);
}
}
}
#endif

if (rumbleCallback) {
if (rumbleUp) {
Expand Down Expand Up @@ -1893,7 +1930,6 @@ bool retro_load_game(const struct retro_game_info* game) {
}
mCoreInitConfig(core, NULL);
core->init(core);
core->setAVStream(core, &stream);

#ifdef _3DS
outputBuffer = linearMemAlign(VIDEO_BUFF_SIZE, 0x80);
Expand All @@ -1903,10 +1939,53 @@ bool retro_load_game(const struct retro_game_info* game) {
memset(outputBuffer, 0xFFFF, VIDEO_BUFF_SIZE);
core->setVideoBuffer(core, outputBuffer, VIDEO_WIDTH_MAX);

core->setAudioBufferSize(core, SAMPLES);

blip_set_rates(core->getAudioChannel(core, 0), core->frequency(core), 32768);
blip_set_rates(core->getAudioChannel(core, 1), core->frequency(core), 32768);
#ifdef M_CORE_GBA
/* GBA emulation produces a fairly regular number
* of audio samples per frame that is consistent
* with the set sample rate. We therefore consume
* audio samples in retro_run() to achieve the
* best possible frame pacing */
if (core->platform(core) == mPLATFORM_GBA) {
/* Set initial output audio buffer size
* to nominal number of samples per frame.
* Buffer will be resized as required in
* retro_run(). */
size_t audioSamplesPerFrame = (size_t)((float)SAMPLE_RATE * (float)core->frameCycles(core) /
(float)core->frequency(core));
audioSampleBufferSize = audioSamplesPerFrame * 2;
audioSampleBuffer = malloc(audioSampleBufferSize * sizeof(int16_t));
audioSamplesPerFrameAvg = (float)audioSamplesPerFrame;
/* Internal audio buffer size should be
* audioSamplesPerFrame, but number of samples
* actually generated varies slightly on a
* frame-by-frame basis. We therefore allow
* for some wriggle room by setting double
* what we need (accounting for the hard
* coded blip buffer limit of 0x4000). */
size_t internalAudioBufferSize = audioSamplesPerFrame * 2;
if (internalAudioBufferSize > 0x4000) {
internalAudioBufferSize = 0x4000;
}
core->setAudioBufferSize(core, internalAudioBufferSize);
} else
#endif
{
/* GB/GBC emulation does not produce a number
* of samples per frame that is consistent with
* the set sample rate, and so it is unclear how
* best to handle this. We therefore fallback to
* using the regular stream-set _postAudioBuffer()
* callback with a fixed buffer size, which seems
* (historically) to produce adequate results */
core->setAVStream(core, &stream);
audioSampleBufferSize = GB_SAMPLES * 2;
audioSampleBuffer = malloc(audioSampleBufferSize * sizeof(int16_t));
audioSamplesPerFrameAvg = GB_SAMPLES;
core->setAudioBufferSize(core, GB_SAMPLES);
}

blip_set_rates(core->getAudioChannel(core, 0), core->frequency(core), SAMPLE_RATE);
blip_set_rates(core->getAudioChannel(core, 1), core->frequency(core), SAMPLE_RATE);

core->setPeripheral(core, mPERIPH_RUMBLE, &rumble);
core->setPeripheral(core, mPERIPH_ROTATION, &rotation);
Expand Down Expand Up @@ -2259,12 +2338,12 @@ void GBARetroLog(struct mLogger* logger, int category, enum mLogLevel level, con
logCallback(retroLevel, "%s: %s\n", mLogCategoryName(category), message);
}

/* Used only for GB/GBC content */
static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
UNUSED(stream);
int16_t samples[SAMPLES * 2];
blip_read_samples(left, samples, SAMPLES, true);
blip_read_samples(right, samples + 1, SAMPLES, true);
audioCallback(samples, SAMPLES);
blip_read_samples(left, audioSampleBuffer, GB_SAMPLES, true);
blip_read_samples(right, audioSampleBuffer + 1, GB_SAMPLES, true);
audioCallback(audioSampleBuffer, GB_SAMPLES);
}

static void _setRumble(struct mRumble* rumble, int enable) {
Expand Down