/*
 * Copyright (c) 1995 Sun Microsystems, Inc.
 * All rights reserved.
 * 
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and its documentation for any purpose, provided that the
 * above copyright notice and the following two paragraphs appear in
 * all copies of this software.
 * 
 * IN NO EVENT SHALL SUN MICROSYSTEMS, INC. BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
 * OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF SUN
 * MICROSYSTEMS, INC. HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * SUN MICROSYSTEMS, INC. SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  THE SOFTWARE PROVIDED
 * HEREUNDER IS ON AN "AS IS" BASIS, AND SUN MICROSYSTEMS, INC. HAS NO
 * OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
 * MODIFICATIONS.
 */

#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/audioio.h>
#include <sys/conf.h>
#include <sys/types.h>
#include <stropts.h>
#include <thread.h>

#include <tcl.h>
#include <tk.h>
#include "doomarena.h"


/*
 * Define an on-disk audio file header.
 *
 * This structure should not be arbitrarily imposed over a stream of bytes,
 * since the byte orders could be wrong.
 *
 * Note that there is an 'info' field that immediately follows this
 * structure in the file.
 *
 * The hdr_size field is problematic in the general case because the
 * field is really "data location", which does not ensure that all
 * the bytes between the header and the data are really 'info'.
 * Further, there are no absolute guarantees that the 'info' is ASCII text,
 * (non-ASCII info may eventually be far more useful anyway).
 *
 * When audio files are passed through pipes, the 'data_size' field may
 * not be known in advance.  In such cases, the 'data_size' should be
 * set to AUDIO_UNKNOWN_SIZE.
 */
typedef struct {
	ulong_t		magic;		/* magic number */
	ulong_t		hdr_size;	/* size of this header */
	ulong_t		data_size;	/* length of data (optional) */
	ulong_t		encoding;	/* data encoding format */
	ulong_t		sample_rate;	/* samples per second */
	ulong_t		channels;	/* number of interleaved channels */
} Audio_filehdr;


/* Define the magic number */
#define	AUDIO_FILE_MAGIC		((ulong_t)0x2e736e64)

/* Define the encoding fields */
#define	AUDIO_FILE_ENCODING_MULAW_8	(1)	/* 8-bit ISDN u-law */
#define	AUDIO_FILE_ENCODING_LINEAR_8	(2)	/* 8-bit linear PCM */
#define	AUDIO_FILE_ENCODING_LINEAR_16	(3)	/* 16-bit linear PCM */
#define	AUDIO_FILE_ENCODING_LINEAR_24	(4)	/* 24-bit linear PCM */
#define	AUDIO_FILE_ENCODING_LINEAR_32	(5)	/* 32-bit linear PCM */
#define	AUDIO_FILE_ENCODING_FLOAT	(6)	/* 32-bit IEEE floating point */
#define	AUDIO_FILE_ENCODING_DOUBLE	(7)	/* 64-bit IEEE floating point */
#define	AUDIO_FILE_ENCODING_ADPCM_G721	(23)	/* 4-bit CCITT g.721 ADPCM */
#define	AUDIO_FILE_ENCODING_ADPCM_G722	(24)	/* CCITT g.722 ADPCM */
#define	AUDIO_FILE_ENCODING_ADPCM_G723_3 (25)	/* CCITT g.723 3-bit ADPCM */
#define	AUDIO_FILE_ENCODING_ADPCM_G723_5 (26)	/* CCITT g.723 5-bit ADPCM */
#define AUDIO_FILE_ENCODING_ALAW_8	(27) 	/* 8-bit ISDN A-law */




