/*
 * Copyright 1993 Network Computing Devices, Inc.
 * 
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name Network Computing Devices, Inc. not be
 * used in advertising or publicity pertaining to distribution of this
 * software without specific, written prior permission.
 * 
 * THIS SOFTWARE IS PROVIDED `AS-IS'.  NETWORK COMPUTING DEVICES, INC.,
 * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT
 * LIMITATION ALL IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE, OR NONINFRINGEMENT.  IN NO EVENT SHALL NETWORK
 * COMPUTING DEVICES, INC., BE LIABLE FOR ANY DAMAGES WHATSOEVER, INCLUDING
 * SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES, INCLUDING LOSS OF USE, DATA,
 * OR PROFITS, EVEN IF ADVISED OF THE POSSIBILITY THEREOF, AND REGARDLESS OF
 * WHETHER IN AN ACTION IN CONTRACT, TORT OR NEGLIGENCE, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 * 
 * $NCDId: @(#)auutil.c,v 1.20 1996/01/26 19:11:42 greg Exp $
 */

#define		_AUUTIL_C_

#include	"misc.h"
#include	"dixstruct.h"
#include	<audio/audio.h>
#include	<audio/Aproto.h>
#include	"au.h"

extern void     AuDequeueEvents(), AuSetupCompiledFlow(),
                AuRequestElementNotifyEvent();
extern AuBool   AuStartFlow();
extern void    AuGetOutputGainAndMode(), AuGetFeedbackGain(),
               AuGetInputGain(), AuGetInputMode();

extern int      auMinibufSamples;
extern ComponentPtr auBuckets,
                auDevices;

AuBool          AuChangeElementState();

RESTYPE         auComponentType,
                auFlowType;
FlowPtr         auClockedFlows,
                auUnclockedFlows;
AuBool          auAverageFlows = AuFalse;

unsigned int    auBytesPerSample[] =
{
    0,						/* unused */
    1,						/* ULAW8 */
    1,						/* LinearUnsigned8 */
    1,						/* LinearSigned8 */
    2,						/* LinearSigned16MSB */
    2,						/* LinearUnsigned16MSB */
    2,						/* LinearSigned16LSB */
    2,						/* LinearUnsigned16LSB */
};

void
AuFreeComponent(c)
ComponentPtr    c;
{
    if (!c->refCnt--)
    {
	if (c->kind == AuComponentKindBucket)
	{
	    RemoveFromLinkedList(auBuckets, c);
	}
	else if (c->kind == AuComponentKindPhysicalInput ||
                c->kind == AuComponentKindPhysicalOutput ||
                c->kind == AuComponentKindPhysicalFeedback)
	{
	    RemoveFromLinkedList(auDevices, c);
	}

	aufree(c);
    }
}

static void
freeCompiledFlow(cmp)
CompiledFlowPtr cmp;
{
    int             i;

    for (i = 0; i < cmp->numOutputs; i++)
    {
	if (cmp->outputs[i].inputs)
	{
	    if (cmp->freeComponents)
	    {
		int             j;

		for (j = 0; j < cmp->outputs[i].numInputs; j++)
		{
		    CompiledFlowInputPtr in = &cmp->outputs[i].inputs[j];

		    if (!in->flowEl->refCnt-- &&
			in->flowEl->raw->type == AuElementTypeImportBucket)
			aufree(in->flowEl->minibuf);

		    if (in->freeDevPrivate)
			(*in->freeDevPrivate) (in->devPrivate);

		    AuFreeComponent(in->component);
		}
	    }

	    aufree(cmp->outputs[i].inputs);
	}

	if (cmp->freeComponents)
	{
	    if (cmp->outputs[i].flowEl->raw->type == AuElementTypeExportBucket)
		aufree(cmp->outputs[i].flowEl->minibuf);

	    AuFreeComponent(cmp->outputs[i].component);
	}

	if (cmp->outputs[i].freeDevPrivate)
	    (*cmp->outputs[i].freeDevPrivate) (cmp->outputs[i].devPrivate);
    }

    aufree(cmp->outputs);

    if (cmp->freeDevPrivate)
	(*cmp->freeDevPrivate) (cmp->devPrivate);

    cmp->numOutputs = 0;
    cmp->outputs = NULL;
}

void
AuFreeFlowElements(flow)
FlowPtr         flow;
{
    /* stop the flow if it's running */
    if (flow->state != AuStateStop)
	AuChangeElementState(flow, AuElementAll, AuStateStop, AuTrue,
			     AuReasonUser);

    if (flow->clocked)
    {
	RemoveFromLinkedList(auClockedFlows, flow);
    }
    else
    {
	RemoveFromLinkedList(auUnclockedFlows, flow);
    }

    freeCompiledFlow(&flow->compiled);
    aufree(flow->elements);
    flow->elements = NULL;
    flow->numElements = 0;
}

static void
freeFlow(flow)
FlowPtr         flow;
{
    if (flow->elements)
	AuFreeFlowElements(flow);

    AuDequeueEvents(flow);
    aufree(flow);
}

void
AuCreateResourceTypes()
{
    void            AuFreeComponent();
    static void     freeFlow();

    auComponentType = CreateNewResourceType(AuFreeComponent);
    auFlowType = CreateNewResourceType(freeFlow);
}

AuPointer
aucalloc(nelem, elsize)
AuUint32        nelem,
                elsize;
{
    AuPointer       p;

    if ((p = aualloc(nelem * elsize)))
	bzero((char *)p, nelem * elsize);

    return p;
}

AuPointer
auProtectedAlloc(size)
AuUint32        size;
{
    AuBlock         l = AuBlockAudio();
    AuPointer       p;

    p = (AuPointer) xalloc(size);
    AuUnBlockAudio(l);
    return p;
}

AuPointer
auProtectedRealloc(p, size)
AuPointer       p;
AuUint32        size;
{
    AuBlock         l = AuBlockAudio();

    p = (AuPointer) xrealloc(p, size);
    AuUnBlockAudio(l);
    return p;
}

