From e9060e7e4c0de71ef6d4b7d7b05c3167790073e3 Mon Sep 17 00:00:00 2001 From: mdw Date: Sat, 2 Feb 2002 19:21:53 +0000 Subject: [PATCH] New audio subsystem. --- au.c | 425 ++++++++++++++++++++++++++++++++++++++++++++++++++++ au.h | 198 ++++++++++++++++++++++++ auerr.c | 104 +++++++++++++ auerr.h | 77 ++++++++++ aunum.c | 243 ++++++++++++++++++++++++++++++ aunum.h | 74 +++++++++ ausys-fake.c | 166 +++++++++++++++++++++ ausys-sdl.c | 472 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ausys-win32.c | 343 ++++++++++++++++++++++++++++++++++++++++++ ausys.h | 137 +++++++++++++++++ err.c | 11 +- err.h | 10 +- jog.h | 11 +- main.c | 42 +++++- rxglue.c | 92 +++++++++++- 15 files changed, 2393 insertions(+), 12 deletions(-) create mode 100644 au.c create mode 100644 au.h create mode 100644 auerr.c create mode 100644 auerr.h create mode 100644 aunum.c create mode 100644 aunum.h create mode 100644 ausys-fake.c create mode 100644 ausys-sdl.c create mode 100644 ausys-win32.c create mode 100644 ausys.h diff --git a/au.c b/au.c new file mode 100644 index 0000000..80382b8 --- /dev/null +++ b/au.c @@ -0,0 +1,425 @@ +/* -*-c-*- + * + * $Id: au.c,v 1.1 2002/02/02 19:16:28 mdw Exp $ + * + * High-level audio subsystem + * + * (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: au.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 +#include + +#include "au.h" +#include "ausys.h" +#include "err.h" +#include "jog.h" + +/*----- Static variables --------------------------------------------------*/ + +static unsigned au_flags = 0; +static sym_table au_tab; /* Sample cache, by name */ +static const char *au_dir = AUDIODIR; /* Directory containing samples */ +static struct { au_data *next, *prev; } au_spare; /* Lists for sample data */ +static size_t au_sz, au_max; /* Size of cached samples */ + +#define AU_SPARE ((au_data*)&au_spare) +#define f_init 1u + +/*----- Utility functions -------------------------------------------------*/ + +/* --- @filename@ --- * + * + * Arguments: @const char *tag@ = sample tag string + * + * Returns: A pointer to a filename for the sample. + * + * Use: Converts tag strings to filenames. + */ + +static const char *filename(const char *tag) +{ + static dstr d = DSTR_INIT; + + DRESET(&d); + dstr_putf(&d, "%s/%s.%s", au_dir, tag, ausys_suffix); + T( trace(T_AU, "au: map tag `%s' -> `%s'", tag, d.buf); ) + return (d.buf); +} + +/* --- @prune@ --- * + * + * Arguments: --- + * + * Returns: --- + * + * Use: Prunes the cache of old sample data. + */ + +static void prune(void) +{ + T( trace(T_AU, "au: pruning cache (%lu/%lu)", + (unsigned long)au_sz, (unsigned long)au_max); ) + while (au_sz > au_max && AU_SPARE->next != AU_SPARE) { + au_data *a = AU_SPARE->next; + au_sample *s = a->s; + assert(!a->ref); + AU_SPARE->next = a->next; + a->next->prev = AU_SPARE; + s->a = 0; + au_sz -= a->sz; + ausys_free(a); + T( trace(T_AU, "au: ... discarded `%s' (%lu/%lu)", SYM_NAME(s), + (unsigned long)au_sz, (unsigned long)au_max); ) + } +} + +/*----- Main code ---------------------------------------------------------*/ + +/* --- @au_init@ --- * + * + * Arguments: @const char *dir@ = samples directory, or null + * @size_t max@ = maximum cache size + * + * Returns: --- + * + * Use: Initializes the audio subsystem. + */ + +void au_init(const char *dir, size_t max) +{ + if (au_flags & f_init) + return; + + /* --- Set up the sound directory --- */ + + if (!dir) + dir = getenv("JOG_AUDIR"); + if (dir) + au_dir = dir; + + /* --- Initialize the sample cache --- */ + + sym_create(&au_tab); + AU_SPARE->next = AU_SPARE->prev = AU_SPARE; + au_max = max; + + /* --- Initialize the system-specific subsystem --- */ + + ausys_init(); + T( trace(T_AU, "au: initialized ok (dir = `%s')", au_dir); ) + + /* --- Done --- */ + + au_flags |= f_init; +} + +/* --- @au_shutdown@ --- * + * + * Arguments: --- + * + * Returns: --- + * + * Use: Shuts down the audio subsystem. + */ + +void au_shutdown(void) +{ + if (!(au_flags & f_init)) + return; + ausys_shutdown(); + T( trace(T_AU, "au: shutdown ok"); ) +} + +/* --- @au_find@ --- * + * + * Arguments: @const char *tag@ = sample tag string + * + * Returns: A pointer to the sample corresponding to the tag, or null if + * the sample wasn't found. + * + * Use: Looks up a sample by its name. + */ + +au_sample *au_find(const char *tag) +{ + unsigned f; + au_sample *s = sym_find(&au_tab, tag, -1, sizeof(*s), &f); + + if (!f) { + struct stat st; + + s->f = 0; + s->a = 0; + if (stat(filename(tag), &st)) + s->f |= AUF_NOMATCH; + } + if (s->f & AUF_NOMATCH) { + T( trace(T_AU, "au: sample `%s' not found%s", tag, f ? " (hit)": ""); ) + return (0); + } + T( trace(T_AU, "au: sample `%s' found%s", tag, f ? " (hit)" : ""); ) + return (s); +} + +/* --- @au_fetch@ --- * + * + * Arguments: @au_sample *s@ = sample pointer + * + * Returns: A pointer to the audio data for the sample. + * + * Use: Fetches a sample, and decodes it, if necessary. The decoded + * sample data is left with an outstanding reference to it, so + * it won't be freed. This is used internally by @au_queue@, + * before passing the fetched sample data to the system-specific + * layer for playback. It can also be used to lock sample data + * in memory (e.g., for the `abort' error message), or (by + * freeing it immediately afterwards) to prefetch a sample which + * will be used soon, to reduce the latency before the sample is + * heard. + */ + +au_data *au_fetch(au_sample *s) +{ + dstr d = DSTR_INIT; + struct stat st; + const char *fn; + size_t n; + ssize_t r; + au_data *a; + int fd; + + /* --- If we already have the sample data --- * + * + * If it's currently languishing in the spare bin, rescue it. If this + * doesn't work, we can release the audio lock, because nothing else messes + * with the spare list. + */ + + ausys_lock(); + a = s->a; + if (a) { + if (!a->ref) { + assert(a->next); + a->prev->next = a->next; + a->next->prev = a->prev; + a->next = a->prev = 0; + } + a->ref++; + T( trace(T_AU, "au: reusing sample `%s'", SYM_NAME(s)); ) + ausys_unlock(); + return (a); + } + ausys_unlock(); + + /* --- Read the file --- * + * + * Buffered I/O will just involve more copying. + */ + + T( trace(T_AU, "au: fetching sample `%s'", SYM_NAME(s)); ) + fn = filename(SYM_NAME(s)); + if ((fd = open(fn, O_RDONLY)) < 0) { + err_report(ERR_AUDIO, ERRAU_OPEN, errno, + "couldn't open sample `%s': %s", fn, strerror(errno)); + goto fail_0; + } + if (fstat(fd, &st)) { + err_report(ERR_AUDIO, ERRAU_OPEN, errno, + "couldn't fstat `%s': %s", fn, strerror(errno)); + goto fail_1; + } + n = st.st_size + 1; + if (n < 4096) + n = 4096; + for (;;) { + dstr_ensure(&d, n); + again: + if ((r = read(fd, d.buf + d.len, n)) < 0) { + if (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR) + goto again; + err_report(ERR_AUDIO, ERRAU_OPEN, errno, + "couldn't read `%s': %s", fn, strerror(errno)); + goto fail_1; + } + if (!r) + break; + d.len += r; + n -= r; + if (!n) + n = d.len; + } + close(fd); + + /* --- Convert it into internal form --- */ + + if ((a = ausys_decode(s, d.buf, d.len)) == 0) + goto fail_0; + au_sz += a->sz; + a->ref = 1; + a->next = a->prev = 0; + a->s = s; + s->a = a; + + /* --- Done --- */ + + ausys_lock(); + prune(); + ausys_unlock(); + goto done; +done: + dstr_destroy(&d); + return (a); + + /* --- Tidy up after botched file I/O --- */ + +fail_1: + close(fd); +fail_0: + dstr_destroy(&d); + return (0); +} + +/* --- @au_queue@ --- * + * + * Arguments: @au_sample *s@ = sample pointer + * + * Returns: Zero on success, nonzero on failure. + * + * Use: Queues a sample to be played. + */ + +int au_queue(au_sample *s) +{ + au_data *a; + + assert(s); + if ((a = au_fetch(s)) == 0) + return (-1); + T( trace(T_AU, "au: queuing sample `%s'", SYM_NAME(s)); ) + ausys_queue(s->a); + return (0); +} + +/* --- @au_free@, @au_free_unlocked@ --- * + * + * Arguments: @au_data *a@ = pointer to audio data block + * + * Returns: --- + * + * Use: Frees a sample data block when it's no longer required. + */ + +void au_free(au_data *a) +{ + ausys_lock(); + au_free_unlocked(a); + ausys_unlock(); +} + +void au_free_unlocked(au_data *a) +{ + /* --- If the sample is unreferenced, throw it in the spare bin --- * + * + * This can be called from a background audio processing thread, so we need + * to acquire the lock. + */ + + assert(a->ref); + assert(!a->next); + assert(!a->prev); + a->ref--; + if (a->ref) { + T( trace(T_AU, "au: drop ref to `%s' (other refs remain)", + SYM_NAME(a->s)); ) + ; + } else { + a->next = AU_SPARE; + a->prev = AU_SPARE->prev; + AU_SPARE->prev->next = a; + AU_SPARE->prev = a; + T( trace(T_AU, "au: drop last ref to `%s'", SYM_NAME(a->s)); ) + prune(); + } +} + +/* --- @au_play@, @au_tryplay@ --- * + * + * Arguments: @const char *tag@ = sample tag string + * + * Returns: Zero on success, nonzero on failure. + * + * Use: Convenience functions for queueing samples by tag. + * If @au_tryplay@ cannot find the requested sample, it returns + * a zero value; if @au_play@ cannot find the sample, it reports + * an error. + */ + +int au_tryplay(const char *tag) +{ + au_sample *s; + + if (!(au_flags & f_init)) + return (0); + if ((s = au_find(tag)) == 0 || au_queue(s)) + return (-1); + return (0); +} + +int au_play(const char *tag) +{ + int rc; + + if ((rc = au_tryplay(tag)) != 0) + err_report(ERR_AUDIO, ERRAU_NOTFOUND, 0, "sample `%s' not found", tag); + return (rc); +} + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/au.h b/au.h new file mode 100644 index 0000000..7678943 --- /dev/null +++ b/au.h @@ -0,0 +1,198 @@ +/* -*-c-*- + * + * $Id: au.h,v 1.1 2002/02/02 19:16:28 mdw Exp $ + * + * 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: au.h,v $ + * Revision 1.1 2002/02/02 19:16:28 mdw + * New audio subsystem. + * + */ + +#ifndef AU_H +#define AU_H + +#ifdef __cplusplus + extern "C" { +#endif + +/*----- Header files ------------------------------------------------------*/ + +#include + +#include +#include + +/*----- Data structures ---------------------------------------------------*/ + +/* --- An audio sample --- * + * + * We maintain a cache of sample data, keyed by textual tags. An entry in + * the cache may indicate one of three states: + * + * * a tag for a sample which doesn't exist (negative answer); + * + * * a sample whose data is currently resident; or + * + * * sample with nonresident data. + * + * A negative answer is indicated by the @AUF_NOMATCH@ flag. If an entry has + * resident data, the @a@ pointer is non-null. + */ + +typedef struct au_sample { + sym_base s; /* Base structure for name lookup */ + unsigned f; /* Flags for this entry */ + struct au_data *a; /* Sample data, if present */ +} au_sample; + +#define AUF_NOMATCH 1u /* Cached non-match for this tag */ + +/* --- Audio data --- * + * + * The actual sample data, in an internal representation chosen by the + * system-specific layer. + * + * The sample data can be in one of two states: in-use or spare. Sample data + * is considered to be in-use if the @ref@ field is nonzero. Spare sample + * data is left in-memory for efficiency's sake, but will be discarded + * (least-recently-used first) when the total size of sample data (as + * measured by the @sz@ fields) exceeds @AU_CACHEMAX@. + */ + +typedef struct au_data { + struct au_data *next, *prev; /* Other samples in this list */ + unsigned ref; /* Reference count for sample data */ + struct au_sample *s; /* Parent sample node */ + octet *p; /* Pointer to sample file data */ + size_t sz; /* Size of sample file data */ +} au_data; + +#define AU_CACHEMAX 0x01000000 /* Maximum resident sample data */ + +/*----- Functions provided ------------------------------------------------*/ + +/* --- @au_init@ --- * + * + * Arguments: @const char *dir@ = samples directory, or null + * @size_t max@ = maximum cache size + * + * Returns: --- + * + * Use: Initializes the audio subsystem. + */ + +extern void au_init(const char */*dir*/, size_t /*max*/); + +/* --- @au_shutdown@ --- * + * + * Arguments: --- + * + * Returns: --- + * + * Use: Shuts down the audio subsystem. + */ + +extern void au_shutdown(void); + +/* --- @au_find@ --- * + * + * Arguments: @const char *tag@ = sample tag string + * + * Returns: A pointer to the sample corresponding to the tag, or null if + * the sample wasn't found. + * + * Use: Looks up a sample by its name. + */ + +extern au_sample *au_find(const char */*tag*/); + +/* --- @au_fetch@ --- * + * + * Arguments: @au_sample *s@ = sample pointer + * + * Returns: A pointer to the audio data for the sample. + * + * Use: Fetches a sample, and decodes it, if necessary. The decoded + * sample data is left with an outstanding reference to it, so + * it won't be freed. This is used internally by @au_queue@, + * before passing the fetched sample data to the system-specific + * layer for playback. It can also be used to lock sample data + * in memory (e.g., for the `abort' error message), or (by + * freeing it immediately afterwards) to prefetch a sample which + * will be used soon, to reduce the latency before the sample is + * heard. + */ + +extern au_data *au_fetch(au_sample */*s*/); + +/* --- @au_queue@ --- * + * + * Arguments: @au_sample *s@ = sample pointer + * + * Returns: Zero on success, nonzero on failure. + * + * Use: Queues a sample to be played. + */ + +extern int au_queue(au_sample */*s*/); + +/* --- @au_free@, @au_free_unlocked@ --- * + * + * Arguments: @au_data *a@ = pointer to audio data block + * + * Returns: --- + * + * Use: Frees a sample data block when it's no longer required. + */ + +extern void au_free(au_data */*a*/); +extern void au_free_unlocked(au_data */*a*/); + +/* --- @au_play@, @au_tryplay@ --- * + * + * Arguments: @const char *tag@ = sample tag string + * + * Returns: Zero on success, nonzero on failure. + * + * Use: Convenience functions for queueing samples by tag. + * If @au_tryplay@ cannot find the requested sample, it returns + * a zero value; if @au_play@ cannot find the sample, it aborts + * the program. + */ + +extern int au_play(const char */*tag*/); +extern int au_tryplay(const char */*tag*/); + +/*----- That's all, folks -------------------------------------------------*/ + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/auerr.c b/auerr.c new file mode 100644 index 0000000..e003afd --- /dev/null +++ b/auerr.c @@ -0,0 +1,104 @@ +/* -*-c-*- + * + * $Id: auerr.c,v 1.1 2002/02/02 19:16:28 mdw Exp $ + * + * Audio error reporting + * + * (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: auerr.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 "au.h" +#include "aunum.h" +#include "err.h" + +/*----- Main code ---------------------------------------------------------*/ + +/* --- @auerr@ --- * + * + * Arguments: @int ctx@ = context code + * @int reason@ = reason code + * @unsigned long err@ = error code + * + * Returns: --- + * + * Use: Reports an error to the audio output. + */ + +void auerr(int ctx, int reason, unsigned long err) +{ + dstr d = DSTR_INIT; + + dstr_putf(&d, "%d", ctx); + if (reason) + dstr_putf(&d, "-%d", reason); + if (err) + dstr_putf(&d, "-%lu", err); + if (!au_tryplay(d.buf)) + goto done; + + au_play("e-error"); + au_play("e-ctx"); aunum_ulong(ctx); + if (reason) { au_play("e-reason"); aunum_ulong(reason); } + if (err) { au_play("e-code"); aunum_ulong(err); } + +done: + dstr_destroy(&d); +} + +/* --- @auerr_abort@ --- * + * + * Arguments: @int reason@ = abort reason code + * @unsigned long err@ = error code + * + * Returns: --- + * + * Use: Reports an abort message to te audio output. + */ + +void auerr_abort(int reason, unsigned long err) +{ + au_play("e-abort"); + au_play("e-reason"); aunum_ulong(reason); + au_play("e-code"); aunum_ulong(err); +} + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/auerr.h b/auerr.h new file mode 100644 index 0000000..bbc15d6 --- /dev/null +++ b/auerr.h @@ -0,0 +1,77 @@ +/* -*-c-*- + * + * $Id: auerr.h,v 1.1 2002/02/02 19:16:28 mdw Exp $ + * + * Audio error reporting + * + * (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: auerr.h,v $ + * Revision 1.1 2002/02/02 19:16:28 mdw + * New audio subsystem. + * + */ + +#ifndef AUERR_H +#define AUERR_H + +#ifdef __cplusplus + extern "C" { +#endif + +/*----- Functions provided ------------------------------------------------*/ + +/* --- @auerr@ --- * + * + * Arguments: @int ctx@ = context code + * @int reason@ = reason code + * @unsigned long err@ = error code + * + * Returns: --- + * + * Use: Reports an error to the audio output. + */ + +extern void auerr(int /*ctx*/, int /*reason*/, unsigned long /*err*/); + +/* --- @auerr_abort@ --- * + * + * Arguments: @int reason@ = abort reason code + * @unsigned long err@ = error code + * + * Returns: --- + * + * Use: Reports an abort message to te audio output. + */ + +extern void auerr_abort(int /*reason*/, unsigned long /*err*/); + +/*----- That's all, folks -------------------------------------------------*/ + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/aunum.c b/aunum.c new file mode 100644 index 0000000..4c3cc95 --- /dev/null +++ b/aunum.c @@ -0,0 +1,243 @@ +/* -*-c-*- + * + * $Id: aunum.c,v 1.1 2002/02/02 19:16:28 mdw Exp $ + * + * Reading numbers to audio output + * + * (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: aunum.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 "au.h" + +/*----- Main code ---------------------------------------------------------*/ + +/* --- @digit@ --- * + * + * Arguments: @char x@ = single digit to read + * + * Returns: --- + * + * Use: Reads a single digit. + */ + +static void digit(int x) +{ + static char tagbuf[4] = "n-?"; + tagbuf[2] = x; + au_play(tagbuf); +} + +/* --- @digits@ --- * + * + * Arguments: @const char *p@ = pointer to digits + * @size_t n@ = number of digits + * + * Returns: --- + * + * Use: Reads a sequence of digits. + */ + +static void digits(const char *p, size_t n) +{ + while (n) { + digit(*p++); + n--; + } +} + +/* --- @lasttwo@ --- * + * + * Arguments: @const char *p@ = pointer to digits + * @size_t n@ = number of digits + * @unsigned f@ = flags + * + * Returns: Nonzero if the number was nonzero. + * + * Use: Reads out a number of no more than two digits. + */ + +#define f_and 1u + +static int lasttwo(const char *p, size_t n, unsigned f) +{ + static char tagbuf[5] = "n-??"; + + while (n && *p == '0') { + n--; + p++; + } + if (!n) + return (0); + + if (f & f_and) + au_play("n-and"); + if (n == 1) + digit(*p); + else if (*p == '1') { + tagbuf[2] = p[0]; + tagbuf[3] = p[1]; + au_play(tagbuf); + } else { + tagbuf[2] = *p++; + tagbuf[3] = '0'; + au_play(tagbuf); + if (*p != '0') + digit(*p); + } + return (1); +} + +/* --- @bignum@ --- * + * + * Arguments: @const char *p@ = pointer to digits + * @size_t n@ = number of digits + * + * Returns: Nonzero if the number was nonzero. + * + * Use: Reads out a (large) integer in English. + */ + +static int bignum(const char *p, size_t n) +{ + int nz = 0; + int rc; + + if (n > 6) { + digits(p, n); + return (1); + } + if (n > 3) { + rc = bignum(p, n - 3); + p += n - 3; + n = 3; + if (rc) { + au_play("n-thou"); + nz = 1; + } + } + if (n > 2) { + if (*p == '0') { + p++; + } else { + digit(*p++); + au_play("n-hun"); + nz = 1; + } + n--; + } + return (lasttwo(p, n, nz ? f_and : 0) || nz); +} + +/* --- @aunum@ --- * + * + * Arguments: @const char *p@ = pointer to number's textual representation + * + * Returns: --- + * + * Use: Reads the given number aloud. + */ + +void aunum(const char *p) +{ + size_t pl; + int nz; + + /* --- Pick off a leading sign --- */ + +again: + if (*p == '+') + p++; + else if (*p == '-') { + au_play("n-minus"); + p++; + } + + /* --- Work out how many digits we have --- */ + + p += strspn(p, "0"); + pl = strspn(p, "0123456789"); + nz = bignum(p, pl); + p += pl; + + /* --- If the value was zero, and there's no `point', say `zero' --- */ + + if (*p != '.' && !nz) + au_play("n-0"); + if (*p == '.') { + au_play("n-point"); + p++; + pl = strspn(p, "0123456789"); + digits(p, pl); + p += pl; + } + if (*p == 'e' || *p == 'E') { + au_play("n-exp"); + p++; + goto again; + } + + /* --- Run out of things to do --- */ + + return; +} + +/* --- @aunum_ulong@ --- * + * + * Arguments: @unsigned long n@ = number to be read + * + * Returns: --- + * + * Use: Reads a number expressed as an @unsigned long@. + */ + +void aunum_ulong(unsigned long n) +{ + dstr d = DSTR_INIT; + + dstr_putf(&d, "%lu", n); + aunum(d.buf); + dstr_destroy(&d); +} + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/aunum.h b/aunum.h new file mode 100644 index 0000000..c05599d --- /dev/null +++ b/aunum.h @@ -0,0 +1,74 @@ +/* -*-c-*- + * + * $Id: aunum.h,v 1.1 2002/02/02 19:16:28 mdw Exp $ + * + * Reading numbers to audio output + * + * (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: aunum.h,v $ + * Revision 1.1 2002/02/02 19:16:28 mdw + * New audio subsystem. + * + */ + +#ifndef AU_NUM_H +#define AU_NUM_H + +#ifdef __cplusplus + extern "C" { +#endif + +/*----- Functions provided ------------------------------------------------*/ + +/* --- @aunum@ --- * + * + * Arguments: @const char *p@ = pointer to number's textual representation + * + * Returns: --- + * + * Use: Reads the given number aloud. + */ + +extern void aunum(const char */*p*/); + +/* --- @aunum_ulong@ --- * + * + * Arguments: @unsigned long n@ = number to be read + * + * Returns: --- + * + * Use: Reads a number expressed as an @unsigned long@. + */ + +extern void aunum_ulong(unsigned long /*n*/); + +/*----- That's all, folks -------------------------------------------------*/ + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/ausys-fake.c b/ausys-fake.c new file mode 100644 index 0000000..121cb79 --- /dev/null +++ b/ausys-fake.c @@ -0,0 +1,166 @@ +/* -*-c-*- + * + * $Id: ausys-fake.c,v 1.1 2002/02/02 19:16:28 mdw Exp $ + * + * Fake not-audio-at-all audio driver + * + * (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-fake.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 "au.h" +#include "ausys.h" +#include "jog.h" + +/*----- Global variables --------------------------------------------------*/ + +const char *const ausys_suffix = "txt"; + +/*----- Main code ---------------------------------------------------------*/ + +/* --- @ausys_init@ --- * + * + * Arguments: --- + * + * Returns: --- + * + * Use: Does any initialization required by the system-specific audio + * handler. + */ + +void ausys_init(void) +{ + T( trace(T_AUSYS, "ausys: initalized ok"); ) +} + +/* --- @ausys_shutdown@ --- * + * + * Arguments: --- + * + * Returns: --- + * + * Use: Does any tidying up required. + */ + +void ausys_shutdown(void) +{ + 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) { T( trace(T_AUSYS, "ausys: acquired lock"); ) ; } +void ausys_unlock(void) { 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 = CREATE(au_data); + const octet *pp = p; + const octet *qq = memchr(pp, '\n', sz); + + if (qq) + sz = qq - pp; + else + sz++; + a->p = xmalloc(sz); + a->sz = sz; + memcpy(a->p, p, sz); + a->p[sz] = 0; + 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) +{ + T( trace(T_AUSYS, "ausys: queuing `%s'", SYM_NAME(a->s)); ) + printf("[%s]\n", a->p); + au_free(a); +} + +/* --- @ausys_free@ --- * + * + * Arguments: @au_data *a@ = an audio thingy to free + * + * Returns: --- + * + * Use: Frees a decoded audio sample. + */ + +void ausys_free(au_data *a) +{ + T( trace(T_AUSYS, "ausys: freeing data for `%s' ok", SYM_NAME(a->s)); ) + xfree(a->p); + DESTROY(a); +} + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/ausys-sdl.c b/ausys-sdl.c new file mode 100644 index 0000000..c54d378 --- /dev/null +++ b/ausys-sdl.c @@ -0,0 +1,472 @@ +/* -*-c-*- + * + * $Id: ausys-sdl.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-sdl.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 SDL_AudioSpec auspec; /* Driver's selected parameters */ +static qnode *qhead = 0, *qtail = 0; /* Queue of samples to play */ +static qnode *qfree = 0; /* Queue of samples to free */ + +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 --------------------------------------------------* + * + * SDL makes a buffer-stuffing thread that @fill@ is run from. This function + * reads samples to play from the queue at @qhead@. Hence @qhead@ must be + * locked when it's modified, using @SDL_LockAudio@. + * + * 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 @fill@ + * 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: + * + * SDL_Audio > free > sub + */ + +/*----- Main code ---------------------------------------------------------*/ + +/* --- @fill@ --- * + * + * Arguments: @void *u@ = user data (ignored) + * @octet *q@ = pointer to buffer to fill in + * @int qsz@ = size of the buffer + * + * Returns: --- + * + * Use: Fills an audio buffer with precomputed stuff. + */ + +static void fill(void *u, octet *q, int qsz) +{ + static const octet *p = 0; /* Current place in current sample */ + static size_t sz = 0; /* How far to go in current sample */ + qnode *qf = 0, **qft = &qf; /* Samples we've finished with */ + + /* --- If @p@ is null we need to get a new sample --- */ + + if (!p && qhead) { + T( trace(T_AUSYS, "ausys: begin playing `%s'", SYM_NAME(qhead->a->s)); ) + p = qhead->a->p; + sz = qhead->a->sz; + } + + /* --- Main queue-filling loop --- */ + + while (qsz) { + size_t n; + + /* --- If the queue is empty, play silence --- */ + + if (!p) { + memset(q, auspec.silence, qsz); + break; + } + + /* --- Fill the buffer with my sample --- */ +exit(0); + n = qsz; + if (n > sz) + n = sz; + memcpy(q, p, n); + p += n; q += n; + sz -= n; qsz -= n; + + /* --- If the sample is used up, throw it away --- */ + + if (!sz) { + qnode *qq = qhead; + qhead = qq->next; + if (!qhead) + qtail = 0; + T( trace(T_AUSYS, "ausys: put `%s' on free list", + SYM_NAME(qq->a->s)); ) + *qft = qq; + qft = &qq->next; + if (qhead) { + T( trace(T_AUSYS, "ausys: begin playing `%s'", + SYM_NAME(qhead->a->s)); ) + p = qhead->a->p; + sz = qhead->a->sz; + } else { + T( trace(T_AUSYS, "ausys: sample queue is empty"); ) + p = 0; + sz = 0; + } + } + } + + /* --- Finally dump freed samples, all in one go --- */ + + if (qf) { + pthread_mutex_lock(&mx_free); + *qft = qfree; + qfree = qf; + 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) +{ + SDL_AudioSpec want; + pthread_attr_t ta; + int e; + + /* --- Crank up the sample-freeing thread --- */ + + if ((e = pthread_mutex_init(&mx_free, 0)) != 0 || + (e = pthread_mutex_init(&mx_sub, 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, 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); + + /* --- Crank up the SDL audio subsystem --- */ + + if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_NOPARACHUTE)) { + err_report(ERR_AUDIO, ERRAU_INIT, 0, + "couldn't initialize SDL audio: %s", SDL_GetError()); + exit(EXIT_FAILURE); + } + + /* --- Open the audio device --- */ + + want.freq = 8000; + want.format = AUDIO_U8; + want.channels = 1; + want.samples = 4096; + want.callback = fill; + want.userdata = 0; + + if (SDL_OpenAudio(&want, &auspec)) { + err_report(ERR_AUDIO, ERRAU_INIT, 0, + "couldn't open audio device: %s", SDL_GetError()); + exit(EXIT_FAILURE); + } + + /* --- Prepare whatever needs to be done --- */ + + SDL_PauseAudio(0); + + T( trace(T_AUSYS, "ausys: initalized ok"); ) +} + +/* --- @ausys_shutdown@ --- * + * + * Arguments: --- + * + * Returns: --- + * + * Use: Does any tidying up required. + */ + +void ausys_shutdown(void) +{ + /* --- Busy-wait until sound buffer empties --- */ + + while (qhead) { + struct timeval tv = { 0, 100000 }; + select(0, 0, 0, 0, &tv); + } + + /* --- Shut stuff down --- */ + + pthread_cancel(tid_free); + SDL_CloseAudio(); + SDL_Quit(); + + 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; + SDL_AudioCVT cvt; + SDL_AudioSpec spec; + SDL_RWops *rw; + Uint8 *buf; + Uint32 len; + void *q; + + /* --- Firstly, extract the audio data from the WAV file --- */ + + rw = SDL_RWFromMem((void *)p, sz); + if (!SDL_LoadWAV_RW(rw, 1, &spec, &buf, &len)) { + err_report(ERR_AUDIO, ERRAU_OPEN, 0, + "can't read WAV file for `%s': %s", + SYM_NAME(s), SDL_GetError()); + goto fail_0; + } + + /* --- Now convert that to the driver's expected format --- */ + + if (SDL_BuildAudioCVT(&cvt, spec.format, spec.channels, spec.freq, + auspec.format, auspec.channels, auspec.freq) == -1) { + err_report(ERR_AUDIO, ERRAU_OPEN, 0, + "can't build conversion structure for `%s': %s", + SYM_NAME(s), SDL_GetError()); + goto fail_1; + } + + cvt.buf = xmalloc(len * cvt.len_mult); + cvt.len = len; + memcpy(cvt.buf, buf, len); + SDL_FreeWAV(buf); + + if (SDL_ConvertAudio(&cvt)) { + err_report(ERR_AUDIO, ERRAU_OPEN, 0, + "can't convert `%s': %s", SYM_NAME(s), SDL_GetError()); + goto fail_2; + } + + /* --- Now fiddle with buffers --- */ + + sz = len * cvt.len_ratio; + if (cvt.len_ratio >= 1) + q = cvt.buf; + else { + q = xmalloc(sz); + memcpy(q, cvt.buf, sz); + xfree(cvt.buf); + } + + /* --- Construct a new node --- */ + + pthread_mutex_lock(&mx_sub); + a = CREATE(au_data); + pthread_mutex_unlock(&mx_sub); + a->p = q; + a->sz = sz; + + T( trace(T_AUSYS, "ausys: decoded `%s' ok", SYM_NAME(s)); ) + return (a); + + /* --- Tidy up after errors --- */ + +fail_2: + xfree(cvt.buf); + goto fail_0; +fail_1: + SDL_FreeWAV(buf); +fail_0: + return (0); +} + +/* --- @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; + SDL_LockAudio(); + if (qtail) + qtail->next = q; + else + qhead = q; + qtail = q; + SDL_UnlockAudio(); + 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 -------------------------------------------------*/ 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 -------------------------------------------------*/ diff --git a/ausys.h b/ausys.h new file mode 100644 index 0000000..d228bbc --- /dev/null +++ b/ausys.h @@ -0,0 +1,137 @@ +/* -*-c-*- + * + * $Id: ausys.h,v 1.1 2002/02/02 19:16:28 mdw Exp $ + * + * System-specific audio-handling interface + * + * (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.h,v $ + * Revision 1.1 2002/02/02 19:16:28 mdw + * New audio subsystem. + * + */ + +#ifndef AUSYS_H +#define AUSYS_H + +#ifdef __cplusplus + extern "C" { +#endif + +/*----- Header files ------------------------------------------------------*/ + +#ifndef AU_H +# include "au.h" +#endif + +/*----- Global variables --------------------------------------------------*/ + +extern const char *const ausys_suffix; /* Suffix for audio files */ + +/*----- Functions provided ------------------------------------------------*/ + +/* --- @ausys_init@ --- * + * + * Arguments: --- + * + * Returns: --- + * + * Use: Does any initialization required by the system-specific audio + * handler. + */ + +extern void ausys_init(void); + +/* --- @ausys_shutdown@ --- * + * + * Arguments: --- + * + * Returns: --- + * + * Use: Does any tidying up required. + */ + +extern void ausys_shutdown(void); + +/* --- @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. + */ + +extern void ausys_lock(void); +extern void ausys_unlock(void); + +/* --- @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. + */ + +extern void ausys_queue(au_data */*a*/); + +/* --- @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. + */ + +extern au_data *ausys_decode(au_sample */*s*/, + const void */*p*/, size_t /*sz*/); + +/* --- @ausys_free@ --- * + * + * Arguments: @au_data *a@ = an audio thingy to free + * + * Returns: --- + * + * Use: Frees a decoded audio sample. + */ + +extern void ausys_free(au_data */*a*/); + +/*----- That's all, folks -------------------------------------------------*/ + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/err.c b/err.c index 27065b0..e34b555 100644 --- a/err.c +++ b/err.c @@ -1,6 +1,6 @@ /* -*-c-*- * - * $Id: err.c,v 1.1 2002/01/25 19:34:45 mdw Exp $ + * $Id: err.c,v 1.2 2002/02/02 19:16:46 mdw Exp $ * * Error reporting * @@ -29,6 +29,9 @@ /*----- Revision history --------------------------------------------------* * * $Log: err.c,v $ + * Revision 1.2 2002/02/02 19:16:46 mdw + * New audio subsystem. + * * Revision 1.1 2002/01/25 19:34:45 mdw * Initial revision * @@ -50,7 +53,7 @@ #include #include -/* #include "au.h" */ +#include "auerr.h" #include "err.h" /*----- Static variables --------------------------------------------------*/ @@ -78,7 +81,7 @@ void err_abortv(int reason, unsigned long err, const char *msg, va_list *ap) fprintf(stderr, "%s: fatal error (code %d-%lu): ", QUIS, reason, err); vfprintf(stderr, msg, *ap); putc('\n', stderr); - /* au_abort(reason, err); */ + auerr_abort(reason, err); abort(); } @@ -151,7 +154,7 @@ void err_reportv(int ctx, int reason, unsigned long err, if (ctx && !(flags & f_thread)) { unsigned f = flags; flags |= f_thread; -/* au_misc(AU_ERROR, ctx, reason, err); */ + auerr(ctx, reason, err); flags = f; } t = time(0); diff --git a/err.h b/err.h index fef75b7..8de0800 100644 --- a/err.h +++ b/err.h @@ -1,6 +1,6 @@ /* -*-c-*- * - * $Id: err.h,v 1.1 2002/01/25 19:34:45 mdw Exp $ + * $Id: err.h,v 1.2 2002/02/02 19:16:46 mdw Exp $ * * Error reporting * @@ -29,6 +29,9 @@ /*----- Revision history --------------------------------------------------* * * $Log: err.h,v $ + * Revision 1.2 2002/02/02 19:16:46 mdw + * New audio subsystem. + * * Revision 1.1 2002/01/25 19:34:45 mdw * Initial revision * @@ -92,6 +95,11 @@ # define ERRTX_READ 4u /* Read error from transport */ # define ERRTX_CONFIG 5u /* Error in configuration */ +#define ERR_AUDIO 5u /* Audio subsystem */ +# define ERRAU_NOTFOUND 1u /* Requested sample wasn't found */ +# define ERRAU_OPEN 2u /* Couldn't read sample file */ +# define ERRAU_INIT 3u /* Couldn't initialize audio */ + /*----- Functions provided ------------------------------------------------*/ /* --- @err_abort@ --- * diff --git a/jog.h b/jog.h index 9d26310..ce9ff89 100644 --- a/jog.h +++ b/jog.h @@ -1,6 +1,6 @@ /* -*-c-*- * - * $Id: jog.h,v 1.1 2002/01/30 09:27:38 mdw Exp $ + * $Id: jog.h,v 1.2 2002/02/02 19:16:57 mdw Exp $ * * Global header file * @@ -29,6 +29,9 @@ /*----- Revision history --------------------------------------------------* * * $Log: jog.h,v $ + * Revision 1.2 2002/02/02 19:16:57 mdw + * New audio subsystem. + * * Revision 1.1 2002/01/30 09:27:38 mdw * New global header file, declares trace codes. * @@ -49,9 +52,11 @@ /* --- Tracing settings --- */ -#define T_TX 1u /* Tracing transport */ +#define T_TX 1u /* Transport */ #define T_TXSYS 2u /* Low-level transport */ -#define T_ALL (T_TX | T_TXSYS) +#define T_AU 4u /* Audio */ +#define T_AUSYS 8u /* System-specific audio */ +#define T_ALL (T_TX | T_TXSYS | T_AU | T_AUSYS) /*----- That's all, folks -------------------------------------------------*/ diff --git a/main.c b/main.c index b30b7ec..19f40fa 100644 --- a/main.c +++ b/main.c @@ -1,6 +1,6 @@ /* -*-c-*- * - * $Id: main.c,v 1.2 2002/01/30 09:27:55 mdw Exp $ + * $Id: main.c,v 1.3 2002/02/02 19:21:53 mdw Exp $ * * Main program * @@ -29,6 +29,9 @@ /*----- Revision history --------------------------------------------------* * * $Log: main.c,v $ + * Revision 1.3 2002/02/02 19:21:53 mdw + * New audio subsystem. + * * Revision 1.2 2002/01/30 09:27:55 mdw * Parse tracing options on the command-line. * @@ -39,6 +42,10 @@ /*----- Header files ------------------------------------------------------*/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + #include #include #include @@ -54,6 +61,7 @@ #include #include +#include "au.h" #include "err.h" #include "jog.h" #include "rxglue.h" @@ -67,6 +75,7 @@ static int sigtab[] = { SIGINT, SIGQUIT, SIGTERM, SIGHUP, -1 }; static void tidy(void) { txsu_shutdown(); + au_shutdown(); } static void sigtidy(int sig) @@ -81,7 +90,8 @@ static void sigtidy(int sig) static void usage(FILE *fp) { pquis(fp, "\ -Usage: $ [-t TRANSPORT] [-f FILE] [-c CONFIG] SCRIPT ARG...\n\ +Usage: $ [-t TRANSPORT] [-f FILE] [-c CONFIG] [-a AUDIR] [-x SIZE]\n\ + SCRIPT ARG...\n\ "); } @@ -105,6 +115,9 @@ Options provided:\n\ -t, --transport=NAME Use transport type NAME.\n\ -f, --tx-file=FILE Communicate using the named FILE.\n\ -c, --tx-config=CONFIG Use CONFIG as transport configuration.\n\ +\n\ +-a, --audio=DIR Set directory containing audio samples.\n\ +-x, --cache-max=SIZE Maximum size for audio sample cache.\n\ ", fp); } @@ -116,6 +129,8 @@ int main(int argc, char *argv[]) unsigned f = 0; int rc = 0; int i; + const char *audir = 0; + size_t aumax = AU_CACHEMAX; #define f_bogus 1u @@ -155,6 +170,15 @@ int main(int argc, char *argv[]) { "txfile", OPTF_ARGREQ, 0, 'f' }, { "file", OPTF_ARGREQ, 0, 'f' }, + /* --- Audio configuration stuff --- */ + + { "audio-directory", + OPTF_ARGREQ, 0, 'a' }, + { "audio-cache-max", + OPTF_ARGREQ, 0, 'x' }, + { "cache-max", + OPTF_ARGREQ, 0, 'x' }, + /* --- Debugging stuff --- */ #ifndef NTRACE @@ -170,12 +194,14 @@ int main(int argc, char *argv[]) static const trace_opt tropt[] = { { 'x', T_TX, "transport layer" }, { 's', T_TXSYS, "low-level transport" }, + { 'a', T_AU, "audio subsystem" }, + { 'y', T_AUSYS, "system-specific audio" }, { 'A', T_ALL, "all of the above" }, { 0, 0, 0 } }; #endif - i = mdwopt(argc, argv, "hvu" "t:c:f:" T("T:"), opt, 0, 0, 0); + i = mdwopt(argc, argv, "hvu" "t:c:f:" "a:x:" T("T:"), opt, 0, 0, 0); if (i < 0) break; @@ -205,6 +231,15 @@ int main(int argc, char *argv[]) txfile = optarg; break; + /* --- Audio configuration stuff --- */ + + case 'a': + audir = optarg; + break; + case 'x': + aumax = strtoul(optarg, 0, 0); + break; + /* --- Tracing --- */ #ifndef NTRACE @@ -226,6 +261,7 @@ int main(int argc, char *argv[]) exit(EXIT_FAILURE); } + au_init(audir, aumax); rc = rx_runfile(argv[optind], argc - optind - 1, (const char *const *)argv + optind + 1); return (rc ? EXIT_FAILURE : 0); diff --git a/rxglue.c b/rxglue.c index 9126ff4..d57d798 100644 --- a/rxglue.c +++ b/rxglue.c @@ -1,6 +1,6 @@ /* -*-c-*- * - * $Id: rxglue.c,v 1.2 2002/01/30 09:22:48 mdw Exp $ + * $Id: rxglue.c,v 1.3 2002/02/02 19:17:41 mdw Exp $ * * REXX glue for C core functionality * @@ -29,6 +29,9 @@ /*----- Revision history --------------------------------------------------* * * $Log: rxglue.c,v $ + * Revision 1.3 2002/02/02 19:17:41 mdw + * New audio subsystem. + * * Revision 1.2 2002/01/30 09:22:48 mdw * Use memory-allocation functions provided by the REXX interpreter. * Now that configuration can be applied after initialization, allow @@ -67,6 +70,8 @@ #include #include +#include "au.h" +#include "aunum.h" #include "err.h" #include "rxglue.h" #include "txport.h" @@ -488,6 +493,88 @@ static APIRET APIENTRY rxfn_txready(const char *fn, ULONG ac, RXSTRING *av, return (0); } +/* --- @AUPLAY(TAG, [FLAG])@ --- * + * + * Arguments: @TAG@ = audio sample tag to play + * @FLAG@ = a string to explain what to do more clearly. + * + * Returns: True if it succeeded. + * + * Use: Plays a sample. If @FLAG@ begins with `t', don't report + * errors if the sample can't be found. + */ + +static APIRET APIENTRY rxfn_auplay(const char *fn, ULONG ac, RXSTRING *av, + const char *sn, RXSTRING *r) +{ + dstr d = DSTR_INIT; + int rc = 1; + + if (ac < 1 || !av[0].strlength || ac > 2) + return (-1); + rxs_get(&av[0], &d); + if (ac > 1 && av[1].strlength >= 1 && + (av[1].strptr[0] == 't' || av[1].strptr[0] == 'T')) + rc = au_tryplay(d.buf); + else + au_play(d.buf); + dstr_destroy(&d); + rxs_putf(r, "%d", rc); + return (0); +} + +/* --- @AUFETCH(TAG)@ --- * + * + * Arguments: @TAG@ = audio sample tag to play + * + * Returns: True if it succeeded. + * + * Use: Prefetches a sample into the cache. + */ + +static APIRET APIENTRY rxfn_aufetch(const char *fn, ULONG ac, RXSTRING *av, + const char *sn, RXSTRING *r) +{ + dstr d = DSTR_INIT; + int rc = 0; + au_sample *s; + au_data *a; + + if (ac < 1 || !av[0].strlength || ac > 1) + return (-1); + rxs_get(&av[0], &d); + if ((s = au_find(d.buf)) != 0 && + (a = au_fetch(s)) != 0) { + au_free(a); + rc = 1; + } + dstr_destroy(&d); + rxs_putf(r, "%d", rc); + return (0); +} + +/* --- @AUNUM(TAG)@ --- * + * + * Arguments: @NUM@ = a number to be read + * + * Returns: --- + * + * Use: Reads a number aloud to the audio device. + */ + +static APIRET APIENTRY rxfn_aunum(const char *fn, ULONG ac, RXSTRING *av, + const char *sn, RXSTRING *r) +{ + dstr d = DSTR_INIT; + + if (ac < 1 || !av[0].strlength || ac > 1) + return (-1); + rxs_get(&av[0], &d); + aunum(d.buf); + dstr_destroy(&d); + return (0); +} + /* --- @MILLIWAIT(MILLIS)@ --- * * * Arguments: @MILLIS@ = how long (in milliseconds) to wait @@ -527,6 +614,9 @@ static const struct rxfntab rxfntab[] = { { "txrecv", rxfn_txrecv }, { "txeof", rxfn_txeof }, { "txready", rxfn_txready }, + { "auplay", rxfn_auplay }, + { "aufetch", rxfn_aufetch }, + { "aunum", rxfn_aunum }, { "milliwait", rxfn_milliwait }, { 0, 0 } }; -- 2.11.0