/* 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/cslpcm.h>
#include <csl/cslutils.h>
#include <stdlib.h>
#include <unistd.h>
#include <math.h>
#include <stdio.h>

static int cfgSamplingRate = 44100;
static int cfgFormat = CSL_PCM_FORMAT_S16_LE;
static int cfgFrequency = 440;
static float cfgLength = 1.0;
static int cfgChannels = 2;

static void exitUsage (const char *progname) CSL_GNUC_NORETURN;

static void
exitUsage (const char *progname)
{
  fprintf(stderr,"usage: %s [ options ]\n",progname);
  fprintf(stderr,"creates a sine wave on stdout\n\n");

  fprintf(stderr,"-f <frequency>      set frequency of the sine wave\n");
  fprintf(stderr,"-l <length>         set length of the sine wave\n");
  fprintf(stderr,"-h                  display this help and exit\n\n");

  fprintf(stderr,"-r <samplingrate>   set samplingrate to use\n");
  fprintf(stderr,"-c <channels>       set number of channels\n");
  fprintf(stderr,"-b <bits>           set number of bits (8, 16, 32=float)\n");
  fprintf(stderr,"-u, -s              set unsigned/signed format\n");
  fprintf(stderr,"-B, -L              set big/little endian\n");
  exit(1);                                                                    
}

static int
float2int (float f)
{
  union {
    float f;
    int i;
  } x;
  x.f = f;
  return x.i;
}

int
main (int argc, char **argv)
{
  int optch;
  int position, channel;
  int isSigned = 0;
  char buffer[1024], *out = &buffer[0];

  while ((optch = getopt (argc, argv, "usr:b:c:f:BLl:h")) > 0)
  {
    switch (optch)
    {
      case 'u': isSigned = CSL_PCM_FORMAT_ENCODING_LINEAR_UNSIGNED;
		break;
      case 's': isSigned = CSL_PCM_FORMAT_ENCODING_LINEAR_SIGNED;
		break;
      case 'r': cfgSamplingRate = atoi (optarg);
		break;
      case 'b': cfgFormat = (cfgFormat & ~(CSL_PCM_FORMAT_SIZE_MASK)) | atoi(optarg);
		break;
      case 'c': cfgChannels = atoi (optarg);
		break;
      case 'f': cfgFrequency = atoi (optarg);
		break;
      case 'B': cfgFormat = (cfgFormat & ~(CSL_PCM_FORMAT_ENDIAN_MASK)) | CSL_PCM_FORMAT_ENDIAN_BE;
		break;
      case 'L': cfgFormat = (cfgFormat & ~(CSL_PCM_FORMAT_ENDIAN_MASK)) | CSL_PCM_FORMAT_ENDIAN_LE;
		break;
      case 'l': cfgLength = atof (optarg);
		break;
      case 'h':
      default:
		exitUsage (argc?argv[0]:"testsine");
		break;
    }
  }

  /* no signedness given -> guess */
  if (!isSigned)
    {
      switch (cfgFormat & CSL_PCM_FORMAT_SIZE_MASK)
	{
	  case 8:   isSigned = CSL_PCM_FORMAT_ENCODING_LINEAR_UNSIGNED;
		  break;

	  case 16:  isSigned = CSL_PCM_FORMAT_ENCODING_LINEAR_SIGNED;
		  break;

	  case 32:  isSigned = CSL_PCM_FORMAT_ENCODING_FLOAT;
		  break;
	}
    }
  cfgFormat = (cfgFormat & ~CSL_PCM_FORMAT_ENCODING_MASK) | isSigned;

  /* 8bit -> no endianness */
  if ((cfgFormat & CSL_PCM_FORMAT_SIZE_MASK) == 8)
    cfgFormat = cfgFormat & ~CSL_PCM_FORMAT_ENDIAN_MASK;

  fprintf (stderr, "format: %d bits", (cfgFormat & CSL_PCM_FORMAT_SIZE_MASK));

  switch (cfgFormat & CSL_PCM_FORMAT_ENDIAN_MASK)
    {
      case CSL_PCM_FORMAT_ENDIAN_LE: 
		  fprintf (stderr, ", little endian");
		break;
      case CSL_PCM_FORMAT_ENDIAN_BE:
		  fprintf (stderr, ", big endian");
		break;
    }

  switch (cfgFormat & CSL_PCM_FORMAT_ENCODING_MASK)
    {
      case CSL_PCM_FORMAT_ENCODING_LINEAR_SIGNED: 
		  fprintf (stderr, ", linear signed");
		break;
      case CSL_PCM_FORMAT_ENCODING_LINEAR_UNSIGNED: 
		  fprintf (stderr, ", linear unsigned");
		break;
      case CSL_PCM_FORMAT_ENCODING_FLOAT:
		  fprintf (stderr, ", float");
		break;
    }
  fprintf (stderr, "\n");

  for (position = 0; ((float)position / (float)cfgSamplingRate) < cfgLength; position++)
    {
      for (channel = 0; channel < cfgChannels; channel++)
	{
	  float value = sin((float)position / (float)cfgSamplingRate * cfgFrequency * 2.0 * M_PI);
	  int value16 = CLAMP(value * 32768.0, -32768, 32767);
	  int value8 = CLAMP(value * 128.0, -128, 127);
	  int valuef = float2int(value);

	  /* render in format */
	  switch (cfgFormat)
	    {
	      case CSL_PCM_FORMAT_SIZE_32 | CSL_PCM_FORMAT_ENDIAN_LE | CSL_PCM_FORMAT_ENCODING_FLOAT:
			*out++ = valuef;
			*out++ = (valuef/256);
			*out++ = (valuef/256/256);
			*out++ = (valuef/256/256/256);
		    break;

	      case CSL_PCM_FORMAT_SIZE_32 | CSL_PCM_FORMAT_ENDIAN_BE | CSL_PCM_FORMAT_ENCODING_FLOAT:
			*out++ = (valuef/256/256/256);
			*out++ = (valuef/256/256);
			*out++ = (valuef/256);
			*out++ = valuef;
		    break;

	      case CSL_PCM_FORMAT_SIZE_16 | CSL_PCM_FORMAT_ENDIAN_LE | CSL_PCM_FORMAT_ENCODING_LINEAR_UNSIGNED:
			value16 += 32768;
			*out++ = value16;
			*out++ = (value16/256);
		    break;

	      case CSL_PCM_FORMAT_SIZE_16 | CSL_PCM_FORMAT_ENDIAN_BE | CSL_PCM_FORMAT_ENCODING_LINEAR_UNSIGNED:
			value16 += 32768;
			*out++ = (value16/256);
			*out++ = value16;
		    break;

	      case CSL_PCM_FORMAT_SIZE_16 | CSL_PCM_FORMAT_ENDIAN_LE | CSL_PCM_FORMAT_ENCODING_LINEAR_SIGNED:
			*out++ = value16;
			*out++ = (value16/256);
		    break;

	      case CSL_PCM_FORMAT_SIZE_16 | CSL_PCM_FORMAT_ENDIAN_BE | CSL_PCM_FORMAT_ENCODING_LINEAR_SIGNED:
			*out++ = (value16/256);
			*out++ = value16;
		    break;

	      case CSL_PCM_FORMAT_SIZE_8 | CSL_PCM_FORMAT_ENCODING_LINEAR_UNSIGNED:
			value8 += 128;
			*out++ = value8;
		    break;

	      case CSL_PCM_FORMAT_SIZE_8 | CSL_PCM_FORMAT_ENCODING_LINEAR_SIGNED:
			*out++ = value8;
		    break;

	      default:	csl_error("unsupported format %4x", cfgFormat);
		    break;
	    }
	}
      if((out - buffer) == 1024)
	{
	  write(1, buffer, 1024);
	  out = buffer;
	}
    }
  write(1, buffer, out-buffer);
  return 1;
}
