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

/* $Id: audio.c,v 4.28 1995/10/13 18:05:51 espie Exp espie $
 * $Log: audio.c,v $
 * Revision 4.28  1995/10/13 18:05:51  espie
 * Support for SoundBlaster Pro :-)
 *
 * Revision 4.27  1995/09/17 23:26:31  espie
 * *** empty log message ***
 *
 * Revision 4.26  1995/09/07 14:31:15  espie
 * no longer opens audio if NO_OUTPUT... Checks that audio is open
 * before any ioctl.
 *
 * Revision 4.25  1995/09/05 19:20:00  espie
 * Cleaner (much).
 *
 * Revision 4.24  1995/09/03 15:50:08  espie
 * *** empty log message ***
 *
 * Revision 4.23  1995/09/03 14:20:17  espie
 * *** empty log message ***
 *
 * Revision 4.22  1995/09/03 13:37:46  espie
 * Corrected left, right data sizes.
 *
 * Revision 4.21  1995/09/02 22:17:24  espie
 * Ludicrous audio opened test fixed.
 * sync_audio updated.
 *
 * Revision 4.20  1995/08/31 13:29:34  espie
 * Added no output sync option.
 *
 * Revision 4.19  1995/07/02 16:10:04  espie
 * Keep track of time....
 *
 * Revision 4.18  1995/06/23  09:33:25  espie
 * Boarf.
 *
 * Revision 4.17  1995/05/30  13:07:35  espie
 * Set up new automaton with differed output frequency change.
 *
 * Revision 4.16  1995/05/23  13:23:34  espie
 * *** empty log message ***
 *
 * 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"
#undef int
#include "extern.h"
#include "prefs.h"

XT int ioctl P((int fd, int request, GENERIC arg));
XT int write P((int fd, char *buf, int nbyte));
XT int close P((int fd));

#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>
     
struct options_set *port_options = 0;


#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
#ifndef AUDIO_DEV_SPEAKERBOX
#define AUDIO_DEV_SPEAKERBOX (2)
#endif
#ifndef AUDIO_DEV_CODEC 
#define AUDIO_DEV_CODEC (3)
#endif
/* I don't know what's the name of that one */
#define AUDIO_DEV_SPARC5	(5)

ID("$Id: audio.c,v 4.28 1995/10/13 18:05:51 espie Exp espie $")

LOCAL int audio = -1;
LOCAL unsigned long written;
LOCAL unsigned long wait_samples;

LOCAL int ADVANCE_TAGS=	-1;


LOCAL unsigned long new_freq;
LOCAL unsigned long current_freq;


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 unsigned long possible[] = 
	{ 8000, 9600, 11025, 16000, 18900, 22050, 32000, 37800, 44100, 48000, 0};

/* Possible sparcs audio devices */
#define SPARC_AMD		0
#define SPARC_DBRI	1
#define SPARC_CS4231	2
#define SOLARIS_SBPRO 3
#define SPARC_UNKNOWN	(-1)

unsigned long open_audio(f, s)
unsigned long f;
int s;
   {
	int ret;
	int type;
	int open_flags;
	char display[80];

#ifdef SOLARIS
	audio_device_t dev;
	open_flags = O_WRONLY;

#else
	static int dev;

	open_flags = O_WRONLY | O_NDELAY;
#endif

	audio = open("/dev/audio", open_flags);

   if (audio == -1)
		end_all("Error: could not open audio");
		/* check whether we know about AUDIO_ENCODING_LINEAR */
	AUDIO_INITINFO(&ainfo);
	AUDIO_INITINFO(&ainfo2);
	ret = ioctl(audio, AUDIO_GETDEV, &dev);
	if (ret)
		type = SPARC_UNKNOWN;
	else
		{
#ifdef SOLARIS
		if (strcmp(dev.name, "SUNW,dbri") == 0)
			type = SPARC_DBRI;
		else if (strcmp(dev.name, "SUNW,CS4231") == 0)
			type = SPARC_CS4231;
		else if (strcmp(dev.name, "SUNW,am79c30") == 0)
			type = SPARC_AMD;
		else if (strcmp(dev.name, "SUNW,sbpro") == 0)
			type = SOLARIS_SBPRO;
		else
			type = SPARC_UNKNOWN;
#else
		switch(dev)
			{
		case AUDIO_DEV_AMD:
			type = SPARC_AMD;
			break;
		case AUDIO_DEV_SPEAKERBOX:
		case AUDIO_DEV_CODEC:
			type = SPARC_DBRI;
			break;
		case AUDIO_DEV_SPARC5:
			type = SPARC_CS4231;
			break;
		case AUDIO_DEV_UNKNOWN:
		default:
			type = SPARC_UNKNOWN;
			}
#endif
		}

		/* round frequency to acceptable value */
	f = best_frequency(f, possible, type == SPARC_UNKNOWN ? 8000 : 22050);

	switch(type)
		{
	case SPARC_AMD:
			/* not a new ss5/10/20 -> revert to base quality audio */
		f = 8000;
		ainfo.play.encoding = AUDIO_ENCODING_ULAW;
		break;
	case SPARC_DBRI:
	case SPARC_CS4231:
			/* tentative set up */
		ainfo.play.encoding = AUDIO_ENCODING_LINEAR;
		dsize = 2;
		break;
	case SOLARIS_SBPRO:
		ainfo.play.encoding = AUDIO_ENCODING_LINEAR;
		dsize = 1;
		break;
	case SPARC_UNKNOWN:
			/* trust the user (somewhat) */
		if (f == 8000)
			{
			ainfo.play.encoding = AUDIO_ENCODING_ULAW;
#ifdef SOLARIS
			sprintf(display, 
				"Audio unknown (%s) 8000Hz Ulaw mono (-freq to override)",
					dev.name);
#else
			sprintf(display, 
				"Audio unknown (%d) 8000Hz Ulaw mono (-freq to override)",
					dev);
#endif
			}
		else
			{
			ainfo.play.encoding = AUDIO_ENCODING_LINEAR;
#ifdef SOLARIS
			sprintf(display,
				"Audio unknown (%s), trusting your choice", dev.name);
#else
			sprintf(display,
				"Audio unknown (%d), trusting your choice", dev);
#endif
			}
		notice(display);
		break;
		}
	ainfo.play.sample_rate = f;
	if (ainfo.play.encoding == AUDIO_ENCODING_LINEAR)
		{
		stereo = s;
		ainfo.play.precision = 8 * dsize;
		}
	else
		{
		dsize = 1;
		stereo = 0;
		}
	if (stereo)
		ainfo.play.channels = 2;
	else
		ainfo.play.channels = 1;

	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();
	current_freq = f;
	return f;
	}

