// AudioMixer.cpp
/*

	By David Shipman, 2002

*/

#include "AudioMixer.h"
#include "IOStructures.h"

#include <media/RealtimeAlloc.h>
#include <media/Buffer.h>
#include <media/TimeSource.h>
#include <media/ParameterWeb.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define MALLOC_DEBUG = 1;

//

mixer_input::mixer_input(media_input &input)
{

	fInput = input;

	fEventOffset = 0;
	fDataSize = 0;
	
	enabled = false;
	
	int channelCount = fInput.format.u.raw_audio.channel_count;
	
	fGainDisplay = new float[channelCount];
	fGainScale = new float[channelCount];
	
	for (int i = 0; i < channelCount; i++)
	{
		fGainScale[i] = 1.0;
		fGainDisplay[i] = 0.0;
	}
		
	fMuteValue = 0;
	fPanValue = 0;

	fGainDisplayLastChange = 0;
	fMuteValueLastChange = 0;
	fPanValueLastChange = 0;

}

mixer_input::~mixer_input()
{
	if (fGainDisplay)
		delete fGainDisplay;
	if (fGainScale)
		delete fGainScale;
	
	if (fData)
		rtm_free(fData);
	
	printf("mixer_input deleted\n");

}


// AudioMixer support classes

bool				
AudioMixer::IsValidDest( media_destination dest)
{

	int32 input_cookie = 0;
	media_input input;
	
	while (GetNextInput(&input_cookie, &input) == B_OK)
	{
		
		if (dest == input.destination)
			return true;
			
	}

	return false;
	
}

bool
AudioMixer::IsValidSource( media_source source)
{

	int32 output_cookie = 0;
	media_output output;
	
	while (GetNextOutput(&output_cookie, &output) == B_OK)
	{

		if (source == output.source)
			return true;
			
	}
	
	return false;
	
}

char *
StringForFormat(const media_format format)
{

	char *name = new char[B_MEDIA_NAME_LENGTH];

	sprintf(name, "%g kHz ", format.u.raw_audio.frame_rate / 1000.0);

	switch (format.u.raw_audio.format)
	{
		case media_raw_audio_format::B_AUDIO_FLOAT:
		
			strncat(name, "float", 5);
			break;
			
		case media_raw_audio_format::B_AUDIO_SHORT:
		
			strncat(name, "16bit", 5);
			break;

	}
	
	return name;

}

/*
// Interface
*/

BParameterWeb *
AudioMixer::BuildParameterWeb()
{

	BParameterWeb *web = new BParameterWeb();

	BParameterGroup *top = web->MakeGroup(Name()); // top level group
	
	BParameterGroup *masterGroup = top->MakeGroup("Master"); // 'Master' group split
	BParameterGroup *channelsGroup = top->MakeGroup("Channels"); // 'Channels' group split
	
	BParameterGroup *headerGroup = masterGroup->MakeGroup("Master"); // The 'header group' for this split... 
	
	headerGroup->MakeNullParameter(100, B_MEDIA_RAW_AUDIO, "Master Gain", B_WEB_BUFFER_OUTPUT);
	headerGroup->MakeNullParameter(100, B_MEDIA_RAW_AUDIO, "44.1kHz 16bit", B_GENERIC); // set our initial display like R5's mixer... 
	headerGroup->MakeDiscreteParameter(2, B_MEDIA_RAW_AUDIO, "Mute", B_MUTE);
	headerGroup->MakeContinuousParameter(3, B_MEDIA_RAW_AUDIO, "Gain", B_MASTER_GAIN, "dB", -60.0, 18.0, 1.0)->SetChannelCount(2);
	headerGroup->MakeNullParameter(100, B_MEDIA_RAW_AUDIO, "To Output", B_WEB_BUFFER_OUTPUT);
	
	(headerGroup->ParameterAt(0))->AddOutput(headerGroup->ParameterAt(1));
	(headerGroup->ParameterAt(1))->AddOutput(headerGroup->ParameterAt(2));
	(headerGroup->ParameterAt(2))->AddOutput(headerGroup->ParameterAt(3));
	(headerGroup->ParameterAt(3))->AddOutput(headerGroup->ParameterAt(4));
		
	int inputsCount = fMixerInputs.CountItems();
	
	for (int which_input = 1; which_input < inputsCount; which_input ++)
	{
	
		mixer_input *mixerInput = (mixer_input *)fMixerInputs.ItemAt(which_input);
	
		char *groupNumLabel = new char[4];
	
		sprintf(groupNumLabel, "%d", which_input);

		BParameterGroup *group = channelsGroup->MakeGroup(groupNumLabel);
	
		int baseID = (which_input) * 65536;
	
		char *formatString = StringForFormat(mixerInput->fInput.format);
		
		group->MakeNullParameter(baseID + 100, B_MEDIA_RAW_AUDIO, mixerInput->fInput.name, B_WEB_BUFFER_OUTPUT); // name of attached producer
		group->MakeNullParameter(baseID + 300, B_MEDIA_RAW_AUDIO, formatString, B_GENERIC); // display information about format
		group->MakeDiscreteParameter(baseID + 3, B_MEDIA_RAW_AUDIO, "Mute", B_MUTE);
		group->MakeContinuousParameter(baseID + 4, B_MEDIA_RAW_AUDIO, "Gain", B_GAIN, "dB", -60.0, 18.0, 1.0)
			->SetChannelCount(mixerInput->fInput.format.u.raw_audio.channel_count);

		bool panning = false;
	
		if (fOutput.destination != media_destination::null)
		{
			if (mixerInput->fInput.format.u.raw_audio.channel_count < fOutput.format.u.raw_audio.channel_count)
			{
			
				group->MakeContinuousParameter(baseID + 1, B_MEDIA_RAW_AUDIO, "Pan", B_BALANCE, "L R", -1.0, 1.0, 0.02);
				(group->ParameterAt(0))->AddOutput(group->ParameterAt(1));	
				(group->ParameterAt(1))->AddOutput(group->ParameterAt(4));
				(group->ParameterAt(4))->AddOutput(group->ParameterAt(2));
				(group->ParameterAt(2))->AddOutput(group->ParameterAt(3));
				
				panning = true;
					
			}
		}
		
		if (!panning)
		{
			(group->ParameterAt(0))->AddOutput(group->ParameterAt(1));
			(group->ParameterAt(1))->AddOutput(group->ParameterAt(2));
			(group->ParameterAt(2))->AddOutput(group->ParameterAt(3));
		}
		
	}

	return web;		
}

