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

/*
 *      audio.c  --  Audio processing for "virtual transceiver".
 *
 *      Copyright (C) 1999-2000  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

#include <sys/types.h>

#define G_LOG_DOMAIN "eppfm"

#ifdef HAVE_LINUX_SOUNDCARD_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>
#endif	

#ifdef HAVE_DIRECTX
#include <directx.h>
#endif

#include "eppfm.h"

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

struct {
	/* gtk timeout handle */
	gint tmotag;
	/* sampling rate conversion state */
	unsigned phinceppdsp, phincdspepp, pheppdsp, phdspepp;
	/* ptt state */
	unsigned ptt_state;
	/* control register */
	unsigned char ctrlreg;
	/* led blink counter */
	unsigned ledcnt;
	/* level counter */
	unsigned lvlicnt, lvliaccum, lvli, lvlocnt, lvloaccum, lvlo;
	/* frequency generators */
	unsigned phase0, phase1, phase2, phase3;
	unsigned freq0, freq1, freq2, freq3;
	
	/* low level driver specific data */

#ifdef HAVE_LINUX_SOUNDCARD_H
	int fddsp;
#endif	
#ifdef HAVE_DIRECTX
	LPDIRECTSOUND dsplay;
	LPDIRECTSOUNDCAPTURE dsrec;
	LPDIRECTSOUNDBUFFER playbuf;
	LPDIRECTSOUNDCAPTUREBUFFER recbuf;
	HANDLE hinst;
	HWND hwnd;
	DWORD playbufsz, recbufsz, playptr, recptr;
#endif

} state;

#define SNDLATENCY 4000   /* approx 1/2 seconds */
#define BUFSIZE    8192   /* must be significantly bigger than SNDLATENCY! */
#define PHASEFRAC  12

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

void audio_ptt(int ptt)
{
	if (ptt)
		state.ptt_state |= 1;
	else
		state.ptt_state &= ~1;
}

void audio_1750(int on)
{
	if (on) {
		state.freq2 = (1750*65536+8000/2)/8000;
	} else {
		state.freq2 = 0;
		state.phase2 = 0x4000;
	}
}

/*
 *
 * DTMF frequencies
 *
 *      1209 1336 1477 1633
 *  697   1    2    3    A
 *  770   4    5    6    B
 *  852   7    8    9    C
 *  941   *    0    #    D
 * 
 */

void audio_dtmf(int ch)
{
	switch (ch) {
	case 0:
		state.freq0 = (1336*65536+8000/2)/8000;
		state.freq1 = (941*65536+8000/2)/8000;
		break;

	case 1:
		state.freq0 = (1209*65536+8000/2)/8000;
		state.freq1 = (697*65536+8000/2)/8000;
		break;

	case 2:
		state.freq0 = (1336*65536+8000/2)/8000;
		state.freq1 = (697*65536+8000/2)/8000;
		break;

	case 3:
		state.freq0 = (1477*65536+8000/2)/8000;
		state.freq1 = (697*65536+8000/2)/8000;
		break;

	case 4:
		state.freq0 = (1209*65536+8000/2)/8000;
		state.freq1 = (770*65536+8000/2)/8000;
		break;

	case 5:
		state.freq0 = (1336*65536+8000/2)/8000;
		state.freq1 = (770*65536+8000/2)/8000;
		break;

	case 6:
		state.freq0 = (1477*65536+8000/2)/8000;
		state.freq1 = (770*65536+8000/2)/8000;
		break;

	case 7:
		state.freq0 = (1209*65536+8000/2)/8000;
		state.freq1 = (852*65536+8000/2)/8000;
		break;

	case 8:
		state.freq0 = (1336*65536+8000/2)/8000;
		state.freq1 = (852*65536+8000/2)/8000;
		break;

	case 9:
		state.freq0 = (1477*65536+8000/2)/8000;
		state.freq1 = (852*65536+8000/2)/8000;
		break;

	case 10:
		state.freq0 = (1633*65536+8000/2)/8000;
		state.freq1 = (697*65536+8000/2)/8000;
		break;

	case 11:
		state.freq0 = (1633*65536+8000/2)/8000;
		state.freq1 = (770*65536+8000/2)/8000;
		break;

	case 12:
		state.freq0 = (1633*65536+8000/2)/8000;
		state.freq1 = (852*65536+8000/2)/8000;
		break;

	case 13:
		state.freq0 = (1633*65536+8000/2)/8000;
		state.freq1 = (941*65536+8000/2)/8000;
		break;

	case 14:
		state.freq0 = (1209*65536+8000/2)/8000;
		state.freq1 = (941*65536+8000/2)/8000;
		break;

	case 15:
		state.freq0 = (1477*65536+8000/2)/8000;
		state.freq1 = (941*65536+8000/2)/8000;
		break;

	default:
		state.freq0 = state.freq1 = 0;
		state.phase0 = state.phase1 = 0x4000;
		break;
	}
}

