/********************************************************************************
*										*
*	Copyright (c) 1993-1994							*
*	Spoken Language Systems Group						*
*	Laboratory for Computer Science						*
*	Massachusetts Institute of Technology					*
*										*
*	FILE: linux_adio.c -- LINUX portion of the ADIO library			*
*										*
********************************************************************************/
/*
  This code implements the interface to the Linux's Voxware audio interface,
  under ADIO functionality.

  Written by Jon Yi

  July 2, 1996
*/

#include <stdio.h>
#include <stdlib.h>
#ifndef __FreeBSD__
#include <malloc.h>
#endif
#include <sys/ioctl.h>
#ifdef __FreeBSD__
#include <machine/soundcard.h>
#else
#include <linux/soundcard.h>
#endif
#include <errno.h>
#include <unistd.h>
#include <math.h>
#include <memory.h>
#include <assert.h>
#include <fcntl.h>

#include "sls/util.h"
#include "sls/types.h"
#include "sls/chg.h"
#include "sls/adio.h"
#include "sls/socket.h"
#include "sls/linux_adio.h"

#undef  MAX
#undef  MIN
#undef  ABS
#define MAX(a,b)	((a) > (b) ? (a) : (b))
#define MIN(a,b)	((a) < (b) ? (a) : (b))
#define ABS(x)		((x) < 0 ? -(x) : (x))
#define LENGTH(x)	(sizeof(x) / sizeof(*x))

static const char *linux_device_names[] = SOUND_DEVICE_NAMES;

LINUX_ADIO **linux_adios = NULL;		/* this maps descriptors to LINUX_ADIO's */

static LINUX_ADIO *linux_adio_i_lookup_fd(int fd_adio, char *fcn_name);

static void dsp_reset(int fd)
{
  printf("skipping reset\n");
/* reset is suppressed, because it is being called at an inappropriate time:
   it resets some earlier ioctl calls to the driver.
  if(ioctl(fd, SNDCTL_DSP_RESET, NULL) < 0)
    sls_warn("dsp_sync(): Unable to sync the DSP device!");
*/
}

static void dsp_sync(int fd)
{
  if(ioctl(fd, SNDCTL_DSP_SYNC, NULL) < 0)
    sls_warn("dsp_sync(): Unable to sync the DSP device!");
}

static void dsp_post(int fd)
{
  if(ioctl(fd, SNDCTL_DSP_POST, NULL) < 0)
    sls_warn("dsp_post(): Unable to post the DSP device!");
}

static void dsp_settrigger(int fd, int trigger)
{
  if(ioctl(fd, SNDCTL_DSP_SETTRIGGER, (char *)&trigger) < 0)
    sls_warn("dsp_settrigger(): Unable to settrigger the DSP device!");
}

static int dsp_gettrigger(int fd)
{
  int trigger;
  if(ioctl(fd, SNDCTL_DSP_GETTRIGGER, (char *)&trigger) < 0) {
    sls_warn("dsp_gettrigger(): Unable to gettrigger the DSP device!");
    return 0;
  }
  return trigger;
}

static void dsp_start_recording(int fd, int has_trigger, int is_hdx)
{
  int trigger = 0;
  char buf[65536]; /* size is arbitrary */

  /* turn off recording */
  if (has_trigger) {
  	trigger = dsp_gettrigger(fd);
  	dsp_settrigger(fd, trigger & ~PCM_ENABLE_INPUT);
	}

  /* drain recording buffer by reading all samples -- this doesn't block */
  while (read(fd, buf, sizeof(buf)) > 0)
    ;

  /* turn on recording */
  if (has_trigger) {
	if (is_hdx) trigger &= ~PCM_ENABLE_OUTPUT;
  	dsp_settrigger(fd, trigger |  PCM_ENABLE_INPUT);
  }
}

static void dsp_stop_recording(int fd, int has_trigger)
{
  if (has_trigger) dsp_settrigger(fd, 
	(dsp_gettrigger(fd) & ~PCM_ENABLE_INPUT) | PCM_ENABLE_OUTPUT);
}