// 'structors... 

AudioMixer::AudioMixer(BMediaAddOn *addOn)
		:	BMediaNode("Audio Mixer"),
			BBufferConsumer(B_MEDIA_RAW_AUDIO),
			BBufferProducer(B_MEDIA_RAW_AUDIO),
			BControllable(),
			BMediaEventLooper(),
			fAddOn(addOn),
			fWeb(NULL),
			fLatency(1),
			fInternalLatency(1),
			fStartTime(0), fNextEventTime(0),
			fFramesSent(0),
			fOutputEnabled(true),
			fBufferGroup(NULL),
			fNextFreeID(1)
			
			
{

	BMediaNode::AddNodeKind(B_SYSTEM_MIXER);
	
	// set up the preferred output format
	
	fPrefOutputFormat.type = B_MEDIA_RAW_AUDIO;
	
	fPrefOutputFormat.u.raw_audio.format = media_raw_audio_format::wildcard.format; // B_AUDIO_FLOAT
	fPrefOutputFormat.u.raw_audio.frame_rate = media_raw_audio_format::wildcard.frame_rate; 
	fPrefOutputFormat.u.raw_audio.byte_order = (B_HOST_IS_BENDIAN) ? B_MEDIA_BIG_ENDIAN : B_MEDIA_LITTLE_ENDIAN;

	fPrefOutputFormat.u.raw_audio.buffer_size = media_raw_audio_format::wildcard.buffer_size; //2048
	fPrefOutputFormat.u.raw_audio.channel_count = 2; //media_raw_audio_format::wildcard.channel_count;
	
	// set up the preferred input format
	
	fPrefInputFormat.type = B_MEDIA_RAW_AUDIO;
	
	fPrefInputFormat.u.raw_audio.format = media_raw_audio_format::B_AUDIO_FLOAT;
	fPrefInputFormat.u.raw_audio.frame_rate = 96000; //wildcard?
	fPrefInputFormat.u.raw_audio.byte_order = (B_HOST_IS_BENDIAN) ? B_MEDIA_BIG_ENDIAN : B_MEDIA_LITTLE_ENDIAN;

	fPrefInputFormat.u.raw_audio.buffer_size = 1024;
	fPrefInputFormat.u.raw_audio.channel_count = 2; //media_raw_audio_format::wildcard.channel_count;

	//fInput.source = media_source::null;

	// init i/o
	
	fOutput.destination = media_destination::null;
	fOutput.format = fPrefOutputFormat;
	fOutput.source.port = ControlPort();
	fOutput.source.id = 0;
	
	strcpy(fOutput.name, "Audio Mix");

	fMasterGainScale = new float[2];
	fMasterGainDisplay = new float[2];
	
	fMasterGainDisplay[0] = 0.0;
	fMasterGainDisplay[1] = 0.0;
	
	fMasterGainScale[0] = 1.0;
	fMasterGainScale[1] = 1.0;
	
	fMasterGainDisplayLastChange = TimeSource()->Now();
	
}

AudioMixer::~AudioMixer()
{
	
	BMediaEventLooper::Quit();
	SetParameterWeb(NULL); 
	fWeb = NULL;	

	// any other cleanup goes here

}

//
// BMediaNode methods
//

BMediaAddOn * 
AudioMixer::AddOn(int32 *internal_id) const
{
	if(fAddOn) 
	{
		*internal_id = 0;
		return fAddOn;
	}
	else 
		return NULL;
}

//
// BControllable methods
//

