//----------------
// Hollywood API for use with the Macintosh Sound Manager 3.0
//
// This code depends upon Universal Interfaces 2.0a3 or better from Apple Computer, Inc
//
// History
//	1/8/95	Created by Steve Hales
//----------------

#include <Types.h>
#include <Memory.h>
#include <Quickdraw.h>
#include <OSEvents.h>
#include <Desk.h>
#include <Events.h>
#include <Resources.h>
#include <Windows.h>
#include <Fonts.h>
#include <TextEdit.h>
#include <Menus.h>
#include <Dialogs.h>
#include <ToolUtils.h>
#include <Retrace.h>
#include <StandardFile.h>
#include <Sound.h>
#include <SoundInput.h>
#include <AIFF.h>
#include <Gestalt.h>
#include <Errors.h>
#include <Traps.h>
#include <ConditionalMacros.h>
#include <FixMath.h>

#ifndef __MIXEDMODE__
	#include <SysEqu.h>
#else
	#include <LowMem.h>
	#define LMGetSoundActive() (* (unsigned char *) 0x27E)
#endif

#if THINK_C
#include <Think.h>
#else
	#define TRUE	true
	#define FALSE	false
#endif

#include "Hollywood.h"

#if 0
	#define DEBUGSTR(x)
#else
	#define DEBUGSTR(x)	DebugStr(x)
#endif

#define kMaxCallbackQueue	50	// total number of queued events

// Structures
struct MM_SoundVoice
{
	SndChannelPtr			theChannel;

	CustomCallbackProc		customCallback;
	short int				voiceNumber;
	long					userData;
	UnsignedFixed			lastRate;

	ExtSoundHeader		theSndBuffer;
	Boolean				voiceActive;
	Boolean				voicePaused;
	Boolean				filePlay;
	FSSpec				fileSpec;
	short int				fileRef;
};
typedef struct MM_SoundVoice	MM_SoundVoice;

// This queue is put together via an interrupt to process events at non-interrupt time
struct MM_CallbackQueue
{
	CustomCallbackProc		customCallback;
	short int				voiceNumber;
	long					userData;
	short int				filePlayRef;		// 0 = no file play to close
	Boolean				active;
};
typedef struct MM_CallbackQueue	MM_CallbackQueue;

// Variables
static long				globalA5;
static short int			maxSoundVoices = 0;			// Max voices allocated for Sound Manager 3.0
static MM_SoundVoice	*theSoundVoices = NULL;
static MM_CallbackQueue	theCallbackQueue[kMaxCallbackQueue];

static long 		*	taskPtr;
static VBLTask		*	theSoundVBPtr;
static void			(*vblankProcPtr)(void);

static long				totalRegisteredSounds;
static long				currentBlockSizeForRegisteredSounds;
static SndReference	*	registeredSounds;				// current list of registered sounds

#ifdef __MIXEDMODE__
	SndCallBackUPP	theMasterSoundCallbackProcPtr;
#else
	SndCallBackProcPtr	theMasterSoundCallbackProcPtr;
#endif


// Internal Defines
#define kSoundManagerID			'sm'	// used to fix a bug in the Sound Manager. It pre-fires the callback, so
								// you must check that its our callback. We do this by 'wasting' the param1.
								// Note that param1 is only 16 bits.

#define kMaxSoundManagerVoices	4	// total number of voices that the Sound Manager can allocate. This may change
								// as CPU performance increases

#define kRegisterSoundBlockSize	40	// inital number of sounds that can be registered without reallocating the
								// block array

// forward references
static pascal void HYP_HandleSoundDoneCallBack(SndChannelPtr pChannel, register SndCommand *pCmd);
static void SHYP_Callback(short int voiceNumber, short int what, long userData);
static OSErr HYP_PostCallbackFunction(SndChannelPtr pChannel, long userData);
static OSErr HYP_AddToCallbackQueue(MM_CallbackQueue *newQueue);

// Private functions
//
// This will return TRUE if the new sound manager is installed, otherwise FALSE

static Boolean HYP_IsNewSoundManagerInstalled(void)
{
	NumVersion	theVersion;
	void *		trap1;
	void *		trap2;
	Boolean		installed;

	installed = FALSE;
	trap1 = (void *)GetToolTrapAddress(_SoundDispatch);	/* SndSoundManagerVersion Trap */
	trap2 = (void *)GetToolTrapAddress(_Unimplemented);	/* Unimplemented Trap */
	if (trap1 != trap2)
	{
		theVersion = SndSoundManagerVersion();
		if (theVersion.majorRev >= 3)
		{
			installed = TRUE;
		}
	}
	return installed;
}


// This will return TRUE if the virtual memory manager is installed, otherwise FALSE.

static Boolean HYP_IsVirtualMemoryAvailable(void)
 {
	long	feature;

	feature = 0;
	if (Gestalt(gestaltVMAttr, &feature) == noErr)
	{
		if (feature & (1<<gestaltVMPresent))
		{
			return TRUE;
		}
	}
	return FALSE;
}

static short int HYP_GetFreeVoice(void)
{
	register short int count, freeVoice;

	freeVoice = kUseAnyVoice;
	for (count = 0; count < maxSoundVoices; count++)
	{
		if (theSoundVoices[count].voiceActive == FALSE)
		{
			freeVoice = count;
			break;
		}
	}
	return freeVoice;
}

static MM_SoundVoice * HYP_GetPrivateDataFromVoice(register short int voice)
{
	register MM_SoundVoice *pVoice;

	pVoice = NULL;
	if (theSoundVoices)
	{
		if ( (voice < maxSoundVoices) && (voice >= 0) )
		{
			if (theSoundVoices[voice].theChannel)
			{
				pVoice = &theSoundVoices[voice];
			}
		}
	}
	return pVoice;
}


static short int HYP_GetVoiceFromChannel(register SndChannelPtr pChannel)
{
	register short int count;

	count = -1;
	for (count = 0; count < maxSoundVoices; count++)
	{
		if (pChannel == theSoundVoices[count].theChannel)
		{
			break;
		}
	}
	return count;
}

// For use with Apple's Sound Manager
static pascal void HYP_FilePlayCompletionDone(SndChannelPtr pChannel)
{
	register long					saveA5;
	register MM_SoundVoice			*pVoice;
	register short int				voiceNumber;
	MM_CallbackQueue				newQueue;

#if GENERATING68K == 1
	saveA5 = SetA5(pChannel->userInfo);         /* restore previous a5 */
#else
	saveA5;
#endif
	voiceNumber = HYP_GetVoiceFromChannel(pChannel);
	pVoice = HYP_GetPrivateDataFromVoice(voiceNumber);
	if (pVoice)
	{
		if (pChannel != pVoice->theChannel)
		{
			DEBUGSTR("\pChannel doesn't match voiceNumber");
		}

		if (pVoice->customCallback)
		{
			(*pVoice->customCallback)(voiceNumber, 
									kSoundDoneNormalStop, 
									pVoice->userData);
			newQueue.customCallback = pVoice->customCallback;
			newQueue.voiceNumber = pVoice->voiceNumber;
			newQueue.userData = pVoice->userData;
			newQueue.filePlayRef = pVoice->fileRef;
			if (HYP_AddToCallbackQueue(&newQueue))
			{
				DEBUGSTR("\pQueue Full!");
			}
		}
		pVoice->voiceActive = FALSE;
	}
	/* Restore A5 for the rest of the interupt process
	*/
#if GENERATING68K == 1
	SetA5(saveA5);         /* restore previous a5 */
#endif

}


