XEmacs 21.2-b1
[chise/xemacs-chise.git.1] / src / linuxplay.c
diff --git a/src/linuxplay.c b/src/linuxplay.c
new file mode 100644 (file)
index 0000000..8c04648
--- /dev/null
@@ -0,0 +1,1095 @@
+/* linuxplay.c - play a sound file on the speaker
+ **
+ ** Copyright (C) 1995,96 by Markus Gutschke (gutschk@math.uni-muenster.de)
+ ** This is version 1.3 of linuxplay.c
+ **
+ ** Parts of this code were inspired by sunplay.c, which is copyright 1989 by
+ ** Jef Poskanzer and 1991,92 by Jamie Zawinski; c.f. sunplay.c for further
+ ** information.
+ **
+ ** Permission to use, copy, modify, and distribute this software and its
+ ** documentation for any purpose and without fee is hereby granted, provided
+ ** that the above copyright notice appear in all copies and that both that
+ ** copyright notice and this permission notice appear in supporting
+ ** documentation.  This software is provided "as is" without express or
+ ** implied warranty.
+ **
+ ** Changelog:
+ **  1.0  --  first release; supports SunAudio, Wave and RAW file formats
+ **           detects (and rejects) VOC file format
+ **           tested with PC-Speaker driver only
+ **  1.1  --  fixed bug with playback of stereo Wave files
+ **           fixed VOC file detection
+ **           fixed mono/8bit conversion
+ **           cleaned up mixer programming (c.f. VoxWare-SDK)
+ **           tested with PC-Speaker driver and with PAS16 soundcard
+ **  1.2  --  first (incompatible) attempt at fixing reliable signal handling
+ **  1.3  --  changed signal handling to use reliable signals; this is done
+ **           by including "syssignal.h"; it fixes nasty program crashes
+ **           when using native sound in TTY mode.
+ **           added support for DEC audio file format (this is basically the
+ **           same as Sun audio, but uses little endian format, instead).
+ **           strip the header from Sun audio and DEC audio files in order to
+ **           prevent noise at beginning of samples (thanks to Thomas Pundt
+ **           <pundtt@math.uni-muenster.de> for pointing out this bug and
+ **           providing information on the file format).
+ **           added a few more conversion routines.
+ **           made the code even more tolerant to the limits imposed by some
+ **           soundcards and try to accept soundfiles even if they are not
+ **           fully conformant to the standard.
+ **  1.4  --  increased header size to 256; I hope there is no sample software
+ **           that requires this much.
+ **           added code for converting from signed to unsigned format as
+ **           some soundcards cannot handle signed 8bit data.
+ */
+
+/* Synched up with: Not in FSF. */
+
+#define HEADERSZ  256   /* has to be at least as big as the biggest header   */
+#define SNDBUFSZ  2048  /* has to be at least as big as HEADERSZ             */
+
+/* XEmacs beta testers say:  undef this by default. */
+#undef NOVOLUMECTRLFORMULAW /* Changing the volume for uLaw-encoded
+                              samples sounds very poor; possibly,
+                              this is true only for the PC-Snd
+                              driver, so undefine this symbol at your
+                              discretion */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+#include SOUNDCARD_H_PATH /* Path computed by configure */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/fcntl.h>
+#include <sys/file.h>
+#include <sys/ioctl.h>
+#include <sys/signal.h>
+#include <unistd.h>
+
+#ifdef LINUXPLAYSTANDALONE
+#define perror(str) fprintf(stderr,"audio: %s %s\n",str,strerror(errno));
+#define warn(str)   fprintf(stderr,"audio: %s\n",str);
+#else
+#include "lisp.h"
+#include "syssignal.h"
+#include "sysfile.h"
+#define perror(str) message("audio: %s, %s ",str,strerror(errno))
+#define warn(str)   message("audio: %s ",GETTEXT(str))
+#endif
+
+#ifdef __GNUC__
+#define UNUSED(x) ((void)(x))
+#else
+#define UNUSED(x)
+#define __inline__
+#endif
+
+static  void (*sighup_handler)(int);
+static  void (*sigint_handler)(int);
+
+/* Maintain global variable for keeping parser state information; this struct
+   is set to zero before the first invocation of the parser. The use of a
+   global variable prevents multiple concurrent executions of this code, but
+   this does not happen anyways... */
+enum wvState
+{ wvMain,
+  wvSubchunk,
+  wvOutOfBlock,
+  wvSkipChunk,
+  wvSoundChunk,
+  wvFatal,
+  wvFatalNotify
+};
+
+static union {
+  struct {
+    int           align;
+    enum wvState state;
+    size_t        left;
+    unsigned char leftover[HEADERSZ];
+    signed long   chunklength;
+  } wave;
+  struct {
+    int           align;
+    int           isdata;
+    int           skipping;
+    size_t        left;
+    unsigned char leftover[HEADERSZ];
+  } audio;
+} parsestate;
+
+/* Use a global buffer as scratch-pad for possible conversions of the
+   sampling format */
+unsigned char linuxplay_sndbuf[SNDBUFSZ];
+
+static int           mix_fd;
+static int           audio_vol;
+static int           audio_fd;
+static char         *audio_dev = "/dev/dsp";
+
+typedef enum {fmtIllegal,fmtRaw,fmtVoc,fmtWave,fmtSunAudio} fmtType;
+
+/* Intercept SIGINT and SIGHUP in order to close the audio and mixer
+   devices before terminating sound output; this requires reliable
+   signals as provided by "syssignal.h" */
+static void sighandler(int sig)
+{
+  if (mix_fd > 0) {
+    if (audio_vol >= 0) {
+      ioctl(mix_fd,SOUND_MIXER_WRITE_PCM,&audio_vol);
+      audio_vol = -1; }
+    if (mix_fd != audio_fd)
+      close(mix_fd);
+    mix_fd = -1; }
+  if (audio_fd > 0) {
+    ioctl(audio_fd,SNDCTL_DSP_SYNC,NULL);
+    ioctl(audio_fd,SNDCTL_DSP_RESET,NULL);
+    close(audio_fd);
+    audio_fd = -1; }
+  if (sig == SIGHUP && sighup_handler)      sighup_handler(sig);
+  else if (sig == SIGINT && sigint_handler) sigint_handler(sig);
+  else exit(1);
+}
+
+/* There is no special treatment required for parsing raw data files; we
+   assume that these files contain data in 8bit unsigned format that
+   has been sampled at 8kHz; there is no extra header */
+static size_t parseraw(void **data,size_t *sz,void **outbuf)
+{
+  int rc = *sz;
+
+  *outbuf = *data;
+  *sz = 0;
+  return(rc);
+}
+
+/* Currently we cannot cope with files in VOC format; if you really need
+   to play these files, they should be converted by using SOX */
+static size_t parsevoc(void **data,size_t *sz,void **outbuf)
+{
+  UNUSED(data);
+  UNUSED(sz);
+  UNUSED(outbuf);
+  return(0);
+}
+
+/* We need to perform some look-ahead in order to parse files in WAVE format;
+   this might require re-partioning of the data segments if headers cross the
+   boundaries between two read operations. This is done in a two-step way:
+   first we request a certain amount of bytes... */
+static __inline__ int waverequire(void **data,size_t *sz,size_t rq)
+{
+  int rc = 1;
+
+  if (rq > HEADERSZ) {
+    warn("Header size exceeded while parsing WAVE file");
+    parsestate.wave.state = wvFatal;
+    *sz = 0;
+    return(0); }
+  if ((rq -= parsestate.wave.left) <= 0)
+    return(rc);
+  if (rq > *sz) {rq = *sz; rc = 0;}
+  memcpy(parsestate.wave.leftover+parsestate.wave.left,
+        *data,rq);
+  parsestate.wave.left      += rq;
+  (*(unsigned char **)data) += rq;
+  *sz                       -= rq;
+  return(rc);
+}
+
+/* ...and next we remove this many bytes from the buffer */
+static __inline__ void waveremove(size_t rq)
+{
+  if (parsestate.wave.left <= rq)
+    parsestate.wave.left = 0;
+  else {
+    parsestate.wave.left -= rq;
+    memmove(parsestate.wave.leftover,
+           parsestate.wave.leftover+rq,
+           parsestate.wave.left); }
+  return;
+}
+
+/* Sound files in WAVE format can contain an arbitrary amount of tagged
+   chunks; this requires quite some effort for parsing the data */
+static size_t parsewave(void **data,size_t *sz,void **outbuf)
+{
+  for (;;)
+    switch (parsestate.wave.state) {
+    case wvMain:
+      if (!waverequire(data,sz,20))
+       return(0);
+      /* Keep compatibility with Linux 68k, etc. by not relying on byte-sex  */
+      parsestate.wave.chunklength = parsestate.wave.leftover[16] +
+       256*(parsestate.wave.leftover[17] +
+            256*(parsestate.wave.leftover[18] +
+                 256*parsestate.wave.leftover[19]));
+      waveremove(20);
+      parsestate.wave.state = wvSubchunk;
+      break;
+    case wvSubchunk:
+      if (!waverequire(data,sz,parsestate.wave.chunklength))
+       return(0);
+      parsestate.wave.align = parsestate.wave.chunklength < 14 ? 1
+       : parsestate.wave.leftover[12];
+      if (parsestate.wave.align != 1 &&
+         parsestate.wave.align != 2 &&
+         parsestate.wave.align != 4) {
+       warn("Illegal datawidth detected while parsing WAVE file");
+       parsestate.wave.state = wvFatal; }
+      else
+       parsestate.wave.state = wvOutOfBlock;
+      waveremove(parsestate.wave.chunklength);
+      break;
+    case wvOutOfBlock:
+      if (!waverequire(data,sz,8))
+       return(0);
+      /* Keep compatibility with Linux 68k, etc. by not relying on byte-sex  */
+      parsestate.wave.chunklength = parsestate.wave.leftover[4] +
+       256*(parsestate.wave.leftover[5] +
+            256*(parsestate.wave.leftover[6] +
+                 256*(parsestate.wave.leftover[7] & 0x7F)));
+      if (memcmp(parsestate.wave.leftover,"data",4))
+       parsestate.wave.state = wvSkipChunk;
+      else
+       parsestate.wave.state = wvSoundChunk;
+      waveremove(8);
+      break;
+    case wvSkipChunk:
+      if (parsestate.wave.chunklength > 0 && *sz > 0 &&
+         (signed long)*sz < (signed long)parsestate.wave.chunklength) {
+       parsestate.wave.chunklength -= *sz;
+       *sz = 0; }
+      else {
+       if (parsestate.wave.chunklength > 0 && *sz > 0) {
+         *sz -= parsestate.wave.chunklength;
+         (*(unsigned char **)data) += parsestate.wave.chunklength; }
+       parsestate.wave.state = wvOutOfBlock; }
+      break;
+    case wvSoundChunk: {
+      size_t count,rq;
+      if (parsestate.wave.left) { /* handle leftover bytes from last
+                                    alignment operation */
+       count = parsestate.wave.left;
+       rq    = HEADERSZ-count;
+       if (rq > (size_t) parsestate.wave.chunklength)
+         rq = parsestate.wave.chunklength;
+       if (!waverequire(data,sz,rq)) {
+         parsestate.wave.chunklength -= parsestate.wave.left - count;
+         return(0); }
+       parsestate.wave.chunklength -= rq;
+       *outbuf                      = parsestate.wave.leftover;
+       parsestate.wave.left         = 0;
+       return(rq); }
+      if (*sz >= (size_t) parsestate.wave.chunklength) {
+       count  = parsestate.wave.chunklength;
+       rq     = 0; }
+      else {
+       count  = *sz;
+       count -= rq = count % parsestate.wave.align; }
+      *outbuf                   = *data;
+      (*(unsigned char **)data) += count;
+      *sz                       -= count;
+      if ((parsestate.wave.chunklength -= count) < parsestate.wave.align) {
+       parsestate.wave.state = wvOutOfBlock;
+       /* Some broken software (e.g. SOX) attaches junk to the end of a sound
+          chunk; so, let's ignore this... */
+       if (parsestate.wave.chunklength)
+         parsestate.wave.state = wvSkipChunk; }
+      else if (rq)
+       /* align data length to a multiple of datasize; keep additional data
+          in "leftover" buffer --- this is necessary to ensure proper
+          functioning of the sndcnv... routines */
+       waverequire(data,sz,rq);
+      return(count); }
+    case wvFatalNotify:
+      warn("Irrecoverable error while parsing WAVE file");
+      parsestate.wave.state = wvFatal;
+      break;
+    case wvFatal:
+    default:
+      *sz = 0;
+      return(0); }
+}
+
+/* Strip the header from files in Sun/DEC audio format; this requires some
+   extra processing as the header can be an arbitrary size and it might
+   result in alignment errors for subsequent conversions --- thus we do
+   some buffering, where needed */
+static size_t parsesundecaudio(void **data,size_t *sz,void **outbuf)
+{
+  /* There is data left over from the last invocation of this function; join
+     it with the new data and return a sound chunk that is as big as a
+     single entry */
+  if (parsestate.audio.left) {
+    if (parsestate.audio.left + *sz > (size_t) parsestate.audio.align) {
+      int  count;
+      memmove(parsestate.audio.leftover + parsestate.audio.left,
+             *data,
+             count = parsestate.audio.align - parsestate.audio.left);
+      *outbuf = parsestate.audio.leftover;
+      *sz    -= count;
+      *data   = (*(char **)data) + count;
+      parsestate.audio.left = 0;
+      return(parsestate.audio.align); }
+    else {
+      /* We need even more data in order to get one complete single entry! */
+      memmove(parsestate.audio.leftover + parsestate.audio.left,
+             *data,
+             *sz);
+      *data = (*(char **)data) + *sz;
+      parsestate.audio.left += *sz;
+      *sz   = 0;
+      return(0); } }
+
+  /* This is the main sound chunk, strip of any extra data that does not fit
+     the alignment requirements and move these bytes into the leftover buffer*/
+  if (parsestate.audio.isdata) {
+    int rc = *sz;
+    *outbuf = *data;
+    if ((parsestate.audio.left = rc % parsestate.audio.align) != 0) {
+      memmove(parsestate.audio.leftover,
+             (char *)*outbuf + rc - parsestate.audio.left,
+             parsestate.audio.left);
+      rc -= parsestate.audio.left; }
+    *sz = 0;
+    return(rc); }
+
+  /* This is the first invocation of this function; we need to parse the
+     header information and determine how many bytes we need to skip until
+     the start of the sound chunk */
+  if (!parsestate.audio.skipping) {
+    unsigned char *header = (unsigned char *) *data;
+    if (*sz < 8) {
+      warn("Irrecoverable error while parsing Sun/DEC audio file");
+      return(0); }
+    /* Keep compatibility with Linux 68k, etc. by not relying on byte-sex  */
+    if (header[3]) { /* Sun audio (big endian) */
+      parsestate.audio.align = ((header[15] > 2)+1)*header[23];
+      parsestate.audio.skipping = header[7]+256*(header[6]+256*
+                                                (header[5]+256*header[4])); }
+    else { /* DEC audio (little endian) */
+      parsestate.audio.align = ((header[12] > 2)+1)*header[20];
+      parsestate.audio.skipping = header[4]+256*(header[5]+256*
+                                                (header[6]+256*header[7])); }}
+
+  /* We are skipping extra data that has been attached to header; most usually
+     this will be just a comment, such as the original filename and/or the
+     creation date. Make sure that we do not return less than one single sound
+     sample entry to the caller; if this happens, rather decide to move those
+     few bytes into the leftover buffer and deal with it later */
+  if (*sz >= (size_t) parsestate.audio.skipping) {
+    /* Skip just the header information and return the sound chunk */
+    int rc = *sz - parsestate.audio.skipping;
+    *outbuf = (char *)*data + parsestate.audio.skipping;
+    if ((parsestate.audio.left = rc % parsestate.audio.align) != 0) {
+      memmove(parsestate.audio.leftover,
+             (char *)*outbuf + rc - parsestate.audio.left,
+             parsestate.audio.left);
+      rc -= parsestate.audio.left; }
+    *sz = 0;
+    parsestate.audio.skipping = 0;
+    parsestate.audio.isdata++;
+    return(rc); }
+  else {
+    /* Skip everything */
+    parsestate.audio.skipping -= *sz;
+    return(0); }
+}
+
+/* If the soundcard could not be set to natively support the data format, we
+   try to do some limited on-the-fly conversion to a different format; if
+   no conversion is needed, though, we can output directly */
+static size_t sndcnvnop(void **data,size_t *sz,void **outbuf)
+{
+  int rc = *sz;
+
+  *outbuf = *data;
+  *sz = 0;
+  return(rc);
+}
+
+/* Convert 8 bit unsigned stereo data to 8 bit unsigned mono data */
+static size_t sndcnv8U_2mono(void **data,size_t *sz,void **outbuf)
+{
+  REGISTER unsigned char *src;
+  REGISTER unsigned char *dest;
+  int rc,count;
+
+  count = *sz / 2;
+  if (count > SNDBUFSZ) { *sz  -= 2*SNDBUFSZ; count = SNDBUFSZ; }
+  else                    *sz   = 0;
+  rc      = count;
+  src     = (unsigned char *) *data;
+  *outbuf =
+  dest    = linuxplay_sndbuf;
+  while (count--)
+    *dest++ = (unsigned char)(((int)*(src)++ +
+                              (int)*(src)++) / 2);
+  *data   = src;
+  return(rc);
+}
+
+/* Convert 8 bit signed stereo data to 8 bit signed mono data */
+static size_t sndcnv8S_2mono(void **data,size_t *sz,void **outbuf)
+{
+  REGISTER unsigned char *src;
+  REGISTER unsigned char *dest;
+  int rc, count;
+
+  count = *sz / 2;
+  if (count > SNDBUFSZ) { *sz  -= 2*SNDBUFSZ; count = SNDBUFSZ; }
+  else                    *sz   = 0;
+  rc      = count;
+  src     = (unsigned char *) *data;
+  *outbuf =
+  dest    = linuxplay_sndbuf;
+  while (count--)
+    *dest++ = (unsigned char)(((int)*((signed char *)(src++)) +
+                              (int)*((signed char *)(src++))) / 2);
+  *data   = src;
+  return(rc);
+}
+
+/* Convert 8 bit signed stereo data to 8 bit unsigned mono data */
+static size_t sndcnv2monounsigned(void **data,size_t *sz,void **outbuf)
+{
+  REGISTER unsigned char *src;
+  REGISTER unsigned char *dest;
+  int rc,count;
+
+  count = *sz / 2;
+  if (count > SNDBUFSZ) { *sz  -= 2*SNDBUFSZ; count = SNDBUFSZ; }
+  else                    *sz   = 0;
+  rc      = count;
+  src     = (unsigned char *) *data;
+  *outbuf =
+  dest    = linuxplay_sndbuf;
+  while (count--)
+    *dest++ = (unsigned char)(((int)*((signed char *)(src++)) +
+                              (int)*((signed char *)(src++))) / 2) ^ 0x80;
+  *data   = src;
+  return(rc);
+}
+
+/* Convert 8 bit signed mono data to 8 bit unsigned mono data */
+static size_t sndcnv2unsigned(void **data,size_t *sz,void **outbuf)
+{
+  REGISTER unsigned char *src;
+  REGISTER unsigned char *dest;
+  int rc,count;
+
+  count = *sz;
+  if (count > SNDBUFSZ) { *sz  -= SNDBUFSZ; count = SNDBUFSZ; }
+  else                    *sz   = 0;
+  rc      = count;
+  src     = (unsigned char *) *data;
+  *outbuf =
+  dest    = linuxplay_sndbuf;
+  while (count--)
+    *dest++ = *(src)++ ^ 0x80;
+  *data   = src;
+  return(rc);
+}
+
+/* Convert a number in the range -32768..32767 to an 8 bit ulaw encoded
+   number --- I hope, I got this conversion right :-) */
+static __inline__ signed char int2ulaw(int i)
+{
+    /* Lookup table for fast calculation of number of bits that need shifting*/
+    static short int t_bits[128] = {
+      0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
+      6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
+      7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+      7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7};
+    REGISTER int bits,logi;
+
+    /* unrolling this condition (hopefully) improves execution speed */
+    if (i < 0) {
+      if ((i = (132-i)) > 0x7FFF) i = 0x7FFF;
+      logi = (i >> ((bits = t_bits[i/256])+4));
+      return((bits << 4 | logi) ^ 0x7F); }
+    else {
+      if ((i = 132+i) > 0x7FFF) i = 0x7FFF;
+      logi = (i >> ((bits = t_bits[i/256])+4));
+      return(~(bits << 4 | logi)); }
+}
+
+/* Convert 8 bit ulaw stereo data to 8 bit ulaw mono data */
+static size_t sndcnvULaw_2mono(void **data,size_t *sz,void **outbuf)
+{
+
+  static short int ulaw2int[256] = {
+    /* Precomputed lookup table for conversion from ulaw to 15 bit signed */
+    -16062,-15550,-15038,-14526,-14014,-13502,-12990,-12478,
+    -11966,-11454,-10942,-10430, -9918, -9406, -8894, -8382,
+     -7998, -7742, -7486, -7230, -6974, -6718, -6462, -6206,
+     -5950, -5694, -5438, -5182, -4926, -4670, -4414, -4158,
+     -3966, -3838, -3710, -3582, -3454, -3326, -3198, -3070,
+     -2942, -2814, -2686, -2558, -2430, -2302, -2174, -2046,
+     -1950, -1886, -1822, -1758, -1694, -1630, -1566, -1502,
+     -1438, -1374, -1310, -1246, -1182, -1118, -1054,  -990,
+      -942,  -910,  -878,  -846,  -814,  -782,  -750,  -718,
+      -686,  -654,  -622,  -590,  -558,  -526,  -494,  -462,
+      -438,  -422,  -406,  -390,  -374,  -358,  -342,  -326,
+      -310,  -294,  -278,  -262,  -246,  -230,  -214,  -198,
+      -186,  -178,  -170,  -162,  -154,  -146,  -138,  -130,
+      -122,  -114,  -106,   -98,   -90,   -82,   -74,   -66,
+       -60,   -56,   -52,   -48,   -44,   -40,   -36,   -32,
+       -28,   -24,   -20,   -16,   -12,    -8,    -4,    +0,
+    +16062,+15550,+15038,+14526,+14014,+13502,+12990,+12478,
+    +11966,+11454,+10942,+10430, +9918, +9406, +8894, +8382,
+     +7998, +7742, +7486, +7230, +6974, +6718, +6462, +6206,
+     +5950, +5694, +5438, +5182, +4926, +4670, +4414, +4158,
+     +3966, +3838, +3710, +3582, +3454, +3326, +3198, +3070,
+     +2942, +2814, +2686, +2558, +2430, +2302, +2174, +2046,
+     +1950, +1886, +1822, +1758, +1694, +1630, +1566, +1502,
+     +1438, +1374, +1310, +1246, +1182, +1118, +1054,  +990,
+      +942,  +910,  +878,  +846,  +814,  +782,  +750,  +718,
+      +686,  +654,  +622,  +590,  +558,  +526,  +494,  +462,
+      +438,  +422,  +406,  +390,  +374,  +358,  +342,  +326,
+      +310,  +294,  +278,  +262,  +246,  +230,  +214,  +198,
+      +186,  +178,  +170,  +162,  +154,  +146,  +138,  +130,
+      +122,  +114,  +106,   +98,   +90,   +82,   +74,   +66,
+       +60,   +56,   +52,   +48,   +44,   +40,   +36,   +32,
+       +28,   +24,   +20,   +16,   +12,    +8,    +4,    +0};
+
+  REGISTER unsigned char *src;
+  REGISTER unsigned char *dest;
+  int rc,count;
+
+  count = *sz / 2;
+  if (count > SNDBUFSZ) { *sz  -= 2*SNDBUFSZ; count = SNDBUFSZ; }
+  else                    *sz   = 0;
+  rc      = count;
+  src     = (unsigned char *) *data;
+  *outbuf =
+  dest    = linuxplay_sndbuf;
+  while (count--)
+    /* it is not possible to directly interpolate between two ulaw encoded
+       data bytes, thus we need to convert to linear format first and later
+       we convert back to ulaw format */
+    *dest++ = int2ulaw(ulaw2int[*(src)++] +
+                      ulaw2int[*(src)++]);
+  *data = src;
+  return(rc);
+}
+
+/* Convert 16 bit little endian signed stereo data to 16 bit little endian
+   signed mono data */
+static size_t sndcnv16_2monoLE(void **data,size_t *sz,void **outbuf)
+{
+  REGISTER unsigned char *src;
+  REGISTER unsigned char *dest;
+  int rc,count;
+  signed short i;
+
+  count = *sz / 2;
+  if (count > SNDBUFSZ) { *sz  -= 2*SNDBUFSZ; count = SNDBUFSZ; }
+  else                    *sz   = 0;
+  rc      = count;
+  src     = (unsigned char *) *data;
+  *outbuf =
+  dest    = linuxplay_sndbuf;
+  for (count /= 2; count--; ) {
+    i = ((int)(src[0]) +
+        256*(int)(src[1]) +
+       (int)(src[2]) +
+       256*(int)(src[3])) / 2;
+    src += 4;
+    *dest++ = (unsigned char)(i & 0xFF);
+    *dest++ = (unsigned char)((i / 256) & 0xFF); }
+  *data = src;
+  return(rc);
+}
+
+/* Convert 16 bit big endian signed stereo data to 16 bit big endian
+   signed mono data */
+static size_t sndcnv16_2monoBE(void **data,size_t *sz,void **outbuf)
+{
+  REGISTER unsigned char *src;
+  REGISTER unsigned char *dest;
+  int rc,count;
+  signed short i;
+
+  count = *sz / 2;
+  if (count > SNDBUFSZ) { *sz  -= 2*SNDBUFSZ; count = SNDBUFSZ; }
+  else                    *sz   = 0;
+  rc      = count;
+  src     = (unsigned char *) *data;
+  *outbuf =
+  dest    = linuxplay_sndbuf;
+  for (count /= 2; count--; ) {
+    i = ((int)(src[1]) +
+        256*(int)(src[0]) +
+       (int)(src[3]) +
+       256*(int)(src[2])) / 2;
+    src += 4;
+    *dest++ = (unsigned char)((i / 256) & 0xFF);
+    *dest++ = (unsigned char)(i & 0xFF); }
+  *data = src;
+  return(rc);
+}
+
+/* Convert 16 bit little endian signed data to 8 bit unsigned data */
+static size_t sndcnv2byteLE(void **data,size_t *sz,void **outbuf)
+{
+  REGISTER unsigned char *src;
+  REGISTER unsigned char *dest;
+  int rc,count;
+
+  count = *sz / 2;
+  if (count > SNDBUFSZ) { *sz  -= 2*SNDBUFSZ; count = SNDBUFSZ; }
+  else                    *sz   = 0;
+  rc      = count;
+  src     = (unsigned char *) *data;
+  *outbuf =
+  dest    = linuxplay_sndbuf;
+  while (count--) {
+    *dest++ = (unsigned char)(((signed char *)src)[1] ^ (signed char)0x80);
+    src += 2;
+  }
+  *data = src;
+  return(rc);
+}
+
+/* Convert 16 bit big endian signed data to 8 bit unsigned data */
+static size_t sndcnv2byteBE(void **data,size_t *sz,void **outbuf)
+{
+  REGISTER unsigned char *src;
+  REGISTER unsigned char *dest;
+  int rc,count;
+
+  count = *sz / 2;
+  if (count > SNDBUFSZ) { *sz  -= 2*SNDBUFSZ; count = SNDBUFSZ; }
+  else                    *sz   = 0;
+  rc      = count;
+  src     = (unsigned char *) *data;
+  *outbuf =
+  dest    = linuxplay_sndbuf;
+  while (count--) {
+    *dest++ = (unsigned char)(((signed char *)src)[0] ^ (signed char)0x80);
+    src += 2;
+  }
+  *data = src;
+  return(rc);
+}
+
+/* Convert 16 bit little endian signed stereo data to 8 bit unsigned
+   mono data */
+static size_t sndcnv2monobyteLE(void **data,size_t *sz,void **outbuf)
+{
+  REGISTER unsigned char *src;
+  REGISTER unsigned char *dest;
+  int rc,count;
+
+  count = *sz / 4;
+  if (count > SNDBUFSZ) { *sz  -= 4*SNDBUFSZ; count = SNDBUFSZ; }
+  else                    *sz   = 0;
+  rc      = count;
+  src     = (unsigned char *) *data;
+  *outbuf =
+  dest    = linuxplay_sndbuf;
+  while (count--) {
+    *dest++ = (unsigned char)(((int)((signed char *)src)[1] +
+                              (int)((signed char *)src)[3]) / 2 ^ 0x80);
+    src += 4;
+  }
+  *data = src;
+  return(rc);
+}
+
+/* Convert 16 bit big endian signed stereo data to 8 bit unsigned
+   mono data */
+static size_t sndcnv2monobyteBE(void **data,size_t *sz,void **outbuf)
+{
+  REGISTER unsigned char *src;
+  REGISTER unsigned char *dest;
+  int rc,count;
+
+  count = *sz / 4;
+  if (count > SNDBUFSZ) { *sz  -= 4*SNDBUFSZ; count = SNDBUFSZ; }
+  else                    *sz   = 0;
+  rc      = count;
+  src     = (unsigned char *) *data;
+  *outbuf =
+  dest    = linuxplay_sndbuf;
+  while (count--) {
+    *dest++ = (unsigned char)(((int)((signed char *)src)[0] +
+                              (int)((signed char *)src)[2]) / 2 ^ 0x80);
+    src += 4;
+  }
+  *data = src;
+  return(rc);
+}
+
+/* Look at the header of the sound file and try to determine the format;
+   we can recognize files in VOC, WAVE, and, Sun/DEC-audio format--- everything
+   else is assumed to be raw 8 bit unsigned data sampled at 8kHz */
+static fmtType analyze_format(unsigned char *format,int *fmt,int *speed,
+                             int *tracks,
+                             size_t (**parsesndfile)(void **,size_t *sz,
+                                                     void **))
+{
+  /* Keep compatibility with Linux 68k, etc. by not relying on byte-sex  */
+  if (!memcmp(format,"Creative Voice File\x1A\x1A\x00",22) &&
+              (format[22]+256*format[23]) ==
+      ((0x1233-format[24]-256*format[25])&0xFFFF)) { /* VOC */
+    *fmt          = AFMT_U8;
+    *speed        = 8000;
+    *tracks       = 2;
+    *parsesndfile = parsevoc;
+    return(fmtVoc); }
+  else if (!memcmp(format,"RIFF",4) &&
+          !memcmp(format+8,"WAVEfmt ",8)) { /* WAVE */
+    if (memcmp(format+20,"\001\000\001"/* PCM mono */,4) &&
+       memcmp(format+20,"\001\000\002"/* PCM stereo */,4))
+      return(fmtIllegal);
+    *fmt          = (format[32]/(*tracks = format[22])) == 1 ?
+                    AFMT_U8 : AFMT_S16_LE;
+    /* Keep compatibility with Linux 68k, etc. by not relying on byte-sex  */
+    *speed        = format[24]+256*(format[25]+256*
+                                   (format[26]+256*format[27]));
+    *parsesndfile = parsewave;
+    return(fmtWave); }
+  else if (!memcmp(format,".snd",4)) { /* Sun Audio (big endian) */
+    if (format[7]+256*(format[6]+256*(format[5]+256*format[4])) < 24) {
+      *fmt          = AFMT_MU_LAW;
+      *speed        = 8000;
+      *tracks       = 1;
+      *parsesndfile = parsesundecaudio;
+      return(fmtSunAudio); }
+    if      (!memcmp(format+12,"\000\000\000\001",4)) *fmt = AFMT_MU_LAW;
+    else if (!memcmp(format+12,"\000\000\000\002",4)) *fmt = AFMT_S8;
+    else if (!memcmp(format+12,"\000\000\000\003",4)) *fmt = AFMT_S16_BE;
+    else return(fmtIllegal);
+    /* Keep compatibility with Linux 68k, etc. by not relying on byte-sex  */
+    *speed        = format[19]+256*(format[18]+256*
+                                   (format[17]+256*format[16]));
+    *tracks       = format[23];
+    *parsesndfile = parsesundecaudio;
+    return(fmtSunAudio); }
+  else if (!memcmp(format,".sd",4)) { /* DEC Audio (little endian) */
+    if (format[4]+256*(format[5]+256*(format[6]+256*format[7])) < 24) {
+      *fmt          = AFMT_MU_LAW;
+      *speed        = 8000;
+      *tracks       = 1;
+      *parsesndfile = parsesundecaudio;
+      return(fmtSunAudio); }
+    if      (!memcmp(format+12,"\001\000\000",4)) *fmt = AFMT_MU_LAW;
+    else if (!memcmp(format+12,"\002\000\000",4)) *fmt = AFMT_S8;
+    else if (!memcmp(format+12,"\003\000\000",4)) *fmt = AFMT_S16_LE;
+    else return(fmtIllegal);
+    /* Keep compatibility with Linux 68k, etc. by not relying on byte-sex  */
+    *speed        = format[16]+256*(format[17]+256*
+                                   (format[18]+256*format[19]));
+    *tracks       = format[20];
+    *parsesndfile = parsesundecaudio;
+    return(fmtSunAudio); }
+  else {
+    *fmt          = AFMT_U8;
+    *speed        = 8000;
+    *tracks       = 1;
+    *parsesndfile = parseraw;
+    return(fmtRaw); }
+}
+
+/* Initialize the soundcard and mixer device with the parameters that we
+   found in the header of the sound file. If the soundcard is not capable of
+   natively supporting the required parameters, then try to set up conversion
+   routines.
+   The difficulty with setting up the sound card is that the parameters are
+   not fully orthogonal; changing one of them might affect some of the
+   others, too. Thus we do quite a lot of double checking; actually most of
+   this is not needed right now, but it will come in handy, if the kernel's
+   sounddriver ever changes or if third-party sounddrivers are used. */
+static int audio_init(int mixx_fd, int auddio_fd, int fmt, int speed,
+                     int tracks, int *volume,
+                     size_t (**sndcnv) (void **, size_t *sz, void **))
+{
+  int i,the_speed,the_stereo,the_fmt;
+
+  *sndcnv = sndcnvnop;
+
+  if (ioctl(auddio_fd,SNDCTL_DSP_SYNC,NULL) < 0) {
+    perror("SNDCTL_DSP_SYNC");
+    return(0); }
+
+  /* Initialize sound hardware with prefered parameters */
+
+  /* If the sound hardware cannot support 16 bit format or requires a
+     different byte sex then try to drop to 8 bit format */
+
+  the_fmt = fmt;
+  if(ioctl(audio_fd,SNDCTL_DSP_SETFMT,&the_fmt) < 0) {
+       perror("SNDCTL_DSP_SETFMT");
+       return(0);
+  }
+
+  if (fmt != the_fmt) {
+    if (fmt == AFMT_S16_LE || fmt == AFMT_S16_BE) {
+      *sndcnv = fmt == AFMT_S16_BE ? sndcnv2byteBE : sndcnv2byteLE;
+      if (((i=fmt=AFMT_U8),ioctl(audio_fd,SNDCTL_DSP_SETFMT,&i)) < 0 ||
+         fmt != i || ioctl(audio_fd,SNDCTL_DSP_SETFMT,&the_fmt) < 0 ||
+         fmt != the_fmt) {
+       perror("SNDCTL_DSP_SETFMT");
+       return(0); } }
+    else if (fmt == AFMT_MU_LAW && the_fmt == AFMT_U8 ) {
+      /* the kernel will convert for us */ }
+    else {
+      perror("SNDCTL_DSP_SETFMT");
+      return(0); } }
+  else if (fmt == AFMT_S8) {
+    *sndcnv = sndcnv2unsigned;
+    if (((i=fmt=AFMT_U8),ioctl(audio_fd,SNDCTL_DSP_SETFMT,&i)) < 0 ||
+        fmt != i || ioctl(audio_fd,SNDCTL_DSP_SETFMT,&the_fmt) < 0 ||
+        fmt != the_fmt) {
+      perror("SNDCTRL_DSP_SETFMT");
+      return(0); } }
+
+  /* The PCSP driver does not support reading of the sampling rate via the
+     SOUND_PCM_READ_RATE ioctl; determine "the_speed" here */
+  the_speed = speed; ioctl(audio_fd,SNDCTL_DSP_SPEED,&the_speed);
+  /* The PCSP driver does not support reading of the mono/stereo flag, thus
+     we assume, that failure to change this mode means we are in mono mode  */
+  if (((i = (the_stereo = tracks)-1),ioctl(audio_fd,SNDCTL_DSP_STEREO,&i)) < 0)
+    the_stereo = 1;
+
+  /* Try to request stereo playback (if needed); if this cannot be supported
+     by the hardware, then install conversion routines for mono playback */
+
+  /* This ioctl will fail if we use the PCSP driver; thus the value of
+     "the_stereo" is still unchanged */
+  ioctl(audio_fd,SOUND_PCM_READ_CHANNELS,&the_stereo);
+  if (tracks != the_stereo) {
+    if (tracks == 2) {
+      tracks = 1;
+      *sndcnv = *sndcnv == sndcnv2byteLE   ? sndcnv2monobyteLE :
+              *sndcnv == sndcnv2byteBE   ? sndcnv2monobyteBE :
+              *sndcnv == sndcnv2unsigned ? sndcnv2monounsigned :
+       the_fmt == AFMT_S16_LE ? sndcnv16_2monoLE :
+       the_fmt == AFMT_S16_BE ? sndcnv16_2monoBE :
+       the_fmt == AFMT_S8     ? sndcnv8S_2mono :
+       the_fmt == AFMT_U8     ? sndcnv8U_2mono :
+       the_fmt == AFMT_MU_LAW ? sndcnvULaw_2mono : NULL;
+      if (*sndcnv == NULL) { /* this should not happen */
+       perror("SNDCTL_DSP_STEREO");
+       return(0); }
+      /* Switch to mono mode */
+      if (((i = 0),ioctl(audio_fd,SNDCTL_DSP_STEREO,&i)) < 0 || i) {
+       perror("SNDCTL_DSP_STEREO");
+       return(0); }
+      /* Now double check that everything is set as expected */
+      if (((i = AFMT_QUERY),ioctl(audio_fd,SNDCTL_DSP_SETFMT,&i)) < 0 ||
+         (i != the_fmt &&
+          (((i=the_fmt),ioctl(audio_fd,SNDCTL_DSP_SETFMT,&i)) < 0 ||
+           i != the_fmt ||
+           ((i = AFMT_QUERY),ioctl(audio_fd,SNDCTL_DSP_SETFMT,&i)) < 0 ||
+           i != the_fmt)) ||
+         (ioctl(audio_fd,SOUND_PCM_READ_CHANNELS,&i) >= 0 &&
+          i != 1)) {
+       /* There was no way that we could set the soundcard to a meaningful
+           mode */
+       perror("SNDCTL_DSP_SETFMT and SNDCTL_DSP_STEREO");
+       return(0); } }
+    else {
+      /* Somebody set the soundcard to stereo even though we requested
+         mono; this should not happen... */
+      if (((i = the_stereo = tracks),ioctl(audio_fd,SNDCTL_DSP_STEREO,&i))<0 ||
+         i != the_stereo-1) {
+       perror("SNDCTL_DSP_STEREO");
+       return(0); }
+      if (((i = AFMT_QUERY),ioctl(audio_fd,SNDCTL_DSP_SETFMT,&i)) < 0 ||
+         i != the_fmt) {
+       perror("SNDCTL_DSP_SETFMT");
+       return(0); } } }
+
+  /* Fail if deviations from desired sampling frequency are too big */
+
+  /* This ioctl will fail if we use the PCSP driver; thus the value of
+     "the_speed" is still unchanged */
+  ioctl(audio_fd,SOUND_PCM_READ_RATE,&the_speed);
+  if (speed*14 < the_speed*10 || speed*6 > the_speed*10) {
+    char buffer[256];
+    sprintf(buffer,"SNDCTL_DSP_SPEED (req: %d, rtn: %d)",speed,the_speed);
+    perror(buffer);
+    return(0); }
+
+  /* Use the mixer device for setting the playback volume */
+  if (mixx_fd > 0) {
+    int vol = *volume & 0xFF;
+    if (ioctl(mixx_fd,SOUND_MIXER_READ_PCM,volume) < 0)
+      *volume = -1;
+    if (vol < 0) vol = 0; else if (vol > 100) vol = 100;
+#ifdef NOVOLUMECTRLFORMULAW
+    if (fmt == AFMT_MU_LAW)
+      vol = 100;
+#endif
+    vol |= 256*vol;
+    /* Do not signal an error, if volume control is unavailable! */
+    ioctl(mixx_fd,SOUND_MIXER_WRITE_PCM,&vol); }
+
+#if defined(LINUXPLAYSTANDALONE) && 1
+  /* Debugging output is displayed only when compiled as stand-alone version */
+  {int the_volume;
+  the_fmt = AFMT_QUERY;
+  ioctl(audio_fd,SNDCTL_DSP_SETFMT,&the_fmt);
+  ioctl(auddio_fd,SOUND_PCM_READ_CHANNELS,&the_stereo);
+  ioctl(auddio_fd,SOUND_PCM_READ_RATE,&the_speed);
+  ioctl(mixx_fd,SOUND_MIXER_READ_PCM,&the_volume);
+  fprintf(stderr,"%s, %s, %dHz, L:%d/R:%d\n",
+         the_fmt == AFMT_MU_LAW ? "AFMT_MU_LAW" :
+         the_fmt == AFMT_A_LAW ? "AFMT_A_LAW" :
+         the_fmt == AFMT_IMA_ADPCM ? "AFMT_IMA_ADPCM" :
+         the_fmt == AFMT_U8 ? "AFMT_U8" :
+         the_fmt == AFMT_S16_LE ? "AFMT_S16_LE" :
+         the_fmt == AFMT_S16_BE ? "AFMT_S16_BE" :
+         the_fmt == AFMT_S8 ? "AFMT_S8" :
+         the_fmt == AFMT_U16_LE ? "AFMT_U16_LE" :
+         the_fmt == AFMT_U16_BE ? "AFMT_U16_BE" :
+         the_fmt == AFMT_MPEG ? "AFMT_MPEG" :
+         "AFMT_???",
+         the_stereo == 2 ? "stereo" : "mono",
+         the_speed,
+         the_volume / 256, the_volume % 256); }
+#endif
+
+  return(1);
+}
+
+/* XEmacs requires code both for playback of pre-loaded data and for playback
+   from a soundfile; we use one function for both cases */
+static void linux_play_data_or_file(int fd,unsigned char *data,
+                                   int length,int volume)
+{
+  size_t         (*parsesndfile)(void **dayta,size_t *sz,void **outbuf);
+  size_t         (*sndcnv)(void **dayta,size_t *sz,void **);
+  fmtType        ffmt;
+  int            fmt,speed,tracks;
+  unsigned char *pptr,*optr,*cptr,*sptr;
+  int            wrtn,rrtn,crtn,prtn;
+
+  /* We need to read at least the header information before we can start
+     doing anything */
+  if (!data || length < HEADERSZ) {
+    if (fd < 0) return;
+    else {
+      length = read(fd,linuxplay_sndbuf,SNDBUFSZ);
+      if (length < HEADERSZ)
+       return;
+      data   = linuxplay_sndbuf;
+      length = SNDBUFSZ; }
+  }
+
+  ffmt = analyze_format(data,&fmt,&speed,&tracks,&parsesndfile);
+
+  if (ffmt != fmtRaw && ffmt != fmtSunAudio && ffmt != fmtWave) {
+    warn("Unsupported file format (neither RAW, nor Sun/DECAudio, nor WAVE)");
+      return; }
+
+  /* The VoxWare-SDK discourages opening /dev/audio; opening /dev/dsp and
+     properly intializing it via ioctl() is prefered */
+  if ((audio_fd=open(audio_dev,
+                    (O_WRONLY|O_NDELAY),0)) < 0) {
+    perror(audio_dev);
+    if (mix_fd > 0 && mix_fd != audio_fd) { close(mix_fd); mix_fd = -1; }
+    return; }
+
+  /* The VoxWare-SDK discourages direct manipulation of the mixer device as
+     this could lead to problems, when multiple sound cards are installed */
+  mix_fd = audio_fd;
+
+  sighup_handler = signal(SIGHUP, sighandler);
+  sigint_handler = signal(SIGINT, sighandler);
+
+  if (!audio_init(mix_fd,audio_fd,fmt,speed,tracks,&volume,&sndcnv))
+    goto END_OF_PLAY;
+  audio_vol = volume;
+
+  /* Initialize global parser state information to zero */
+  memset(&parsestate,0,sizeof(parsestate));
+
+  /* Mainloop: read a block of data, parse its contents, perform all
+               the necessary conversions and output it to the sound
+               device; repeat until all data has been processed */
+  rrtn = length;
+  do {
+    for (pptr = data; (prtn = parsesndfile((void **)&pptr,(size_t *)&rrtn,
+                                          (void **)&optr)) > 0; )
+      for (cptr = optr; (crtn = sndcnv((void **)&cptr,(size_t *) &prtn,
+                                      (void **)&sptr)) > 0; ) {
+       for (;;) {
+         if ((wrtn = write(audio_fd,sptr,crtn)) < 0) {
+           perror("write"); goto END_OF_PLAY; }
+         else if (wrtn) break;
+         else if (ioctl(audio_fd,SNDCTL_DSP_SYNC,NULL) < 0) {
+           perror("SNDCTL_DSP_SYNC"); goto END_OF_PLAY; } }
+       if (wrtn != crtn) {
+         char buf[255];
+         sprintf(buf,"play: crtn = %d, wrtn = %d",crtn,wrtn);
+         warn(buf);
+         goto END_OF_PLAY; } }
+    if (fd >= 0) {
+      if ((rrtn = read(fd,linuxplay_sndbuf,SNDBUFSZ)) < 0) {
+       perror("read"); goto END_OF_PLAY; } }
+    else
+      break;
+  } while (rrtn > 0);
+
+  /* Verify that we could fully parse the entire soundfile; this is needed
+     only for files in WAVE format */
+  if (ffmt == fmtWave && parsestate.wave.state != wvOutOfBlock &&
+      parsestate.wave.state != wvFatal)
+    warn("Unexpected end of WAVE file");
+
+END_OF_PLAY:
+  /* Now cleanup all used resources */
+
+  ioctl(audio_fd,SNDCTL_DSP_SYNC,NULL);
+  ioctl(audio_fd,SNDCTL_DSP_RESET,NULL);
+
+  signal(SIGHUP,sighup_handler);
+  signal(SIGINT,sigint_handler);
+
+  if (mix_fd > 0) {
+    if (audio_vol >= 0) {
+      ioctl(mix_fd,SOUND_MIXER_WRITE_PCM,&audio_vol);
+      audio_vol = -1; }
+    if (mix_fd != audio_fd)
+      close(mix_fd);
+    mix_fd = -1; }
+
+  close(audio_fd);
+  audio_fd = -1;
+
+  return;
+}
+
+/* Call "linux_play_data_or_file" with the appropriate parameters for
+   playing a soundfile */
+void play_sound_file (char *sound_file, int volume);
+void play_sound_file (char *sound_file, int volume)
+{
+  int fd;
+
+  if ((fd=open(sound_file,O_RDONLY,0)) < 0) {
+    perror(sound_file);
+    return; }
+  linux_play_data_or_file(fd,NULL,0,volume);
+  close(fd);
+  return;
+}
+
+/* Call "linux_play_data_or_file" with the appropriate parameters for
+   playing pre-loaded data */
+void play_sound_data (unsigned char *data, int length, int volume);
+void play_sound_data (unsigned char *data, int length, int volume)
+{
+  linux_play_data_or_file(-1,data,length,volume);
+  return;
+}