/*=======================================================================
 *  Wave - a sound waveform generator
 *  Copyright (C) 1996,2001 Jeff Tranter (tranter@pobox.com)
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  (see COPYING) along with this program; if not, write to the Free
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *=======================================================================
 */

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <math.h>
#include <string.h>
#include <errno.h>
#include <csl/csl.h>

/* type definitions */
typedef enum { sine, square, triangle, ramp } waveType; /* waveform types */

/* global variables */
int frequency     = 440;	/* waveform frequency in hertz */
int rate          = 44100;	/* sampling rate in samples/second */
double duration   = 0;		/* playing duration in seconds, 0=infinite */
double lLevel     = 100;	/* left  channel output level in percent */
double rLevel     = 100;	/* right channel output level in percent */
waveType wave     = sine;	/* waveform type */
int channels      = 2;		/* 1 = mono 2 = stereo */
int bits          = 16;		/* sample size in bits */
int verbose      = FALSE;	/* verbose output mode */
unsigned char *buffer = 0;	/* buffer for one cycle of sound data */
CslErrorType error;
CslDriver *driver;
CslPcmStream *stream;

/* display name for a waveform type */
static char *Name(waveType type)
{
	switch (type) {
	  case sine:
		  return "sine";
	  case square:
		  return "square";
	  case triangle:
		  return "triangle";
	  case ramp:
		  return "ramp";
	}
	return "unknown";
}

/* Display error message and quit */
/* Accepts printf() style arguments */
static void Error(char *format, ...) CSL_GNUC_NORETURN;

static void Error(char *format, ...)
{
	va_list ap;
	fprintf(stderr, "wave: error: ");
	va_start(ap, format);
	vfprintf(stderr, format, ap);
	va_end(ap);
	fprintf(stderr, "\n");
	exit(1);
}

/* Display warning message and returns */
/* Accepts printf() style arguments */
static void Warning(char *format, ...)
{
	va_list ap;
	fprintf(stderr, "wave: warning: ");
	va_start(ap, format);
	vfprintf(stderr, format, ap);
	va_end(ap);
	fprintf(stderr, "\n");
}

/* display usage */
static void Usage() CSL_GNUC_NORETURN;

static void Usage()
{
  fprintf(stderr,
"usage: wave [options]\n"
"\n"
"options:\n"
"  -f <frequency>          frequency in hertz, e.g. 1000\n"
"  -r <sampling-rate>      sampling rate in samples per second\n"
"  -d <duration>           duration of sound in seconds (0=infinite)\n"
"  -l <level>              output level in percent, e.g. 50 or\n"
"                          40:55 for left and right channels\n"
"  -w <waveform>           waveform type: sine, square, triangle, ramp\n"
"  -n <channels>           1 (mono) or 2 (stereo)\n"
"  -s <sample-size>        8 or 16 bits\n"
"  -v                      verbose output\n"
"\n"
"Default is a 440 Hz sine wave at 44100 samples/second and 100%% level\n"
"using 16-bit samples sent continuously.\n"
);
  exit(1);
}


/* parse options */
static void ParseOptions(int argc, char *argv[])
{
	const char     *flags = "f:r:d:l:w:n:s:v";
	int             c;
  
	while ((c = getopt(argc, argv, flags)) != EOF) {
		switch (c) {
		  case 'f':
			  frequency = atol(optarg);
			  if (frequency <= 0)
				  Error("frequency must be > 0");
			  break;
		  case 'r':
			  rate = atol(optarg);
			  if (rate <= 0)
				  Error("sampling rate must be > 0");
			  break;
		  case 'd':
			  duration = atof(optarg);
			  if (duration < 0)
				  Error("duration must be >= 0");
			  break;
		  case 'l':
			  if (strchr(optarg, ':')) {
				  sscanf(optarg, "%lf:%lf", &lLevel, &rLevel);
			  } else {
				  lLevel = rLevel = atof(optarg);
			  }
			  if (lLevel < 0 || lLevel > 100 
				  || rLevel < 0 || rLevel > 100)
				  Error("level must be between 0 and 100");
			  break;
		  case 'w':
			  if (!strcmp(optarg, "sine"))
				  wave = sine;
			  else if (!strcmp(optarg, "square"))
				  wave = square;
			  else if (!strcmp(optarg, "triangle"))
				  wave = triangle;
			  else if (!strcmp(optarg, "ramp"))
				  wave = ramp;
			  else
				  Error("invalid wave type; must be sine, square, triangle, or ramp");
			  break;
		  case 'n':
			  channels = atol(optarg);
			  if ((channels != 1) && (channels != 2))
				  Error("number of channels must be 1 or 2");
			  break;
		  case 's':
			  bits = atol(optarg);
			  if ((bits != 8) && (bits != 16))
				  Error("sample size must be 8 or 16 bits");
			  break;
		  case 'v':
			  verbose = TRUE;
			  break;
		  case '?':
			  Usage();
			  break;
		}
	}
	
	/* check for a single additional argument */
	if ((argc - optind) > 1)
		Usage(); /* too many arguments */
	
	/* validate some arguments */
	if (rate < frequency)
		Error("frequency cannot be higher than sampling rate");
	
	if ((lLevel != rLevel) && (channels != 2)) {
		Warning("using stereo mode since you specified different channel gains");
		channels = 2;
	}
}