static pascal void HYP_HandleSoundDoneCallBack(SndChannelPtr pChannel, register SndCommand *pCmd)
{
	register long					saveA5;
	register MM_SoundVoice			*pVoice;
	register short int				voiceNumber;
	MM_CallbackQueue				newQueue;

	if (pCmd->param1 == kSoundManagerID)
	{
#if GENERATING68K == 1
		saveA5 = SetA5(pChannel->userInfo);         /* restore previous a5 */
#else
		saveA5;
#endif
		voiceNumber = HYP_GetVoiceFromChannel(pChannel);
		pVoice = HYP_GetPrivateDataFromVoice(voiceNumber);
		if (pVoice)
		{
			if (pChannel != pVoice->theChannel)
			{
				DEBUGSTR("\pChannel doesn't match voiceNumber");
			}

			if (pVoice->customCallback)
			{
				(*pVoice->customCallback)(voiceNumber, 
										kSoundDoneNormalStop, 
										pVoice->userData);
				newQueue.customCallback = pVoice->customCallback;
				newQueue.voiceNumber = pVoice->voiceNumber;
				newQueue.userData = pVoice->userData;
				newQueue.filePlayRef = 0;
				if (HYP_AddToCallbackQueue(&newQueue))
				{
					DEBUGSTR("\pQueue Full!");
				}
			}
			pVoice->voiceActive = FALSE;
		}
		/* Restore A5 for the rest of the interupt process
		*/
#if GENERATING68K == 1
		SetA5(saveA5);         /* restore previous a5 */
#endif
	}
}

static OSErr HYP_PostCallbackFunction(SndChannelPtr pChannel, long userData)
{
	SndCommand	theCmd;
	OSErr		theErr;

	if (pChannel)
	{
		theCmd.cmd = callBackCmd;
		theCmd.param1 = kSoundManagerID;							// used for ID. Bug in SM that a callback is
															// sometimes called at the begining of a sample
															// with the wrong information.
															// Use this to make sure that it is our callback.
		theCmd.param2 = userData;
		pChannel->userInfo = globalA5;
		theErr = SndDoCommand(pChannel, &theCmd, FALSE);
	}
	else
	{
		theErr = badChannel;
	}
	return theErr;
}


static void HYP_SetupQueues(void)
{
	short int count;

	for (count = 0; count < kMaxCallbackQueue; count++)
	{
		theCallbackQueue[count].active = FALSE;
		theCallbackQueue[count].customCallback = NULL;
	}
}

static void HYP_CleanupQueues(void)
{
}

static OSErr HYP_AddToCallbackQueue(MM_CallbackQueue *newQueue)
{
	OSErr	theErr;
	short int	count;
	Boolean	foundQueue;

//	DEBUGSTR("\pHYP_AddToCallbackQueue");
	theErr = noErr;

	foundQueue = FALSE;
	for (count = 0; count < kMaxCallbackQueue; count++)
	{
		if (theCallbackQueue[count].active == FALSE)
		{
			theCallbackQueue[count] = *newQueue;
			theCallbackQueue[count].active = TRUE;
			foundQueue = TRUE;
			break;
		}
	}
	if (foundQueue == FALSE)
	{
		theErr = qErr;
	}
	return theErr;
}

static void HYP_ProcessNextCallbackQueue(void)
{
	register MM_CallbackQueue *	theQueue;
	short int					count;

//	DEBUGSTR("\pHYP_ProcessNextCallbackQueue");
	for (count = 0; count < kMaxCallbackQueue; count++)
	{
		theQueue = &theCallbackQueue[count];
		if (theQueue->active)
		{
//			DEBUGSTR("\pHYP_ProcessNextCallbackQueue:BEFORE CALLBACK");
			if (theQueue->filePlayRef)
			{
				FSClose(theQueue->filePlayRef);
				theQueue->filePlayRef = 0;
			}
			if (theQueue->customCallback)
			{
				(*theQueue->customCallback)(theQueue->voiceNumber, kSoundDoneNoInterrupt, theQueue->userData);
				theQueue->customCallback = NULL;
			}
			theQueue->active = FALSE;
		}
	}
}

// General purpose VBL task to execute user defined task
static void HYP_ProcessVBLTask(void)
{
	if (vblankProcPtr)
	{
		(*vblankProcPtr)();
	}
}

#if GENERATING68K == 1
// 68k based VBL task function
#pragma parameter __D0 GetA0toVariable
pascal long GetA0toVariable(void) = {0x2028, 0xFFFC}; 

static pascal void HYP_PreProcessVBLTask(void)
{
	long			saveA5, newA5;

	newA5 = GetA0toVariable();	/* Globals register points the vbl task structure */

	saveA5 = SetA5(newA5);			/* set current a5 */
	theSoundVBPtr->vblCount = 1;		/* tell it to contiue */

	HYP_ProcessVBLTask();

	/* Restore A5 for the rest of the interupt process
	*/
	SetA5(saveA5);         /* restore previous a5 */
}
#else
// PowerPC based VBL task function
static pascal void HYP_PreProcessVBLTask(VBLTaskPtr theVBLTask) 
{
	theSoundVBPtr->vblCount = 1;		/* tell it to contiue */

	HYP_ProcessVBLTask();
}

#endif


