/*
 * Copyright (c) 1991-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: audio-sgi.cc,v 1.14 96/04/26 04:08:04 van Exp $ (LBL)";

#include "config.h"
#define _BSD_COMPAT 1
#include "Tcl.h"

/*
 * We have AF/audio.h, ./audio.h and /usr/include/audio.h.  Crimony.
 */
#include </usr/include/audio.h>
#include "audio.h"

class SGIAudio : public Audio {
    public:
	SGIAudio();
	virtual int FrameReady();
	virtual u_char* Read();
	virtual	void Write(u_char *);
	virtual void SetRGain(int);
	virtual void SetPGain(int);
	virtual void InputPort(int);
	virtual void Obtain();
	virtual void Release();
    protected:
	int GainClip(int);

	u_int lastsamp_;
	u_int lastout_;
	u_char* buf_;
	ALport in;
	ALport out;
	ALconfig conf;
};


extern const u_char lintomulawX[];
extern const short mulawtolin[];


#define AUDIO_MIN_GAIN 0
#define AUDIO_MAX_GAIN 255

static class SGIAudioMatcher : public Matcher {
public:
	SGIAudioMatcher() : Matcher("audio") {}
	TclObject* match(const char* id) {
		if (strcasecmp(id, "sgi") == 0)
			return (new SGIAudio);
		return (0);
	}
} sgiaudio_matcher;

SGIAudio::SGIAudio()
{
	/* open (or create) the lock file */
	openlock();
	iports = 2;
	oports = 1;
	conf = ALnewconfig();
	ALsetwidth(conf, AL_SAMPLE_16);
	ALsetqueuesize(conf, 8000);
	ALsetchannels(conf, AL_MONO);
	fd = -1;
	lastout_ = 0;
	lastsamp_ = 0;
	buf_ = new u_char[blksize];
}

void SGIAudio::Release()
{
	if (HaveAudio()) {
		unlock();
		unlink();
		fd = -1;
		ALcloseport(in);
		ALcloseport(out);
		notify();
	}
}

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

	if (lock() == 0) {
		out = ALopenport("vatOut", "w", conf);
		if (out == NULL) {
			fprintf(stderr,
				"vat: couldn't open AL output port.\n");
			return;
		}
		in = ALopenport("vatIn", "r", conf);
		if (in == NULL) {
			fprintf(stderr,
				"vat: couldn't open AL input port.\n");
			return;
		}
		ALsetfillpoint(in, 160);
		long pvbuf[6];
		pvbuf[0] = AL_INPUT_RATE;
		pvbuf[1] = 8000;
		pvbuf[2] = AL_OUTPUT_RATE;
		pvbuf[3] = 8000;
		pvbuf[4] = AL_INPUT_SOURCE;
		pvbuf[5] = iport? AL_INPUT_LINE : AL_INPUT_MIC;
		ALsetparams(AL_DEFAULT_DEVICE, pvbuf, 6);
		fd = ALgetfd(in);
		SetRGain(rgain);
		SetPGain(pgain);
		Audio::Obtain();
	}
}

void SGIAudio::Write(u_char *cp)
{
	register int len = blksize;
	u_int samps[MAXAUDIOSIZE/2];
	register u_int* sp = samps;
	register u_int* ep = sp + len / 2;
	register const u_short* u2l = (u_short*)mulawtolin;
	register u_int* ip = (u_int*)cp;
	for ( ; sp < ep; sp += 4) {
		register u_int s = *ip++;
		sp[0] = (u2l[(s >> 24) & 0xff] << 16) | u2l[(s >> 16) & 0xff];
		sp[1] = (u2l[(s >> 8) & 0xff] << 16) | u2l[s & 0xff];
		s = *ip++;
		sp[2] = (u2l[(s >> 24) & 0xff] << 16) | u2l[(s >> 16) & 0xff];
		sp[3] = (u2l[(s >> 8) & 0xff] << 16) | u2l[s & 0xff];
	}
	ALwritesamps(out, samps, len);
}

int SGIAudio::FrameReady()
{
	return (ALgetfilled(in) >= blksize);
}

