New audio subsystem.
authormdw <mdw>
Sat, 2 Feb 2002 19:21:53 +0000 (19:21 +0000)
committermdw <mdw>
Sat, 2 Feb 2002 19:21:53 +0000 (19:21 +0000)
15 files changed:
au.c [new file with mode: 0644]
au.h [new file with mode: 0644]
auerr.c [new file with mode: 0644]
auerr.h [new file with mode: 0644]
aunum.c [new file with mode: 0644]
aunum.h [new file with mode: 0644]
ausys-fake.c [new file with mode: 0644]
ausys-sdl.c [new file with mode: 0644]
ausys-win32.c [new file with mode: 0644]
ausys.h [new file with mode: 0644]
err.c
err.h
jog.h
main.c
rxglue.c

diff --git a/au.c b/au.c
new file mode 100644 (file)
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 <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 -------------------------------------------------*/
diff --git a/au.h b/au.h
new file mode 100644 (file)
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 <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
diff --git a/auerr.c b/auerr.c
new file mode 100644 (file)
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 <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 -------------------------------------------------*/
diff --git a/auerr.h b/auerr.h
new file mode 100644 (file)
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 (file)
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 <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 -------------------------------------------------*/
diff --git a/aunum.h b/aunum.h
new file mode 100644 (file)
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 (file)
index 0000000..121cb79
--- /dev/null
@@ -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 <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 -------------------------------------------------*/
diff --git a/ausys-sdl.c b/ausys-sdl.c
new file mode 100644 (file)
index 0000000..c54d378
--- /dev/null
@@ -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 <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 -------------------------------------------------*/
diff --git a/ausys-win32.c b/ausys-win32.c
new file mode 100644 (file)
index 0000000..22670e3
--- /dev/null
@@ -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 <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 -------------------------------------------------*/
diff --git a/ausys.h b/ausys.h
new file mode 100644 (file)
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 (file)
--- 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 <mLib/exc.h>
 #include <mLib/quis.h>
 
-/* #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 (file)
--- 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
  *
 #  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 (file)
--- 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.
  *
 
 /* --- 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 (file)
--- 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.
  *
 
 /*----- Header files ------------------------------------------------------*/
 
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
 #include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -54,6 +61,7 @@
 #include <mLib/report.h>
 #include <mLib/trace.h>
 
+#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);
index 9126ff4..d57d798 100644 (file)
--- 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 <mLib/exc.h>
 #include <mLib/dstr.h>
 
+#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 }
 };