#if GENERATING68K == 1
// 68k based VBL task setup and cleanup
static OSErr HYP_SetupVBLTask(void)
{
	OSErr theErr;
	struct Jump
	{
		short 	bsr_inderect;		// 0
		void *	address;			// 4
		long		movea;			// 8
		short	jmp_a1;			// 12
	};
	struct Jump *heapJumpPtr;

/*
		0x6104,			 bsr.s 6(pc)
		0x0000, 0x0000,	 dc.l	0
		0x225F			 movea.l (a7)+, a1
		0x2251			 move (a1), a1
		0x4Ed1			 jmp (a1)
*/

/* Set up Vertical Blank Interrupt
*/
	taskPtr = (long *)NewPtrClear((long)sizeof(VBLTask) + sizeof(long));
	heapJumpPtr = (struct Jump *)NewPtrSys((long)sizeof(struct Jump));
	if ( (taskPtr) && (heapJumpPtr) )
	{
		if (HYP_IsVirtualMemoryAvailable())
		{
			LockMemory(taskPtr, (long)sizeof(VBLTask) + sizeof(long));
			LockMemory(heapJumpPtr, (long)sizeof(struct Jump));
		}

		theSoundVBPtr = (VBLTask *)( ((Byte *)taskPtr) + sizeof(long) );
		taskPtr[0] = SetCurrentA5();
		heapJumpPtr->bsr_inderect = 0x6104;
		heapJumpPtr->address = HYP_PreProcessVBLTask;
		heapJumpPtr->movea = 0x225F2251L;
		heapJumpPtr->jmp_a1 = 0x4Ed1;

		theSoundVBPtr->vblAddr = (VBLUPP)heapJumpPtr;
		theSoundVBPtr->vblCount = 1;		/* Every 1/60th of a second */
		theSoundVBPtr->qType = vType;
		theSoundVBPtr->qLink = NULL;
		theSoundVBPtr->vblPhase = 0;

/* Ok, flush the code/data cache for the '040 Macs. */
#ifndef __MIXEDMODE__
		if (*((char *)CPUFlag) >= 2)
#else
		if (LMGetCPUFlag() >= 2)
#endif
		{
			FlushInstructionCache();
			FlushDataCache();
		}

		theErr = VInstall((QElemPtr)theSoundVBPtr);
	}
	else
	{
		if (heapJumpPtr)
		{
			if (HYP_IsVirtualMemoryAvailable())
			{
				UnlockMemory((Ptr)heapJumpPtr, 12L);
			}
			DisposePtr((Ptr)heapJumpPtr);
		}
		if (taskPtr)
		{
			if (HYP_IsVirtualMemoryAvailable())
			{
				UnlockMemory(taskPtr, (long)sizeof(VBLTask) + sizeof(long));
			}
			DisposePtr((Ptr)taskPtr);
			taskPtr = NULL;
			theSoundVBPtr = NULL;
		}
	}
	return theErr;
}

static OSErr HYP_CleanupVBLTask(void)
{
	if (theSoundVBPtr)
	{
		if (theSoundVBPtr->vblAddr)
		{
			if (HYP_IsVirtualMemoryAvailable())
			{
				UnlockMemory((Ptr)theSoundVBPtr->vblAddr, 12L);
			}
			DisposePtr((Ptr)theSoundVBPtr->vblAddr);
		}
		VRemove((QElemPtr)theSoundVBPtr);
		theSoundVBPtr = NULL;
	}
	if (taskPtr)
	{
		if (HYP_IsVirtualMemoryAvailable())
		{
			UnlockMemory(taskPtr, (long)sizeof(VBLTask) + sizeof(long));
		}
		DisposePtr((Ptr)taskPtr);
	}
	return noErr;
}
#else
// PowerPC based VBL task setup and cleanup
static OSErr HYP_SetupVBLTask(void)
{
	OSErr	theErr;
/* Set up Vertical Blank Interrupt
*/
	theErr = noErr;
	taskPtr = NULL;
	theSoundVBPtr = (VBLTask *)NewPtrSysClear((long)sizeof(VBLTask));
	if (theSoundVBPtr)
	{
		if (HYP_IsVirtualMemoryAvailable())
		{
			LockMemory(theSoundVBPtr, (long)sizeof(VBLTask));
		}
		theSoundVBPtr->vblAddr = NewVBLProc(HYP_PreProcessVBLTask);
		theSoundVBPtr->vblCount = 1;		/* Every 1/60th of a second */
		theSoundVBPtr->qType = vType;
		theSoundVBPtr->qLink = NULL;
		theSoundVBPtr->vblPhase = 0;

		theErr = VInstall((QElemPtr)theSoundVBPtr);
	}
	return theErr;
}

static OSErr HYP_CleanupVBLTask(void)
{
	if (theSoundVBPtr)
	{
		if (theSoundVBPtr->vblAddr)
		{
			DisposeRoutineDescriptor((VBLUPP)theSoundVBPtr->vblAddr);
			theSoundVBPtr->vblAddr = NULL;
		}
		VRemove((QElemPtr)theSoundVBPtr);

		if (HYP_IsVirtualMemoryAvailable())
		{
			UnlockMemory(theSoundVBPtr, (long)sizeof(VBLTask));
		}
		DisposePtr((Ptr)theSoundVBPtr);
		theSoundVBPtr = NULL;
	}
	return noErr;
}
#endif

static Boolean HYP_IsThisSoundRegistered(SndReference theSound)
{
	register short int	count;
	register Boolean	foundPlace;

	foundPlace = FALSE;
	for (count = 0; count < totalRegisteredSounds; count++)
	{
		if (registeredSounds[count] == theSound)
		{
			foundPlace = TRUE;
		}
	}
	return foundPlace;
}


static void HYP_RegisterThisSound(SndReference theSound)
{
	register short int	count;
	register Boolean	foundPlace;
	SndReference *		newArray;
	register long		theSize;

	if (registeredSounds == NULL)		// first time to register?
	{
		theSize = (long)sizeof(SndReference *) * kRegisterSoundBlockSize;
		registeredSounds = (SndReference *)NewPtrClear(theSize);
		totalRegisteredSounds = 0;
		currentBlockSizeForRegisteredSounds = kRegisterSoundBlockSize;
	}
	if (registeredSounds)
	{
		if (HYP_IsThisSoundRegistered(theSound) == FALSE)		// don't register a sound twice
		{
			// walk through the sound register array and see if there are any free blocks, if so, then put
			// our reference there
			foundPlace = FALSE;
			for (count = 0; count < totalRegisteredSounds; count++)
			{
				if (registeredSounds[count] == NULL)
				{
					registeredSounds[count] = theSound;
					foundPlace = TRUE;
				}
			}
			// if we didn't find a place in our array, go ahead and put it into the next place. if we've need more
			// in the array then reallocate and then place it in there
			if (foundPlace == FALSE)
			{
				totalRegisteredSounds++;		// next entry
				if (totalRegisteredSounds > currentBlockSizeForRegisteredSounds)
				{
					// out of space, so reallocate a new array and copy all the current registered sounds into it
					theSize = sizeof(SndReference *) * (currentBlockSizeForRegisteredSounds + kRegisterSoundBlockSize);
					newArray = (SndReference *)NewPtrClear(theSize);
					if (newArray)
					{
						theSize = (long)sizeof(SndReference *) * currentBlockSizeForRegisteredSounds;
						BlockMove((Ptr)registeredSounds, (Ptr)newArray, theSize);
						DisposePtr((Ptr)registeredSounds);

						registeredSounds = newArray;		// replace old one with new one
						currentBlockSizeForRegisteredSounds += kRegisterSoundBlockSize;	// increase our tollerance
					}
				}
				// we have enough room in our current array or the array has been enlarged,
				// so put our reference in the next place
				registeredSounds[totalRegisteredSounds-1] = theSound;
			}
		}
	}
}

static void HYP_UnregisterThisSound(SndReference theSound)
{
	register short int	count;

	// walk through our reference array and remove by setting the placeholder to NULL, our
	// reference. We never shrink the reference array because it will never get that big, and
	// its wastes time
	if (registeredSounds)
	{
		for (count = 0; count < totalRegisteredSounds; count++)
		{
			if (registeredSounds[count] == theSound)
			{
				registeredSounds[count] = NULL;		// ok remove from our list
			}
		}
	}
}