unsigned long update_frequency()
	{
	unsigned long f = new_freq;

	if (new_freq)
		{
		samples_max = ainfo.play.channels * new_freq;
		buffer = realloc(buffer, dsize * samples_max);
		buffer16 = (short *)buffer;
		current_freq = new_freq;
		new_freq = 0;
		}

	return f;
	}

LOCAL void set_freq(f)
unsigned long f;
	{
	ainfo.play.sample_rate = f;
	if (audio != -1)
		{
		ioctl(audio, AUDIO_SETINFO, &ainfo);
		printf("%lu\n", f);
		}
	}

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

	switch(c)
		{
	case '+':
		if (audio != -1 && ainfo.play.encoding == AUDIO_ENCODING_LINEAR)
			{
			for (i = 0; ; i++)
				if (possible[i] == current_freq)
					break;
			if (possible[i+1])
				{
				sync_audio(set_freq, set_freq, (GENERIC)(possible[i+1]));
				new_freq = possible[i+1];
				}
			}
		break;
	case '-':
		if (audio != -1 && ainfo.play.encoding == AUDIO_ENCODING_LINEAR)
			{
			for (i = 0; ; i++)
				if (possible[i] == current_freq)
					break;
			if (i)
				{
				sync_audio(set_freq, set_freq, (GENERIC)(possible[i-1]));
				new_freq = possible[i-1];
				}
			}
		break;
	default:
		break;
		}
	}


void output_samples(left, right, n)
long left, right;
int n;
   {
	switch(ainfo.play.encoding)
		{
	case AUDIO_ENCODING_LINEAR:
		if (dsize == 2)
			add_samples16(left, right, n);
		else
			add_samples8(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 */

LOCAL struct tagged
	{
	struct tagged *next;			/* simply linked list */
	void (*f) P((GENERIC p));	/* function to call */
	void (*f2) P((GENERIC p));	/* function to call  for flush */
	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()
	{
	if (audio != -1)
		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;

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

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

	if (audio != -1)
		{
		t = malloc(sizeof(struct tagged));
		if (!t)
			{
			(*function)(parameter);
			return;
			}
			/* build new tag */
		t->next = 0;
		t->f = function;
		t->f2 = f2;
		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++;
		}
	else
		(*function)(parameter);
	}

void flush_buffer()
   {
	int actual;

	if (idx > 0)
		{
		actual = write(audio, buffer, dsize * idx);
		if (actual == -1)
			notice("Write to audio failed");
		else if (actual != dsize * idx)
			notice("Short write to audio");
		idx = 0;
		}
	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;
			if (audio != -1)
				ioctl(audio, AUDIO_SETINFO, &ainfo2);
			}
		}
	flush_tags();
   }

void discard_buffer()
	{
	remove_pending_tags();
	if (audio != -1)
		{
		ioctl(audio, I_FLUSH, (GENERIC)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);
	if (audio != -1)
		close(audio);
	audio = -1;
   }

int output_resolution()
	{
	return 16;
	}
