/*=============================================================================
	CAChannelMapper.cpp
	
	$Log: CAChannelMapper.cpp,v $
	Revision 1.3  2005/01/12 22:01:17  luke
	gcc 4.0 fixes (code reviewed by asynth)
	
	Revision 1.2  2004/09/30 21:07:42  jcm10
	flat includes
	
	Revision 1.1  2004/01/14 00:08:09  dwyatt
	moved from Source/Tests/AudioFileUtility/Utility
	
	Revision 1.2  2003/08/23 04:53:24  dwyatt
	don't spew matrix mixer dumps
	
	Revision 1.1  2003/08/04 23:40:00  dwyatt
	initial checkin
	
	created Mon Jul 28 2003, Doug Wyatt
	Copyright (c) 2003 Apple Computer, Inc.  All Rights Reserved

	$NoKeywords: $
=============================================================================*/

#include "CAChannelMapper.h"
#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)
	#include <AudioToolbox/AudioToolbox.h>
#else
	#include <AudioToolbox.h>
#endif

static void DefaultChannelLayout(CAAudioChannelLayout &layout, UInt32 nchannels)
{
	// we really can't do a sensible downmix without valid source/destination channel layouts
	if (nchannels == 1)
		layout = CAAudioChannelLayout(kAudioChannelLayoutTag_Mono);
	else if (nchannels == 2)
		layout = CAAudioChannelLayout(kAudioChannelLayoutTag_Stereo);
}

CAChannelMapper::CAChannelMapper(	const CAStreamBasicDescription &srcFormat, 
									const CAStreamBasicDescription &destFormat,
									const CAAudioChannelLayout *	inSrcLayout,
									const CAAudioChannelLayout *	inDestLayout) :
	mSrcNChannels(srcFormat.mChannelsPerFrame),
	mDestNChannels(destFormat.mChannelsPerFrame)
{
	if (inSrcLayout && inSrcLayout->IsValid())
		mSrcLayout = *inSrcLayout;
	else
		DefaultChannelLayout(mSrcLayout, srcFormat.mChannelsPerFrame);

	if (inDestLayout && inDestLayout->IsValid())
		mDestLayout = *inDestLayout;
	else
		DefaultChannelLayout(mDestLayout, destFormat.mChannelsPerFrame);
}

CAChannelMapper::~CAChannelMapper()
{
	if (mMatrixMixer)
		CloseComponent(mMatrixMixer);
}

OSStatus	CAChannelMapper::OpenMixer(double sampleRate)
{
	CAComponent comp(kAudioUnitType_Mixer, kAudioUnitSubType_MatrixMixer, kAudioUnitManufacturer_Apple);
	OSStatus err;
	
	err = CAAudioUnit::Open(comp, mMatrixMixer);
	if (err) return err;

	CAStreamBasicDescription fmt;
	fmt.mSampleRate = sampleRate;
	UInt32 nbuses = 1;

	err = mMatrixMixer.SetProperty(kAudioUnitProperty_BusCount, kAudioUnitScope_Input, 0, &nbuses, sizeof(UInt32));
	if (err) return err;
	err = mMatrixMixer.SetProperty(kAudioUnitProperty_BusCount, kAudioUnitScope_Output, 0, &nbuses, sizeof(UInt32));
	if (err) return err;

	fmt.SetCanonical(mSrcNChannels, false);
	err = mMatrixMixer.SetFormat(kAudioUnitScope_Input, 0, fmt);
	if (err) return err;

	fmt.SetCanonical(mDestNChannels, false);
	err = mMatrixMixer.SetFormat(kAudioUnitScope_Output, 0, fmt);
	if (err) return err;
	
	// set render callback
	AURenderCallbackStruct input;
	input.inputProc = MixerInputProc;
	input.inputProcRefCon = this;
	err = mMatrixMixer.SetProperty(
								kAudioUnitProperty_SetRenderCallback, 
								kAudioUnitScope_Global,
								0,
								&input, 
								sizeof(input));
	
	err = mMatrixMixer.Initialize();
	if (err) return err;
	
	return ResetMixer();
}

