/*****************************************************************************/

/*
 *      audio.c  --  Audio processing for "virtual transceiver", Linux IO.
 *
 *      Copyright (C) 1999  Thomas Sailer (sailer@ife.ee.ethz.ch)
 *
 *      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.
 *
 *  Please note that the GPL allows you to use the driver, NOT the radio.
 *  In order to use the radio, you need a license from the communications
 *  authority of your country.
 *
 */

/*****************************************************************************/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef HAVE_LINUX_SOUNDCARD_H

#include <sys/types.h>

#include <linux/soundcard.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

#include "eppfm.h"

#include <gtk/gtk.h>
#include <glib.h>

/* ---------------------------------------------------------------------- */

#define BUFSIZE    8192   /* must be significantly bigger than SNDLATENCY! */
#define OVERLAP    32

/* ---------------------------------------------------------------------- */

struct {
	enum { st_mdmerr, st_off, st_play, st_rec } state;

	/* gtk timeout handle */
	gint tmotag;
	/* sampling rate conversion state */
	int16_t sample[OVERLAP];
	/* control register */
	unsigned char ctrlreg;
	/* led blink counter */
	unsigned ledcnt;
	
	/* low level driver specific data */
	int fddsp;
} state = { st_mdmerr, };

/* ---------------------------------------------------------------------- */

static gint periodic_play(gpointer data)
{
	union {
		unsigned char u[BUFSIZE];
		signed char s[BUFSIZE];
	} buf;
	int16_t sbuf[BUFSIZE];
	int apar, i, icnt, cnt;

	/* epp->dsp direction */
	/* get FIFO count */
	buf.u[0] = state.ctrlreg | 1;
	if (parport_epp_write_addr(buf.u, 1) != 1)
		goto err;
	if (parport_epp_read_addr(buf.u, 2) != 2)
		goto err;
	icnt = buf.u[0] | (((unsigned int)buf.u[1]) << 8);
	buf.u[0] = state.ctrlreg;
	if (parport_epp_write_addr(buf.u, 1) != 1)
		goto err;
	icnt &= 0x7fff;
	if (icnt > 0) {
		if (icnt > BUFSIZE/2)
			icnt = BUFSIZE/2;
		memcpy(buf.s, state.sample, OVERLAP);
		if (parport_epp_read_data(buf.s+OVERLAP, icnt) != icnt)
			goto err;
		memcpy(state.sample, buf.s+icnt, OVERLAP);
		cnt = audio_convertoutput(icnt, buf.s+OVERLAP, sbuf);
		if ((i = write(state.fddsp, sbuf, 2*cnt)) != 2*cnt) {
			lprintf(0, "write(%d) failed %i (%s)\n", 2*cnt, i, strerror(errno));
			goto err;
		}
		if (ioctl(state.fddsp, SNDCTL_DSP_GETODELAY, &apar) == -1) {
			lprintf(0, "ioctl SNDCTL_DSP_GETODELAY failed (%s)\n", strerror(errno));
			goto err;
		}
		apar >>= 1;
		/* adjust speed */
		if (audio_adjustoutput(cnt, icnt, apar))
			goto err;
		/* play games with the LEDS */
		state.ledcnt += icnt;
		if (state.ledcnt >= 4000) {
			state.ledcnt %= 4000;
			state.ctrlreg += 0x40;
		}
		/* DCD led */
		if (audio_getsquelch())
			state.ctrlreg |= 0x10;
		else
			state.ctrlreg &= ~0x10;
	}
	return TRUE;

  err:
	reset_modem();
	lprintf(0, "EPP timeout\n");
	state.state = st_mdmerr;
	close(state.fddsp);
	return FALSE;
}

