/*
   vplay.c - plays and records 
             CREATIVE LABS VOICE-files, Microsoft WAVE-files and raw data

   Autor:    Michael Beck - beck@informatik.hu-berlin.de

   Version 1.1: thanks to gary@minster.york.ac.uk for updating WAVE-handling
*/

#include <stdio.h>
#include <malloc.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>

#include "fmtheaders.h"

/* trying to define independent ioctl for sounddrivers version 1 and 2+ ,
   but it doesn't work everywere */
#ifdef SOUND_VERSION
#define IOCTL(a,b,c)		ioctl(a,b,&c)
#else
#define IOCTL(a,b,c)		(c = ioctl(a,b,c) )
#endif

#ifndef DEFAULT_DSP_SPEED
#define DEFAULT_DSP_SPEED 	8000
#endif

#ifndef AUDIO
#define AUDIO "/dev/dsp"
#endif

#define RECORD	0
#define PLAY	1

#define min(a,b) 		((a) <= (b) ? (a) : (b))
#define d_printf(f,a...)        if (verbose_mode) fprintf (stderr,f,##a)

#define VOC_FMT			0
#define WAVE_FMT		1
#define RAW_DATA		2

/* global data */

char *audio_device = AUDIO;
char *version = "vplay 1.2.1 (30 Jan 97)\n";
int timelimit = 0, dsp_speed = DEFAULT_DSP_SPEED, dsp_stereo = 0;
int samplesize   = 8;
int quiet_mode   = 0;
int verbose_mode = 0;
int record_type  = VOC_FMT;
int override     = 0;
u_long count;
int audio, abuf_size, zbuf_size;
int direction, omode;
char *audiobuf, *zerobuf, *command;
int vocminor, vocmajor;		/* .VOC version */

/* defaults for playing raw data */
struct {
  int timelimit, dsp_speed, dsp_stereo, samplesize;
} raw_info = { 0, DEFAULT_DSP_SPEED, 0, 8 };

/* needed prototypes */

void record_play (char *name);
void start_voc (int fd, u_long count);
void start_wave (int fd, u_long count);
void end_voc (int fd);
void end_wave_raw (int fd);

struct fmt_record {
  void (*start) (int fd, u_long count);
  void (*end) (int fd);
  char *what;
} fmt_rec_table[] = {
  { start_voc,  end_voc,      "VOC" },
  { start_wave, end_wave_raw, "WAVE" },
  { NULL,       end_wave_raw, "raw data" }
}; 

struct option options[] =
{
  { "quiet",      no_argument,       NULL, 'q' },
  { "help",       no_argument,       NULL, 'h' },
  { "version",    no_argument,       NULL, 'V' },
  { "speed",      required_argument, NULL, 's' },
  { "stereo",     no_argument,       NULL, 'S' },
  { "timelimit",  required_argument, NULL, 't' },
  { "samplesize", required_argument, NULL, 'b' },
  { "voc",        no_argument,       NULL, 'v' },
  { "wave",       no_argument,       NULL, 'w' },
  { "raw",        no_argument,       NULL, 'r' },
  { "verbose",    no_argument,       NULL, 'd' },
  { "device",     required_argument, NULL, 'o' },
  { 0, 0, 0, 0 }
};

void usage(void)
{
  fprintf (stderr, "Usage: %s [OPTIONS] [file ...]\n\n", command);
  fprintf (stderr, "  -V --version           output version information and exit\n");
  fprintf (stderr, "  -S --stereo            stereo output (default is mono)\n");
  fprintf (stderr, "  -s --speed=SPEED       sets the samplerate (default is %d Hz)\n", DEFAULT_DSP_SPEED);
  fprintf (stderr, "  -t --timelimit=SEC     sets the recording time in seconds\n");
  fprintf (stderr, "  -b --samplesize=BITS   sets the sample size (default is 8 bit)\n");
  fprintf (stderr, "  -o --device=DEVICE     changes the audio device (default is " AUDIO ")\n");
  fprintf (stderr, "  -v --voc               record a CREATIVE LABS VOICE file (default)\n");
  fprintf (stderr, "  -w --wave              record a MICROSOFT WAVE file\n");
  fprintf (stderr, "  -r --raw               record raw data without header\n");
  fprintf (stderr, "  -q --quiet             quiet mode\n");
  fprintf (stderr, "  -d --verbose           show verbose informations\n");
  fprintf (stderr, "  -h --help              display this help and exit\n");
}

