/* 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_alsa.c --- adin microphone library for ALSA native API */

/* $Id: adin_mic_linux_alsa.c,v 1.3 2002/09/11 22:01:50 ri Exp $ */

/*
 * From rev.3.0: catch up for alsa-driver-0.5.5.
 *
 */

/*
 * NOTE: the ALSA sound driver supports 2 APIs below:
 *
 *        1) ALSA native API, for new ALSA-aware sound applications.
 *        2) OSS emulation API, for legacy OSS applications.
 *
 * As though Julius supports both, you'd better use the 2) in Julius.
 * The 1) is still under heavy development now (2000/03/08) and I don't
 * want to spend much time catching up with the still-varying driver APIs...
 * :-(  This implementation will work for 0.5.5, but is unstable.
 *
 * The ALSA native API will be fully supported when ALSA becomes
 * stable and full documentation becomes available.  Of course patches for
 * this file or a new maintainer are always welcomed :-).
 *
 * You can explicitly select OSS API by specifying "--with-mictype=oss" option
 * at configuration.
 * 
 */

/*
 * Use mixer program like alsamixer or alsactl to setup mic device
 * (mute/unmute, volume control, etc.)
 *
 * !!Note that ALSA drivers first mute all audio devices by default!!
 */

/* see http://www.alsa-prpject.org/ for information about ALSA, */
/* Advanced Linux Sound Architecture */


/*
 * When multiple sound cards are found, the first one will be used.
 */

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

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

static int bufsize;		/* buffer size in bytes */
static boolean need_swap;	/* whether samples need byte swap */
static int samplefreq;		/* sampling frequency */

/* sound header for ALSA */
#include <sys/asoundlib.h>
static int cardid, devid;
static snd_pcm_format_t format;
static snd_pcm_t *handle;

boolean
adin_mic_standby(int sfreq, void *dummy)
{
  int ret;
  char *cardname;
  snd_ctl_t *chandle;
  struct snd_ctl_hw_info info;
  snd_pcm_info_t pinfo;
  snd_pcm_channel_info_t cinfo;

  samplefreq = sfreq;

  cardid = 0;
  devid = 0;

  /* prepare recording format */
  memset(&format, 0, sizeof(snd_pcm_format_t));
  format.interleave = 1;
#ifdef WORDS_BIGENDIAN
  format.format = SND_PCM_SFMT_S16_BE;
  need_swap = FALSE;
#else  /* little endian */
  format.format = SND_PCM_SFMT_S16_LE;
  need_swap = FALSE;
#endif
  format.rate = samplefreq;
  format.voices = 1;		/* monoral */

  /* determine which soundcard / device / subdevice to use */
  /* currently, first found record-capable device will be used */
  if ((ret = snd_cards()) <= 0) {
    j_printerr("Error: adin_mic_standby: no soundcards!\n");
    return FALSE;
  }
  if (ret > 1) {
    j_printf("Warning: adin_mic_standby: multiple soundcards found, using first one.\n");
  }
  cardid = 0;
  /* create handle and open communication with kernel sound control interface */
  if ((ret = snd_ctl_open(&chandle, cardid)) < 0) {
    j_printerr("Error: adin_mic_standby: ctl_open(%d): %s\n", cardid, snd_strerror(ret));
    return(FALSE);
  }

  /* get sound hardware resource and display info */
  if ( (ret = snd_ctl_hw_info( chandle, &info )) < 0 ) {
    j_printerr("Error: adin_mic_standby: ctl_hw_info(%d): %s\n", cardid, snd_strerror(ret));
    snd_ctl_close(chandle);
    return(FALSE);
  }
  j_printf("Sound Card %d: %s [%s]\n", cardid + 1, info.name, info.id);
  /* search for recordable device */
  j_printf("Installed PCM devices: %i\n", info.pcmdevs );
  if (info.pcmdevs < 1) {
    j_printerr("Error: no PCM devices available on this card\n");
    return(FALSE);
  }
  for (devid = 0; devid < info.pcmdevs; devid++) {
    if ( (ret = snd_ctl_pcm_info( chandle, devid, &pinfo )) < 0 ) {
      j_printerr("Error: adin_mic_standby: ctl_pcm_info(%d): %s\n", cardid, snd_strerror(ret));
      snd_ctl_close(chandle);
      return(FALSE);
    }
    j_printf("#%d: %s", devid, pinfo.name);
    if (pinfo.flags & SND_PCM_INFO_CAPTURE) {
      j_printf("\n");
      break;			/* use first found recordable device */
    } else {
      j_printf(" --- not recordable, skipped\n");
    }
  }
  /* check for record subdevices */
  j_printf("Record subdevices in this device: %d\n", pinfo.capture + 1);
  {
    int i;
    
    for(i=0;i<=pinfo.capture; i++) {
      memset(&cinfo, 0, sizeof(snd_pcm_channel_info_t));
      cinfo.channel = SND_PCM_CHANNEL_CAPTURE;
      if ( (ret = snd_ctl_pcm_channel_info( chandle, devid, SND_PCM_CHANNEL_CAPTURE, i, &cinfo )) < 0 ) {
	j_printerr("Error: adin_mic_standby: ctl_pcm_channel_info(%d): %s\n", cardid, snd_strerror(ret));
	snd_ctl_close(chandle);
	return(FALSE);
      }
      j_printf(" #%d: %s\n", i, cinfo.subname);
      break;		
    }
  }

  /* close control interface */
  if ((ret = snd_ctl_close(chandle)) < 0) {
    j_printerr("Error: adin_mic_standby: %s\n", snd_strerror(ret));
    return(FALSE);
  }
  return(TRUE);
}

