/*
 * Purpose: File format parse routines for ossplay
 */
/*
 * This file is part of Open Sound System
 *
 * Copyright (C) 4Front Technologies 1996-2008.
 *
 * This software is released under the BSD license.
 * See the COPYING file included in the main directory of this source
 * distribution for the license terms and conditions
 */


#include "ossplay_parser.h"
#include "ossplay_decode.h"

#include <ctype.h>
#include <sys/stat.h>

/* Magic numbers used in Sun and NeXT audio files (.au/.snd) */
#define SUN_MAGIC 	0x2e736e64	/* Really '.snd' */
#define SUN_INV_MAGIC	0x646e732e	/* '.snd' upside-down */
#define DEC_MAGIC	0x2e736400	/* Really '\0ds.' (for DEC) */
#define DEC_INV_MAGIC	0x0064732e	/* '\0ds.' upside-down */

/* Magic numbers for file formats based on IFF */
#define FORM_MAGIC	0x464f524d	/* 'FORM' */
#define AIFF_MAGIC	0x41494646	/* 'AIFF' */
#define AIFC_MAGIC	0x41494643	/* 'AIFC' */
#define _8SVX_MAGIC	0x38535658	/* '8SVX' */
#define _16SV_MAGIC	0x31365356	/* '16SV' */
#define MAUD_MAGIC	0x4D415544	/* 'MAUD' */

/* Magic numbers for .wav files */
#define RIFF_MAGIC	0x52494646	/* 'RIFF' */
#define RIFX_MAGIC	0x52494658	/* 'RIFX' */
#define WAVE_MAGIC	0x57415645	/* 'WAVE' */

/* Beginning of magic for Creative .voc files */
#define Crea_MAGIC	0x43726561	/* 'Crea' */

/* Ogg */
#define OggS_MAGIC      0x4F676753      /* 'OggS' */

extern int quiet, verbose, force_fmt;
extern long seek_byte;
extern flag from_stdin, raw_file;
extern off_t (*ossplay_lseek) (int, off_t, int);

static errors_t play_au (dspdev_t *, const char *, int, unsigned char *, int);
static errors_t play_iff (dspdev_t *, const char *, int, unsigned char *, int);
static errors_t play_voc (dspdev_t *, const char *, int, unsigned char *, int);
static void print_verbose_fileinfo (const char *, int, int, int, int);

#ifdef OGG_SUPPORT
static errors_t play_ogg (dspdev_t *, const char *, int, unsigned char *, int,
                          dlopen_funcs_t **);

/*
 * OV_CALLBACKS_DEFAULT is not defined by older Vorbis versions so 
 * we have to define our own version.
 */
static int _ov_header_fseek_wrap_ (FILE *f, ogg_int64_t off, int whence)
{
  if (f == NULL) return -1;
  return fseek(f, off, whence);
}

static ov_callbacks OV_CALLBACKS_DEFAULT_ = {
  (size_t (*)(void *, size_t, size_t, void *))  fread,
  (int (*)(void *, ogg_int64_t, int))           _ov_header_fseek_wrap_,
  (int (*)(void *))                             fclose,
  (long (*)(void *))                            ftell
};

/* Only handles Ogg/Vorbis */
static errors_t
play_ogg (dspdev_t * dsp, const char * filename, int fd, unsigned char * hdr,
          int l, dlopen_funcs_t ** vft)
{

  FILE * f;
  errors_t ret = E_OK;
  ogg_data_t ogg_data;
  vorbis_info * vi;

  f = fdopen (fd, "rb");

  if (*vft == NULL)
    {
      *vft = (dlopen_funcs_t *)ossplay_malloc (sizeof (dlopen_funcs_t));

      (*vft)->vorbisfile_handle = ossplay_dlopen ("libvorbisfile.so.3");

      if ((*vft)->vorbisfile_handle == NULL)
        {
          const char * msg = ossplay_dlerror();

          ossplay_free (*vft);
          *vft = NULL;
          print_msg (ERRORM, "Can't dlopen libvorbisfile! (Error was %s)\n",
                     msg?msg:"");
          return E_DECODE;
        }

      /*
       * Assigning to the address pointed by the casted lvalue is the
       * POSIX.1-2003 workaround to the dlsym return type issue, and both the
       * opengroup and glibc examples use that method, so this should be safe
       * on POSIX systems.
       */
      if (ossplay_vdlsym ((*vft)->vorbisfile_handle,
                     (void **)&(*vft)->ov_clear, "ov_clear",
                     (void **)&(*vft)->ov_comment, "ov_comment",
                     (void **)&(*vft)->ov_info, "ov_info",
                     (void **)&(*vft)->ov_open_callbacks, "ov_open_callbacks",
                     (void **)&(*vft)->ov_raw_tell, "ov_raw_tell",
                     (void **)&(*vft)->ov_read, "ov_read",
                     (void **)&(*vft)->ov_seekable, "ov_seekable",
                     (void **)&(*vft)->ov_time_total, "ov_time_total",
                     (void **)&(*vft)->ov_time_seek, "ov_time_seek",
                     (void **)NULL))
        {
          /* The call already took care of making an error printout for us */
          ossplay_dlclose ((*vft)->vorbisfile_handle);
          ossplay_free (*vft);
          *vft = NULL;
          return E_DECODE;
        }
    }

  ogg_data.f = *vft;

  if (ogg_data.f->ov_open_callbacks (f, &ogg_data.vf, (char *)hdr, l,
                                     OV_CALLBACKS_DEFAULT_) < 0)
    {
      print_msg (ERRORM, "File %s is not an OggVorbis file!\n", filename);
      return E_DECODE;
    }

  ogg_data.dsp = dsp;
  ogg_data.setup = 0;
  ogg_data.bitstream = -1;

  vi = ogg_data.f->ov_info (&ogg_data.vf, -1);
  if (verbose)
    {
      vorbis_comment * oc = ogg_data.f->ov_comment(&ogg_data.vf,-1);
      char **p = oc->user_comments;

      while (*p)
        print_msg (VERBOSEM, "%s: Comments: %s\n", filename, *p++);
      if ((verbose > 1) && (oc->vendor))
        print_msg (VERBOSEM, "%s: Vendor: %s\n", filename, oc->vendor);
      print_msg (VERBOSEM, "%s: Nominal bitrate: %d\n", filename, vi->bitrate_nominal);

      print_verbose_fileinfo (filename, OGG_FILE, AFMT_VORBIS, vi->channels, vi->rate);
    }

  ret = decode_sound (dsp, fd, BIG_SPECIAL, AFMT_VORBIS, vi->channels, vi->rate,
                      (void *)&ogg_data);

  ogg_data.f->ov_clear (&ogg_data.vf);

  return ret;
}
#endif

