/*
 * FILE:     sparc.c
 * PROGRAM:  RAT
 * AUTHOR:   Isidor Kouvelas
 * MODIFIED: Colin Perkins
 *
 * $Revision: 1.1.1.1 $
 * $Date: 1998/08/15 19:16:46 $
 *
 * Copyright (c) 1995,1996 University College London
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, is permitted, for non-commercial use only, provided
 * that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the Computer Science
 *      Department at University College London
 * 4. Neither the name of the University nor of the Department may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 * Use of this software for commercial purposes is explicitly forbidden
 * unless prior written permission is obtained from the authors.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */



#include "assert.h"
#include "bat_include.h"
#ifdef SunOS

#include <multimedia/audio_hdr.h>

typedef Audio_hdr* audio_header_pointer;

static audio_info_t	dev_info;
static int 		mulaw_device = FALSE;	/* TRUE if the hardware can only do 8bit mulaw sampling */

#define bat_to_device(x)	((x) * AUDIO_MAX_GAIN / MAX_AMP)
#define device_to_bat(x)	((x) * MAX_AMP / AUDIO_MAX_GAIN)

/* Try to open the audio device.                        */
/* Returns a valid file descriptor if ok, -1 otherwise. */
int
audio_open(audio_format format)
{
	audio_info_t	tmp_info;

	int audio_fd = open("/dev/audio", O_RDWR | O_NDELAY);

	if (audio_fd > 0) {
		AUDIO_INITINFO(&dev_info);
		dev_info.monitor_gain       = 0;
		dev_info.output_muted       = 0; /* 0==not muted */
		dev_info.play.sample_rate   = format.sample_rate;
		dev_info.record.sample_rate = format.sample_rate;
		dev_info.play.channels      = format.num_channels;
		dev_info.record.channels    = format.num_channels;
		dev_info.play.precision     = format.bits_per_sample;
		dev_info.record.precision   = format.bits_per_sample;
		dev_info.play.gain          = (AUDIO_MAX_GAIN - AUDIO_MIN_GAIN) * 0.75;
		dev_info.record.gain        = (AUDIO_MAX_GAIN - AUDIO_MIN_GAIN) * 0.75;
		dev_info.play.port          = AUDIO_HEADPHONE;
		dev_info.record.port        = AUDIO_MICROPHONE;
		dev_info.play.balance       = AUDIO_MID_BALANCE;
		dev_info.record.balance     = AUDIO_MID_BALANCE;
#ifdef SunOS_5
		dev_info.play.buffer_size   = DEVICE_BUF_UNIT;
		dev_info.record.buffer_size = DEVICE_BUF_UNIT;
#endif
                switch (format.encoding) {
		case DEV_PCMU:
			dev_info.record.encoding = AUDIO_ENCODING_ULAW;
			dev_info.play.encoding   = AUDIO_ENCODING_ULAW;
			break;
		case DEV_L8:
			assert(format.bits_per_sample == 8);
			dev_info.record.encoding = AUDIO_ENCODING_LINEAR;
			dev_info.play.encoding   = AUDIO_ENCODING_LINEAR;
			break;
		case DEV_L16:
			assert(format.bits_per_sample == 16);
			dev_info.record.encoding = AUDIO_ENCODING_LINEAR;
			dev_info.play.encoding   = AUDIO_ENCODING_LINEAR;
			break;
		default:
			printf("ERROR: Unknown audio encoding in audio_open!\n");
			abort();
                }

		memcpy(&tmp_info, &dev_info, sizeof(audio_info_t));
		if (ioctl(audio_fd, AUDIO_SETINFO, (caddr_t)&tmp_info) < 0) {
			if (format.encoding == DEV_L16) {
#ifdef DEBUG
				printf("Old hardware detected: can't do 16 bit audio, trying 8 bit...\n");
#endif
				dev_info.play.precision = 8;
				dev_info.record.precision = 8;
				dev_info.record.encoding = AUDIO_ENCODING_ULAW;
				dev_info.play.encoding = AUDIO_ENCODING_ULAW;
				if (ioctl(audio_fd, AUDIO_SETINFO, (caddr_t)&dev_info) < 0) {
					perror("Setting MULAW audio paramterts");
					return -1;
				}
				mulaw_device = TRUE;
			} else {
				perror("Setting audio paramterts");
				return -1;
			}
		}
		return audio_fd;
	} else {
		/* Because we opened the device with O_NDELAY
		 * the waiting flag was not updated so update
		 * it manually using the audioctl device...
		 */
		audio_fd = open("/dev/audioctl", O_RDWR);
		AUDIO_INITINFO(&dev_info);
		dev_info.play.waiting = 1;
		if (ioctl(audio_fd, AUDIO_SETINFO, (caddr_t)&dev_info) < 0) {
#ifdef DEBUG
			perror("Setting requests");
#endif
		}
		close(audio_fd);
		return -1;
	}
}