static gint periodic_rec(gpointer data)
{
	union {
		unsigned char u[BUFSIZE];
		signed char s[BUFSIZE];
	} buf;
	int16_t sbuf[BUFSIZE];
	int ocnt, cnt, ocnts, omax;

	/* dsp->epp direction */
	/* read sound */
	memcpy(sbuf, state.sample, OVERLAP*2);
	ocnts = read(state.fddsp, sbuf+OVERLAP, 2*BUFSIZE/2);
	if (ocnts == -1 && errno == EAGAIN)
		ocnts = 0;
	if (ocnts < 0) {
		lprintf(0, "read(%d) failed %i (%s)\n", 2*BUFSIZE/2, ocnts, strerror(errno));
		goto err;
	}
	ocnts >>= 1;
	memcpy(state.sample, sbuf+ocnts, OVERLAP*2);
	/* get FIFO count */
	buf.u[0] = state.ctrlreg | 2;
	if (parport_epp_write_addr(buf.u, 1) != 1)
		goto err;
	if (parport_epp_read_addr(buf.u, 2) != 2)
		goto err;
	ocnt = buf.u[0] | (((unsigned int)buf.u[1]) << 8);
	buf.u[0] = state.ctrlreg;
	if (parport_epp_write_addr(buf.u, 1) != 1)
		goto err;
	ocnt &= 0x7fff;
	omax = 16384 - ocnt;
	cnt = 0;
	if (ocnts > 0) {
		cnt = audio_convertinput(ocnts, sbuf+OVERLAP, buf.s);
		if (cnt > omax) {
			lprintf(0, "epp adapter output overrun (%d, %d)\n", cnt, omax);
			goto err;
		}
		if (parport_epp_write_data(buf.s, cnt) != cnt)
			goto err;
		/* reget the FIFO count */
		buf.u[0] = state.ctrlreg | 2;
		if (parport_epp_write_addr(buf.u, 1) != 1)
			goto err;
		if (parport_epp_read_addr(buf.u, 2) != 2)
			goto err;
		ocnt = buf.u[0] | (((unsigned int)buf.u[1]) << 8);
		buf.u[0] = state.ctrlreg;
		if (parport_epp_write_addr(buf.u, 1) != 1)
			goto err;
		ocnt &= 0x7fff;
	}
	/* adjust speed */
	if (audio_adjustinput(cnt, ocnts, ocnt))
		goto err;
	return TRUE;

  err:
	reset_modem();
	lprintf(0, "EPP timeout\n");
	state.state = st_mdmerr;
	close(state.fddsp);
	return FALSE;
}

/* ---------------------------------------------------------------------- */

static void playstop(void)
{
	unsigned char buf;

	if (state.state != st_play)
		return;
	gtk_timeout_remove(state.tmotag);
	close(state.fddsp);
	/* reset the EPP adapter */
	buf = 7;
	if (parport_epp_write_addr(&buf, 1) != 1) {
		state.state = st_mdmerr;
		reset_modem();
	} else
		state.state = st_off;
}

static void recstop(void)
{
	unsigned char buf;

	if (state.state != st_rec)
		return;
	gtk_timeout_remove(state.tmotag);
	close(state.fddsp);	
	/* reset the EPP adapter */
	buf = 7;
	if (parport_epp_write_addr(&buf, 1) != 1) {
		state.state = st_mdmerr;
		reset_modem();
	} else
		state.state = st_off;
}