errors_t
play_file (dspdev_t * dsp, const char * filename, dlopen_funcs_t ** dlt)
{
  int fd;
  ssize_t l, i;
  unsigned char buf[PLAYBUF_SIZE];
  const char * suffix;
  struct stat st;
  errors_t ret = E_OK;

  if (from_stdin)
    {
      FILE *fp;

      fp = fdopen(0, "rb");
      fd = fileno(fp);
      /*
       * Use emulation if stdin is not seekable (e.g. on Linux).
       */
      if (lseek (fd, 0, SEEK_CUR) == -1) ossplay_lseek = ossplay_lseek_stdin;
    }
  else fd = open (filename, O_RDONLY, 0);
  if (fd == -1)
    {
      perror_msg (filename);
      return E_DECODE;
    }

  if (seek_byte != 0)
    {
      ossplay_lseek (fd, (off_t)seek_byte, SEEK_CUR);
    }

  if (raw_file)
    {
      if (fstat (fd, &st) == -1)
        {
          perror_msg (filename);
          return E_DECODE;
        }
      print_msg (NORMALM, "%s: Playing RAW file.\n", filepart (filename));

      ret = decode_sound (dsp, fd, st.st_size, DEFAULT_FORMAT,
                          DEFAULT_CHANNELS, DEFAULT_SPEED, NULL);
      goto done;
    }

  if ((l = read (fd, buf, 12)) == -1)
    {
      perror_msg (filename);
      goto seekerror;
    }

  if (l == 0)
    {
      print_msg (ERRORM, "%s is empty file.\n", filepart (filename));
      goto seekerror;
    }

/*
 * Try to detect the file type
 */
  switch (be_int (buf, 4))
    {
      case SUN_MAGIC:
      case DEC_MAGIC:
      case SUN_INV_MAGIC:
      case DEC_INV_MAGIC:
        if ((i = read (fd, buf + 12, 12)) == -1)
          {
            perror_msg (filename);
            goto seekerror;
          }
        l += i;
        if (l < 24) break;
        ret = play_au (dsp, filepart (filename), fd, buf, l);
        goto done;
      case Crea_MAGIC:
        if ((i = read (fd, buf + 12, 7)) == -1)
          {
            perror_msg (filename);
            goto seekerror;
          }
        l += i;
        if ((l < 19) || (memcmp (buf, "Creative Voice File", 19))) break;
        ret = play_voc (dsp, filepart (filename), fd, buf, l);
        goto done;
      case RIFF_MAGIC:
        if ((l < 12) || be_int (buf + 8, 4) != WAVE_MAGIC) break;
        if (force_fmt == AFMT_IMA_ADPCM) force_fmt = AFMT_MS_IMA_ADPCM;
        ret = play_iff (dsp, filepart (filename), fd, buf, WAVE_FILE);
        goto done;
      case RIFX_MAGIC:
        if ((l < 12) || be_int (buf + 8, 4) != WAVE_MAGIC) break;
        if (force_fmt == AFMT_IMA_ADPCM) force_fmt = AFMT_MS_IMA_ADPCM;
        ret = play_iff (dsp, filepart (filename), fd, buf, WAVE_FILE_BE);
        goto done;
      case FORM_MAGIC:
        if (l < 12) break;
        switch (be_int (buf + 8, 4))
          {
            case AIFF_MAGIC:
              if (force_fmt == AFMT_IMA_ADPCM) force_fmt = AFMT_MAC_IMA_ADPCM;
              ret = play_iff (dsp, filepart (filename), fd, buf, AIFF_FILE);
              goto done;
            case AIFC_MAGIC:
              if (force_fmt == AFMT_IMA_ADPCM) force_fmt = AFMT_MAC_IMA_ADPCM;
              ret = play_iff (dsp, filepart (filename), fd, buf, AIFC_FILE);
              goto done;
            case _8SVX_MAGIC:
              ret = play_iff (dsp, filepart (filename), fd, buf, _8SVX_FILE);
              goto done;
            case _16SV_MAGIC:
              ret = play_iff (dsp, filepart (filename), fd, buf, _16SV_FILE);
              goto done;
            case MAUD_MAGIC:
              ret = play_iff (dsp, filepart (filename), fd, buf, MAUD_FILE);
              goto done;
            default: break;
          }
      case OggS_MAGIC:
#ifdef OGG_SUPPORT
        ret = play_ogg (dsp, filepart (filename), fd, buf, l, dlt);
        fd = -1;
        goto done;
#endif
      default: break;
    }

  ossplay_lseek (fd, 0, SEEK_SET);	/* Start from the beginning */

/*
 *	The file was not identified by its content. Try using the file name
 *	suffix.
 */

  suffix = strrchr (filename, '.');
  if (suffix == NULL) suffix = filename;

  if (fstat (fd, &st) == -1)
    {
      perror_msg (filename);
      return E_DECODE;
    }

  if (strcmp (suffix, ".au") == 0 || strcmp (suffix, ".AU") == 0)
    {				/* Raw mu-Law data */
      print_msg (VERBOSEM, "Playing raw mu-Law file %s\n",
                 filepart (filename));

      ret = decode_sound (dsp, fd, st.st_size, AFMT_MU_LAW, 1, 8000, NULL);
      goto done;
    }

  if (strcmp (suffix, ".snd") == 0 || strcmp (suffix, ".SND") == 0)
    {
      print_msg (VERBOSEM,
                 "%s: Unknown format. Assuming RAW audio (%d/%d/%d).\n",
                 filepart (filename), DEFAULT_SPEED, DEFAULT_FORMAT,
                 DEFAULT_CHANNELS);

      ret = decode_sound (dsp, fd, st.st_size, DEFAULT_FORMAT, DEFAULT_CHANNELS,
                          DEFAULT_SPEED, NULL);
      goto done;
    }

  if (strcmp (suffix, ".cdr") == 0 || strcmp (suffix, ".CDR") == 0)
    {
      print_msg (VERBOSEM, "%s: Playing CD-R (cdwrite) file.\n",
                 filepart (filename));

      ret = decode_sound (dsp, fd, st.st_size, AFMT_S16_BE, 2, 44100, NULL);
      goto done;
    }


  if (strcmp (suffix, ".raw") == 0 || strcmp (suffix, ".RAW") == 0)
    {
      print_msg (VERBOSEM, "%s: Playing RAW file.\n", filename);

      ret = decode_sound (dsp, fd, st.st_size, DEFAULT_FORMAT, DEFAULT_CHANNELS,
                          DEFAULT_SPEED, NULL);
      goto done;
    }

  print_msg (ERRORM, "%s: Unrecognized audio file type.\n", filename);
done:
  if (fd != -1) close (fd);

#if 0
  ioctl (audiofd, SNDCTL_DSP_SYNC, NULL);
#endif
  return ret;
seekerror:
  close (fd);
  return E_DECODE;
}