/* Close the audio device */
void
audio_close(int audio_fd)
{
	close(audio_fd);
	audio_fd = -1;
}

/* Flush input buffer */
void
audio_drain(int audio_fd)
{
	ioctl(audio_fd, I_FLUSH, (caddr_t)FLUSHR);
}

/* Gain and volume values are in the range 0 - MAX_AMP */

void
audio_set_gain(int audio_fd, int gain)
{
	if (audio_fd <= 0)
		return;

	AUDIO_INITINFO(&dev_info);
	dev_info.record.gain = bat_to_device(gain);
	if (ioctl(audio_fd, AUDIO_SETINFO, (caddr_t)&dev_info) < 0)
		perror("Setting gain");
}

int
audio_get_gain(int audio_fd)
{
	if (audio_fd <= 0)
		return (0);

	AUDIO_INITINFO(&dev_info);
	if (ioctl(audio_fd, AUDIO_GETINFO, (caddr_t)&dev_info) < 0)
		perror("Getting gain");
	return (device_to_bat(dev_info.record.gain));
}

void
audio_set_volume(int audio_fd, int vol)
{
	if (audio_fd <= 0)
		return;

	AUDIO_INITINFO(&dev_info);
	dev_info.play.gain = bat_to_device(vol);
	if (ioctl(audio_fd, AUDIO_SETINFO, (caddr_t)&dev_info) < 0)
		perror("Setting volume");
}

int
audio_get_volume(int audio_fd)
{
	if (audio_fd <= 0)
		return (0);

	AUDIO_INITINFO(&dev_info);
	if (ioctl(audio_fd, AUDIO_GETINFO, (caddr_t)&dev_info) < 0)
		perror("Getting gain");
	return (device_to_bat(dev_info.play.gain));
}

int
audio_read(int audio_fd, sample *buf, int samples)
{
	int	i, len;
	static u_char mulaw_buf[DEVICE_REC_BUF];
	u_char	*p;

	if (mulaw_device) {
		if ((len = read(audio_fd, mulaw_buf, samples)) < 0) {
			return 0;
		} else {
			p = mulaw_buf;
			for (i = 0; i < len; i++) {
				*buf++ = u2s((unsigned)*p);
				p++;
			}
			return (len);
		}
	} else {
		if ((len = read(audio_fd, (char *)buf, samples * BYTES_PER_SAMPLE)) < 0) {
			return 0;
		} else {
			return (len / BYTES_PER_SAMPLE);
		}
	}
}

int
audio_write(int audio_fd, sample *buf, int samples)
{
	int		i, done, len, bps;
	unsigned char	*p, *q;
	static u_char mulaw_buf[DEVICE_REC_BUF];

	if (mulaw_device) {
		p = mulaw_buf;
		for (i = 0; i < samples; i++)
			*p++ = lintomulaw[(unsigned short)*buf++];
		p = mulaw_buf;
		len = samples;
		bps = 1;
	} else {
		p = (char *)buf;
		len = samples * BYTES_PER_SAMPLE;
		bps = BYTES_PER_SAMPLE;
	}

	q = p;
	while (1) {
		if ((done = write(audio_fd, p, len)) == len)
			break;
		if (errno != EINTR)
			return (samples - ((len - done) / bps));
		len -= done;
		p += done;
	}

	return (samples);
}