static void HYP_UnregisterAllSounds(void)
{
	register short int	count;

	// walk through our reference array and remove by setting the placeholder to NULL, our
	// reference. At this point dispose of the reference array too
	if (registeredSounds)
	{
		for (count = 0; count < totalRegisteredSounds; count++)
		{
			if (registeredSounds[count])
			{
				registeredSounds[count] = NULL;		// ok remove from our list
			}
		}
		DisposePtr((Ptr)registeredSounds);
		registeredSounds = NULL;
	}
}

// Public functions
//

void HY_SetSoundVBCallBack(void (*theProc)(void))
{
	vblankProcPtr = theProc;
}


#define kCompressionPacketSize	6	// 2 bytes at 3:1 is 6 bytes for a packet
								// 1 byte at 6:1 is 6 bytes also
Handle HY_GetMACESound(register CmpSoundHeaderPtr pSndBuffer, 
					Ptr *pWave, long *length, 
					long *pLoopStart, long *loopend, 
					long *rate)
{
	Ptr				inBuffer, outBuffer, theInState, theOutState;
	CmpSoundHeader *	csndHeaderPtr;
	Handle			outHandle;
	unsigned long		sampleCount;
	long				buffLen;

	outHandle = NULL;
	csndHeaderPtr = (CmpSoundHeaderPtr) pSndBuffer;
	if (csndHeaderPtr->compressionID) 					// see if sound is compressed
	{
		sampleCount = csndHeaderPtr->numFrames;		// get number of number of frames
		buffLen = sampleCount * kCompressionPacketSize;	// bufferLen = number of frames * packet size */

		theInState = NewPtrClear(128L);				// allocate working buffers
		theOutState = NewPtrClear(128L);
		outHandle = NewHandleClear(buffLen);
		if ((theInState && theOutState && outHandle))
		{
			HLock(outHandle);
			outBuffer = *outHandle;
			if ((inBuffer = csndHeaderPtr->samplePtr) == NULL)	// get ptr to sample data
			{
				inBuffer = (Ptr) csndHeaderPtr->sampleArea;
			}												/* decompress sound */

			switch(csndHeaderPtr->compressionID)	// MACE compression ID's only
			{
				case threeToOne:
					Exp1to3(inBuffer, outBuffer, sampleCount, (StateBlockPtr)theInState, (StateBlockPtr)theOutState,
									csndHeaderPtr->numChannels, 1);
					break;
				case sixToOne:
					Exp1to6(inBuffer,outBuffer, sampleCount, (StateBlockPtr)theInState, (StateBlockPtr)theOutState,
									csndHeaderPtr->numChannels, 1);
					break;
				default:
					BlockMove(inBuffer, outBuffer, sampleCount);
					break;
			}
			*pWave = outBuffer;
			*length = buffLen;
			*pLoopStart = csndHeaderPtr->loopStart;
			*loopend = csndHeaderPtr->loopEnd;
			*rate = csndHeaderPtr->sampleRate;
		}
		if (theInState)
		{
			DisposePtr(theInState);
		}
		if (theOutState)
		{
			DisposePtr(theOutState);
		}
	}
	return outHandle;
}


void HY_RegisterThisSound(SndReference theSound)
{
	if (theSound)
	{
		HLock(theSound);
		HYP_RegisterThisSound(theSound);			// register this sound
	}
}


SndReference HY_GetSoundResource(short int resourceID)
{
	Handle				theSoundData;
	SndReference			theSndRef;

	theSndRef = NULL;
	theSoundData = GetResource('snd ', resourceID);		// get the resource data type 'snd '
	if (theSoundData)
	{
		HLock(theSoundData);						// lock it down
		HYP_RegisterThisSound(theSoundData);			// register this sound
		theSndRef = (SndReference)theSoundData;
	}
	return theSndRef;
}

void HY_UnregisterSoundResource(SndReference theSound)
{
	if (HYP_IsThisSoundRegistered(theSound))		// only work with sounds that have been access through our API
	{
		HYP_UnregisterThisSound(theSound);
		HUnlock((Handle)theSound);
	}
}

void HY_UnregisterAllSoundResources(void)
{
	register short int	count;

	// walk through our reference array and remove by setting the placeholder to NULL, our
	// reference. At this point dispose of the reference array too
	if (registeredSounds)
	{
		for (count = 0; count < totalRegisteredSounds; count++)
		{
			if (registeredSounds[count])
			{
				HY_UnregisterSoundResource(registeredSounds[count]);		// ok remove from our list
			}
		}
		HYP_UnregisterAllSounds();
	}
}


Boolean HY_Is16BitAvailable(void)
{
	long	feature;

	feature = 0;
	if (Gestalt(gestaltSoundAttr, &feature) == noErr)
	{
		if (feature & (1<<gestalt16BitSoundIO))
		{
			return TRUE;
		}
	}
	return FALSE;
}

Boolean HY_IsStereoAvailable(void)
{
	long	feature;

	feature = 0;
	if (Gestalt(gestaltSoundAttr, &feature) == noErr)
	{
		if (feature & (1<<gestaltStereoCapability))
		{
			return TRUE;
		}
	}
	return FALSE;
}


OSErr HY_Setup(short int featureMask, short int maxVoices)
{
	OSErr				theErr;
	short int				count;
	register MM_SoundVoice	*pVoice;
	register long			features;
	SoundVolume			fullVolume;

	if (HYP_IsNewSoundManagerInstalled())
	{
		theErr = noErr;
		maxSoundVoices = 0;
		fullVolume.left = kFullVolume;
		fullVolume.right = kFullVolume;

		registeredSounds = NULL;		// no sounds registered

#if GENERATING68K == 1
		globalA5 = (long)SetCurrentA5();		// we do this here, so HY_PlaySample can be called via interrupt
#else
		globalA5 = 0;
#endif
		theSoundVoices = (MM_SoundVoice *)NewPtrClear((long)sizeof(MM_SoundVoice) * maxVoices);
		if (theSoundVoices)
		{
#ifdef __MIXEDMODE__
			theMasterSoundCallbackProcPtr = NewSndCallBackProc(HYP_HandleSoundDoneCallBack);
#else
			theMasterSoundCallbackProcPtr = (void *)HYP_HandleSoundDoneCallBack;
#endif

			features = 0L;
			if ((featureMask & kUseStereo) == kUseStereo)
			{
				features |= initStereo;		// stereo sound support
			}
			else
			{
				features |= initMono;			// mono sound support
			}
			if ((featureMask & kMaxQuality) == kMaxQuality)
			{
				features |= initNoDrop;		// keep interpolation on, turn off drop sample convertion (CPU heavy)
			}
			else
			{
				features |= initNoInterp;		// turn off interpolation
			}
			for (count = 0; count < maxVoices; count++)
			{
				pVoice = &theSoundVoices[count];
				theErr = SndNewChannel(&pVoice->theChannel, 
							sampledSynth,
							features,
							theMasterSoundCallbackProcPtr);
				if (theErr)
				{	/* we failed, so bail
					*/
					pVoice->theChannel = NULL;
					HY_Cleanup();
					break;
				}
				else
				{	// success
					maxSoundVoices++;		// we increment our global in case we fail so we can back
										// out easily
					pVoice->voiceActive = FALSE;
					pVoice->voicePaused = FALSE;
					pVoice->filePlay = FALSE;
					pVoice->theChannel->callBack = theMasterSoundCallbackProcPtr;	// extra, just in case
					pVoice->theChannel->userInfo = globalA5;
				}
			}
			for (count = 0; count < maxVoices; count++)
			{
				HY_SetVolume(count, fullVolume);
				if (HY_Is16BitAvailable())
				{
					HY_SetRate(count, rate22050hz);
				}
				else
				{
					HY_SetRate(count, rate22khz);
				}
			}
			HYP_SetupQueues();
			HYP_SetupVBLTask();
		}
		else
		{
			theErr = memFullErr;
		}
	}
	else
	{
		theErr = smRevisionErr;	// we can only work with Sound Manager 3.0. We're using cool features of SM 3.0
	}
	return theErr;
}

