/* sparc/audio.c 
	vi:ts=3 sw=3:
 */

/* $Id: audio.c,v 4.15 1995/05/12 20:40:46 espie Exp espie $
 * $Log: audio.c,v $
 * Revision 4.15  1995/05/12  20:40:46  espie
 * Added frequency change.
 *
 * Revision 4.14  1995/05/12  13:52:39  espie
 * New synchronization.
 *
 * Revision 4.13  1995/03/03  14:22:55  espie
 * Fixed audio info bug.
 *
 * Revision 4.12  1995/02/26  23:07:14  espie
 * solaris.
 *
 * Revision 4.11  1995/02/25  15:44:15  espie
 * discard_buffer incorrect.
 *
 * Revision 4.10  1995/02/24  15:36:39  espie
 * Finally fixed speed/sync/late start.
 *
 * Revision 4.9  1995/02/24  13:48:39  espie
 * Fixed minor bug (interaction between pause and -sync).
 *
 * Revision 4.8  1995/02/24  13:43:52  espie
 * Added -sync feature.
 * In the absence of -sync, pause half a second at start to allow for
 * data to accumulate in buffer first.
 * Suppressed update freq on the fly since audioctl does not allow it.
 *
 * Revision 4.7  1995/02/23  22:41:45  espie
 * Added # of bits.
 *
 * Revision 4.6  1995/02/23  16:42:27  espie
 * Began conversion to `common' model.
 *
 * Revision 4.5  1995/02/23  13:52:30  espie
 * primary, secondary -> primary+secondary, primary-secondary
 * strike out 2 multiplications out of 4 !
 *
 * Revision 4.4  1995/02/21  17:57:55  espie
 * Internal problem: RCS not working.
 *
 * Revision 4.3  1995/02/01  16:43:47  espie
 * 23 bit samples.
 *
 * Revision 4.2  1994/01/13  09:19:08  espie
 * Forgotten something.
 *
 * Revision 4.0  1994/01/11  18:16:36  espie
 * New release.
 *
 * Revision 3.14  1993/12/04  16:12:50  espie
 * BOOL -> boolean.
 * Merged ss10/solaris.
 * Merged support for solaris together.
 * Fixed /16 bug.
 * Corrected mix problem.
 * restore stty.
 * Sync pseudo call.
 * Added update_frequency call, mostly unchecked
 * Added finetune.
 * Protracker commands.
 *
 * Revision 1.3  1992/11/17  15:38:00  espie
 * discard_buffer() call for snappier interface calls.
 * - Unified support for all sparcs.
 * - moved down to level 2 io.
 */

#include "defs.h"
#include "extern.h"
#ifdef SOLARIS
#include <sys/audioio.h>
#else
#include <sun/audioio.h>
#endif
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stropts.h>
#include <signal.h>
     
#define DEFAULT_SET_MIX
#define DEFAULT_BUFFERS
#define NEW_OUTPUT_SAMPLES_AWARE
#define NEW_FUNCS

#include "Arch/common.c"

/* things that aren't defined in all sun/audioio.h */

#ifndef AUDIO_ENCODING_LINEAR
#define AUDIO_ENCODING_LINEAR (3)
#endif
#ifndef AUDIO_GETDEV
#define AUDIO_GETDEV	_IOR(A, 4, int)
#endif
#ifndef AUDIO_DEV_UNKNOWN
#define AUDIO_DEV_UNKNOWN (0)
#endif
#ifndef AUDIO_DEV_AMD
#define AUDIO_DEV_AMD (1)
#endif

ID("$Id: audio.c,v 4.15 1995/05/12 20:40:46 espie Exp espie $")

LOCAL int audio;
LOCAL int written;
LOCAL int wait_samples;
LOCAL int tsync;

LOCAL int ADVANCE_TAGS=	-1;

LOCAL struct audio_info ainfo, ainfo2;