static int dsp_set_sample_rate(int fd, int sample_rate)
{
  dsp_sync(fd);

  if(ioctl(fd, SNDCTL_DSP_SPEED, (char *)&sample_rate) < 0) {
    sls_warn("dsp_set_sample_rate(): Unable to set sample_rate to %d!",
	     sample_rate);
    return(-1);
  }
  return(0);
}

static int dsp_set_sample_type(int fd, int sample_size, int linear)
{
  int format;

  dsp_sync(fd);

  format = 0;
  if(!linear)
    format |= AFMT_MU_LAW;
  if(sample_size == 8)
    format |= AFMT_U8;
  else if(sample_size == 16)
    format |= AFMT_S16_LE;
  else {
    sls_warn("dsp_set_sample_type(): Invalid number of bits: %d", sample_size);
    return(-1);
  }
    
  if(ioctl(fd, SNDCTL_DSP_SETFMT, (char *)&format) < 0) {
    sls_warn("dsp_set_sample_type(): Unable to set sample_size to %d bits and type to %s!",
	     sample_size, linear ? "linear" : "u-law");
    return(-1);
  }
  return(0);
}

static int dsp_set_stereo(int fd, int stereo)
{
  dsp_sync(fd);

  if(ioctl(fd, SNDCTL_DSP_STEREO, (char *)&stereo) < 0) {
    if(stereo)
      sls_warn("dsp_set_stereo(): Unable to set to stereo!");
    else
      sls_warn("dsp_set_stereo(): Unable to set to mono!");
    return(-1);
  }
  return(0);
}

static int mixer_set_record_port(int fd, int port)
{
  int recsrc;

  recsrc = (1 << port);
  if(ioctl(fd, SOUND_MIXER_WRITE_RECSRC, (char *)&port) < 0) {
    sls_warn("mixer_set_record_port(): Unable to set record port to %s!",
	     linux_device_names[port]);
    return(-1);
  }
  return(0);
}

#if 0

static int mixer_get_record_port(int fd, int *port)
{
  if(ioctl(fd, SOUND_MIXER_READ_RECSRC, (char *)port) < 0) {
    sls_warn("mixer_get_record_port(): Unable to get record port to %s!");
    return(-1);
  }
  return(0);
}

static int mixer_get_play_gain(int fd, int *gain)
{
  if(ioctl(fd, SOUND_MIXER_READ_VOLUME, (char *)gain) < 0) {
    sls_warn("mixer_get_play_gain(): Unable to get play_gain");
    return(-1);
  }
  *gain &= 0x7F;
  return(0);
}

static int mixer_get_record_gain(int fd, int *gain)
{
  int recsrc, bit;

  if(mixer_get_record_port(fd, &recsrc) == -1)
    return(-1);
  for(bit=0;recsrc!=1;bit++)
    recsrc /= 2;
  recsrc = bit;

  if(ioctl(fd, MIXER_READ(recsrc), (char *)gain) < 0) {
    sls_warn("mixer_get_record_gain(): Unable to get record_gain for %s!",
	     linux_device_names[recsrc]);
    return(-1);
  }
  *gain &= 0x7F;
  return(0);
}

static int mixer_set_play_gain(int fd, int *gain)
{
  int stereogain;

  stereogain = ((*gain & 0x7F) << 8) | (*gain & 0x7F);
  if(ioctl(fd, SOUND_MIXER_WRITE_VOLUME, (char *)&stereogain) < 0) {
    sls_warn("mixer_set_play_gain(): Unable to set play_gain to %d!", *gain);
    return(-1);
  }
  return(0);
}

static int mixer_set_record_gain(int fd, int *gain)
{
  int recsrc, bit, stereogain;

  if(mixer_get_record_port(fd, &recsrc) == -1)
    return(-1);
  for(bit=0;recsrc!=1;bit++)
    recsrc /= 2;
  recsrc = bit;

  stereogain = ((*gain & 0x7F) << 8) | (*gain & 0x7F);
  if(ioctl(fd, MIXER_WRITE(recsrc), (char *)&stereogain) < 0) {
    sls_warn("mixer_set_record_gain(): Unable to set record_gain for %d to %s!",
	     linux_device_names[recsrc], *gain);
    return(-1);
  }
  return(0);
}

#endif