OSErr HY_Cleanup(void)
{
	register short int	count;

	HYP_CleanupVBLTask();			// clean up our VBL task
	HY_UnregisterAllSoundResources();	// clean up and remove all references to sounds. This calls HYP_UnregisterAllSounds

	if (theSoundVoices)
	{
		for (count = 0; count < maxSoundVoices; count++)
		{
			if (theSoundVoices[count].theChannel)
			{
				HY_StopSample(count);
	
				SndDisposeChannel(theSoundVoices[count].theChannel, TRUE);
				theSoundVoices[count].theChannel = NULL;
			}
		}
#ifdef __MIXEDMODE__
		if (theMasterSoundCallbackProcPtr)
		{
			DisposeRoutineDescriptor(theMasterSoundCallbackProcPtr);
		}
#endif

		theMasterSoundCallbackProcPtr = NULL;
		DisposePtr((Ptr)theSoundVoices);
		theSoundVoices = NULL;
	}
	return noErr;
}

Boolean HY_Active(void)
{
	return (theSoundVoices) ? TRUE : FALSE;
}

OSErr HY_PlaySample(	short int voiceNumber, 					// voice to play sample on
					void *pSample, 						// morph data pointer
					long length, 							// sample length
					UnsignedFixed rate,								// Fixed 16.16 value
					short dataBitSize,						// 8 or 16 bit data
					short channelSize,						// 1 or 2 channels of date
					CustomCallbackProc	customCallback,		// callback when finished, event what
					long userData,
					Boolean killSound)
{
	SndCommand			theCmd;
	register MM_SoundVoice	*pVoice;
	register OSErr			theErr;
	ExtSoundHeader		theSndBuffer;

	theErr = noErr;
	if (voiceNumber == kUseAnyVoice)
	{
		voiceNumber = HYP_GetFreeVoice();
		if (voiceNumber == kUseAnyVoice)
		{
			theErr = channelBusy;
		}
	}
	if (length < 1)							// data length greater than zero
	{
		theErr = buffersTooSmall;
	}
	if ( (dataBitSize != 8) && (dataBitSize != 16) )	// sample bit size is 8 or 16 bits
	{
		theErr = badFormat;
	}
	if ( (channelSize != 1) && (channelSize != 2) )	// mono or stereo
	{
		theErr = badFormat;
	}
	if (rate < 0x10000L)						// sample rate is at least 1.0
	{
		theErr = siInvalidSampleRate;
	}
	if (theErr == noErr)
	{
		pVoice = HYP_GetPrivateDataFromVoice(voiceNumber);		// voice to play on is in range
		if (pVoice)
		{
			if (pVoice->voiceActive)
			{
				if (killSound)
				{
					HY_StopSample(voiceNumber);
				}
				else
				{
					theErr = channelBusy;
				}
			}
			if (theErr == noErr)
			{
				pVoice->customCallback = customCallback;
				pVoice->userData = userData;
				/* Play sample */
	
				theSndBuffer.samplePtr = (Ptr)pSample;
				theSndBuffer.numChannels = channelSize;
				theSndBuffer.sampleRate =  rate;
				theSndBuffer.loopStart = 0;		// Apple Sound Manager looping doesn't work for one shot sounds
				theSndBuffer.loopEnd = 0;
				theSndBuffer.encode = extSH;
				theSndBuffer.baseFrequency = 0;
				theSndBuffer.numFrames = length;
//				theSndBuffer.AIFFSampleRate = 0;
				theSndBuffer.markerChunk = NULL;
				theSndBuffer.instrumentChunks = NULL;
				theSndBuffer.AESRecording = NULL;
				theSndBuffer.sampleSize = dataBitSize;
				theSndBuffer.futureUse1 = 0;
				theSndBuffer.futureUse2 = 0;
				theSndBuffer.futureUse3 = 0;
				theSndBuffer.futureUse4 = 0;
				pVoice->theSndBuffer = theSndBuffer;
				theCmd.param1 = 0;
				theCmd.param2 = (long)&theSndBuffer;
				theCmd.cmd = bufferCmd;
				theErr = SndDoCommand(pVoice->theChannel, &theCmd, FALSE);		// start sound
				if (theErr == noErr)
				{
					theErr = HYP_PostCallbackFunction(pVoice->theChannel, (long)pVoice);
				}
				pVoice->voiceActive = TRUE;
			}
		}
		else
		{
			theErr = qtParamErr;
		}
	}
	if (theErr)
	{
		DEBUGSTR("\pError in HY_PlaySample");
	}
	return theErr;
}

void HY_StopSample(register short int voiceNumber)
{
	register MM_SoundVoice	*pVoice;
	SndCommand			theCmd;

	if (voiceNumber == kUseAnyVoice)
	{
		voiceNumber = HYP_GetFreeVoice();
	}

	pVoice = HYP_GetPrivateDataFromVoice(voiceNumber);
	if (pVoice)
	{
		if (HY_IsVoiceEmpty(voiceNumber) == FALSE)
		{
			if (pVoice->filePlay)
			{
				SndStopFilePlay(pVoice->theChannel, TRUE);
				FSClose(pVoice->fileRef);
				pVoice->filePlay = FALSE;
				pVoice->fileRef = 0;
			}
			theCmd.param1 = 0;
			theCmd.param2 = 0;
			theCmd.cmd = quietCmd;
			SndDoImmediate(pVoice->theChannel, &theCmd);
			theCmd.cmd = flushCmd;
			SndDoImmediate(pVoice->theChannel, &theCmd);
			if (pVoice->customCallback)
			{
				(*pVoice->customCallback)(	voiceNumber, 
										kSoundDoneForcedStop, 
										pVoice->userData);
			}
			pVoice->voiceActive = FALSE;
		}
	}
}