/* set_synchronize(): pause the audio device at start of play,
 * in order to accumulate enough samples before playing
 */
LOCAL void set_synchronize()
	{
	written = ainfo.play.eof;
	if (!ainfo2.play.pause)
		{
		ainfo2.play.pause = TRUE;
		ioctl(audio, AUDIO_SETINFO, &ainfo2);
			/* number of samples to accumulate */
		wait_samples = ainfo.play.sample_rate / 2;
		}
	else
		wait_samples = 0;
	}

LOCAL int possible[] = { 8000, 9600, 11025, 16000, 18900, 22050, 32000,
	37800, 44100, 48000, 0};

int open_audio(f, s)
int f;
int s;
   {
	int type;

	int basic;
#ifdef SOLARIS
	audio_device_t dev;

	audio = open("/dev/audio", O_WRONLY);
#else
   audio = open("/dev/audio", O_WRONLY|O_NDELAY);
#endif


	basic = 0;
   if (audio == -1)
		end_all("Error: could not open audio");

		/* round frequency to acceptable value */
	f = best_frequency(f, possible, 22050);

		/* check whether we know about AUDIO_ENCODING_LINEAR */
	AUDIO_INITINFO(&ainfo);
	AUDIO_INITINFO(&ainfo2);
#ifdef SOLARIS
	ioctl(audio, AUDIO_GETDEV, &dev);
	if (strcmp(dev.name, "SUNW,dbri") != 0)
#else
	if (ioctl(audio, AUDIO_GETDEV, &type) ||
	type == AUDIO_DEV_UNKNOWN || type == AUDIO_DEV_AMD || basic)
#endif
		{
			/* not a new ss5/10/20 -> revert to base quality audio */
		stereo = 0;
		dsize = 1;
		f = 8000;
		ainfo.play.encoding = AUDIO_ENCODING_ULAW;
		ainfo.play.channels = 1;
		}
	else
		{
			/* tentative set up */
		stereo = s;
		ainfo.play.precision = 16;
		dsize = 2;
		if (stereo)
			ainfo.play.channels = 2;
		else
			ainfo.play.channels = 1;
			/* try it */
		ainfo.play.encoding = AUDIO_ENCODING_LINEAR;
		}
	ainfo.play.sample_rate = f;
	if (ioctl(audio, AUDIO_SETINFO, &ainfo) != 0)
		/* didn't work: fatal problem */
		end_all("Error: AUDIO_SETINFO");
	idx = 0;
	samples_max = ainfo.play.channels*ainfo.play.sample_rate;
	buffer = (char *)malloc(dsize*samples_max);
	buffer16 = (short *)buffer;
	if (!buffer)
		end_all("Error: could not allocate buffer");

	set_synchronize();
	return f;
	}

void set_synchro(s)
int s;
	{
	tsync = s;
	}

LOCAL int newfreq;

int update_frequency()
	{
	if (newfreq)
		{
		newfreq = 0;
		samples_max = ainfo.play.channels * ainfo.play.sample_rate;
		buffer = realloc(buffer, dsize * samples_max);
		buffer16 = (short *)buffer;
		return ainfo.play.sample_rate;
		}
	else
		return 0;

	/* the current implementation of the audio device does not allow
	 * output frequency change through /dev/audioctl
	 *	-> this code is not needed 
	int oldfreq;

	oldfreq = ainfo.play.sample_rate;
	if (ioctl(audio, AUDIO_GETINFO, &ainfo) == 0)
		{
		if (oldfreq != ainfo.play.sample_rate)
			{
			samples_max = ainfo.play.channels * ainfo.play.sample_rate;
			buffer = realloc(buffer, dsize * samples_max);
			buffer16 = (short *)buffer;
			return ainfo.play.sample_rate;
			}
		}
	return 0;
	 */
	}