int
linux_adio_open(bnum_UNUSED)
  int bnum_UNUSED;
{
  int fd, mixer_fd, status, adio_fd, trigger, hdx;
  int abuf_size, flags;
  extern int sys_nerr;
  LINUX_ADIO *linux_adio = NULL;

  /* Find an available descriptor slot */
  if (linux_adios == NULL)
    linux_adios = (LINUX_ADIO **) calloc(ADIO_MAX_FD, sizeof(LINUX_ADIO *));

  for(adio_fd=0;adio_fd<ADIO_MAX_FD;adio_fd++)
    if (linux_adios[adio_fd] == NULL) break;

  if (adio_fd == ADIO_MAX_FD) {
    sls_warn("linux_adio_open(): FATAL ERROR too many adio's open (%d)", ADIO_MAX_FD);
    return(-1);
  }

  /* Try and open /dev/mixer.  If the device is already busy,
     open will set errno to EBUSY and we'll return -1. */
  if((mixer_fd = open("/dev/mixer", O_RDWR)) == -1) {
    /* open(/dev/mixer) failed. */
    sls_warn("linux_adio_open(): open failed for /dev/mixer with error# %d",
	     errno);
    if(errno < sys_nerr) 
      sls_warn("\t%s", sys_errlist[errno]);
  }

  /* Try and open /dev/dsp.  If the device is already busy,
     open will set errno to EBUSY and we'll return -1. */
  if((fd = open("/dev/dsp", O_RDWR | O_NDELAY)) == -1) {
    /* Backoff to write-only - Solve this later */
    sls_warn("linux_adio_open(): can't open /dev/dsp for duplex mode - backing off to write-only mode and try again later\n");
    if((fd = open("/dev/dsp", O_WRONLY | O_NDELAY)) == -1) {
      close(mixer_fd);
      return(-1);
    }
  }

  if (fd != -1) {

#if 0
    /* Set fragment size - 16K fragments */
    status = 0x7fff000e;
    if(ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &status) == -1) {
      sls_warn("linux_adio_open(): can't set fragment size");
      close(mixer_fd);
      close(fd);
      return(-1);
    }
#endif

    /* Set DUPLEX */
    if(ioctl(fd, SNDCTL_DSP_GETCAPS, (char *)&flags) != -1) {
      if(flags & DSP_CAP_DUPLEX) {
/* disabled because freebsd lacks SNDCTL_DSP_SETDUPLEX defn */
#ifndef __FreeBSD__
	if(ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0) == -1) {
	  sls_warn("linux_adio_open(): can't set to duplex mode");
	  close(mixer_fd);
	  close(fd);
	  return(-1);
	} else {
	  sls_debug1("linux_adio_open(): succesfully set to duplex mode\n");
	}
#endif
      } else {
	sls_debug1("linux_adio_open(): no duplex abilities\n");
      }
      if (!(flags & DSP_CAP_TRIGGER)) {
	trigger = 0;
	/* sls_warn("linux_adio_open(): no trigger capability\n"); */
	}
      else trigger = 1;
      if (!(flags & DSP_CAP_DUPLEX)) {
	hdx = 1;
	/* sls_warn("linux_adio_open(): no trigger capability\n"); */
	}
      else hdx = 0;
      
    } else sls_warn("linux_adio_open(): Couldn't determine capabilities!");

    /* Skipping hardware check for now...  Write it later */
    /* Make sure AD/DA is 16-bit and has stereo */
    if(ioctl(fd, SNDCTL_DSP_GETFMTS, (char *)&flags) != -1) {
      if(flags & AFMT_S16_LE || flags & AFMT_S16_BE ||
	 flags & AFMT_U16_LE || flags & AFMT_U16_BE)
	flags = 1;
      else flags = 0;
    } else {
      flags = 0;
      sls_warn("linux_adio_open(): Couldn't determine formats!");
    }

    /* Turn NDELAY/NONBLOCK mode off -- it doesn't work with OSS anyways */
    if (hdx) {  /* not sure this is the right test... */
      if((status = fcntl(fd, F_GETFL, 0)) == -1) {
        sls_warn("fcntl F_GETFL on /dev/dsp failed");
      } else {
        status &= ~O_NDELAY;
        if(fcntl(fd, F_SETFL, status) == -1) {
	  sls_warn("fcntl F_SETFL on /dev/dsp failed");
        }
      }
    }

    /* Fill in the LINUXAD structure. */
    linux_adio = (LINUX_ADIO *) calloc(1, sizeof(LINUX_ADIO));
    linux_adio->fd = fd;
    linux_adio->input_device = SOUND_MIXER_MIC;
    linux_adio->sampling_rate_hz = ADIO_DEFAULT_SRATE;
    linux_adio->wide = flags;
    linux_adio->a_max = 0;
    linux_adio->b_max = 0;
    linux_adio->last_error = 0;
    linux_adio->state = 0;
    linux_adio->chg = (CH_G *) chg_new(2);
    linux_adio->play_gain = 100;
    linux_adio->rec_gain = 100;
    linux_adio->mixer_fd = mixer_fd;
    linux_adio->trigger = trigger;
    linux_adio->hdx = hdx;

    /* 4front-tech.com website suggests decoupling mixer things from
       audio things.  however, we do need to set the port to record from */
    mixer_set_record_port(linux_adio->mixer_fd, linux_adio->input_device);
