/* Copyright 2008, Unpublished Work of Technologic Systems
 * All Rights Reserved.
 *
 * THIS WORK IS AN UNPUBLISHED WORK AND CONTAINS CONFIDENTIAL,
 * PROPRIETARY AND TRADE SECRET INFORMATION OF TECHNOLOGIC SYSTEMS.
 * ACCESS TO THIS WORK IS RESTRICTED TO (I) TECHNOLOGIC SYSTEMS 
 * EMPLOYEES WHO HAVE A NEED TO KNOW TO PERFORM TASKS WITHIN THE SCOPE
 * OF THEIR ASSIGNMENTS AND (II) ENTITIES OTHER THAN TECHNOLOGIC
 * SYSTEMS WHO HAVE ENTERED INTO APPROPRIATE LICENSE AGREEMENTS.  NO
 * PART OF THIS WORK MAY BE USED, PRACTICED, PERFORMED, COPIED, 
 * DISTRIBUTED, REVISED, MODIFIED, TRANSLATED, ABRIDGED, CONDENSED, 
 * EXPANDED, COLLECTED, COMPILED, LINKED, RECAST, TRANSFORMED, ADAPTED
 * IN ANY FORM OR BY ANY MEANS, MANUAL, MECHANICAL, CHEMICAL, 
 * ELECTRICAL, ELECTRONIC, OPTICAL, BIOLOGICAL, OR OTHERWISE WITHOUT
 * THE PRIOR WRITTEN PERMISSION AND CONSENT OF TECHNOLOGIC SYSTEMS.
 * ANY USE OR EXPLOITATION OF THIS WORK WITHOUT THE PRIOR WRITTEN
 * CONSENT OF TECHNOLOGIC SYSTEMS COULD SUBJECT THE PERPETRATOR TO
 * CRIMINAL AND CIVIL LIABILITY.
 */

/* Author: Jesse Off <support@embeddedARM.com> */

#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <sched.h>
#include <math.h>
#include <limits.h>
#include <signal.h>
#include <string.h>

#define NTAP 127
static int filter_coef_quarter[NTAP] = {
	0, 2292, 9423, 10479, -11252, -57135, -98049, -84297, 17379, 181747,
	313317, 288609, 41321, -359130, -698872, -714476, -261033, 540721,
	1289484, 1476040, 787052, -623648, -2087767, -2697889, -1819159,
	428089, 3033817, 4491454, 3607188, 320994, -3972419, -6924294,
	-6438987, -2007563, 4621181, 9989219, 10630368, 5142696, -4536471,
	-13581599, -16538791, -10416252, 3056432, 17492507, 24649414,
	18861941, 848798, -21422286, -35866367, -32385851, -9062674,
	25014669, 52500087, 55678769, 26232604, -27906527, -82651992,
	-105918954, -71104049, 29784196, 179485810, 339045845, 460895240,
	506440842, 460895240, 339045845, 179485810, 29784196, -71104049,
	-105918954, -82651992, -27906527, 26232604, 55678769, 52500087,
	25014669, -9062674, -32385851, -35866367, -21422286, 848798,
	18861941, 24649414, 17492507, 3056432, -10416252, -16538791,
	-13581599, -4536471, 5142696, 10630368, 9989219, 4621181, -2007563,
	-6438987, -6924294, -3972419, 320994, 3607188, 4491454, 3033817,
	428089, -1819159, -2697889, -2087767, -623648, 787052, 1476040,
	1289484, 540721, -261033, -714476, -698872, -359130, 41321,
	288609, 313317, 181747, 17379, -84297, -98049, -57135, -11252,
	10479, 9423, 2292, 0
};


