--- /dev/null
+/* -*-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 <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <mLib/alloc.h>
+#include <mLib/dstr.h>
+#include <mLib/sub.h>
+#include <mLib/sym.h>
+#include <mLib/trace.h>
+
+#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 -------------------------------------------------*/
--- /dev/null
+/* -*-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 <stddef.h>
+
+#include <mLib/bits.h>
+#include <mLib/sym.h>
+
+/*----- 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
--- /dev/null
+/* -*-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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <mLib/dstr.h>
+
+#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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+/* -*-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 <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <mLib/dstr.h>
+
+#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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+/* -*-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 <stdio.h>
+#include <string.h>
+
+#include <mLib/alloc.h>
+#include <mLib/sub.h>
+#include <mLib/trace.h>
+
+#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 -------------------------------------------------*/
--- /dev/null
+/* -*-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 <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <pthread.h>
+
+#include <SDL.h>
+
+#include <mLib/alloc.h>
+#include <mLib/bits.h>
+#include <mLib/sub.h>
+#include <mLib/trace.h>
+
+#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 -------------------------------------------------*/
--- /dev/null
+/* -*-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 <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <pthread.h>
+
+#include <windows.h>
+
+#include <mLib/alloc.h>
+#include <mLib/bits.h>
+#include <mLib/sub.h>
+#include <mLib/trace.h>
+
+#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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
/* -*-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
*
/*----- 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
*
#include <mLib/exc.h>
#include <mLib/quis.h>
-/* #include "au.h" */
+#include "auerr.h"
#include "err.h"
/*----- Static variables --------------------------------------------------*/
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();
}
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);
/* -*-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
*
/*----- 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
*
# 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@ --- *
/* -*-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
*
/*----- 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.
*
/* --- 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 -------------------------------------------------*/
/* -*-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
*
/*----- 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.
*
/*----- Header files ------------------------------------------------------*/
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <mLib/report.h>
#include <mLib/trace.h>
+#include "au.h"
#include "err.h"
#include "jog.h"
#include "rxglue.h"
static void tidy(void)
{
txsu_shutdown();
+ au_shutdown();
}
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\
");
}
-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);
}
unsigned f = 0;
int rc = 0;
int i;
+ const char *audir = 0;
+ size_t aumax = AU_CACHEMAX;
#define f_bogus 1u
{ "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
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;
txfile = optarg;
break;
+ /* --- Audio configuration stuff --- */
+
+ case 'a':
+ audir = optarg;
+ break;
+ case 'x':
+ aumax = strtoul(optarg, 0, 0);
+ break;
+
/* --- Tracing --- */
#ifndef NTRACE
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);
/* -*-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
*
/*----- 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
#include <mLib/exc.h>
#include <mLib/dstr.h>
+#include "au.h"
+#include "aunum.h"
#include "err.h"
#include "rxglue.h"
#include "txport.h"
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
{ "txrecv", rxfn_txrecv },
{ "txeof", rxfn_txeof },
{ "txready", rxfn_txready },
+ { "auplay", rxfn_auplay },
+ { "aufetch", rxfn_aufetch },
+ { "aunum", rxfn_aunum },
{ "milliwait", rxfn_milliwait },
{ 0, 0 }
};