/*
 * sound.c: raw test sound synthesis and raw sound storage to AU file.
 */

/* Written by Matt Harang <matt@ioioio.net>
 * Mathematics and some bugfixes by Andy Goth <unununium@openverse.com>
 *
 * Link with -lm for fmod, sin, ...
 *
 * This program makes no attempt to be efficient */

#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define E_OK 0
#define E_PARAM 3
#define E_MEMORY 4


void swap_bytes(void* a, size_t elem_size, int num_elem)
{
	char foo;
	char* sp;
	char* dp;

	sp = a;
	dp = (char*)a + elem_size - 1;
	while (num_elem) { /* for each elem: */
		while (sp < dp) { /* for each byte: */
			/* swap bytes */
			foo = *sp;
			*sp = *dp;
			*dp = foo;
			sp++;
			dp--;
		}
		/* update ptrs to the end and start of next elem,
		 * respectively */
		dp += elem_size / 2 + elem_size;
		sp += elem_size / 2;
		num_elem--;
	}
}


void die(char* s)
{
	if (s) fprintf(stderr, "%s\n", s);
	exit(1);
}


void perror_die(char* s)
{
	perror(s);
	exit(1);
}


void nfloat_to_s16(float* src, signed short* dest, int n)
{
	int i;

	for (i = 0; i < n; i++)
		/* There's a little bit of error in this calculation, but
		 * who cares? :-) */
		dest[i] = (signed short)(src[i] * (float)32767.0);
}


/*
 * Generate a periodic wave that ramps from start_freq to end_freq on an
 * exponential curve, by evaluating (*func) for the first n sample points, and
 * storing the results in buf. If this function is to be called repeatedly, or
 * in conjunction with other periodic wave-generating functions, the resulting
 * waves can be made continuous by saving the ending phase after each call and
 * passing it as the starting phase of the next call, with the phase
 * parameter.
 */
int gen_periodic_wave_ramp_freq(
		float* buf,		/* buffer */
		int n,			/* buffer length (floats) */
		int samp_rate,		/* buffer sampling rate */

		double (*func)(double),	/* periodic func to run on buf */
		double func_period,	/* period of func() */
		
		float start_freq,
		float end_freq,
		float* phase		/* func starting/ending phase */
	)
{
	int i;		/* sample index into buf */
	double x;	/* function input phase counter */
	double p;	/* period */
	double dp;	/* period change */

	/* avoid division by zero... */
	if (n == 0) return E_PARAM;
	if (start_freq == 0.0) return E_PARAM;
	
	/* dp = nth root of (end_freq / start_freq), for exp. curve */
	dp = pow(end_freq / start_freq, 1.0 / (float)n);
	
	/* fill table */
	p = func_period * start_freq / samp_rate;
	if (phase != NULL) x = *phase;
	else x = 0.0;
	for (i = 0; i < n; i++) {
		buf[i] = func(x);
		x += p;
		p *= dp;
	}

	/* maybe calculate and save ending phase */
	if (phase != NULL) {
		while (x > func_period) x -= func_period;
		*phase = x;
	}
	return E_OK;
}


/*
 * Do a linear crossfade between buf1 and buf2, by taking weighted averages of
 * samples from each and storing the results in dest. pan_start and pan_end
 * are the starting and ending 'pans' (0.0 = 100% of buf1, 0% of buf2; 1.0 =
 * 0% of buf1, 100% of buf2).
 */
int crossfade(float* buf1, float* buf2, float* dest, int n,
		double pan_start, double pan_end)
{
	int i;
	double p;	/* pan position */
	double dp;	/* pan change */

	/* avoid division by zero... */
	if (n == 0) return E_PARAM;

	p = pan_start;
	dp = (pan_end - pan_start) / (double)n;
	
	for (i = 0; i < n; i++) {
		dest[i] = buf1[i] * (1.0 - p) + buf2[i] * p;
		p += dp;
	}

	return E_OK;
}


/*
 * Do a linear fade between two amplitutes (must be between 0.0 and 1.0).
 */
int fade(float* src, float* dest, int n, double amp_start, double amp_end)
{
	int i;
	double a;	/* amplitude */
	double ap;	/* amp. change */

	/* avoid division by zero... */
	if (n == 0) return E_PARAM;

	a = amp_start;
	ap = (amp_end - amp_start) / (double)n;
	
	for (i = 0; i < n; i++) {
		dest[i] = src[i] * a;
		a += ap;
	}

	return E_OK;
}


/*
 * Clip the first n samples of buf, by making sure none are greater than 1 or
 * less than -1.
 */
int clip(float* buf, int n)
{
	int i;

	for (i = 0; i < n; i++) {
		if (buf[i] > 1.0) buf[i] = 1.0;
		else if (buf[i] < -1.0) buf[i] = -1.0;
	}

	return E_OK;
}


double saw(double x)
{
	return fmod(x, 2.0) - 1.0;
}


double triangle(double x)
{
	double y = fmod(x, 2.0);
	if (y < 1) return y * 2 - 1.0;
	else return 3.0 - 2 * y;
}


double square(double x)
{
	if (fmod(x, 2.0) < 1) return -1.0;
	else return 1.0;
}


/* The ugliest main() I've ever written... */
int main(void)
{
	FILE* file;
	char* header;
	float* float_buf;
	char* buf;
	int length;		/* length of buffers, in samples */
	float phase = 0.0;
	int n;
	int i;
	int samp_rate = 44100;		/* 44.1 KHz (leave this value alone) */

	/* set these as you like */
	float duration = 10;		/* Ten seconds */
	float start_freq = 440;		/* Start with middle A */
	float end_freq = 44100 * 3;	/* Let's totally blast the Nyquist */

	/* Just in case we want to generate noise...? */
	srand(time(NULL));

	/* allocate buffers */
	length = samp_rate * duration;
	if ((float_buf = malloc(sizeof(float) * length)) == NULL)
		die("malloc(): error allocating float buffer");
	if ((buf = malloc(sizeof(short) * length)) == NULL)
		die("malloc(): error allocating integer buffer");
	if ((header = malloc(28)) == NULL)
		die("malloc(): error allocating header buffer");
	
	/* open output file */
	if ((file = fopen("sound.au", "wb")) == NULL) perror_die("fopen()");

	/* build AU header */
	memcpy(header, ".snd", 4);
	*(unsigned long*)(header + 4) = 28UL;		/* data offset */
	*(unsigned long*)(header + 8) = ~0UL;
	*(unsigned long*)(header + 12) = 3UL;		/* 16-bit signed PCM */
	*(unsigned long*)(header + 16) = 44100UL;	/* sampling rate */
	*(unsigned long*)(header + 20) = 1UL;		/* channels */
	*(unsigned long*)(header + 24) = 0UL;

	/* write header to disk */
	swap_bytes(header + 4, 4, 5);
	fwrite(header, 4, 6, file);

	/* generate sound */

	gen_periodic_wave_ramp_freq(
		float_buf,
		length,
		samp_rate,
		sin,
		2 * M_PI,
		start_freq,
		end_freq,
		NULL
	);

	/* convert format from normalized floating-point samples to 16-bit
	 * signed linear PCM samples, and reverse endianness */
	nfloat_to_s16(float_buf, (short*)buf, length);
	swap_bytes(buf, 2, length);

	/* write PCM samples */
	n = fwrite(buf, sizeof(short), length, file);
	printf("%d samples written\n", n);

	/* cleanup */
	fclose(file);
	free(buf);
	free(float_buf);
	free(header);
	
	return EXIT_SUCCESS;
}

/* EOF */