/*ARGSUSED*/
static errors_t
play_iff (dspdev_t * dsp, const char * filename, int fd, unsigned char * buf,
          int type)
{
/*
 * Generalized IFF parser - handles WAV, AIFF, AIFC, 8SVX, 16SV and MAUD.
 */
  enum
  {
    COMM_BIT,
    SSND_BIT,
    FVER_BIT
  };
#define COMM_FOUND (1 << COMM_BIT)
#define SSND_FOUND (1 << SSND_BIT)
#define FVER_FOUND (1 << FVER_BIT)

#define LIST_HUNK 0x4C495354

#define FVER_HUNK 0x46564552
#define COMM_HUNK 0x434F4D4D
#define SSND_HUNK 0x53534E44
#define ANNO_HUNK 0x414E4E4F
#define NAME_HUNK 0x4E414D45
#define AUTH_HUNK 0x41555448
#define COPY_HUNK 0x28632920 /* "(c) " */ 

#define VHDR_HUNK 0x56484452
#define BODY_HUNK 0x424f4459
#define CHAN_HUNK 0x4348414e

#define MDAT_HUNK 0x4D444154
#define MHDR_HUNK 0x4D484452

#define alaw_FMT 0x616C6177
#define ALAW_FMT 0x414C4157
#define ulaw_FMT 0x756C6177
#define ULAW_FMT 0x554C4157
#define sowt_FMT 0x736F7774
#define twos_FMT 0x74776F73
#define fl32_FMT 0x666C3332
#define FL32_FMT 0x464C3332
#define fl64_FMT 0x666C3634
#define FL64_FMT 0x464C3634
#define ima4_FMT 0x696D6134
#define NONE_FMT 0x4E4F4E45
#define raw_FMT  0x72617720
#define in16_FMT 0x696E3136
#define in24_FMT 0x696E3234
#define ni24_FMT 0x6E693234
#define in32_FMT 0x696E3332
#define ni32_FMT 0x6E693332
#define _23ni_FMT 0x32336E69

#define fmt_HUNK 0x666D7420
#define data_HUNK 0x64617461
#define INFO_HUNK 0x494E464F
#define DISP_HUNK 0x44495350

#define IARL_HUNK 0x4941524C
#define IART_HUNK 0x49415254
#define ICMS_HUNK 0x49434D53
#define ICMT_HUNK 0x49434D54
#define ICOP_HUNK 0x49434F50
#define ICRD_HUNK 0x49435244
#define IENG_HUNK 0x49454E47
#define IGNR_HUNK 0x49474E52
#define IKEY_HUNK 0x494B4559
#define INAM_HUNK 0x494E414D
#define IPRD_HUNK 0x49505244
#define ISBJ_HUNK 0x4953424A
#define ISFT_HUNK 0x49534654
#define ISRC_HUNK 0x49535243
#define ITCH_HUNK 0x49544348

#define READ_MACRO(fd, buf, len) \
  do { \
    if (read(fd, buf, len) < len) \
      { \
        print_msg (ERRORM, \
                   "%s: error: cannot read data in chunk (chunk id: " \
                   "0x%04X).\n", filename, chunk_id); \
        if ((found & SSND_FOUND) && (found & COMM_FOUND)) goto nexta; \
        else return E_DECODE; \
      } \
    chunk_pos += len; \
   } while(0)

#define ASEEK(fd, offset) \
  do { \
    if ((offset != 0) && (ossplay_lseek (fd, offset, SEEK_CUR) == -1)) \
      { \
        print_msg (ERRORM, \
                   "%s: error: cannot seek to end of chunk (chunk id: " \
                   "0x%04X).\n", filename, chunk_id); \
        if ((found & SSND_FOUND) && (found & COMM_FOUND)) goto nexta; \
        else return E_DECODE; \
      } \
    chunk_pos += offset; \
  } while (0)

#define AREAD(fd, buf, len) \
  do { \
    if (chunk_size < len + chunk_pos) \
      { \
        print_msg (ERRORM, \
                   "%s: error: chunk size is too small (chunk id: 0x%04X).\n", \
                   filename, chunk_id); \
        if ((found & SSND_FOUND) && (found & COMM_FOUND)) goto nexta; \
        else return E_DECODE; \
      } \
    READ_MACRO (fd, buf, len); \
  } while (0)

#define ONLYF(f) \
  if (!(type & (f))) \
    { \
      break; \
    } \

#define BITS2SFORMAT(endian) \
  do { \
    if (force_fmt == 0) switch (bits) \
      { \
         case 8: format = AFMT_S8; break; \
         case 16: format = AFMT_S16_##endian; break; \
         case 24: format = AFMT_S24_PACKED_##endian; break; \
         case 32: format = AFMT_S32_##endian; break; \
         default: format = AFMT_S16_##endian; break; \
     } break; \
  } while (0)

  int channels = 1, bits = 8, format, speed = 11025;
  big_t csize = 12;
  uint32 chunk_id, chunk_size = 18, chunk_pos, found = 0, offset = 0,
         sound_loc = 0, sound_size = 0, timestamp, total_size;
  int (*ne_int) (const unsigned char *p, int l) = be_int;

  msadpcm_values_t msadpcm_val = {
    256, 496, 7, 4, {
      {256, 0},
      {512, -256},
      {0, 0},
      {192, 64},
      {240, 0},
      {460, -208},
      {392, -232} },
    DEFAULT_CHANNELS
  };

  if (type == _8SVX_FILE) format = AFMT_S8;
  else format = AFMT_S16_BE;
  if (force_fmt != 0) format = force_fmt;
  if (type == WAVE_FILE) ne_int = le_int;

  total_size = ne_int (buf + 4, 4);
  if (verbose > 1)
    print_msg (NORMALM, "Filelen = %u\n", total_size);
  do
    {
      chunk_pos = 0;
      if (read (fd, buf, 8) < 8)
        {
          print_msg (ERRORM, "%s: Cannot read chunk header at pos %u\n",
                     filename, csize);
          if ((found & SSND_FOUND) && (found & COMM_FOUND)) goto nexta;
          return E_DECODE;
        }
      chunk_id = be_int (buf, 4);
      chunk_size = ne_int (buf + 4, 4);
      if (verbose > 3)
        print_msg (VERBOSEM, "%s: Reading chunk %c%c%c%c, size %d\n",
                   filename, buf[0], buf[1], buf[2], buf[3], chunk_size);
      if (chunk_size == 0)
        {
          print_msg (ERRORM, "%s: Chunk size equals 0 (pos: %u)!\n",
                     filename, csize);
          if ((found & SSND_FOUND) && (found & COMM_FOUND)) goto nexta;
          csize += 8;
          continue;
        }

      switch (chunk_id)
        {
          /* AIFF / AIFC chunks */
          case COMM_HUNK:
            ONLYF (AIFF_FILE | AIFC_FILE);
            if (found & COMM_FOUND)
              {
                print_msg (ERRORM, "%s: error: COMM hunk not singular!\n",
                           filename);
                return E_DECODE;
              }
            if (type == AIFC_FILE) AREAD (fd, buf, 22);
            else AREAD (fd, buf, 18);
            found |= COMM_FOUND;

            channels = be_int (buf, 2);
#if 0
            num_frames = be_int (buf + 2, 4); /* ossplay doesn't use this */
#endif
            bits = be_int (buf + 6, 2);
            bits += bits % 8;
            BITS2SFORMAT (BE);
            {
              /*
               * Conversion from IEEE-754 extended 80-bit to long double.
               * We take some shortcuts which don't affect this application.
               */
              int exp;
              ldouble_t COMM_rate = 0;

              exp = ((buf[8] & 127) << 8) + buf[9] - 16383;
#if 0
              /*
               * This part of the mantissa will always be resolved to
               * sub-Hz rates which we don't support anyway.
               */
              COMM_rate = (buf[14] << 24) + (buf[15] << 16) +
                          (buf[16] << 8) + buf[17];
              COMM_rate /= 1L << 32;
#endif
              COMM_rate += ((buf[10] & 127) << 24) + (buf[11] << 16) +
                           (buf[12] << 8) + buf[13];
              COMM_rate = ossplay_ldexpl (COMM_rate, exp-31);
              if (buf[10] & 128)
                COMM_rate += ossplay_ldexpl (1, exp); /* Normalize bit */
              if (buf[8] & 128) COMM_rate = -COMM_rate; /* Sign bit */
              if ((exp == 16384) || (COMM_rate <= 0))
                {
                  print_msg (ERRORM, "Invalid sample rate!\n");
                  return E_DECODE;
                }
              speed = COMM_rate;
            }

            if (type != AIFC_FILE) break;
            if (force_fmt != 0) break;
            switch (be_int (buf + 18, 4))
              {
                case NONE_FMT: break;
                case in16_FMT: format = AFMT_S16_BE; break;
                case in24_FMT: format = AFMT_S24_BE; break;
                case in32_FMT: format = AFMT_S32_BE; break;
                case ni24_FMT: format = AFMT_S24_LE; break;
                case _23ni_FMT:
                case ni32_FMT: format = AFMT_S32_LE; break;
                /*
                 * sowt/tows were intended as 16 bits only, but some recording
                 * programs misinterpret this. We can try to handle that,
                 * since complaint programs set the bits field to 16 anyway.
                 * (See QT doc chap.4 section 3).
                 */
                case sowt_FMT: BITS2SFORMAT (LE); break;
                case twos_FMT: BITS2SFORMAT (BE); break;
                case raw_FMT: format = AFMT_U8; break;
                case alaw_FMT:
                case ALAW_FMT: format = AFMT_A_LAW; break;
                case ulaw_FMT:
                case ULAW_FMT: format = AFMT_MU_LAW; break;
                case ima4_FMT: format = AFMT_MAC_IMA_ADPCM; break;
                case fl32_FMT:
                case FL32_FMT: format = AFMT_FLOAT32_BE; break;
                case fl64_FMT:
                case FL64_FMT: format = AFMT_DOUBLE64_BE; break;
                default:
                  print_msg (ERRORM,
                           "%s: error: %c%c%c%c compression is not supported\n",
                           filename, *(buf + 18), *(buf + 19),
                           *(buf + 20), *(buf + 21));
                  return E_FORMAT_UNSUPPORTED;
              }
            break;
          case SSND_HUNK:
            ONLYF (AIFF_FILE | AIFC_FILE);
            if (found & SSND_FOUND)
              {
                print_msg (ERRORM,
                           "%s: error: SSND hunk not singular!\n", filename);
                return E_DECODE;
              }
            AREAD (fd, buf, 8);
            found |= SSND_FOUND;

            offset = be_int (buf, 4);
#if 0
            block_size = be_int (buf + 4, 4); /* ossplay doesn't use this */
#endif
            sound_loc = csize + 16 + offset;
            sound_size = chunk_size - 8;

            if ((from_stdin) && (ossplay_lseek == ossplay_lseek_stdin))
              goto stdinext;
            break;
          case FVER_HUNK:
            ONLYF (AIFC_FILE);
            AREAD (fd, buf, 4);
            timestamp = be_int (buf, 4);
            /* timestamp = 0xA2805140 for typical AIFF files */
            found |= FVER_FOUND;
            break;

          /* 8SVX / 16SV chunks */
          case VHDR_HUNK:
            ONLYF (_8SVX_FILE | _16SV_FILE);
            AREAD (fd, buf, 16);
            speed = be_int (buf + 12, 2);
            found |= COMM_FOUND;
            if ((force_fmt != 0) || (type != _8SVX_FILE)) break;
            switch (buf[15])
              {
                case 0: format = AFMT_S8; break;
                case 1: format = AFMT_FIBO_DELTA; break;
                case 2: format = AFMT_EXP_DELTA; break;
                default:
                  print_msg (ERRORM, "%s: Unsupported compression %d\n",
                             filename, buf[15]);
                  return E_FORMAT_UNSUPPORTED;
              }
            break;
          case data_HUNK: /* WAVE chunk */
            ONLYF (WAVE_FILE | WAVE_FILE_BE);
            if (verbose > 2)
              print_msg (NORMALM,  "DATA chunk. Offs = %u, "
                         "len = %u\n", csize+8, chunk_size);
            sound_loc = csize + 8;
          case MDAT_HUNK: /* MAUD chunk */
          case BODY_HUNK: /* 8SVX/16SV */
            sound_size = chunk_size;
            if (chunk_id != data_HUNK) sound_loc = csize + 4;
            found |= SSND_FOUND;
            if ((from_stdin) && (ossplay_lseek == ossplay_lseek_stdin))
              goto stdinext;
            break;
          case CHAN_HUNK:
            /* Used in 8SVX too: http://amigan.1emu.net/reg/8SVX.CHAN.PAN.txt */
            ONLYF (MAUD_FILE);
            AREAD (fd, buf, 4);
            channels = be_int (buf, 4);
            if (channels > 2)
              {
                print_msg (ERRORM, "ossplay doesn't support MAUD format's "
                           "%d channel configuration\n", channels);
                return E_CHANNELS_UNSUPPORTED;
              }
            break;

          /* MAUD chunks */
          case MHDR_HUNK:
            ONLYF (MAUD_FILE);
            AREAD (fd, buf, 20);
            bits = be_int (buf + 4, 2);
            BITS2SFORMAT (BE);
            if (be_int (buf + 12, 2) == 0)
              {
                print_msg (ERRORM, "Invalid rate!\n");
                return E_DECODE;
              }
            speed = be_int (buf + 8, 4) / be_int (buf + 12, 2);
            channels = be_int (buf + 16, 2);
            found |= COMM_FOUND;
            if (force_fmt != 0) break;
            switch (be_int (buf + 18, 2))
              {
                case 0: /* NONE */ break;
                case 2: format = AFMT_A_LAW; break;
                case 3: format = AFMT_MU_LAW; break;
                case 6: format = AFMT_IMA_ADPCM; break;
                default:
                  print_msg (ERRORM, "%s: format not supported", filename);
                  return E_FORMAT_UNSUPPORTED;
              }
            break;

          /* WAVE chunks */
          case fmt_HUNK: { unsigned int len, i, x; int wtype = 0x1;
            ONLYF (WAVE_FILE | WAVE_FILE_BE);
            if (found & COMM_FOUND)
              {
                print_msg (ERRORM, "%s: error: fmt hunk not singular!\n",
                           filename);
                return E_DECODE;
              }
            if (chunk_size > 1024) len = 1024;
            else len = chunk_size;
            AREAD (fd, buf, len);

            if (force_fmt == 0) wtype = format = ne_int (buf, 2);
            if (verbose > 2)
              print_msg (NORMALM,  "FMT chunk: len = %u, fmt = %#x\n",
                         chunk_size, format);

            msadpcm_val.channels = channels = ne_int (buf + 2, 2);
            speed = ne_int (buf + 4, 4);
#if 0
            bytes_per_sec = be_int (buf + 8, 4); /* ossplay doesn't use this */
#endif
            msadpcm_val.nBlockAlign = ne_int (buf + 12, 2);
            if (msadpcm_val.nBlockAlign == 0)
              print_msg (WARNM, "%s: nBlockAlign is 0!\n", filename);
            msadpcm_val.bits = bits = ne_int (buf + 14, 2);

            if (wtype == 0xFFFE) /* WAVE_FORMAT_EXTINSIBLE */
              {
                if (chunk_size < 40)
                  {
                    print_msg (ERRORM, "%s: invalid fmt chunk\n", filename);
                    return E_DECODE;
                  }
                /* TODO: parse the rest of WAVE_FORMAT_EXTENSIBLE */
                format = ne_int (buf + 24, 2);
              }
            if (force_fmt == 0) switch (format)
              {
                case 0x1: /* WAVE_FORMAT_PCM */
                  bits += bits % 8;
                  if (type == WAVE_FILE) BITS2SFORMAT (LE);
                  else BITS2SFORMAT (BE);
                  if (bits == 8) format = AFMT_U8;
                  break;
                case 0x2: /* WAVE_FORMAT_MS_ADPCM */
                  format = AFMT_MS_ADPCM;
                  break;
                case 0x3: /* WAVE_FORMAT_IEEE_FLOAT */
                  if (bits == 32) format = AFMT_FLOAT32_LE;
                  else if (bits == 64) format = AFMT_DOUBLE64_LE;
                  else
                    {
                      print_msg (ERRORM, "%s: Odd number of bits (%d) for "
                                 "WAVE_FORMAT_IEEE_FLOAT!\n", filename, bits);
                      return E_FORMAT_UNSUPPORTED;
                    }
                   break;
                case 0x102: /* IBM_FORMAT_ALAW */
                case 0x6: /* WAVE_FORMAT_ALAW */
                  format = AFMT_A_LAW;
                  break;
                case 0x101: /* IBM_FORMAT_MULAW */
                case 0x7: /* WAVE_FORMAT_MULAW */
                  format = AFMT_MU_LAW;
                  break;
                case 0x11: /* WAVE_FORMAT_IMA_ADPCM */
                  if (bits == 4) format = AFMT_MS_IMA_ADPCM;
                  else if (bits == 3) format = AFMT_MS_IMA_ADPCM_3BITS;
                  else
                    {
                      print_msg (ERRORM, "%s: Invalid number of bits (%d) for "
                                 "WAVE_FORMAT_IMA_ADPCM!\n", filename, bits);
                      return E_FORMAT_UNSUPPORTED;
                    }
                  break;
#if 0
                case 0x50: /* MPEG */
                case 0x55: /* MPEG 3 */
#endif
                default:
                  print_msg (ERRORM, "%s: Unsupported wave format %#x\n",
                             filename, format);
                  return E_FORMAT_UNSUPPORTED;
              }
            found |= COMM_FOUND;

            if ((chunk_size < 20) ||
                ((format != AFMT_MS_ADPCM) &&
                 (format != AFMT_MS_IMA_ADPCM) &&
                 (format != AFMT_MS_IMA_ADPCM_3BITS)
                )
               ) break;
            msadpcm_val.wSamplesPerBlock = ne_int (buf + 18, 2);
            if ((format != AFMT_MS_ADPCM) || (chunk_size < 22)) break;
            msadpcm_val.wNumCoeff = ne_int (buf + 20, 2);
            if (msadpcm_val.wNumCoeff > 32) msadpcm_val.wNumCoeff = 32;

            x = 22;

            for (i = 0; (i < msadpcm_val.wNumCoeff) && (x < chunk_size-3); i++)
              {
                msadpcm_val.coeff[i].coeff1 = (int16) ne_int (buf + x, 2);
                x += 2;
                msadpcm_val.coeff[i].coeff2 = (int16) ne_int (buf + x, 2);
                x += 2;
              }
            msadpcm_val.wNumCoeff = i;
            } break;

          case LIST_HUNK: {
            unsigned char * tag;
            uint32 i, cssize = 4, len, subchunk_id, subchunk_size;

            ONLYF (WAVE_FILE | WAVE_FILE_BE);
            if ((!verbose) || (chunk_size < 12)) break;
            READ_MACRO (fd, buf, 4);
            chunk_id = be_int (buf, 4);
            if (chunk_id != INFO_HUNK) break;
            do
              {
                cssize += 8;
                if (cssize > chunk_size) break;
                READ_MACRO (fd, buf, 8);
                subchunk_id = be_int (buf, 4);
                subchunk_size = ne_int (buf + 4, 4);
                if (verbose > 3)
                  print_msg (VERBOSEM, "%s: Reading subchunk %c%c%c%c, size %d\n",
                             filename, buf[0], buf[1], buf[2], buf[3],
                             subchunk_size);
                if (subchunk_size == 0) break;
                cssize += subchunk_size;
                if (cssize > chunk_size) break;
                switch (subchunk_id)
                  {
                    case IARL_HUNK:
                      print_msg (STARTM, "%s: Archival Location: ", filename);
                      break;
                    case IART_HUNK:
                      print_msg (STARTM, "%s: Artist Name: ", filename);
                      break;
                    case ICMS_HUNK:
                      print_msg (STARTM, "%s: Commissioned: ", filename);
                      break;
                    case ICMT_HUNK:
                      print_msg (STARTM, "%s: Comment: ", filename);
                      break;
                    case ICOP_HUNK:
                      print_msg (STARTM, "%s: Copyright: ", filename);
                      break;
                    case ICRD_HUNK:
                      print_msg (STARTM, "%s: Creation date: ", filename);
                      break;
                    case IENG_HUNK:
                      print_msg (STARTM, "%s: Engineer: ", filename);
                      break;
                    case IGNR_HUNK:
                      print_msg (STARTM, "%s: Genre: ", filename);
                      break;
                    case IKEY_HUNK:
                      print_msg (STARTM, "%s: Keywords: ", filename);
                      break;
                    case INAM_HUNK:
                      print_msg (STARTM, "%s: Name: ", filename);
                      break;
                    case IPRD_HUNK:
                      print_msg (STARTM, "%s: Product: ", filename);
                      break;
                    case ISBJ_HUNK:
                      print_msg (STARTM, "%s: Subject: ", filename);
                      break;
                    case ISFT_HUNK:
                      print_msg (STARTM, "%s: Software: ", filename);
                      break;
                    case ISRC_HUNK:
                      print_msg (STARTM, "%s: Source: ", filename);
                      break;
                    case ITCH_HUNK:
                      print_msg (STARTM, "%s: Technician: ", filename);
                      break;
                    default:
                      ASEEK (fd, subchunk_size + (subchunk_size & 1));
                      continue;
                  }

                if (subchunk_size > 1024)
                  {
                    READ_MACRO (fd, buf, 1024);
                    ASEEK (fd, subchunk_size + (subchunk_size & 1) - 1024);
                    len = 1024;
                  }
                else
                  {
                    len = subchunk_size + (subchunk_size & 1);
                    READ_MACRO (fd, buf, len);
                  }

                tag = buf + len;
                /*
                 * According to the spec, all of the above hunks contain ZSTRs,
                 * so we can safely ignore the last char.
                 */
                for (i = 0; i < len-1; i++)
                  {
                    if (!isprint (buf[i])) buf[i] = ' ';
                    else tag = buf + i + 1;
                  }
                /* Remove trailing nonprintables */
                *tag = '\0';
                print_msg (ENDM, "%s\n", buf);
               } while (cssize < chunk_size);
            } break;

          /* Cool Edit can create this chunk. Also some Windows files use it */
          case DISP_HUNK: {
            unsigned char * tag;
            uint32 i, len;

            ONLYF (WAVE_FILE | WAVE_FILE_BE);
            if ((verbose < 2) || (chunk_size < 4)) break;
            READ_MACRO (fd, buf, 4);
            if (ne_int (buf, 4) != 1) break;
            if (chunk_size > 1028) len = 1024;
            else len = chunk_size - 4;
            AREAD (fd, buf, len);

            tag = buf + len;
            for (i = 0; i < len-1; i++)
              {
                if (!isprint (buf[i])) buf[i] = ' ';
                else tag = buf + i + 1;
              }
            *tag = '\0';
            print_msg (VERBOSEM, "%s: %s\n", filename, buf);
            } break;

          case NAME_HUNK:
          case AUTH_HUNK:
          case ANNO_HUNK:
          case COPY_HUNK:
            ONLYF (AIFF_FILE | AIFC_FILE | _8SVX_FILE | _16SV_FILE | MAUD_FILE);
            if (verbose)
              {
                unsigned char * tag;
                uint32 i, len;

                print_msg (STARTM,  "%s: ", filename);
                switch (chunk_id)
                  {
                    case NAME_HUNK:
                      print_msg (CONTM, "Name: ");
                      break;
                    case AUTH_HUNK:
                      print_msg (CONTM, "Author: ");
                      break;
                    case COPY_HUNK:
                      print_msg (CONTM, "Copyright: ");
                      break;
                    case ANNO_HUNK:
                      print_msg (CONTM, "Annonations: ");
                      break;
                  }
                if (chunk_size > 1023) len = 1023;
                else len = chunk_size;
                AREAD (fd, buf, len);

                tag = buf + len + 1;
                for (i = 0; i < len; i++)
                  {
                    if (!isprint (buf[i])) buf[i] = ' ';
                    else tag = buf + i + 1;
                  }
                *tag = '\0';
                print_msg (ENDM, "%s\n", buf);
                break;
              }

          default:
            break;
       }

      csize += chunk_size + (chunk_size & 1) + 8;
      ASEEK (fd, chunk_size + (chunk_size & 1) - chunk_pos);

    } while (csize < total_size);

nexta:
    if ((found & COMM_FOUND) == 0)
      {
        print_msg (ERRORM, "%s: Couldn't find format chunk!\n", filename);
        return E_DECODE;
      }

    if ((found & SSND_FOUND) == 0)
      {
        print_msg (ERRORM, "%s: Couldn't find sound chunk!\n", filename);
        return E_DECODE;
      }

    if ((type == AIFC_FILE) && ((found & FVER_FOUND) == 0))
      print_msg (WARNM, "%s: Couldn't find AIFC FVER chunk.\n", filename);

    if (ossplay_lseek (fd, sound_loc, SEEK_SET) == -1)
      {
        perror_msg (filename);
        print_msg (ERRORM, "Can't seek in file\n");
        return E_DECODE;
      }

stdinext:
  if (verbose)
    print_verbose_fileinfo (filename, type, format, channels, speed);

  return decode_sound (dsp, fd, sound_size, format, channels, speed,
                       (void *)&msadpcm_val);
}

