apple2ix/src/audio/soundcore.c
2018-11-17 15:57:27 -08:00

240 lines
5.7 KiB
C

/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2013-2015 Aaron Culliney
*
*/
/*
* Apple //e core sound system support. Source inspired/derived from AppleWin.
*
* 2015/10/01 AUDIO LIFECYCLE WARNING : CPU thread owns all audio, otherwise bad things may happen in system sound layer
* (OpenAL/OpenSLES/ALSA/etc)
*/
#include "common.h"
#define MAX_SOUND_DEVICES 100
static AudioContext_s *audioContext = NULL;
bool audio_isAvailable = false;
float audio_latencySecs = 0.25f;
typedef struct backend_node_s {
struct backend_node_s *next;
long order;
AudioBackend_s *backend;
} backend_node_s;
static backend_node_s *head = NULL;
static AudioBackend_s *currentBackend = NULL;
//-----------------------------------------------------------------------------
long audio_createSoundBuffer(INOUT AudioBuffer_s **audioBuffer) {
// CPU thread owns audio lifecycle (see note above)
ASSERT_ON_CPU_THREAD();
if (!audio_isAvailable) {
*audioBuffer = NULL;
return -1;
}
if (*audioBuffer) {
audio_destroySoundBuffer(audioBuffer);
}
long err = 0;
do {
if (!audioContext) {
LOG("Cannot create sound buffer, no context");
err = -1;
break;
}
err = audioContext->CreateSoundBuffer(audioContext, audioBuffer);
if (err) {
break;
}
} while (0);
return err;
}
void audio_destroySoundBuffer(INOUT AudioBuffer_s **audioBuffer) {
// CPU thread owns audio lifecycle (see note above)
ASSERT_ON_CPU_THREAD();
if (audioContext) {
audioContext->DestroySoundBuffer(audioContext, audioBuffer);
}
}
bool audio_init(void) {
// CPU thread owns audio lifecycle (see note above)
ASSERT_ON_CPU_THREAD();
if (audio_isAvailable) {
return true;
}
do {
if (audioContext) {
currentBackend->shutdown(&audioContext);
}
long err = currentBackend->setup((AudioContext_s**)&audioContext);
if (err) {
LOG("Failed to create an audio context!");
break;
}
audio_isAvailable = true;
} while (0);
return audio_isAvailable;
}
void audio_shutdown(void) {
// CPU thread owns audio lifecycle (see note above)
ASSERT_ON_CPU_THREAD();
if (!audio_isAvailable) {
return;
}
currentBackend->shutdown(&audioContext);
audio_isAvailable = false;
}
void audio_pause(void) {
// CPU thread owns audio lifecycle (see note above)
// Deadlock on Kindle Fire 1st Gen if audio_pause() and audio_resume() happen off CPU thread ...
#if TARGET_OS_MAC || TARGET_OS_PHONE
# warning FIXME TODO : this assert is firing on iOS port ... but the assert is valid ... fix soon
#else
ASSERT_ON_CPU_THREAD();
#endif
if (!audio_isAvailable) {
return;
}
currentBackend->pause(audioContext);
}
void audio_resume(void) {
// CPU thread owns audio lifecycle (see note above)
ASSERT_ON_CPU_THREAD();
if (!audio_isAvailable) {
return;
}
currentBackend->resume(audioContext);
}
void audio_setLatency(float latencySecs) {
audio_latencySecs = latencySecs;
}
float audio_getLatency(void) {
return audio_latencySecs;
}
//-----------------------------------------------------------------------------
void audio_registerBackend(AudioBackend_s *backend, long order) {
backend_node_s *node = MALLOC(sizeof(backend_node_s));
assert((uintptr_t)node);
node->next = NULL;
node->order = order;
node->backend = backend;
backend_node_s *p0 = NULL;
backend_node_s *p = head;
while (p && (order > p->order)) {
p0 = p;
p = p->next;
}
if (p0) {
p0->next = node;
} else {
head = node;
}
node->next = p;
currentBackend = head->backend;
}
void audio_printBackends(FILE *out) {
backend_node_s *p = head;
int count = 0;
while (p) {
const char *name = p->backend->name();
if (count++) {
fprintf(out, "|");
}
fprintf(out, "%s", name);
p = p->next;
}
}
static const char *_null_backend_name(void);
void audio_chooseBackend(const char *name) {
if (!name) {
name = _null_backend_name();
}
backend_node_s *p = head;
while (p) {
const char *bname = p->backend->name();
if (strcasecmp(name, bname) == 0) {
currentBackend = p->backend;
LOG("Setting current audio backend to %s", name);
break;
}
p = p->next;
}
}
AudioBackend_s *audio_getCurrentBackend(void) {
return currentBackend;
}
static const char *_null_backend_name(void) {
return "none";
}
static long _null_backend_setup(INOUT AudioContext_s **audio_context) {
*audio_context = NULL;
return -1;
}
static long _null_backend_shutdown(INOUT AudioContext_s **audio_context) {
*audio_context = NULL;
return -1;
}
static long _null_backend_pause(AudioContext_s *audio_context) {
return -1;
}
static long _null_backend_resume(AudioContext_s *audio_context) {
return -1;
}
static void _init_soundcore(void) {
LOG("Initializing audio subsystem");
static AudioBackend_s null_backend = { { 0 } };
null_backend.name = &_null_backend_name;
null_backend.setup = &_null_backend_setup;
null_backend.shutdown = &_null_backend_shutdown;
null_backend.pause = &_null_backend_pause;
null_backend.resume = &_null_backend_resume;
audio_registerBackend(&null_backend, AUD_PRIO_NULL);
}
static __attribute__((constructor)) void __init_soundcore(void) {
emulator_registerStartupCallback(CTOR_PRIORITY_LATE, &_init_soundcore);
}