/**************************************************************************************************/
/* WAV-File player/recorder - Version 0.1 							  */
/* using code of recplay.c from Audio Utilities for Linux 2.0 (c) Liam Corner                     */
/* (C) FF-Soft '93 										  */
/* Andre Fuechsel; email: af1@irz.inf.tu-dresden.de / fuechsel@freia.inf.tu-dresden.de            */
/**************************************************************************************************/

#include <stdio.h>
#include <getopt.h>
#include <fcntl.h>
#include <linux/soundcard.h>

#define RECORD 0
#define PLAY 1
#define AUDIO "/dev/dsp"

typedef unsigned long DWORD;
typedef unsigned short WORD;

typedef struct {		/* header for WAV-Files */
	char main_chunk[4];	/* 'RIFF' */
	DWORD length;		/* length of file */
	char chunk_type[4];	/* 'WAVE' */
	char sub_chunk[4];	/* 'fmt' */
	DWORD length_chunk;	/* length sub_chunk, always 16 bytes */
	WORD format;		/* always 1 = PCM-Code */
	WORD modus;		/* 1 = Mono, 2 = Stereo */
	DWORD sample_fq;	/* Sample Freq */
	DWORD byte_p_sec;	/* Data per sec */
	WORD byte_p_spl;	/* bytes per sample, 1=8 bit, 2=16 bit (mono)
						     2=8 bit, 4=16 bit (stereo) */
	WORD bit_p_spl;		/* bits per sample, 8, 12, 16 */
	char data_chunk[4];	/* 'data' */
	DWORD data_length;	/* length of data */
} wave_header;

int quiet_mode=0;
int time_limit=0;
int stereo = 0;
DWORD speed = 0, sample_size = 0;
int force_speed = 0, force_stereo = 0, force_sample_size = 0;
int mode, omode;

void err(char *err_str)			/* future expansion planned */ 
{
	fprintf(stderr, "%s \n", err_str);
}

int header_check(wave_header *head) 
{
	unsigned p;
	int severity = 0;
	
	p = head->length - head->data_length;
	if ((p != 36) && (p != 37)) { 
		err("wrong length difference");
		severity = 2;
	}

	if (strncmp(head->main_chunk, "RIFF", 4)) {
		err("not in RIFF-Format");
		severity = 2;
	}

	if (strncmp(head->chunk_type, "WAVE", 4)) {
		err("not a WAVE-Type");
		severity = 2;
	}
		
	if (strncmp(head->sub_chunk, "fmt ", 4)) {
		err("not a WAVE-Format");
		severity = 2;
	}

	if ((head->sample_fq * head->byte_p_spl) != head->byte_p_sec) {
		err("wrong speed/freq."); 
		if (!severity) severity = 1;
	}

	if (severity == 2) return(0); else return(1);
}