static int filter_coef_half[NTAP] = {
	0, -1705, 6634, 18685, -21684, -60623, 35426, 134390, -34609,
	-244376, 2231, 391094, 82341, -569438, -242713, 766694, 504032,
	-960572, -890676, 1117546, 1423034, -1191870, -2113453, 1125593,
	2961605, -849898, -3949604, 287913, 5037292, 640939, -6158175,
	-2015215, 7216401, 3904648, -8085065, -6362800, 8605903, 9419766,
	-8590055, -13075661, 7819071, 17295608, -6044527, -22006871,
	2983300, 27098648, 1696890, -32424817, -8412162, 37809683,
	17739750, -43056468, -30591451, 47958063, 48630382, -52309262,
	-75412665, 55919575, 120274868, -58625598, -217764265, 60301922,
	680162526, 1012871289, 680162526, 60301922, -217764265, -58625598,
	120274868, 55919575, -75412665, -52309262, 48630382, 47958063,
	-30591451, -43056468, 17739750, 37809683, -8412162, -32424817,
	1696890, 27098648, 2983300, -22006871, -6044527, 17295608, 7819071,
	-13075661, -8590055, 9419766, 8605903, -6362800, -8085065, 3904648,
	7216401, -2015215, -6158175, 640939, 5037292, 287913, -3949604,
	-849898, 2961605, 1125593, -2113453, -1191870, 1423034, 1117546,
	-890676, -960572, 504032, 766694, -242713, -569438, 82341, 391094,
	2231, -244376, -34609, 134390, 35426, -60623, -21684, 18685, 6634,
	-1705, 0
};

static short filter_fifo[NTAP];
static int filter_idx = 0;
static int srate, nchan, nbits; 
static unsigned int len;
static int tried_sox = 0;
static int wav_is_pipe = 0;
static short *samplebuf;
static volatile unsigned short *audioregs;
static FILE *wav;

static
void silence(int dummy) {
	int i;
	/* Make sure we leave the buffer with only silence playing */
	for (i = 0; i < 4096; i++) samplebuf[i] = 0;
	if (wav_is_pipe) pclose(wav); else fclose(wav);
	exit(0);
}

static
short filter_half(short in1, short in2) {
	int i, j;
	long long acc = 0;
	int *hh = filter_coef_half;
	int Z;
	int o;

	filter_fifo[filter_idx++] = in1;
	if (filter_idx == NTAP) filter_idx = 0;

	filter_fifo[filter_idx++] = in2;
	if (filter_idx == NTAP) filter_idx = 0;

	for (i = filter_idx, j = 0; j < NTAP; j++) {
		Z = (int)filter_fifo[i++];
		if (i == NTAP) i = 0;
		acc += (long long)(*hh++) * Z;
	}
	
	o = acc >> 31;
	if (o > SHRT_MAX) o = SHRT_MAX;
	else if (o < SHRT_MIN) o = SHRT_MIN;
	return (short)o;
} 

static
short filter_quarter(short in1, short in2, short in3, short in4) {
	int i, j;
	long long acc = 0;
	int *hh = filter_coef_quarter;
	int Z;
	int o;

	filter_fifo[filter_idx++] = in1;
	if (filter_idx == NTAP) filter_idx = 0;

	filter_fifo[filter_idx++] = in2;
	if (filter_idx == NTAP) filter_idx = 0;

	filter_fifo[filter_idx++] = in3;
	if (filter_idx == NTAP) filter_idx = 0;

	filter_fifo[filter_idx++] = in4;
	if (filter_idx == NTAP) filter_idx = 0;

	for (i = filter_idx, j = 0; j < NTAP; j++) {
		Z = (int)filter_fifo[i++];
		if (i == NTAP) i = 0;
		acc += (long long)(*hh++) * Z;
	}
	
	o = acc >> 31;
	if (o > SHRT_MAX) o = SHRT_MAX;
	else if (o < SHRT_MIN) o = SHRT_MIN;
	return (short)o;
} 

static
int try_sox(char *file) {
	char cmd[128];

	if (tried_sox || !strcmp(file, "-")) {
		return 0;
	}

	tried_sox = 1;
	snprintf(cmd, sizeof(cmd), 
	  "exec sox \"%s\" -s -w -t wav - 2>/dev/null", file);
	fclose(wav);
	wav = popen(cmd, "r");
	if (wav == NULL) return 0;
	wav_is_pipe = 1;
	return 1;
}