int main (int argc, char *argv[])
{
  int c, index = 0;

  command = argv[0];
  if (strstr (argv[0], "vrec")) {
    direction = RECORD;
    omode = O_RDONLY;
  }
  else if (strstr (argv[0], "vplay")) {
    direction = PLAY;
    omode = O_WRONLY;
  }
  else {
    fprintf (stderr,
             "Error: command should be named either vrec or vplay\n");
    exit (1);
  }

  while ((c = getopt_long (argc, argv, "Vqs:St:b:vrwdo:Hh?",
                           options, &index)) != EOF)
    switch (c) {
      case 'V':
        fprintf (stderr, version);
#ifdef SOUND_VERSION
        fprintf (stderr, "compiled for sounddriver V2.0+\n");
#else
	fprintf (stderr, "compiled for sounddriver V1.x\n");
#endif
	exit(0);
        break;
      case 'S':
	dsp_stereo = raw_info.dsp_stereo = 1;
	break;
      case 'q':
	quiet_mode = 1;
	break;
      case 'r':
        record_type = RAW_DATA;
	override = 1;
        break;
      case 'v' :
        record_type = VOC_FMT;
        break;
      case 'w' :
        record_type = WAVE_FMT;
        break;
      case 's':
	dsp_speed = atoi (optarg);
	if (dsp_speed < 300)
	  dsp_speed *= 1000;
        raw_info.dsp_speed = dsp_speed;
	break;
      case 't':
	timelimit = raw_info.timelimit = atoi (optarg);
	break;
      case 'b':
	samplesize = raw_info.samplesize = atoi (optarg);
	break;
      case 'd':
        verbose_mode = 1; quiet_mode = 0;
        break;
      case 'o':
        audio_device = strdup(optarg);
        break;
      case 'H':
      case 'h':
      case '?':
        fprintf (stderr, version);
	usage();
	exit(0);
      default:
        fprintf (stderr, version);
	usage();
	exit (-1);
    }

  audio = open (audio_device, omode, 0);
  if (audio == -1) {
    perror (audio_device);
    exit (-1);
  }

  IOCTL(audio, SNDCTL_DSP_GETBLKSIZE, abuf_size);
  if (abuf_size == -1)
    perror (audio_device);
  if (abuf_size < 4096)		/* make the audiobuffer a little bit bigger */
    abuf_size = 4096;
 
  zbuf_size = 256;
  if ( (audiobuf = malloc (abuf_size)) == NULL ||
       (zerobuf  = malloc (zbuf_size)) == NULL )  {
    fprintf (stderr, "%s: unable to allocate input/output buffer\n", command);
    exit (-1);
  }
  memset (zerobuf, 128, zbuf_size);
 
  if (optind > argc - 1)
    record_play (NULL);
  else
    while (optind <= argc - 1) {
      record_play (argv[optind++]);
    }
  close (audio);
  return 0;
}

/*
 test, if it is a .VOC file and return >=0 if ok (this is the length of rest)
                                       < 0 if not 
*/
int test_vocfile(void *buffer)
{
  VocHeader *vp = buffer;
  if (strstr(vp->magic, MAGIC_STRING) ) {
    vocminor = vp->version & 0xFF;
    vocmajor = vp->version / 256;
    if (vp->version != (0x1233 - vp->coded_ver) )
      return -2;				/* coded version mismatch */
    return vp->headerlen - sizeof(VocHeader);	/* 0 mostly */
  }
  return -1;					/* magic string fail */
}