status_t 
AudioMixer::GetParameterValue(int32 id, bigtime_t *last_change, 
								void *value, size_t *ioSize)
{

	int idGroup = int(id/65536.0);
	
	if (idGroup == 0)
	{
		if (id == 3)
		{						
			float *valueOut = (float *)value;
			valueOut[0] = fMasterGainDisplay[0];
			valueOut[1] = fMasterGainDisplay[1];
			
			*last_change = fMasterGainDisplayLastChange;
			*ioSize = 8;
		
			return B_OK;
		}
		else if (id == 2)
		{
					
			int *valueOut = (int *)value;
			*valueOut = fMasterMuteValue;
							
			*last_change = fMasterMuteValueLastChange;
			*ioSize = 4;
		
			return B_OK;
		}
	}
	else
	{
	
		mixer_input *mixerInput = (mixer_input *)fMixerInputs.ItemAt(idGroup);
		
		int idBase = idGroup * 65536;
		
		if (id == idBase + 4)
		{
			float *valueOut = (float *)value;
			valueOut[0] = mixerInput->fGainDisplay[0];
			valueOut[1] = mixerInput->fGainDisplay[1];
			
			*last_change = mixerInput->fGainDisplayLastChange;
			*ioSize = 8;
		
			return B_OK;
		}
		else if (id == idBase + 3)
		{
			int *valueOut = (int *)value;
			*valueOut = mixerInput->fMuteValue;
							
			*last_change = mixerInput->fMuteValueLastChange;
			*ioSize = 4;
		
			return B_OK;
		}		
	
	}			
			
	return B_ERROR;

}

void 
AudioMixer::SetParameterValue(int32 id, bigtime_t when, 
								const void *value, size_t ioSize)
{
	
	int idGroup = int(id/65536.0);
	
	if (idGroup == 0)
	{
		if (id == 3)
		{			
					
			float *inValue = (float *)value;
			if (ioSize == 4) {
				fMasterGainDisplay[0] = inValue[0];
				fMasterGainDisplay[1] = inValue[0];
			}
			else if (ioSize == 8) {
				fMasterGainDisplay[0] = inValue[0];
				fMasterGainDisplay[1] = inValue[1];
			}
			
			fMasterGainDisplayLastChange = when;
			
			BroadcastNewParameterValue(fMasterGainDisplayLastChange,
				id, fMasterGainDisplay, ioSize);
		
			fMasterGainScale[0] = pow(2, (fMasterGainDisplay[0] / 6));
			fMasterGainScale[1] = pow(2, (fMasterGainDisplay[1] / 6));
			
		}
		else if (id == 2)
		{
			
			int *inValue = (int *)value;
			
			fMasterMuteValue = *inValue;
							
			fMasterMuteValueLastChange = when;
			
			BroadcastNewParameterValue(fMasterMuteValueLastChange,
				id, &fMasterMuteValue, ioSize);
		
		}
	}
	else
	{
	
		mixer_input *mixerInput = (mixer_input *)fMixerInputs.ItemAt(idGroup);
		
		int idBase = idGroup * 65536;
		
		if (id == idBase + 4)
		{
		
			float *inValue = (float *)value;
			if (ioSize == 4) {
				mixerInput->fGainDisplay[0] = inValue[0];
				mixerInput->fGainDisplay[1] = inValue[0];
			}
			else if (ioSize == 8) {
				mixerInput->fGainDisplay[0] = inValue[0];
				mixerInput->fGainDisplay[1] = inValue[1];
			}
			
			mixerInput->fGainDisplayLastChange = when;
			
			BroadcastNewParameterValue(mixerInput->fGainDisplayLastChange,
				id, mixerInput->fGainDisplay, ioSize);
		
			mixerInput->fGainScale[0] = pow(2, (mixerInput->fGainDisplay[0] / 6));
			mixerInput->fGainScale[1] = pow(2, (mixerInput->fGainDisplay[1] / 6));
			
		}
		else if (id == idBase + 3)
		{
			int *inValue = (int *)value;
			
			mixerInput->fMuteValue = *inValue;
							
			mixerInput->fMuteValueLastChange = when;
			
			BroadcastNewParameterValue(mixerInput->fMuteValueLastChange,
				id, &mixerInput->fMuteValue, ioSize);
		}		
	
	}			

}

//
// BBufferConsumer methods
//

status_t
AudioMixer::HandleMessage( int32 message, const void *data, size_t size)
{
	
	// since we're using a mediaeventlooper, there shouldn't be any messages
	
	printf("Received message to BBufferConsumer - this should not be!\n");
	
	return B_ERROR;

}

status_t
AudioMixer::AcceptFormat(const media_destination &dest, media_format *format)
{

	// we accept any raw audio 

	if (IsValidDest(dest))
	{	
		if ((format->type != B_MEDIA_UNKNOWN_TYPE) && (format->type != B_MEDIA_RAW_AUDIO))
			return B_MEDIA_BAD_FORMAT;
		else
			return B_OK;
	}
	else
		return B_MEDIA_BAD_DESTINATION;	
	
}