OSErr HY_PlaySoundHandle(	short int voiceNumber, 
						SndReference theSound,
						CustomCallbackProc customCallback,	
						long userData,
						Boolean killSound)
{
	register MM_SoundVoice	*pVoice;
	register OSErr			theErr;

	theErr = noErr;
	if (voiceNumber == kUseAnyVoice)
	{
		voiceNumber = HYP_GetFreeVoice();
		if (voiceNumber == kUseAnyVoice)
		{
			theErr = channelBusy;
		}
	}
	// only work with sounds that have been access through our API
	if ( (theErr == noErr) && (HYP_IsThisSoundRegistered(theSound)))
	{
		pVoice = HYP_GetPrivateDataFromVoice(voiceNumber);		// voice to play on is in range
		if (pVoice)
		{
			if (pVoice->voiceActive)
			{
				if (killSound)
				{
					HY_StopSample(voiceNumber);
				}
				else
				{
					theErr = channelBusy;
				}
			}
			if (theErr == noErr)
			{
				pVoice->customCallback = customCallback;
				pVoice->userData = userData;

				// Play sound sample. This allows the sound reference to be a MACE compressed sound, 8 or 16 bit, mono,
				// stereo, or whatever.
				theErr = SndPlay(pVoice->theChannel, (SndListHandle)theSound, TRUE);
				if (theErr == noErr)
				{
					// now post a callback to process this sample when its finished
					theErr = HYP_PostCallbackFunction(pVoice->theChannel, (long)pVoice);
					pVoice->voiceActive = TRUE;
				}
			}
		}
	}
	return noErr;
}



Boolean HY_IsVoiceEmpty(register short int voiceNumber)
{
	SCStatus				status;
	register OSErr			theErr;
	register MM_SoundVoice	*pVoice;
	register Boolean		empty;

	empty = TRUE;
	if (voiceNumber == kUseAnyVoice)
	{
		voiceNumber = HYP_GetFreeVoice();
	}

	pVoice = HYP_GetPrivateDataFromVoice(voiceNumber);
	if (pVoice)
	{
// Our voiceActive works because we set it at the end of a sample playback. But we're going to query
// the Sound Manager, just in case it fails to call our callback that sets the flag and we'll store the 
// query in our variable voiceActive;
		empty = ! pVoice->voiceActive;
		theErr = SndChannelStatus(pVoice->theChannel, sizeof(SCStatus), &status);
		if (theErr == noErr)
		{
			if (pVoice->voiceActive != status.scChannelBusy)
			{
				pVoice->voiceActive = status.scChannelBusy;
			}
			empty = ! pVoice->voiceActive;
		}
	}
	return empty;
}

void HY_SetRate(short int voiceNumber, UnsignedFixed newRate)
{
	register MM_SoundVoice	*pVoice;
	SndCommand			theCmd;

	if (voiceNumber == kUseAnyVoice)
	{
		voiceNumber = HYP_GetFreeVoice();
	}
	pVoice = HYP_GetPrivateDataFromVoice(voiceNumber);
	if (pVoice)
	{
		pVoice->lastRate = newRate;
		// Convert the passed rate into a relative sample multipler
		newRate = UnsignedFixedMulDiv(newRate, 0x10000, rate22khz);

		theCmd.param1 = 0;
		theCmd.param2 = (long)newRate;
		theCmd.cmd = rateCmd;
		SndDoImmediate(pVoice->theChannel, &theCmd);		// change rate now for this channel
	}
}

UnsignedFixed HY_GetRate(short int voiceNumber)
{
	register MM_SoundVoice	*pVoice;
	UnsignedFixed			oldRate;
	SndCommand			theCmd;

	if (voiceNumber == kUseAnyVoice)
	{
		voiceNumber = HYP_GetFreeVoice();
	}
	oldRate = rate22khz;
	pVoice = HYP_GetPrivateDataFromVoice(voiceNumber);
	if (pVoice)
	{
		oldRate = pVoice->lastRate;
		if (pVoice->voiceActive)
		{
			theCmd.param1 = 0;
			theCmd.param2 = (long)&oldRate;
			theCmd.cmd = getRateCmd;
			SndDoImmediate(pVoice->theChannel, &theCmd);		// get the current rate for this channel

			// Use this cool Toolbox routine to determine the actual sample rate
			oldRate = UnsignedFixedMulDiv(rate22khz, oldRate, 0x10000);
		}
	}
	return oldRate;
}

void HY_SetVolume(short int voiceNumber, SoundVolume newVolume)
{
	register MM_SoundVoice	*pVoice;
	SndCommand			theCmd;

	if (voiceNumber == kUseAnyVoice)
	{
		voiceNumber = HYP_GetFreeVoice();
	}
	pVoice = HYP_GetPrivateDataFromVoice(voiceNumber);
	if (pVoice)
	{
		theCmd.param1 = 0;
		// by breaking down the volumes here, we become a little more flexible to compilers
		theCmd.param2 = (long)((long)newVolume.right << 16L | (newVolume.left & 0xFFFF));
		theCmd.cmd = volumeCmd;
		SndDoImmediate(pVoice->theChannel, &theCmd);		// change volume now for this channel
	}
}

SoundVolume HY_GetVolume(short int voiceNumber)
{
	register MM_SoundVoice	*pVoice;
	SndCommand			theCmd;
	SoundVolume			oldVolume;
	long					soundVolume;

	if (voiceNumber == kUseAnyVoice)
	{
		voiceNumber = HYP_GetFreeVoice();
	}
	*((long *)&oldVolume) = -1L;
	pVoice = HYP_GetPrivateDataFromVoice(voiceNumber);
	if (pVoice)
	{
		soundVolume = 0;
		theCmd.param1 = 0;
		theCmd.param2 = (long)&soundVolume;

		theCmd.cmd = getVolumeCmd;
		SndDoImmediate(pVoice->theChannel, &theCmd);		// get the current volume for this channel

		// by breaking down the volumes here, we become a little more flexible to compilers
		oldVolume.right = soundVolume >> 16L;
		oldVolume.left = soundVolume & 0xFFFFL;
	}
	return oldVolume;
}

void HY_SetStereoPosition(short int voiceNumber, short positionMask)
{
	register MM_SoundVoice	*pVoice;
	SoundVolume			theVolume;

	if (voiceNumber == kUseAnyVoice)
	{
		voiceNumber = HYP_GetFreeVoice();
	}

	pVoice = HYP_GetPrivateDataFromVoice(voiceNumber);
	if (pVoice)
	{
		theVolume.right = kFullVolume + positionMask;
		theVolume.left = kFullVolume - positionMask;

		HY_SetVolume(voiceNumber, theVolume);
	}
}

short HY_GetStereoPosition(short int voiceNumber)
{
	register MM_SoundVoice	*pVoice;
	SoundVolume			theVolume;
	short				positionMask;

	if (voiceNumber == kUseAnyVoice)
	{
		voiceNumber = HYP_GetFreeVoice();
	}

	pVoice = HYP_GetPrivateDataFromVoice(voiceNumber);
	if (pVoice)
	{
		theVolume = HY_GetVolume(voiceNumber);
		positionMask = theVolume.right - theVolume.left;
	}
	return positionMask;
}

