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
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
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)
|
|
|
|
assert(pthread_self() == cpu_thread_id);
|
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)
|
|
|
|
assert(pthread_self() == cpu_thread_id);
|
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)
|
2015-07-26 20:38:43 +00:00
|
|
|
assert(pthread_self() == cpu_thread_id);
|
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-07-15 23:25:00 +00:00
|
|
|
audio_getCurrentBackend()->shutdown(&audioContext);
|
2015-07-05 01:13:01 +00:00
|
|
|
}
|
|
|
|
|
2017-07-15 23:25:00 +00:00
|
|
|
long err = audio_getCurrentBackend()->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)
|
2015-07-26 20:38:43 +00:00
|
|
|
assert(pthread_self() == cpu_thread_id);
|
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-07-15 23:25:00 +00:00
|
|
|
audio_getCurrentBackend()->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 ...
|
2016-01-05 08:11:58 +00:00
|
|
|
#ifdef __APPLE__
|
|
|
|
# warning FIXME TODO : this assert is firing on iOS port ... but the assert is valid ... fix soon
|
|
|
|
#else
|
|
|
|
assert(pthread_self() == cpu_thread_id);
|
|
|
|
#endif
|
2015-07-05 01:13:01 +00:00
|
|
|
if (!audio_isAvailable) {
|
|
|
|
return;
|
|
|
|
}
|
2017-07-15 23:25:00 +00:00
|
|
|
audio_getCurrentBackend()->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)
|
|
|
|
assert(pthread_self() == cpu_thread_id);
|
2015-07-05 01:13:01 +00:00
|
|
|
if (!audio_isAvailable) {
|
|
|
|
return;
|
|
|
|
}
|
2017-07-15 23:25:00 +00:00
|
|
|
audio_getCurrentBackend()->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) {
|
|
|
|
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
pthread_mutex_lock(&mutex);
|
|
|
|
|
|
|
|
backend_node_s *node = MALLOC(sizeof(backend_node_s));
|
|
|
|
assert(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;
|
|
|
|
|
|
|
|
pthread_mutex_unlock(&mutex);
|
|
|
|
}
|
|
|
|
|
|
|
|
AudioBackend_s *audio_getCurrentBackend(void) {
|
|
|
|
return head->backend;
|
|
|
|
}
|
|
|
|
|
|
|
|
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.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);
|
|
|
|
}
|
|
|
|
|