static char *
encoding_to_string(
    unsigned int file_encoding,
    unsigned int *sample_size
)
{
    unsigned int junk;
    if ( sample_size == NULL)
	sample_size= &junk;
    switch (file_encoding)  {
    case AUDIO_FILE_ENCODING_MULAW_8:
	*sample_size= 8;
	return "ulaw";
    case AUDIO_FILE_ENCODING_ALAW_8:
	*sample_size= 8;
	return "alaw";
    case AUDIO_FILE_ENCODING_LINEAR_8:
	*sample_size= 8;
	return "PCM";
    case AUDIO_FILE_ENCODING_LINEAR_16:
	*sample_size= 16;
	return "PCM";
    case AUDIO_FILE_ENCODING_LINEAR_24:
	*sample_size= 24;
	return "PCM";
    case AUDIO_FILE_ENCODING_LINEAR_32:
	*sample_size= 32;
	return "PCM";
    case AUDIO_FILE_ENCODING_FLOAT:
	*sample_size= 32;
	return "float";
    case AUDIO_FILE_ENCODING_DOUBLE:
	*sample_size= 64;
	return "double";
    case AUDIO_FILE_ENCODING_ADPCM_G721:
	*sample_size= 4;
	return "ADPCM g.721";
    case AUDIO_FILE_ENCODING_ADPCM_G722:
	*sample_size= 4;
	return "ADPCM g.722";
    case AUDIO_FILE_ENCODING_ADPCM_G723_3:
	*sample_size= 3;
	return "ADPCM g.723 3-bit";
    case AUDIO_FILE_ENCODING_ADPCM_G723_5:
	*sample_size= 5;
	return "ADPCM g.723 5-bit";
    }
    *sample_size= 0;
    return "unknown";
}


#define BUFFER_SIZE 4096

typedef struct {
    FILE *file;
    int audio;
    int close_file;
    uint_t restore_volume;
} AudioFD;
 
static void *
audioplay_thread(void *arg)
{
    AudioFD *afd = (AudioFD *)arg;
    char buf[BUFFER_SIZE];
    int n_read, rc;
 
    while ( ( n_read= fread( buf, 1, BUFFER_SIZE, afd->file)) > 0)  {
	int rc= write( afd->audio, buf, n_read);
	if ( rc <= 0)
	    break;
    }
 
    (void) ioctl( afd->audio, AUDIO_DRAIN, NULL);

    if ( afd->restore_volume != 0xffffffff)  {
	audio_info_t a_reset;
	AUDIO_INITINFO( &a_reset);
	a_reset.play.gain= afd->restore_volume;
	(void) ioctl( afd->audio, AUDIO_SETINFO, &a_reset);
    }
    close( afd->audio);
    if ( afd->close_file)
	fclose( afd->file);
    free(afd);
    return 0;
}

