/*
	$Id: ossplay.c,v 1.4 2000/12/30 01:30:50 rogue Exp $

	Copyright (c) 1999, 2000 Xforge project
*/

#include <Xm/Xm.h>
#include "xforge.h"

char *dspdev = DSPDEV;				/* dsp device node name */
int dspbuf = DSPBUF;
unsigned char *b1 = NULL, *b2 = NULL;		/* dsp buffers */

#ifdef OSSPLAY
/* FreeBSD 2.2.6 has <machine/soundcard.h> instead of <sys/soundcard.h> */
#ifdef __FreeBSD__
#include <machine/soundcard.h>
#endif
/* NetBSD 1.3.2 has <soundcard.h> instead of <sys/soundcard.h> */
#ifdef __NetBSD__
#include <soundcard.h>
#endif
#ifndef __FreeBSD__
#ifndef __NetBSD__
#include <sys/soundcard.h>
#endif
#endif
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include "glob.h"

static volatile pid_t p_pid = -1;		/* player pid */

static void childhandler(int sig) {
	wait(NULL);		/* FIXME: check if this is ok */
	p_pid = -1;		/* mark process as killed */
}

static void do_play(struct buffer *buf, int bstart, int bstop) {
	unsigned char *b, *w;			/* audio buffers */
	int i, dspfd, fmt = AFMT_U8, rfmt = AFMT_U8, sample, rate, channels;
	int pmode = PMODE_8;
	unsigned int j = 0;
	int bsize;				/* block size */
	int dsize;				/* data size left */
	int lsize;				/* loop size / length */
	int csize;				/* copy / write size */
	int buflen = dspbuf;
	float *p, *q;				/* data pointers */

	/* install signal handler for catching SIGCHLDs */
	if ((signal(SIGCHLD, childhandler)) == SIG_ERR)
		xferr("warning: cannot set signal handler");

	if ((dspfd = open(dspdev, O_WRONLY)) == -1) {
		syserr(dspdev);
		return;
	}

	switch (buf->bits) {
		case 8:
			fmt = rfmt = AFMT_U8;
			pmode = PMODE_8;
			buflen = dspbuf;
			break;
		case 16:
			fmt = rfmt = AFMT_S16_LE;
			pmode = PMODE_16;
			buflen = dspbuf / 2;
			break;
		/* OSS only supports up to 16 bits */
		case 24:
			fmt = rfmt = AFMT_S16_LE;
			pmode = PMODE_16;
			buflen = dspbuf / 3;
			break;
		case 32:
			fmt = rfmt = AFMT_S16_LE;
			pmode = PMODE_16;
			buflen = dspbuf / 4;
			break;
	}

	if (ioctl(dspfd, SNDCTL_DSP_SETFMT, &fmt) == -1) {
		syserr("ioctl(): SNDCTL_DSP_SETFMT error");
		close(dspfd);
		return;
	}

	if (fmt != rfmt) {
		xferr("audio hardware does not support requested format");
		close(dspfd);
		return;
	}

	channels = buf->channels - 1;
	if (buf->channels == 2) {
		pmode |= PMODE_STEREO;
		buflen /= 2;
	}

	if (ioctl(dspfd, SNDCTL_DSP_STEREO, &channels) == -1) {
		syserr("ioctl(): SNDCTL_DSP_STEREO error");
		close(dspfd);
		return;
	}

	rate = buf->rate;
	if (ioctl(dspfd, SNDCTL_DSP_SPEED, &rate) == -1) {
		syserr("ioctl(): SNDCTL_DSP_SPEED error");
		close(dspfd);
		return;
	}

	if ((p_pid = fork()) == -1) {
		syserr("fork()");
		return;
	}

	if (p_pid > 0) {
		/* close dsp if we are parent */
		close(dspfd);
		return;
	}

	p = buf->data + bstart;
	q = p + buf->len;
	b = b1;
	dsize = lsize = bstop - bstart + 1;
	/* FIXME: bsize assignment is just to silence gcc */
	bsize = csize = (dsize >= buflen) ? buflen : dsize;
	switch (pmode) {
		case PMODE_M8:
			bsize = csize;
			for (i = 0; i < csize; i++, j++) {
				sample = (int)(*(p + j % lsize) * 128.0f);
				*b++ = sample + 128;
			}
			break;
		case PMODE_M16:
			bsize = csize * 2;
			for (i = 0; i < csize; i++, j++) {
				sample = (int)(*(p + j % lsize) * 32768.0f);
				*b++ = sample & 0xff;
				*b++ = (sample >> 8) & 0xff;
			}
			break;
		case PMODE_S8:
			bsize = csize * 2;
			for (i = 0; i < csize; i++, j++) {
				sample = (int)(*(p + j % lsize) * 128.0f);
				*b++ = sample + 128;
				sample = (int)(*(q + j % lsize) * 128.0f);
				*b++ = sample + 128;
			}
			break;
		case PMODE_S16:
			bsize = csize * 4;
			for (i = 0; i < csize; i++, j++) {
				sample = (int)(*(p + j % lsize) * 32768.0f);
				*b++ = sample & 0xff;
				*b++ = (sample >> 8) & 0xff;
				sample = (int)(*(q + j % lsize) * 32768.0f);
				*b++ = sample & 0xff;
				*b++ = (sample >> 8) & 0xff;
			}
			break;
	}
	b -= bsize;
	if (!(buf->p_loop))
		dsize -= csize;
	while (dsize) {
		w = b;
		if (b == b1)		/* swap buffers */
			b = b2;
		else
			b = b1;
		write(dspfd, w, bsize);
		csize = (dsize >= buflen) ? buflen : dsize;
		switch (pmode) {
			case PMODE_M8:
				bsize = csize;
				for (i = 0; i < csize; i++, j++) {
					sample = (int)(*(p + j % lsize) *
						128.0f);
					*b++ = sample + 128;
				}
				break;
			case PMODE_M16:
				bsize = csize * 2;
				for (i = 0; i < csize; i++, j++) {
					sample = (int)(*(p + j % lsize) *
						32768.0f);
					*b++ = sample & 0xff;
					*b++ = (sample >> 8) & 0xff;
				}
				break;
			case PMODE_S8:
				bsize = csize * 2;
				for (i = 0; i < csize; i++, j++) {
					sample = (int)(*(p + j % lsize) *
						128.0f);
					*b++ = sample + 128;
					sample = (int)(*(q + j % lsize) *
						128.0f);
					*b++ = sample + 128;
				}
				break;
			case PMODE_S16:
				bsize = csize * 4;
				for (i = 0; i < csize; i++, j++) {
					sample = (int)(*(p + j % lsize) *
						32768.0f);
					*b++ = sample & 0xff;
					*b++ = (sample >> 8) & 0xff;
					sample = (int)(*(q + j % lsize) *
						32768.0f);
					*b++ = sample & 0xff;
					*b++ = (sample >> 8) & 0xff;
				}
				break;
		}
		b -= bsize;
		if (!(buf->p_loop))
			dsize -= csize;
	}
	w = b;
	write(dspfd, w, bsize);

	ioctl(dspfd, SNDCTL_DSP_SYNC, 0);
	close(dspfd);

	_exit(EXIT_SUCCESS);
}

void oss_play_range(struct buffer *buf) {
	if (buf->selend != buf->selstart)
		do_play(buf, buf->selstart, buf->selend);
}

void oss_play_all(struct buffer *buf) {
	do_play(buf, 0, buf->len - 1);
}

void oss_play_stop(struct buffer *buf) {
	if (p_pid != -1) {
		kill(p_pid, SIGTERM);
		/* wait (yuck) until process is really killed */
		while (p_pid != -1)
			usleep(10);
	}
}

#else /* OSSPLAY */
/* stubs for oss_play_* */
void oss_play_all(struct buffer *buf) {
}

void oss_play_range(struct buffer *buf) {
}

void oss_play_stop(struct buffer *buf) {
}
#endif /* OSSPLAY */