void HY_ServiceTasks(void)
{
	register short int		count;
	SCStatus				status;
	register OSErr			theErr;
	register MM_SoundVoice	*pVoice;

	HYP_ProcessNextCallbackQueue();

	for (count = 0; count < maxSoundVoices; count++)
	{
		pVoice = &theSoundVoices[count];
		if (pVoice->theChannel)
		{
			theErr = SndChannelStatus(pVoice->theChannel, sizeof(SCStatus), &status);
			if (theErr == noErr)
			{
				if (pVoice->voiceActive != status.scChannelBusy)
				{
					pVoice->voiceActive = status.scChannelBusy;
				}
			}
		}
	}
}

OSErr HY_PauseHardware(void)
{
	register MM_SoundVoice	*pVoice;
	register short int		count;

	HYP_CleanupVBLTask();

	for (count = 0; count < maxSoundVoices; count++)
	{
		pVoice = &theSoundVoices[count];
		if (pVoice->theChannel)
		{
			if (pVoice->voicePaused == FALSE)
			{
				pVoice->voicePaused = TRUE;
				if (pVoice->voiceActive)
				{
					if (pVoice->filePlay)
					{
						SndPauseFilePlay(pVoice->theChannel);	// pause file playback
					}
				}
			}
		}
	}
	return noErr;
}

OSErr HY_ResumeHardware(void)
{
	register MM_SoundVoice	*pVoice;
	register short int		count;

	HYP_SetupVBLTask();

	for (count = 0; count < maxSoundVoices; count++)
	{
		pVoice = &theSoundVoices[count];
		if (pVoice->theChannel)
		{
			if (pVoice->voicePaused)
			{
				pVoice->voicePaused = FALSE;
				if (pVoice->voiceActive)
				{
					if (pVoice->filePlay)
					{
						SndPauseFilePlay(pVoice->theChannel);	// resume file playback
					}
				}
			}
		}
	}
	return noErr;
}

OSErr HY_StartFilePlay(short int voiceNumber, 
						FSSpec *pFile, 
						CustomCallbackProc customCallback,	
						long userData,
						long bufferSize,
						Boolean killSound)
{
	register MM_SoundVoice	*pVoice;
	register OSErr			theErr;

	theErr = noErr;
	if (voiceNumber == kUseAnyVoice)
	{
		voiceNumber = HYP_GetFreeVoice();
		if (voiceNumber == kUseAnyVoice)
		{
			theErr = channelBusy;
		}
	}
	if (theErr == noErr)
	{
		pVoice = HYP_GetPrivateDataFromVoice(voiceNumber);		// voice to play on is in range
		if (pVoice)
		{
			if (pVoice->voiceActive)
			{
				if (killSound)
				{
					HY_StopSample(voiceNumber);
				}
				else
				{
					theErr = channelBusy;
				}
			}
			if (theErr == noErr)
			{
				pVoice->customCallback = customCallback;
				pVoice->userData = userData;
				pVoice->theChannel->userInfo = globalA5;
				pVoice->fileSpec = *pFile;

//	We could use the cooler file open function, but it only works with System 7 or better. Sound Manager 3.0
//	can be installed on a System eariler than 7.0
//				theErr = FSpOpenDF(pFile, fsRdPerm, &pVoice->fileRef);

				theErr = HOpen(pFile->vRefNum, pFile->parID, pFile->name, fsRdPerm, &pVoice->fileRef);
				if (theErr == noErr)
				{
					theErr = SndStartFilePlay(pVoice->theChannel, 
										pVoice->fileRef, 0, 
										bufferSize, NULL, 
										NULL, 
										NewFilePlayCompletionProc(HYP_FilePlayCompletionDone),
										TRUE);
					if (theErr == noErr)
					{
						pVoice->filePlay = TRUE;
						pVoice->voiceActive = TRUE;
					}
				}
			}
		}
	}
	return theErr;
}


OSErr HY_GetSoundResourceInformation(Handle theSnd, long *pLoopStart, long *pLoopEnd, 
									long *pSampleOffsetStart, long *pTotalSize, short *pBaseKey,
									short int *pNumChannels, short int *pBitSize,
									UnsignedFixed *pRate,
									short int *pCompressionType)
{
	register SoundHeader	* 	pSndBuffer;
	register CmpSoundHeader *	pCmpBuffer;
	register ExtSoundHeader	*	pExtBuffer;
	short int					soundFormat;
	short int					numSynths, numCmds;
	long						offset;
	register Ptr				pSndFormat;
	OSErr					theErr;

	theErr = badFormat;
	*pSampleOffsetStart = 0;
	*pTotalSize = 0;
	*pLoopStart = 0;
	*pLoopEnd = 0;
	*pBaseKey = 0;
	*pCompressionType = notCompressed;
	*pNumChannels = 1;		// defaults for standard header
	*pBitSize = 8;
	*pRate = 0;

	if (theSnd)
	{
		HLock(theSnd);
		pSndFormat = (Ptr)*theSnd;
		soundFormat = *(short int *)pSndFormat;
		switch (soundFormat)
		{
			case 1:	// format 1 sound
				// look inside the format 1 resource and decode offsets
				numSynths = ((short int *)pSndFormat)[1];					// get number of synths
				numCmds = *(short int *)(pSndFormat + 4 + numSynths * 6);		// get number of commands
				break;
			case 2:	// format 2 sound
				numSynths = 0;		// format 2 has none
				numCmds = ((short int *)pSndFormat)[2];
				break;
			default:
				soundFormat = -1;
				break;
		}

		if (soundFormat != -1)	/* did we get the right format? */
		{
			/* compute address of sound header. 
			*/
			offset = 6 + 6 * numSynths + 8 * numCmds;
			pSndBuffer = (SoundHeader *) (StripAddress(*theSnd) + offset);
			switch (pSndBuffer->encode)
			{
				case stdSH:	// standard header
					*pSampleOffsetStart = (long)&pSndBuffer->sampleArea[0] - (long)((Byte *)*theSnd);
					*pTotalSize = pSndBuffer->length;
					*pLoopStart = pSndBuffer->loopStart;
					*pLoopEnd = pSndBuffer->loopEnd;
					*pBaseKey = pSndBuffer->baseFrequency;
					*pRate = pSndBuffer->sampleRate;
					theErr = noErr;
					break;

				case extSH:	// extened header
					pExtBuffer = (ExtSoundHeader *)pSndBuffer;
					*pSampleOffsetStart = (long)&pExtBuffer->sampleArea[0] - (long)((Byte *)*theSnd);
					*pNumChannels = pExtBuffer->numChannels;
					*pBitSize = pExtBuffer->sampleSize;
					*pTotalSize = pExtBuffer->numFrames * (*pNumChannels) * (*pBitSize / 8);
					*pLoopStart = pExtBuffer->loopStart;
					*pLoopEnd = pExtBuffer->loopEnd;
					*pBaseKey = pExtBuffer->baseFrequency;
					*pRate = pExtBuffer->sampleRate;
					theErr = noErr;
					break;
					
				case cmpSH:	// compressed header
					pCmpBuffer = (CmpSoundHeader *)pSndBuffer;
					*pSampleOffsetStart = (long)&pCmpBuffer->sampleArea[0] - (long)((Byte *)*theSnd);
					*pNumChannels = pCmpBuffer->numChannels;
					*pBitSize = pCmpBuffer->sampleSize;
					*pTotalSize = pCmpBuffer->numFrames * (*pNumChannels) * (*pBitSize / 8);
					*pLoopStart = pCmpBuffer->loopStart;
					*pLoopEnd = pCmpBuffer->loopEnd;
					*pBaseKey = pCmpBuffer->baseFrequency;
					*pRate = pCmpBuffer->sampleRate;
					*pCompressionType = pCmpBuffer->compressionID;
					theErr = noErr;
					break;
			}
		}
		HUnlock(theSnd);
	}
	return theErr;
}


