/***************************************************************************
*   NAME:  MIX.C $Revision: 1.20 $
**  COPYRIGHT:
**  "Copyright (c) 1994,1995 by e-Tek Labs"
**
**  "This software is furnished under a license and may be used,
**   copied, or disclosed only in accordance with the terms of such
**   license and with the inclusion of the above copyright notice.
**   This software or any other copies thereof may not be provided or
**   otherwise made available to any other person. No title to and
**   ownership of the software is hereby transfered."
****************************************************************************
* $Log: mix.c $
* Revision 1.20  1995/11/10 15:00:34  sdsmith
* Changes for Win95 compatability
* Revision 1.19  1995/11/02 13:28:01  mks
* Final changes for prod to add REVEAL_BUILD control
* Revision 1.18  1995/10/27 14:38:19  sdsmith
* Voyetra fix
* Revision 1.17  1995/10/24 15:34:05  unknown
* Changes for Voyetra
* Revision 1.16  1995/10/24 11:26:35  sdsmith
* Fixes for Windows 95 compatability tests
* Revision 1.15  1995/10/16 14:42:03  sdsmith
* Made sure source types are assigned only to MASTER nodes
* Revision 1.14  1995/10/13 17:33:12  mleibow
* Changed patch loader to use iwllist.c and iwllist.h instead of list.c and list.h
* Revision 1.13  1995/08/09 17:01:16  sdsmith
* Corrections for mixer bugs
* Revision 1.12  1995/07/12 19:08:04  sdsmith
* Changed to only mark MASTER nodes
* Revision 1.11  1995/05/29 17:32:42  sdsmith
* Various bug fixes
* Revision 1.10  1995/05/03 14:21:05  teckert
* Revision 1.9  1995/04/28 13:24:08  sdsmith
* Added support for intermediate nodes
* Revision 1.8  1995/04/26 15:51:27  sdsmith
* Undid change in last revision
* Revision 1.7  1995/04/26 14:24:12  sdsmith
* Added AUX 2 usage check
* Revision 1.6  1995/04/19 15:40:20  sdsmith
* Various fixes
* Revision 1.4  1995/03/31 02:47:19  sdsmith
* Fixed SetupMixer so it is only executed once
* Revision 1.3  1995/03/30 10:10:32  sdsmith
* Added new mixer support
* Revision 1.2  1995/02/23 15:15:14  sdsmith
* hello
* Revision 1.1  1995/02/23 15:09:00  sdsmith
* Initial revision
***************************************************************************/
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <mmddk.h>
#include "iw.h"
#include "os.h"
#include "interwav.h"

static LPMIXERNODE mixer_nodes = 0;
LPMIXEROUTPUT outputs = 0;
int num_mixer_nodes;
int num_outputs = 0;
LPMIXERNODE find_node(short id, iw_mixer_node_type type, short ordinal);

/****************************************************************************

FUNCTION DEFINITION:
iw_mix_report_nodes - kernel callback to report total number of mixer nodes

DESCRIPTION:
iw_mix_report_nodes is the first callback the kernel calls after a wrapper
makes a call to iw_build_mixer.  Since this is the first call, the pointer
to track the tail of the nodelist is initialized here.

PARAMETERS:
number - total number of mixer nodes

RETURNS: int - IW_OK

*/
int iw_mix_report_nodes(int number)
{
    num_mixer_nodes = number;
    return(IW_OK);
}

