/*
 * Copyright (c) 1993,1994 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted 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 Systems
 *	Engineering Group at Lawrence Berkeley Laboratory.
 * 4. Neither the name of the University nor of the Laboratory may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS 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 REGENTS 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.
 */
static const char rcsid[] =
    "@(#) $Header: /work/projects/tove/cvs/src/testing/vat/audio-af.cc,v 1.1 1997/12/08 17:22:49 parnanen Exp $ (LBL)";

#include <sys/file.h>

#include "audio.h"
#include <AF/AFlib.h>

struct afstate {
	AC ac;
	int mingain;
	int maxgain;
	int gain;
	int soft;
};

class AFAudio : public Audio {
    public:
	AFAudio();
	virtual int FrameReady();
	virtual u_char* Read();
	virtual	void Write(u_char *);
	virtual void SetRGain(int);
	virtual void SetPGain(int);
	virtual void OutputPort(int);
	virtual void Obtain();
	virtual void Release();
    protected:
	void SendReadRequest();
	int FindDefaultDevice(AFAudioConn*);
	void noserver();
	int slidergain(const afstate& af) const;
	void setgain(int level, afstate& af);
	void chksoftgain(afstate&, int softgain, int mask);

	AFAudioConn* raud;
	AFAudioConn* paud;

	u_char* readptr;
	u_char* readbufend;
	u_char* readbuf;
	u_char* replybuf;
	u_int afblksize;

	u_char* nextframe;
	u_char* firstframe;
	u_char* writebuf;
	int usingbuf;

	int lastmean_[4];

	u_int aftime;		/* time of last frame read */
	u_int wrttime;		/* time of last frame written */
	u_int minusoff;		/* max - diff. between server & writer */
	u_int plusoff;		/* avg + diff. between server & writer */
	int plusvar;		/* avg + variation between server & writer */
	u_int poff;		/* play offset relative to aftime */
	int pmiss;		/* number of consecutive missed frames */

	afstate raf;		/* record context */
	afstate saf;		/* speaker context */
	afstate haf;		/* headphone context */
	afstate *paf;		/* play context (points at saf/haf) */

	enum {
		PHONE_CODEC = 0,
		LOCAL_CODEC = 1,
		HIFI_BOTH = 2,
		HIFI_LEFT = 3,
		HIFI_RIGHT = 4
	};
};

static class AFAudioMatcher : public Matcher {
public:
	AFAudioMatcher() : Matcher("audio") {}
	TclObject* match(const char* id) {
		if (strcasecmp(id, "af") == 0)
			return (new AFAudio);
		return (0);
	}
} afaudio_matcher;

/*XXX*/
#include "/usr/src/local/AudioFile/AF/lib/AF/Alibint.h"
extern "C" void _AFlush(AFAudioConn* aud);
extern "C" void _ARead(AFAudioConn* aud, char* data, long size);
extern "C" void _AReadPad(AFAudioConn* aud, char* data, long size);
extern "C" AStatus _AReply(AFAudioConn* aud, aReply* rep,
			   int extra, ABool discard);
extern "C" AStatus _AReplyAsync(AFAudioConn* aud, aReply* rep,
				int extra, ABool discard);

#ifdef __osf__
extern "C" int flock(int, int);
#endif

void AFAudio::chksoftgain(afstate& af, int softgain, int mask)
{
	if (af.mingain == af.maxgain) {
		af.mingain = -30;
		af.maxgain = 30;
		af.soft = 1;
	} else {
		af.soft = 0;
		if (softgain != 0) {
			AFSetACAttributes attr;
			attr.rec_gain = softgain;
			AFChangeACAttributes(af.ac, mask, &attr);
		}
	}
}