/*ARGSUSED*/
static errors_t
play_au (dspdev_t * dsp, const char * filename, int fd, unsigned char * hdr,
         int l)
{
  int channels = 1, format = AFMT_S8, speed = 11025;
  big_t filelen;
  uint32 fmt = 0, i, p = 24, an_len = 0;

  p = be_int (hdr + 4, 4);
  if (memcmp (hdr + 8, "\xFF\xFF\xFF\xFF", 4))
    filelen = be_int (hdr + 8, 4);
  else
    filelen = BIG_SPECIAL;
  fmt = be_int (hdr + 12, 4);
  speed = be_int (hdr + 16, 4);
  channels = be_int (hdr + 20, 4);

  if (verbose > 1)
    {
      if (filelen == BIG_SPECIAL)
        print_msg (NORMALM, "%s: Filelen: unspecified\n", filename);
      else
        print_msg (NORMALM, "%s: Filelen: %u\n", filename, filelen);
    }
  if (verbose > 2) print_msg (NORMALM, "%s: Offset: %u\n", filename, p);

  if (force_fmt == 0) switch (fmt)
    {
    case 1:
      format = AFMT_MU_LAW;
      break;

    case 2:
      format = AFMT_S8;
      break;

    case 3:
      format = AFMT_S16_BE;
      break;

    case 4:
      format = AFMT_S24_PACKED_BE;
      break;

    case 5:
      format = AFMT_S32_BE;
      break;

    case 6:
      format = AFMT_FLOAT32_BE;
      break;

    case 7:
      format = AFMT_DOUBLE64_BE;
      break;

    case 23:
    case 24:
    case 25:
    case 26:
      print_msg (ERRORM, "%s: G.72x ADPCM encoded .au files are not supported",
                 filename);
      return E_FORMAT_UNSUPPORTED;

    case 27:
      format = AFMT_A_LAW;
      break;

    default:
      print_msg (ERRORM, "%s: Unknown encoding %d.\n", filename, fmt);
      return E_FORMAT_UNSUPPORTED;
    }

  if (verbose)
    {
      print_verbose_fileinfo (filename, AU_FILE, format, channels, speed);

      if (p > 24)
        {
          unsigned char * tag;

          if (p > 1047) an_len = 1023;
          else an_len = p - 24;
          if (read (fd, hdr, an_len) < an_len)
            {
              print_msg (ERRORM, "%s: Can't read %u bytes from pos 24\n",
                         filename, an_len);
              return E_DECODE;
            }

          tag = hdr + an_len;
          for (i = 0; i < an_len; i++)
            {
              if (!isprint (hdr[i])) hdr[i] = ' ';
              else tag = hdr + i + 1;
            }
          *tag = '\0';
          print_msg (NORMALM, "%s: Annotations: %s\n", filename, hdr);
        }
    }

  if (ossplay_lseek (fd, p - l - an_len, SEEK_CUR) == -1)
    {
      perror_msg (filename);
      print_msg (ERRORM, "Can't seek to the data chunk\n");
      return E_DECODE;
    }

  return decode_sound (dsp, fd, filelen, format, channels, speed, NULL);
}