#if 0
    mixer_get_play_gain(linux_adio->mixer_fd, &linux_adio->play_gain); 
    mixer_get_record_gain(linux_adio->mixer_fd, &linux_adio->rec_gain);
#endif

    /* The audio device is open and of the correct type -- set it up. */
    dsp_reset(fd);

    /* This is order suggested by Voxware SDK Hacker's Guide
       1) bits/sample, 2) number of channels, 3) sampling_rate
       */
    if(linux_adio->wide)
      dsp_set_sample_type(fd, 16, 1);
    else dsp_set_sample_type(fd, 8, 1);
    dsp_set_stereo(fd, 1);
    dsp_set_sample_rate(fd, ADIO_DEFAULT_SRATE);

    /* Linux does need to get a buffer - OSSFree Pguide suggests doing this
       after setting sample parameters */
    if(ioctl(fd, SNDCTL_DSP_GETBLKSIZE, (char *)&abuf_size) == -1) {
      sls_warn("linux_adio_open(): ioctl failed for SNDCTL_DSP_GETBLKSIZE with error# %d", errno);
      close(mixer_fd);
      close(fd);
      return(-1);
    }

    if((linux_adio->buffer=(short *)malloc(abuf_size))==NULL) {
      sls_warn("linux_adio_open(): Unable to allocate audio buffer");
      close(mixer_fd);
      close(fd);
      return(-1);
    }
    linux_adio->buffer_size = abuf_size;
  }
  else { 	/* open(/dev/dsp) failed. */
    sls_warn("linux_adio_open(): open failed for /dev/dsp with error# %d", errno);
    if (errno < sys_nerr) 
      sls_warn("\t%s", sys_errlist[errno]);
  }

  /* Clean up on error. */
  if(!linux_adio && fd != -1) {
    close(mixer_fd);
    close(fd);
  }
  
  if (linux_adio) {
    linux_adios[adio_fd] = linux_adio;
    return(adio_fd);
  }
  else return(-1);
}

/* ------------------------------------------------------------
   Sets parameters of the fd_adio to those specified in params
   ------------------------------------------------------------	*/