u_char* SGIAudio::Read()
{
	register long len = blksize;
	u_char* cp = buf_;

	/*
	 * for some reason, SGI didn't bother to filter out the
	 * mike 'phantom power' DC signal (god forbid they should
	 * use the Indigo DSP for anything or invest a dime in
	 * transformer coupling the mike) so we end up with a
	 * large DC offset that screws up the lin-to-mu conversion
	 * and the speakerphone power calculations.  So all the
	 * extra junk in the following loop is a low pass filter
	 * to estimate the DC bias & remove it.
	 *
	 * The multiply by 2 on the samples is because SGI maps
	 * stereo to mono by doing (L+R)/2 rather than clip(L+R).
	 * Since the mikes they ship are mono, this effectively
	 * cuts the mike gain by a factor of two.  We can't
	 * restore the 1 bit of dynamic range they throw away
	 * but we jack the gain back up where is should be.
	 */
	short samps[MAXAUDIOSIZE];
	register short* sp = samps;
	ALreadsamps(in, sp, len);
	register short* ep = sp + len;
	register const u_char* l2u = lintomulawX;
	register u_int* ip = (u_int*)cp;
	register int smean = lastsamp_;
	for ( ; sp < ep; sp += 4) {
		register int mean, dif;
		register u_int res;
		register int s0 = sp[0] << 1;
		register int s1 = sp[1] << 1;
		register int s2 = sp[2] << 1;
		register int s3 = sp[3] << 1;

		mean = smean >> 13;
		dif = s0 - mean;
		smean += dif;
		res = l2u[dif & 0x1ffff] << 24;

		mean = smean >> 13;
		dif = s1 - mean;
		smean += dif;
		res |= l2u[dif & 0x1ffff] << 16;

		mean = smean >> 13;
		dif = s2 - mean;
		smean += dif;
		res |= l2u[dif & 0x1ffff] << 8;

		mean = smean >> 13;
		dif = s3 - mean;
		smean += dif;
		res |= l2u[dif & 0x1ffff];

		*ip++ = res;
	}
	lastsamp_ = smean;
	return (cp);
}

int SGIAudio::GainClip(int level)
{
        if (level < AUDIO_MIN_GAIN)
                return AUDIO_MIN_GAIN;
        else if (level > AUDIO_MAX_GAIN)
                return AUDIO_MAX_GAIN;
        else
                return level;
}

void SGIAudio::SetRGain(int level)
{
	rgain = GainClip(level);
	if (fd >= 0) {
		static long atten[] = { 34, 37, 40, 45, 50, 55, 63 };
		long pvbuf[4];
		int index = ((255 - rgain) * 39) / 255;
		pvbuf[3] = pvbuf[1] = (index > 32) ?
					(atten[index-33] << 2) : (index << 2);
		pvbuf[0] = AL_LEFT_INPUT_ATTEN;
		pvbuf[2] = AL_RIGHT_INPUT_ATTEN;
		ALsetparams(AL_DEFAULT_DEVICE, pvbuf, 4);
	}
}

void SGIAudio::SetPGain(int level)
{
	pgain = GainClip(level);
	if (fd >= 0) {
		long pvbuf[4];
#ifdef SGI_COMPAT
		float gain = pgain <= 0?
			    0.
			    : pow(10.0, float(pgain)*(2.406540183/255.)) + 0.5;
		pvbuf[3] = pvbuf[1] = long(gain);
#else
		pvbuf[3] = pvbuf[1] = long(pgain);
#endif
		pvbuf[0] = AL_LEFT_SPEAKER_GAIN;
		pvbuf[2] = AL_RIGHT_SPEAKER_GAIN;
		ALsetparams(AL_DEFAULT_DEVICE, pvbuf, 4);
	}
}

void SGIAudio::InputPort(int p)
{
	iport = p;
	if (fd >= 0) {
		long pvbuf[2];
		pvbuf[0] = AL_INPUT_SOURCE;
		pvbuf[1] = iport? AL_INPUT_LINE : AL_INPUT_MIC;
		ALsetparams(AL_DEFAULT_DEVICE, pvbuf, 2);
	}
}
