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

/* $Id: audio.c,v 1.15 1995/10/21 00:41:35 espie Exp espie $
 * $Log: audio.c,v $
 * Revision 1.15  1995/10/21 00:41:35  espie
 * *** empty log message ***
 *
 * Revision 1.14  1995/09/17 23:26:35  espie
 * *** empty log message ***
 *
 * Revision 1.13  1995/09/03 14:20:19  espie
 * *** empty log message ***
 *
 * Revision 1.12  1995/09/03 13:38:03  espie
 * Corrected data size.
 *
 * Revision 1.11  1995/09/02 22:17:48  espie
 * sync_audio updated.
 *
 * Revision 1.10  1995/08/27 18:44:26  espie
 * *** empty log message ***
 *
 * Revision 1.9  1995/03/17  00:31:13  espie
 * Added linear 8 to NeXt audio.
 *
 * Revision 1.8  1995/02/27  14:25:37  espie
 * Rolf Grossmann patch.
 *
 * Revision 1.7  1995/02/23  22:41:45  espie
 * Added # of bits.
 *
 * Revision 1.6  1995/02/23  17:03:14  espie
 * Continuing changes for a standard file.
 *
 * Revision 1.5  1995/02/23  16:42:27  espie
 * Began conversion to `common' model.
 *
 * Revision 1.4  1995/02/23  13:52:30  espie
 * primary, secondary -> primary+secondary, primary-secondary
 * strike out 2 multiplications out of 4 !
 *
 * Revision 1.3  1995/02/21  17:57:55  espie
 * Internal problem: RCS not working.
 *
 * Revision 1.2  1995/02/08  13:16:22  espie
 * *** empty log message ***
 *
 * Revision 1.1  1995/02/01  16:43:47  espie
 * Initial revision
 *
 */

#include "defs.h"
#include <sound/sound.h>
#include "extern.h"
#include "prefs.h"
#include "parse_options.h"

ID("$Id: audio.c,v 1.15 1995/10/21 00:41:35 espie Exp espie $")

#define DEFAULT_BUFFERS
#define DEFAULT_SET_MIX
#define NEW_OUTPUT_SAMPLES_AWARE
#define NEW_FUNCS

#include "Arch/common.c"


#define SND_PLAY_PRIO   5
#define USE_SNDQ

LOCAL struct option next_opts[] = {
	{ "play-ahead", 'n', 5 }
};
LOCAL VALUE next_args[sizeof(next_opts)/sizeof(struct option)];
LOCAL struct option_set next_options = {
	next_opts, sizeof(next_opts)/sizeof(struct option), next_args
};
struct option_set *port_options = &next_options;

LOCAL SNDSoundStruct ainfo;
LOCAL struct MySND {
    SNDSoundStruct snd;
    char data;
} *snd;
#ifdef USE_SNDQ
LOCAL struct MySNDq 
{
    struct MySNDq *next;
    struct MySND *snd;
} *freeq, *usedq;
#endif
LOCAL int tag, new_tag;
LOCAL unsigned int play_ahead;

LOCAL int dsize;
#define DATASIZE 40960 /* 176400 */

LOCAL void actually_flush_buffer(void);

#ifdef USE_SNDQ
/* sound structure queue handling */
void
init_q(int format, int rate, int channels)
{
    int i, err;
    struct MySNDq *new;

    freeq = NULL;
    usedq = NULL;
    for(i = 0; i < play_ahead; i++) {
		new = malloc(sizeof(*new));
		new->next = freeq;
		if((err=SNDAlloc((SNDSoundStruct **)&new->snd, DATASIZE, format,
						 rate, channels, 4))) {
			fprintf(stderr,"Sound allocation error: %s.\n",
					SNDSoundError(err));
			end_all("SNDAlloc failed");
		}
		freeq = new;
	}
}

struct MySND *
getMySND(void) 
{
    struct MySNDq *this;
    
    if ((this = freeq) == NULL) {
	SNDWait(new_tag-play_ahead);
#ifdef DEBUG
	if ((this = freeq) == NULL) {
	    end_all("panic: freelist still empty.");
	}
#else
	this = freeq;
#endif
    }
    freeq = this->next;
    this->next = usedq;
    usedq = this;
    return this->snd;
}

int
addMySND(SNDSoundStruct *s, int tag, int err)
{
    struct MySNDq *this;

#ifdef DEBUG
    if ((this = usedq) == NULL) {
	end_all("panic: no sound used.");
    }
#else
    this = usedq;
#endif
    usedq = this->next;
    this->next = freeq;
    this->snd = (struct MySND *)s;
    freeq = this;
    return 0;
}
#endif /* USE_SNDQ */

/* tracker audio handling */
unsigned long
open_audio(unsigned long f, int s)
	{
	static unsigned long possible[] = { 8012, 22050, 44100, 0};

	f = best_frequency(f, possible, 22050);

   stereo = s;
   ainfo.samplingRate = f;
   if (stereo)
		ainfo.channelCount = 2;
   else
		ainfo.channelCount = 1;
   if(f!=8012)
		{
		ainfo.dataFormat = SND_FORMAT_LINEAR_16;
		dsize = 2;
		}
   else
		{
		ainfo.dataFormat = SND_FORMAT_MULAW_8;
		dsize = 1;
		if (stereo)
			notice("Warning: Your hardware may not be fast enough \
for mulaw-stereo.");
		}
    
	tag = new_tag = 1;
	play_ahead = next_args[0].scalar;
	if (play_ahead < 1) play_ahead = 1;
#ifdef USE_SNDQ
	init_q(ainfo.dataFormat, ainfo.samplingRate, ainfo.channelCount);
	snd = getMySND();
#else
	if (SNDAlloc((SNDSoundStruct **)&snd, DATASIZE, ainfo.dataFormat,
		     ainfo.samplingRate, ainfo.channelCount, 4))
	    end_all("Sound allocation error.");
#endif
	buffer = &snd->data;
	buffer16 = (short *)&snd->data;
	idx = 0;
	samples_max = DATASIZE/dsize;
	return f;
	}