/*ARGSUSED*/
static errors_t
play_voc (dspdev_t * dsp, const char * filename, int fd, unsigned char * hdr,
          int l)
{
#define VREAD(fd, buf, len) \
  do { \
    if (read (fd, buf, len) < len) \
      { \
        print_msg (ERRORM, "%s: Can't read %d bytes at pos %d\n", \
                   filename, len, l); \
        return E_DECODE; \
      } \
    pos += len; \
  } while (0)

  uint32 blklen, data_offs, fmt, id, len, loopcount = 0, loopoffs = 4,
         pos = l + 7, tmp, vers;
  unsigned char buf[256], block_type;
  flag plock = 0;
  int speed = 11025, channels = 1, bits = 8, format = AFMT_U8;
  errors_t ret;

  if (read (fd, hdr + 19, 7) < 7)
    {
      print_msg (ERRORM, "%s: Not a valid .VOC file\n", filename);
      return E_DECODE;
    }

  data_offs = le_int (hdr + 0x14, 2);
  vers = le_int (hdr + 0x16, 2);
  id = le_int (hdr + 0x18, 2);

  if ((((~vers) + 0x1234) & 0xffff) != id)
    {
      print_msg (ERRORM, "%s: Not a valid .VOC file\n", filename);
      return E_DECODE;
    }

  print_msg (VERBOSEM, "Playing .VOC file %s\n", filename);

   /*LINTED*/ while (1)
    {
      if (ossplay_lseek (fd, data_offs - pos, SEEK_CUR) == -1)
        {
          print_msg (ERRORM, "%s: Can't seek to pos %d\n", filename, data_offs);
          return E_DECODE;
        }
      pos = data_offs + 4;

      if ((tmp = read (fd, buf, 1)) < 1)
        {
          /* Don't warn when read returns 0 - it may be end of file. */
          if (tmp != 0)
            print_msg (ERRORM,
                       "%s: Can't read 1 byte at pos %d\n", filename, l);
          return E_DECODE;
        }

      block_type = buf[0];

      if (block_type == 0)
	return E_OK;			/* End */

      if (read (fd, buf, 3) != 3)
	{
	  print_msg (ERRORM, "%s: Truncated .VOC file (%d)\n",
		     filename, buf[0]);
	  return E_DECODE;
	}

      blklen = len = le_int (buf, 3);

      if (verbose > 2)
	print_msg (NORMALM, "%s: %0x: Block type %d, len %d\n",
		   filename, data_offs, block_type, len);
      switch (block_type)
	{

	case 1:		/* Sound data buf */
          if (!plock)
            {
	      VREAD (fd, buf, 2);

	      tmp = 256 - buf[0];	/* Time constant */
	      speed = (1000000 + tmp / 2) / tmp / channels;

              fmt = buf[1];
              len -= 2;

               if (force_fmt != 0) break;
               switch (fmt)
                 {
                   case 0: format = AFMT_U8; break;
                   case 1: format = AFMT_CR_ADPCM_4; break;
                   case 2: format = AFMT_CR_ADPCM_3; break;
                   case 3: format = AFMT_CR_ADPCM_2; break;
                   case 4: format = AFMT_S16_LE; break;
                   case 6: format = AFMT_A_LAW; break;
                   case 7: format = AFMT_MU_LAW; break;
                   default:
                     print_msg (ERRORM,
                                "%s: encoding %d is not supported\n",
                                filename, fmt);
                     return E_FORMAT_UNSUPPORTED;
                 }
            }

	case 2:		/* Continuation data */
          if ((ret = decode_sound(dsp, fd, len, format, channels, speed, NULL)))
            return ret;
          pos += len;
	  break;

	case 3:		/* Silence */
	  VREAD (fd, buf, 3);
	  len = le_int (buf, 2);
	  tmp = 256 - buf[2];	/* Time constant */
	  speed = (1000000 + tmp / 2) / tmp;
	  if ((ret = silence (dsp, len, speed))) return ret;
	  break;

        case 5: 	/* Text */
          if (!quiet)
            {
              size_t i;

              if (len > 256) len = 256;
              VREAD (fd, buf, len);
              for (i = 0; i < len; i++) if (!isprint (buf[i])) buf[i] = '.';
              buf[len-1] = '\0';
              print_msg (NORMALM, "Text: %s\n", buf);
            }
          break;

        case 6:		/* Loop start */
          VREAD (fd, buf, 2);
          loopoffs = data_offs + blklen + 4;
          loopcount = le_int (buf, 2);
          break;

        case 7:		/* End of repeat loop */
          if (loopcount != 0xffff) loopcount--;

          /* Set "return" point. Compensate for increment of data_offs. */
          if (loopcount > 0) data_offs = loopoffs - blklen - 4;

          break;

        case 8:		/* Sampling parameters */
          VREAD (fd, buf, 4);

          speed = 256000000/(channels * (65536 - le_int (buf, 2)));
          channels = buf[3] + 1;
          fmt = buf[2];
          plock = 1;

          if (force_fmt != 0) break;
          switch (fmt)
            {
              case 0: format = AFMT_U8; break;
              case 1: format = AFMT_CR_ADPCM_4; break;
              case 2: format = AFMT_CR_ADPCM_3; break;
              case 3: format = AFMT_CR_ADPCM_2; break;
              case 4: format = AFMT_S16_LE; break;
              case 6: format = AFMT_A_LAW; break;
              case 7: format = AFMT_MU_LAW; break;
              default:
                print_msg (ERRORM,
                           "%s: encoding %d is not supported\n", filename, fmt);
                return E_FORMAT_UNSUPPORTED;
            }
          break;

        case 9:		/* New format sound data */
          VREAD (fd, buf, 12);

          len -= 12;

          speed = le_int (buf, 3);
          bits = buf[4];
          channels = buf[5];
          fmt = le_int (buf + 6, 2);

          if (force_fmt == 0) switch (fmt)
            {
              case 0: format = AFMT_U8; break;
              case 1: format = AFMT_CR_ADPCM_4; break;
              case 2: format = AFMT_CR_ADPCM_3; break;
              case 3: format = AFMT_CR_ADPCM_2; break;
              case 4: format = AFMT_S16_LE; break;
              case 6: format = AFMT_A_LAW; break;
              case 7: format = AFMT_MU_LAW; break;
              default:
                print_msg (ERRORM,
                           "%s: encoding %d is not supported\n", filename, fmt);
                return E_FORMAT_UNSUPPORTED;
            }

          if ((ret = decode_sound(dsp, fd, len, format, channels, speed, NULL)))
            return ret;
          pos += len;
	  break;
	}

      if (block_type != 8) plock = 0;
      data_offs += blklen + 4;
    }
  /* return 0; */ /* Not reached */
}