void
auProtectedFree(p)
AuPointer       p;
{
    AuBlock         l = AuBlockAudio();

    xfree(p);
    AuUnBlockAudio(l);
}

/* create an import or export */
static          ComponentPtr
createPort(format, numTracks, numSamples, lowWater, highWater, discard)
AuUint32        format,
                numTracks,
                numSamples,
                lowWater,
                highWater;
AuBool          discard;
{
    ComponentPtr    port;
    AuUint32        dataSize,
                    minibufSize;

    dataSize = numSamples * sizeofFormat(format) * numTracks;
    minibufSize = auNativeBytesPerSample * auMinibufSamples * numTracks;

    /* the minibuf needs to be twice as large for output range checking */
    if (!(port = (ComponentPtr) aualloc(PAD4(sizeof(ComponentRec)) +
					PAD4(dataSize) +
					PAD4(minibufSize) * 2)))
	return NULL;

    port->numSamples = numSamples;
    port->format = format;
    port->numTracks = numTracks;
    port->lowWaterMark = lowWater * sizeofFormat(format) * numTracks;
    port->highWaterMark = highWater * sizeofFormat(format) * numTracks;
    port->dataSize = dataSize;
    port->data = (AuUint8 *) port + PAD4(sizeof(ComponentRec));
    port->dataEnd = port->data + port->dataSize;
    port->refCnt = 0;
    port->read = port->write = port->data;
    port->currentSize = 0;
    port->discard = discard;
    port->minibuf = port->data + PAD4(dataSize);
    port->minibufSize = minibufSize;
    port->kind = AuComponentKindOther;
    port->bytesPerSample = port->numTracks * sizeofFormat(port->format);
    port->physicalDeviceMask = NotAPhysicalDevice;

    return port;
}

/* create a monitor export */
static          ComponentPtr
createMonitor(format, numTracks)
AuUint32        format,
                numTracks;
{
    ComponentPtr    c;
    AuUint32        minibufSize;

    minibufSize = auNativeBytesPerSample * auMinibufSamples * numTracks;

    /* the minibuf needs to be twice as large for output range checking */
    if (!(c = (ComponentPtr) aualloc(PAD4(sizeof(ComponentRec)) +
				     PAD4(minibufSize) * 4)))
	return NULL;

    c->format = format;
    c->numTracks = numTracks;
    c->refCnt = 0;
    c->minibuf = (AuUint8 *) c + PAD4(sizeof(ComponentRec));
    c->read = c->minibuf + PAD4(minibufSize) * 2;
    c->minibufSize = minibufSize;
    c->physicalDeviceMask = NotAPhysicalDevice;
    c->kind = AuComponentKindOther;
    c->bytesPerSample = c->numTracks * sizeofFormat(c->format);

    return c;
}

/* create a wave form import */
static          ComponentPtr
createWaveForm(form)
int             form;
{
    ComponentPtr    c;
    AuUint32        minibufSize, n;
    /* these arrays must have a power of 2 number of elements */
    static AuUint16 squareWave[] =
    {
	0x7fff, 0x8001
    },
                    sineWave[] =
    {
	0x0000, 0x0324, 0x0647, 0x096a, 0x0c8b, 0x0fab, 0x12c7, 0x15e1,
	0x18f8, 0x1c0b, 0x1f19, 0x2223, 0x2527, 0x2826, 0x2b1e, 0x2e10,
	0x30fb, 0x33de, 0x36b9, 0x398c, 0x3c56, 0x3f16, 0x41cd, 0x447a,
	0x471c, 0x49b3, 0x4c3f, 0x4ebf, 0x5133, 0x539a, 0x55f4, 0x5842,
	0x5a81, 0x5cb3, 0x5ed6, 0x60eb, 0x62f1, 0x64e7, 0x66ce, 0x68a5,
	0x6a6c, 0x6c23, 0x6dc9, 0x6f5e, 0x70e1, 0x7254, 0x73b5, 0x7503,
	0x7640, 0x776b, 0x7883, 0x7989, 0x7a7c, 0x7b5c, 0x7c29, 0x7ce2,
	0x7d89, 0x7e1c, 0x7e9c, 0x7f08, 0x7f61, 0x7fa6, 0x7fd7, 0x7ff5,
	0x7fff, 0x7ff5, 0x7fd7, 0x7fa6, 0x7f61, 0x7f08, 0x7e9c, 0x7e1c,
	0x7d89, 0x7ce2, 0x7c29, 0x7b5c, 0x7a7c, 0x7989, 0x7883, 0x776b,
	0x7640, 0x7503, 0x73b5, 0x7254, 0x70e1, 0x6f5e, 0x6dc9, 0x6c23,
	0x6a6c, 0x68a5, 0x66ce, 0x64e7, 0x62f1, 0x60eb, 0x5ed6, 0x5cb3,
	0x5a81, 0x5842, 0x55f4, 0x539a, 0x5133, 0x4ebf, 0x4c3f, 0x49b3,
	0x471c, 0x447a, 0x41cd, 0x3f16, 0x3c56, 0x398c, 0x36b9, 0x33de,
	0x30fb, 0x2e10, 0x2b1e, 0x2826, 0x2527, 0x2223, 0x1f19, 0x1c0b,
	0x18f8, 0x15e1, 0x12c7, 0x0fab, 0x0c8b, 0x096a, 0x0647, 0x0324,
	0x0000, 0xfcdc, 0xf9b9, 0xf696, 0xf375, 0xf055, 0xed39, 0xea1f,
	0xe708, 0xe3f5, 0xe0e7, 0xdddd, 0xdad9, 0xd7da, 0xd4e2, 0xd1f0,
	0xcf05, 0xcc22, 0xc947, 0xc674, 0xc3aa, 0xc0ea, 0xbe33, 0xbb86,
	0xb8e4, 0xb64d, 0xb3c1, 0xb141, 0xaecd, 0xac66, 0xaa0c, 0xa7be,
	0xa57f, 0xa34d, 0xa12a, 0x9f15, 0x9d0f, 0x9b19, 0x9932, 0x975b,
	0x9594, 0x93dd, 0x9237, 0x90a2, 0x8f1f, 0x8dac, 0x8c4b, 0x8afd,
	0x89c0, 0x8895, 0x877d, 0x8677, 0x8584, 0x84a4, 0x83d7, 0x831e,
	0x8277, 0x81e4, 0x8164, 0x80f8, 0x809f, 0x805a, 0x8029, 0x800b,
	0x8001, 0x800b, 0x8029, 0x805a, 0x809f, 0x80f8, 0x8164, 0x81e4,
	0x8277, 0x831e, 0x83d7, 0x84a4, 0x8584, 0x8677, 0x877d, 0x8895,
	0x89c0, 0x8afd, 0x8c4b, 0x8dac, 0x8f1f, 0x90a2, 0x9237, 0x93dd,
	0x9594, 0x975b, 0x9932, 0x9b19, 0x9d0f, 0x9f15, 0xa12a, 0xa34d,
	0xa57f, 0xa7be, 0xaa0c, 0xac66, 0xaecd, 0xb141, 0xb3c1, 0xb64d,
	0xb8e4, 0xbb86, 0xbe33, 0xc0ea, 0xc3aa, 0xc674, 0xc947, 0xcc22,
	0xcf05, 0xd1f0, 0xd4e2, 0xd7da, 0xdad9, 0xdddd, 0xe0e7, 0xe3f5,
	0xe708, 0xea1f, 0xed39, 0xf055, 0xf375, 0xf696, 0xf9b9, 0xfcdc,
    };

    minibufSize = auNativeBytesPerSample * auMinibufSamples;

    if (!(c = (ComponentPtr) aualloc(PAD4(sizeof(ComponentRec)) +
				     PAD4(minibufSize))))
	return NULL;

    c->format = auNativeFormat;
    c->numTracks = 1;
    c->refCnt = 0;
    c->minibuf = (AuUint8 *) c + PAD4(sizeof(ComponentRec));
    c->minibufSize = minibufSize;
    c->physicalDeviceMask = NotAPhysicalDevice;
    c->kind = AuComponentKindOther;
    c->bytesPerSample = sizeofFormat(c->format);

    switch (form)
    {
	case AuWaveFormSquare:
	    c->data = (AuUint8 *) squareWave;
	    n = sizeof(squareWave);
	    break;
	case AuWaveFormSine:
	    c->data = (AuUint8 *) sineWave;
	    n = sizeof(sineWave);
	    break;
    }

    c->dataEnd = c->data + n;
    c->waveSamples = n / auNativeBytesPerSample;
    return c;
}