/* display verbose */
static void PrintVerbose()
{
	char s[255];
	if (duration == 0)
		sprintf(s, "continous");
	else
		sprintf(s, "%.1f seconds", duration);
	fprintf(stderr, 
"           Frequency: %d Hertz\n"
"       Sampling Rate: %d samples/second\n"
"            Duration: %s\n"
"           Left Gain: %.1f%%\n"
"          Right Gain: %.1f%%\n"
"            Waveform: %s\n"
"            Channels: %d\n"
"         Sample Size: %d bits\n",
			frequency, rate, s, lLevel, rLevel, Name(wave), channels,
			bits);
}

/* open output device */
static void OpenDevice()
{
	unsigned int pcm_format;

	if (bits == 8)
		pcm_format = CSL_PCM_FORMAT_U8;
	else
		pcm_format = CSL_PCM_FORMAT_S16_LE;
	
	error = csl_driver_init (NULL, &driver);        /* choose backend */
	if (error)
		csl_error ("unable to initialize driver: %s", csl_strerror (error));
	
	/* open PCM output stream */
	error = csl_pcm_open_output (driver, "wave", rate, channels, (CslPcmFormatType)pcm_format, &stream);
	if (error)
		csl_error ("failed to open output device: %s", csl_strerror (error));
}

/* close output device */
/* and also free up data buffer */
static void CloseDevice()
{
	csl_pcm_close (stream);
	csl_driver_shutdown (driver);
	free(buffer);
}

/* normalize a number to fit in an 8-bit unsigned value */
static inline void Normalize8(int *n) {
	if (*n < 0)
		*n =  0;
	if (*n > 255)
		*n = 255;
}

/* normalize a number to fit in a 16-bit signed value */
static inline void Normalize16(int *n) {
	if (*n < -32768)
		*n =  -32768;
	if (*n > 32767)
		*n = 32767;
}

/* generate one cycle of a sine waveform */
static void GenerateSineWave()
{
	int numPoints = rate/frequency;
	int i, j;
	
	buffer = (unsigned char *) malloc(numPoints*channels*(bits/8));
	
	if (bits == 8) {    
		if (channels == 1) {
			for (i = 0 ; i < numPoints ; i++) {
				j = (int)(128 * lLevel/100 * sin(2.0*M_PI*i/numPoints) + 128);
				Normalize8(&j);
				buffer[i] = j;
			}
		} else {
			for (i = 0 ; i < numPoints ; i++) {
				j = (int)(128 * lLevel/100 * sin(2.0*M_PI*i/numPoints) + 128);
				Normalize8(&j);
				buffer[2*i] = j;
				j = (int)(128 * rLevel/100 * sin(2.0*M_PI*i/numPoints) + 128);
				Normalize8(&j);
				buffer[2*i+1] = j;
			}
		}
	}
	if (bits == 16) {    
		if (channels == 1) {
			for (i = 0 ; i < numPoints ; i++) {
				j = (int)(32768 * lLevel/100 * sin(2.0*M_PI*i/numPoints));
				Normalize16(&j);
				buffer[2*i]   = j % 256; /* low order byte */
				buffer[2*i+1] = j / 256; /* high order byte */
			}
		} else {
			for (i = 0 ; i < numPoints ; i++) {
				j = (int)(32768 * lLevel/100 * sin(2.0*M_PI*i/numPoints));
				Normalize16(&j);
				buffer[4*i]   = j % 256;
				buffer[4*i+1] = j  / 256;
				j = (int)(32768 * rLevel/100 * sin(2.0*M_PI*i/numPoints));
				Normalize16(&j);
				buffer[4*i+2] = j % 256;
				buffer[4*i+3] = j /256;
			}
		}
	}
}