AFAudio::AFAudio()
{
	Tcl& tcl = Tcl::instance();
	int device = atoi(tcl.attr("afDevice"));
	int blocks = atoi(tcl.attr("afBlocks"));
	int rgain = atoi(tcl.attr("afSoftInputGain"));
	int pgain = atoi(tcl.attr("afSoftOuputGain"));

	/*
	 * if the AUDIOFILE environment variable is set, use it as
	 * the server name.  Otherwise AFOpenAudioConn will try
	 * to use DISPLAY which is probably wrong so force ":0".
	 */
	const char* sname = getenv("AUDIOFILE");
	if (sname == 0)
		sname = ":0";
	raud = AFOpenAudioConn((char*)sname);
	if (raud == 0)
		noserver();
	paud = AFOpenAudioConn((char*)sname);
	if (paud == 0)
		noserver();

	if (device >= ANumberOfAudioDevices(raud)) {
		fprintf(stderr, "vat: AF: bad device %d", device);
		exit(1);
	}
	if (device < 0) {
		device = FindDefaultDevice(raud);
		if (device < 0) {
			fprintf(stderr, "vat: AF: cannot find ulaw device");
			exit(1);
		}
	}

	/* set up audio context, find sample size and sample rate */
	
	AFSetACAttributes attr;
	attr.type = MU255;
	raf.ac = AFCreateAC(raud, device, ACEncodingType, &attr);
	saf.ac = AFCreateAC(paud, device, ACEncodingType, &attr);
#ifdef notyet
	haf.ac = AFCreateAC(paud, HIFI_LEFT, 0, 0);
#endif
	paf = &saf;
	raf.gain = AFQueryInputGain(raf.ac, &raf.mingain, &raf.maxgain);
	chksoftgain(raf, rgain, ACRecordGain);
	saf.gain = AFQueryOutputGain(saf.ac, &saf.mingain, &saf.maxgain);
	chksoftgain(saf, pgain, ACPlayGain);
#ifdef notyet
	haf.gain = AFQueryOutputGain(haf.ac, &haf.mingain, &haf.maxgain);
	chksoftgain(haf);
#endif
	/*
	 * Set midscale initial values.  Since the server won't tell us
	 * what the real initial value is, and we want the slider position
	 * to reflect the startup value, we have no other choice.
	 */
	SetRGain(128);
	SetPGain(128);

	afblksize = blksize * blocks;
	replybuf = new u_char[afblksize + sizeof(aReply)];
	readbuf = replybuf + sizeof(aReply);
	readptr = readbufend = readbuf + afblksize;

	nextframe = 0;
	firstframe = 0;
	usingbuf = 0;
	if (afblksize != blksize)
		writebuf = new u_char[afblksize];
	else
		writebuf = 0;

	poff = 3 * afblksize;
	plusoff = poff << 5;
	plusvar = 0;

	lastmean_[0] = 0;
	lastmean_[1] = 0;
	lastmean_[2] = 0;
	lastmean_[3] = 0;

	/* open (or create) the lock file */
	openlock();
}

void AFAudio::noserver()
{
	if (getenv("AUDIOFILE") == 0) {
		fprintf(stderr,
			"vat: can't connect to AF server (AUDIOFILE not set)");
	} else
		fprintf(stderr, "can't connect to AF server");
	exit(1);
}

/* Find a suitable default device (the first device not connected to the phone)
 * Returns device number or -1 if no suitable device can be found.
 */
int
AFAudio::FindDefaultDevice(AFAudioConn* aud)
{
	char *s = (char *)getenv("AF_DEVICE");
	if (s != NULL)
		return (atoi(s));

	/* Find the first non-phone, 8kHz, mono device */
	int n = ANumberOfAudioDevices(aud);
	for (int i = 0; i < n; ++i) {
		AFDeviceDescriptor* a = AAudioDeviceDescriptor(aud, i);
		if (a->inputsFromPhone == 0 && a->outputsToPhone == 0 &&
		    a->playSampleFreq == 8000 && a->playNchannels == 1)
			return (i);
	}
	return (-1);
}