static inline
short get_sample(void) {
	short s, s2;
	int sum;
	int n;

	if (nbits == 16) {
		s = getc(wav);
		s |= getc(wav) << 8;
		n = 2;
		if (nchan == 2) {
			s2 = getc(wav);
			s2 |= getc(wav) << 8;
			sum = (int)s2 + (int)s;
			sum = sum >> 1;
			s = (short)sum;
			n += 2;
		}
	} else {
		s = (getc(wav) - 128) << 8;
		n = 1;
		if (nchan == 2) {
			s2 = (getc(wav) - 128) << 8;
			sum = (int)s2 + (int)s;
			sum = sum >> 1;
			s = (short)sum;
			n++;
		}
	}
	if (len < INT_MAX) {
		if (len >= n) len -= n;
		else len = 0;
	}
	return s;
}

int main(int argc, char **argv) {
	int devmemfd, head, tail, i, fmtlen;
	enum { NORMAL, HALF, QUARTER } fmode;
	short s;
	volatile unsigned short *regs, *audioregs, *sysconregs;
	unsigned char header[128];
	struct sched_param sched;


	if (argc == 2 && (!strncmp(argv[1], "-h", 2) || 
	  !strncmp(argv[1], "--help", 6))) {
		fprintf(stderr, "usage: %s SNDFILE ...\n", argv[0]);
		return 0;
	}

	devmemfd = open("/dev/mem", O_RDWR|O_SYNC);
	if (devmemfd == -1) {
		perror("/dev/mem");
		return 9;
	}
	samplebuf = (short *) mmap(0, 8192,
	  PROT_READ | PROT_WRITE, MAP_SHARED, devmemfd, 0x60000000);
	regs = (short *) mmap(0, 4096, 
	  PROT_READ | PROT_WRITE, MAP_SHARED, devmemfd, 0x600ff000);

	assert (samplebuf != MAP_FAILED);
	assert (regs != MAP_FAILED);
	audioregs = regs + (0x100 / sizeof(short));
	sysconregs = regs + (0x80 / sizeof(short));

	if ((sysconregs[0] >> 4) != 0xcde) return 0;

	if (argc == 1) {
		fprintf(stderr, "usage: %s SNDFILE ...\n", argv[0]);
		return 0;
	}
	nice(-20);
	sched.sched_priority = 50;
	sched_setscheduler(0, SCHED_FIFO, &sched);

	if (!(sysconregs[3] & 0x800)) { /* speaker not on, do init */
		for (i = 0; i < 4096; i++) samplebuf[i] = 0; /* prime silence */
		sysconregs[3] |= 0x800; /* enable speaker */
		audioregs[1] = 0x108; /* line driver power up */
		audioregs[1] = 0x65e; /* line out unmute */
		usleep(100000);
	}

	signal(SIGTERM, silence);
	signal(SIGINT, silence);
	signal(SIGHUP, silence);

nextfile:
	if (!strcmp(argv[1], "-")) wav = stdin; 
	else wav = fopen(argv[1], "r");
	if (wav == NULL) {
		perror(argv[1]);
		return 7;
	}

retry:
	bzero(header, sizeof(header));
	fread(header, 20, 1, wav);
	if (strncmp(&header[0], "RIFF", 4) || strncmp(&header[8], "WAVE", 4)
	  || strncmp(&header[12], "fmt ", 4)) {
		if (!try_sox(argv[1])) {
			fprintf(stderr, "bad/unsupported sound file\n");
			return 1;
		} else goto retry;
	}
	fmtlen = header[16]|(header[17]<<8)|(header[18]<<16)|(header[19]<<24);
	fread(header, fmtlen, 1, wav);
	if ((header[0]|(header[1]<<8)) != 1) { // must be "PCM/uncompressed"
		if (!try_sox(argv[1])) {
			fprintf(stderr, "bad/unsupported sound file\n");
			return 1;
		} else goto retry;
	}
	nchan = header[2]|(header[3]<<8);
	if (!(nchan == 1 || nchan == 2)) {  // must be stereo or mono
		fprintf(stderr, "bad/unsupported sound file\n");
		return 3;
	}
	srate = header[4]|(header[5]<<8)|(header[6]<<16)|(header[7]<<24);
	if (!(srate == 11025 || srate == 22050 || srate == 44100 
	  || srate == 8000 || srate == 12000 || srate == 24000
	  || srate == 48000)) {
		fprintf(stderr, "bad/unsupported sound file\n");
		return 4;
	}
	fmode = NORMAL;
	if (srate == 8000) { /* change PLL settings */
		audioregs[1] = 0x33a; /* write reg 3, value 0x3a */
		audioregs[1] = 0x490; /* reg 4, value 0x90, etc.. etc.. */
	} else if (srate == 11025 || srate == 22050 || srate == 44100) {
		audioregs[1] = 0x34a;
		audioregs[1] = 0x4fd;
		if (srate == 22050) fmode = HALF;
		else if (srate == 44100) fmode = QUARTER;
	} else if (srate == 12000 || srate == 24000 || srate == 48000) {
		audioregs[1] = 0x332;
		audioregs[1] = 0x4bb;
		if (srate == 24000) fmode = HALF;
		else if (srate == 48000) fmode = QUARTER;
	}
	nbits = header[14]|(header[15]<<8);
	if (!(nbits == 8 || nbits == 16)) {
		fprintf(stderr, "bad/unsupported sound file\n");
		return 5;
	}
	while (!feof(wav)) { /* search for data section */
		fread(header, 8, 1, wav);
		len = header[4]|(header[5]<<8)|(header[6]<<16)|(header[7]<<24);
		if (strncmp(&header[0], "data", 4) == 0) break;
		for (i = 0; i < len && !feof(wav); i++) getc(wav);
	}

	tail = head = (audioregs[2] & 0x1fff) >> 1;
	s = 0;
	do {
		short s2, s3, s4;
		samplebuf[tail] = s;
		tail = (tail + 1) & 0xfff;

		/* get_sample() handles stereo/mono and 8/16 bit conversion */
		s = get_sample();
		/* filter_*() handles sample rate downconversion */
		if (fmode == HALF) {
			s2 = get_sample();
			s = filter_half(s, s2);
		} else if (fmode == QUARTER) {
			s2 = get_sample();
			s3 = get_sample();
			s4 = get_sample();
			s = filter_quarter(s, s2, s3, s4);
		}

		while (head == tail) {
			usleep(10000);
			head = (audioregs[2] & 0x1fff) >> 1;
		}
	} while (!feof(wav) && len);

	/* Empty FIR filter if we're using it */
	if (fmode != NORMAL) for (i = 0; i < NTAP;) {
		if (fmode == HALF) {
			samplebuf[tail] = filter_half(0, 0);
			i += 2;
		} else {
			samplebuf[tail] = filter_quarter(0, 0, 0, 0);
			i += 4;
		}
		tail = (tail + 1) & 0xfff;

		while (head == tail) {
			usleep(10000);
			head = (audioregs[2] & 0x1fff) >> 1;
		}
	}

	if (argc > 2) {
		argv++;
		argc--;
		if (wav_is_pipe) pclose(wav);
		else fclose(wav);
		wav_is_pipe = 0;
		tried_sox = 0;
		goto nextfile;
	}


	/* Make sure we leave the buffer with only silence playing */
	for (i = 0; i < 4096; i++) {
		samplebuf[tail] = 0;
		tail = (tail + 1) & 0xfff;

		while (head == tail) {
			usleep(10000);
			head = (audioregs[2] & 0x1fff) >> 1;
		}

	}
	return 0;
}