static int
compileInputs(client, elements, output, inputNum, multiplyConstant, addConstant,
	      numTracks, inTracks, firstOutTrack, recompile, inputCnt)
ClientPtr       client;
FlowElementPtr  elements;
CompiledFlowOutputPtr output;
AuUint32        inputNum,
               *inputCnt;
AuFixedPoint    multiplyConstant,
                addConstant;
AuBool          recompile;
AuUint8         numTracks,
               *inTracks,
                firstOutTrack;

{
    auElement      *el = elements[inputNum].raw;
    int             status = AuSuccess;
    AuBool          compiled = elements[inputNum].compiled,
                    compileInput = AuFalse;
    AuUint32        nextInput;
    AuUint8         inputTracks[auMaxTracks];

    /* XXX - need to check for loops */

    /* indicate that we've compiled this element */
    elements[inputNum].compiled = AuTrue;

    switch (el->type)
    {
	case AuElementTypeImportBucket:
	case AuElementTypeImportDevice:
	case AuElementTypeImportClient:
	case AuElementTypeImportWaveForm:
	    {
		CompiledFlowInputPtr input;

		if (!recompile)
		{
		    if (!(input = (CompiledFlowInputPtr)
			  aurealloc(output->inputs,
				    sizeof(CompiledFlowInputRec) *
				    (output->numInputs + 1))))
			AU_ERROR(AuBadAlloc, 0);

		    output->inputs = input;
		}

		input = &output->inputs[(*inputCnt)++];
		input->multiplyConstant = multiplyConstant;
		input->addConstant = addConstant;
		input->devPrivate = NULL;
		input->freeDevPrivate = (void (*) ()) NULL;

		if (recompile)
		    break;

		input->flowEl = &elements[inputNum];
		input->numTracks = numTracks;

		if (numTracks)
		{
		    int             i;

		    for (i = 0; i < (int) numTracks; i++)
			input->outTrack[i] = firstOutTrack++;

		    bcopy(inTracks, input->inTrack, numTracks);
		}

		if (compiled)
		{
		    input->component = input->flowEl->component;
		    input->flowEl->refCnt++;
		    input->component->refCnt++;
		    output->numInputs++;
		    break;
		}

		switch (el->type)
		{
		    case AuElementTypeImportClient:
			{
			    auElementImportClient *p = &el->importclient;

			    if (!(input->component =
				  createPort(p->format, p->num_tracks,
					  p->max_samples, p->low_water_mark,
					     0, p->discard)))
				AU_ERROR(AuBadAlloc, 0);

			    input->component->sampleRate = p->sample_rate;
			    break;
			}
		    case AuElementTypeImportWaveForm:
			{
			    auElementImportWaveForm *p = &el->importwaveform;

			    if (!(input->component =
				  createWaveForm(p->wave_form)))
				AU_ERROR(AuBadAlloc, 0);

			    input->component->sampleRate = p->sample_rate;
			    break;
			}
		    case AuElementTypeImportDevice:
			{
			    auElementImportDevice *p = &el->importdevice;

			    if (!(input->component = (ComponentPtr)
				LookupIDByType(p->device, auComponentType)))
				AU_ERROR(AuBadDevice, p->device);

			    input->component->refCnt++;
			    input->component->sampleRate = p->sample_rate;
			    break;
			}
		    case AuElementTypeImportBucket:
			{
			    auElementImportBucket *p = &el->importbucket;

			    if (!(input->component = (ComponentPtr)
				LookupIDByType(p->bucket, auComponentType)))
				AU_ERROR(AuBadBucket, p->bucket);

			    input->flowEl->sampleRate = p->sample_rate;
			    break;
			}
		}

		if (el->type == AuElementTypeImportBucket)
		{
		    if (!(input->flowEl->minibuf = (AuUint8 *)
			  aualloc(input->component->minibufSize)))
			AU_ERROR(AuBadAlloc, 0);

		    input->component->refCnt++;
		}
		else
		    input->flowEl->minibuf = input->component->minibuf;

		input->flowEl->component = input->component;
		output->numInputs++;
	    }
	    break;
	case AuElementTypeBundle:
	    {
		int             i;
		auInputTrack   *tracks = (auInputTrack *) & el[1];

		numTracks = 0;
		inTracks = inputTracks;
		nextInput = -1;

		for (i = 0; i < (int) el->bundle.num_inputs; i++, tracks++)
		{
		    if (numTracks && tracks->element_num != nextInput)
		    {
			status = compileInputs(client, elements, output,
					       nextInput, multiplyConstant,
					       addConstant, numTracks,
					       inTracks, firstOutTrack,
					       recompile, inputCnt);

			if (status != AuSuccess)
			    return status;

			firstOutTrack += numTracks;
			numTracks = 0;
		    }

		    nextInput = tracks->element_num;
		    inTracks[numTracks++] = tracks->track;
		}

		compileInput = AuTrue;
		break;
	    }
	case AuElementTypeMultiplyConstant:
	    multiplyConstant =
		AuFixedPointMultiply(multiplyConstant,
				     el->multiplyconstant.constant);
	    compileInput = AuTrue;
	    nextInput = el->multiplyconstant.input;
	    break;
	case AuElementTypeAddConstant:
	    addConstant += el->addconstant.constant;
	    compileInput = AuTrue;
	    nextInput = el->addconstant.input;
	    break;
	case AuElementTypeSum:
	    {
		AuUint16       *inputs = (AuUint16 *) &el[1];
		int             i;

		for (i = 0;
		     i < (int) el->sum.num_inputs && status == AuSuccess; i++)
		    status = compileInputs(client, elements, output, *inputs++,
				   multiplyConstant, addConstant, numTracks,
					 inTracks, firstOutTrack, recompile,
					   inputCnt);
		break;
	    }
	default:
	    AU_ERROR(AuBadElement, inputNum);
    }

    if (compileInput)
	status = compileInputs(client, elements, output, nextInput,
			       multiplyConstant, addConstant, numTracks,
			       inTracks, firstOutTrack, recompile, inputCnt);

    return status;
}