/****************************************************************************

FUNCTION DEFINITION:
iw_mix_add_node - kernel callback to add a new mixer node

DESCRIPTION:
iw_mix_add_node is called whenever the kernel has a new node to report
to the wrapper.  This routine builds a forward chained link list of
the mixer nodes as they are reported from the kernel.  The nodes of
the list are allocated from the local heap.

COMMENTS:
This routine uses fixes memory as it is allocated.  An improvement would
be to allocate the memory as moveable and lock it when necessary.  This
would give Windows more effective management of the local heap.  

RETURNS: int - IW_OK if no problems
			   IW_NO_MEMORY if we run out of memory

*/
int iw_mix_add_node(
	short id,
	short parent_id,
	short owner_id,
	iw_mixer_node_type type,
	iw_mixer_node_subtype subtype,
	short ordinal,
	short subordinal,
	short attributes,
	int range_min,
	int range_max,
	int inc_integer,
	int inc_fraction,
	int left_value,
	int right_value,
	unsigned char RFAR *description,
	unsigned char RFAR *name)
{
    int rc = IW_OK, ord, left, right;
    LPMIXERNODE new_node, tmp;
    HGLOBAL hnode;

    if (type == OUTPUT) {
	num_outputs++;
    }
    hnode = GlobalAlloc(GHND, sizeof(MIXERNODE));
    if (hnode) {
	new_node = (LPMIXERNODE)GlobalLock(hnode);
	new_node->id = id;
	new_node->type = type;
	new_node->subtype = subtype;
	new_node->ordinal = ordinal;
	new_node->subordinal = subordinal;
	new_node->attributes = attributes;
	new_node->range_min = range_min;
	new_node->range_max = range_max;
	new_node->description = description;
	new_node->name = name;
	if (new_node->type == CONTROL) {
	    iw_mix_get_control(new_node->id, &left, &right);
	    if (new_node->subtype == LEVEL) {
		if (new_node->attributes & IW_MIX_ATTR_LOG) {
		    new_node->left = MAKELONG((iw_db_to_linear(new_node->id, left) << 9),0);
		    if (new_node->attributes & IW_MIX_ATTR_STEREO)
			new_node->right = MAKELONG((iw_db_to_linear(new_node->id, right) << 9),0);
		}
		else {
		    new_node->left = MAKELONG((left << 9),0);
		    if (new_node->attributes & IW_MIX_ATTR_STEREO)
			new_node->right = MAKELONG((right << 9),0);
		}
	    }
	    if (new_node->subtype == SWITCH) {
		new_node->left = (DWORD)left;
	    }
	    new_node->path1.id = parent_id;
	}
	else if (new_node->type == JUNCTION) {
	    if (new_node->subtype == MUX) {
		new_node->left = left_value;
		new_node->path1.id = parent_id;
	    }
	    else if (new_node->subtype == TEE) {
		tmp = mixer_nodes;
		while (tmp) {
		    if (tmp->id == id && tmp->subtype == TEE) {
			break;
		    }
		    tmp = tmp->next;
		}
		if (tmp) {
		    tmp->path2.id = parent_id;
		    GlobalUnlock(hnode);
		    GlobalFree(hnode);
		    new_node = 0;
		}
		else
		    new_node->path1.id = parent_id;
	    }
	    else
		new_node->path1.id = parent_id;
	}
	else
	    new_node->path1.id = parent_id;
	if (new_node) {
	    tmp = mixer_nodes;
	    if (tmp) {
		while (tmp->next) {
		    tmp = tmp->next;
		}
		new_node->next = tmp->next;
		tmp->next = new_node;
		new_node->prev = tmp;
	    }
	    else {
		new_node->next = mixer_nodes;
		mixer_nodes = new_node;
		new_node->prev = 0;
	    }
	}
    }
    else
	rc = IW_NO_MEMORY;
    return(rc);
}

/****************************************************************************

FUNCTION DEFINITION:
iw_add_weighted_path - kernel callback to report feedback paths

DESCRIPTION:
iw_add_weighted_path is called by the kernel when a tee junction in the
tree of mixer nodes feeds back to a mixer or a multiplexer.  Thus sndoe
must always be a tee junction and dnode must be a mixer or a multiplexer.

RETURNS: int - IW_OK

*/
int iw_add_weighted_path(int snode, int dnode)
{
    LPMIXERNODE tmp;

    tmp = mixer_nodes;
    while (tmp) {
	if (tmp->id == snode) {
	    if (tmp->path1.id == dnode)
		tmp->weight1 = 1;
	    if (tmp->path2.id == dnode)
		tmp->weight2 = 1;
	}
	tmp = tmp->next;
    }
    return(IW_OK);
}

static LPMIXERNODE find_child(LPMIXERNODE node)
{
    LPMIXERNODE tmp, rc = 0;

    tmp = mixer_nodes;
    while (tmp) {
	if (tmp->path1.parent == node) {
	    rc = tmp;
	    break;
	}
	tmp = tmp->next;
    }
    return(rc);
}

