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

/*
 *      audiolinux.c  --  Audio processing for "virtual transceiver", Linux IO.
 *
 *      Copyright (C) 1999, 2001  Thomas Sailer (t.sailer@alumni.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

/* AIX requires this to be the first thing in the file.  */
#ifndef __GNUC__
# if HAVE_ALLOCA_H
#  include <alloca.h>
# else
#  ifdef _AIX
#pragma alloca
#  else
#   ifndef alloca /* predefined by HP cc +Olibcalls */
char *alloca ();
#   endif
#  endif
# endif
#endif

#include <sys/types.h>

#if defined(HAVE_SYS_SOUNDCARD_H)
#include <sys/soundcard.h>
#elif defined(HAVE_LINUX_SOUNDCARD_H)
#include <linux/soundcard.h>
#endif
#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 "trxapi.h"
#include "trx.h"

/* ---------------------------------------------------------------------- */
#if (defined(HAVE_LINUX_SOUNDCARD_H) || defined(HAVE_SYS_SOUNDCARD_H))

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

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

static void stop(struct trx_thread_state *state)
{
	if (state->du.a.io.fdout != -1 && state->du.a.io.fdout != state->du.a.io.fdin)
		close(state->du.a.io.fdout);
	if (state->du.a.io.fdin != -1)
		close(state->du.a.io.fdin);
	state->du.a.io.fdin = state->du.a.io.fdout = -1;
}