unsigned audio_getilevel(void)
{
	return state.lvli;
}

unsigned audio_getolevel(void)
{
	return state.lvlo;
}

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

#define LEVELACCUM 2048

static void process_input(int16_t *buf, unsigned cnt)
{
	unsigned i;

	for (i = 0; i < cnt; i++) {
		state.lvliaccum += ((int)buf[i] * (int)buf[i]) >> 16;
		if ((++state.lvlicnt) >= LEVELACCUM) {
			state.lvli = state.lvliaccum / LEVELACCUM;
			state.lvlicnt = state.lvliaccum = 0;
		}
	}
}

static void process_output(int16_t *buf, unsigned cnt)
{
	unsigned i;
	int acc;

	for (i = 0; i < cnt; i++) {
		acc = buf[i];
		state.lvloaccum += (acc * acc) >> 16;
		if ((++state.lvlocnt) >= LEVELACCUM) {
			state.lvlo = state.lvloaccum / LEVELACCUM;
			state.lvlocnt = state.lvloaccum = 0;
		}
		acc += (SIN(state.phase0) + SIN(state.phase1) + SIN(state.phase2) + SIN(state.phase3)) >> 1;
		if (acc > 32767)
			acc = 32767;
		else if (acc < -32768)
			acc = -32768;
		buf[i] = acc;
		state.phase0 += state.freq0;
		state.phase1 += state.freq1;
		state.phase2 += state.freq2;
		state.phase3 += state.freq3;
	}
}

static void initstate(void)
{
	state.pheppdsp = 0;
	state.phdspepp = 0;
	state.ptt_state = 0;
	state.ctrlreg = 0;
	state.ledcnt = 0;
	state.lvlicnt = state.lvliaccum = state.lvli = 0;
	state.lvlocnt = state.lvloaccum = state.lvlo = 0;
	state.phase0 = state.phase1 = state.phase2 = state.phase3 = 0x4000;
	state.freq0 = state.freq1 = state.freq2 = state.freq3 = 0;
}

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

#ifdef HAVE_LINUX_SOUNDCARD_H

