/*
 * FILE:    auddev_pca.c
 * PROGRAM: RAT
 * AUTHOR:  Jim Lowe (james@cs.uwm.edu) 
 * MODS: Orion Hodson
 *
 * Copyright (c) 1996-2001 University College London
 * All rights reserved.
 */
 
#ifndef HIDE_SOURCE_STRINGS
static const char cvsid[] = 
	"$Id: auddev_pca.c,v 1.23 2001/01/08 20:29:49 ucaccsp Exp $";
#endif /* HIDE_SOURCE_STRINGS */

/*
 * PCA speaker support for FreeBSD.
 *
 * This is an output only device so we pretend we read audio...
 */

#include <machine/pcaudioio.h>
#include "config_unix.h"
#include "audio_types.h"
#include "audio_fmt.h"
#include "auddev_pca.h"
#include "codec_g711.h"
#include "memory.h"
#include "debug.h"

static audio_info_t   dev_info;			/* For PCA device */
static int            audio_fd;
static struct timeval last_read_time;
static int            bytes_per_block;
static int            present;

static int avail_bytes; /* Number of bytes available because of under read */

#define pca_bat_to_device(x)	((x) * AUDIO_MAX_GAIN / MAX_AMP)
#define pca_device_to_bat(x)	((x) * MAX_AMP / AUDIO_MAX_GAIN)

int
pca_audio_device_count()
{
        return present;
}

char*
pca_audio_device_name(audio_desc_t ad)
{
        UNUSED(ad);
        return "PCA Audio Device";
}

int
pca_audio_init()
{
        int audio_fd;
        if ((audio_fd = open("/dev/pcaudio", O_WRONLY | O_NDELAY)) != -1) {
                close(audio_fd);
                present = 1;
                return TRUE;
        }
        return FALSE;
}

/*
 * Try to open the audio device.
 * Return: valid file descriptor if ok, -1 otherwise.
 */

int
pca_audio_open(audio_desc_t ad, audio_format *ifmt, audio_format *ofmt)
{
	audio_info_t tmp_info;

        UNUSED(ofmt);
        assert(audio_format_match(ifmt, ofmt));

        if (ifmt->sample_rate != 8000 || ifmt->channels != 1) {
                return FALSE;
        }

        avail_bytes = 0;

	audio_fd = open("/dev/pcaudio", O_WRONLY | O_NDELAY );

	if (audio_fd > 0) {
		AUDIO_INITINFO(&dev_info);
		dev_info.monitor_gain     = 0;
		dev_info.play.sample_rate = ifmt->sample_rate;
		dev_info.play.channels    = ifmt->channels;
		dev_info.play.gain	      = (AUDIO_MAX_GAIN - AUDIO_MIN_GAIN) * 0.75;
		dev_info.play.port	      = 0;

                if (ifmt->encoding != DEV_PCMU) {
                        audio_format_change_encoding(ifmt, DEV_PCMU);
                }

                if (ofmt->encoding != DEV_PCMU) {
                        audio_format_change_encoding(ofmt, DEV_PCMU);
                }

                assert(ifmt->bits_per_sample == 8);
                dev_info.play.encoding  = AUDIO_ENCODING_ULAW;
                dev_info.play.precision   = 8;

                bytes_per_block = ofmt->bytes_per_block;

		memcpy(&tmp_info, &dev_info, sizeof(audio_info_t));

		if (ioctl(audio_fd, AUDIO_SETINFO, (caddr_t)&tmp_info) < 0) {
			perror("pca_audio_info: setting parameters");
                        pca_audio_close(ad);
			return FALSE;
		}

                return TRUE;
	} else {
		/* 
		 * Because we opened the device with O_NDELAY, the wait
		 * flag was not updaed so update it manually.
		 */
		audio_fd = open("/dev/pcaudioctl", O_WRONLY);
		if (audio_fd < 0) {
			AUDIO_INITINFO(&dev_info);
			dev_info.play.waiting = 1;
			(void)ioctl(audio_fd, AUDIO_SETINFO, (caddr_t)&dev_info);
			close(audio_fd);
		}
		audio_fd = -1;
	}
	return FALSE;
}