int
linux_adio_set_params(fd_adio, params)
  int fd_adio;
  ADIO_PARAMS *params;
{
  LINUX_ADIO save, *linux_adio;

  if (!(linux_adio = linux_adio_i_lookup_fd(fd_adio, "adio_set_params"))) return(-1);

  /* keep a copy around in case we have an error */
  memcpy(&save, linux_adio, sizeof(LINUX_ADIO));

  /* Input device */
  if (strcmp(params->input_device, "line_in") == 0) {
    linux_adio->input_device = SOUND_MIXER_LINE;
  }
  else if (strcmp(params->input_device, "microphone") == 0) {
    linux_adio->input_device = SOUND_MIXER_MIC;
  }
  else {
    sls_warn("linux_adio_set_params(): illegal input device '%s'", params->input_device);
    memcpy(linux_adio, &save, sizeof(LINUX_ADIO));
    return(-1);
  }

  if (save.input_device != linux_adio->input_device) {
    mixer_set_record_port(linux_adio->mixer_fd, linux_adio->input_device);
  }
  /* Output device */
  /* Linux outputs to all simultaneously */

#if 0
  /* Output encoding */
  if (strcmp(params->output_encoding, "mu-law") == 0)
  {
    params->sampling_rate_hz = 8000;
    audio_get_play_config(linux_adio->fd, &config_h);
    config_h.bytes_per_unit = 1;
    config_h.encoding = AUDIO_ENCODING_ULAW;
    audio_set_play_config(linux_adio->fd, &config_h);
  }
  else if (strcmp(params->output_encoding, "linear") == 0)
  {
    if (!params->sampling_rate_hz)
      params->sampling_rate_hz = ADIO_DEFAULT_SRATE;
    audio_get_play_config(linux_adio->fd, &config_h);
    config_h.bytes_per_unit = 2;
    config_h.encoding = AUDIO_ENCODING_LINEAR;
    audio_set_play_config(linux_adio->fd, &config_h);
  }
#endif

  /* Sampling rate */
  if (params->sampling_rate_hz != save.sampling_rate_hz) {
    linux_adio->sampling_rate_hz = params->sampling_rate_hz;
    dsp_set_sample_rate(linux_adio->fd, linux_adio->sampling_rate_hz);
  }

  /* 4front-tech.com website suggests decoupling mixer things from
     audio things.  however, we do need to set the port to record from */
#if 0
  /* Gains -- Sun doesn't have indpt gain control*/
  if (params->play_gain[0] != save.play_gain) {
    linux_adio->play_gain = params->play_gain[0];
    mixer_set_play_gain(linux_adio->mixer_fd, &linux_adio->play_gain);
  }

  if (params->rec_gain[0] != save.rec_gain) {
    linux_adio->rec_gain = params->rec_gain[0];
    mixer_set_record_gain(linux_adio->mixer_fd, &linux_adio->rec_gain);
  }
#endif

  linux_adio->n_samp_recorded += params->delta_n_samp_recorded;
  linux_adio->n_samp_played += params->delta_n_samp_played;

  return(0);
}

/* --------------------------------------------------------------------------
   ADIO_GET_PARAMS -- returns the settings of various parameters in 'params'
   -------------------------------------------------------------------------- */
int
linux_adio_get_params(fd_adio, params)
  int fd_adio;
  ADIO_PARAMS *params;
{
  LINUX_ADIO *linux_adio;
  audio_buf_info buf_info;
  count_info info;

  if (!(linux_adio = linux_adio_i_lookup_fd(fd_adio, "adio_get_params"))) return(-1);

  params->num_channels = 2;

  /* Input device */
  switch(linux_adio->input_device) {
  case SOUND_MIXER_LINE:
    params->input_device = "line_in";
    break;
    
  case SOUND_MIXER_MIC:
    params->input_device = "microphone";
    break;
    
  default:
    sls_warn("linux_adio_get_params(): Unknown input device (%d), setting to microphone", linux_adio->input_device);
    params->input_device = "microphone";
    break;
  }
  /* Output device */
  /* Linux outputs to all simultaneously */

  /* Sampling rate */
  params->sampling_rate_hz = linux_adio->sampling_rate_hz;
   
  /* Gains */
  params->play_gain[0] = linux_adio->play_gain;
  params->play_gain[1] = linux_adio->play_gain;
  params->rec_gain[0] = linux_adio->rec_gain;
  params->rec_gain[1] = linux_adio->rec_gain;

  /* Max stuff */
  params->max[0] = linux_adio->a_max;
  params->max[1] = linux_adio->b_max;
  params->new_max_set = linux_adio->new_max_set;
  linux_adio->new_max_set = 0;

  /* Using the ioctl suggested by OSSFree */
  ioctl(linux_adio->fd, SNDCTL_DSP_GETIPTR, (char *)&info);
#if 0
  /* NO!  This reads the hardware sample counter.  We need to use our software
     counter because ep_adio needs to be able to reset. */
  if(linux_adio->wide)
    linux_adio->n_samp_recorded = info.bytes/(2*sizeof(short));
  else linux_adio->n_samp_recorded = info.bytes/(2*sizeof(char));
#endif
  params->n_samp_recorded = linux_adio->n_samp_recorded;
  ioctl(linux_adio->fd, SNDCTL_DSP_GETOPTR, (char *)&info);
  if(linux_adio->wide)
    linux_adio->n_samp_played = info.bytes/(2*sizeof(short));
  else linux_adio->n_samp_played = info.bytes/(2*sizeof(char));
  params->n_samp_played = linux_adio->n_samp_played;

  if(linux_adio->state & ADIO_RECORD)
    params->is_recording = 1;
  else params->is_recording = 0;

  /* Using the ioctl suggested by OSSFree */
  ioctl(linux_adio->fd, SNDCTL_DSP_GETOSPACE, (char *)&buf_info);
  if(buf_info.bytes < buf_info.fragstotal*buf_info.fragsize)
    params->is_playing = 1;
  else
    params->is_playing = 0;

  params->last_error = linux_adio->last_error;
  linux_adio->last_error = 0;

  params->delta_n_samp_played = 0;
  params->delta_n_samp_recorded = 0;

  /*  sls_warn("  (PLAYED %d)\n", n_samp); */
  return(0);
}