int
AuCompileFlow(client, flow)
ClientPtr       client;
FlowPtr         flow;
{
    AuUint32        i,
                    outputCnt = 0,
                    inputCnt,
                    status;
    auElement      *el;
    CompiledFlowPtr cmp = &flow->compiled;
    CompiledFlowOutputPtr output;
    AuBool          recompile;

    recompile = cmp->numOutputs ? AuTrue : AuFalse;

    for (i = 0; i < flow->numElements; i++)
    {
	el = flow->elements[i].raw;

	/* look for all of the exports */
	switch (el->type)
	{
	    case AuElementTypeExportBucket:
	    case AuElementTypeExportClient:
	    case AuElementTypeExportDevice:
	    case AuElementTypeExportMonitor:
		if (!recompile)
		{
		    /* alloc space for the new output */
		    if (!(output = (CompiledFlowOutputPtr)
			  aurealloc(cmp->outputs,
				    sizeof(CompiledFlowOutputRec) *
				    (cmp->numOutputs + 1))))
			AU_ERROR(AuBadAlloc, 0);

		    cmp->outputs = output;
		}

		output = &cmp->outputs[outputCnt++];

		if (!recompile)
		{
		    output->flowEl = &flow->elements[i];

		    switch (el->type)
		    {
			case AuElementTypeExportClient:
			    {
				auElementExportClient *p = &el->exportclient;

				if (!(output->component =
				      createPort(p->format, p->num_tracks,
						 p->max_samples, 0,
					   p->high_water_mark, p->discard)))
				    AU_ERROR(AuBadAlloc, 0);

				output->component->sampleRate = p->sample_rate;
				output->firstInput = el->exportclient.input;
				break;
			    }
			case AuElementTypeExportDevice:
			    {
				auElementExportDevice *p = &el->exportdevice;

				if (!(output->component = (ComponentPtr)
				LookupIDByType(p->device, auComponentType)))
				    AU_ERROR(AuBadDevice, p->device);

				output->component->refCnt++;
                              output->flowEl->sampleRate = p->sample_rate;
				output->firstInput = el->exportdevice.input;
				break;
			    }
			case AuElementTypeExportBucket:
			    {
				auElementExportBucket *p = &el->exportbucket;

				if (!(output->component = (ComponentPtr)
				LookupIDByType(p->bucket, auComponentType)))
				    AU_ERROR(AuBadBucket, p->bucket);
				output->firstInput = el->exportbucket.input;
				break;
			    }
			case AuElementTypeExportMonitor:
			    {
				auElementExportMonitor *p = &el->exportmonitor;

				if (!(output->component =
				   createMonitor(p->format, p->num_tracks)))
				    AU_ERROR(AuBadAlloc, 0);

				output->component->sampleRate = p->event_rate;
				output->firstInput = el->exportmonitor.input;
				break;
			    }
		    }

		    if (el->type == AuElementTypeExportBucket)
		    {
			/* the minibuf needs to be twice as large for
                           output range checking */
			if (!(output->flowEl->minibuf = (AuUint8 *)
			      aualloc(output->component->minibufSize * 2)))
			    AU_ERROR(AuBadAlloc, 0);

			output->component->refCnt++;
		    }
		    else
			output->flowEl->minibuf = output->component->minibuf;

		    /* initialize compiled output */
		    output->numInputs = 0;
		    output->inputs = NULL;
		    output->devPrivate = NULL;
		    output->freeDevPrivate = (void (*) ()) NULL;
		    output->flowEl->component = output->component;
		    output->flowEl->compiled = AuTrue;
		    cmp->numOutputs++;
		}

		/* compile the inputs for this output */
		inputCnt = 0;
		status = compileInputs(client, flow->elements, output,
				       output->firstInput,
				       AuFixedPointFromSum(1, 0),
				       AuFixedPointFromSum(0, 0),
				       0, NULL, 0, recompile, &inputCnt);

		if (status != AuSuccess)
		    return status;

		break;
	}
    }

    return AuSuccess;
}

