2013-10-06 06:22:08 +00:00
|
|
|
/*
|
2015-10-22 05:13:26 +00:00
|
|
|
* Apple // emulator for *ix
|
2015-06-17 05:19:19 +00:00
|
|
|
*
|
|
|
|
* This software package is subject to the GNU General Public License
|
2015-10-22 05:13:26 +00:00
|
|
|
* version 3 or later (your choice) as published by the Free Software
|
2015-06-17 05:19:19 +00:00
|
|
|
* Foundation.
|
|
|
|
*
|
2015-10-22 05:13:26 +00:00
|
|
|
* Copyright 2013-2015 Aaron Culliney
|
2013-10-06 06:22:08 +00:00
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2015-06-17 05:19:19 +00:00
|
|
|
/*
|
|
|
|
* Apple //e core sound system support. Source inspired/derived from AppleWin.
|
2015-10-02 07:11:49 +00:00
|
|
|
*
|
|
|
|
* 2015/10/01 AUDIO LIFECYCLE WARNING : CPU thread owns all audio, otherwise bad things may happen in system sound layer
|
|
|
|
* (OpenAL/OpenSLES/ALSA/etc)
|
2015-06-17 05:19:19 +00:00
|
|
|
*/
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-06-17 05:19:19 +00:00
|
|
|
#include "common.h"
|
2013-10-06 06:22:08 +00:00
|
|
|
|
|
|
|
#define MAX_SOUND_DEVICES 100
|
|
|
|
|
2015-06-17 04:33:31 +00:00
|
|
|
static AudioContext_s *audioContext = NULL;
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-06-14 21:46:15 +00:00
|
|
|
bool audio_isAvailable = false;
|
2015-07-25 19:41:50 +00:00
|
|
|
float audio_latencySecs = 0.25f;
|
2017-07-15 23:25:00 +00:00
|
|
|
|
|
|
|
|
|
|
|
typedef struct backend_node_s {
|
|
|
|
struct backend_node_s *next;
|
|
|
|
long order;
|
|
|
|
AudioBackend_s *backend;
|
|
|
|
} backend_node_s;
|
|
|
|
|
|
|
|
static backend_node_s *head = NULL;
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2017-09-10 01:35:00 +00:00
|
|
|
static AudioBackend_s *currentBackend = NULL;
|
|
|
|
|
2013-10-06 06:22:08 +00:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
2015-07-11 21:37:41 +00:00
|
|
|
long audio_createSoundBuffer(INOUT AudioBuffer_s **audioBuffer) {
|
2015-10-02 07:11:49 +00:00
|
|
|
// CPU thread owns audio lifecycle (see note above)
|
2018-03-25 22:54:23 +00:00
|
|
|
ASSERT_ON_CPU_THREAD();
|
2015-07-05 01:13:01 +00:00
|
|
|
|
|
|
|
if (!audio_isAvailable) {
|
2015-09-12 22:04:09 +00:00
|
|
|
*audioBuffer = NULL;
|
2015-07-05 01:13:01 +00:00
|
|
|
return -1;
|
|
|
|
}
|
2015-07-02 05:54:09 +00:00
|
|
|
|
|
|
|
if (*audioBuffer) {
|
|
|
|
audio_destroySoundBuffer(audioBuffer);
|
2015-06-17 04:33:31 +00:00
|
|
|
}
|
2015-01-31 21:57:10 +00:00
|
|
|
|
2015-06-17 04:33:31 +00:00
|
|
|
long err = 0;
|
|
|
|
do {
|
2015-06-26 04:32:37 +00:00
|
|
|
if (!audioContext) {
|
2017-07-16 00:34:43 +00:00
|
|
|
LOG("Cannot create sound buffer, no context");
|
2015-06-26 04:32:37 +00:00
|
|
|
err = -1;
|
|
|
|
break;
|
|
|
|
}
|
2015-07-11 21:37:41 +00:00
|
|
|
err = audioContext->CreateSoundBuffer(audioContext, audioBuffer);
|
2015-06-17 04:33:31 +00:00
|
|
|
if (err) {
|
2015-01-31 21:57:10 +00:00
|
|
|
break;
|
|
|
|
}
|
2015-06-17 04:33:31 +00:00
|
|
|
} while (0);
|
2015-01-31 21:57:10 +00:00
|
|
|
|
2015-06-17 04:33:31 +00:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2015-06-17 06:32:19 +00:00
|
|
|
void audio_destroySoundBuffer(INOUT AudioBuffer_s **audioBuffer) {
|
2015-10-02 07:11:49 +00:00
|
|
|
// CPU thread owns audio lifecycle (see note above)
|
2018-03-25 22:54:23 +00:00
|
|
|
ASSERT_ON_CPU_THREAD();
|
2015-06-26 04:32:37 +00:00
|
|
|
if (audioContext) {
|
2015-07-02 05:54:09 +00:00
|
|
|
audioContext->DestroySoundBuffer(audioContext, audioBuffer);
|
2015-06-26 04:32:37 +00:00
|
|
|
}
|
2013-10-06 06:22:08 +00:00
|
|
|
}
|
|
|
|
|
2015-06-17 04:45:45 +00:00
|
|
|
bool audio_init(void) {
|
2015-10-02 07:11:49 +00:00
|
|
|
// CPU thread owns audio lifecycle (see note above)
|
2018-03-25 22:54:23 +00:00
|
|
|
ASSERT_ON_CPU_THREAD();
|
2015-06-17 04:45:45 +00:00
|
|
|
if (audio_isAvailable) {
|
|
|
|
return true;
|
2015-01-31 21:57:10 +00:00
|
|
|
}
|
|
|
|
|
2015-07-05 01:13:01 +00:00
|
|
|
do {
|
|
|
|
if (audioContext) {
|
2017-09-10 01:35:00 +00:00
|
|
|
currentBackend->shutdown(&audioContext);
|
2015-07-05 01:13:01 +00:00
|
|
|
}
|
|
|
|
|
2017-09-10 01:35:00 +00:00
|
|
|
long err = currentBackend->setup((AudioContext_s**)&audioContext);
|
2015-07-05 01:13:01 +00:00
|
|
|
if (err) {
|
|
|
|
LOG("Failed to create an audio context!");
|
|
|
|
break;
|
|
|
|
}
|
2015-01-31 21:57:10 +00:00
|
|
|
|
2015-06-17 04:45:45 +00:00
|
|
|
audio_isAvailable = true;
|
2015-07-05 01:13:01 +00:00
|
|
|
} while (0);
|
2015-06-17 04:53:32 +00:00
|
|
|
|
2015-07-05 01:13:01 +00:00
|
|
|
return audio_isAvailable;
|
2013-10-06 06:22:08 +00:00
|
|
|
}
|
|
|
|
|
2015-06-17 04:45:45 +00:00
|
|
|
void audio_shutdown(void) {
|
2015-10-02 07:11:49 +00:00
|
|
|
// CPU thread owns audio lifecycle (see note above)
|
2018-03-25 22:54:23 +00:00
|
|
|
ASSERT_ON_CPU_THREAD();
|
2015-06-17 04:45:45 +00:00
|
|
|
if (!audio_isAvailable) {
|
2015-01-31 21:57:10 +00:00
|
|
|
return;
|
2015-06-17 04:45:45 +00:00
|
|
|
}
|
2017-09-10 01:35:00 +00:00
|
|
|
currentBackend->shutdown(&audioContext);
|
2015-06-14 21:46:15 +00:00
|
|
|
audio_isAvailable = false;
|
2013-10-06 06:22:08 +00:00
|
|
|
}
|
|
|
|
|
2015-06-17 06:02:25 +00:00
|
|
|
void audio_pause(void) {
|
2015-10-02 07:11:49 +00:00
|
|
|
// CPU thread owns audio lifecycle (see note above)
|
|
|
|
// Deadlock on Kindle Fire 1st Gen if audio_pause() and audio_resume() happen off CPU thread ...
|
2017-08-20 06:05:58 +00:00
|
|
|
#if TARGET_OS_MAC || TARGET_OS_PHONE
|
2016-01-05 08:11:58 +00:00
|
|
|
# warning FIXME TODO : this assert is firing on iOS port ... but the assert is valid ... fix soon
|
|
|
|
#else
|
2018-03-25 22:54:23 +00:00
|
|
|
ASSERT_ON_CPU_THREAD();
|
2016-01-05 08:11:58 +00:00
|
|
|
#endif
|
2015-07-05 01:13:01 +00:00
|
|
|
if (!audio_isAvailable) {
|
|
|
|
return;
|
|
|
|
}
|
2017-09-10 01:35:00 +00:00
|
|
|
currentBackend->pause(audioContext);
|
2015-06-17 06:02:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void audio_resume(void) {
|
2015-10-02 07:11:49 +00:00
|
|
|
// CPU thread owns audio lifecycle (see note above)
|
2018-03-25 22:54:23 +00:00
|
|
|
ASSERT_ON_CPU_THREAD();
|
2015-07-05 01:13:01 +00:00
|
|
|
if (!audio_isAvailable) {
|
|
|
|
return;
|
|
|
|
}
|
2017-09-10 01:35:00 +00:00
|
|
|
currentBackend->resume(audioContext);
|
2015-06-17 06:02:25 +00:00
|
|
|
}
|
|
|
|
|
2015-07-25 19:41:50 +00:00
|
|
|
void audio_setLatency(float latencySecs) {
|
|
|
|
audio_latencySecs = latencySecs;
|
|
|
|
}
|
|
|
|
|
|
|
|
float audio_getLatency(void) {
|
|
|
|
return audio_latencySecs;
|
|
|
|
}
|
|
|
|
|
2017-07-15 23:25:00 +00:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
void audio_registerBackend(AudioBackend_s *backend, long order) {
|
|
|
|
backend_node_s *node = MALLOC(sizeof(backend_node_s));
|
2018-11-17 23:57:27 +00:00
|
|
|
assert((uintptr_t)node);
|
2017-07-15 23:25:00 +00:00
|
|
|
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;
|
|
|
|
|
2017-09-10 01:35:00 +00:00
|
|
|
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;
|
|
|
|
}
|
2017-07-15 23:25:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
AudioBackend_s *audio_getCurrentBackend(void) {
|
2017-09-10 01:35:00 +00:00
|
|
|
return currentBackend;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char *_null_backend_name(void) {
|
|
|
|
return "none";
|
2017-07-15 23:25:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 } };
|
2017-09-10 01:35:00 +00:00
|
|
|
null_backend.name = &_null_backend_name;
|
2017-07-15 23:25:00 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|