status_t
AudioMixer::GetNextInput(int32 *cookie, media_input *out_input)
{

	if ((*cookie < fMixerInputs.CountItems()) && (*cookie >= 0))
	{
		mixer_input *channel = (mixer_input *)fMixerInputs.ItemAt(*cookie);
		*out_input = channel->fInput;
		*cookie += 1;
		return B_OK;
	}
	else
		return B_BAD_INDEX;
	
}

void
AudioMixer::DisposeInputCookie(int32 cookie)
{

	// nothing yet

}

void
AudioMixer::BufferReceived(BBuffer *buffer)
{

	if (buffer)
	{
		if (buffer->Header()->type == B_MEDIA_PARAMETERS) 
		{
			printf("Control Buffer Received\n");
			ApplyParameterData(buffer->Data(), buffer->SizeUsed());
		}
		else 
		{
		
			media_header *hdr = buffer->Header();
					
			// check input
			
			mixer_input *channel;
			
			int inputcount = fMixerInputs.CountItems();
			
			for (int i = 0; i < inputcount; i++)
			{
			
				channel = (mixer_input *)fMixerInputs.ItemAt(i);
				
				if (channel->fInput.destination.id == hdr->destination)
				{
				
					i = inputcount;
							
					bigtime_t now = TimeSource()->Now();
					bigtime_t perf_time = hdr->start_time;				
					bigtime_t how_early = perf_time - fLatency - now;
		
					if ((RunMode() != B_OFFLINE) && 
					(RunMode() != B_RECORDING) &&
					(how_early < 0))
					{
						printf("Received buffer %d usecs late from %s\n", -how_early, channel->fInput.name);
						NotifyLateProducer(channel->fInput.source, -how_early, perf_time);
					}
					else
					{
					
						size_t sample_size = channel->fInput.format.u.raw_audio.format & media_raw_audio_format::B_AUDIO_SIZE_MASK;
		
						// calculate total byte offset for writing to the ringbuffer
						// this takes account of the Event offset, as well as the buffer's start time			
		
						size_t total_offset = int(channel->fEventOffset + ((((perf_time - fNextEventTime) / 1000000) * 
							channel->fInput.format.u.raw_audio.frame_rate) * 
							sample_size * channel->fInput.format.u.raw_audio.channel_count)) % int(channel->fDataSize);
		
						char *indata = (char *)buffer->Data();
														
						if (buffer->SizeUsed() > (channel->fDataSize - total_offset))
						{
							memcpy(channel->fData + total_offset, indata, channel->fDataSize - total_offset);
							memcpy(channel->fData, indata + (channel->fDataSize - total_offset), buffer->SizeUsed() - (channel->fDataSize - total_offset));	
						} 
						else
							memcpy(channel->fData + total_offset, indata, buffer->SizeUsed());
												
					}
				
				}	
			
			if ((B_OFFLINE == RunMode()) && (B_DATA_AVAILABLE == channel->fProducerDataStatus))
			{
				RequestAdditionalBuffer(channel->fInput.source, buffer);
			}
			
		}
				
	}
	
	buffer->Recycle();
	
	}
}

void
AudioMixer::ProducerDataStatus( const media_destination &for_whom, 
								int32 status, bigtime_t at_performance_time)
{
	
	if (IsValidDest(for_whom))
	{
		media_timed_event event(at_performance_time, BTimedEventQueue::B_DATA_STATUS,
			(void *)(&for_whom), BTimedEventQueue::B_NO_CLEANUP, status, 0, NULL);
		EventQueue()->AddEvent(event);
		
		// FIX_THIS
		// the for_whom destination is not being sent correctly - verify in HandleEvent loop
		
	}

}

status_t
AudioMixer::GetLatencyFor( const media_destination &for_whom, bigtime_t *out_latency, 
							media_node_id *out_timesource)
{

	if (! IsValidDest(for_whom))
		return B_MEDIA_BAD_DESTINATION;
		
	// return our event latency - this includes our internal + downstream 
	// latency, but _not_ the scheduling latency
	
	*out_latency = EventLatency();
	*out_timesource = TimeSource()->ID();

	return B_OK;

}