int
linux_adio_start_record(fd_adio)
  int fd_adio;
{
  LINUX_ADIO *linux_adio;

  if (!(linux_adio = linux_adio_i_lookup_fd(fd_adio, "adio_start_record")))
    return(-1);

  dsp_start_recording(linux_adio->fd, linux_adio->trigger, linux_adio->hdx);

  linux_adio->n_samp_recorded = 0;
  linux_adio->state |= ADIO_RECORD;

  return(0);
}

int
linux_adio_stop_record(fd_adio)
  int fd_adio;
{ 
  LINUX_ADIO *linux_adio;

  if (!(linux_adio = linux_adio_i_lookup_fd(fd_adio, "adio_stop_record")))
    return(-1);

  linux_adio->state &= ~ADIO_RECORD;

  dsp_stop_recording(linux_adio->fd, linux_adio->trigger);

  return(0);
}

int
linux_adio_start_play(fd_adio)
  int fd_adio;
{ 
  LINUX_ADIO *linux_adio;

  if (!(linux_adio = linux_adio_i_lookup_fd(fd_adio, "adio_start_play"))) return(-1);

  linux_adio->n_samp_played = 0;
  linux_adio->state |= ADIO_PLAY;

  return(0);
}

int
linux_adio_stop_play(fd_adio)
  int fd_adio;
{
  LINUX_ADIO *linux_adio;

  if (!(linux_adio = linux_adio_i_lookup_fd(fd_adio, "adio_stop_play"))) return(-1);
  linux_adio->state &= ~ADIO_PLAY;

  dsp_sync(linux_adio->fd);

  return(0);
}