/*
 test, if it's a .WAV file, 0 if ok (and set the speed, stereo etc.)
                          < 0 if not
 little bit improved now
*/
int test_wavefile(void *buffer, int fd)
{
  WaveHeader *wp = buffer;
  DataHeader data;

  if (wp->main_chunk == RIFF && wp->chunk_type == WAVE &&
      wp->sub_chunk == FMT) {
    if (wp->format != PCM_CODE) {
      fprintf (stderr, "%s: can't play not PCM-coded WAVE-files\n", command);
      exit (-1);
    }
    if (wp->modus > 2) {
      fprintf (stderr, "%s: can't play WAVE-files with %d tracks\n",
               command, wp->modus);
      exit (-1);
    }
    dsp_stereo = (wp->modus == WAVE_STEREO) ? 1 : 0;
    samplesize = wp->bit_p_spl;
    dsp_speed = wp->sample_fq; 
    /* it is a wavefile, try to read the data-chunks */
    for (;;) {
      if (read(fd, &data, sizeof(data)) != sizeof(data))
        exit (-1);
      switch (data.data_chunk) {
        case DATA: 
          count = data.data_length;
          return 0;
        default:
          fprintf (stderr, "Skipping unknown chunk %4s.\n", (char *)&data.data_chunk);
          if (lseek (fd, data.data_length, SEEK_CUR) < 0) {
            /* isn't seekable, eat it up in the audiobuf */
            while (data.data_length > abuf_size) {
              read (fd, audiobuf, abuf_size);
              data.data_length -= abuf_size;
            }
            if (data.data_length)
              read (fd, audiobuf, data.data_length);
          }
      }
    }
  }
  return -1;
}
 
/*
  writing zeros from the zerobuf to simulate silence,
  perhaps it's enough to use a long var instead of zerobuf ?
*/
void write_zeros (unsigned x)
{
  unsigned l;
 
  while (x) {
    l = min (x, zbuf_size);
    if (write (audio, zerobuf, l) != l) {
      perror (audio_device);
      exit (-1);
    }
    x -= l;
  }
} 

/* if need a SYNC, 
   (is needed if we plan to change speed, stereo ... during output)
*/
inline void sync_dsp(void)
{
  if (ioctl (audio, SNDCTL_DSP_SYNC, NULL) < 0) {
    perror(audio_device);
    exit (-1);
  }
}

/* setting the speed for output */
void set_dsp_speed (int dsp_speed)
{
  if (IOCTL(audio, SNDCTL_DSP_SPEED, dsp_speed) < 0) {
    fprintf (stderr, "%s: unable to set audio speed\n", command);
    perror (audio_device);
    exit (-1);
  }
}

/* setting the samplesize for output */
int set_dsp_samplesize (int samplesize)
{
  int tmps = samplesize;

  IOCTL(audio, SNDCTL_DSP_SAMPLESIZE, tmps);
  if (tmps != samplesize) {
    fprintf (stderr, "%s: unable to set %d bit sample size", 
             command, samplesize);
    if (samplesize == 16) {
      samplesize = 8;
      IOCTL(audio, SNDCTL_DSP_SAMPLESIZE, samplesize);
      if (samplesize != 8) {
        fprintf(stderr, "%s: unable to set 8 bit sample size!\n", command);
        exit (-1);
      }
      fprintf (stderr, "; reducing to 8 bit\n");
      return 8;
    }
    else {
      fprintf (stderr, "\n");
      exit (-1);
    }
  }
  return samplesize;
}

/* if to_mono: 
   mixing 8 bit stereo data, needed if we want play Mono
   this is usefull, if you have SB 1.0 - 2.0 (I have 1.0) and want
   hear the sample (in Mono)
   if to_8:
   compress 16 bit to 8 by using hi-byte; wave-files use signed words,
   so we need to convert it in "unsigned" sample (0x80 is now zero)

   WARNING: this procedure can't compress 16 bit stereo to 16 bit mono,
            because if you have a 16 (or 12) bit card you should have
            stereo (or I'm wrong ???)
   */
inline u_long mix_channels(char *buf, u_long l, char to_mono, char to_8)
{
  register char *w  = buf;
  register char *w2 = buf;
  register int mix;
  register u_long c;

  if (to_mono && to_8) {
    c = l >> 2; ++w2;
    while (c--) {
      mix = *w2;
      w2 += 2;
      mix += *w2;
      w2 += 2;
      *w++ = ((mix + 256) >> 1);
    }
    return (l >> 2);
  }
  else if (to_mono) {
    c = l >> 1;
    while (c--) {
      mix = *w2++ + *w2++;
      *w++ = (mix >> 1);
    }
  }
  else if (to_8) {
    c = l >> 1; ++w2;
    while (c--) {
      *w++ = *w2 + 128;
      w2 += 2;
    }
  }
  return (l >> 1);
}