status_t
AudioMixer::Connected( const media_source &producer, const media_destination &where,
						const media_format &with_format, media_input *out_input)
{
	if (! IsValidDest(where))
		return B_MEDIA_BAD_DESTINATION;
	
	char *name = out_input->name;
	mixer_input *mixerInput;
	
	// We use fNextFreeID to keep track of the next available input
	// 1-4 are our initial "free" inputs
	
	if (fNextFreeID < 5)
	{
	
		mixerInput = (mixer_input *)fMixerInputs.ItemAt(fNextFreeID);

		mixerInput->fInput.format = with_format;
		mixerInput->fInput.source = producer;

		strcpy(mixerInput->fInput.name, name);

	}
	else
	{
	
		media_input *newInput = new media_input;
	
		newInput->format = with_format;
		newInput->source = producer;
		newInput->destination.port = ControlPort();
		newInput->destination.id = fNextFreeID;
		newInput->node = Node();
		
		strcpy(newInput->name, name);
		
		mixerInput = new mixer_input(*newInput);
		
		fMixerInputs.AddItem(mixerInput, fNextFreeID);	
	
	}

	// Recalculate the next available input
	
	bool idSet = 0;
	int testID = fNextFreeID;
	int inputsCount = fMixerInputs.CountItems();
	
	for (int testID = fNextFreeID; testID < inputsCount; testID ++)
	{
		
		mixer_input *testInput = (mixer_input *)fMixerInputs.ItemAt(testID);
		
		if (testID < 5)
		{
			if (testInput->fInput.source == media_source::null) {
				fNextFreeID = testID;
				idSet = 1;
				testID = inputsCount;
			}
		}
		else
		{
			if (testInput->fInput.destination.id > testID)
			{
				fNextFreeID = testID;
				idSet = 1;
				testID = inputsCount;		
			}	
		
		}
	
	}
	if (!idSet)
		fNextFreeID = inputsCount;
	
	// Set up the ringbuffer in which to write incoming data
	
	mixerInput->fData = (char *)rtm_alloc(NULL, 65536); // CHANGE_THIS - we only need to allocate space for as many
	mixerInput->fDataSize = 65536;			// input buffers as would fill an output - plus one
		
	memset(mixerInput->fData, 0, mixerInput->fDataSize);
		
	mixerInput->enabled = true;

	*out_input = mixerInput->fInput;
	
	fWeb = BuildParameterWeb();
	SetParameterWeb(fWeb);
				
	return B_OK;

}

void
AudioMixer::Disconnected( const media_source &producer, const media_destination &where)
{
	
	// One of our inputs has been disconnected
	// If its one of our initial "Free" inputs, we need to set it back to its original state 
	// Otherwise, we can delete the input
	
	mixer_input *mixerInput;

	int inputcount = fMixerInputs.CountItems();
		
	for (int i = 1; i < inputcount; i++)
	{
		mixerInput = (mixer_input *)fMixerInputs.ItemAt(i);
		if (mixerInput->fInput.destination == where)
		{	
			if (mixerInput->fInput.source != media_source::null) // input already disconnected?
			{
				if ((mixerInput->fInput.destination.id > 0) && (mixerInput->fInput.destination.id < 5))
				{
					mixerInput->fInput.source = media_source::null;
					strcpy(mixerInput->fInput.name, "Free");
					mixerInput->fInput.format = fPrefInputFormat;
									
					rtm_free(mixerInput->fData);
					
					mixerInput->fData = NULL;
					
					mixerInput->fEventOffset = 0;
					mixerInput->fDataSize = 0;
					
					delete mixerInput->fGainDisplay, mixerInput->fGainScale;
					
					mixerInput->fMuteValue = 0;
					mixerInput->fPanValue = 0.0;
					
					mixerInput->enabled = false;
					
					int channelCount = mixerInput->fInput.format.u.raw_audio.channel_count;
	
					mixerInput->fGainDisplay = new float[channelCount];
					mixerInput->fGainScale = new float[channelCount];
	
					for (int i = 0; i < channelCount; i++)
					{
						mixerInput->fGainScale[i] = 1.0;
						mixerInput->fGainDisplay[i] = 0.0;
					}
					
				}
				else if (mixerInput->fInput.destination.id > 4)
				{
				
					delete mixerInput;
					fMixerInputs.RemoveItem(mixerInput);
					
				}
			}
			
			fWeb = BuildParameterWeb();
			SetParameterWeb(fWeb);
			
			if (mixerInput->fInput.destination.id < fNextFreeID)
				fNextFreeID = mixerInput->fInput.destination.id;
			
			i = inputcount;
		
		}
		else printf("Disconnection requested for input with no source\n");
		
	}					

}

status_t
AudioMixer::FormatChanged( const media_source &producer, const media_destination &consumer, 
							int32 change_tag, const media_format &format)
{

	// Format changed, we need to update our local info
	// and change our ringbuffer size if needs be
	// Not supported (yet)

	printf("Format changed\n");
	return B_ERROR;
	
}

//
// BBufferProducer methods
//

status_t
AudioMixer::FormatSuggestionRequested( media_type type,	int32 quality, media_format *format)
{

	// downstream consumers are asking for a preferred format
	// if suitable, return our previously constructed format def

	if (!format)
		return B_BAD_VALUE;

	*format = fPrefOutputFormat;
	
	if (type == B_MEDIA_UNKNOWN_TYPE)
		type = B_MEDIA_RAW_AUDIO;
	
	if (type != B_MEDIA_RAW_AUDIO) 
		return B_MEDIA_BAD_FORMAT;
	else return B_OK;

}

status_t
AudioMixer::FormatProposal( const media_source &output,	media_format *format)
{

	// check to see if format is valid for the specified output
	
	if (! IsValidSource(output))
		return B_MEDIA_BAD_SOURCE;
	
	if ((format->type != B_MEDIA_UNKNOWN_TYPE) && (format->type != B_MEDIA_RAW_AUDIO))
		return B_MEDIA_BAD_FORMAT;
	else return B_OK;

}

