/*********************************************************************
 *	play_mmap.c - exercises output interface of an OSS sound driver
 *			using mmap()
 *	Copyright (C) 1999-2000 Rui Sousa 
 ********************************************************************* 
 *     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 along with this program; if not, write to the Free 
 *     Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, 
 *     USA. 
 ********************************************************************* 
*/

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <sys/soundcard.h>
#include <stdlib.h>
#include <sys/mman.h>
#include "common.h"

int main()
{
	int audio_fd;
	__u8 *buffer;
	int dev_ptr, ptr, optr, n_loops;
	__u8 devbuf[BUF_SIZE];
	struct wave wave[CHANNELS];
	int ret, val;
	int min;
	int is16bit;
	audio_buf_info ospace_info;
	count_info optr_info;
	mode_t mode;
	unsigned int channel;
	int bufsize, fragsize;

	mode = O_RDWR;
	audio_fd = setup("/dev/dsp", mode);

	switch (FORMAT) {

	case AFMT_S8:
	case AFMT_U8:
		is16bit = 0;
		break;
	case AFMT_S16_LE:
	case AFMT_S16_BE:
	case AFMT_U16_LE:
	case AFMT_U16_BE:
		is16bit = 1;
		break;
	}

	for (channel = 0; channel < CHANNELS; channel++)
        {
                wave[channel].w = 2.0 * M_PI * freq[channel] / SPEED;
                wave[channel].t0 = 0.0;
        }

	if ((ret = ioctl(audio_fd, SNDCTL_DSP_GETOSPACE, &ospace_info)) == -1)
		perror("SNDCTL_DSP_GETOSPACE");

	fragsize = ospace_info.fragsize;
	bufsize = ospace_info.fragsize * ospace_info.fragstotal;
	ptr = 0;
	optr = 0;
	n_loops = 0;

	if ((buffer = (unsigned char *)
	     mmap(NULL, bufsize, PROT_WRITE | PROT_READ, MAP_FILE | MAP_SHARED, audio_fd, 0)) == (unsigned char *) -1) {
		perror("mmap");
		exit(EXIT_FAILURE);
	}

	val = 0;
	if (ioctl(audio_fd, SNDCTL_DSP_SETTRIGGER, &val) == -1)
		perror("SNDCTL_DSP_SETTRIGGER");

	val = PCM_ENABLE_OUTPUT;
	if (ioctl(audio_fd, SNDCTL_DSP_SETTRIGGER, &val) == -1)
		perror("SNDCTL_DSP_SETTRIGGER");

	while (1) {

		if (DEBUG)
			print_info(audio_fd, mode);

		fill_devbuf(wave, devbuf);

		dev_ptr = 0;

		while (dev_ptr < BUF_SIZE) {

			if ((ret = ioctl(audio_fd, SNDCTL_DSP_GETOPTR, &optr_info)) == -1)
				perror("SNDCTL_DSP_GETOPTR");

			if (optr > optr_info.ptr)
				n_loops--;

			optr = optr_info.ptr;

			min = BUF_SIZE - dev_ptr < fragsize ? BUF_SIZE - dev_ptr : fragsize;

			while (bufsize - (ptr - optr_info.ptr + n_loops * bufsize) < min) {
				usleep(1);
				if ((ret = ioctl(audio_fd, SNDCTL_DSP_GETOPTR, &optr_info))
				    == -1)
					perror("SNDCTL_DSP_GETOPTR");

				if (optr > optr_info.ptr)
					n_loops--;

				optr = optr_info.ptr;
			}

			if (bufsize - ptr > min) {
				memcpy(buffer + ptr, devbuf + dev_ptr, min);
				ptr += min;
			} else {
				memcpy(buffer + ptr, devbuf + dev_ptr, bufsize - ptr);
				memcpy(buffer, devbuf + dev_ptr + bufsize - ptr, min - (bufsize - ptr));
				ptr = min - (bufsize - ptr);
				n_loops++;
			}
			dev_ptr += min;
		}
	}

	munmap(buffer, bufsize);
	close(audio_fd);

	return 0;
}