void AFAudio::Release()
{
	if (HaveAudio()) {
		/* gobble the result of the in-progress read */
		aRecordSamplesReply reply;
		_AReply(raud, (aReply*)&reply, 0, aFalse);
		if (reply.length * 4 == afblksize) {
			char dummy[512];
			_AReadPad(raud, dummy, afblksize);
		}
		unlock();
		unlink();
		fd = -1;
		notify();
	}
}

void AFAudio::Obtain()
{
	if (HaveAudio())
		abort();

	if (lock() == 0) {
		/* audio is ours - kick off first read */
		fd = raud->fd;
		aftime = AFGetTime(raf.ac);
		wrttime = 0;
		minusoff = 0;
		readptr = readbufend;
		SendReadRequest();
		Audio::Obtain();
	}
}

void AFAudio::Write(u_char *cp)
{
	if (HaveAudio()) {
		if (afblksize != blksize) {
			if (nextframe == 0) {
				firstframe = cp;
				nextframe = cp + blksize;
				usingbuf = 0;
				return;
			}
			if (nextframe != cp) {
				if (! usingbuf) {
					/*
					 * frames wrapped in ss buffer --
					 * copy to writebuf to keep things
					 * contiguous.
					 */
					int curlen = nextframe - firstframe;
					memcpy(writebuf, firstframe, curlen);
					firstframe = writebuf;
					nextframe = writebuf + curlen;
					usingbuf = 1;
				}
				memcpy(nextframe, cp, blksize);
			}
			nextframe += blksize;
			u_int len = nextframe - firstframe;
			if (len < afblksize)
				return;

			cp = firstframe;
			nextframe = 0;
		}
		u_int at = aftime + poff;
		if (at - wrttime > 3 * afblksize && wrttime) {
			/*
			 * start of talk after silence -- see if we
			 * should adjust offset.  If AF missed any
			 * frames in the last talkspurt, adjust the
			 * offset to one that wouldn't have missed any
			 * frames.  Otherwise if we're more than a
			 * frame time ahead of the recent average offset,
			 * drop the current offset by half the difference
			 * (or the max that wouldn't reorder AF playout,
			 * whichever is smaller).
			 */
			u_int noff = poff;
			if (minusoff) {
				noff = minusoff >> 2;
				minusoff = 0;
			} else {
				/*
				 * we went through the last talkspurt
				 * with no drops & an average backlog
				 * variation between us & AF of 'plusvar'.
				 * To avoid drops we need 2*afblksize +
				 * 2*plusvar of buffer between us & AF.
				 * If we have more than that, reduce it.
				 */
				u_int doff = (plusvar >> 2) + (2 * afblksize);
				if (doff < noff) {
					int adj = (noff - doff) >> 2;
					noff -= adj;
					if (int(noff) < int(wrttime - aftime))
						noff = wrttime - aftime;
				}
			}
			if (noff != poff) {
				poff = (noff + 3) & ~3;
				at = aftime + noff;
			}
		}
		wrttime = at;
		u_int now = AFPlaySamples(paf->ac, at, afblksize, cp);
		int dif = now - at;
		if (dif > 0) {
			u_int noff = now - aftime + 2 * afblksize;
			if (minusoff)
				minusoff += noff - (minusoff >> 1);
			else
				minusoff = noff << 1;
			if (++pmiss >= 3) {
				/*
				 * losing bad - adapt now rather than
				 * waiting for next talkspurt.
				 */
				poff = ((minusoff >> 1) + 3) & ~3;
				pmiss = 0;
				minusoff = 0;
			}
		} else {
			int delta = dif + (plusoff >> 5);
			plusoff -= delta;
			if (delta < 0)
				delta = -delta;
			plusvar += delta - (plusvar >> 3);
			pmiss = 0;
		}
	}
}