status_t
AudioMixer::FormatChangeRequested( const media_source &source, const media_destination &destination, 
									media_format *io_format, int32 *_deprecated_)
{
	
	// try to change the format of the specified source to io_format
	// not supported (yet)
	
	return B_ERROR;
	
}

status_t 
AudioMixer::GetNextOutput( int32 *cookie, media_output *out_output)
{

	// iterate through available outputs
	
	if (*cookie == 0)
	{
		*out_output = fOutput;
		*cookie = 1;
		return B_OK;
	}
	else return B_BAD_INDEX;
	
}

status_t 
AudioMixer::DisposeOutputCookie( int32 cookie)
{

	// we don't need to do anything here, since we haven't 
	// used the cookie for anything special... 
	
	return B_OK;

}

status_t 
AudioMixer::SetBufferGroup(const media_source &for_source, BBufferGroup *newGroup)
{

	if (! IsValidSource(for_source))
		return B_MEDIA_BAD_SOURCE;

	if (newGroup == fBufferGroup) // we're already using this buffergroup
		return B_OK;

	// Ahh, someone wants us to use a different buffer group.  At this point we delete
	// the one we are using and use the specified one instead.  If the specified group is
	// NULL, we need to recreate one ourselves, and use *that*.  Note that if we're
	// caching a BBuffer that we requested earlier, we have to Recycle() that buffer
	// *before* deleting the buffer group, otherwise we'll deadlock waiting for that
	// buffer to be recycled!
	delete fBufferGroup;		// waits for all buffers to recycle
	if (newGroup != NULL)
	{
		// we were given a valid group; just use that one from now on
		fBufferGroup = newGroup;
	}
	else
	{
		// we were passed a NULL group pointer; that means we construct
		// our own buffer group to use from now on
		AllocateBuffers();
	}


	return B_OK;
}

status_t
AudioMixer::GetLatency(bigtime_t *out_latency)
{

	// report our *total* latency:  internal plus downstream plus scheduling
	*out_latency = EventLatency() + SchedulingLatency();
	return B_OK;
	
}

status_t
AudioMixer::PrepareToConnect(const media_source &what, const media_destination &where, 
								media_format *format, media_source *out_source, char *out_name)
{
	// PrepareToConnect() is the second stage of format negotiations that happens
	// inside BMediaRoster::Connect().  At this point, the consumer's AcceptFormat()
	// method has been called, and that node has potentially changed the proposed
	// format.  It may also have left wildcards in the format.  PrepareToConnect()
	// *must* fully specialize the format before returning!

	// trying to connect something that isn't our source?
	if (! IsValidSource(what))
		return B_MEDIA_BAD_SOURCE;

	// are we already connected?
	if (fOutput.destination != media_destination::null)
		return B_MEDIA_ALREADY_CONNECTED;

	// the format may not yet be fully specialized (the consumer might have
	// passed back some wildcards).  Finish specializing it now, and return an
	// error if we don't support the requested format.
	if (format->type != B_MEDIA_RAW_AUDIO)
		return B_MEDIA_BAD_FORMAT;
		
		// CHANGE_THIS  -  we're messing around with formats, need to clean up
		// still need to check u.raw_audio.format
	
	if (format->u.raw_audio.format == media_raw_audio_format::wildcard.format)
		format->u.raw_audio.format = media_raw_audio_format::B_AUDIO_FLOAT;
	
	if ((format->u.raw_audio.format != media_raw_audio_format::B_AUDIO_FLOAT) &&  // currently support floating point
		(format->u.raw_audio.format != media_raw_audio_format::B_AUDIO_SHORT))		// and 16bit int audio
		return B_MEDIA_BAD_FORMAT;

	// all fields MUST be validated here
	// or else the consumer is prone to divide-by-zero errors

	if(format->u.raw_audio.frame_rate == media_raw_audio_format::wildcard.frame_rate)
		format->u.raw_audio.frame_rate = 44100;

	if(format->u.raw_audio.channel_count == media_raw_audio_format::wildcard.channel_count)
		format->u.raw_audio.channel_count = 2;

	if(format->u.raw_audio.byte_order == media_raw_audio_format::wildcard.byte_order)
		format->u.raw_audio.byte_order = fPrefOutputFormat.u.raw_audio.byte_order;

	if (format->u.raw_audio.buffer_size == media_raw_audio_format::wildcard.buffer_size)
		format->u.raw_audio.buffer_size = 1024;		// pick something comfortable to suggest

	
	// Now reserve the connection, and return information about it
	fOutput.destination = where;
	fOutput.format = *format;
	*out_source = fOutput.source;
	strncpy(out_name, fOutput.name, B_MEDIA_NAME_LENGTH); // strncpy?

	return B_OK;
}