/* generate one cycle of a square waveform */
static void GenerateSquareWave()
{
	int numPoints = rate/frequency;
	int i, j = 0;

	buffer = (unsigned char *) malloc(numPoints*channels*bits/8);
	
	if (bits == 8) {
		if (channels == 1) {
			for (i = 0 ; i < numPoints ; i++) {
				if (i < numPoints/2)
					buffer[i] = (int)(lLevel/100*127 + 128);
				else
					buffer[i] = (int)(128 - lLevel/100*128);
			}
		} else {
			for (i = 0 ; i < numPoints ; i++) {
				if (i < numPoints/2) {
					buffer[2*i]   = (int)(lLevel/100*127 + 128);
					buffer[2*i+1] = (int)(rLevel/100*127 + 128);
				} else {
					buffer[2*i]   = (int)(128 - lLevel/100*128);
					buffer[2*i+1] = (int)(128 - rLevel/100*128);
				}
			}
		}
	}
	if (bits == 16) {
		if (channels == 1) {
			for (i = 0 ; i < numPoints ; i++) {
				if (i < numPoints/2)
					j = (int)(32767*lLevel/100);
				else
					j = (int)(-32767*lLevel/100);
				buffer[2*i]   = j % 256;
				buffer[2*i+1] = j / 256;
			}
		} else {
			for (i = 0 ; i < numPoints ; i++) {
				if (i < numPoints/2) {
					j = (int)(32767*lLevel/100);
					buffer[4*i]   = j % 256;
					buffer[4*i+1] = j / 256;
					j = (int)(32767*rLevel/100);
					buffer[4*i+2] = j % 256;
					buffer[4*i+3] = j / 256;
				} else {
					j = (int)(-32767*lLevel/100);
					buffer[4*i]   = j % 256;
					buffer[4*i+1] = j / 256;
					j = (int)(-32767*rLevel/100);
					buffer[4*i+2] = j % 256;
					buffer[4*i+3] = j / 256;
				}
			}
		}
	}
}

/* generate one cycle of a triangle waveform */
static void GenerateTriangleWave()
{
	int numPoints = rate/frequency;
	int i = 0, j;
	buffer = (unsigned char *) malloc(numPoints*channels*bits/8);
	
	if (bits == 8) {
		if (channels == 1) {
			for (i = 0 ; i < numPoints ; i++) {
				if (i < numPoints/4)
					j = 128 + (int)(lLevel/100*512*i/numPoints);
				else if (i < 3*numPoints/4)
					j = 384 - (int)(lLevel/100*512*i/numPoints);
				else
					j = (int)(lLevel/100*512*i/numPoints) - 384;
				Normalize8(&j);
				buffer[i] = j;
			}
		} else {
			for (i = 0 ; i < numPoints ; i++) {
				if (i < numPoints/4)
					j = 128 + (int)(lLevel/100*512*i/numPoints);
				else if (i < 3*numPoints/4)
					j = 384 - (int)(lLevel/100*512*i/numPoints);
				else
					j = (int)(lLevel/100*512*i/numPoints) - 384;
				Normalize8(&j);
				buffer[2*i]   = j;
				if (i < numPoints/4)
					j = 128 + (int)(rLevel/100*512*i/numPoints);
				else if (i < 3*numPoints/4)
					j = 384 - (int)(rLevel/100*512*i/numPoints);
				else
					j = (int)(rLevel/100*512*i/numPoints) - 384;
				Normalize8(&j);
				buffer[2*i+1] = j;
			}
		}
	}
	if (bits == 16) {
		if (channels == 1) {
			for (i = 0 ; i < numPoints ; i++) {
				if (i < numPoints/4)
					j = (int)(32767*(lLevel/100)*(i/(numPoints/4)));
				else if (i < 3*numPoints/4)
					j = (int)(65534 - 32767*(lLevel/100)*(i/numPoints));
				else
					j = (int)(32767*(lLevel/100*512*i/numPoints) - 131068);
				Normalize16(&j);
				buffer[2*i]   = j % 256;
				buffer[2*i+1] = j / 256;
			}
		} else {
			for (i = 0 ; i < numPoints ; i++) {
				if (i < numPoints/4)
					j = (int)(32767*(lLevel/100)*(i/(numPoints/4)));
				else if (i < 3*numPoints/4)
					j = (int)(65534 - 32767*(lLevel/100)*(i/numPoints));
				else
					j = (int)(32767*(lLevel/100*512*i/numPoints) - 131068);
				Normalize16(&j);
				buffer[4*i]   = j % 256;
				buffer[4*i+1] = j / 256;
				if (i < numPoints/4)
					j = (int)(32767*(rLevel/100)*(i/(numPoints/4)));
				else if (i < 3*numPoints/4)
					j = (int)(65534 - 32767*(rLevel/100)*(i/numPoints));
				else
					j = (int)(32767*(rLevel/100*512*i/numPoints) - 131068);
				Normalize16(&j);
				buffer[4*i+2] = j % 256;
				buffer[4*i+3] = j / 256;
			}
		}
	}
}