void AFAudio::SendReadRequest()
{
        register aRecordSamplesReq *req;

	if (HaveAudio()) {
#define aud raud
		GetReq(RecordSamples, req);
#undef aud
		req->ac = raf.ac->acontext;
		req->startTime = aftime;
		req->nbytes = afblksize;
		req->sampleType = raf.ac->attributes.type;
		req->nchannels = raf.ac->attributes.channels;
		req->mask = ABlockMask;
		if (raf.ac->attributes.endian == ABigEndian)
			req->mask |= ABigEndianMask;
		_AFlush(raud);
	}
}

int AFAudio::FrameReady()
{
	u_char* cp = readptr;
	if (cp >= readbufend) {
		aRecordSamplesReply* reply = (aRecordSamplesReply*)replybuf;
		if (_AReplyAsync(raud, (aReply*)reply, afblksize >> 2, aFalse) <= 0)
			/* no data available */
			return (0);

		/* queue the next read */
		SendReadRequest();
		/*
		 * If we get too far behind or get confused &
		 * think we're ahead, jump forward.  (This can
		 * easily happen if the process is suspended.)
		 */
		u_int srvtime = reply->currentTime;
		u_int dif = srvtime - aftime;
		if (dif > 16000)
			if (int(dif) < -1600 || int(dif) > 0) {
				aftime = srvtime;
			}
		aftime += afblksize;
		readptr = readbuf;
	}
	return (1);
}

extern const unsigned char lintomulaw[];
extern const short mulawtolin[];

u_char* AFAudio::Read()
{
	u_char* cp = readptr;
	readptr = cp + blksize;

	/*
	 * remove any dc bias from the input signal.
	 */
	register u_char* ip = cp;
	register u_char* ep = readptr;
	register int smean = lastmean_[iport];
	register const short* u2l = mulawtolin;
	register const u_char* l2u = lintomulaw;
	while (ip < ep) {
		register int mean, dif;
		register int s0 = u2l[ip[0]];
		register int s1 = u2l[ip[1]];
		register int s2 = u2l[ip[2]];
		register int s3 = u2l[ip[3]];

		mean = smean >> 13;
		dif = s0 - mean;
		smean += dif;
		ip[0] = l2u[dif & 0xffff];

		mean = smean >> 13;
		dif = s1 - mean;
		smean += dif;
		ip[1] = l2u[dif & 0xffff];

		mean = smean >> 13;
		dif = s2 - mean;
		smean += dif;
		ip[2] = l2u[dif & 0xffff];

		mean = smean >> 13;
		dif = s3 - mean;
		smean += dif;
		ip[3] = l2u[dif & 0xffff];

		ip += 4;
	}
	return (cp);
}

int AFAudio::slidergain(const afstate& af) const
{
	if (af.mingain == 0 && af.maxgain == 0)
		/* Not adjustable.  Just maintain mid-scale */
		return (128);

	float range = af.maxgain - af.mingain;
	return (int(255. * float(af.gain - af.mingain) / range));
}

void AFAudio::setgain(int level, afstate& af)
{
	float range = af.maxgain - af.mingain;
	af.gain = int(af.mingain + range * float(level) / 255.);
}

void AFAudio::SetRGain(int level)
{
	setgain(level, raf);
	if (raf.soft) {
		AFSetACAttributes attr;
		attr.rec_gain = raf.gain;
		AFChangeACAttributes(raf.ac, ACRecordGain, &attr);
	} else
		AFSetInputGain(raf.ac, raf.gain);
	rgain = slidergain(raf);
}

void AFAudio::SetPGain(int level)
{
	setgain(level, *paf);
	if (paf->soft) {
		AFSetACAttributes attr;
		attr.play_gain = paf->gain;
		AFChangeACAttributes(paf->ac, ACPlayGain, &attr);
	} else
		AFSetOutputGain(paf->ac, paf->gain);
	pgain = slidergain(*paf);
}

void AFAudio::OutputPort(int p)
{
	oport = p;
#ifdef notyet
	paf = oport? &haf : &saf;
#else
	paf = &saf;
#endif
}