int
linux_adio_read(fd_adio, num_s, chg)
  int fd_adio, num_s;
  CH_G *chg;
{
  int samp_avail, i, channel, max[2];
  int status, absval, num_bytes;
  short *dest[2], *sp;
  unsigned char *cp;
  audio_buf_info buf_info;
  LINUX_ADIO *linux_adio;

  if (!(linux_adio = linux_adio_i_lookup_fd(fd_adio, "adio_poll")))
    return(-1);

  /* get number of samples available - only use full fragments */
  if(ioctl(linux_adio->fd, SNDCTL_DSP_GETISPACE, (char *)&buf_info) == -1)
    sls_warn("linux_adio: ioctl error on SNDCTL_DSP_GETISPACE!");
  if(linux_adio->wide)
    samp_avail = (buf_info.bytes)/(2*sizeof(short));
  else samp_avail = (buf_info.fragments*buf_info.fragsize)/(2*sizeof(char));
  sls_debug1("SNDCTL_DSP_GETISPACE: fragments=%d fragstotal=%d fragsize=%d bytes=%d\n",
	     buf_info.fragments, buf_info.fragstotal, buf_info.fragsize,
	     buf_info.bytes);
  sls_debug1("linux_adio: SNDCTL_DSP_GETISPACE reports %d samples available!\n",
	     samp_avail);

  if(samp_avail > num_s)
    samp_avail = num_s;

  if(samp_avail == 0)
    return(0);

  if(linux_adio->wide) {
    if(samp_avail > linux_adio->buffer_size/(2*sizeof(short)))
      samp_avail = linux_adio->buffer_size/(2*sizeof(short));
    num_bytes = samp_avail*2*sizeof(short);
  } else {
    if(samp_avail > linux_adio->buffer_size/(2*sizeof(char)))
      samp_avail = linux_adio->buffer_size/(2*sizeof(char));
    num_bytes = samp_avail*2*sizeof(char);
  }

  dest[0] = chg->channels[0];
  dest[1] = chg->channels[1];

  /* okay, so we can transfer samp_avail */
#if 0
  fprintf(stderr, "read %d ", num_bytes);
  fflush(stderr);
#endif
  status = read(linux_adio->fd, (char *)linux_adio->buffer, num_bytes);
#if 0
  fprintf(stderr, "-> %d\n", status);
#endif

  sls_debug1("  (linux_adio: READ %d avail, %d read)\n",
	     samp_avail,
	     status/(2*(linux_adio->wide ? sizeof(short) : sizeof(char))));

  if (status > 0) {
    if(linux_adio->wide) {
      status /= 2*sizeof(short);
      for(i=0, sp=(short *)linux_adio->buffer, max[0] = max[1] = 0; i < 2*status;i++, sp++) {
	channel = i&1;
	if (dest[channel])
	  *(dest[channel]++) = *sp;
	absval = ABS(*sp);
	if (absval > max[channel])
	  max[channel] = absval;
      }
    } else {
      status /= 2*sizeof(char);
      for(i=0, cp=(unsigned char *)linux_adio->buffer, max[0] = max[1] = 0; i < 2*status;i++, cp++) {
	channel = i&1;
	if (dest[channel])
	  *(dest[channel]++) = (*cp-128)*256;
	absval = ABS((*cp-128)*256);
	if (absval > max[channel])
	  max[channel] = absval;
      }
    }
    linux_adio->n_samp_recorded += status;
  }

  linux_adio->a_max = max[0];
  linux_adio->b_max = max[1];
  linux_adio->new_max_set = 1;

  return(status);
}

int
linux_adio_flush_record(fd_adio)
  int fd_adio;
{
  LINUX_ADIO *linux_adio;

  if (!(linux_adio = linux_adio_i_lookup_fd(fd_adio, "adio_flush_record")))
    return(-1);

  if (linux_adio->state & ADIO_RECORD) {
    sls_warn("linux_adio_flush_record(): must STOP recording first");
    return(-1);
  }
  return(0);
}

int
linux_adio_flush_play(fd_adio)
  int fd_adio;
{
  LINUX_ADIO *linux_adio;

  if (!(linux_adio = linux_adio_i_lookup_fd(fd_adio, "adio_flush_play")))
    return(-1);

  if (linux_adio->state & ADIO_PLAY) {
    sls_warn("linux_adio_flush_play(): must STOP playing first");
    return(-1);
  }
  dsp_post(linux_adio->fd);
  return(0);
}

int
linux_adio_drain_play(fd_adio)
  int fd_adio;
{
  LINUX_ADIO *linux_adio;

  if (!(linux_adio = linux_adio_i_lookup_fd(fd_adio, "adio_drain_play")))
    return(-1);

  if (!(linux_adio->state & ADIO_PLAY)) {
    sls_warn("linux_adio_drain_play(): must be playing first");
    return(-1);
  }
  dsp_sync(linux_adio->fd);
  return(0);
}