static void playstart(void)
{
	unsigned char buf[2];
	int16_t sbuf[SNDLATENCY];
	audio_buf_info abinfo;
	int apar;
	unsigned srate;
	int i;

	if (state.state == st_play)
		playstop();
	if (state.state == st_rec)
		recstop();
	state.ctrlreg = 0;
	state.ledcnt = 0;
	memset(state.sample, 0, sizeof(state.sample));
	/* start modem if mdmerr */
	if (state.state == st_mdmerr) {
		cfg.bitrate = SAMPLINGRATE;
		if ((i = adapter_start_eppsamp(&cfg))) {
			lprintf(0, "Cannot initialize the modem\n");
			return;
		}
	}
	state.state = st_off;
	if ((state.fddsp = open("/dev/dsp", O_WRONLY | O_NONBLOCK)) == -1) {
		lprintf(0, "cannot open /dev/dsp (%s)\n", strerror(errno));
		return;
	}
	fcntl(state.fddsp, F_SETFL, fcntl(state.fddsp, F_GETFL, 0) | O_NONBLOCK);
	if (ioctl(state.fddsp, SNDCTL_DSP_NONBLOCK, 0) == -1) {
		lprintf(0, "ioctl SNDCTL_DSP_NONBLOCK failed (%s)\n", strerror(errno));
		goto errsnd;
	}
	if (ioctl(state.fddsp, SNDCTL_DSP_GETCAPS, &apar) == -1) {
		lprintf(0, "ioctl SNDCTL_DSP_GETCAPS failed (%s)\n", strerror(errno));
		goto errsnd;
	}
	if (!(apar & DSP_CAP_TRIGGER)) {
		lprintf(0, "soundcard does not support trigger\n");
		goto errsnd;
	}
	apar = AFMT_S16_LE;
	if (ioctl(state.fddsp, SNDCTL_DSP_SETFMT, &apar) == -1) {
		lprintf(0, "ioctl SNDCTL_DSP_SETFMT failed (%s), S16_LE not supported??\n", strerror(errno));
		goto errsnd;
	}
	apar = 0;
	if (ioctl(state.fddsp, SNDCTL_DSP_STEREO, &apar) == -1) {
		lprintf(0, "ioctl SNDCTL_DSP_STEREO failed (%s), mono not supported??\n", strerror(errno));
		goto errsnd;
	}
	srate = cfg.bitrate;
	if (ioctl(state.fddsp, SNDCTL_DSP_SPEED, &srate) == -1) {
		lprintf(0, "ioctl SNDCTL_DSP_SPEED failed (%s), samplingrate %u not supported??\n", 
			strerror(errno), cfg.bitrate);
		goto errsnd;
	}
	lprintf(2, "epp adapter sampling rate %u, soundcard sampling rate %u\n", cfg.bitrate, srate);
	if (abs(cfg.bitrate - srate) > cfg.bitrate / 2) {
		lprintf(0, "sampling rates (%u,%u) too different\n", cfg.bitrate, srate);
		goto errsnd;
	}
	if (ioctl(state.fddsp, SNDCTL_DSP_GETOSPACE, &abinfo) == -1) {
		lprintf(0, "ioctl SNDCTL_DSP_GETOSPACE failed (%s)\n", strerror(errno));
		goto errsnd;
	}
	if (abinfo.fragstotal * abinfo.fragsize < 2*SNDLATENCY*2) {
		lprintf(0, "soundcard buffers too small (%u)\n", 
			abinfo.fragstotal * abinfo.fragsize);
		goto errsnd;
	}
	audio_initoutput(cfg.bitrate, srate);
	lprintf(0, "Audio Output to Linux Soundcard\n");
	/* reset the EPP adapter */
	buf[0] = 7;
	buf[1] = state.ctrlreg;
	if (parport_epp_write_addr(buf, 2) != 2)
		goto errret;
	/* prefill to nominal queue size and start soundcard */
	memset(sbuf, 0, sizeof(sbuf));
	if ((i = write(state.fddsp, sbuf, 2*SNDLATENCY)) != 2*SNDLATENCY) {
		lprintf(0, "write(%d) failed %i (%s)\n", 2*SNDLATENCY, i, strerror(errno));
		goto errsnd;
	}
	apar = PCM_ENABLE_OUTPUT;
	if (ioctl(state.fddsp, SNDCTL_DSP_SETTRIGGER, &apar) == -1) {
		lprintf(0, "ioctl SNDCTL_DSP_SETTRIGGER failed (%s)\n", strerror(errno));
		goto errsnd;
	}
	state.tmotag = gtk_timeout_add(100, periodic_play, NULL);
	state.state = st_play;
	return;

  errsnd:
	buf[0] = 7;
	if (parport_epp_write_addr(buf, 1) != 1)
		goto errret;
	state.state = st_off;
	goto errclose;

  errret:
	lprintf(0, "EPP timeout\n");
	state.state = st_mdmerr;
	reset_modem();
  errclose:
	close(state.fddsp);
}

