/*=============================================================================
	CAStreamBasicDescription.cpp
 
	$Log: CAStreamBasicDescription.cpp,v $
	Revision 1.32  2005/01/12 21:55:59  luke
	gcc 4.0 warning fixes
	
	Revision 1.31  2004/11/16 23:27:05  asynth
	alac wasn't printing a newline if flags were zero. also print out frames per packet.
	
	Revision 1.30  2004/11/11 19:51:08  asynth
	add Brad's support for lossless
	
	Revision 1.29  2004/09/17 22:17:59  dwyatt
	[3806361] ignore non-interleaved bit when comparing formats with 0 or 1 channels
	
	Revision 1.28  2004/08/19 19:08:24  jcm10
	make it build and work on a stock Panther system
	
	Revision 1.27  2004/06/25 01:14:20  jcm10
	fix up NormalizeLinearPCMFormat to support the IsNonMixable flag
	
	Revision 1.26  2004/06/07 18:55:11  jcm10
	non-mixable should always sort after mixable for LPCM
	
	Revision 1.25  2004/05/28 23:01:34  jcm10
	make it compile without kAudioFormatFlagIsNonMixable
	
	Revision 1.24  2004/05/27 19:48:45  jcm10
	support kAudioFormatFlagIsNonMixable
	
	Revision 1.23  2004/05/13 22:49:22  dwyatt
	tweak debug print methods for case of 0 channels in PCM
	
	Revision 1.22  2004/04/09 22:44:45  dwyatt
	the great floating-point-equality-test inquisition
	
	Revision 1.21  2003/11/20 22:56:53  dwyatt
	__COREAUDIO_USE_FLAT_INCLUDES__
	
	Revision 1.20  2003/09/17 23:40:53  asynth
	bare minimum sanity check.
	
	Revision 1.19  2003/09/17 23:05:01  asynth
	bare minimum sanity check.
	
	Revision 1.18  2003/09/17 02:08:11  asynth
	3369425 don't limit sample rate
	
	Revision 1.17  2003/07/22 00:11:34  ealdrich
	Make Doug's changes build on Windows.
	
	Revision 1.16  2003/07/17 23:54:15  dwyatt
	need CoreServices.h for Endian.h
	
	Revision 1.15  2003/07/17 23:49:30  dwyatt
	endian vs. formatID display
	
	Revision 1.14  2003/07/09 23:38:40  jcm10
	ignore the endian flag for comparisons with 8 bit linear PCM data
	
	Revision 1.13  2003/07/08 21:41:36  dwyatt
	cleanup PrintFormat more
	
	Revision 1.12  2003/05/16 19:26:42  dwyatt
	cleanup PrintFormat a little
	
	Revision 1.11  2003/04/25 18:40:31  asynth
	add ASBD SanityCheck
	
	Revision 1.10  2003/04/24 21:49:02  jcm10
	move CAShowStreamDescription to CAShow.cpp
	
	Revision 1.9  2003/04/24 02:31:57  asynth
	more don't care matching
	
	Revision 1.8  2003/04/24 02:10:02  asynth
	do don't care matching of alignment bit.
	
	Revision 1.7  2003/04/23 22:50:31  dwyatt
	oops, forgot one of the constructors
	
	Revision 1.6  2003/04/23 22:33:13  dwyatt
	more fallout of class merge
	
	Revision 1.5  2003/04/23 22:09:04  dwyatt
	CAAudioStreamBasicDescription merged into CAStreamBasicDescription
	
	Revision 1.4  2003/03/17 23:40:34  dwyatt
	improve display of packed formats
	
	Revision 1.3  2003/03/12 04:48:17  bills
	Remove persistence calls to CAPersistence
	
	Revision 1.2  2003/03/11 20:57:30  bills
	first pass at save/restore state
	
	Revision 1.1  2003/03/11 00:30:43  bills
	add .cpp implementation of print calls
	
	Created by William Stewart on Sat Mar 08 2003.
	Copyright (c) 2003 Apple Computer, Inc.  All Rights Reserved

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

#include "CAConditionalMacros.h"

#include "CAStreamBasicDescription.h"
#include "CAMath.h"

#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)
	#include <CoreServices/CoreServices.h>
#else
	#include <Endian.h>
#endif

const AudioStreamBasicDescription	CAStreamBasicDescription::sEmpty = { 0.0, 0, 0, 0, 0, 0, 0, 0, 0 };

CAStreamBasicDescription::CAStreamBasicDescription(double inSampleRate,		UInt32 inFormatID,
									UInt32 inBytesPerPacket,	UInt32 inFramesPerPacket,
									UInt32 inBytesPerFrame,		UInt32 inChannelsPerFrame,
									UInt32 inBitsPerChannel,	UInt32 inFormatFlags)
{
	mSampleRate = inSampleRate;
	mFormatID = inFormatID;
	mBytesPerPacket = inBytesPerPacket;
	mFramesPerPacket = inFramesPerPacket;
	mBytesPerFrame = inBytesPerFrame;
	mChannelsPerFrame = inChannelsPerFrame;
	mBitsPerChannel = inBitsPerChannel;
	mFormatFlags = inFormatFlags;
}

void CAStreamBasicDescription::PrintFormat(FILE *f, const char *indent, const char *name) const
{
	fprintf(f, "%s%s ", indent, name);
	char formatID[5];
	*(UInt32 *)formatID = EndianU32_NtoB(mFormatID);
	formatID[4] = '\0';
	fprintf(f, "%2ld ch, %6.0f Hz, '%-4.4s' (0x%08lX) ",		
				NumberChannels(), mSampleRate, formatID,
				mFormatFlags);
	if (mFormatID == kAudioFormatLinearPCM) {
		bool isInt = !(mFormatFlags & kLinearPCMFormatFlagIsFloat);
		int wordSize = SampleWordSize();
		const char *endian = (wordSize > 1) ? 
			((mFormatFlags & kLinearPCMFormatFlagIsBigEndian) ? " big-endian" : " little-endian" ) : "";
		const char *sign = isInt ? 
			((mFormatFlags & kLinearPCMFormatFlagIsSignedInteger) ? " signed" : " unsigned") : "";
		const char *floatInt = isInt ? "integer" : "float";
		char packed[32];
		if (wordSize > 0 && PackednessIsSignificant()) {
			if (mFormatFlags & kLinearPCMFormatFlagIsPacked)
				sprintf(packed, "packed in %d bytes", wordSize);
			else
				sprintf(packed, "unpacked in %d bytes", wordSize);
		} else
			packed[0] = '\0';
		const char *align = (wordSize > 0 && AlignmentIsSignificant()) ?
			((mFormatFlags & kLinearPCMFormatFlagIsAlignedHigh) ? " high-aligned" : " low-aligned") : "";
		const char *deinter = (mFormatFlags & kAudioFormatFlagIsNonInterleaved) ? ", deinterleaved" : "";
		const char *commaSpace = (packed[0]!='\0') || (align[0]!='\0') ? ", " : "";
		
		fprintf(f, "%ld-bit%s%s %s%s%s%s%s\n",
			mBitsPerChannel, endian, sign, floatInt, 
			commaSpace, packed, align, deinter);
	} else if (mFormatID == kAudioFormatAppleLossless) {
		int sourceBits = 0;
		switch (mFormatFlags)
		{
			case kAppleLosslessFormatFlag_16BitSourceData:
				sourceBits = 16;
				break;
    		case kAppleLosslessFormatFlag_20BitSourceData:
    			sourceBits = 20;
    			break;
    		case kAppleLosslessFormatFlag_24BitSourceData:
    			sourceBits = 24;
    			break;
    		case kAppleLosslessFormatFlag_32BitSourceData:
    			sourceBits = 32;
    			break;
		}
		if (sourceBits)
			fprintf(f, "from %d-bit source, ", sourceBits);
		else
			fprintf(f, "from UNKNOWN source bit depth, ");
			
		fprintf(f, "%ld frames/packet\n", mFramesPerPacket);
	}
	else
		fprintf(f, "%ld bits/channel, %ld bytes/packet, %ld frames/packet, %ld bytes/frame\n", 
			mBitsPerChannel, mBytesPerPacket, mFramesPerPacket, mBytesPerFrame);
}

void	CAStreamBasicDescription::NormalizeLinearPCMFormat(AudioStreamBasicDescription& ioDescription)
{
	//  the only thing that changes is to make mixable linear PCM into the canonical linear PCM format
	if((ioDescription.mFormatID == kAudioFormatLinearPCM) && ((ioDescription.mFormatFlags & kIsNonMixableFlag) == 0))
	{
		//  the canonical linear PCM format is 32 bit native endian floats
		ioDescription.mFormatFlags = kAudioFormatFlagsNativeFloatPacked;
		ioDescription.mBytesPerPacket = sizeof(Float32) * ioDescription.mChannelsPerFrame;
		ioDescription.mFramesPerPacket = 1;
		ioDescription.mBytesPerFrame = sizeof(Float32) * ioDescription.mChannelsPerFrame;
		ioDescription.mBitsPerChannel = 8 * sizeof(Float32);
	}
}

void	CAStreamBasicDescription::ResetFormat(AudioStreamBasicDescription& ioDescription)
{
	ioDescription.mSampleRate = 0;
	ioDescription.mFormatID = 0;
	ioDescription.mBytesPerPacket = 0;
	ioDescription.mFramesPerPacket = 0;
	ioDescription.mBytesPerFrame = 0;
	ioDescription.mChannelsPerFrame = 0;
	ioDescription.mBitsPerChannel = 0;
	ioDescription.mFormatFlags = 0;
}

void	CAStreamBasicDescription::FillOutFormat(AudioStreamBasicDescription& ioDescription, const AudioStreamBasicDescription& inTemplateDescription)
{
	if(fiszero(ioDescription.mSampleRate))
	{
		ioDescription.mSampleRate = inTemplateDescription.mSampleRate;
	}
	if(ioDescription.mFormatID == 0)
	{
		ioDescription.mFormatID = inTemplateDescription.mFormatID;
	}
	if(ioDescription.mFormatFlags == 0)
	{
		ioDescription.mFormatFlags = inTemplateDescription.mFormatFlags;
	}
	if(ioDescription.mBytesPerPacket == 0)
	{
		ioDescription.mBytesPerPacket = inTemplateDescription.mBytesPerPacket;
	}
	if(ioDescription.mFramesPerPacket == 0)
	{
		ioDescription.mFramesPerPacket = inTemplateDescription.mFramesPerPacket;
	}
	if(ioDescription.mBytesPerFrame == 0)
	{
		ioDescription.mBytesPerFrame = inTemplateDescription.mBytesPerFrame;
	}
	if(ioDescription.mChannelsPerFrame == 0)
	{
		ioDescription.mChannelsPerFrame = inTemplateDescription.mChannelsPerFrame;
	}
	if(ioDescription.mBitsPerChannel == 0)
	{
		ioDescription.mBitsPerChannel = inTemplateDescription.mBitsPerChannel;
	}
}

void	CAStreamBasicDescription::GetSimpleName(const AudioStreamBasicDescription& inDescription, char* outName, bool inAbbreviate)
{
	switch(inDescription.mFormatID)
	{
		case kAudioFormatLinearPCM:
			{
				const char* theEndianString = NULL;
				if((inDescription.mFormatFlags & kAudioFormatFlagIsBigEndian) != 0)
				{
					#if	TARGET_RT_LITTLE_ENDIAN
						theEndianString = "Big Endian";
					#endif
				}
				else
				{
					#if	TARGET_RT_BIG_ENDIAN
						theEndianString = "Little Endian";
					#endif
				}
				
				const char* theKindString = NULL;
				if((inDescription.mFormatFlags & kAudioFormatFlagIsFloat) != 0)
				{
					theKindString = (inAbbreviate ? "Float" : "Floating Point");
				}
				else if((inDescription.mFormatFlags & kAudioFormatFlagIsSignedInteger) != 0)
				{
					theKindString = (inAbbreviate ? "SInt" : "Signed Integer");
				}
				else
				{
					theKindString = (inAbbreviate ? "UInt" : "Unsigned Integer");
				}
				
				const char* thePackingString = NULL;
				if((inDescription.mFormatFlags & kAudioFormatFlagIsPacked) == 0)
				{
					if((inDescription.mFormatFlags & kAudioFormatFlagIsAlignedHigh) != 0)
					{
						thePackingString = "High";
					}
					else
					{
						thePackingString = "Low";
					}
				}
				
				const char* theMixabilityString = NULL;
				if((inDescription.mFormatFlags & kIsNonMixableFlag) == 0)
				{
					theMixabilityString = "Mixable";
				}
				else
				{
					theMixabilityString = "Unmixable";
				}
				
				if(inAbbreviate)
				{
					if(theEndianString != NULL)
					{
						if(thePackingString != NULL)
						{
							sprintf(outName, "%s %d Ch %s %s %s%d/%s%d", theMixabilityString, (int)inDescription.mChannelsPerFrame, theEndianString, thePackingString, theKindString, (int)inDescription.mBitsPerChannel, theKindString, (int)(inDescription.mBytesPerFrame / inDescription.mChannelsPerFrame) * 8);
						}
						else
						{
							sprintf(outName, "%s %d Ch %s %s%d", theMixabilityString, (int)inDescription.mChannelsPerFrame, theEndianString, theKindString, (int)inDescription.mBitsPerChannel);
						}
					}
					else
					{
						if(thePackingString != NULL)
						{
							sprintf(outName, "%s %d Ch %s %s%d/%s%d", theMixabilityString, (int)inDescription.mChannelsPerFrame, thePackingString, theKindString, (int)inDescription.mBitsPerChannel, theKindString, (int)((inDescription.mBytesPerFrame / inDescription.mChannelsPerFrame) * 8));
						}
						else
						{
							sprintf(outName, "%s %d Ch %s%d", theMixabilityString, (int)inDescription.mChannelsPerFrame, theKindString, (int)inDescription.mBitsPerChannel);
						}
					}
				}
				else
				{
					if(theEndianString != NULL)
					{
						if(thePackingString != NULL)
						{
							sprintf(outName, "%s %d Channel %d Bit %s %s Aligned %s in %d Bits", theMixabilityString, (int)inDescription.mChannelsPerFrame, (int)inDescription.mBitsPerChannel, theEndianString, theKindString, thePackingString, (int)(inDescription.mBytesPerFrame / inDescription.mChannelsPerFrame) * 8);
						}
						else
						{
							sprintf(outName, "%s %d Channel %d Bit %s %s", theMixabilityString, (int)inDescription.mChannelsPerFrame, (int)inDescription.mBitsPerChannel, theEndianString, theKindString);
						}
					}
					else
					{
						if(thePackingString != NULL)
						{
							sprintf(outName, "%s %d Channel %d Bit %s Aligned %s in %d Bits", theMixabilityString, (int)inDescription.mChannelsPerFrame, (int)inDescription.mBitsPerChannel, theKindString, thePackingString, (int)(inDescription.mBytesPerFrame / inDescription.mChannelsPerFrame) * 8);
						}
						else
						{
							sprintf(outName, "%s %d Channel %d Bit %s", theMixabilityString, (int)inDescription.mChannelsPerFrame, (int)inDescription.mBitsPerChannel, theKindString);
						}
					}
				}
			}
			break;
		
		case kAudioFormatAC3:
			strcpy(outName, "AC-3");
			break;
		
		case kAudioFormat60958AC3:
			strcpy(outName, "AC-3 for SPDIF");
			break;
		
		default:
			{
				char* the4CCString = (char*)&inDescription.mFormatID;
				outName[0] = the4CCString[0];
				outName[1] = the4CCString[1];
				outName[2] = the4CCString[2];
				outName[3] = the4CCString[3];
				outName[4] = 0;
			}
			break;
	};
}

#if CoreAudio_Debug
#include "CALogMacros.h"

void	CAStreamBasicDescription::PrintToLog(const AudioStreamBasicDescription& inDesc)
{
	PrintFloat		("  Sample Rate:        ", inDesc.mSampleRate);
	Print4CharCode	("  Format ID:          ", inDesc.mFormatID);
	PrintHex		("  Format Flags:       ", inDesc.mFormatFlags);
	PrintInt		("  Bytes per Packet:   ", inDesc.mBytesPerPacket);
	PrintInt		("  Frames per Packet:  ", inDesc.mFramesPerPacket);
	PrintInt		("  Bytes per Frame:    ", inDesc.mBytesPerFrame);
	PrintInt		("  Channels per Frame: ", inDesc.mChannelsPerFrame);
	PrintInt		("  Bits per Channel:   ", inDesc.mBitsPerChannel);
}
#endif

bool	operator<(const AudioStreamBasicDescription& x, const AudioStreamBasicDescription& y)
{
	bool theAnswer = false;
	bool isDone = false;
	
	//	note that if either side is 0, that field is skipped
	
	//	format ID is the first order sort
	if((!isDone) && ((x.mFormatID != 0) && (y.mFormatID != 0)))
	{
		if(x.mFormatID != y.mFormatID)
		{
			//	formats are sorted numerically except that linear
			//	PCM is always first
			if(x.mFormatID == kAudioFormatLinearPCM)
			{
				theAnswer = true;
			}
			else if(y.mFormatID == kAudioFormatLinearPCM)
			{
				theAnswer = false;
			}
			else
			{
				theAnswer = x.mFormatID < y.mFormatID;
			}
			isDone = true;
		}
	}
	
	
	//  mixable is always better than non-mixable for linear PCM and should be the second order sort item
	if((!isDone) && ((x.mFormatID == kAudioFormatLinearPCM) && (y.mFormatID == kAudioFormatLinearPCM)))
	{
		if(((x.mFormatFlags & kIsNonMixableFlag) == 0) && ((y.mFormatFlags & kIsNonMixableFlag) != 0))
		{
			theAnswer = true;
			isDone = true;
		}
		else if(((x.mFormatFlags & kIsNonMixableFlag) != 0) && ((y.mFormatFlags & kIsNonMixableFlag) == 0))
		{
			theAnswer = false;
			isDone = true;
		}
	}
	
	//	floating point vs integer for linear PCM only
	if((!isDone) && ((x.mFormatID == kAudioFormatLinearPCM) && (y.mFormatID == kAudioFormatLinearPCM)))
	{
		if((x.mFormatFlags & kAudioFormatFlagIsFloat) != (y.mFormatFlags & kAudioFormatFlagIsFloat))
		{
			//	floating point is better than integer
			theAnswer = y.mFormatFlags & kAudioFormatFlagIsFloat;
			isDone = true;
		}
	}
	
	//	bit depth
	if((!isDone) && ((x.mBitsPerChannel != 0) && (y.mBitsPerChannel != 0)))
	{
		if(x.mBitsPerChannel != y.mBitsPerChannel)
		{
			//	deeper bit depths are higher quality
			theAnswer = x.mBitsPerChannel < y.mBitsPerChannel;
			isDone = true;
		}
	}
	
	//	sample rate
	if((!isDone) && fnonzero(x.mSampleRate) && fnonzero(y.mSampleRate))
	{
		if(fnotequal(x.mSampleRate, y.mSampleRate))
		{
			//	higher sample rates are higher quality
			theAnswer = x.mSampleRate < y.mSampleRate;
			isDone = true;
		}
	}
	
	//	number of channels
	if((!isDone) && ((x.mChannelsPerFrame != 0) && (y.mChannelsPerFrame != 0)))
	{
		if(x.mChannelsPerFrame != y.mChannelsPerFrame)
		{
			//	more channels is higher quality
			theAnswer = x.mChannelsPerFrame < y.mChannelsPerFrame;
			isDone = true;
		}
	}
	
	return theAnswer;
}

static bool MatchFormatFlags(const AudioStreamBasicDescription& x, const AudioStreamBasicDescription& y)
{
	UInt32 xFlags = x.mFormatFlags;
	UInt32 yFlags = y.mFormatFlags;
	
	// match wildcards
	if (x.mFormatID == 0 || y.mFormatID == 0 || xFlags == 0 || yFlags == 0) 
		return true;
	
	if (x.mFormatID == kAudioFormatLinearPCM)
	{		 		
		// knock off the all clear flag
		xFlags = xFlags & ~kAudioFormatFlagsAreAllClear;
		yFlags = yFlags & ~kAudioFormatFlagsAreAllClear;
	
		// if both kAudioFormatFlagIsPacked bits are set, then we don't care about the kAudioFormatFlagIsAlignedHigh bit.
		if (xFlags & yFlags & kAudioFormatFlagIsPacked) {
			xFlags = xFlags & ~kAudioFormatFlagIsAlignedHigh;
			yFlags = yFlags & ~kAudioFormatFlagIsAlignedHigh;
		}
		
		// if both kAudioFormatFlagIsFloat bits are set, then we don't care about the kAudioFormatFlagIsSignedInteger bit.
		if (xFlags & yFlags & kAudioFormatFlagIsFloat) {
			xFlags = xFlags & ~kAudioFormatFlagIsSignedInteger;
			yFlags = yFlags & ~kAudioFormatFlagIsSignedInteger;
		}
		
		//	if the bit depth is 8 bits or less and the format is packed, we don't care about endianness
		if((x.mBitsPerChannel <= 8) && ((xFlags & kAudioFormatFlagIsPacked) == kAudioFormatFlagIsPacked))
		{
			xFlags = xFlags & ~kAudioFormatFlagIsBigEndian;
		}
		if((y.mBitsPerChannel <= 8) && ((yFlags & kAudioFormatFlagIsPacked) == kAudioFormatFlagIsPacked))
		{
			yFlags = yFlags & ~kAudioFormatFlagIsBigEndian;
		}
		
		//	if the number of channels is 0 or 1, we don't care about non-interleavedness
		if (x.mChannelsPerFrame <= 1 && y.mChannelsPerFrame <= 1) {
			xFlags &= ~kLinearPCMFormatFlagIsNonInterleaved;
			yFlags &= ~kLinearPCMFormatFlagIsNonInterleaved;
		}
	}
	return xFlags == yFlags;
}

bool	operator==(const AudioStreamBasicDescription& x, const AudioStreamBasicDescription& y)
{
	//	the semantics for equality are:
	//		1) Values must match exactly
	//		2) wildcard's are ignored in the comparison
	
#define MATCH(name) ((x.name) == 0 || (y.name) == 0 || (x.name) == (y.name))
	
	return 
			//	check the sample rate
		(fiszero(x.mSampleRate) || fiszero(y.mSampleRate) || fequal(x.mSampleRate, y.mSampleRate))
		
			//	check the format ids
		&& MATCH(mFormatID)
		
			//	check the format flags
		&& MatchFormatFlags(x, y)  
			
			//	check the bytes per packet
		&& MATCH(mBytesPerPacket) 
		
			//	check the frames per packet
		&& MATCH(mFramesPerPacket) 
		
			//	check the bytes per frame
		&& MATCH(mBytesPerFrame) 
		
			//	check the channels per frame
		&& MATCH(mChannelsPerFrame) 
		
			//	check the channels per frame
		&& MATCH(mBitsPerChannel) ;
}

bool SanityCheck(const AudioStreamBasicDescription& x)
{
	return (x.mSampleRate >= 0.);
}