/* start recording */
boolean
adin_mic_start()
{
  int ret;
  snd_pcm_channel_info_t cinfo;
  static struct snd_pcm_channel_params params;
  
  /* open device */
  if ((ret = snd_pcm_open(&handle, cardid, devid, SND_PCM_OPEN_CAPTURE)) < 0 ) {
    j_printerr("Error: adin_mic_start: open error: %s\n", snd_strerror(ret));
    return(FALSE);
  }
  /* check recording capability */
  memset(&cinfo, 0, sizeof(snd_pcm_channel_info_t));
  cinfo.channel = SND_PCM_CHANNEL_CAPTURE;
  if ( (ret = snd_pcm_plugin_info(handle, &cinfo )) < 0 ) {
    j_printerr("Error: adin_mic_start: ctl_pcm_channel_info(%d): %s\n", cardid, snd_strerror(ret));
    return(FALSE);
  }
  if (cinfo.min_rate > samplefreq || cinfo.max_rate < samplefreq) {
    j_printerr("-- cannot set frequency to %dHz\n", samplefreq);
    return(FALSE);
  }
  if (!(cinfo.formats & (1 << format.format))) {
    j_printerr("-- 16bit recording not supported\n");
    return(FALSE);
  }
  /* set format */
    memset(&params, 0, sizeof(params));
    params.channel = SND_PCM_CHANNEL_CAPTURE;
    params.mode = SND_PCM_MODE_BLOCK;
    memcpy(&params.format, &format, sizeof(format));
    params.start_mode = SND_PCM_START_DATA;
    params.stop_mode = SND_PCM_STOP_STOP;
    params.buf.block.frag_size = 8192;
    params.buf.block.frags_max = -1;
    params.buf.block.frags_min = 1;
    
    if ((ret = snd_pcm_plugin_params(handle, &params)) < 0) {
      j_printerr("Error: adin_mic_start: unable to set channel params: %s\n",snd_strerror(ret));
      return(FALSE);
    }

  if (snd_pcm_plugin_prepare(handle,SND_PCM_CHANNEL_CAPTURE) < 0) {
    j_printerr("Error: adin_mic_start: unable to prepare channel\n");
    return(FALSE);
  }

  {
    struct snd_pcm_channel_setup setup;
    memset(&setup, 0, sizeof(setup));
    setup.channel = SND_PCM_CHANNEL_CAPTURE;
    setup.mode = SND_PCM_MODE_BLOCK;
    if (snd_pcm_plugin_setup(handle, &setup) < 0) {
      j_printerr("Error: adin_mic_start: unable to obtain setup\n");
      return(FALSE);
    }
    bufsize = setup.buf.block.frag_size;
  }
    
  /* set nonblock */
  if (snd_pcm_nonblock_mode(handle, 1) < 0) {
    j_printerr("Error: adin_mic_start: unable to prepare channel\n");
    return(FALSE);
  }

  return(TRUE);
}
  
/* stop recording */
boolean
adin_mic_stop()
{
  int ret;
  /* close device  */
  if ((ret = snd_pcm_close(handle)) < 0) {
      j_printerr("Error: adin_mic_stop: unable to stop recording: %s\n",snd_strerror(ret));
      return(FALSE);
  }
  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 cnt;
  int tsamp, size;

  tsamp = 0;
  if (tsamp + (bufsize / sizeof(SP16)) > sampnum) bufsize = (sampnum - tsamp) * sizeof(SP16);
  /* wait till first input */
  do {
    cnt = snd_pcm_plugin_read(handle, buf, bufsize);
    if (cnt > 0) break;
    if (cnt == -EPIPE || cnt == -EAGAIN) {
      cnt = 0;
    }
    if (cnt < 0) {
      j_printerr("Error: adin_mic_read: %s\n", snd_strerror(cnt));
      return(-2);
    }
    usleep(10000);
  } while (cnt == 0);

  do {
    tsamp += cnt / sizeof(SP16);
    if (tsamp >= sampnum) break;
    if (tsamp + (bufsize / sizeof(SP16)) > sampnum) bufsize = (sampnum - tsamp) * sizeof(SP16);
    cnt = snd_pcm_plugin_read(handle, buf + tsamp, bufsize);
  } while (cnt > 0);
  if (cnt == -EPIPE || cnt == -EAGAIN) {
    cnt = 0;
  }
  if (cnt < 0) {
    j_printerr("Error: adin_mic_read: %s\n", snd_strerror(cnt));
    return(-2);
  }
  return(tsamp);
}