/*
 * Shutdown.
 */
void
pca_audio_close(audio_desc_t ad)
{
        UNUSED(ad);
	if(audio_fd > 0)
		(void)close(audio_fd);
	audio_fd = -1;
	return;
}

/*
 * Flush input buffer.
 */
void
pca_audio_drain(audio_desc_t ad)
{
        UNUSED(ad);
        avail_bytes = 0;
	return;
}

/*
 * Set record gain.
 */
void
pca_audio_set_igain(audio_desc_t ad, int gain)
{
        UNUSED(ad);
        UNUSED(gain);
	return;
}

/*
 * Get record gain.
 */
int
pca_audio_get_igain(audio_desc_t ad)
{
        UNUSED(ad);
	return 0;
}

int
pca_audio_duplex(audio_desc_t ad)
{
        UNUSED(ad);
        /* LIE! LIE! LIE! LIE! 
         * But we really only support full duplex devices
         */
        return TRUE;
}

/*
 * Set play gain.
 */
void
pca_audio_set_ogain(audio_desc_t ad, int vol)
{
        UNUSED(ad);
        AUDIO_INITINFO(&dev_info);
        dev_info.play.gain = pca_bat_to_device(vol);
        if (ioctl(audio_fd, AUDIO_SETINFO, (caddr_t)&dev_info) < 0) 
                perror("pca_audio_set_ogain");

	return;
}

/*
 * Get play gain.
 */
int
pca_audio_get_ogain(audio_desc_t ad)
{
        UNUSED(ad);
	AUDIO_INITINFO(&dev_info);
	if (ioctl(audio_fd, AUDIO_GETINFO, (caddr_t)&dev_info) < 0)
		perror("pca_audio_get_ogain");
	return pca_device_to_bat(dev_info.play.gain);
}

/*
 * Record audio data.
 */
int
pca_audio_read(audio_desc_t ad, u_char *buf, int buf_bytes)
{
	/*
	 * Reading data from internal PC speaker is a little difficult,
	 * so just return the time (in audio samples) since the last time called.
	 */
	int	                read_bytes;
	struct timeval          curr_time;
	static int              virgin = TRUE;

        UNUSED(ad);

	if (virgin) {
		gettimeofday(&last_read_time, NULL);
		virgin = FALSE;
	}

	gettimeofday(&curr_time, NULL);
	read_bytes = (curr_time.tv_sec  - last_read_time.tv_sec) * 1000 + (curr_time.tv_usec - last_read_time.tv_usec) / 1000;
        /* diff from ms to samples */
        read_bytes *= dev_info.play.sample_rate / 1000 * dev_info.play.precision / 8 * dev_info.play.channels;

        if (read_bytes + avail_bytes < bytes_per_block) {
                return 0;
        }

        if (buf_bytes > read_bytes + avail_bytes) {
                /* Have requested more bytes than we read this time and
                 * are available in reserve.
                 */
                read_bytes += avail_bytes;
                avail_bytes = 0;
        } else {
                avail_bytes += read_bytes - buf_bytes;
                read_bytes   = buf_bytes;
        }
        assert(avail_bytes >= 0);

        memcpy(&last_read_time, &curr_time, sizeof(struct timeval));
        memset(buf, 0, read_bytes);
        xmemchk();
        return read_bytes;
}

/*
 * Playback audio data.
 */
int
pca_audio_write(audio_desc_t ad, u_char *buf, int write_bytes)
{
	int	 nbytes;

        UNUSED(ad);

        if ((nbytes = write(audio_fd, buf, write_bytes)) != write_bytes) {
		if (errno == EWOULDBLOCK) {	/* XXX */
                        perror("pca_audio_write");
			return 0;
		}
		if (errno != EINTR) {
			perror("pca_audio_write");
			return (write_bytes - nbytes);
		}
	} 
    
	return write_bytes;
}