void audio_ui(c)
int c;
	{
	int i;

	switch(c)
		{
	case '+':
		if (audio & ainfo.play.encoding == AUDIO_ENCODING_LINEAR)
			{
			for (i = 0; ; i++)
				if (possible[i] == ainfo.play.sample_rate)
					break;
			if (possible[i+1])
				{
				ainfo.play.sample_rate = possible[i+1];
				if (ioctl(audio, AUDIO_SETINFO, &ainfo) == 0)
					newfreq = 1;
				}
			}
		break;
	case '-':
		if (audio & ainfo.play.encoding == AUDIO_ENCODING_LINEAR)
			{
			for (i = 0; ; i++)
				if (possible[i] == ainfo.play.sample_rate)
					break;
			if (i)
				{
				ainfo.play.sample_rate = possible[i-1];
				if (ioctl(audio, AUDIO_SETINFO, &ainfo) == 0)
					newfreq = 1;
				}
			}
		break;
	default:
		}
	}


void output_samples(left, right, n)
int left, right, n;
   {

	switch(ainfo.play.encoding)
		{
	case AUDIO_ENCODING_LINEAR:
		add_samples16(left, right, n);
		break;
	case AUDIO_ENCODING_ULAW:
		buffer[idx++] = linear2ulaw((left + right) >> (n-14));
		break;
	default:
		end_all("Error:Unknown audio encoding");
		}
	}


/* synchronize stuff with audio output */

struct tagged
	{
	struct tagged *next;			/* simply linked list */
	void (*f) P((GENERIC *));	/* function to call */
	GENERIC p;						/* and parameter */
	int when;						/* number of chunks to let go before calling */
	} 
	*start,	/* what still to output */
	*end;	/* where to add new tags */



/* flush_tags: use tags that have gone by recently */
LOCAL void flush_tags()
	{
	ioctl(audio, AUDIO_GETINFO, &ainfo);
	if (start)
		{
		while (start && start->when <= ainfo.play.eof + ADVANCE_TAGS)
			{
			struct tagged *tofree;

			(*start->f)(start->p);
			tofree = start;
			start = start->next;
			free(tofree);
			}
		}
	}

/* remove unused tags at end */
LOCAL void remove_pending_tags()
	{
	while (start)
		{
		struct tagged *tofree;

		tofree = start;
		start = start->next;
		free(tofree);
		}
	}

void sync_audio(function, parameter)
void (*function) P((void *));
GENERIC parameter;
	{
	struct tagged *t;

	t = malloc(sizeof(struct tagged));
	if (!t)
		{
		(*function)(parameter);
		return;
		}
		/* build new tag */
	t->next = 0;
	t->f = function;
	t->p = parameter;
	t->when = written;

		/* add it to list */
	if (start) 
		end->next = t;
	else
		start = t;
	end = t;

		/* set up for next tag */
	write(audio, buffer, 0);
	written++;

	if (!tsync)
		flush_tags();
	}

void flush_buffer()
   {
	int actual;
	int number;

	actual = write(audio, buffer, dsize * idx);
	if (actual == -1)
		notice("Write to audio failed");
	else if (actual != dsize * idx)
		notice("Short write to audio");
	if (wait_samples)		
		{		/* currently paused ? */
		wait_samples -= actual;
		if (wait_samples <= 0)
			{	/* right number of samples gone by ? */
			wait_samples = 0;
			ainfo2.play.pause = FALSE;
			ioctl(audio, AUDIO_SETINFO, &ainfo2);
			}
		}
	if (tsync)
		flush_tags();
	idx = 0;
   }

void discard_buffer()
	{
	remove_pending_tags();
	ioctl(audio, I_FLUSH, FLUSHW);
	if (wait_samples)
		{
		ainfo2.play.pause = FALSE;
		ioctl(audio, AUDIO_SETINFO, &ainfo2);
		wait_samples = 0;
		}
	set_synchronize();
	}

void close_audio()
   {
	remove_pending_tags();
	free(buffer);
   close(audio);
   }

int output_resolution()
	{
	return 16;
	}
