/* CSL - Common Sound Layer
 * Copyright (C) 2000-2001 Stefan Westerfeld and Tim Janik
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 */
#include <csl/csl.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <values.h>
#include <errno.h>

#undef  ABS
#define ABS(a)     (((a) < 0) ? -(a) : (a))

/* --- option parser helpers --- */
static unsigned int shift_argc = 0;
static char       **shift_argv = NULL;
static char*	shift (void) {
  char *a; if (shift_argc > 1) { shift_argc--; a = shift_argv++[1];
  return a ? a : ""; } else return NULL;
}
static char*	pshift (void) {
  char *arg = shift (); return arg ? arg : "";
}


/* --- statistics --- */
typedef struct
{
  int  min_left, max_left, min_right, max_right, avr_left, avr_right;
  unsigned int max_diff;
} Stats;
static void stats_init (Stats *stats)
{
  stats->max_left = -MAXINT;
  stats->max_right = -MAXINT;
  stats->min_left = MAXINT;
  stats->min_right = MAXINT;
  stats->avr_left = 0;
  stats->avr_right = 0;
  stats->max_diff = 0;
}
static inline void stats_update (Stats *stats,
				 short  left,
				 short  right)
{
  stats->max_left = MAX (stats->max_left, left);
  stats->min_left = MIN (stats->min_left, left);
  stats->avr_left += (left);
  stats->max_right = MAX (stats->max_right, right);
  stats->min_right = MIN (stats->min_right, right);
  stats->avr_right += (right);
  stats->max_diff = MAX (stats->max_diff, ABS (left - right));
}
#define SIGNED_HEX(val) ((val) < 0 ? '-' : '+') , ((val) < 0 ? -val : val)
static inline void stats_report (Stats *stats)
{
  fprintf (stderr,
	   "STATS: | minL=-0x%04x avrL=%c0x%07x maxL=0x%04x "
	   "| minR=-0x%04x avrL=%c0x%07x maxR=0x%04x | pL=%c0x%04x pR=%c0x%04x diff=0x%04x\n",
	  -stats->min_left, SIGNED_HEX (stats->avr_left), stats->max_left,
	  -stats->min_right, SIGNED_HEX (stats->avr_right), stats->max_right,
	  SIGNED_HEX ((stats->max_left + stats->min_left) / 2),
	  SIGNED_HEX ((stats->max_right + stats->min_right) / 2),
	  stats->max_diff);
}


/* --- main --- */
int
main (int   argc,
      char *argv[])
{
  CslErrorType error;
  CslDriver *driver;
  CslPcmStream *stream;
  float len = 0;
  Stats stats;
  unsigned int todo = 0;
  CslOptions options = { 0, };
  CslBool infinite = TRUE;
  int need_stats = FALSE;
  char *arg;

  /* parse and eliminate CSL specific options */
  csl_options_parse (&options, &argc, &argv);
  csl_set_debug_mask (options.debug_flags);

  /* parse options */
  shift_argc = argc;
  shift_argv = argv;
  arg = shift ();
  while (arg)
    {
      if (strcmp (arg, "-l") == 0)
	{
	  len = atof (pshift ());
	  infinite = FALSE;
	  if (len <= 0)
	    csl_error ("length must be > 0");
	}
      else if (strcmp (arg, "--stats") == 0)
	need_stats = TRUE;
      else
	{
	  csl_error ("bad option: `%s'\n"
		     "usage: cslrec [csl-options] [options]\n"
		     "csl-options:\n%s\noptions:\n%s",
		     arg,
		     csl_options_describe (2),
		     "  --stats              print statistics\n"
		     "  -l <n_values>        number of values to record\n");
	}
      arg = shift ();
    }
  fprintf (stderr, "csl-options:\n%s\n", csl_options_dump (&options));

  error = csl_driver_init (NULL, &driver);	/* choose backend */
  if (error)
    csl_error ("unable to initialize driver: %s", csl_strerror (error));

  error = csl_pcm_open_input (driver, "cslrec",
			       options.rate,
			       options.n_channels,
			       options.pcm_format,
			       &stream);
  if (error)
    csl_error ("failed to open input device: %s", csl_strerror (error));
  else if (csl_debug (PCM))
    {
      char *format1 = csl_describe_pcm_format (options.pcm_format);
      char *format2 = csl_describe_pcm_format (csl_pcm_get_format (stream));

      csl_message ("requested: (%s), got (%s)",
		   format1, format2);
      csl_free (format1);
      csl_free (format2);
    }

  todo = options.n_channels *
	 (csl_pcm_get_format (stream) & CSL_PCM_FORMAT_SIZE_MASK) *
	 (int)(len * (float)options.rate) / 32;

  do
    {
      unsigned char buffer[32768];
      int r = infinite ? 32768 : CLAMP (todo, 0, 32768), length;

      r = csl_pcm_read (stream, r, buffer);
      if (r > 0)
	{
	  length = fwrite (buffer, 1, r, stdout);
	  if (length != r)
	    csl_error ("failed to write %d bytes (wrote %d)", r, length);
	  todo -= r;
	  if (need_stats && options.n_channels == 2 && options.pcm_format == CSL_PCM_FORMAT_S16_LE)
	    {
	      short *vl = (short*) buffer, *vr = vl + 1;

	      stats_init (&stats);
	      while (r >= 4)
		{
		  r -= 4;
		  stats_update (&stats, *vl++, *vr++);
		}
	      stats_report (&stats);
	    }
	}
    }
  while (todo > 0 || infinite);

  csl_pcm_close (stream);
  csl_driver_shutdown (driver);
  
  return 0;
}
/* vim:ts=8:sw=2:sts=2
 */