/*
  return the name of a compression method
 */
char *comp_method(int which)
{
  switch (which) {
    case SB_PCM8:      return "8 bit PCM";
    case SB_PACK1:     return "8-4 ADPCM";
    case SB_PACK2:     return "8-2.6 ADPCM";
    case SB_PACK3:     return "8-2 ADPCM";
    case SB16_PCM16:   return "16 bit PCM";
    case SB16_ALAW:    return "CCITT ALAW";
    case SB16_MULAW:   return "CCITT MULAW";
    case SB16_CTADPCM: return "4 bit CTADPCM";
    default:           return "unknown";
  }
}

/*
  ok, let's play a .voc file
*/ 
void vplay (int fd, int ofs, char *name)
{
  int l, real_l;
  BlockType *bp;
  Voice_data *vd;
  Ext_Block *eb;
  Sb16_Block *sb16;
  u_long nextblock, in_buffer;
  u_char *data = audiobuf;
  char was_extended = 0, output = 0;
  u_short *sp, repeat = 0;
  u_long silence;
  int filepos = 0;
  char one_chn = 0;
  char to_8 = 0;

#define COUNT(x)	nextblock -= x; in_buffer -=x ;data += x

  /* first SYNC the dsp */
  sync_dsp();
 
  if (!quiet_mode) 
    fprintf (stderr, "Playing Creative Labs Voice file ...\n");

  /* first we waste the rest of header, ugly but we don't need seek */
  while (ofs > abuf_size) {	
    read (fd, audiobuf, abuf_size);
    ofs -= abuf_size;
  }
  if (ofs)
    read (fd, audiobuf, ofs);

  /* the real size will be set from the first data block */
  samplesize = 0;

  /* .VOC files are MONO by default */
  dsp_stereo = MODE_MONO;
  IOCTL(audio, SNDCTL_DSP_STEREO, dsp_stereo);

  in_buffer = nextblock = 0;
  while (1) {
    Fill_the_buffer:		/* need this for repeat */
    if ( in_buffer < 32 ) {
      /* move the rest of buffer to pos 0 and fill the audiobuf up */
      if (in_buffer)
        memcpy (audiobuf, data, in_buffer);
      data = audiobuf;
      if ((l = read (fd, audiobuf + in_buffer, abuf_size - in_buffer) ) > 0) 
        in_buffer += l;
      else if (! in_buffer) {
	/* the file is truncated, so simulate 'Terminator' 
           and reduce the datablock for save landing */
        nextblock = audiobuf[0] = 0;
        if (l == -1) {
          perror (name);
          exit (-1);
        }
      }
    }
    while (! nextblock) { 	/* this is a new block */
      bp = (BlockType *)data; COUNT(sizeof (BlockType));
      nextblock = DATALEN(bp);
      if (output && !quiet_mode)
        fprintf (stderr, "\n");	/* write /n after ASCII-out */
      output = 0;
      switch (bp->type) {
        case 0:
          d_printf ("Terminator\n");
          return;		/* VOC-file stop */
        case 1:
          vd = (Voice_data *)data; COUNT(sizeof(Voice_data));
          /* we need a SYNC, before we can set new SPEED, STEREO ... */
          sync_dsp();

          if (samplesize != VOC_SAMPLESIZE) {
	    samplesize = VOC_SAMPLESIZE;
	    set_dsp_samplesize (samplesize);
	    to_8 = 0;
	  }
          if (! was_extended) {
            dsp_speed = (int)(vd->tc);
            dsp_speed = 1000000 / (256 - dsp_speed); 
            d_printf ("Voice data %d Hz\n", dsp_speed);
            if (vd->pack) {	/* /dev/dsp can't it */
              fprintf (stderr, "%s: can't play %s packed .voc files\n",
	               command, comp_method(vd->pack));
              return;
            }
            if (dsp_stereo) {	/* if we are in Stereo-Mode, switch back */
              dsp_stereo = MODE_MONO;
              IOCTL(audio, SNDCTL_DSP_STEREO, dsp_stereo); 
	      one_chn = 0;
            }
          }
          else {		/* there was extended block */
            if (one_chn)	/* if one Stereo fails, why test another ? */
              dsp_stereo = MODE_MONO;
            else if (dsp_stereo) {	/* want Stereo */
              /* shit, my MACRO dosn't work here */
#ifdef SOUND_VERSION
              if (ioctl(audio, SNDCTL_DSP_STEREO, &dsp_stereo) < 0 ||
                  dsp_stereo != MODE_STEREO) {
#else
              if (dsp_stereo != ioctl(audio, SNDCTL_DSP_STEREO, dsp_stereo)) { 
#endif
                dsp_stereo = MODE_MONO; 
                fprintf (stderr, "%s: can't play in Stereo; mixing both channels\n",
                         command);
                one_chn = 1;
              }
            }
            was_extended = 0;
          }
          set_dsp_speed (dsp_speed);
          break;
        case 2:			/* nothing to do, pure data */
          d_printf ("Voice continuation\n");
          break;
        case 3:			/* a silence block, no data, only a count */
          sp = (u_short *)data; COUNT(sizeof(u_short));
          dsp_speed = (int)(*data); COUNT(1);
          dsp_speed = 1000000 / (256 - dsp_speed);
          sync_dsp();
          set_dsp_speed (dsp_speed);
          silence = ( ((u_long)*sp) * 1000) / dsp_speed; 
          d_printf ("Silence for %lu ms\n", silence);
          write_zeros (*sp);
          break;
        case 4:			/* a marker for syncronisation, no effect */
          sp = (u_short *)data; COUNT(sizeof(u_short));
          d_printf ("Marker %d\n", *sp); 
          break;
        case 5:			/* ASCII text, we copy to stderr */
          output = 1;
          d_printf ("ASCII - text :\n");
          break; 
        case 6:			/* repeat marker, says repeatcount */
          /* my specs don't say it: maybe this can be recursive, but
             I don't think somebody use it */
          repeat = *(u_short *)data; COUNT(sizeof(u_short));
          d_printf ("Repeat loop %d times\n", repeat);
          if (filepos >= 0)	/* if < 0, one seek fails, why test another */
            if ( (filepos = lseek (fd, 0, SEEK_CUR)) < 0 ) {
              fprintf(stderr, "%s: can't play loops; %s isn't seekable\n", 
                      command, name);
              repeat = 0;
            }
            else
              filepos -= in_buffer;	/* set filepos after repeat */
          else
            repeat = 0;
          break;
        case 7:			/* ok, lets repeat that be rewinding tape */
          if (repeat) {
            if (repeat != 0xFFFF) {
              d_printf ("Repeat loop %d\n", repeat);
              --repeat;
            }
            else
              d_printf ("Neverending loop\n");
            lseek (fd, filepos, SEEK_SET);
            in_buffer = 0;		/* clear the buffer */
            goto Fill_the_buffer;
          }
          else
            d_printf ("End repeat loop\n");
          break;
        case 8:			/* the extension to play Stereo, I have SB 1.0 :-( */
          was_extended = 1;
          eb = (Ext_Block *)data; COUNT(sizeof(Ext_Block));
          dsp_speed = (int)(eb->tc);
          dsp_speed = 256000000L / (65536 - dsp_speed);
          dsp_stereo = eb->mode;
          if (dsp_stereo == MODE_STEREO) 
            dsp_speed = dsp_speed >> 1;
          if (eb->pack) {     /* /dev/dsp can't it */
            fprintf (stderr, "%s: can't play %s packed .voc files\n",
	             command, comp_method(eb->pack));
            return;
          }
          d_printf ("Extended block %s %d Hz\n", 
                    (eb->mode ? "Stereo" : "Mono"), dsp_speed);
          break;
	case 9:			/* the 16 bit extension */
	  sb16 = (Sb16_Block *)data; COUNT(sizeof(Sb16_Block));
	  dsp_speed = (int)(sb16->rate);
	  if (sb16->pack != SB_PCM8 && sb16->pack != SB16_PCM16) {
	    fprintf (stderr, "%s: can't play %s packed .voc files\n",
	             command, comp_method(sb16->pack));
	    return;
	  }
          if (samplesize != sb16->s_size) {
	    to_8 = 0;
            samplesize = sb16->s_size;
	    if (set_dsp_samplesize(samplesize) != samplesize)
	      to_8 = 1;
          }
	  set_dsp_speed (dsp_speed);
	  switch (sb16->mode) {
	    case 1:
              if (dsp_stereo) {	/* if we are in Stereo-Mode, switch back */
                dsp_stereo = MODE_MONO;
                IOCTL(audio, SNDCTL_DSP_STEREO, dsp_stereo); 
		one_chn = 0;
              }
	      break;
	    case 2:
              if (one_chn)	/* if one Stereo fails, why test another ? */
                dsp_stereo = MODE_MONO;
              else {		/* it wants Stereo */
	        dsp_stereo = MODE_STEREO;
#ifdef SOUND_VERSION
                if (ioctl(audio, SNDCTL_DSP_STEREO, &dsp_stereo) < 0 ||
                    dsp_stereo != MODE_STEREO) {
#else
                if (dsp_stereo != ioctl(audio, SNDCTL_DSP_STEREO, dsp_stereo)) { 
#endif
                  dsp_stereo = MODE_MONO; 
                  fprintf (stderr, "%s: can't play in Stereo; mixing both channels\n",
                           command);
                  one_chn = 1;
                }
              }
              break;
            default:
	      fprintf( stderr, "%s: can't play %d channels!\n",
	               command, sb16->mode);
	      exit(-1);
	  }
	  d_printf ("SB16 Voice data %d bit %s %d Hz\n",
	            samplesize, (sb16->mode == 2 ? "Stereo" : "Mono"),
		    dsp_speed);
	  break;
        default:
          fprintf (stderr, "%s: unknown blocktype %d. terminate.\n", 
                   command, bp->type);
          return;
      } 		/* switch (bp->type) */
    }			/* while (! nextblock)  */
    /* put nextblock data bytes to dsp */
    l = min (in_buffer, nextblock);
    if (l) {  
      if (output && !quiet_mode)
        write (2, data, l);	/* to stderr */
      else {
        real_l = (one_chn || to_8) ? mix_channels(data, l, one_chn, to_8) : l;
        if (write (audio, data, real_l) != real_l) {
          perror (audio_device);
          exit(-1);
        }
      }
      COUNT(l);
    }
  }			/* while(1) */
}
/* that was a big one, perhaps somebody split it :-) */

/* setting the globals for playing raw data */
void init_raw_data(void)
{
  timelimit  = raw_info.timelimit;
  dsp_speed  = raw_info.dsp_speed;
  dsp_stereo = raw_info.dsp_stereo;
  samplesize = raw_info.samplesize;
}

/* calculate the data count to read from/to dsp */
u_long calc_count(void)
{
  u_long count;

  if (!timelimit)
    count = 0x7fffffff;
  else {
    count = timelimit * dsp_speed;
    if (dsp_stereo)
      count *= 2;
    if (samplesize != 8)
      count *= 2;
  }
  return count;
}

/* write a .VOC-header */ 
void start_voc(int fd, u_long cnt)
{
  VocHeader  vh;
  BlockType  bt;
  Voice_data vd;
  Ext_Block  eb;
  Sb16_Block sb;
  int new_version = samplesize != 8 || (dsp_stereo && dsp_speed > 22222);

  strncpy(vh.magic,MAGIC_STRING,20);
  vh.magic[19] = 0x1A;
  vh.headerlen = sizeof(VocHeader);
  
  if (! new_version) {
    vh.version = OLD_VERSION;
    vh.coded_ver = 0x1233 - OLD_VERSION;
    write (fd, &vh, sizeof(VocHeader));

    /* SBPro comes with the block 8 for stereo or high speed data */
    if (dsp_stereo || dsp_speed > 22222) {
      /* write a extended block */
      bt.type = 8;
      bt.datalen = 4;
      bt.datalen_m = bt.datalen_h = 0;
      write (fd, &bt, sizeof(BlockType));
      eb.tc = (u_short)(65536 - 256000000L / (dsp_speed << 1));
      eb.pack = SB_PCM8;
      eb.mode = dsp_stereo;
      write (fd, &eb, sizeof(Ext_Block));
    }
    bt.type = 1;
    cnt += sizeof(Voice_data);	/* Voice_data block follows */
    bt.datalen   = (u_char)  (cnt & 0xFF);
    bt.datalen_m = (u_char)( (cnt & 0xFF00) >> 8 );
    bt.datalen_h = (u_char)( (cnt & 0xFF0000) >> 16 );
    write (fd, &bt, sizeof(BlockType));
    vd.tc = (u_char)(256 - (1000000 / dsp_speed) );
    vd.pack = SB_PCM8;
    write (fd, &vd, sizeof(Voice_data) );
  }
  else if (samplesize == 16 || samplesize == 8) {
    /*
       SB16-drivers makes all things with this new kind of block,
       but for compability with old SB tools we use this block
       only for true SB16 data
    */

    vh.version = ACTUAL_VERSION;
    vh.coded_ver = 0x1233 - ACTUAL_VERSION;
    write (fd, &vh, sizeof(VocHeader));

    bt.type = 9;
    cnt += sizeof(Sb16_Block);  /* Sb16_data block follows */
    bt.datalen   = (u_char)  (cnt & 0xFF);
    bt.datalen_m = (u_char)( (cnt & 0xFF00) >> 8 );
    bt.datalen_h = (u_char)( (cnt & 0xFF0000) >> 16 );
    write (fd, &bt, sizeof(BlockType));
    sb.rate = dsp_speed;
    sb.mode = dsp_stereo ? 2 : 1;
    sb.s_size = samplesize;
    sb.pack = (samplesize == 16) ? SB16_PCM16 : SB_PCM8;
    sb.unknown  = 0;
    sb.unknown2 = 0;
    write (fd, &sb, sizeof(Sb16_Block) );
  }
  else {
    fprintf (stderr, "%s: samplesize %d not supported!\n", command, samplesize);
    exit(-1);
  }
} 

/* write a WAVE-header */
void start_wave(int fd, u_long cnt)
{
  WaveHeader wh;
  DataHeader dt;

  wh.main_chunk = RIFF;
  wh.length     = cnt + sizeof(WaveHeader) - 8; 
  wh.chunk_type = WAVE;
  wh.sub_chunk  = FMT;
  wh.sc_len     = 16;
  wh.format     = PCM_CODE;
  wh.modus      = dsp_stereo ? 2 : 1;
  wh.sample_fq  = dsp_speed;
  wh.byte_p_spl = (samplesize == 8) ? 1 : 2;
  wh.byte_p_sec = dsp_speed * wh.modus * wh.byte_p_spl;
  wh.bit_p_spl  = samplesize;
  dt.data_chunk = DATA;
  dt.data_length= cnt;
  write (fd, &wh, sizeof(WaveHeader));
  write (fd, &dt, sizeof(DataHeader));
}

/* closing .VOC */
void end_voc(int fd)
{
  char dummy = 0;		/* Write a Terminator */
  write (fd, &dummy, 1);
  if (fd != 1)
    close (fd);
}

void end_wave_raw(int fd)
{				/* only close output */
  if (fd != 1)
    close (fd);
}

/* playing/recording raw data, this proc handels WAVE files and
   recording .VOCs (as one block) */ 
void recplay (int fd, int loaded, u_long count, int rtype, char *name)
{
  int l, real_l;
  u_long c, mask;
  char one_chn = 0;
  char to_8 = 0;
  int tmps;

  sync_dsp();
  if (!quiet_mode) {
    fprintf (stderr, "%s %s : ", 
             (direction == PLAY) ? "Playing" : "Recording",
             fmt_rec_table[rtype].what);
    if (samplesize != 8)
      fprintf(stderr, "%d bit, ", samplesize);
    fprintf (stderr, "Speed %d Hz ", dsp_speed);
    fprintf (stderr, "%s ...\n", dsp_stereo ? "Stereo" : "Mono");
  }
  if (set_dsp_samplesize (samplesize) != samplesize)
    to_8 = 1;

#ifdef SOUND_VERSION
  tmps = dsp_stereo;
  if (ioctl (audio, SNDCTL_DSP_STEREO, &tmps) < 0 ||
      tmps != dsp_stereo) {
#else  
  if (dsp_stereo != ioctl (audio, SNDCTL_DSP_STEREO, dsp_stereo) ) {
#endif
    if (direction == PLAY) {
      fprintf (stderr, "%s: can't play in Stereo; mixing both channels\n", 
               command);
      dsp_stereo = MODE_MONO;
      one_chn = 1;
    }
    else {
      fprintf (stderr, "%s: can't record in Stereo\n", command);
      exit (-1);
    }
  }
  set_dsp_speed (dsp_speed);

  /* adjust the count, if wrong */
  mask = 3;
  if (samplesize == 8)
    mask >>= 1;
  if (dsp_stereo == MODE_MONO)
    mask >>= 1;
  count &= ~mask;

  if (direction == PLAY) {
    while (count) {
      c = count;

      if (c > abuf_size)
        c = abuf_size;

      if ((l = read (fd, audiobuf + loaded, c - loaded)) > 0) {
        l += loaded; loaded = 0;	/* correct the count; ugly but ... */
        real_l = (one_chn || to_8) ? mix_channels(audiobuf, l, one_chn, to_8) : l;
        if (write (audio, audiobuf, real_l) != real_l) {
	  perror (audio_device);
	  exit (-1);
	}
        count -= l;
      }
      else {
	if (l == -1) { 
	  perror (name);
          exit (-1);
        }
        count = 0;	/* Stop */
      }
    }			/* while (count) */
  }
  else {		/* we are recording */

    while (count) {
      c = count;
      if (c > abuf_size)
        c = abuf_size;

      if ((l = read (audio, audiobuf, c)) > 0) {
        if (write (fd, audiobuf, l) != l) {
	  perror (name);
	  exit (-1);
	}
        count -= l;
      }

      if (l == -1) {
        perror (audio_device);
        exit (-1);
      }
    }			/* While count */
  }
}

/*
  let's play or record it (record_type says VOC/WAVE/raw)
*/
void record_play(char *name)
{
  int fd, ofs;

  if (direction == PLAY) {
 
    if (!name) {
      fd = 0;
      name = "stdin";
    }
    else if ((fd = open (name, O_RDONLY, 0)) == -1) {
      perror (name);
      exit (-1);
    }
    if (! override) {	/* try to recognize the filetype */
      /* read the file header */
      read (fd, audiobuf, sizeof(VocHeader));
      if ( (ofs = test_vocfile (audiobuf) ) >= 0) 
        vplay (fd, ofs, name);
      else {
        /* read bytes for WAVE-header */
        read (fd,  audiobuf + sizeof(VocHeader), 
              sizeof(WaveHeader) - sizeof(VocHeader) );
        if (test_wavefile (audiobuf, fd) >= 0)
          recplay (fd, 0, count, WAVE_FMT, name);
        else /* unknown type */
          goto play_raw;
      }
    }
    else {	/* ignore the header */
play_raw:
      init_raw_data();
      count = calc_count();
      recplay (fd, sizeof(WaveHeader), count, RAW_DATA, name);
    }
    if (fd != 0)
      close(fd);
  }
  else {		/* recording ... */
    if (!name) {
      fd = 1;
      name = "stdout";
    }
    else {
      if ((fd = open (name, O_WRONLY | O_CREAT, 0666)) == -1) {
        perror (name);
        exit (-1);
      }
    }
    count = calc_count() & 0xFFFFFFFE;
    /* WAVE-file should be even (I'm not sure), but wasting one byte
       isn't a problem (this can only be in 8 bit mono) */
    if (fmt_rec_table[record_type].start)
      fmt_rec_table[record_type].start(fd, count);
    recplay (fd, 0, count, record_type, name);
    fmt_rec_table[record_type].end(fd);
  }
} 