int
linux_adio_write(fd_adio, num_s, chg)
  int fd_adio, num_s;
  CH_G *chg;
{
  int i, n_samp, status, num_bytes;
  short *sp, *a_ptr, *b_ptr;
  unsigned char *cp;
  LINUX_ADIO *linux_adio;

  if (!(linux_adio = linux_adio_i_lookup_fd(fd_adio, "adio_poll")))
    return(-1);

  n_samp = num_s;
  a_ptr = chg->channels[0];
  b_ptr = chg->channels[1];

  if(linux_adio->wide) {
    if(n_samp > linux_adio->buffer_size/(2*sizeof(short)))
      n_samp = linux_adio->buffer_size/(2*sizeof(short));

    /* interleave the samples in preparation for writing to driver */
    for(i=0, sp=linux_adio->buffer;i<n_samp;i++) {
      if (a_ptr != NULL) *(sp++) = *(a_ptr++);
      else *(sp++) = 0;

      if (b_ptr != NULL) *(sp++) = *(b_ptr++);
      else *(sp++) = 0;
    }

    /* Pad to end of buffer with zeroes
       Linux driver is picky about writing full fragments */
    if(n_samp < linux_adio->buffer_size/(2*sizeof(short))) {
      sls_pinfo2("Padding n_samp = %d to %d\n", n_samp,
		 linux_adio->buffer_size/(2*sizeof(short)));
      memset(linux_adio->buffer+n_samp*2, 0,
	     linux_adio->buffer_size-n_samp*2*sizeof(short));
      n_samp = linux_adio->buffer_size/(2*sizeof(short));
    }
    num_bytes = n_samp*2*sizeof(short);
  } else {
    if(n_samp > linux_adio->buffer_size/(2*sizeof(char)))
      n_samp = linux_adio->buffer_size/(2*sizeof(char));

    /* interleave the samples in preparation for writing to driver */
    for(i=0, cp=(unsigned char *)linux_adio->buffer;i<n_samp;i++) {
      if (a_ptr != NULL) *(cp++) = 128 + (char)((*(a_ptr++))/256);
      else *(cp++) = 0;

      if (b_ptr != NULL) *(cp++) = 128 + (char)((*(b_ptr++))/256);
      else *(cp++) = 0;
    }

    /* Pad to end of buffer with zeroes
       Linux driver is picky about writing full fragments */
    if(n_samp < linux_adio->buffer_size/(2*sizeof(char))) {
      sls_pinfo2("Padding n_samp = %d to %d\n", n_samp,
		 linux_adio->buffer_size/(2*sizeof(char)));
      memset(linux_adio->buffer+n_samp*2*sizeof(char)/sizeof(short), 0,
	     linux_adio->buffer_size-n_samp*2*sizeof(char));
      n_samp = linux_adio->buffer_size/(2*sizeof(char));
    }
    num_bytes = n_samp*2*sizeof(char);
  }

  /* Actually write the data to the audio device. */
  status = write(linux_adio->fd, (char *)linux_adio->buffer, num_bytes);
  if (status == -1)
    return(0);

  if(linux_adio->wide)
    status /= 2*sizeof(short);
  else status /= 2*sizeof(char);
  linux_adio->n_samp_played += status;

  return(status);
}

int
linux_adio_close(fd_adio)
  int fd_adio;
{
  LINUX_ADIO *linux_adio;

  if (!(linux_adio = linux_adio_i_lookup_fd(fd_adio, "adio_close"))) return(-1);

/*  printf("(CLOSE)\n"); */
  close(linux_adio->fd);

/*  printf("(FREE)\n"); */
  free((void *)linux_adio->buffer);
  free((void *)linux_adio);
  linux_adios[fd_adio] = NULL;

  return(0);
}

/* ----------------------------------------------------------
   INTERNAL: lookup the linux_adio pointer from a descriptor
   ---------------------------------------------------------- */
static LINUX_ADIO *linux_adio_i_lookup_fd(int fd_adio, char *fcn_name)
{ 
  if (fd_adio < 0 || fd_adio >= ADIO_MAX_FD) {
    sls_warn("%s: bad descriptor %d", fcn_name, fd_adio);
    return(NULL);
  }
  if (linux_adios[fd_adio] == NULL) {
    sls_warn("%s: bad descriptor %d", fcn_name, fd_adio);
    return(NULL);
  }
  return(linux_adios[fd_adio]);
}

/* 
  for Emacs...
  Local Variables:
  mode: c
  fill-column: 110
  comment-column: 80
  c-tab-always-indent: nil
  c-indent-level: 2
  c-continued-statement-offset: 2
  c-brace-offset: -2
  c-argdecl-indent: 2
  c-label-offset: -2
  End:
*/

/* 
  for Emacs...
  Local Variables:
  mode: C
  comment-column: 50
  fill-column: 77
  c-indent-level: 2
  c-continued-statement-offset: 2
  c-brace-offset: -2
  c-argdecl-indent: 2
  c-label-offset: -2
  End:
*/