static int find_path(LPMIXERNODE start, LPMIXERNODE fin, BOOL check_feedback)
{
    static enum {forward, backward, none}
    direction = none;
    static int use_feedback;
    LPMIXERNODE tmp;
    int rc = 0;

    if (start && fin) {
	if (direction == none) {
	    use_feedback = 0;
	    if (start->type == OUTPUT) {
		direction = backward;
		if (check_feedback)
		    use_feedback = 1;
	    }
	    else
		direction = forward;
	}
	if (direction != none) {
	    switch (start->type) {
		case OUTPUT:
		    if (fin->id == start->id) {
			rc = 1;
			direction = none;
		    }
		    if (direction == forward) {
			direction = none;
		    }
		    else if (direction == backward)
			rc = find_path(find_child(start), fin, NULL);
		    break;
		case INPUT:
		    if (fin->id == start->id) {
			rc = 1;
			direction = none;
		    }
		    if (direction == backward) {
			direction = none;
		    }
		    else if (direction == forward)
			rc = find_path(start->path1.parent, fin, NULL);
		    break;
		case CONTROL:
		case JUNCTION:
		    if (start->subtype == MUX) {
			if (direction == forward) {
			    if (fin->id == start->id) {
				direction = none;
				rc = 1;
			    }
			    else {
				if (fin->type == OUTPUT) {
				    tmp = find_node(start->path1.parent->id, -1, 0);
				    if (!(tmp->attributes & IW_MIX_ATTR_INTERMEDIATE))
					rc = find_path(start->path1.parent, fin, NULL);
				}
				else
				    direction = none;
			    }
			}
			else {
			    if (fin->id == start->id) {
				rc = 1;
			    }
			    direction = none;
			}
		    }
		    else if (start->subtype == MIXER) {
			if (fin->type != OUTPUT)
			    direction = none;
			else {
			    if (direction == forward)
				rc = find_path(start->path1.parent, fin, NULL);
			    else
				direction = none;
			}
		    }
		    else if (start->subtype == TEE) {
			if (direction == forward) {
					/* follow path 1 */
			    if (start->weight1 == 0)
				rc = find_path(start->path1.parent, fin, NULL);
			    if (!rc) {
				if (start->weight2 == 0)
				    rc = find_path(start->path2.parent, fin, NULL);
			    }
			}
			else if (direction == backward) {
			    if (use_feedback) {
				if (start->weight1 == 1) {
				    direction = forward;
				    rc = find_path(start->path1.parent, fin, NULL);
				}
				if (start->weight2 == 1 && rc == 0) {
				    direction = forward;
				    rc = find_path(start->path2.parent, fin, NULL);
				}
			    }
			    if (rc == 0) {
				direction = backward;
				rc = find_path(find_child(start), fin, NULL);
			    }
			}
		    }
		    else {
			if (fin->id == start->id) {
			    rc = 1;
			    direction = none;
			}
			if (direction == forward)
			    rc = find_path(start->path1.parent, fin, NULL);
			else if (direction == backward)
			    rc = find_path(find_child(start), fin, NULL);
		    }
		    break;
		default:
		    direction = none;
		    break;
	    }
	}
    }
    return (rc);
}

LPMIXERNODE find_node(short id, iw_mixer_node_type type, short ordinal)
{
    LPMIXERNODE rc, tmp;
    int conditions = 0, tests;

    rc = 0;
    if (id)
	conditions++;
    if (type >= 0)
	conditions++;
    if (ordinal > 0)
	conditions++;
    tmp = mixer_nodes;
    while (tmp) {
	tests = conditions;
	if (id) {
	    if (tmp->id == id)
		tests--;
	}
	if (type >= 0) {
	    if (tmp->type == type)
		tests--;
	}
	if (ordinal > 0) {
	    if (tmp->ordinal == ordinal)
		tests--;
	}
	if (tests == 0) {
	    rc = tmp;
	    break;
	}
	tmp = tmp->next;
    }
}