static void start(struct trx_thread_state *state)
{
	int16_t buf[SNDLATENCY];
	audio_buf_info abinfo;
	int apar;
	unsigned srate;

	stop(state);
	if (strncmp(state->cfg.adapt.audiodevin, state->cfg.adapt.audiodevout, sizeof(state->cfg.adapt.audiodevin))) {
		if ((state->du.a.io.fdin = open(state->cfg.adapt.audiodevin, O_RDONLY | O_NONBLOCK)) == -1) {
			lprintf(0, "cannot open \"%s\" (%s)\n", state->cfg.adapt.audiodevin, strerror(errno));
			goto errsnd;
		}
		if ((state->du.a.io.fdout = open(state->cfg.adapt.audiodevout, O_WRONLY | O_NONBLOCK)) == -1) {
			lprintf(0, "cannot open \"%s\" (%s)\n", state->cfg.adapt.audiodevout, strerror(errno));
			goto errsnd;
		}
	} else {
		if ((state->du.a.io.fdin = state->du.a.io.fdout = open(state->cfg.adapt.audiodevin, O_RDWR | O_NONBLOCK)) == -1) {
			lprintf(0, "cannot open \"%s\" (%s)\n", state->cfg.adapt.audiodevin, strerror(errno));
			goto errsnd;
		}
	}
	fcntl(state->du.a.io.fdin, F_SETFL, fcntl(state->du.a.io.fdin, F_GETFL, 0) | O_NONBLOCK);
	if (state->du.a.io.fdin != state->du.a.io.fdout)
		fcntl(state->du.a.io.fdout, F_SETFL, fcntl(state->du.a.io.fdout, F_GETFL, 0) | O_NONBLOCK);
	if (ioctl(state->du.a.io.fdin, SNDCTL_DSP_NONBLOCK, 0) == -1) {
		lprintf(0, "ioctl SNDCTL_DSP_NONBLOCK failed (%s)\n", strerror(errno));
		goto errsnd;
	}
	if (state->du.a.io.fdin != state->du.a.io.fdout && ioctl(state->du.a.io.fdout, SNDCTL_DSP_NONBLOCK, 0) == -1) {
		lprintf(0, "ioctl SNDCTL_DSP_NONBLOCK failed (%s)\n", strerror(errno));
		goto errsnd;
	}
	if (ioctl(state->du.a.io.fdin, 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;
	}
	if (state->du.a.io.fdin != state->du.a.io.fdout) {
		if (ioctl(state->du.a.io.fdout, 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;
		}
	} else {
		if (!(apar & DSP_CAP_DUPLEX)) {
			lprintf(0, "soundcard does not support full duplex\n");
			goto errsnd;
		}
	}
	apar = AFMT_S16_LE;
	if (ioctl(state->du.a.io.fdin, SNDCTL_DSP_SETFMT, &apar) == -1) {
		lprintf(0, "ioctl SNDCTL_DSP_SETFMT failed (%s), S16_LE not supported??\n", strerror(errno));
		goto errsnd;
	}
#if 0
	apar = 0;
	if (ioctl(state->du.a.io.fdin, SNDCTL_DSP_STEREO, &apar) == -1) {
		lprintf(0, "ioctl SNDCTL_DSP_STEREO failed (%s), mono not supported??\n", strerror(errno));
		goto errsnd;
	}
#else
	apar = 1;
	if (ioctl(state->du.a.io.fdin, SNDCTL_DSP_CHANNELS, &apar) == -1) {
		lprintf(0, "ioctl SNDCTL_DSP_CHANNELS failed (%s), mono not supported??\n", strerror(errno));
		goto errsnd;
	}
#endif
	srate = state->du.a.p.srateusb;
	if (ioctl(state->du.a.io.fdin, SNDCTL_DSP_SPEED, &srate) == -1) {
		lprintf(0, "ioctl SNDCTL_DSP_SPEED failed (%s), samplingrate %u not supported??\n", 
			strerror(errno), state->du.a.p.srateusb);
		goto errsnd;
	}
 	state->du.a.p.sratedspout = state->du.a.p.sratedspin = srate;
	if (state->du.a.io.fdin != state->du.a.io.fdout) {
		apar = AFMT_S16_LE;
		if (ioctl(state->du.a.io.fdout, SNDCTL_DSP_SETFMT, &apar) == -1) {
			lprintf(0, "ioctl SNDCTL_DSP_SETFMT failed (%s), S16_LE not supported??\n", strerror(errno));
			goto errsnd;
		}
#if 0
		apar = 0;
		if (ioctl(state->du.a.io.fdout, SNDCTL_DSP_STEREO, &apar) == -1) {
			lprintf(0, "ioctl SNDCTL_DSP_STEREO failed (%s), mono not supported??\n", strerror(errno));
			goto errsnd;
		}
#else
		apar = 1;
		if (ioctl(state->du.a.io.fdout, SNDCTL_DSP_CHANNELS, &apar) == -1) {
			lprintf(0, "ioctl SNDCTL_DSP_CHANNELS failed (%s), mono not supported??\n", strerror(errno));
			goto errsnd;
		}
#endif
		srate = state->du.a.p.srateusb;
		if (ioctl(state->du.a.io.fdout, SNDCTL_DSP_SPEED, &srate) == -1) {
			lprintf(0, "ioctl SNDCTL_DSP_SPEED failed (%s), samplingrate %u not supported??\n", 
				strerror(errno), state->du.a.p.srateusb);
			goto errsnd;
		}
	 	state->du.a.p.sratedspout = srate;
}
	lprintf(2, "usb adapter sampling rate %u, soundcard sampling rate in %u out %u\n", 
		state->du.a.p.srateusb, state->du.a.p.sratedspin, state->du.a.p.sratedspout);
	if (ioctl(state->du.a.io.fdin, 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;
	}
	if (ioctl(state->du.a.io.fdout, 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;
	}
	if (state->du.a.io.fdin != state->du.a.io.fdout) {
		apar = PCM_ENABLE_INPUT;
		if (ioctl(state->du.a.io.fdin, SNDCTL_DSP_SETTRIGGER, &apar) == -1) {
			lprintf(0, "ioctl SNDCTL_DSP_SETTRIGGER failed (%s)\n", strerror(errno));
			goto errsnd;
		}
		apar = PCM_ENABLE_OUTPUT;
		if (ioctl(state->du.a.io.fdout, SNDCTL_DSP_SETTRIGGER, &apar) == -1) {
			lprintf(0, "ioctl SNDCTL_DSP_SETTRIGGER failed (%s)\n", strerror(errno));
			goto errsnd;
		}
	} else {
		apar = PCM_ENABLE_INPUT|PCM_ENABLE_OUTPUT;
		if (ioctl(state->du.a.io.fdin, SNDCTL_DSP_SETTRIGGER, &apar) == -1) {
			lprintf(0, "ioctl SNDCTL_DSP_SETTRIGGER failed (%s)\n", strerror(errno));
			goto errsnd;
		}
	}
	state->du.a.io.inzerosamples = SNDLATENCY;
	memset(buf, 0, sizeof(buf));
	write(state->du.a.io.fdout, buf, sizeof(buf));
	return;

  errsnd:
	stop(state);
	state->du.a.p.sratedspin = state->du.a.p.sratedspout = state->du.a.p.srateusb;
}

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

void audio_globalinit(void)
{
}

struct trxapi_baycomusb_adapter_audio_devs *audio_get_device_list(void)
{
	struct trxapi_baycomusb_adapter_audio_devs *ad;

	/* retrieve audio devs */
	if (!(ad = calloc(1, sizeof(struct trxapi_baycomusb_adapter_audio_devs) + 
			  4 * sizeof(trxapi_audiodevice_t) + 
			  4 * sizeof(trxapi_audiodevice_t))))
		return NULL;
	ad->audiodevsin = (void *)(ad + 1);
	ad->nraudiodevsin = 4;
	ad->audiodevsout = &ad->audiodevsin[4];
	ad->nraudiodevsout = 4;
	strncpy(ad->audiodevsin[0], "/dev/dsp", sizeof(ad->audiodevsin[0]));
	strncpy(ad->audiodevsin[1], "/dev/dsp1", sizeof(ad->audiodevsin[1]));
	strncpy(ad->audiodevsin[2], "/dev/dsp2", sizeof(ad->audiodevsin[2]));
	strncpy(ad->audiodevsin[3], "/dev/dsp3", sizeof(ad->audiodevsin[3]));
	strncpy(ad->audiodevsout[0], "/dev/dsp", sizeof(ad->audiodevsout[0]));
	strncpy(ad->audiodevsout[1], "/dev/dsp1", sizeof(ad->audiodevsout[1]));
	strncpy(ad->audiodevsout[2], "/dev/dsp2", sizeof(ad->audiodevsout[2]));
	strncpy(ad->audiodevsout[3], "/dev/dsp3", sizeof(ad->audiodevsout[3]));
	return ad;
}

void audio_open(struct trx_thread_state *state)
{
	state->du.a.p.srateusb = state->du.a.p.sratedspin = state->du.a.p.sratedspout = AUDIOSAMPLINGRATE;
	start(state);
	audioproc_init(state);
}

void audio_close(struct trx_thread_state *state)
{
	stop(state);
}

void audio_input(struct trx_thread_state *state, const signed char *samples, unsigned int nrsamples)
{
	int16_t sbuf[BUFSIZE];
	unsigned int apar, cnt;
	int r;

	/* usb->dsp direction */
	cnt = audioproc_convertoutput(state, nrsamples, samples, sbuf);
	if (state->du.a.io.fdout == -1)
		return;
	if ((r = write(state->du.a.io.fdout, sbuf, 2*cnt)) != 2*cnt) {
		lprintf(0, "write(%d) failed %i (%s)\n", 2*cnt, r, strerror(errno));
		goto err;
	}
	if (ioctl(state->du.a.io.fdout, SNDCTL_DSP_GETODELAY, &apar) == -1) {
		lprintf(0, "ioctl SNDCTL_DSP_GETODELAY failed (%s)\n", strerror(errno));
		goto err;
	}
	apar >>= 1;
	/* adjust speed */
	if (audioproc_adjustoutput(state, apar))
		goto err;
	return;

  err:
	stop(state);
}

void audio_output(struct trx_thread_state *state, signed char *samples, unsigned int nrsamples)
{
	int16_t *sbuf;
	unsigned int isamples;
	audio_buf_info abinfo;
	int r;

	/* dsp->usb direction */
	if (!nrsamples)
		return;
	if (state->du.a.io.fdin == -1) {
		isamples = audioproc_convertinput_isamples(state, nrsamples);
		sbuf = alloca(isamples * sizeof(sbuf[0]));
		memset(sbuf, 0, isamples * sizeof(sbuf[0]));
		audioproc_convertinput(state, isamples, nrsamples, sbuf, samples);
		return;
	}
	isamples = audioproc_convertinput_isamples(state, nrsamples);
	sbuf = alloca(isamples * sizeof(sbuf[0]));
	memset(sbuf, 0, isamples * sizeof(sbuf[0]));
	if (state->du.a.io.inzerosamples >= isamples) {
		state->du.a.io.inzerosamples -= isamples;
	} else {
		state->du.a.io.inzerosamples = 0;
		r = read(state->du.a.io.fdin, sbuf+state->du.a.io.inzerosamples, (isamples-state->du.a.io.inzerosamples) * sizeof(sbuf[0]));
		if (r == -1 && errno != EAGAIN) {
			audioproc_convertinput(state, isamples, nrsamples, sbuf, samples);
			lprintf(0, "read failed %i (%s)\n", r, strerror(errno));
			goto err;
		}
	}
	audioproc_convertinput(state, isamples, nrsamples, sbuf, samples);
	if (ioctl(state->du.a.io.fdin, SNDCTL_DSP_GETISPACE, &abinfo) == -1) {
		lprintf(0, "ioctl SNDCTL_DSP_GETISPACE failed (%s)\n", strerror(errno));
		goto err;
	}
	/* adjust speed */
	if (audioproc_adjustinput(state, abinfo.bytes / sizeof(sbuf[0]) + state->du.a.io.inzerosamples))
		goto err;
	return;

  err:
	stop(state);
}

/* ---------------------------------------------------------------------- */
#else /* !HAVE_LINUX_SOUNDCARD_H && !HAVE_SYS_SOUNDCARD_H */

void audio_globalinit(void)
{
}

struct trxapi_baycomusb_adapter_audio_devs *audio_get_device_list(void)
{
	return NULL;
}

void audio_open(struct trx_thread_state *state)
{
	state->du.a.p.srateusb = state->du.a.p.sratedspin = state->du.a.p.sratedspout = AUDIOSAMPLINGRATE;
	audioproc_init(state);
}

void audio_close(struct trx_thread_state *state)
{
}

void audio_input(struct trx_thread_state *state, const signed char *samples, unsigned int nrsamples)
{
	audioproc_convertoutput(state, nrsamples, samples, sbuf);
}

void audio_output(struct trx_thread_state *state, signed char *samples, unsigned int nrsamples)
{
	int16_t *sbuf;
	unsigned int isamples;

	if (!nrsamples)
		return;
	isamples = audioproc_convertinput_isamples(state, nrsamples);
	sbuf = alloca(isamples * sizeof(sbuf[0]));
	memset(sbuf, 0, isamples * sizeof(sbuf[0]));
	audioproc_convertinput(state, isamples, nrsamples, sbuf, samples);
}

/* ---------------------------------------------------------------------- */
#endif /* HAVE_LINUX_SOUNDCARD_H || HAVE_SYS_SOUNDCARD_H */