static void recstart(void)
{
	unsigned char buf[SNDLATENCY];
	audio_buf_info abinfo;
	int apar;
	unsigned srate;
	int i;

	if (state.state == st_play)
		playstop();
	if (state.state == st_rec)
		recstop();
	state.ctrlreg = 0x20;
	state.ledcnt = 0;
	memset(state.sample, 0, sizeof(state.sample));
	/* start modem if mdmerr */
	if (state.state == st_mdmerr) {
		cfg.bitrate = SAMPLINGRATE;
		if ((i = adapter_start_eppsamp(&cfg))) {
			lprintf(0, "Cannot initialize the modem\n");
			return;
		}
	}
	state.state = st_off;
	if ((state.fddsp = open("/dev/dsp", O_RDONLY | O_NONBLOCK)) == -1) {
		lprintf(0, "cannot open /dev/dsp (%s)\n", strerror(errno));
		return;
	}
	fcntl(state.fddsp, F_SETFL, fcntl(state.fddsp, F_GETFL, 0) | O_NONBLOCK);
	if (ioctl(state.fddsp, SNDCTL_DSP_NONBLOCK, 0) == -1) {
		lprintf(0, "ioctl SNDCTL_DSP_NONBLOCK failed (%s)\n", strerror(errno));
		goto errsnd;
	}
	if (ioctl(state.fddsp, SNDCTL_DSP_GETCAPS, &apar) == -1) {
		lprintf(0, "ioctl SNDCTL_DSP_GETCAPS failed (%s)\n", strerror(errno));
		goto errsnd;
	}
	if (!(apar & DSP_CAP_TRIGGER)) {
		lprintf(0, "soundcard does not support trigger\n");
		goto errsnd;
	}
	apar = AFMT_S16_LE;
	if (ioctl(state.fddsp, SNDCTL_DSP_SETFMT, &apar) == -1) {
		lprintf(0, "ioctl SNDCTL_DSP_SETFMT failed (%s), S16_LE not supported??\n", strerror(errno));
		goto errsnd;
	}
	apar = 0;
	if (ioctl(state.fddsp, SNDCTL_DSP_STEREO, &apar) == -1) {
		lprintf(0, "ioctl SNDCTL_DSP_STEREO failed (%s), mono not supported??\n", strerror(errno));
		goto errsnd;
	}
	srate = cfg.bitrate;
	if (ioctl(state.fddsp, SNDCTL_DSP_SPEED, &srate) == -1) {
		lprintf(0, "ioctl SNDCTL_DSP_SPEED failed (%s), samplingrate %u not supported??\n", 
			strerror(errno), cfg.bitrate);
		goto errsnd;
	}
	lprintf(2, "epp adapter sampling rate %u, soundcard sampling rate %u\n", cfg.bitrate, srate);
	if (abs(cfg.bitrate - srate) > cfg.bitrate / 2) {
		lprintf(0, "sampling rates (%u,%u) too different\n", cfg.bitrate, srate);
		goto errsnd;
	}
	if (ioctl(state.fddsp, SNDCTL_DSP_GETISPACE, &abinfo) == -1) {
		lprintf(0, "ioctl SNDCTL_DSP_GETISPACE failed (%s)\n", strerror(errno));
		goto errsnd;
	}
	if (abinfo.fragstotal * abinfo.fragsize < 2*SNDLATENCY*2) {
		lprintf(0, "soundcard buffers too small (%u)\n", 
			abinfo.fragstotal * abinfo.fragsize);
		goto errsnd;
	}
	audio_initinput(cfg.bitrate, srate);
	lprintf(0, "Audio Input from Linux Soundcard\n");
	/* reset the EPP adapter */
	buf[0] = 7;
	buf[1] = state.ctrlreg;
	if (parport_epp_write_addr(buf, 2) != 2)
		goto errret;
	/* prefill EPP adapter start soundcard */
	memset(buf, 0, sizeof(buf));
	if (parport_epp_write_data(buf, SNDLATENCY) != SNDLATENCY)
		goto errret;
	apar = PCM_ENABLE_INPUT;
	if (ioctl(state.fddsp, SNDCTL_DSP_SETTRIGGER, &apar) == -1) {
		lprintf(0, "ioctl SNDCTL_DSP_SETTRIGGER failed (%s)\n", strerror(errno));
		goto errsnd;
	}
	state.tmotag = gtk_timeout_add(100, periodic_rec, NULL);
	state.state = st_rec;
	return;

  errsnd:
	buf[0] = 7;
	if (parport_epp_write_addr(buf, 1) != 1)
		goto errret;
	state.state = st_off;
	goto errclose;
	
  errret:
	lprintf(0, "EPP timeout\n");
	state.state = st_mdmerr;
	reset_modem();
  errclose:
	close(state.fddsp);
}

/* ---------------------------------------------------------------------- */

void audio_ptt(int ptt)
{
	if (ptt && state.state != st_rec)
		recstart();
	else if (!ptt && state.state != st_play)
		playstart();
}

int audio_drvinit(const char *config)
{
	return 0;
}

/* ---------------------------------------------------------------------- */
#endif /* HAVE_LINUX_SOUNDCARD_H */