//#define REVEAL_BUILD
void setup_input(int i, LPMIXERNODE input, LPMIXERNODE output, BOOL check_feedback)
{
    HGLOBAL hInput, hCtrl;
    LPMIXERINPUT lpmxl;
    LPMIXERNODE tmp, junk;
    LPMIXERCONTROL mxctl;


    if (find_path(input, output, check_feedback)) {
#ifdef REVEAL_BUILD
	if (input->attributes & IW_MIX_ATTR_MASTER) {
#endif
	outputs[i].mxl.cConnections++;
	if (outputs[i].inputs) {
	    hInput = GlobalReAlloc(hInput, outputs[i].mxl.cConnections * sizeof(MIXERINPUT), GHND);
	    if (hInput) {
		outputs[i].inputs = (LPMIXERINPUT)GlobalLock(hInput);
	    }
	}
	else {
	    hInput = GlobalAlloc(GHND, sizeof(MIXERINPUT));
	    if (hInput) {
		outputs[i].inputs = (LPMIXERINPUT)GlobalLock(hInput);
	    }
	}
	lpmxl = &(outputs[i].inputs[outputs[i].mxl.cConnections - 1]);
	lpmxl->mxl.cbStruct = sizeof(MIXERLINE);
	lpmxl->mxl.dwDestination = i;
	lpmxl->mxl.dwSource = outputs[i].mxl.cConnections - 1;
	lpmxl->mxl.dwLineID = MAKELONG(output->id, input->id);
	lpmxl->mxl.fdwLine = MIXERLINE_LINEF_SOURCE|MIXERLINE_LINEF_ACTIVE;
	lpmxl->mxl.cConnections = 0;
	lstrcpy(lpmxl->mxl.szName, input->description);
	lstrcpy(lpmxl->mxl.szShortName, input->name);
	lpmxl->mxl.Target.dwType = MIXERLINE_TARGETTYPE_UNDEFINED;
	if (input->attributes & IW_MIX_ATTR_MASTER) {
	    switch (input->subtype) {
		case ADC:
		    lpmxl->mxl.dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_ANALOG;
		    break;
		case LINE:
		    lpmxl->mxl.dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_LINE;
		    break;
		case MIC:
		    lpmxl->mxl.dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE;
		    break;
		case AUX:
		    lpmxl->mxl.dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_AUXILIARY;
		    break;
		case EFFECTS:
		    lpmxl->mxl.dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_PCSPEAKER;
		    break;
		case SYNTH:
		    lpmxl->mxl.dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER;
		    lpmxl->mxl.fdwLine = MIXERLINE_LINEF_SOURCE;
		    break;
		case DAC:
		    lpmxl->mxl.dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT;
		    lpmxl->mxl.fdwLine = MIXERLINE_LINEF_SOURCE;
		    break;
		default:
		    lpmxl->mxl.dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED;
		    break;
	    }
	}
	else
	    lpmxl->mxl.dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED;
	if (input->attributes & (IW_MIX_ATTR_STEREO & ~IW_MIX_ATTR_LOCKLR)) {
	    lpmxl->mxl.cChannels = 2;
	}
	else
	    lpmxl->mxl.cChannels = 1;

				/* Find the controls for this input */
	lpmxl->mxlc.cbStruct = sizeof(MIXERLINECONTROLS);
	lpmxl->mxlc.dwLineID = lpmxl->mxl.dwLineID;
	tmp = mixer_nodes;
	while (tmp) {
	    if (tmp->type == CONTROL) {
		junk = input;
		if (find_path(input, tmp, check_feedback) && find_path(tmp, output, NULL)) {
		    lpmxl->mxl.cControls++;
		    lpmxl->mxlc.cControls++;
		    lpmxl->mxlc.cbmxctrl = sizeof(MIXERCONTROL);
		    if (lpmxl->mxlc.pamxctrl) {
			hCtrl = GlobalReAlloc(hCtrl, lpmxl->mxl.cControls * sizeof(MIXERCONTROL), GHND);
			if (hCtrl) {
			    lpmxl->mxlc.pamxctrl = (LPMIXERCONTROL)GlobalLock(hCtrl);
			}
		    }
		    else {
			hCtrl = GlobalAlloc(GHND, sizeof(MIXERCONTROL));
			if (hCtrl) {
			    lpmxl->mxlc.pamxctrl = (LPMIXERCONTROL)GlobalLock(hCtrl);
			}
		    }
		    mxctl = &(lpmxl->mxlc.pamxctrl[lpmxl->mxl.cControls - 1]);
		    mxctl->cbStruct = sizeof(MIXERCONTROL);
		    mxctl->dwControlID = tmp->id;
		    mxctl->cMultipleItems = 0;
		    lstrcpy(mxctl->szName, tmp->description);
		    lstrcpy(mxctl->szShortName, tmp->name);
		    switch (tmp->subtype) {
			case LEVEL:
			    mxctl->dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
			    mxctl->Bounds.dwMinimum = 0;
			    mxctl->Bounds.dwMaximum = 0xFF00;
			    mxctl->Metrics.cSteps = 127;
			    break;
			case SWITCH:
			    mxctl->dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE;
			    mxctl->fdwControl |= MIXERCONTROL_CONTROLF_UNIFORM;
			    break;
			case DUMMY:
			    mxctl->dwControlType = MIXERCONTROL_CONTROLTYPE_UNSIGNED;
			    mxctl->Bounds.dwMinimum = 0;
			    mxctl->Bounds.dwMaximum = 0xFF00;
			    mxctl->Metrics.cSteps = 127;
			    break;
			default:
			    break;
		    }
		    if (tmp->attributes & IW_MIX_ATTR_LOCKLR) {
			mxctl->fdwControl |= MIXERCONTROL_CONTROLF_UNIFORM;
		    }
		}
	    }
	    tmp = tmp->next;
	}
    }
#ifdef REVEAL_BUILD
    }
#endif
}