static int
Audio_PlayCmd(
     ClientData clientData,
     Tcl_Interp *interp,
     int argc,
     char **argv
)
{
    static char *options= "?-bell? ?-block? ?-sync? ?-volume percent?";
    Audio_filehdr header;
    audio_info_t a_info;
    audio_info_t vol_info;
    int audiofd, rc, filefd= 0;
    int bell, sync, block, flags, volume;
    FILE *file;
    size_t n_read;
    unsigned char buffer[BUFFER_SIZE];
    AudioFD *afd;
    char **arg;

    if ( argc < 3)  {
	sprintf( interp->result,
		"wrong # args: must be \"%s %s %s file-id|path\"",
		argv[0], argv[1], options);
	return TCL_ERROR;
    }

    bell = 0;
    sync = 0;
    block = 0;
    volume = 100;
 
    for ( arg= argv+2; *arg != NULL; arg++)  {
	if ( **arg != '-')
	    break;

	if (!strcmp(*arg, "-bell"))
	    bell = 1;
	else if (!strcmp(*arg, "-sync"))
	    sync = 1;
	else if (!strcmp(*arg, "-block"))
	    block = 1;
	else if (!strcmp(*arg, "-volume"))  {
	    arg++;
	    if ( ! *arg)  {
		interp->result= "missing arg for -volume option";
		return TCL_ERROR;
	    }
	    if ( Tcl_GetInt( interp, *arg, &volume) != TCL_OK)
		return TCL_ERROR;
	    if ( volume < 0 || volume > 200)  {
		interp->result= "volume out of range; must be >= 0 and <= 200";
		return TCL_ERROR;
	    }
	}
	else {
	    sprintf(interp->result,
		"illegal option \"%s\": must be -bell, -block, -sync, or -volume", *arg);
	    return TCL_ERROR;
	}
    }

    if ( sscanf( *arg, "file%d", &filefd) == 1)  {
	if ( Tcl_GetOpenFile( interp, *arg, 1, 0, &file) == TCL_ERROR)
	    return TCL_ERROR;
        rewind( file);
    }
    else {
	file= fopen( *arg, "r");
	if ( file == NULL)  {
	    interp->result= "couldn't open audio file";
	    return TCL_ERROR;
	}
    }

    rc= fread( &header, 1, sizeof( header), file);
    if ( rc != sizeof( header))  {
	interp->result= "audio file shorter than minimum header";
	return TCL_ERROR;
    }
    if ( header.magic != AUDIO_FILE_MAGIC)  {
	interp->result= "audio file not magic";
	return TCL_ERROR;
    }

    if ( fseek( file, header.hdr_size, SEEK_SET) == -1)  {
	interp->result= "audio file seek to data failed";
	return TCL_ERROR;
    }

    flags= O_WRONLY;
    if ( !block)
	flags |= O_NDELAY;

    audiofd= open( "/dev/audio", flags);
    if ( audiofd == -1)  {
    AudioError:
	if ( bell)  {
	    XBell(Tk_Display(Tk_MainWindow(interp)), 0);
	    XFlush(Tk_Display(Tk_MainWindow(interp)));
	    if ( filefd == 0)  fclose( file);
	    return TCL_OK;
	}
	if ( filefd == 0)  fclose( file);
/*	interp->result= "can't open audio device"; */
/*        return TCL_ERROR; */
	return TCL_OK;
    }

    AUDIO_INITINFO(&a_info);
    AUDIO_INITINFO(&vol_info);

    switch ( header.encoding)  {
    case AUDIO_FILE_ENCODING_MULAW_8:
	a_info.play.encoding= AUDIO_ENCODING_ULAW;
	a_info.play.precision= 8;
	break;
    case AUDIO_FILE_ENCODING_ALAW_8:
	a_info.play.encoding= AUDIO_ENCODING_ALAW;
	a_info.play.precision= 8;
	break;
    case AUDIO_FILE_ENCODING_LINEAR_8:
	a_info.play.encoding= AUDIO_ENCODING_LINEAR;
	a_info.play.precision= 8;
	break;
    case AUDIO_FILE_ENCODING_LINEAR_16:
	a_info.play.encoding= AUDIO_ENCODING_LINEAR;
	a_info.play.precision= 16;
	break;
    case AUDIO_FILE_ENCODING_LINEAR_24:
	a_info.play.encoding= AUDIO_ENCODING_LINEAR;
	a_info.play.precision= 24;
	break;
    case AUDIO_FILE_ENCODING_LINEAR_32:
	a_info.play.encoding= AUDIO_ENCODING_LINEAR;
	a_info.play.precision= 32;
	break;
    default:
	close( audiofd);
	goto AudioError;

/*        interp->result= "Unsupported audio encoding"; */
/*        return TCL_ERROR; */
    }

    a_info.play.channels= header.channels;
    a_info.play.sample_rate= header.sample_rate;

    if ( a_info.play.sample_rate == 8012)
	a_info.play.sample_rate= 8000;

    if ( volume != 100)  {
	if ( ioctl( audiofd, AUDIO_GETINFO, &vol_info) >= 0)  {
	    if ( ! vol_info.output_muted)  {
		a_info.play.gain= (vol_info.play.gain * volume) / 100;
		if ( a_info.play.gain > AUDIO_MAX_GAIN)
		    a_info.play.gain= AUDIO_MAX_GAIN;
	    }
	    else
		vol_info.play.gain= 0xffffffff;
	}
    }

    if ( ioctl( audiofd, AUDIO_SETINFO, &a_info) < 0)  {
	close( audiofd);
	goto AudioError;

/*	interp->result= "AUDIO_SETINFO failed"; */
/*       return TCL_ERROR; */
    }

    afd = (AudioFD *)malloc(sizeof(AudioFD));
    afd->file= file;
    afd->audio= audiofd;
    afd->close_file= (filefd == 0);
    afd->restore_volume= vol_info.play.gain;

    if ( sync)
	audioplay_thread( afd);
    else {
      sigset_t orig_mask, no_input_mask;
      sigfillset(&no_input_mask);
      thr_sigsetmask(SIG_SETMASK, &no_input_mask, &orig_mask);
      thr_create(NULL, 0, audioplay_thread, afd,
		 THR_DETACHED | THR_DAEMON, NULL);
      thr_sigsetmask(SIG_SETMASK, &orig_mask, NULL);
    }

    return TCL_OK;
}