static void
print_verbose_fileinfo (const char * filename, int type, int format,
                        int channels, int speed)
{
  char chn[32];
  const char * fmt = "";

  switch (type)
    {
      case WAVE_FILE:
      case WAVE_FILE_BE:
        print_msg (VERBOSEM, "Playing WAVE file %s, ", filename); break;
      case AIFC_FILE:
        print_msg (VERBOSEM, "Playing AIFC file %s, ", filename); break;
      case AIFF_FILE:
        print_msg (VERBOSEM, "Playing AIFF file %s, ", filename); break;
      case AU_FILE:
        print_msg (VERBOSEM, "Playing AU file %s, ", filename); break;
      case _8SVX_FILE:
        print_msg (VERBOSEM, "Playing 8SVX file %s, ", filename); break;
      case _16SV_FILE:
        print_msg (VERBOSEM, "Playing 16SV file %s, ", filename); break;
      case MAUD_FILE:
        print_msg (VERBOSEM, "Playing MAUD file %s, ", filename); break;
      case OGG_FILE:
        print_msg (VERBOSEM, "Playing OGG file %s, ", filename); break;
    }

  if (channels == 1)
    strcpy (chn, "mono");
  else if (channels == 2)
    strcpy (chn, "stereo");
  else
    snprintf (chn, sizeof(chn), "%d channels", channels);

  switch (format)
    {
       case AFMT_QUERY: fmt = "Invalid format"; break;
       case AFMT_MAC_IMA_ADPCM:
       case AFMT_MS_IMA_ADPCM:
       case AFMT_IMA_ADPCM: fmt = "IMA ADPCM"; break;
       case AFMT_MS_IMA_ADPCM_3BITS: fmt = "3BIT DVI ADPCM"; break;
       case AFMT_MS_ADPCM: fmt = "MS-ADPCM"; break;
       case AFMT_MU_LAW: fmt = "mu-law"; break;
       case AFMT_A_LAW: fmt = "A-law"; break;
       case AFMT_U8:
       case AFMT_S8: fmt = "8 bits"; break;
       case AFMT_S16_LE:
       case AFMT_S16_BE:
       case AFMT_U16_LE:
       case AFMT_U16_BE: fmt = "16 bits"; break;
       case AFMT_S24_LE:
       case AFMT_S24_BE:
       case AFMT_S24_PACKED_BE:
       case AFMT_S24_PACKED: fmt = "24 bits"; break;
       case AFMT_SPDIF_RAW:
       case AFMT_S32_LE:
       case AFMT_S32_BE: fmt = "32 bits"; break;
       case AFMT_FLOAT32_LE:
       case AFMT_FLOAT32_BE: fmt = "32 bit float"; break;
       case AFMT_FLOAT: fmt = "float"; break;
       case AFMT_DOUBLE64_LE:
       case AFMT_DOUBLE64_BE: fmt = "64 bit float"; break;
       case AFMT_VORBIS: fmt = "vorbis"; break;
       case AFMT_MPEG: fmt = "mpeg"; break;
       case AFMT_FIBO_DELTA: fmt = "fibonacci delta"; break;
       case AFMT_EXP_DELTA: fmt = "exponential delta"; break;
    }
  print_msg (VERBOSEM, "%s/%s/%d Hz\n", fmt, chn, speed);
}