/* Set ops on audio device to be non-blocking */
void
audio_non_block(int audio_fd)
{
	int	on = 1;

	if (audio_fd <= 0)
		return;

	if (ioctl(audio_fd, FIONBIO, (char *)&on) < 0)
		fprintf(stderr, "Failed to set non blocking mode on audio device!\n");
}

/* Set ops on audio device to block */
void
audio_block(int audio_fd)
{
	int	on = 0;

	if (audio_fd <= 0)
		return;

	if (ioctl(audio_fd, FIONBIO, (char *)&on) < 0)
		fprintf(stderr, "Failed to set blocking mode on audio device!\n");
}

void
audio_set_oport(int audio_fd, int port)
{
	if (audio_fd <= 0)
		return;

	AUDIO_INITINFO(&dev_info);
	/* AUDIO_SPEAKER or AUDIO_HEADPHONE */
	dev_info.play.port = port;
	if (ioctl(audio_fd, AUDIO_SETINFO, (caddr_t)&dev_info) < 0)
		perror("Setting port");
}

int
audio_get_oport(int audio_fd)
{
	if (audio_fd <= 0)
		return (AUDIO_SPEAKER);

	AUDIO_INITINFO(&dev_info);
	if (ioctl(audio_fd, AUDIO_GETINFO, (caddr_t)&dev_info) < 0)
		perror("Getting port");
	return (dev_info.play.port);
}

int
audio_next_oport(int audio_fd)
{
	int	port;

	if (audio_fd <= 0)
		return (AUDIO_SPEAKER);

	AUDIO_INITINFO(&dev_info);
	if (ioctl(audio_fd, AUDIO_GETINFO, (caddr_t)&dev_info) < 0)
		perror("Getting port");

	
	port = dev_info.play.port;
	port <<= 1;

	/* It is either wrong on some machines or i got something wrong! */
	if (dev_info.play.avail_ports < 3)
		dev_info.play.avail_ports = 3;

	if ((port & dev_info.play.avail_ports) == 0)
		port = 1;

	AUDIO_INITINFO(&dev_info);
	dev_info.play.port = port;
	if (ioctl(audio_fd, AUDIO_SETINFO, (caddr_t)&dev_info) < 0)
		perror("Setting port");

	return (port);
}

void
audio_set_iport(int audio_fd, int port)
{
	if (audio_fd <= 0)
		return;

	AUDIO_INITINFO(&dev_info);
	dev_info.record.port = port;
	if (ioctl(audio_fd, AUDIO_SETINFO, (caddr_t)&dev_info) < 0)
		perror("Setting port");
}

int
audio_get_iport(int audio_fd)
{
	if (audio_fd <= 0)
		return (AUDIO_SPEAKER);

	AUDIO_INITINFO(&dev_info);
	if (ioctl(audio_fd, AUDIO_GETINFO, (caddr_t)&dev_info) < 0)
		perror("Getting port");
	return (dev_info.record.port);
}

int
audio_next_iport(int audio_fd)
{
	int	port;

	if (audio_fd <= 0)
		return (AUDIO_SPEAKER);

	AUDIO_INITINFO(&dev_info);
	if (ioctl(audio_fd, AUDIO_GETINFO, (caddr_t)&dev_info) < 0)
		perror("Getting port");

	port = dev_info.record.port;
	port <<= 1;

	if (dev_info.record.avail_ports > 3)
		dev_info.record.avail_ports = 3;

	/* Hack to fix Sparc 5 SOLARIS bug */
	if ((port & dev_info.record.avail_ports) == 0)
		port = 1;

	AUDIO_INITINFO(&dev_info);
	dev_info.record.port = port;
	if (ioctl(audio_fd, AUDIO_SETINFO, (caddr_t)&dev_info) < 0)
		perror("Setting port");

	return (port);
}

void
audio_switch_out(int audio_fd, cushion_struct *ap)
{
  /* Full duplex device: do nothing! */
}
   
void
audio_switch_in(int audio_fd)
{
  /* Full duplex device: do nothing! */
}

int
audio_duplex(int audio_fd)
{
  return 1;
}

#endif /* SunOS */