OSStatus	CAChannelMapper::ResetMixer()
{
	int nin = mSrcNChannels, nout = mDestNChannels;
	int i, j;

	// set global, input and output volumes
	mMatrixMixer.SetParameter(kMatrixMixerParam_Volume, kAudioUnitScope_Global, 0xFFFFFFFF, 1.);
	for (i = 0; i < nout; ++i) {
		mMatrixMixer.SetParameter(kMatrixMixerParam_Volume, kAudioUnitScope_Global, 0xFFFF0000 | i, 1.);
	}
	for (i = 0; i < nin; ++i) {
		mMatrixMixer.SetParameter(kMatrixMixerParam_Volume, kAudioUnitScope_Global, (i<<16) | 0xFFFF, 1.);
	}
	// set crosspoint volumes
	for (i = 0; i < nin; ++i) {
		for (j = 0; j < nout; ++j) {
			mMatrixMixer.SetParameter(kMatrixMixerParam_Volume, kAudioUnitScope_Global, (i<<16) | j, 0.);
		}
	}
	return noErr;
}

OSStatus	CAChannelMapper::ConfigureDownmix()
{
	OSStatus err = ResetMixer();
	if (err)
		return err;
	
	const AudioChannelLayout *layouts[] = { &mSrcLayout.Layout(), &mDestLayout.Layout() };
	UInt32 propSize;
	err = AudioFormatGetPropertyInfo(kAudioFormatProperty_MatrixMixMap, sizeof(layouts), layouts, &propSize);
	if (err)
		return err;
	
	void *mixmap = malloc(propSize);
	err = AudioFormatGetProperty(kAudioFormatProperty_MatrixMixMap, sizeof(layouts), layouts, &propSize, mixmap);
	if (!err) {
		mMatrixMixer.SetParameter(kMatrixMixerParam_Volume, kAudioUnitScope_Global, 0xFFFFFFFF, 1.);
		int nin = mSrcNChannels, nout = mDestNChannels;
		int i, j;
		// set the crosspoint volumes
		Float32 *val = (Float32 *)mixmap;
		for (i = 0; i < nin; ++i) {
			for (j = 0; j < nout; ++j) {
				mMatrixMixer.SetParameter(kMatrixMixerParam_Volume, kAudioUnitScope_Global, (i<<16) | j, *val++);
			}
		}
	}
	free(mixmap);
	return noErr;
}

OSStatus	CAChannelMapper::ConnectChannelToChannel(UInt32 inChannel, UInt32 outChannel)
{
	return mMatrixMixer.SetParameter(kMatrixMixerParam_Volume, kAudioUnitScope_Global,
								(inChannel << 16) | outChannel, 1.);
}

OSStatus	CAChannelMapper::Mix(const AudioBufferList *src, AudioBufferList *dest, UInt32 nFrames)
{
	mMixInputBufferList = src;
	AudioUnitRenderActionFlags flags = 0;
	AudioTimeStamp ts;
	ts.mFlags = 0;
	return AudioUnitRender(mMatrixMixer.AU(), &flags, &ts, 0, nFrames, dest);
}

OSStatus	CAChannelMapper::MixerInputProc(
						void *						inRefCon,
						AudioUnitRenderActionFlags *ioActionFlags,
						const AudioTimeStamp *		inTimeStamp,
						UInt32 						inBusNumber,
						UInt32 						inNumberFrames,
						AudioBufferList *			ioData)
{
	CAChannelMapper *This = static_cast<CAChannelMapper *>(inRefCon);
	const AudioBufferList *mixInputBufferList = This->mMixInputBufferList;
	UInt32 copySize = sizeof(UInt32) + (mixInputBufferList->mNumberBuffers * sizeof(AudioBuffer));
	memcpy(ioData, mixInputBufferList, copySize);
	
	return noErr;
}
