X-Git-Url: https://git.distorted.org.uk/~mdw/jog/blobdiff_plain/b1ffb19d137241e62b4eea0e6db834bd624bdda2..e9060e7e4c0de71ef6d4b7d7b05c3167790073e3:/ausys-win32.c diff --git a/ausys-win32.c b/ausys-win32.c new file mode 100644 index 0000000..22670e3 --- /dev/null +++ b/ausys-win32.c @@ -0,0 +1,343 @@ +/* -*-c-*- + * + * $Id: ausys-win32.c,v 1.1 2002/02/02 19:16:28 mdw Exp $ + * + * Unix-specific (SDL) audio handling + * + * (c) 2002 Mark Wooding + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of Jog: Programming for a jogging machine. + * + * Jog is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Jog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jog; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/*----- Revision history --------------------------------------------------* + * + * $Log: ausys-win32.c,v $ + * Revision 1.1 2002/02/02 19:16:28 mdw + * New audio subsystem. + * + */ + +/*----- Header files ------------------------------------------------------*/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include "au.h" +#include "ausys.h" +#include "err.h" +#include "jog.h" + +/*----- Data structures ---------------------------------------------------*/ + +/* --- Queue of samples to play --- */ + +typedef struct qnode { + struct qnode *next; /* Next item in the queue */ + au_data *a; /* Pointer to sample data */ +} qnode; + +/*----- Global variables --------------------------------------------------*/ + +const char *const ausys_suffix = "wav"; + +/*----- Static data -------------------------------------------------------*/ + +static qnode *qhead = 0, *qtail = 0; /* Queue of samples to play */ +static qnode *qfree = 0; /* Queue of samples to free */ + +static pthread_t tid_play; /* The sample-playing thread */ +static pthread_mutex_t mx_queue; /* Mutex for @qhead@ */ +static pthread_cond_t cv_play; /* More samples to play */ + +static pthread_t tid_free; /* The sample-freeing thread */ +static pthread_mutex_t mx_free; /* Mutex for @qfree@ */ +static pthread_cond_t cv_free; /* More sample data to free */ + +static pthread_mutex_t mx_sub; /* Protects mLib @sub@ functions */ + +/*----- Thread structure --------------------------------------------------* + * + * In order to ensure that samples don't overlap each other, we play them + * synchronously in a separate thread. The @play@ function reads samples to + * play from the queue at @qhead@. Hence @qhead@ must be locked when it's + * modified, using @mx_queue@. + * + * In order to keep latency down when new sample data is wanted, we don't do + * potentially tedious things like freeing sample data blocks in the @play@ + * thread. Instead, there's a queue of sample blocks which need freeing, and + * a separate thread @dofree@ which processes the queue. The queue, @qfree@ + * is locked by @mx_free@. When new nodes are added to @qfree@, the + * condition @cv_free@ is signalled. + * + * Finally, the mLib `sub' module isn't thread-safe, so it's locked + * separately by @mx_sub@. + * + * There's an ordering on mutexes. If you want more than one mutex at a + * time, you must lock the greatest first. The ordering is: + * + * play > free > sub + */ + +/*----- Main code ---------------------------------------------------------*/ + +/* --- @dofree@ --- * + * + * Arguments: @void *u@ = unused pointer + * + * Returns: An unused pointer. + * + * Use: Frees unwanted sample queue nodes. + */ + +static void *play(void *u) +{ + qnode *q, *qq = 0; + + for (;;) { + + /* --- If we have nothing to do locally, fetch the external queue --- */ + + if (!qq) { + pthread_mutex_lock(&mx_queue); + while (!qhead) { + T( trace(T_AUSYS, "ausys: waiting for samples to play"); ) + pthread_cond_wait(&cv_play, &mx_queue); + } + qq = qhead; + qhead = qtail = 0; + pthread_mutex_unlock(&mx_queue); + } + + /* --- Play the next sample --- */ + q = qq; + qq = q->next; + T( trace(T_AUSYS, "ausys: playing `%s'", SYM_NAME(q->a->s)); ) + PlaySound((const void *)q->a->p, 0, SND_SYNC | SND_MEMORY); + + /* --- Put it on the free list --- */ + + pthread_mutex_lock(&mx_free); + q->next = qfree; + qfree = q; + pthread_mutex_unlock(&mx_free); + pthread_cond_signal(&cv_free); + } +} + +/* --- @dofree@ --- * + * + * Arguments: @void *u@ = unused pointer + * + * Returns: An unused pointer. + * + * Use: Frees unwanted sample queue nodes. + */ + +static void *dofree(void *u) +{ + qnode *q, *qq; + + pthread_mutex_lock(&mx_free); + for (;;) { + T( trace(T_AUSYS, "ausys: dofree sleeping"); ) + pthread_cond_wait(&cv_free, &mx_free); + T( trace(T_AUSYS, "ausys: dofree woken: work to do"); ) + + while (qfree) { + q = qfree; + qfree = 0; + pthread_mutex_lock(&mx_sub); + while (q) { + qq = q->next; + T( trace(T_AUSYS, "ausys: freeing `%s'", SYM_NAME(q->a->s)); ) + au_free_unlocked(q->a); + DESTROY(q); + q = qq; + } + pthread_mutex_unlock(&mx_sub); + } + } +} + +/* --- @ausys_init@ --- * + * + * Arguments: --- + * + * Returns: --- + * + * Use: Does any initialization required by the system-specific audio + * handler. + */ + +void ausys_init(void) +{ + pthread_attr_t ta; + int e; + + if ((e = pthread_mutex_init(&mx_free, 0)) != 0 || + (e = pthread_mutex_init(&mx_sub, 0)) != 0 || + (e = pthread_mutex_init(&mx_queue, 0)) != 0 || + (e = pthread_cond_init(&cv_play, 0)) != 0 || + (e = pthread_cond_init(&cv_free, 0)) != 0 || + (e = pthread_attr_init(&ta)) != 0 || + (e = pthread_attr_setdetachstate(&ta, PTHREAD_CREATE_DETACHED)) != 0 || + (e = pthread_create(&tid_free, &ta, play, 0)) != 0 || + (e = pthread_create(&tid_free, &ta, dofree, 0)) != 0) { + err_report(ERR_AUDIO, ERRAU_INIT, e, + "couldn't create audio threads: %s", strerror(errno)); + exit(EXIT_FAILURE); + } + pthread_attr_destroy(&ta); + + T( trace(T_AUSYS, "ausys: initalized ok"); ) +} + +/* --- @ausys_shutdown@ --- * + * + * Arguments: --- + * + * Returns: --- + * + * Use: Does any tidying up required. + */ + +void ausys_shutdown(void) +{ + pthread_cancel(tid_free); + pthread_cancel(tid_play); + T( trace(T_AUSYS, "ausys: shut down ok"); ) +} + +/* --- @ausys_lock@, @ausys_unlock@ --- * + * + * Arguments: --- + * + * Returns: --- + * + * Use: Locks or unlocks the audio subsystem. This protects the + * audio queue from becoming corrupted during all the tedious + * asynchronous stuff. + */ + +void ausys_lock(void) +{ + pthread_mutex_lock(&mx_free); + pthread_mutex_lock(&mx_sub); + T( trace(T_AUSYS, "ausys: acquired lock"); ) +} + +void ausys_unlock(void) +{ + pthread_mutex_unlock(&mx_sub); + pthread_mutex_unlock(&mx_free); + T( trace(T_AUSYS, "ausys: released lock"); ) +} + +/* --- @ausys_decode@ --- * + * + * Arguments: @au_sample *s@ = pointer to sample block + * @const void *p@ = pointer to sample file contents + * @size_t sz@ = size of sample file contents + * + * Returns: Pointer to a sample data structure. + * + * Use: Decodes a WAV file into something the system-specific layer + * actually wants to deal with. + */ + +au_data *ausys_decode(au_sample *s, const void *p, size_t sz) +{ + au_data *a; + + pthread_mutex_lock(&mx_sub); + a = CREATE(au_data); + pthread_mutex_unlock(&mx_sub); + a->p = xmalloc(sz); + memcpy(a->p, p, sz); + a->sz = sz; + + T( trace(T_AUSYS, "ausys: decoded `%s' ok", SYM_NAME(s)); ) + return (a); +} + +/* --- @ausys_queue@ --- * + * + * Arguments: @au_data *a@ = an audio thingy to play + * + * Returns: --- + * + * Use: Queues an audio sample to be played. The sample should be + * freed (with @au_free@) when it's no longer wanted. + */ + +void ausys_queue(au_data *a) +{ + qnode *q; + + pthread_mutex_lock(&mx_sub); + q = CREATE(qnode); + pthread_mutex_unlock(&mx_sub); + q->next = 0; + q->a = a; + pthread_mutex_lock(&mx_queue); + if (qtail) + qtail->next = q; + else + qhead = q; + qtail = q; + pthread_mutex_unlock(&mx_queue); + pthread_cond_signal(&cv_play); + T( trace(T_AUSYS, "ausys: queuing `%s'", SYM_NAME(a->s)); ) +} + +/* --- @ausys_free@ --- * + * + * Arguments: @au_data *a@ = an audio thingy to free + * + * Returns: --- + * + * Use: Frees a decoded audio sample. + */ + +void ausys_free(au_data *a) +{ + xfree(a->p); + DESTROY(a); + T( trace(T_AUSYS, "ausys: freeing data for `%s' ok", SYM_NAME(a->s)); ) +} + +/*----- That's all, folks -------------------------------------------------*/