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

#include "play.h"

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

	if (strncmp(head->main_chunk, "RIFF", 4)) {
		err(1, "not in RIFF-Format");
		return(0);
	}

	if (strncmp(head->chunk_type, "WAVE", 4)) {
		err(1, "not a WAVE-Type");
		return(0);
	}
		
	if (strncmp(head->sub_chunk, "fmt ", 4)) {
		err(1, "not a WAVE-Format");
		return(0);
	}

	if ((head->sample_fq * head->byte_p_spl) != head->byte_p_sec) 
		err(0, "wrong speed/freq."); 

	return(1);
}

void recplay(char *name, int mode, header_data data, char *edit_buf)
/* name = filename, NULL, if input comes from stdin or caller = X11 */
/* mode = PLAY / RECORD                                             */
/* data = command_line options                                      */
/* edit_buf = buffer (caller: X11)                                  */
/* edit_buf is NULL, if called from play (commandline)              */
{
	int l, c, count;
	int fd, audio;
	int abuf_size;
	wave_header head;
	int omode;
	char *audiobuf, *edit_buf_pointer, *point;
	char string[80];

	omode = (mode == PLAY) ? O_WRONLY : O_RDONLY;	
						/* open mode for /dev/audio */

	if (mode == PLAY) {
		edit_buf_pointer = edit_buf;
		if (edit_buf == NULL) {
		   /* called from commandline */
		   if (!name) {
			fd = 0;
			name = "stdin";
		        read(fd, &head, sizeof(head));
		        if (!header_check(&head)) return; 
		   } else {
			if ((fd = open (name, O_RDONLY, 0)) == -1) {
				sprintf(string, "Error opening %s", name);
				err(1, string);
				return;
			}
		        read(fd, &head, sizeof(head));
		        if (!header_check(&head)) return; 
		   } 

		   if (!data.force_stereo) data.stereo = (head.modus == 2);
		   if (!data.force_speed) data.speed = head.sample_fq;
		   if (!data.force_sample_size) 
		      data.sample_size = head.bit_p_spl;
		  
		   /* set upper border accordingly */

		   if (data.time_limit == 0.0) 
		      data.upper_border = head.data_length;
		   else {
		      if (!data.force_speed)
		         data.upper_border = data.time_limit * head.sample_fq;
		      else
		         data.upper_border = data.time_limit * data.speed;

		      if (data.stereo) data.upper_border *= 2;
		      if (data.sample_size != 8) data.upper_border *= 2;
		   }

		} else {
		   /* called from X-interface */
		   memcpy(&head, edit_buf_pointer, sizeof(head));
		   edit_buf_pointer += sizeof(head);
		   edit_buf_pointer += data.lower_border;
		   if (data.upper_border > head.data_length) 
		      data.upper_border = head.data_length;
		}
	} else {				/* mode = RECORD */
		if (edit_buf != NULL) 
		   edit_buf_pointer = edit_buf;
		else {
		   if (!name) {
		      fd = 1;
		      name = "stdout";
		   } else {
		      remove(name);
		      if ((fd=open(name, O_WRONLY | O_CREAT, 0666)) == -1) {
		         err(1, "Error opening output file");
		         return;
		      }
		   }
		}
		/* 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_chunk = 16;
		head.format = 1;
		head.modus = data.stereo ? 2 : 1;		
		head.sample_fq = data.speed;

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

		head.byte_p_sec = data.speed * head.byte_p_spl;
		head.data_length = head.byte_p_sec * data.time_limit;

		head.bit_p_spl = data.sample_size;
		head.length = head.data_length + 36;
		data.upper_border = 
		   data.time_limit * data.speed * head.byte_p_spl;
		data.lower_border = 0;

		if (edit_buf == NULL) {
		   if (write(fd, &head, sizeof(head)) != sizeof(head)) {
		   	err(1, "Error writing header");
		   	return;
		   }
		} else {
		   memcpy(edit_buf_pointer, &head, sizeof(head));
		   edit_buf_pointer += sizeof(head);
		}
	}

	count = data.upper_border - data.lower_border;

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

	audio = open(AUDIO, omode, 0);
	if (audio == -1)  {
		err(1, "Error opening audio-device");
		return;
	}

	abuf_size = ioctl(audio, SNDCTL_DSP_GETBLKSIZE);
	if (abuf_size < 4096 || abuf_size > 65536) {
		close(audio);
		if (abuf_size == -1) {
			err(1, "Can't get blocksize");
			return;
		} else {
			err(1, "Invalid audio buffer size");
			return;
		}
		exit(-1);
	}

	if ((audiobuf = malloc(abuf_size)) == NULL) {
		err(1, "Unable to allocate output buffer");
		close(audio);
		return;
	}

	if (ioctl(audio, SNDCTL_DSP_SAMPLESIZE, data.sample_size)!=
	    data.sample_size) {
		err(1, "Unable to set sample size");
		close(audio);
		return;
	}

	data.stereo = ioctl(audio, SNDCTL_DSP_STEREO, data.stereo);

	if ((data.speed = ioctl(audio, SNDCTL_DSP_SPEED, data.speed)) == -1) {
		err(1, "Unable to set audio speed");
		close(audio);
		return;
	}

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

			if (edit_buf == NULL) 
			   l=read(fd, audiobuf, c);
			else {
			   memcpy(audiobuf, edit_buf_pointer, c); 
			   edit_buf_pointer += c;
			   l = c;
			}
			if (l > 0) {
				if (write(audio, audiobuf, l) != l) {
					err(1, "Error writing on audio device");
					close(audio);
					return;
				}
				count -= l;
			} else {
				if (l == -1) {
					err(1, "Error reading from .wav-file");
					close(audio);
					return;
				}
				count = 0;	/* stop */
			}
		}

		if (fd) close(fd);
		close(audio);
	} else {			/* mode = RECORD */
		while(count) {
			c = count;
			if (c > abuf_size) c = abuf_size;

			if ((l=read(audio, audiobuf, c)) > 0) {
			   if (edit_buf == NULL) {
				if (write(fd, audiobuf, l) != l) {
					err(1, "Error writing to .wav-file");
					close(audio);
					return;
				}
			   } else {
				memcpy(edit_buf_pointer, audiobuf, l);
				edit_buf_pointer += l;
			   }
			   count -= l;
			} else {
			   if (l == -1) {
			      err(1, "Error reading from audio device");
			      close(audio);
			      return;
			   }
			}
		}
		close(audio);
		if (edit_buf == NULL) 
		   close(fd);
	}
}