/*
 * Set options on audio device to be non-blocking.
 */
void
pca_audio_non_block(audio_desc_t ad)
{
	int on = 1;

        UNUSED(ad);

	if (ioctl(audio_fd, FIONBIO, (char *)&on) < 0)
		perror("pca_audio_non_block");
 
	return;
}

/*
 * Set options on audio device to be blocking.
 */
void
pca_audio_block(audio_desc_t ad)
{
	int on = 0;

        UNUSED(ad);

	if (ioctl(audio_fd, FIONBIO, (char *)&on) < 0)
		perror("pca_audio_block");
	return;
}

#define PCA_SPEAKER    0x0101
#define PCA_MICROPHONE 0x0201

static audio_port_details_t in_ports[] = {
        { PCA_MICROPHONE, AUDIO_PORT_MICROPHONE}
};

static audio_port_details_t out_ports[] = {
        { PCA_SPEAKER,    AUDIO_PORT_SPEAKER}
};

/*
 * Set output port.
 */
void
pca_audio_oport_set(audio_desc_t ad, audio_port_t port)
{
	/* There is only one port... */
        UNUSED(ad); UNUSED(port);

        assert(port == PCA_SPEAKER);

	return;
}

/*
 * Get output port.
 */
audio_port_t
pca_audio_oport_get(audio_desc_t ad)
{
	/* There is only one port... */
        UNUSED(ad);

	return out_ports[0].port;
}

int
pca_audio_oport_count(audio_desc_t ad)
{
        UNUSED(ad);
        return 1;
}

const audio_port_details_t*
pca_audio_oport_details(audio_desc_t ad, int idx)
{
        UNUSED(ad);
        UNUSED(idx);
        assert(idx == 0);
        return &out_ports[0];
}

/*
 * Set input port.
 */
void
pca_audio_iport_set(audio_desc_t ad, audio_port_t port)
{
	/* Hmmm.... */
        UNUSED(ad);
        UNUSED(port);
	return;
}

/*
 * Get input port.
 */
audio_port_t
pca_audio_iport_get(audio_desc_t ad)
{
	/* Hmm...hack attack */
        UNUSED(ad);
	return in_ports[0].port;
}

int
pca_audio_iport_count(audio_desc_t ad)
{
        UNUSED(ad);
        return 1;
}

const audio_port_details_t*
pca_audio_iport_details(audio_desc_t ad, int idx)
{
        UNUSED(ad);
        UNUSED(idx);
        assert(idx == 0);
        return &in_ports[0];
}

/*
 * Enable hardware loopback
 */
void 
pca_audio_loopback(audio_desc_t ad, int gain)
{
        UNUSED(ad);
        UNUSED(gain);
        /* Nothing doing... */
}

/*
 * For external purposes this function returns non-zero
 * if audio is ready.
 */
int
pca_audio_is_ready(audio_desc_t ad)
{
        struct timeval now;
        uint32_t read_bytes;

        UNUSED(ad);

        gettimeofday(&now,NULL);
	read_bytes = (now.tv_sec  - last_read_time.tv_sec) * 1000 + (now.tv_usec - last_read_time.tv_usec)/1000;
        read_bytes *= dev_info.play.sample_rate / 1000 * dev_info.play.precision / 8 * dev_info.play.channels;

        if (read_bytes + avail_bytes > (unsigned)bytes_per_block) return TRUE;
        return FALSE;
}

void
pca_audio_wait_for(audio_desc_t ad, int delay_ms)
{
        if (pca_audio_is_ready(ad)) {
                return;
        } else {
                usleep(delay_ms * 1000);
        }
}

int
pca_audio_supports(audio_desc_t ad, audio_format *fmt)
{
        UNUSED(ad);
        if (fmt->channels == 1 && fmt->sample_rate == 8000) return TRUE;
        return FALSE;
}