static          CompiledFlowPtr
AuProcessFlows(fl)
FlowPtr         fl;
{
    FlowPtr         flow,
                    f;
    CompiledFlowPtr new;
    CompiledFlowOutputPtr output,
                    newOutput;
    int             i,
                    j,
                    numGroups,
                    totalInputs;
    AuUint32        maxRate = 0,
		    r;
    CompiledFlowOutputPtr *groups;

    /* allocate and clear the compiled flow */
    if (!(new = (CompiledFlowPtr) aucalloc(1, sizeof(CompiledFlowRec))))
	return NULL;		/* XXX: this means we'll keep trying */

    /* clear all the processed flags */
    flow = fl;

    while (flow)
    {
	output = flow->compiled.outputs;

	for (i = 0; i < flow->compiled.numOutputs; i++, output++)
	    output->component->processed = AuFalse;

	flow = flow->next;
    }

    flow = fl;

    while (flow)
    {
	output = flow->compiled.outputs;

	/* nothing to do if we're not starting the flow */
	if (flow->pendingState != AuStateStart)
	{
	    flow = flow->next;
	    continue;
	}

	/* process all the outputs in the flow */
	for (i = 0; i < flow->compiled.numOutputs; i++, output++)
	{
	    if (output->component->processed)
		continue;

	    if (!(newOutput = (CompiledFlowOutputPtr)
		  aurealloc(new->outputs, sizeof(CompiledFlowOutputRec) *
			    (new->numOutputs + 1))))
	    {
		freeCompiledFlow(new);
		aufree(new);
		return NULL;	/* XXX: this means we'll keep trying */
	    }

	    new->outputs = newOutput;
	    newOutput = &new->outputs[new->numOutputs];
	    new->numOutputs++;

	    newOutput->component = output->component;
	    newOutput->numInputs = 0;
	    newOutput->inputs = NULL;
	    newOutput->flowEl = output->flowEl;
	    newOutput->devPrivate = NULL;
	    newOutput->freeDevPrivate = (void (*) ()) NULL;
	    new->physicalDeviceMask |= output->component->physicalDeviceMask;

	    /* accumulate all the input groups for this output */
	    f = fl;
	    numGroups = totalInputs = 0;
	    groups = NULL;

	    while (f)
	    {
		int             k;
		CompiledFlowOutputPtr *group,
		                out;
		CompiledFlowPtr cmp;

		if (f->pendingState != AuStateStart)
		{
		    f = f->next;
		    continue;
		}

		cmp = &f->compiled;

		for (k = 0; k < cmp->numOutputs; k++)
		{
		    out = &cmp->outputs[k];

		    /* look for the current output */
		    if (out->component != output->component)
			continue;

		    if (!(group = (CompiledFlowOutputPtr *)
			  aurealloc(groups, sizeof(CompiledFlowOutputPtr) *
				    (numGroups + 1))))
		    {
			freeCompiledFlow(new);
			aufree(groups);
			aufree(new);
			return NULL;	/* XXX: this means we'll keep trying */
		    }

		    groups = group;
		    groups[numGroups++] = out;
		    totalInputs += out->numInputs;
		    out->component->processed = AuTrue;
		}

		f = f->next;
	    }

	    if (!(newOutput->inputs = (CompiledFlowInputPtr)
		  aualloc(sizeof(CompiledFlowInputRec) * totalInputs)))
	    {
		freeCompiledFlow(new);
		aufree(groups);
		aufree(new);
		return NULL;	/* XXX: this means we'll keep trying */
	    }

	    /* special case for only one group of inputs */
	    if (numGroups == 1)
	    {
		/* copy the inputs */
		bcopy(output->inputs, newOutput->inputs,
		      sizeof(CompiledFlowInputRec) * output->numInputs);
	    }
	    else
	    {
		/*
		 * adjust the multiply constants to average all the input
		 * groups
		 */
		CompiledFlowOutputPtr *group = groups;
		CompiledFlowInputPtr input = newOutput->inputs;
		AuFixedPoint    m = (1 << 16) / numGroups;

		while (numGroups--)
		{
		    int             k;

		    /* copy the group */
		    bcopy((*group)->inputs, input,
			sizeof(CompiledFlowInputRec) * (*group)->numInputs);

		    if (auAverageFlows)
			/* average the multiply constants */
			for (k = 0; k < (*group)->numInputs; k++, input++)
			    input->multiplyConstant =
				AuFixedPointMultiply(input->multiplyConstant,
						     m);
		    else
			input += (*group)->numInputs;

		    group++;
		}
	    }

	    newOutput->numInputs = totalInputs;
	    aufree(groups);

	    /*
	     * see if any of the inputs have a higher sampling rate and
	     * initialize some stuff
	     */
	    for (j = 0; j < newOutput->numInputs; j++)
	    {
		CompiledFlowInputPtr input = &newOutput->inputs[j];

		new->physicalDeviceMask |= input->component->physicalDeviceMask;

		if (input->flowEl->raw->type == AuElementTypeImportBucket)
		{
		    input->preadTag = &input->flowEl->readTag;

		    if (input->flowEl->sampleRate > maxRate)
			maxRate = input->flowEl->sampleRate;
		}
		else
		{
		    input->preadTag = &input->component->readTag;

		    if (input->component->sampleRate > maxRate)
			maxRate = input->component->sampleRate;
		}
	    }

	    r = newOutput->flowEl->raw->type == AuElementTypeExportDevice ?
		newOutput->flowEl->sampleRate :
		newOutput->component->sampleRate;

	    if (r > maxRate)
		maxRate = r;
  
	    newOutput->component->processed = AuTrue;
	}

	flow = flow->next;
    }

    flow = fl;

    /* update all of the states */
    while (flow)
    {
	flow->state = flow->pendingState;
	flow = flow->next;
    }

    AuSetupCompiledFlow(new, maxRate);
    return new;
}