void 
AudioMixer::Connect( status_t error, const media_source &source, const media_destination &dest, 
						const media_format &format, char *io_name)
{
	printf("AudioMixer::Connect\n");

	// we need to check which output dest refers to - we only have one for now
	
	if (error)
	{
		fOutput.destination = media_destination::null;
		fOutput.format = fPrefOutputFormat;
		return;
	}

	// connection is confirmed, record information and send output name
	
	fOutput.destination = dest;
	fOutput.format = format;
	strncpy(io_name, fOutput.name, B_MEDIA_NAME_LENGTH);

	// Now that we're connected, we can determine our downstream latency.
	// Do so, then make sure we get our events early enough.
	media_node_id id;
	FindLatencyFor(fOutput.destination, &fLatency, &id);

	// we need at least the length of a full output buffer's latency (I think?)

	size_t sample_size = fOutput.format.u.raw_audio.format & media_raw_audio_format::B_AUDIO_SIZE_MASK;

	size_t framesPerBuffer = (fOutput.format.u.raw_audio.buffer_size / sample_size) / fOutput.format.u.raw_audio.channel_count;
	
	//fInternalLatency = (framesPerBuffer / fOutput.format.u.raw_audio.frame_rate); // test * 1000000
	
	void *mouse = malloc(fOutput.format.u.raw_audio.buffer_size);
	
	bigtime_t latency_start = TimeSource()->RealTime();
	FillMixBuffer(mouse, fOutput.format.u.raw_audio.buffer_size);
	bigtime_t latency_end = TimeSource()->RealTime();
	
	fInternalLatency = latency_end - latency_start;
	printf("Latency set at %d usecs\n", fInternalLatency);
	
	delete mouse;
	

	// might need to tweak the latency

	SetEventLatency(fLatency + fInternalLatency);

	// reset our buffer duration, etc. to avoid later calculations
	// crashes w/ divide-by-zero when connecting to a variety of nodes... 
//	if (fOutput.format.u.raw_audio.frame_rate == media_raw_audio_format::wildcard.frame_rate)
//	{
//		fOutput.format.u.raw_audio.frame_rate = 44100;
//	}
	
	bigtime_t duration = bigtime_t(1000000) * framesPerBuffer / bigtime_t(fOutput.format.u.raw_audio.frame_rate);
	SetBufferDuration(duration);

	// Set up the buffer group for our connection, as long as nobody handed us a
	// buffer group (via SetBufferGroup()) prior to this.  That can happen, for example,
	// if the consumer calls SetOutputBuffersFor() on us from within its Connected()
	// method.
	if (!fBufferGroup) AllocateBuffers();
}


void 
AudioMixer::Disconnect(const media_source &what, const media_destination &where)
{

	printf("AudioMixer::Disconnect\n");
	// Make sure that our connection is the one being disconnected
	if ((where == fOutput.destination) && (what == fOutput.source)) // change later for multisource outputs
	{
		fOutput.destination = media_destination::null;
		fOutput.format = fPrefOutputFormat;
		delete fBufferGroup;
		fBufferGroup = NULL;
	}

}


void 
AudioMixer::LateNoticeReceived(const media_source &what, bigtime_t how_much, bigtime_t performance_time)
{

	// We've produced some late buffers... Increase Latency 
	// is the only runmode in which we can do anything about this
	
	if (what == fOutput.source)
		if (RunMode() == B_INCREASE_LATENCY)
		{
			fInternalLatency += how_much;
			SetEventLatency(fLatency + fInternalLatency);
		}
		
	printf("Late notice received. Buffer was %d usecs late\n", how_much);

}


void 
AudioMixer::EnableOutput(const media_source &what, bool enabled, int32 *_deprecated_)
{

	// right now we've only got one output... check this against the supplied
	// media_source and set its state accordingly... 

	if (what == fOutput.source)
	{
		fOutputEnabled = enabled;
	}
}


//
// BMediaEventLooper methods
//

void
AudioMixer::NodeRegistered()
{

		Run();
		
		fOutput.node = Node();
		
		
		for (int i = 0; i < 5; i++)
		{

			media_input *newInput = new media_input;
	
			newInput->format = fPrefInputFormat;
			newInput->source = media_source::null;
			newInput->destination.port = ControlPort();
			newInput->destination.id = i;
			newInput->node = Node();
			strcpy(newInput->name, "Free"); // 
			
			mixer_input *mixerInput = new mixer_input(*newInput);
			
			fMixerInputs.AddItem(mixerInput);
			
		}
		
		SetPriority(120);
		
		fWeb = BuildParameterWeb();
		
		SetParameterWeb(fWeb);

}