static int
Audio_InfoCmd(
     ClientData clientData,
     Tcl_Interp *interp,
     int argc,
     char **argv
)
{
    Audio_filehdr header;
    int rc, filefd= 0;
    unsigned int sample_size;
    FILE *file;
    char *encoding;

    if ( argc != 3)  {
	sprintf( interp->result, "wrong # args: must be \"%s %s file-id|path\"",
		argv[0], argv[1]);
	return TCL_ERROR;
    }

    if ( sscanf( argv[2], "file%d", &filefd) == 1)  {
	if ( Tcl_GetOpenFile( interp, argv[2], 1, 0, &file) == TCL_ERROR)
	    return TCL_ERROR;
        rewind( file);
    }
    else {
	file= fopen( argv[2], "r");
	if ( file == NULL)  {
	    interp->result= "couldn't open audio file";
	    return TCL_ERROR;
	}
    }

    rc= fread( &header, 1, sizeof( header), file);
    if ( rc != sizeof( header))  {
	interp->result= "audio file shorter than minimum header";
	return TCL_ERROR;
    }
    if ( header.magic != AUDIO_FILE_MAGIC)  {
	interp->result= "audio file not magic";
	return TCL_ERROR;
    }

    encoding= encoding_to_string(header.encoding, &sample_size);

    sprintf( interp->result, "%s %dbit %dHz %s", encoding, sample_size,
	header.sample_rate, (header.channels == 1) ? "mono" : "stereo");

    if ( filefd == 0)
	fclose( file);
    return TCL_OK;
}

static int
Audio_DevTypeCmd(
     ClientData clientData,
     Tcl_Interp *interp,
     int argc,
     char **argv
)
{
    int audiofd;
    struct audio_device a_device;
    audiofd= open( "/dev/audio", O_RDONLY);
    if ( audiofd == -1)  {
	interp->result= "failure";
	return TCL_ERROR;
    }
    if ( ioctl( audiofd, AUDIO_GETDEV, &a_device) < 0)  {
	close( audiofd);
	interp->result= "AUDIO_SETINFO failed";
        return TCL_ERROR;
    }
    sprintf( interp->result, "%s %s %s", a_device.name, a_device.version,
	a_device.config);
    close( audiofd);
    return TCL_OK;
}

static char *audio_trace_command = NULL;
static int audioctl_fd = 0;
static int audio_trace_fd[2] = {0, 0};

static void
AudioTrace(ClientData clientData, int mask)
{
  Tcl_Interp *interp = (Tcl_Interp *)clientData;
  Tcl_DString dstr;
  Tcl_CmdInfo info;
  int status;
  audio_info_t get_info;
  char byte;
  char buf[128];
  char *argv[3];

  if (mask == TK_READABLE)
    read(audio_trace_fd[1], &byte, 1);

  if (!*audio_trace_command)
    return;

  if (!Tcl_GetCommandInfo(interp, audio_trace_command, &info)) {
    sprintf(interp->result, "Tcl_GetCommandInfo %s failed.",
	    audio_trace_command);
    Tk_BackgroundError(interp);
    return;
  }

  while ((status = ioctl(audioctl_fd, AUDIO_GETINFO, &get_info)) == -1 &&
	 errno == EINTR);
  if (status == -1) {
    SysError(interp, "audio", "trace");
    Tk_BackgroundError(interp);
    return;
  }

  Tcl_DStringInit(&dstr);
  sprintf(buf, "gain %d", get_info.play.gain);
  Tcl_DStringAppendElement(&dstr, buf);
  sprintf(buf, "balance %d", get_info.play.balance);
  Tcl_DStringAppendElement(&dstr, buf);
  sprintf(buf, "mute %d", get_info.output_muted);
  Tcl_DStringAppendElement(&dstr, buf);
  sprintf(buf, "speaker %d",
	  (get_info.play.port & AUDIO_SPEAKER) ? 1 : 0);
  Tcl_DStringAppendElement(&dstr, buf);
  sprintf(buf, "headphone %d",
	  (get_info.play.port & AUDIO_HEADPHONE) ? 1 : 0);
  Tcl_DStringAppendElement(&dstr, buf);
  sprintf(buf, "lineout %d",
	  (get_info.play.port & AUDIO_LINE_OUT) ? 1 : 0);
  Tcl_DStringAppendElement(&dstr, buf);

  argv[0] = audio_trace_command;
  argv[1] = Tcl_DStringValue(&dstr);
  argv[2] = NULL;
  (*info.proc)(info.clientData, interp, 2, argv);

  Tcl_DStringFree(&dstr);
}

static void
AudioPoll(int dummy)
{
  char byte = 1;
  if (audio_trace_fd[0]) {
    write(audio_trace_fd[0], &byte, 1);
  }
}