void
AuProcessUnclockedFlows()
{
    /* XXX: yet to be implemented */
}

void
AuProcessClockedFlows()
{
    CompiledFlowPtr new,
                    old;

    if ((new = AuProcessFlows(auClockedFlows)))
    {
	if (!AuStartFlow(new, &old))
	{
	    freeCompiledFlow(new);
	    aufree(new);
	}

	if (old)
	{
	    freeCompiledFlow(old);
	    aufree(old);
	}
    }
}

static void
setFlowState(flow)
FlowPtr         flow;
{
    int             i,
                    stop,
                    start,
                    pause;

    start = stop = pause = 0;

    for (i = 0; i < flow->numElements; i++)
	if (flow->elements[i].isComponent)
	    switch (flow->elements[i].state)
	    {
		case AuStateStop:
		    stop++;
		    break;
		case AuStateStart:
		    start++;
		    break;
		default:	/* AuStatePause */
		    pause++;
	    }

    /**
     * Flow state is:
     *
     *	AuStateStart - if any element is started
     *  AuStatePause - if no elements are started and any element is paused
     *  AuStateStop  - if all elements are stopped
     */
    flow->pendingState = start ? AuStateStart :
	(pause ? AuStatePause : AuStateStop);
}

#define CHECK_STATE(t, s) ((t) == AuStateAny || (t) == (s))
#define CHECK_REASON(t, r) ((t) == AuReasonAny || (t) == (r))

static void
checkForTriggeredActions(el, processFlow, reason)
FlowElementPtr  el;
AuBool          processFlow;
int             reason;
{
    int             i,
                    state = el->state,		/* so these don't get changed */
                    prevState = el->prevState;	/* out from under us */
    ActionPtr       a;

    for (i = 0; i < el->numActions; i++)
    {
	a = &el->actions[i];

	if (CHECK_STATE(a->triggerState, state) &&
	    CHECK_STATE(a->triggerPrevState, prevState) &&
	    CHECK_REASON(a->triggerReason, reason))
	    switch (a->action)
	    {
		case AuElementActionChangeState:
		    AuChangeElementState(a->flow, a->elementNum, a->newState,
					 processFlow, reason);
		    break;
		case AuElementActionSendNotify:
		    AuRequestElementNotifyEvent(AuElementNotifyKindState,
						reason, el);
		    break;
		case AuElementActionNoop:
		    break;
	    }
    }
}

/* could be called at audio interrupt level */
AuBool
AuChangeElementState(flow, elementNum, newState, processFlow, reason)
FlowPtr         flow;
AuUint32        elementNum,
                newState;
AuBool          processFlow;
int             reason;
{
    AuBlock         l;
    FlowElementPtr  el;
    int             first,
                    last,
                    i;
    AuBool          stateChange = AuFalse;

    if (elementNum == AuElementAll)
    {
	first = 0;
	last = flow->numElements - 1;
    }
    else
	first = last = elementNum;

    l = AuBlockAudio();
    for (i = first; i <= last; i++)
    {
	el = &flow->elements[i];

	/* see if there's a state change */
	if (newState == el->state)
	    continue;

	/* changing from stopped to paused is not allowed */
	if (newState == AuStatePause && el->state == AuStateStop)
	    continue;

	/*
	 * if we're starting up an element from a stopped state, we may need
	 * to do some stuff
	 */
	if (el->compiled &&
	    newState == AuStateStart && el->state == AuStateStop)
	{
	    el->currentSample = 0;

	    switch (el->raw->type)
	    {
		case AuElementTypeImportWaveForm:
		    {
			el->countSamples =
			    el->raw->importwaveform.num_samples !=
			    AuUnlimitedSamples;

			if (el->countSamples)
			    el->numBytes = el->raw->importwaveform.num_samples *
				auNativeBytesPerSample;

			el->component->frequency =
			    el->raw->importwaveform.frequency *
			    el->component->waveSamples;

			el->read = el->component->data;
			break;
		    }
		case AuElementTypeImportDevice:
		    el->countSamples = el->raw->importdevice.num_samples !=
			AuUnlimitedSamples;

		    if (el->countSamples)
			el->numBytes = el->raw->importdevice.num_samples *
			    el->component->bytesPerSample;
		    break;
		case AuElementTypeExportDevice:
		    el->countSamples = el->raw->exportdevice.num_samples !=
			AuUnlimitedSamples;

		    if (el->countSamples)
			el->numBytes = el->raw->exportdevice.num_samples *
			    el->component->bytesPerSample;
		    break;
		case AuElementTypeImportClient:
		    if (el->component->discard)
		    {
			el->component->read = el->component->write =
			    el->component->data;
			el->component->incoming =
			    el->component->currentSize = 0;
		    }

		    el->component->eof = AuFalse;
		    break;
		case AuElementTypeExportClient:
		    if (el->component->discard)
		    {
			el->component->read = el->component->write =
			    el->component->data;
			el->component->outgoing =
			    el->component->currentSize = 0;
		    }
		    break;
		case AuElementTypeImportBucket:
		    {
			AuUint32        n;

			n = el->component->bytesPerSample;
			el->read = el->component->data +
			    el->raw->importbucket.offset * n;
			el->countSamples = el->raw->importbucket.num_samples !=
			    AuUnlimitedSamples;

			if (el->countSamples)
			    el->numBytes =
				el->raw->importbucket.num_samples * n;
			break;
		    }
		case AuElementTypeExportBucket:
		    {
			AuUint32        n;

			n = el->component->bytesPerSample;
			el->write = el->component->data +
			    el->raw->exportbucket.offset * n;
			el->countSamples = el->raw->exportbucket.num_samples !=
			    AuUnlimitedSamples;

			if (el->countSamples)
			    el->numBytes =
				el->raw->exportbucket.num_samples * n;
			break;
		    }
		case AuElementTypeExportMonitor:
 		    {
 			int             i;
 			AuUint32       *p = (AuUint32 *) el->component->read;
 
 			for (i = 0; i < (int) el->component->numTracks *
 			     auMinibufSamples; i++)
 			    *p++ = 0x7fff8001;

			el->component->write = el->component->read;
  			break;
		    }
	    }
	}

	el->prevState = el->state;
	el->state = newState;
	stateChange = el->stateChange = AuTrue;
    }

    for (i = first; i <= last; i++)
    {
	el = &flow->elements[i];

	if (el->stateChange)
	{
	    el->stateChange = AuFalse;

	    if (el->raw->type == AuElementTypeImportClient &&
		el->component->discard && el->prevState == AuStateStop &&
		el->state == AuStateStart)
	    {
		/*
		 * this forces an empty import to be paused before anything
		 * gets going
		 */
		el->state = AuStatePause;
		el->prevState = AuStateStart;
		checkForTriggeredActions(el, processFlow, AuReasonUnderrun);
	    }
	    else
		checkForTriggeredActions(el, processFlow, reason);
	}
    }

    if (stateChange)
	setFlowState(flow);

    AuUnBlockAudio(l);

    if (processFlow && flow->state != flow->pendingState)
	if (flow->clocked)
	    AuProcessClockedFlows();
	else
	    AuProcessUnclockedFlows();

    return flow->state != flow->pendingState;
}

