/* Copyright (c) 1991-2002 Doshita Lab. Speech Group, Kyoto University */
/* Copyright (c) 2000-2002 Speech and Acoustics Processing Lab., NAIST */
/*   All rights reserved   */

/* adin_mic_linux_oss.c --- adin microphone library for OSS API */

/* $Id: adin_mic_linux_oss.c,v 1.6 2003/05/08 08:23:49 ri Exp $ */

/* for standard sound drivers in linux-2.0.x, 2.2.x */
/* for OSS/Linux, OSS/Free or other OSS compatible API */
/* see http://www.opensound.com/ for OSS/Linux */

/* Tested on kernel-2.2.14 with OSS/Linux / ALSA emulation mode */

/* Changes from rev.3.0 --- strictly conforming to the programming guide */

/*
 * Use other mixer program to setup mic device
 * (mute/unmute, volume control, etc.)
 */

#include <sent/stddefs.h>
#include <sent/adin.h>

#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

/* sound header */
#include <sys/soundcard.h>

#define FREQALLOWRANGE 200	/* acceptable sampling frequency width around 16kHz */
#define MAXPOLLINTERVAL 300	/* adin read timeout interval (msec) */
#define FRAGMENT_POWER 10	/* data fragment size (= input delay)
				  2^10 = 1024 byte = 32ms in 16kHz */

static int audio_fd;		/* audio descriptor */
static boolean need_swap;	/* whether samples need byte swap */
static int fmt;			/* signed 16bit */
static int frag_size;		/* data fragment size */
static int poll_interval;

/* check audio port resource and initialize */
/* will be called once at startup time */
boolean
adin_mic_standby(int sfreq, void *dummy)
{
  int fmt_can;
  int fmt1, fmt2;                /* sampling format */
  int rfmt;
  int samplerate;	/* 16kHz */
  int stereo;		/* mono */

  /* open device */
  if ((audio_fd = open("/dev/dsp", O_RDONLY|O_NONBLOCK)) == -1) {
    perror("adin_mic_standby: open /dev/dsp");
    return(FALSE);
  }

  /* check whether soundcard can record 16bit data */
  /* and set fmt */
  if (ioctl(audio_fd, SNDCTL_DSP_GETFMTS, &fmt_can) == -1) {
    perror("adin_mic_standby: sndctl_dsp_getfmts");
    return(FALSE);
  }
#ifdef WORDS_BIGENDIAN
  fmt1 = AFMT_S16_BE;
  fmt2 = AFMT_S16_LE;
#else
  fmt1 = AFMT_S16_LE;               /* 16bit signed (little endian) */
  fmt2 = AFMT_S16_BE;               /* (big endian) */
#endif /* WORDS_BIGENDIAN */
  /* fmt2 needs byte swap */
  if (fmt_can & fmt1) {
    fmt = fmt1;
    need_swap = FALSE;
  } else if (fmt_can & fmt2) {
    fmt = fmt2;
    need_swap = TRUE;
  } else {
    fprintf(stderr, "adin_mic_standby: 16bit recording not supported\n");
    return FALSE;
  }
#ifdef DEBUG
  if (need_swap) {
    j_printf("samples need swap\n");
  } else {
    j_printf("samples need not swap\n");
  }
#endif
  
  if (close(audio_fd) != 0) return FALSE;

  /* re-open for recording */
  /* open device */
  if ((audio_fd = open("/dev/dsp", O_RDONLY)) == -1) {
    perror("adin_mic_standby: open /dev/dsp");
    return(FALSE);
  }

  /* try to set a small fragment size to minimize delay, */
  /* although many devices use static fragment size... */
  /* (and smaller fragment causes busy buffering) */
  {
    int arg;
    arg = 0x7fff0000 | FRAGMENT_POWER;
    if (ioctl(audio_fd, SNDCTL_DSP_SETFRAGMENT, &arg)) {
      fprintf(stderr, "adin_mic_standby: set fragment size to 2^%d=%d bytes (%d msec)\n", FRAGMENT_POWER, 2 << (FRAGMENT_POWER-1), (2 << (FRAGMENT_POWER-1)) * 1000 / (sfreq * sizeof(SP16)));
    }
  }
  
  /* set format, samplerate, channels */
  rfmt = fmt;
  if (ioctl(audio_fd, SNDCTL_DSP_SETFMT, &rfmt) == -1) {
    perror("adin_mic_standby: sndctl_dsp_setfmt");
    return(FALSE);
  }
  if (rfmt != fmt) {
    fprintf(stderr, "adin_mic_standby: 16bit recording not supported\n");
    return FALSE;
  }

  stereo = 0;			/* mono */
  if (ioctl(audio_fd, SNDCTL_DSP_STEREO, &stereo) == -1) {
    perror("adin_mic_standby: sndctl_dsp_stereo (mono)");
    return(FALSE);
  }
  if (stereo != 0) {
    fprintf(stderr,"adin_mic_standby: monoral recording not supported\n");
    return FALSE;
  }

  samplerate = sfreq;
  if (ioctl(audio_fd, SNDCTL_DSP_SPEED, &samplerate) == -1) {
    fprintf(stderr, "adin_mic_standby: sndctl_dsp_speed (%dHz)\n", sfreq);
    return(FALSE);
  }
  if (samplerate < sfreq - FREQALLOWRANGE || samplerate > sfreq + FREQALLOWRANGE) {
    fprintf(stderr,"adin_mic_standby: couldn't set sampling rate to near %dHz. (%d)\n", sfreq, samplerate);
    return FALSE;
  }
  if (samplerate != sfreq) {
    fprintf(stderr,"adin_mic_standby: set sampling rate to %dHz\n", samplerate);
  }

  /* get actual fragment size */
  if (ioctl(audio_fd, SNDCTL_DSP_GETBLKSIZE, &frag_size) == -1) {
    fprintf(stderr, "adin_mic_standby: failed to get fragment size\n");
    return(FALSE);
  }
  j_printf("fragment size = %d bytes (%d msec)", frag_size, frag_size * 1000/ (sfreq * sizeof(SP16)));
  if (frag_size !=  2 << (FRAGMENT_POWER-1)) {
    j_printf(" (tried = %d bytes)", 2 << (FRAGMENT_POWER-1));
  }
  j_printf("\n");

  return TRUE;
}
 