/* generate one cycle of a ramp waveform */
static void GenerateRampWave()
{
	int numPoints = (rate/frequency);
	int i, j;
	
	buffer = (unsigned char *) malloc(numPoints*channels*bits/8);
	if (bits == 8) {
		if (channels == 1) {
			for (i = 0 ; i < numPoints ; i++) {
				buffer[i] = (int)(lLevel/100*256*i/numPoints);
			}
		} else {
			for (i = 0 ; i < numPoints ; i++) {
				buffer[2*i]   = (int)(lLevel/100*256*i/numPoints);
				buffer[2*i+1] = (int)(rLevel/100*256*i/numPoints);
			}
		}
	}
	if (bits == 16) {
		if (channels == 1) {
			for (i = 0 ; i < numPoints ; i++) {
				j = (int)(32767*(lLevel/100)*i/numPoints - 32767);
				buffer[2*i]   = j % 256;
				buffer[2*i+1] = j / 256;
			}
		} else {
			for (i = 0 ; i < numPoints ; i++) {
				j = (int)(32767*(lLevel/100)*i/numPoints - 32767);
				buffer[4*i]   = j % 256;
				buffer[4*i+1] = j / 256;
				j = (int)(32767*(rLevel/100)*i/numPoints - 32767);
				buffer[4*i+2] = j % 256;
				buffer[4*i+3] = j / 256;
			}
		}
	}
}

/* generate one cycle of a waveform */
static void GenerateWave()
{
	int numPoints = rate/frequency;
	switch (wave) {
	  case sine:
		  GenerateSineWave();
		  break;
	  case square:
		  GenerateSquareWave();
		  break;
	  case triangle:
		  GenerateTriangleWave();
		  break;
	  case ramp:
		  GenerateRampWave();
		  break;
	}
	if (verbose)
		fprintf(stderr, "         Data Points: %d per cycle\n", numPoints);
}

/* write cycles of wave data to a device */
static void WriteWaveData()
{
	int numPoints = (rate/frequency)*channels*(bits/8);
	int numCycles = (int)(frequency*duration);
	int i;

/*
 * Originally I wrote one cycle of the waveform at a time, but that
 * introduces a lot of overhead at low sampling rates, so we use a
 * larger buffer here (arbitrarily 10X larger)
 */
#if 1
	const int mult = 10;
	unsigned char *buff = malloc(mult*numPoints);
	
	for (i = 0; i < mult*numPoints; i++)
		buff[i] = buffer[i % numPoints];
	
	for (i = 0 ; (i < numCycles/mult) || duration == 0 ; i++)
		csl_pcm_write(stream, mult*numPoints, buff);
	
	free(buff);
#endif
	
#if 0
	for (i = 0 ; (i < numCycles) || duration == 0 ; i++)
		csl_pcm_write(stream, numPoints, buffer);
#endif
}

/* main program */
int main(int argc, char *argv[])
{
	ParseOptions(argc, argv);
	OpenDevice();
	if (verbose)
		PrintVerbose();
	GenerateWave();
	WriteWaveData();
	CloseDevice();
	return 0;
}