OSErr HY_CreateAIFFFileFromPtr(FSSpec *pFile, Ptr pSample, long dataLength, UnsignedFixed sampleRate, 
										short bitSize, short numChannels)
{
	OSErr	theErr;
	short int	fileRef;
	long		length;

	theErr = badFormat;
	if ( (bitSize == 8) || (bitSize == 16) || (numChannels == 1) || (numChannels == 2) )
	{
		theErr = FSpCreate(pFile, 'hlly', AIFFID , 0);
		if (theErr == noErr)
		{
			theErr = FSpOpenDF(pFile, fsRdWrPerm, &fileRef);
			if (theErr == noErr)
			{
				SetFPos(fileRef, fsFromStart, 0L);
				theErr = SetupAIFFHeader(fileRef, numChannels, sampleRate, bitSize, NoneType, 
										0L, dataLength);
				if (theErr == noErr)
				{
					length = dataLength * numChannels * (bitSize / 8);
					theErr = FSWrite(fileRef, &length, pSample);
					if (theErr == noErr)
					{
						SetFPos(fileRef, fsFromStart, 0L);
						theErr = SetupAIFFHeader(fileRef, numChannels, sampleRate, bitSize, NoneType, 
												length, dataLength);
					}
					FSClose(fileRef);
				}
			}
		}
	}
	return theErr;
}

Handle HY_CreateSndResourceFromPtr(Ptr pSample, long dataLength, UnsignedFixed sampleRate, 
										short bitSize, short numChannels,
										short int baseFreq)
{
	OSErr	theErr;
	Handle	theSoundHeader;
	Handle	theSound;
	short	headerLength;

	theSound = NULL;
	// first allocate enough of a handle to setup the header information
	theSoundHeader = NewHandleClear(200L);
	if (theSoundHeader)
	{
		theErr = SetupSndHeader((SndListHandle)theSoundHeader, numChannels, sampleRate, bitSize, 'NONE',
							baseFreq, 0, &headerLength);
		if (theErr == noErr)
		{
			// ok, now we know how large header info is, so we can block move the sample data right after
			theSound = NewHandle(headerLength + dataLength);
			if (theSound)
			{
				HLock(theSound);
				BlockMove(*theSoundHeader, *theSound, headerLength);
				BlockMove(pSample, ((Byte *)*theSound) + headerLength, dataLength);
				HUnlock(theSound);
				DisposeHandle(theSoundHeader);

				// ok, now set the new length of the data
				theErr = SetupSndHeader((SndListHandle)theSound, numChannels, sampleRate, bitSize, 'NONE',
									baseFreq, dataLength, &headerLength);
				if (theErr)
				{
					DisposeHandle(theSound);
					theSound = NULL;
				}
			}
		}
		else
		{
			DisposeHandle(theSoundHeader);
		}
	}
	return theSound;
}

Handle HY_CreateMACESndResourceFromPtr(short maceType, Ptr pSample, long dataLength, 
										UnsignedFixed sampleRate, 
										short bitSize, short numChannels,
										short int baseFreq)
{
	OSErr		theErr;
	Handle		theSoundHeader;
	Handle		theSound;
	short		headerLength;
	long			compressLength;
	OSType		compressionType;
	Byte			compressInState[128];
	Byte			compressOutState[128];
	Byte	*		pData;
	long			count;

	theErr = badFormat;
	theSound = NULL;
	if ( (bitSize == 8) && (numChannels == 1) )	// MACE only supports 8 bit samples. Hollywood only supports mono
	{
		switch (maceType)
		{
			case threeToOne:
				compressionType = 'MAC3';
				compressLength = dataLength / 3L;
				theErr = noErr;
				break;
			case sixToOne:
				compressionType = 'MAC6';
				compressLength = dataLength / 6L;
				theErr = noErr;
				break;
		}
	}
	if (theErr == noErr)
	{
		// first allocate enough of a handle to setup the header information
		theSoundHeader = NewHandleClear(200L);
		if (theSoundHeader)
		{
			theErr = SetupSndHeader((SndListHandle)theSoundHeader, 1, sampleRate, bitSize, compressionType,
								baseFreq, 0, &headerLength);
			if (theErr == noErr)
			{
				// ok, now we know how large header info is, so we can block move the sample data right after
				theSound = NewHandle(headerLength + dataLength);
				if (theSound)
				{
					HLock(theSound);
					BlockMove(*theSoundHeader, *theSound, headerLength);

					// Clear state data for MACE compressor
					for (count = 0; count < 128; count++)
					{
						compressInState[count] = 0;
						compressOutState[count] = 0;
					}

		// MACE supports stereo compressed data, but we don't bother to put it together here.
		// In order to create stereo compressed data, you need to build to buffers, one for each
		// channel. Compress each channel into that buffer by calling CompXto1 with the last parameter
		// set to the channel you are compressing. Then to rebuild the final data, you need to interleave
		// the two buffers into one buffer for the final output. Simple huh! :o
					pData = ((Byte *)*theSound) + headerLength;
					switch (maceType)
					{
						case threeToOne:
							Comp3to1(pSample, pData, dataLength, 
											NULL, NULL, 1, 1);
//											compressInState, compressOutState, 1, 1);
							break;
						case sixToOne:
							Comp6to1(pSample, pData, dataLength, 
											NULL, NULL, 1, 1);
//											compressInState, compressOutState, 1, 1);
							break;
					}
					HUnlock(theSound);
					SetHandleSize(theSound, headerLength + compressLength + 6);
					theErr = MemError();
					DisposeHandle(theSoundHeader);
					if (theErr == noErr)
					{
						// ok, now set the new length of the data
						theErr = SetupSndHeader((SndListHandle)theSound, 1, sampleRate, bitSize, compressionType,
											baseFreq, compressLength, &headerLength);
					}
					if (theErr)
					{
						DisposeHandle(theSound);
						theSound = NULL;
					}
				}
			}
			else
			{
				if (theSound)
				{
					DisposeHandle(theSoundHeader);
				}
			}
		}
	}
	return theSound;
}



// EOF of Hollywood.c