void
AuProcessStateChanges(numStates, states, flows)
AuUint32        numStates;
auElementState *states;
FlowPtr        *flows;
{
    while (numStates--)
    {
	AuChangeElementState(*flows, states->element_num, states->state,
			     AuFalse, AuReasonUser);
	states++;
	flows++;
    }

    AuProcessUnclockedFlows();
    AuProcessClockedFlows();
}

#define COMMON(x)	((auCommonPart *) a)->x
#define DEVICE(x)	((auDeviceAttributes *) a)->device.x
#define BUCKET(x)	((auBucketAttributes *) a)->bucket.x

int
AuSetComponentAttributes(a, c, varData)
AuUint8        *a,
               *varData;
ComponentPtr    c;
{
    AuUint32        mask,
                    i;

    /* XXX: need to add error checking */

    mask = COMMON(value_mask) & AuCompCommonMasks;
    i = 1;

    while (mask)
    {
	if (mask & 1)
	    switch (i)
	    {
		case AuCompCommonIDMask:
		    c->id = COMMON(id);
		    break;
		case AuCompCommonKindMask:
		    c->kind = COMMON(kind);
		    break;
		case AuCompCommonUseMask:
		    c->use = COMMON(use);
		    break;
		case AuCompCommonFormatMask:
		    c->format = COMMON(format);
		    break;
		case AuCompCommonNumTracksMask:
		    c->numTracks = COMMON(num_tracks);
		    break;
		case AuCompCommonAccessMask:
		    c->access = COMMON(access);
		    break;
		case AuCompCommonDescriptionMask:
		    c->description.type = COMMON(description.type);
		    c->description.len = COMMON(description.len);
		    bcopy(varData, c->description.string, c->description.len);
		    varData += PAD4(c->description.len);
		    break;
	    }

	mask >>= 1;
	i <<= 1;
    }

    /* this assumes that the common masks occupy the lower 16 bits */
    i = (1L << 16);
    mask = COMMON(value_mask);

    switch (c->kind)
    {
	case AuComponentKindPhysicalInput:
	case AuComponentKindPhysicalOutput:
       case AuComponentKindPhysicalFeedback:
	    mask = (mask & AuCompDeviceMasks) >> 16;

	    while (mask)
	    {
		if (mask & 1)
		    switch (i)
		    {
#if 0
			case AuCompDeviceMinSampleRateMask:
			    c->minSampleRate = DEVICE(min_sample_rate);
			    break;
			case AuCompDeviceMaxSampleRateMask:
			    c->maxSampleRate = DEVICE(max_sample_rate);
			    break;
			case AuCompDeviceLocationMask:
			    c->location = DEVICE(location);
			    break;
#endif
			case AuCompDeviceGainMask:
			    c->gain = DEVICE(gain);
			    break;
			case AuCompDeviceLineModeMask:
			    c->lineMode = DEVICE(line_mode);
			    break;
#if 0
			case AuCompDeviceChildrenMask:
			    break;
#endif
		    }

		mask >>= 1;
		i <<= 1;
	    }
	    break;
	case AuComponentKindBucket:
	    mask = (mask & AuCompBucketMasks) >> 16;

	    while (mask)
	    {
		if (mask & 1)
		    switch (i)
		    {
			case AuCompBucketSampleRateMask:
			    c->sampleRate = BUCKET(sample_rate);
			    break;
			case AuCompBucketNumSamplesMask:
			    c->numSamples = BUCKET(num_samples);
			    break;
		    }

		mask >>= 1;
		i <<= 1;
	    }
	    break;
#ifdef NOTYET
	case AuComponentKindRadio:
	    mask &= AuCompRadioMasks;

	    while (mask)
	    {
		switch (mask & 1)
		{
		}

		mask >>= 1;
		i <<= 1;
	    }
	    break;
#endif				/* NOTYET */
    }

    return AuSuccess;
}