unsigned long
update_frequency(void)
	{ /* frequency can't change */
   return 0;
	}

void
audio_ui(char c)
	{
	    /* no NeXT-specific interface. */
	}

void
output_samples(long left, long right, int n)
	{
	switch(ainfo.dataFormat)
		{
	case SND_FORMAT_LINEAR_16:
		add_samples16(left, right, n);
		break;
	case SND_FORMAT_LINEAR_8:
		add_samples8(left, right, n);
		break;
	case SND_FORMAT_MULAW_8:
		if (stereo)
			{				/* stuff to fix (size of data) right there */
			if (pms[n] == pps[n])
				{
				buffer[idx++] = SNDMulaw(left/65536);
				buffer[idx++] = SNDMulaw(right/65536);
				}
			else
				{
				int s1, s2;

				s1 = (left+right)*pps[n];
				s2 = (left-right)*pms[n];

				buffer[idx++] = SNDMulaw((s1+s2)/65536);
				buffer[idx++] = SNDMulaw((s1-s2)/65536);
				}
			}
		else
			buffer[idx++] = SNDMulaw((left + right)/256);
		break;
	default:
		end_all("Error: unknown output format");
		}
	if (idx >= samples_max)
		actually_flush_buffer();
	}


/* 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 when flushing */
    GENERIC p;			/* and parameter */
    int when;			/* tag the should be played 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(int totag)
{
    if (start) {
	struct tagged *tofree;
	    
	while (start && start->when <= totag) {
	    (*start->f)(start->p);
	    tofree = start;
	    start = start->next;
	    free(tofree);
	}
    }
}

/* remove unused tags at end */
LOCAL void
remove_pending_tags(void)
{
    struct tagged *tofree;
    
    while (start)
    {
	 (*start->f2)(start->p);
	tofree = start;
	start = start->next;
	free(tofree);
    }
}

void
sync_audio(void (*function)(GENERIC p), void (*f2)(GENERIC p), GENERIC param)
{
    struct tagged *t;
    
    t = 0;
    if (get_pref_scalar(PREF_OUTPUT))
       t = malloc(sizeof(struct tagged));
    if (!t)
    {
	(*function)(param);
	return;
    }
    /* build new tag */
    t->next = 0;
    t->f = function;
    t->f2 = f2;
    t->p = param;
    t->when = new_tag;

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

void 
flush_buffer(void)
	{
	/* does not really flush the buffer, because we need to buffer more data
	 * to ensure continous play. On the other hand, the system assumes, that
	 * the first tag is flushed here, when no sound was played.
	 */
	}

LOCAL int
record_tag(SNDSoundStruct *s, int this_tag, int err)
{
	 tag = this_tag;
    return 0;
}

LOCAL void
actually_flush_buffer(void)
	{
#ifndef USE_SNDQ
	    if (tag > play_ahead)
			SNDWait(tag-play_ahead);
#endif
	flush_tags(tag);
   if (ainfo.dataFormat == SND_FORMAT_LINEAR_16)
       SNDSwapSoundToHost(&snd->data, &snd->data, samples_max, 1,
			  SND_FORMAT_LINEAR_16);
#ifdef USE_SNDQ
   if (SNDStartPlaying(&snd->snd, new_tag++, SND_PLAY_PRIO, 0,
							  record_tag, addMySND))
#else
   if (SNDStartPlaying(&snd->snd, new_tag++, SND_PLAY_PRIO, 0,
							  record_tag, (SNDNotificationFun)SNDFree))
#endif
		notice("Sound playing error.");	/* ### end_all? */

   idx = 0;
#ifdef USE_SNDQ
	snd = getMySND();
#else
	if (SNDAlloc((SNDSoundStruct **)&snd, DATASIZE, ainfo.dataFormat,
				 ainfo.samplingRate, ainfo.channelCount, 4))
		end_all("Sound allocation error.");
#endif
   buffer = &snd->data;
   buffer16 = (short *)&snd->data;
	}

void
discard_buffer(void)
	{
   int i;

   remove_pending_tags();
   for(i=1; i<=play_ahead; i++)
		SNDStop(new_tag-i);
	}

void
close_audio(void)
	{
		 /* I hope this is only called before tracker exits */
   if (idx > 0) {
       flush_tags(new_tag);
       snd->snd.dataSize = idx * dsize;
       if (ainfo.dataFormat == SND_FORMAT_LINEAR_16)
	   SNDSwapSoundToHost(&snd->data, &snd->data, idx, 1,
			      SND_FORMAT_LINEAR_16);
       SNDStartPlaying(&snd->snd, new_tag, SND_PLAY_PRIO, 0,
		       NULL, (SNDNotificationFun)SNDFree);
   } else {
       flush_tags(new_tag-1);
       SNDFree(&snd->snd);
       remove_pending_tags();	/* there should not be any */
   }

   SNDWait(0);
	}