void 
AudioMixer::HandleEvent( const media_timed_event *event, bigtime_t lateness, 
								bool realTimeEvent = false)
{

	switch (event->type)
	{
	
		case BTimedEventQueue::B_HANDLE_BUFFER:
		{

			// check output
			if (fOutput.destination != media_destination::null && fOutputEnabled) // this is in the wrong order too
			{
					
				BBuffer *outbuffer = fBufferGroup->RequestBuffer(fOutput.format.u.raw_audio.buffer_size, BufferDuration());
				
				if (outbuffer)
				{

					FillMixBuffer(outbuffer->Data(), outbuffer->SizeAvailable());
					
					media_header *outheader = outbuffer->Header();
					outheader->type = B_MEDIA_RAW_AUDIO;
					outheader->size_used = fOutput.format.u.raw_audio.buffer_size;
					outheader->time_source = TimeSource()->ID();
					
					// if this is the first buffer, mark with the start time
					// we need this to calculate the other buffer times
					
					if (fStartTime == 0) {
						fStartTime = event->event_time;
						fNextEventTime = fStartTime;
					}

					bigtime_t stamp;
					if (RunMode() == B_RECORDING)
						stamp = event->event_time; // this is actually the same as the other modes, since we're using 
					else							// a timedevent queue and adding events at fNextEventTime
					{
						
						// we're in a live performance mode
						// use the start time we calculate at the end of the the mix loop
						// this time is based on the offset of all media produced so far, 
						// plus fStartTime, which is the recorded time of our first event

						stamp = fNextEventTime;

					}
								
					outheader->start_time = stamp;

					status_t err = SendBuffer(outbuffer, fOutput.destination);
												
					if(err != B_OK)
					{
						outbuffer->Recycle();
						printf("Could not send buffer to output : %s\n", fOutput.name);
					}
						
					}
				
				else
					printf("Failed to allocate a buffer\n");
					
				// even if we didn't get a buffer allocated, we still need to send the next event
				
				size_t sample_size = fOutput.format.u.raw_audio.format & media_raw_audio_format::B_AUDIO_SIZE_MASK;
	
				int framesperbuffer = (fOutput.format.u.raw_audio.buffer_size / (sample_size * fOutput.format.u.raw_audio.channel_count));
					
				fFramesSent += (framesperbuffer);
					
				// calculate the start time for the next event
					
				fNextEventTime = bigtime_t(fStartTime + double(fFramesSent / fOutput.format.u.raw_audio.frame_rate) * 1000000.0);
					
				media_timed_event nextBufferEvent(fNextEventTime, BTimedEventQueue::B_HANDLE_BUFFER);
				EventQueue()->AddEvent(nextBufferEvent);

			}

		
		break;
			
		}	
		
		case BTimedEventQueue::B_START:

			if (RunState() != B_STARTED)
			{
				// We want to start sending buffers now, so we set up the buffer-sending bookkeeping
				// and fire off the first "produce a buffer" event.
				fStartTime = 0;
				fFramesSent = 0;
				
				//fThread = spawn_thread(_mix_thread_, "audio mixer thread", B_REAL_TIME_PRIORITY, this);
				
				media_timed_event firstBufferEvent(event->event_time, BTimedEventQueue::B_HANDLE_BUFFER);

				// Alternatively, we could call HandleEvent() directly with this event, to avoid a trip through
				// the event queue, like this:
				//
				//		this->HandleEvent(&firstBufferEvent, 0, false);
				//
				EventQueue()->AddEvent(firstBufferEvent);
				
				//	fStartTime = event->event_time;
	
			}
		
			break;
			
		case BTimedEventQueue::B_STOP:
		{
			// stopped - don't process any more buffers, flush all buffers from eventqueue

			EventQueue()->FlushEvents(0, BTimedEventQueue::B_ALWAYS, true, BTimedEventQueue::B_HANDLE_BUFFER);
			break;
		}

		case BTimedEventQueue::B_DATA_STATUS:
		{
		
			printf("DataStatus message\n");
			
			mixer_input *mixerInput;
			
			int inputcount = fMixerInputs.CountItems();
		
			for (int i = 0; i < inputcount; i++)
			{
				mixerInput = (mixer_input *)fMixerInputs.ItemAt(i);
				if (mixerInput->fInput.destination == (media_destination &)event->pointer)
				{
				
					printf("Valid DatasStatus destination\n");
				
					mixerInput->fProducerDataStatus = event->data;
					
					if (mixerInput->fProducerDataStatus == B_DATA_AVAILABLE)
						printf("B_DATA_AVAILABLE\n");
					else if (mixerInput->fProducerDataStatus == B_DATA_NOT_AVAILABLE)
						printf("B_DATA_NOT_AVAILABLE\n");
					else if (mixerInput->fProducerDataStatus == B_PRODUCER_STOPPED)
						printf("B_PRODUCER_STOPPED\n");			
					
					i = inputcount;
					
				}
			}
			break;
		}
		
		default:
		
			break;	
	
	}

}
								
//
// AudioMixer methods
//		
		
void 
AudioMixer::AllocateBuffers()
{

	// allocate enough buffers to span our downstream latency, plus one
	size_t size = fOutput.format.u.raw_audio.buffer_size;
	int32 count = int32((fLatency / (BufferDuration() + 1)) + 1);

	fBufferGroup = new BBufferGroup(size, count);
	
}

// use this later for separate threads

int32
AudioMixer::_mix_thread_(void *data)
{
	return ((AudioMixer *)data)->MixThread();
}

int32 
AudioMixer::MixThread()
{
	while (1)
	{
		snooze(500000);
	}
	
}