#define COMPARE_COMMON(x, y) 	     if (c->x != COMMON(y)) 	return AuFalse
#define COMPARE_COMMON_MASK(x, y)    if (!(c->x & COMMON(y))) 	return AuFalse
#define COMPARE_BUCKET(x, y) 	     if (c->x != BUCKET(y)) 	return AuFalse
#define COMPARE_DEVICE(x, y) 	     if (c->x != DEVICE(y)) 	return AuFalse
#define COMPARE_DEVICE_MASK(x, y)    if (!(c->x & DEVICE(y))) 	return AuFalse

AuBool
AuMatchAttributes(a, c, varData)
AuUint8        *a,
               *varData;
ComponentPtr    c;
{
    AuUint32        mask,
                    i;

    mask = COMMON(value_mask) & AuCompCommonMasks;
    i = 1;

    while (mask)
    {
	if (mask & 1)
	    switch (i)
	    {
		case AuCompCommonIDMask:
		    COMPARE_COMMON(id, id);
		    break;
		case AuCompCommonKindMask:
		    COMPARE_COMMON(kind, kind);
		    break;
		case AuCompCommonUseMask:
		    COMPARE_COMMON_MASK(use, use);
		    break;
		case AuCompCommonFormatMask:
		    COMPARE_COMMON(format, format);
		    break;
		case AuCompCommonNumTracksMask:
		    COMPARE_COMMON(numTracks, num_tracks);
		    break;
		case AuCompCommonAccessMask:
		    COMPARE_COMMON_MASK(access, access);
		    break;
		case AuCompCommonDescriptionMask:
		    COMPARE_COMMON(description.type, description.type);
		    COMPARE_COMMON(description.len, description.len);

		    if (strncmp(c->description.string, (char *)varData,
				COMMON(description.len)))
			return AuFalse;

		    varData += PAD4(COMMON(description.len));
		    break;
	    }

	mask >>= 1;
	i <<= 1;
    }

    /* this assumes that the common masks occupy the lower 16 bits */
    i = (1L << 16);
    mask = COMMON(value_mask);

    switch (c->kind)
    {
	case AuComponentKindPhysicalInput:
	case AuComponentKindPhysicalOutput:
       case AuComponentKindPhysicalFeedback:
	    mask = (mask & AuCompDeviceMasks) >> 16;

	    while (mask)
	    {
		if (mask & 1)
		    switch (i)
		    {
			case AuCompDeviceMinSampleRateMask:
			    COMPARE_DEVICE(minSampleRate, min_sample_rate);
			    break;
			case AuCompDeviceMaxSampleRateMask:
			    COMPARE_DEVICE(maxSampleRate, max_sample_rate);
			    break;
			case AuCompDeviceLocationMask:
			    COMPARE_DEVICE_MASK(location, location);
			    break;
			case AuCompDeviceGainMask:
                           if (c->kind == AuComponentKindPhysicalInput)
                               AuGetInputGain(&c->gain);
                           else if (c->kind == AuComponentKindPhysicalOutput)
				AuGetOutputGainAndMode(&c->gain, &c->lineMode);
                           else if (c->kind == AuComponentKindPhysicalFeedback)
                               AuGetFeedbackGain(&c->gain);
			    COMPARE_DEVICE(gain, gain);
			    break;
			case AuCompDeviceLineModeMask:
                           if (c->kind == AuComponentKindPhysicalInput)
                               AuGetInputMode(&c->lineMode);
                           else if (c->kind == AuComponentKindPhysicalOutput)
				AuGetOutputGainAndMode(&c->gain, &c->lineMode);
			    COMPARE_DEVICE(lineMode, line_mode);
			    break;
			case AuCompDeviceChildrenMask:
			    COMPARE_DEVICE(numChildren, num_children);
			    if (memcmp(c->children, varData,
				       DEVICE(num_children) *
				       sizeof(AuDeviceID)))
				return AuFalse;

			    varData += DEVICE(num_children) *
				sizeof(AuDeviceID);
			    break;
		    }

		mask >>= 1;
		i <<= 1;
	    }
	    break;
	case AuComponentKindBucket:
	    mask = (mask & AuCompBucketMasks) >> 16;

	    while (mask)
	    {
		if (mask & 1)
		    switch (i)
		    {
			case AuCompBucketSampleRateMask:
			    COMPARE_BUCKET(sampleRate, sample_rate);
			    break;
			case AuCompBucketNumSamplesMask:
			    COMPARE_BUCKET(numSamples, num_samples);
			    break;
		    }

		mask >>= 1;
		i <<= 1;
	    }
	    break;
#ifdef NOTYET
	case AuComponentKindRadio:
	    mask &= AuCompRadioMasks;

	    while (mask)
	    {
		switch (mask & 1)
		{
		}

		mask >>= 1;
		i <<= 1;
	    }
	    break;
#endif				/* NOTYET */
    }

    return AuTrue;
}

AuFixedPoint
AuFixedPointMultiply(m1, m2)
AuFixedPoint    m1,
                m2;
{
    AuBool          negative = AuFalse;
    AuFixedPoint    result;

    if (m1 < 0)
    {
	negative = !negative;
	m1 = -m1;
    }

    if (m2 < 0)
    {
	negative = !negative;
	m2 = -m2;
    }

    /**
     *		 A.B
     *	       x C.D
     *         -----
     *	        D * A
     *	        xx.xx
     * +	   D * B
     *		   xx.xx
     * +      C * AB
     *		xx.xx
     * -----------------
     *		xx.xx
     */

#define A 	AuFixedPointIntegralAddend(m1)
#define B 	AuFixedPointFractionalAddend(m1)
#define C 	AuFixedPointIntegralAddend(m2)
#define D 	AuFixedPointFractionalAddend(m2)
#define	AB	m1

    result = D * A + (D * B >> 16) + C * AB;

#undef A
#undef B
#undef C
#undef D
#undef AB

    if (negative)
	result = -result;

    return result;
}