void audio_stop(void)
{
	if (state.fddsp != -1) {
		gtk_timeout_remove(state.tmotag);
		close(state.fddsp);
		state.fddsp = -1;
		reset_modem();
	}
}

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

	/* first do the 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;
		if (parport_epp_read_data(buf.s, icnt) != icnt)
			goto err;
		state.pheppdsp &= 0xff;
		cnt = 0;
		while (state.pheppdsp < (icnt << PHASEFRAC)) {
			sbuf[cnt] = buf.s[state.pheppdsp >> PHASEFRAC] << 8;
			state.pheppdsp += state.phinceppdsp;
			cnt++;
		}
		process_input(sbuf, cnt);
		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 */
		lprintf(4, "icnt %d cnt %d odel %d\n", icnt, cnt, apar);
		if (apar > SNDLATENCY)
			state.phinceppdsp++;
		else if (apar < SNDLATENCY)
			state.phinceppdsp--;
		if (state.phinceppdsp < (0x9 << (PHASEFRAC-4)) || state.phinceppdsp > (0x1f << (PHASEFRAC-4))) {
			lprintf(0, "phinceppdsp (0x%05x) out of range\n", state.phinceppdsp);
			goto err;
		}
		/* play games with the LEDS */
		state.ledcnt += icnt;
		if (state.ledcnt >= 4000) {
			state.ledcnt %= 4000;
			state.ctrlreg += 0x40;
		}
	}
	/* next do the dsp->epp direction */
	/* read sound */
	ocnts = read(state.fddsp, sbuf, 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;
	process_output(sbuf, ocnts);
	if (state.ptt_state == 2) {
		state.ptt_state = 0;
		state.ctrlreg &= ~0x20;
		buf.u[0] = state.ctrlreg;
		if (parport_epp_write_addr(buf.u, 1) != 1)
			goto err;
	}
	if (!state.ptt_state)
		goto endper;
	/* get FIFO count */
	state.ctrlreg |= 0x20;
	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;
	if (state.ptt_state == 1) {
		if (ocnt < SNDLATENCY) {
			memset(buf.s, 0, SNDLATENCY-ocnt);
			if (parport_epp_write_data(buf.s, SNDLATENCY-ocnt) != SNDLATENCY-ocnt)
				goto err;
		}
		state.ptt_state = 3;
		goto endper;
	}
	if (ocnts > 0) {
		state.phdspepp &= 0xff;
		cnt = 0;
		while (state.phdspepp < (ocnts << PHASEFRAC)) {
			buf.s[cnt] = sbuf[state.phdspepp >> PHASEFRAC] >> 8;
			state.phdspepp += state.phincdspepp;
			cnt++;
		}
		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 */
	lprintf(4, "ocnts %d cnt %d ocnt %d\n", ocnts, cnt, ocnt);
	if (ocnt > SNDLATENCY)
		state.phincdspepp++;
	else if (ocnt < SNDLATENCY)
		state.phincdspepp--;
	if (state.phincdspepp < (0x9 << (PHASEFRAC-4)) || state.phincdspepp > (0x1f << (PHASEFRAC-4))) {
		lprintf(0, "phincdspepp (0x%05x) out of range\n", state.phincdspepp);
		goto err;
	}
endper:
	lprintf(3, "phase increments: 0x%05x 0x%05x\n", state.phinceppdsp, state.phincdspepp);
	return TRUE;

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

int audio_start(struct adapter_config *cfg)
{
	unsigned char buf[2];
	int16_t sbuf[SNDLATENCY];
	audio_buf_info aboinfo, abiinfo;
	int apar;
	unsigned srate;
	int i;

	initstate();
	cfg->bitrate = 8000;
	if ((i = adapter_start_eppsamp(cfg))) {
		lprintf(0, "Cannot initialize the modem\n");
		return i;
	}
	if ((state.fddsp = open("/dev/dsp", O_RDWR | O_NONBLOCK)) == -1) {
		lprintf(0, "cannot open /dev/dsp (%s)\n", strerror(errno));
		return -1;
	}
	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_DUPLEX)) {
		lprintf(0, "full duplex soundcard required\n");
		goto errsnd;
	}
	if (!(apar & DSP_CAP_TRIGGER)) {
		lprintf(0, "soundcard does not support trigger\n");
		goto errsnd;
	}
	if (ioctl(state.fddsp, SNDCTL_DSP_SETDUPLEX, 0) == -1) {
		lprintf(0, "ioctl SNDCTL_DSP_SETDUPLEX failed (%s)\n", strerror(errno));
		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;
	}
	state.phinceppdsp = ((1 << PHASEFRAC) * cfg->bitrate + srate / 2) / srate;
	state.phincdspepp = ((1 << PHASEFRAC) * srate + cfg->bitrate / 2) / cfg->bitrate;
	lprintf(2, "epp->dsp phase inc: 0x%05x  dsp->epp phase inc: 0x%05x\n", state.phinceppdsp, state.phincdspepp);
	if (ioctl(state.fddsp, SNDCTL_DSP_GETOSPACE, &aboinfo) == -1) {
		lprintf(0, "ioctl SNDCTL_DSP_GETOSPACE failed (%s)\n", strerror(errno));
		goto errsnd;
	}
	if (ioctl(state.fddsp, SNDCTL_DSP_GETISPACE, &abiinfo) == -1) {
		lprintf(0, "ioctl SNDCTL_DSP_GETISPACE failed (%s)\n", strerror(errno));
		goto errsnd;
	}
	if (aboinfo.fragstotal * aboinfo.fragsize < 2*SNDLATENCY*2 ||
	    abiinfo.fragstotal * abiinfo.fragsize < 2*SNDLATENCY*2) {
		lprintf(0, "soundcard buffers too small (%u,%u)\n", 
			aboinfo.fragstotal * aboinfo.fragsize, 
			abiinfo.fragstotal * abiinfo.fragsize);
		goto errsnd;
	}
	lprintf(0, "Audio IO to Linux Soundcard\n");
	/* reset the EPP adapter */
	buf[0] = 7;
	buf[1] = 0;
	if (parport_epp_write_addr(buf, 2) != 2)
		goto errret;
	/* prefill to nominal queue size and stard 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_INPUT | 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, NULL);
	return 0;

errret:
	lprintf(0, "EPP timeout\n");
errsnd:
	close(state.fddsp);
	return -1;
}

#endif /* HAVE_LINUX_SOUNDCARD_H */

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

#ifdef HAVE_DIRECTX

static BOOL CALLBACK DSEnumProc(LPGUID guid, LPCSTR lpszDesc, LPCSTR lpszDrvName, LPVOID lpcontext)
{
        lprintf(1, "has %sGUID, desc %s drvname %s\n", guid ? "" : "no ", lpszDesc, lpszDrvName);
        return TRUE;
}

#if 0
static LRESULT CALLBACK MyWndProc(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam)
{
	switch(umsg) {
		/* Add cases such as WM_CREATE, WM_COMMAND, WM_PAINT if you don't 
		   want to pass these messages along for default processing. */
	case WM_CLOSE:
		DestroyWindow(hwnd);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return DefWindowProc(hwnd, umsg, wParam, lParam);
}
#endif

void audio_stop(void)
{
	if (state.hwnd != NULL) {
		gtk_timeout_remove(state.tmotag);
		IDirectSoundBuffer_Stop(state.playbuf);
		IDirectSoundCaptureBuffer_Stop(state.recbuf);
		IDirectSoundBuffer_Release(state.playbuf);
		IDirectSoundCaptureBuffer_Release(state.recbuf);
		IDirectSoundCapture_Release(state.dsrec);
		IDirectSound_Release(state.dsplay);
#if 0
		DestroyWindow(state.hwnd);
		UnregisterClass("MyClass", state.hinst);
#endif
		state.hwnd = NULL;
		reset_modem();
	}
}

static gint periodic(gpointer data)
{
	union {
		unsigned char u[BUFSIZE];
		signed char s[BUFSIZE];
	} buf;
	int16_t sbuf[BUFSIZE];
	int16_t *sptr1, *sptr2;
	HRESULT res;
	DWORD lockbytes1, lockbytes2, delay;
	int icnt, ocnt, cnt, ocnts, omax;

	/* first do the 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;
		if (parport_epp_read_data(buf.s, icnt) != icnt)
			goto err;
		state.pheppdsp &= 0xff;
		cnt = 0;
		while (state.pheppdsp < (icnt << PHASEFRAC)) {
			sbuf[cnt] = buf.s[state.pheppdsp >> PHASEFRAC] << 8;
			state.pheppdsp += state.phinceppdsp;
			cnt++;
		}
		process_input(sbuf, cnt);
		if (FAILED(res = IDirectSoundBuffer_Lock(state.playbuf, state.playptr, cnt*2, 
							 (LPVOID)&sptr1, &lockbytes1,
							 (LPVOID)&sptr2, &lockbytes2, 0))) {
			lprintf(0, "IDirectSoundBuffer_Lock error %lu\n", res);
			goto err;
		}
		memcpy(sptr1, sbuf, lockbytes1);
		if (lockbytes1 < 2*cnt)
			memcpy(sptr2, sbuf + lockbytes1/2, 2*cnt - lockbytes1);
		if (FAILED(res = IDirectSoundBuffer_Unlock(state.playbuf, sptr1, lockbytes1, sptr2, lockbytes2))) {
			lprintf(0, "IDirectSoundBuffer_Unlock error %lu\n", res);
			goto err;
		}
		state.playptr = (state.playptr + 2*cnt) % state.playbufsz;
		/* get output delay */
		if (FAILED(res = IDirectSoundBuffer_GetCurrentPosition(state.playbuf, &delay, NULL))) {
			lprintf(0, "IDirectSoundBuffer_GetCurrentPosition error %lu\n", res);
			goto err;
		}
		delay = (state.playbufsz + state.playptr - delay) % state.playbufsz;
		delay >>= 1;
		/* adjust speed */
		lprintf(4, "icnt %d cnt %d odel %u\n", icnt, cnt, delay);
		if (delay > SNDLATENCY)
			state.phinceppdsp++;
		else if (delay < SNDLATENCY)
			state.phinceppdsp--;
		if (state.phinceppdsp < (0x9 << (PHASEFRAC-4)) || state.phinceppdsp > (0x1f << (PHASEFRAC-4))) {
			lprintf(0, "phinceppdsp (0x%05x) out of range\n", state.phinceppdsp);
			goto err;
		}
		/* play games with the LEDS */
		state.ledcnt += icnt;
		if (state.ledcnt >= 4000) {
			state.ledcnt %= 4000;
			state.ctrlreg += 0x40;
		}
	}
	/* next do the dsp->epp direction */
	/* read sound */
	if (FAILED(res = IDirectSoundCaptureBuffer_GetCurrentPosition(state.recbuf, &delay, NULL))) {
		lprintf(0, "IDirectSoundCaptureBuffer_GetCurrentPosition error %lu\n", res);
		goto err;
	}
	ocnts = (state.recbufsz + delay - state.recptr) % state.recbufsz;
	if (ocnts > 0) {
		if (FAILED(res = IDirectSoundCaptureBuffer_Lock(state.recbuf, state.recptr, ocnts, 
								(LPVOID)&sptr1, &lockbytes1,
								(LPVOID)&sptr2, &lockbytes2, 0))) {
			lprintf(0, "IDirectSoundCaptureBuffer_Lock error %lu\n", res);
			goto err;
		}
		memcpy(sbuf, sptr1, lockbytes1);
		if (lockbytes1 < ocnts)
			memcpy(sbuf + lockbytes1/2, sptr2, ocnts - lockbytes1);
		state.recptr = (state.recptr + ocnts) % state.recbufsz;
		if (FAILED(res = IDirectSoundCaptureBuffer_Unlock(state.recbuf, sptr1, lockbytes1, sptr2, lockbytes2))) {
			lprintf(0, "IDirectSoundCaptureBuffer_Unlock error %lu\n", res);
			goto err;
		}
	}
	ocnts >>= 1;
	process_output(sbuf, ocnts);
	if (state.ptt_state == 2) {
		state.ptt_state = 0;
		state.ctrlreg &= ~0x20;
		buf.u[0] = state.ctrlreg;
		if (parport_epp_write_addr(buf.u, 1) != 1)
			goto err;
	}
	if (!state.ptt_state)
		goto endper;
	/* get FIFO count */
	state.ctrlreg |= 0x20;
	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;
	if (state.ptt_state == 1) {
		if (ocnt < SNDLATENCY) {
			memset(buf.s, 0, SNDLATENCY-ocnt);
			if (parport_epp_write_data(buf.s, SNDLATENCY-ocnt) != SNDLATENCY-ocnt)
				goto err;
		}
		state.ptt_state = 3;
		goto endper;
	}
	if (ocnts > 0) {
		state.phdspepp &= 0xff;
		cnt = 0;
		while (state.phdspepp < (ocnts << PHASEFRAC)) {
			buf.s[cnt] = sbuf[state.phdspepp >> PHASEFRAC] >> 8;
			state.phdspepp += state.phincdspepp;
			cnt++;
		}
		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 */
	lprintf(4, "ocnts %d cnt %d ocnt %d\n", ocnts, cnt, ocnt);
	if (ocnt > SNDLATENCY)
		state.phincdspepp++;
	else if (ocnt < SNDLATENCY)
		state.phincdspepp--;
	if (state.phincdspepp < (0x9 << (PHASEFRAC-4)) || state.phincdspepp > (0x1f << (PHASEFRAC-4))) {
		lprintf(0, "phincdspepp (0x%05x) out of range\n", state.phincdspepp);
		goto err;
	}
endper:
	lprintf(3, "phase increments: 0x%05x 0x%05x\n", state.phinceppdsp, state.phincdspepp);
	return TRUE;

err:
	lprintf(0, "EPP timeout\n");
	IDirectSoundBuffer_Stop(state.playbuf);
	IDirectSoundCaptureBuffer_Stop(state.recbuf);
	IDirectSoundBuffer_Release(state.playbuf);
	IDirectSoundCaptureBuffer_Release(state.recbuf);
	IDirectSoundCapture_Release(state.dsrec);
	IDirectSound_Release(state.dsplay);
#if 0
	DestroyWindow(state.hwnd);
	UnregisterClass("MyClass", state.hinst);
#endif
	state.hwnd = NULL;
	reset_modem();
	return FALSE;
}

int audio_start(struct adapter_config *cfg)
{
	WNDCLASS wndclass;
	HRESULT res;
        WAVEFORMATEX waveformat;
        DSBUFFERDESC bdesc;
        DSCBUFFERDESC cbdesc;
	DWORD lockbytes;
	int16_t *sptr;
	int retval = -1;
	unsigned char buf[2];
	int i;

	state.dsplay = NULL;
	state.dsrec = NULL;
	state.playbuf = NULL;
	state.recbuf = NULL;
	state.hwnd = NULL;
	state.hinst = GetModuleHandleA(0);
	state.playptr = 0;
	state.recptr = 0;
	initstate();
        lprintf(1, "DirectSound drivers\n");
        DirectSoundEnumerateA(DSEnumProc, NULL);
        lprintf(1, "DirectSoundCapture drivers\n");
        DirectSoundCaptureEnumerateA(DSEnumProc, NULL);
#if 0
	wndclass.style = CS_HREDRAW | CS_VREDRAW;
	wndclass.lpfnWndProc = (WNDPROC)MyWndProc;
	wndclass.cbClsExtra = 0;
	wndclass.cbWndExtra = 0;
	wndclass.hIcon = NULL;
	wndclass.hInstance = state.hinst;
	wndclass.hCursor = NULL;
	wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wndclass.lpszMenuName = NULL;
	wndclass.lpszClassName = "MyClass";
	if (!RegisterClass(&wndclass)) {
		lprintf(0, "RegisterClass error, %d\n", GetLastError());
		return -1;
	}
	state.hwnd = CreateWindowEx(WS_EX_APPWINDOW, "MyClass", "eppfpga", WS_DISABLED | WS_POPUP,
				    0, 0, 0, 0, NULL, NULL, state.hinst, NULL);
	if (!state.hwnd) {
		lprintf(0, "CreateWindowEx error, %d\n", GetLastError());
		goto errcreatewin;
	}
#else
	state.hwnd = GetDesktopWindow();
#endif
        if (FAILED(res = DirectSoundCreate(NULL, &state.dsplay, NULL))) {
                lprintf(0, "DirectSoundCreate error %lu\n", res);
                goto errdscreate;
        }
        if (FAILED(res = IDirectSound_SetCooperativeLevel(state.dsplay, state.hwnd, DSSCL_NORMAL))) {
                lprintf(0, "SetCooperativeLevel error %lu\n", res);
                goto errdscreate1;
        }
        if (FAILED(res = DirectSoundCaptureCreate(NULL, &state.dsrec, NULL))) {
                lprintf(0, "DirectSoundCaptureCreate error %lu\n", res);
                goto errdscreate1;
        }
        memset(&waveformat, 0, sizeof(waveformat));
        waveformat.wFormatTag = WAVE_FORMAT_PCM;
        waveformat.wBitsPerSample = 8;
        waveformat.nChannels = 1;
        waveformat.nSamplesPerSec = 8000;
        waveformat.nBlockAlign = waveformat.nChannels * waveformat.wBitsPerSample / 8;
        waveformat.nAvgBytesPerSec = waveformat.nSamplesPerSec * waveformat.nBlockAlign;
        memset(&cbdesc, 0, sizeof(cbdesc));
        cbdesc.dwSize = sizeof(cbdesc);
        cbdesc.dwFlags = /* DSCBCAPS_WAVEMAPPED */ 0;
        state.recbufsz = cbdesc.dwBufferBytes = waveformat.nAvgBytesPerSec;
        cbdesc.lpwfxFormat = &waveformat;
        if (FAILED(res = IDirectSoundCapture_CreateCaptureBuffer(state.dsrec, &cbdesc, &state.recbuf, NULL))) {
                lprintf(0, "CreateSoundCaptureBuffer error %lu\n", res);
                goto errdscreate2;
        }
        memset(&waveformat, 0, sizeof(waveformat));
        waveformat.wFormatTag = WAVE_FORMAT_PCM;
        waveformat.wBitsPerSample = 8;
        waveformat.nChannels = 1;
        waveformat.nSamplesPerSec = 8000;
        waveformat.nBlockAlign = waveformat.nChannels * waveformat.wBitsPerSample / 8;
        waveformat.nAvgBytesPerSec = waveformat.nSamplesPerSec * waveformat.nBlockAlign;
        memset(&bdesc, 0, sizeof(bdesc));
        bdesc.dwSize = sizeof(bdesc);
        bdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLPAN;
        state.playbufsz = bdesc.dwBufferBytes = waveformat.nAvgBytesPerSec;
        bdesc.lpwfxFormat = &waveformat;
        if (FAILED(res = IDirectSound_CreateSoundBuffer(state.dsplay, &bdesc, &state.playbuf, NULL))) {
                lprintf(0, "CreateSoundBuffer error %lu\n", res);
                goto errdscreate3;
        }
	cfg->bitrate = 8000;
	if ((i = adapter_start_eppsamp(cfg))) {
		retval = i;
		lprintf(0, "Cannot initialize the modem\n");
		goto errdscreate4;
	}
	state.phinceppdsp = ((1 << PHASEFRAC) * cfg->bitrate + 8000 / 2) / 8000;
	state.phincdspepp = ((1 << PHASEFRAC) * 8000 + cfg->bitrate / 2) / cfg->bitrate;
	lprintf(2, "epp->dsp phase inc: 0x%05x  dsp->epp phase inc: 0x%05x\n", state.phinceppdsp, state.phincdspepp);
	lprintf(0, "Audio IO to DirectX\n");
	/* reset the EPP adapter */
	buf[0] = 7;
	buf[1] = 0;
	if (parport_epp_write_addr(buf, 2) != 2)
		goto errret;
	/* prefill to nominal queue size and stard soundcard */
	if (FAILED(res = IDirectSoundBuffer_Lock(state.playbuf, 0, 2*SNDLATENCY, (LPVOID)&sptr, &lockbytes, NULL, NULL, 0))) {
                lprintf(0, "IDirectSoundBuffer_Lock error %lu\n", res);
                goto errresmodem;
        }
	memset(sptr, 0, 2*SNDLATENCY);
	if (FAILED(res = IDirectSoundBuffer_Unlock(state.playbuf, sptr, lockbytes, NULL, 0))) {
                lprintf(0, "IDirectSoundBuffer_Unlock error %lu\n", res);
                goto errresmodem;
        }
	state.playptr = SNDLATENCY;
	if (FAILED(res = IDirectSoundBuffer_Play(state.playbuf, 0, 0, DSBPLAY_LOOPING))) {
                lprintf(0, "IDirectSoundBuffer_Play error %lu\n", res);
                goto errresmodem;
        }
	if (FAILED(res = IDirectSoundCaptureBuffer_Start(state.recbuf, DSCBSTART_LOOPING))) {
                lprintf(0, "IDirectSoundCaptureBuffer_Start error %lu\n", res);
                goto errresmodem;
        }
	state.tmotag = gtk_timeout_add(100, periodic, NULL);
	return 0;

errret:
	lprintf(0, "EPP timeout\n");
errresmodem:
	IDirectSoundBuffer_Stop(state.playbuf);
	IDirectSoundCaptureBuffer_Stop(state.recbuf);
	reset_modem();
errdscreate4:
	IDirectSoundBuffer_Release(state.playbuf);
errdscreate3:
	IDirectSoundCaptureBuffer_Release(state.recbuf);
errdscreate2:
	IDirectSoundCapture_Release(state.dsrec);
errdscreate1:
	IDirectSound_Release(state.dsplay);
errdscreate:
#if 0
	DestroyWindow(state.hwnd);
errcreatewin:
	UnregisterClass("MyClass", state.hinst);
#endif
	return -1;
}

#endif /* HAVE_DIRECTX */

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