/* start recording */
boolean
adin_mic_start()
{
  char buf[2];
  /* Read 1 sample (and ignore it) to tell the audio device start recording.
     (If you knows better way, teach me...) */
  read(audio_fd, buf, 2);

  return(TRUE);
}

/* stop recording */
boolean
adin_mic_stop()
{
  /*
   * Not reset device on each end of speech, just let the buffer overrun...
   * Resetting and restarting of recording device sometimes causes
   * hawling noises at the next recording.
   * I don't now why, so take the easy way... :-(
   */
  return TRUE;
}

/* read samples from audio device */
/* try to read `sampnum' samples and returns actual sample num recorded */
int
adin_mic_read(SP16 *buf, int sampnum)
{
  int size,cnt;
  audio_buf_info info;
  fd_set rfds;
  struct timeval tv;
  int status;

  /* check for incoming samples in device buffer */
  /* if there is at least one sample fragment, go next */
  /* if not exist, wait for the data to come for at most MAXPOLLINTERVAL msec */
  /* if no sample fragment has come in the MAXPOLLINTERVAL period, go next */
  FD_ZERO(&rfds);
  FD_SET(audio_fd, &rfds);
  tv.tv_sec = 0;
  tv.tv_usec = MAXPOLLINTERVAL * 1000;
  status = select(audio_fd+1, &rfds, NULL, NULL, &tv);
  if (status < 0) {
    /* select() failed */
    j_printerr("adin_mic_read: device polling failed\n");
    return(-2);			/* error */
  }
  if (FD_ISSET(audio_fd, &rfds)) { /* has some data */
    /* get sample num that can be read without blocking */
    if (ioctl(audio_fd, SNDCTL_DSP_GETISPACE, &info) == -1) {
      perror("adin_mic_read: sndctl_dsp_getispace");
      return(-2);
    }
    /* get them as much as possible */
    size = sampnum * sizeof(SP16);
    if (size > info.bytes) size = info.bytes;
    if (size < frag_size) size = frag_size;
    cnt = read(audio_fd, buf, size);
    if ( cnt < 0 ) {
      perror("adin_mic_read: read error\n");
      return ( -2 );
    }
    cnt /= sizeof(short);
    if (need_swap) swap_sample_bytes(buf, cnt);
  } else {			/* no data after waiting */
    j_printerr("adin_mic_read: no data fragment after %d msec?\n", MAXPOLLINTERVAL);
    cnt = 0;
  }

  return(cnt);
}