void SetupMixer(void)
{
    volatile LPMIXERNODE tmp, tmp2, tmp3;
    HGLOBAL hnode;
    LPMIXERINPUT lpmxl;
    LPMIXERCONTROL mxctl;
    int i;

    if (outputs == 0) {
	num_outputs = 0;
	iw_build_mixer();

	/* First thing to do is resolve the parent id's into pointers */
	tmp = mixer_nodes;
	while (tmp) {
	    if (tmp->subtype == TEE) {
		if (tmp->path1.id) {
		    tmp2 = mixer_nodes;
		    while (tmp2) {
			if (tmp2->id == tmp->path1.id) {
			    tmp->path1.parent = tmp2;
			    break;
			}
			tmp2 = tmp2->next;
		    }
		}
		if (tmp->path2.id) {
		    tmp2 = mixer_nodes;
		    while (tmp2) {
			if (tmp2->id == tmp->path2.id) {
			    tmp->path2.parent = tmp2;
			    break;
			}
			tmp2 = tmp2->next;
		    }
		}
	    }
	    else {
		if (tmp->path1.id) {
		    tmp2 = mixer_nodes;
		    while (tmp2) {
			if (tmp2->id == tmp->path1.id) {
			    tmp->path1.parent = tmp2;
			    break;
			}
			tmp2 = tmp2->next;
		    }
		}
	    }
	    tmp = tmp->next;
	}
	hnode = GlobalAlloc(GHND, sizeof(MIXEROUTPUT) * num_outputs);
	if (hnode) {
	    outputs = (LPMIXEROUTPUT)GlobalLock(hnode);
	}
	if (outputs) {
	    for (i=0; i<num_outputs; i++) {
		tmp = find_node(NULL, OUTPUT, i+1);
			/* First fill in the MIXERLINE structure */
		if (tmp) {
		    outputs[i].mxl.cbStruct = sizeof(MIXERLINE);
		    outputs[i].mxl.dwDestination = i;
		    outputs[i].mxl.dwSource = 0;
		    outputs[i].mxl.dwLineID = tmp->id;
		    outputs[i].mxl.fdwLine = MIXERLINE_LINEF_ACTIVE;
		    switch (tmp->subtype) {
			case ADC:
			    outputs[i].mxl.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_WAVEIN;
			    outputs[i].mxl.fdwLine = 0;
			    break;
			case LINE:
			    if (tmp->attributes & IW_MIX_ATTR_MASTER)
				outputs[i].mxl.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
			    else
				outputs[i].mxl.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_MONITOR;
			    break;
			default:
			    outputs[i].mxl.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_UNDEFINED;
			    break;
		    }
		    if (tmp->attributes & (IW_MIX_ATTR_STEREO & ~IW_MIX_ATTR_LOCKLR)) {
			outputs[i].mxl.cChannels = 2;
		    }
		    else
			outputs[i].mxl.cChannels = 1;
		    outputs[i].mxl.Target.dwType = MIXERLINE_TARGETTYPE_UNDEFINED;
		    lstrcpy(outputs[i].mxl.szName, tmp->description);
		    lstrcpy(outputs[i].mxl.szShortName, tmp->name);
				
				/* Find the controls for this output */
		    outputs[i].mxlc.cbStruct = sizeof(MIXERLINECONTROLS);
		    outputs[i].mxlc.dwLineID = outputs[i].mxl.dwLineID;
		    tmp2 = mixer_nodes;
		    while (tmp2) {
			if (tmp2->type == CONTROL ||
				(tmp2->type == JUNCTION && tmp2->subtype != TEE)) {
			    if (find_path(tmp, tmp2, NULL)) {
				outputs[i].mxl.cControls++;
				outputs[i].mxlc.cControls++;
				outputs[i].mxlc.cbmxctrl = sizeof(MIXERCONTROL);
				if (outputs[i].mxlc.pamxctrl) {
				    hnode = GlobalReAlloc(hnode, outputs[i].mxl.cControls * sizeof(MIXERCONTROL), GHND);
				    if (hnode) {
					outputs[i].mxlc.pamxctrl = (LPMIXERCONTROL)GlobalLock(hnode);
				    }
				}
				else {
				    hnode = GlobalAlloc(GHND, sizeof(MIXERCONTROL));
				    if (hnode) {
					outputs[i].mxlc.pamxctrl = (LPMIXERCONTROL)GlobalLock(hnode);
				    }
				}
				mxctl = &(outputs[i].mxlc.pamxctrl[outputs[i].mxl.cControls - 1]);
				mxctl->cbStruct = sizeof(MIXERCONTROL);
				mxctl->dwControlID = tmp2->id;
				mxctl->cMultipleItems = 0;
				lstrcpy(mxctl->szName, tmp2->description);
				lstrcpy(mxctl->szShortName, tmp2->name);
				switch (tmp2->subtype) {
				    case LEVEL:
					mxctl->dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
					mxctl->Bounds.dwMinimum = 0;
					mxctl->Bounds.dwMaximum = 0xFF00;
					mxctl->Metrics.cSteps = 127;
					break;
				    case SWITCH:
					mxctl->dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE;
					mxctl->fdwControl |= MIXERCONTROL_CONTROLF_UNIFORM;
					break;
				    case MIXER:
					mxctl->dwControlType = MIXERCONTROL_CONTROLTYPE_MIXER;
					break;
				    case MUX:
					mxctl->dwControlType = MIXERCONTROL_CONTROLTYPE_MUX;
					mxctl->fdwControl |= MIXERCONTROL_CONTROLF_MULTIPLE;
					mxctl->cMultipleItems = tmp2->range_max + 1;
					break;
				    default:
					break;
				}
				if (tmp2->attributes & IW_MIX_ATTR_LOCKLR) {
				    mxctl->fdwControl |= MIXERCONTROL_CONTROLF_UNIFORM;
				}
			    }
			}
			tmp2 = tmp2->next;
		    }

		    /* Find the inputs for this output */
		    tmp2 = mixer_nodes;
		    while (tmp2) {
			if (tmp2->type == INPUT) {
			    setup_input(i, tmp2, tmp, NULL);
			}
			tmp2 = tmp2->next;
		    }

		    /* Find any feedback paths for this output */
		    tmp2 = mixer_nodes;
		    while (tmp2) {
			if (tmp2->type == OUTPUT && tmp != tmp2) {
			    setup_input(i, tmp2, tmp, 1);
			}
			tmp2 = tmp2->next;
		    }
		}
	    }
	}
    }
}