static int
Audio_ControlCmd(
     ClientData clientData,
     Tcl_Interp *interp,
     int argc,
     char **argv
)
{
  audio_info_t set_info;
  audio_info_t get_info;
  int i, status, diff, bool, mute, balance;

  if (!audioctl_fd) {
    if ((audioctl_fd = open("/dev/audioctl", O_RDONLY)) == -1) {
      audioctl_fd = 0;
      return SysError(interp, argv[0], "open /dev/audioctl");
    }
  }

  if (argc < 3) {
    sprintf(interp->result,
	    "wrong # args: must be \"%s %s option ?args?\"",
	    argv[0], argv[1]);
    return TCL_ERROR;
  }

  if (!strcmp(argv[2], "get")) {
    if (argc != 4) {
      sprintf(interp->result,
	      "wrong # args: must be \"%s %s %s arg\"",
	      argv[0], argv[1], argv[2]);
      return TCL_ERROR;
    }

    while ((status = ioctl(audioctl_fd, AUDIO_GETINFO, &get_info)) == -1 &&
	   errno == EINTR);
    if (status == -1) {
      return SysError(interp, argv[0], "getinfo");
    }

    if (!strcmp(argv[3], "gain")) {
      sprintf(interp->result, "%d", get_info.play.gain);
    }
    else if (!strcmp(argv[3], "balance")) {
      sprintf(interp->result, "%d", get_info.play.balance);
    }
    else if (!strcmp(argv[3], "mute")) {
      sprintf(interp->result, "%d", get_info.output_muted);
    }
    else if (!strcmp(argv[3], "speaker")) {
      interp->result = (get_info.play.port & AUDIO_SPEAKER) ? "1" : "0";
    }
    else if (!strcmp(argv[3], "headphone")) {
      interp->result = (get_info.play.port & AUDIO_HEADPHONE) ? "1" : "0";
    }
    else if (!strcmp(argv[3], "lineout")) {
      interp->result = (get_info.play.port & AUDIO_LINE_OUT) ? "1" : "0";
    }
    else {
      sprintf(interp->result, "bad option \"%s\": must be "
	      "gain, balance, mute, speaker, headphone, or lineout",
	      argv[3]);
      return TCL_ERROR;
    }
  }

  else if (!strcmp(argv[2], "set")) {
    if (argc != 5) {
      sprintf(interp->result,
	      "wrong # args: must be \"%s %s %s arg value\"",
	      argv[0], argv[1], argv[2]);
      return TCL_ERROR;
    }

    diff = 0;
    AUDIO_INITINFO(&set_info);
    while ((status = ioctl(audioctl_fd, AUDIO_GETINFO, &get_info)) == -1 &&
	   errno == EINTR);
    if (status == -1) {
      return SysError(interp, argv[0], "getinfo");
    }

    if (!strcmp(argv[3], "gain")) {
      if (Tcl_GetInt(interp, argv[4],
		     (int *)&set_info.play.gain) != TCL_OK)
	return TCL_ERROR;
      if (set_info.play.gain != get_info.play.gain)
	diff = 1;
    }
    else if (!strcmp(argv[3], "balance")) {
      if (Tcl_GetInt(interp, argv[4], &balance) != TCL_OK)
	return TCL_ERROR;
      set_info.play.balance = (unsigned char)balance;
      if (set_info.play.balance != get_info.play.balance)
	diff = 1;
    }
    else if (!strcmp(argv[3], "mute")) {
      if (Tcl_GetBoolean(interp, argv[4], &mute) != TCL_OK)
	return TCL_ERROR;
      set_info.output_muted = (unsigned char)mute;
      if (set_info.output_muted != get_info.output_muted)
	diff = 1;
    }
    else if (!strcmp(argv[3], "speaker")) {
      if (Tcl_GetBoolean(interp, argv[4], &bool) != TCL_OK)
	return TCL_ERROR;

      if (bool) {
	if (!(get_info.play.port & AUDIO_SPEAKER)) {
	  set_info.play.port = get_info.play.port | AUDIO_SPEAKER;
	  diff = 1;
	}
      }
      else {
	if (get_info.play.port & AUDIO_SPEAKER) {
	  set_info.play.port = get_info.play.port & ~AUDIO_SPEAKER;
	  diff = 1;
	}
      }
    }
    else if (!strcmp(argv[3], "headphone")) {
      if (Tcl_GetBoolean(interp, argv[4], &bool) != TCL_OK)
	return TCL_ERROR;

      if (bool) {
	if (!(get_info.play.port & AUDIO_HEADPHONE)) {
	  set_info.play.port = get_info.play.port | AUDIO_HEADPHONE;
	  diff = 1;
	}
      }
      else {
	if (get_info.play.port & AUDIO_HEADPHONE) {
	  set_info.play.port = get_info.play.port & ~AUDIO_HEADPHONE;
	  diff = 1;
	}
      }
    }
    else if (!strcmp(argv[3], "lineout")) {
      if (Tcl_GetBoolean(interp, argv[4], &bool) != TCL_OK)
	return TCL_ERROR;

      if (bool) {
	if (!(get_info.play.port & AUDIO_LINE_OUT)) {
	  set_info.play.port = get_info.play.port | AUDIO_LINE_OUT;
	  diff = 1;
	}
      }
      else {
	if (get_info.play.port & AUDIO_LINE_OUT) {
	  set_info.play.port = get_info.play.port & ~AUDIO_LINE_OUT;
	  diff = 1;
	}
      }
    }
    else {
      sprintf(interp->result, "bad option \"%s\": must be "
	      "gain, balance, mute, speaker, headphone, or lineout",
	      argv[3]);
      return TCL_ERROR;
    }

    if (diff) {
      while ((status = ioctl(audioctl_fd, AUDIO_SETINFO, &set_info)) == -1 &&
	     errno == EINTR);
      if (status == -1) {
	return SysError(interp, argv[0], "setinfo");
      }
    }
  }
  else if (!strcmp(argv[2], "trace")) {
    if (argc != 3 && argc != 4) {
      sprintf(interp->result,
	      "wrong # arg: must be \"%s %s %s ?command?\"",
	      argv[0], argv[1], argv[2]);
      return TCL_ERROR;
    }

    if (!audio_trace_command)
      audio_trace_command = strdup("");

    if (argc == 3) {
      sprintf(interp->result, "%s", audio_trace_command);
    }
    else {
      free(audio_trace_command);
      audio_trace_command = strdup(argv[3]);
      if (*audio_trace_command) {
	if (!audio_trace_fd[0]) {
	  if (pipe(audio_trace_fd) == -1)
	    return SysError(interp, argv[0], "pipe");

	  Tk_CreateFileHandler(audio_trace_fd[1], TK_READABLE,
			       AudioTrace, (ClientData)interp);
	  sigset(SIGPOLL, AudioPoll);
	  ioctl(audioctl_fd, I_SETSIG, S_MSG);
	  AudioTrace((ClientData)interp, 0);
	}
      } else {
	if (audio_trace_fd[0]) {
	  Tk_DeleteFileHandler(audio_trace_fd[1]);
	  close(audio_trace_fd[0]);
	  close(audio_trace_fd[1]);
	  audio_trace_fd[0] = 0;
	  audio_trace_fd[1] = 0;
	  sigset(SIGPOLL, SIG_IGN);
	  ioctl(audioctl_fd, I_SETSIG, 0);
	}
      }      
    }
  }
  else {
    sprintf(interp->result,
	    "bad option \"%s\": must be get, set, or trace",
	    argv[2]);
    return TCL_ERROR;
  }

  return TCL_OK;
}

int
AudioCmd(
     ClientData clientData,
     Tcl_Interp *interp,
     int argc,
     char **argv
)
{
    static char *options= "play info devtype";

    if ( argc == 1)  {
	sprintf( interp->result, "wrong # args: must be \"%s option ?args?\"",
		argv[0]);
	return TCL_ERROR;
    }

    if ( strcmp( argv[1], "play") == 0)  {
	return Audio_PlayCmd( clientData, interp, argc, argv);
    }
    else if ( strcmp( argv[1], "info") == 0)  {
	return Audio_InfoCmd( clientData, interp, argc, argv);
    }
    else if ( strcmp( argv[1], "devtype") == 0)  {
	return Audio_DevTypeCmd( clientData, interp, argc, argv);
    }
    else if ( strcmp( argv[1], "control") == 0)  {
	return Audio_ControlCmd( clientData, interp, argc, argv);
    }

    sprintf( interp->result, "unknown option \"%s\": must be one of %s",
		argv[1], options);
    TCL_ERROR;
}