void recplay(char *name)
{
	int l, c, count;
	int fd, fd1, audio;
	int abuf_size;
	DWORD length; 
	wave_header head;
	char *audiobuf;
	char tmp_name[] = "tmp.wav";		/* temporary file */

	if (mode == PLAY) {
		if (!name) {
			fd = 0;
			name = "stdin";
		} else {
			if ((fd = open (name, O_RDONLY, 0)) == -1) {
				perror(name);
				exit(-1);
			}
		}
		read(fd, &head, sizeof(head));
		if (!header_check(&head)) exit(-1);

		if (!force_stereo) stereo = (head.modus == 2);
		length = head.data_length;
		if (!force_speed) speed = head.sample_fq;
		if (!force_sample_size) sample_size = head.bit_p_spl;
	} else {				/* mode = RECORD */
		remove(tmp_name);
		if ((fd = open (tmp_name, O_WRONLY | O_CREAT, 0666)) == -1) {
			perror(tmp_name);
			exit(-1);
		}
		/* create header */
		strncpy(head.main_chunk, "RIFF", 4);
		strncpy(head.chunk_type, "WAVE", 4);
		strncpy(head.sub_chunk, "fmt ", 4);
		strncpy(head.data_chunk, "data", 4);
		head.length = 0;			/* to be calculated later */ 
		head.length_chunk = 16;
		head.format = 1;
		head.modus = stereo ? 2 : 1;		
		head.sample_fq = speed;

		if (!stereo)	 		/* mono */
			if (sample_size == 8)
				head.byte_p_spl = 1;
			else 
				head.byte_p_spl = 2;
		else				/* stereo */
			if (sample_size == 8)
				head.byte_p_spl = 2;
			else
				head.byte_p_spl = 4;

		head.byte_p_sec = speed * head.byte_p_spl;

		head.bit_p_spl = sample_size;
		head.data_length = 0;			/* to be calculated later */
	}

	if (!time_limit) 
		count = 0x7fffffff;
	else {
		count = time_limit * speed;
		if (stereo) count *= 2;
		if (sample_size != 8) count *=2;
	}

	if (!quiet_mode) {
		if (mode == PLAY)
			fprintf(stderr, "Playing: %s", name);
		else
			if (!name) 
				fprintf(stderr, "Recording: stdout");
			else
				fprintf(stderr, "Recording: %s", name);
		if (!time_limit) 
			fprintf(stderr, " (full-song)\n");
		else
			fprintf(stderr, " %u secs\n", time_limit);
		if (stereo) 
			fprintf(stderr, "Stereo-Sample");
		else
			fprintf(stderr, "Mono-Sample");
		fprintf(stderr, ", speed: %u Hz, %u Bit per Sample\n", speed, sample_size);
	}

	audio = open(AUDIO, omode, 0);
	if (audio == -1) {
		perror(AUDIO);
		exit(-1);
	}

	abuf_size = ioctl(audio, SNDCTL_DSP_GETBLKSIZE);
	if (abuf_size < 4096 || abuf_size > 65536) {
		if (abuf_size == -1)
			perror(AUDIO);
		else
			fprintf(stderr, "Invalid audio buffer size %d\n", abuf_size);
		exit(-1);
	}

	if ((audiobuf = malloc(abuf_size)) == NULL) {
		fprintf(stderr, "Unable to allocate output buffer\n");
		exit(-1);
	}

	if (ioctl(audio, SNDCTL_DSP_SAMPLESIZE, sample_size) != sample_size) {
		fprintf(stderr, "Unable to set sample size\n");
		exit(-1);
	}

	stereo = ioctl(audio, SNDCTL_DSP_STEREO, stereo);

	if ((speed = ioctl(audio, SNDCTL_DSP_SPEED, speed)) == -1) {
		fprintf(stderr, "Unable to set audio speed\n");
		perror(AUDIO);
		exit(-1);
	}

	if (mode == PLAY) {
		while(count) {
			c = count;
			if (c > abuf_size) c = abuf_size;

			if ((l=read(fd, audiobuf, c)) > 0) {
				if (write(audio, audiobuf, l) != l) {
					perror(AUDIO);
					exit(-1);
				}
				count -= l;
			} else {
				if (l == -1) {
					perror(name);
					exit(-1); 
				}
				count = 0;	/* stop */
			}
		}

		if (fd) close(fd);
		close(audio);
	} else {			/* mode = RECORD */
		if (!quiet_mode) fprintf(stderr, "...recording\n");
		while(count) {
			c = count;
			if (c > abuf_size) c = abuf_size;

			if ((l=read(audio, audiobuf, c)) > 0) {
				if (write(fd, audiobuf, l) != l) {
					perror(tmp_name);
					exit(-1);
				}
				count -= l;
				head.data_length += l;
			} else {
				if (l == -1) {
					perror (AUDIO);
					exit(-1);
				}
			}
		}
		/* now lets correct the header */

		close(fd);
		close(AUDIO);

		head.length = head.data_length + 36;

		/* copy the temporary file to the output file with corrected header */

		if (!name) {
			fd = 1;
			name = "stdout";
		} else {
			remove(name);
			if ((fd = open(name, O_WRONLY | O_CREAT, 0666)) == -1) {
				perror(name);
				exit(-1);
			}
		}
		if ((fd1 = open(tmp_name, O_RDONLY, 0)) == -1) {
			perror(tmp_name);
			exit(-1);
		}

		if (write(fd, &head, sizeof(head)) != sizeof(head)) {
			perror(name);
			exit(-1);
		}
		if (!quiet_mode) fprintf(stderr, "...copying\n");
		while (l = read(fd1, audiobuf, abuf_size)) {
			if (write(fd, audiobuf, l) != l) {
				perror(name);
				exit(-1);
			}
		}
		close(fd);
		close(fd1);
		remove(tmp_name);
		if (!quiet_mode) fprintf(stderr, "READY\n");
	}
}

main(int argc, char *argv[])
{
	char c;

	if (strstr(argv[0], "play")) {
		mode = PLAY;
		omode = O_WRONLY;
	} else if (strstr(argv[0], "record")) {
		mode = RECORD;
		omode = O_RDONLY;
	} else {
		fprintf(stderr, "%s: command should either be named play or record\n", argv[0]);
		exit(-1);
	}

	while((c = getopt(argc, argv, "qs:St:b:h")) != EOF)
		switch (c) {
			case 'q': quiet_mode=1; break;
			case 's': speed = atoi(optarg); force_speed = 1; break;
			case 'S': stereo = force_stereo = 1; break;
			case 't': time_limit = atoi(optarg); break;
			case 'b': sample_size = atoi(optarg); force_sample_size = 1; break;
			case 'h': 
			   fprintf(stderr, 
	"Usage: %s [-h] [-q] [-s speed] [-S] [-t limit] [-b bits] filename...\n\n", argv[0]);
			   fprintf(stderr, "Switches:\n  -h .. displays this help text\n");
			   fprintf(stderr, "  -q .. quiet mode; don't display");
			   fprintf(stderr, " any information about the sample\n");
			   fprintf(stderr, "  -s .. force speed\n  -S .. force stereo\n");
			   fprintf(stderr, "  -t .. time limit\n");
			   fprintf(stderr, "  -b .. force bit_p_spl (8, 12, 16)\n\n");
			   fprintf(stderr, "Normally speed, stereo/mono and bit_p_spl");
			   fprintf(stderr, " will be read from the given file. Using the\n");
			   fprintf(stderr, "switches shown above you can set your own.\n");
			   exit(-1);
			default: fprintf(stderr, 
	"Usage: %s [-h] [-q] [-s speed] [-S] [-t limit] [-b bits] filename...\n\n", argv[0]);
				 exit(-1);
		}

	if (mode == RECORD) {				/* possibly set default values */
		if (!time_limit) time_limit = 10;	/* DEFAULT: 10 secs */
		if (!force_stereo) stereo = 0;		/* DEFAULT: mono */
		if (!force_speed) speed = 22050;	/* DEFAULT: 22050 Hz */
		if (!force_sample_size) sample_size = 8;/* DEFAULT: 8 bit samples */
	}

	if (optind > argc-1) 
		recplay(NULL);
	else 
		while (optind <= argc - 1) 
			recplay(argv[optind++]);

	exit(0);
}
