//////////
//
//	File:		SoundSnippets.c
//
//	Contains:	Code snippets showing how to perform a few typical sound-related operations.
//
//	Written by:	Tim Monroe
//				Some routines based on existing code by Jim Reekes and Kip Olson.
//
//	Copyright:	© 1998 by Apple Computer, Inc., all rights reserved.
//
//	Change History (most recent first):
//
//	   <7>	 	02/03/99	rtm		minor tweaks based on AudioConvert.c sample code
//	   <6>	 	02/03/99	rtm		reworked prompt and filename handling to remove "\p" sequences
//	   <5>	 	06/25/98	rtm		added SndSnip_HasSoundManager3_1; modified the function
//									SndSnip_PromptUserForDiskFileAndSaveCompressed to compress a
//									file in 40K chunks
//	   <4>	 	06/22/98	rtm		added SndSnip_GetAudioSettings and SndSnip_ConvertWAVEFormats;
//									added SndSnip_PromptUserForAudioFileAndCompress and
//									SndSnip_PromptUserForDiskFileAndSaveCompressed, which illustrate
//									both the standard sound compression dialog component and the
//									sound converter routines
//	   <3>	 	05/29/98	rtm		added SndSnip_GetHardwareSettings, SndSnip_CheckVersionNumber,
//									SndSnip_GetVolume, and SndSnip_SetVolume
//	   <2>	 	04/24/98	rtm		got bufferCmd double-buffering working
//	   <1>	 	04/17/98	rtm		first file
//	  
//	This sample code illustrates how to perform some common sound-related operations,
//	such as saving a movie's sound track into a separate file or playing a continuous sound using
//	the bufferCmd sound command. This is not really a library of utilities, but more a grab-bag
//	of "how-to" recipes. Here's a partial list of what's illustrated here:
//
//	*Save a sound-only QuickTime movie as a WAVE file.
//	*Save a sound-only QuickTime movie as a file whose type the user selects.
//	*Save a sound track in a QuickTime movie as a file whose type the user selects.
//	*Create a handle that contains the first sound track in a QuickTime movie.
//	*Open and play a WAVE file using the QuickTime API.
//	*Set the volume level of a sound track in a QuickTime movie.
//	*Use the bufferCmd sound command to continuously play a large buffer of audio data.
//	*Get the current hardware settings.
//	*Get the settings for a sound track in a QuickTime movie.
//	*Get and set the current left and right volumes of a sound channel.
//	*Let the user select an audio file and select its compression settings; then compress it.
//
//	NOTES:
//
//	*** (1) ***
//	The function SndSnip_PromptUserForAudioFileAndCompress uses the standard sound compression dialog routines
//	to elicit compression/conversion settings from the user. These routines were based on the standard image
//	compression dialog routines. You can use modal-dialog filter functions and hook functions with the sound
//	routines in the same way that you use them with the image routines; this, however, is not illustrated here.
//	See the code snippet QTStdCompr for sample code that does illustrate these features of the standard
//	compression dialog routines.
//
//	*** (2) ***
//	The function SndSnip_PromptUserForDiskFileAndSaveCompressed uses the sound converter routines (such as
//	SoundConverterConvertBuffer) to compress audio data. These routines were introduced in Sound Manager 3.2.
//	If you want to run in earlier Sound Manager versions, you'll need to replace this conversion method with
//	some other method. The method illustrated here is based on code in the file SoundConvert.c by Andrew Wulf,
//	Apple Developer Technical Support. 
//
//////////

#include "SoundSnippets.h"
#include "QTUtilities.h"


// global variables
SoundHeader					gSndHeader1;
SoundHeader					gSndHeader2;
Boolean						gCurrChunkDirty = false;
short						gCurrChunkIndex;
SoundHeaderPtr				gCurrChunkHeader;
SndChannelPtr				gCurrChannel;


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Sound movie conversion routines.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////
//
// SndSnip_SaveSoundMovieAsWAVEFile
// Save a sound-only QuickTime movie as a WAVE file.
//
//////////

void SndSnip_SaveSoundMovieAsWAVEFile (Movie theMovie)
{	
	StandardFileReply		myReply;
	StringPtr 				myPrompt = QTUtils_ConvertCToPascalString(kSaveSoundPrompt);
	StringPtr 				myFileName = QTUtils_ConvertCToPascalString(kSaveSoundWaveName);
	
	// have the user select the name and location of the new WAVE file
	StandardPutFile(myPrompt, myFileName, &myReply);
	if (!myReply.sfGood)
		return;
		
	// use the default progress procedure, if any
	SetMovieProgressProc(theMovie, (MovieProgressUPP)-1L, 0);
	
	// export the movie into a file
	ConvertMovieToFile(	theMovie,					// the movie to convert
						NULL,						// all tracks in the movie
						&myReply.sfFile,			// the output file
						kQTFileTypeWave,			// the output file type
						FOUR_CHAR_CODE('TVOD'),		// the output file creator
						smSystemScript,				// the script
						NULL, 						// no resource ID to be returned
						0L,							// no flags
						NULL);						// no specific component
						
	free(myPrompt);
	free(myFileName);
}


//////////
//
// SndSnip_SaveSoundMovieAsAnyTypeFile
// Save a sound-only QuickTime movie as a file whose type the user selects.
//
//////////

void SndSnip_SaveSoundMovieAsAnyTypeFile (Movie theMovie)
{	
	StandardFileReply		myReply;
	StringPtr 				myPrompt = QTUtils_ConvertCToPascalString(kSaveSoundPrompt);
	StringPtr 				myFileName = QTUtils_ConvertCToPascalString(kSaveSoundFileName);
	
	// have the user select the name and location of the new file
	StandardPutFile(myPrompt, myFileName, &myReply);
	if (!myReply.sfGood)
		return;
		
	// use the default progress procedure, if any
	SetMovieProgressProc(theMovie, (MovieProgressUPP)-1L, 0);
	
	// export the movie into a file; since the flag showUserSettingsDialog is set,
	// the user gets to select the audio format of the saved audio data
	ConvertMovieToFile(	theMovie,					// the movie to convert
						NULL,						// all tracks in the movie
						&myReply.sfFile,			// the output file
						0L,							// the output file type
						FOUR_CHAR_CODE('TVOD'),		// the output file creator
						smSystemScript,				// the script
						NULL, 						// no resource ID to be returned
						createMovieFileDeleteCurFile | movieToFileOnlyExport | showUserSettingsDialog,	
						NULL);						// no specific component
						
	free(myPrompt);
	free(myFileName);

}


//////////
//
// SndSnip_SaveSoundTrackAsAnyTypeFile
// Save the first sound track in a QuickTime movie as a file whose type the user selects.
//
//////////

void SndSnip_SaveSoundTrackAsAnyTypeFile (Movie theMovie)
{	
	StandardFileReply		myReply;
	Track					myTrack = NULL;
	StringPtr 				myPrompt = QTUtils_ConvertCToPascalString(kSaveSoundPrompt);
	StringPtr 				myFileName = QTUtils_ConvertCToPascalString(kSaveSoundFileName);
	
	// find the first sound track in the specified movie
	myTrack = GetMovieIndTrackType(theMovie, 1, AudioMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly);
	if (myTrack == NULL)
		return;
	
	// have the user select the name and location of the new file
	StandardPutFile(myPrompt, myFileName, &myReply);
	if (!myReply.sfGood)
		return;
		
	// use the default progress procedure, if any
	SetMovieProgressProc(theMovie, (MovieProgressUPP)-1L, 0);
	
	// export the specified track into a file; since the flag showUserSettingsDialog is set,
	// the user gets to select the audio format of the saved audio data
	ConvertMovieToFile(	theMovie,					// the movie to convert
						myTrack,					// save only the specified track
						&myReply.sfFile,			// the output file
						0L,							// the output file type
						FOUR_CHAR_CODE('TVOD'),		// the output file creator
						smSystemScript,				// the script
						NULL, 						// no resource ID to be returned
						createMovieFileDeleteCurFile | movieToFileOnlyExport | showUserSettingsDialog,	
						NULL);						// no specific component
						
	free(myPrompt);
	free(myFileName);
}


//////////
//
// SndSnip_ExtractSoundTrackIntoHandle
// Create a handle that contains the first sound track in a QuickTime movie.
//
// NOTE: To create a handle that contains ALL the sound tracks in the movie
// mixed together, pass NULL instead of myTrack to PutMovieIntoTypedHandle.
//
//////////

void SndSnip_ExtractSoundTrackIntoHandle (Movie theMovie)
{	
	Track					myTrack = NULL;
	Handle					myHandle = NULL;
	OSErr					myErr = noErr;
	
	// find the first sound track in the specified movie
	myTrack = GetMovieIndTrackType(theMovie, 1, AudioMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly);
	if (myTrack == NULL)
		goto bail;
	
	// create an empty handle; this will be resized by PutMovieIntoTypedHandle
	myHandle = NewHandle(0);
	if (myHandle == NULL)
		goto bail;
	
	// use the default progress procedure, if any
	SetMovieProgressProc(theMovie, (MovieProgressUPP)-1L, 0);
	
	// extract the sound track and put it into the handle
	myErr = PutMovieIntoTypedHandle(
							theMovie,					// the movie to convert
							myTrack,					// save only the specified track
							soundListRsrc,				// make it a sound resource handle
							myHandle,					// the handle into which to put the audio data
							0,							// start time
							GetTrackDuration(myTrack),	// ending time
							0L,							// flags
							NULL);						// no specific component
	if (myErr != noErr)
		goto bail;
		
	// to prove we really did create the handle, play it
	SndPlay(NULL, (SndListHandle)myHandle, false);
	
bail:
	if (myHandle != NULL)
		DisposeHandle(myHandle);
}


//////////
//
// SndSnip_PlayWAVEFileWithQuickTime
// Open and play a WAVE file using the QuickTime API.
//
//////////

void SndSnip_PlayWAVEFileWithQuickTime (void)
{
	StandardFileReply		myReply;
	SFTypeList				myTypeList = {kQTFileTypeWave, 0, 0, 0};
	short					myRefNum;
	
	// elicit a file from the user
	StandardGetFile(NULL, 1, myTypeList, &myReply);
	if (myReply.sfGood) {
		Movie				myMovie;
		
		OpenMovieFile(&myReply.sfFile, &myRefNum, fsRdPerm);
		NewMovieFromFile(&myMovie, myRefNum, NULL, (StringPtr)NULL, newMovieActive, NULL);
		if (myRefNum != 0)
			CloseMovieFile(myRefNum);
			
		// play the movie once thru
		StartMovie(myMovie);
		while (!IsMovieDone(myMovie))
			MoviesTask(myMovie, 0);
		DisposeMovie(myMovie);
	}
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Volume routines.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////
//
// SndSnip_SetVolumeOfSoundTrack
// Set the volume level of the first sound track in a QuickTime movie.
//
// The range for the theVolume parameter is 0 (no volume) to 256 (full volume).
// You can also pass negative values (e.g., -128). No sound is produced for negative
// values, but they are useful for maintaining the absolute value of a volume.
//
//////////

void SndSnip_SetVolumeOfSoundTrack (Movie theMovie, short theVolume)
{
	Track			myTrack = NULL;
	
	// find the first sound track in the specified movie
	myTrack = GetMovieIndTrackType(theMovie, 1, AudioMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly);
	if (myTrack == NULL)
		return;
	
	// set the volume of the track
	SetTrackVolume(myTrack, theVolume);
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Double-buffering routines.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////
//
// SndSnip_PlaySoundResourceUsingBufferCmds
// Use the bufferCmd sound command to continuously play a large buffer of audio data.
//
// NOTE: This routine exists solely to illustrate one way to use bufferCmd sound commands
// to play a buffer of audio data continuously; here, we read the audio data from a resource
// and then hand chunks of it to the Sound Manager using bufferCmds. OF COURSE we could have
// played the entire buffer in one fell swoop by calling SndPlay. The idea is that a real
// application would read the audio data from disk in bite-sized chunks and use the techniques
// illustrated here to play that data.
//
//////////

OSErr SndSnip_PlaySoundResourceUsingBufferCmds (void)
{
	Handle					myHandle = NULL;
	SoundComponentData		myResourceInfo;
	unsigned long			myNumFrames;
	unsigned long			myOffset;
	Ptr						myPtr = NULL;
	Size					mySize;
	long					myFreq;
	SndChannelPtr			mySndChannel = NULL;
	SndCallBackUPP			mySndCallBackProc;
	unsigned long			myBufferSize;			// the size of the buffer we pass to each bufferCmd command
	OSErr					myErr = memFullErr;

	// get the resource data; we assume this resource contains sampled sound data
	myHandle = GetResource(soundListRsrc, kSampleResourceID);
	if (myHandle == NULL)
		goto bail;
		
	// get info from the sound resource header
	myErr = ParseSndHeader((SndListHandle)myHandle, &myResourceInfo, &myNumFrames, &myOffset);
	if (myErr != noErr)
		goto bail;
	
	// get the base frequency from the sampled sound header
	myFreq = SndSnip_GetSndBaseFrequency(myHandle);
	
	// lock the handle, so we can use a pointer to cruise thru the sound data
	HLockHi(myHandle);

	myPtr = *myHandle + myOffset;
	mySize = GetHandleSize(myHandle) - myOffset;
	myBufferSize = mySize / kNumberOfBufferChunks;
	
	// allocate a routine descriptor for our sound callback routine
	mySndCallBackProc = NewSndCallBackProc(SndSnip_CallbackProc);

	// create storage for a new sound channel
	mySndChannel = (SndChannelPtr)NewPtrClear(sizeof(SndChannel));
	if (mySndChannel == NULL)
		goto bail;
	
	// set the number of commands in the sound channel queue;
	// pass the application's A5 value in the userInfo field
	mySndChannel->qLength = kNumberOfCmdsInQueue;
#if TARGET_OS_MAC	
	mySndChannel->userInfo = SetCurrentA5();
#endif
	
	// create the sound channel
	myErr = SndNewChannel(&mySndChannel, sampledSynth, initMono, mySndCallBackProc);
	if (myErr != noErr) {
		DisposePtr((Ptr)mySndChannel);
		goto bail;
	}
	
	// the basic idea is this: fill two buffers with data and pass them to the Sound Manager
	// using the bufferCmd sound command; each bufferCmd is immediately followed in the queue
	// by a callBackCmd sound command; a callBackCmd triggers the _CheckBuffers routine, which
	// installs another buffer of data and an associated callBackCmd
	
	// configure the first sound header
	gSndHeader1.samplePtr = myPtr;
	gSndHeader1.length = myBufferSize;
	gSndHeader1.sampleRate = myResourceInfo.sampleRate;
	gSndHeader1.loopStart = 0;
	gSndHeader1.loopEnd = 0;
	gSndHeader1.encode = stdSH;
	gSndHeader1.baseFrequency = myFreq;
	
	// install the first bufferCmd, to play the first chunk of the buffer
	myErr = SndSnip_InstallBufferCmd(mySndChannel, &gSndHeader1);

	// install the first callBackCmd
	myErr = SndSnip_InstallCallbackCmd(mySndChannel, 0, (long)&gSndHeader1);
	
	// configure the second sound header
	gSndHeader2.samplePtr = myPtr + myBufferSize;
	gSndHeader2.length = myBufferSize;
	gSndHeader2.sampleRate = myResourceInfo.sampleRate;
	gSndHeader2.loopStart = 0;
	gSndHeader2.loopEnd = 0;
	gSndHeader2.encode = stdSH;
	gSndHeader2.baseFrequency = myFreq;
	
	// install the second bufferCmd, to play the second chunk of the buffer
	myErr = SndSnip_InstallBufferCmd(mySndChannel, &gSndHeader2);

	// install the second callBackCmd
	myErr = SndSnip_InstallCallbackCmd(mySndChannel, 1, (long)&gSndHeader2);
	
bail:
	return(myErr);
}


//////////
//
// SndSnip_CallbackProc
// Handle callback messages. Keep it quick, okay?
//
//////////

PASCAL_RTN void SndSnip_CallbackProc (SndChannelPtr theChannel, SndCommand *theCommand)
{
#if TARGET_OS_MAC	
	long		myA5;
	
	myA5 = SetA5(theChannel->userInfo);
#endif	

	// in this sample code, param1 of theCommand is the 0-based index of the chunk of the buffer
	// that just finished playing, and param2 is a pointer to the sound header for that chunk
	gCurrChunkDirty = true;
	gCurrChunkIndex = theCommand->param1;
	gCurrChunkHeader = (SoundHeaderPtr)(theCommand->param2);
	gCurrChannel = theChannel;
		
#if TARGET_OS_MAC	
	myA5 = SetA5(myA5);
#endif	
}


//////////
//
// SndSnip_CheckBuffers
// Handle callback messages.
//
// This function needs to be called periodically. On MacOS, you could call this function
// every time thru your event loop. On Windows, you'll need to use a different strategy;
// for instance, you could install a timer that is called often enough that the bufferCmd
// gets installed before the currently-playing buffer is exhausted.
//
//////////

void SndSnip_CheckBuffers (void)
{
	if (gCurrChunkDirty) {

		// install next buffer of data, if there is one
		if (gCurrChunkIndex < (kNumberOfBufferChunks - 2)) {
		
			gCurrChunkHeader->samplePtr = gCurrChunkHeader->samplePtr + (2 * (gCurrChunkHeader->length));
			
			// install a bufferCmd command, to play the next buffer of data			
			SndSnip_InstallBufferCmd(gCurrChannel, gCurrChunkHeader);

			// install a callBackCmd command, to be called when this buffer is finished playing		
			SndSnip_InstallCallbackCmd(gCurrChannel, gCurrChunkIndex + 2, (long)gCurrChunkHeader);
		}
	}
	
	gCurrChunkDirty = false;
}


//////////
//
// SndSnip_InstallBufferCmd
// Install a bufferCmd sound command to play the specified buffer of data.
//
//////////

static OSErr SndSnip_InstallBufferCmd (SndChannelPtr theChannel, SoundHeaderPtr theHeaderPtr)
{
	SndCommand		mySndCommand;
	
	mySndCommand.cmd = bufferCmd;
	mySndCommand.param1 = 0;
	mySndCommand.param2 = (long)theHeaderPtr;
	return(SndDoCommand(theChannel, &mySndCommand, kWait));
}


//////////
//
// SndSnip_InstallCallbackCmd
// Install a callBackCmd sound command.
//
//////////

static OSErr SndSnip_InstallCallbackCmd (SndChannelPtr theChannel, short theParam1, long theParam2)
{
	SndCommand		mySndCommand;
	
	mySndCommand.cmd = callBackCmd;
	mySndCommand.param1 = theParam1;
	mySndCommand.param2 = theParam2;
	return(SndDoCommand(theChannel, &mySndCommand, kWait));
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Sample sound information routines.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////
//
// SndSnip_GetSoundHeader
// Returns a pointer to the sound header in a sampled sound resource.
//
//////////

SoundHeaderPtr SndSnip_GetSoundHeader (Handle theSndHandle)
{
	SoundHeaderPtr		mySndHeader = NULL;
	long				myOffset = 0;
	OSErr				myErr = noErr;
	
	myErr = GetSoundHeaderOffset((SndListHandle)theSndHandle, &myOffset);
	if (myErr == noErr)
		mySndHeader = (SoundHeaderPtr)((long)*theSndHandle + myOffset);

	return(mySndHeader);
}


//////////
//
// SndSnip_GetSndBaseFrequency
// Returns the base frequency of a sampled sound.
//
//////////

long SndSnip_GetSndBaseFrequency (Handle theSndHandle)
{
	SoundHeaderPtr		mySndHeader;
	long				myBaseFreq = kMiddleC;		// a reasonable default
	
	mySndHeader = SndSnip_GetSoundHeader(theSndHandle);
	if (mySndHeader != NULL)
		myBaseFreq = mySndHeader->baseFrequency;

	return(myBaseFreq);
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Settings routines.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////
//
// SndSnip_GetHardwareSettings
// Get the current hardware settings.
//
// Based on the function GetHardwareSettings by Kip Olson (develop, issue 24).
//
//////////

OSErr SndSnip_GetHardwareSettings (SndChannelPtr theChannel, SoundComponentData *theInfo)
{
	NumVersion			myVersion;
	OSErr				myErr = noErr;
	
	// SndGetInfo is available only in Sound Manager 3.1 or later,	
	// so check for an appropriate Sound Manager version
	myVersion = SndSoundManagerVersion();

	if (!SndSnip_CheckVersionNumber(&myVersion, 3, 1, 0))
		goto bail;
	
	myErr = SndGetInfo(theChannel, siNumberChannels, &theInfo->numChannels);
	if (myErr != noErr)
		goto bail;
	
	myErr = SndGetInfo(theChannel, siSampleRate, &theInfo->sampleRate);
	if (myErr != noErr)
		goto bail;
	
	myErr = SndGetInfo(theChannel, siSampleSize, &theInfo->sampleSize);
	if (myErr != noErr)
		goto bail;
	
	if (theInfo->sampleSize == 8)
		theInfo->format = kOffsetBinary;
	else
		theInfo->format = kTwosComplement;

bail:		
	return(myErr);
}


//////////
//
// SndSnip_GetAudioSettings
// Get the settings for the first audio track in the specified movie.
//
//////////

OSErr SndSnip_GetAudioSettings (Movie theMovie, SoundComponentData *theInfo)
{
	Track						myTrack = NULL;
	Media						myMedia = NULL;
	SoundDescriptionHandle		myDesc = NULL;
	OSErr						myErr = paramErr;
	
	// get the first sound track in the specified movie
	myTrack = GetMovieIndTrackType(theMovie, 1, AudioMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly);
	if (myTrack == NULL)
		goto bail;
	
	myMedia = GetTrackMedia(myTrack);
	if (myMedia == NULL)
		goto bail;

	myDesc = (SoundDescriptionHandle)NewHandle(0);
	if (myDesc == NULL)
		goto bail;

	GetMediaSampleDescription(myMedia, 1, (SampleDescriptionHandle)myDesc);
	if (GetHandleSize((Handle)myDesc) == 0)
		goto bail;

	theInfo->numChannels = (**myDesc).numChannels;
	theInfo->sampleRate = (**myDesc).sampleRate;
	theInfo->sampleSize = (**myDesc).sampleSize;
	theInfo->format = (**myDesc).dataFormat;
	
	myErr = noErr;
	
bail:

	if (myDesc != NULL)
		DisposeHandle((Handle)myDesc);
		
	return(myErr);
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Sound information routines.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////
//
// SndSnip_HasSoundManager3_1
// Returns true if the installed version of the Sound Manager is at least 3.1.
//
//////////

Boolean SndSnip_HasSoundManager3_1 (void)
{
	NumVersionVariant		myVersion;
	
	myVersion.parts = SndSoundManagerVersion();
	return(myVersion.whole >= 0x03100000);
}


//////////
//
// SndSnip_CheckVersionNumber
// Returns true if the given version number is compatible with
// (that is, not older than) version theMajor.theMinor.theBug.
//
//////////

Boolean SndSnip_CheckVersionNumber (
	const NumVersion		*theVersion,
	UInt8					theMajor,
	UInt8					theMinor,
	UInt8					theBug)
{
	if (theVersion->majorRev != theMajor)
		return(theVersion->majorRev > theMajor);
	else
		return(theVersion->minorAndBugRev >= theMinor << 4 | theBug);
}


//////////
//
// SndSnip_GetVolume
// Get the current left and right volumes of a sound channel.
//
// Based on the function GetVolume by Kip Olson (develop, issue 24).
//
//////////

OSErr SndSnip_GetVolume (SndChannelPtr theChannel, unsigned short *theLeftVol, unsigned short *theRightVol)
{
	SndCommand			mySndCommand;
	unsigned long		myVolume;
	OSErr				myErr = noErr;

	// getVolumeCmd is available only in Sound Manager version 3.0 and later;
	// you might want to check for a correct version here

	mySndCommand.cmd = getVolumeCmd;
	mySndCommand.param1 = 0;
	mySndCommand.param2 = (long)&myVolume;
	myErr = SndDoImmediate(theChannel, &mySndCommand);
	
	if (myErr == noErr) {
		*theLeftVol = myVolume & 0x0000ffff;
		*theRightVol = myVolume >> 16;
	}
	
	return(myErr);
}


//////////
//
// SndSnip_SetVolume
// Set the left and right volumes of a sound channel.
//
// Based on the function SetVolume by Kip Olson (develop, issue 24).
//
//////////

OSErr SndSnip_SetVolume (SndChannelPtr theChannel, unsigned short theLeftVol, unsigned short theRightVol)
{
	SndCommand			mySndCommand;
	OSErr				myErr = noErr;

	// volumeCmd is available only in Sound Manager version 3.0 and later;
	// you might want to check for a correct version here
		
	mySndCommand.cmd = volumeCmd;
	mySndCommand.param1 = 0;
	mySndCommand.param2 = (theRightVol << 16) | theLeftVol;
	
	myErr = SndDoImmediate(theChannel, &mySndCommand);
	return(myErr);
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Movie export component routines.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////
//
// SndSnip_ConvertWAVEFormats
// Convert an 8-bit, 22 kHz WAVE file to a 16-bit, 22 kHz IMAPCM WAVE file.
//
//////////

OSErr SndSnip_ConvertWAVEFormats (Movie theMovie, FSSpec *theFile)
{
	ComponentInstance			myComponent = NULL;
	SoundDescriptionHandle		myDesc = NULL;
	ComponentResult				myErr = badComponentType;

	// open a movie export component
	myComponent = OpenDefaultComponent(MovieExportType, kQTFileTypeWave);
	if (myComponent == NULL)
		goto bail;

	// create and fill in a sound description
	myDesc = (SoundDescriptionHandle)NewHandleClear(sizeof(SoundDescription));
	if (myDesc == NULL) {
		myErr = MemError();
		goto bail;
	}
	
	(**myDesc).descSize = sizeof(SoundDescription);
	(**myDesc).sampleSize = 16;
	(**myDesc).sampleRate = rate22050hz;
	(**myDesc).dataFormat = k16BitLittleEndianFormat;
	
	// tell the export component to use the specified audio characteristics
	myErr = MovieExportSetSampleDescription(myComponent, (SampleDescriptionHandle)myDesc, SoundMediaType);
	if (myErr != noErr)
		goto bail;
		
	// export the movie into a file
	myErr = ConvertMovieToFile(	
						theMovie,					// the movie to convert
						NULL,						// all tracks in the movie
						theFile,					// the output file
						kQTFileTypeWave,			// the output file type
						FOUR_CHAR_CODE('TVOD'),		// the output file creator
						smSystemScript,				// the script
						NULL, 						// no resource ID to be returned
						0L,							// no flags
						myComponent);				// the export component

bail:
	// dispose of any storage we allocated
	if (myComponent != NULL)
		CloseComponent(myComponent);
		
	if (myDesc != NULL)
		DisposeHandle((Handle)myDesc);

	return((OSErr)myErr);
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Standard sound compression dialog routines.
//
// These routines illustrate how to use QuickTime's standard sound compression dialog routines
// to get compression settings from the user and to compress a sound using those settings. See
// Chapter 20 of QuickTime 3 Reference for information on the standard sound compression dialog
// routines.
//
// These routines show how to display the standard sound compression dialog box directly; you can
// also display the dialog box indirectly by using ConvertMovieToFile with the showUserSettingsDialog
// flag set; see SndSnip_SaveSoundTrackAsAnyTypeFile above for an example.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////
//
// SndSnip_PromptUserForAudioFileAndCompress
// Let the user select an audio file and select its compression settings; then compress it.
//
//////////

void SndSnip_PromptUserForAudioFileAndCompress (void)
{
	SFTypeList					myTypeList;
	StandardFileReply			myReply;
	short						myRefNum = 0;
	SoundComponentData			mySrcInfo;
	SoundComponentData			myDstInfo;
	unsigned long				myNumFrames = 0;
	unsigned long				myDataOffset = 0;
	ComponentInstance			myComponent = NULL;
	OSErr						myErr = noErr;

	//////////
	//
	// have the user select an AIFF or AIFF-C file
	//
	//////////

	myTypeList[0] = kQTFileTypeAIFF;
	myTypeList[1] = kQTFileTypeAIFC;

	StandardGetFilePreview(NULL, 2, myTypeList, &myReply);
	if (!myReply.sfGood)
		goto bail;
	
	//////////
	//
	// open the selected sound file and get the characteristics of the sound
	//
	//////////
	
	myErr = FSpOpenDF(&myReply.sfFile, fsCurPerm, &myRefNum);
	if (myErr != noErr)
		goto bail;

	myErr = ParseAIFFHeader(myRefNum, &mySrcInfo, &myNumFrames, &myDataOffset);
	if (myErr != noErr)
		goto bail;
	
	// zero out fields we're not interested in
	mySrcInfo.flags = 0L;
	mySrcInfo.sampleCount = 0L;
	mySrcInfo.buffer = NULL;
	mySrcInfo.reserved = 0L;
	
	//////////
	//
	// display the standard sound compression dialog box
	//
	//////////
	
	// open the standard compression dialog component
	myComponent = OpenDefaultComponent(StandardCompressionType, StandardCompressionSubTypeSound);
	if (myComponent == NULL)
		goto bail;
			
	// set the initial values for the dialog box, using the current characteristics of the selected sound
	SCSetInfo(myComponent, scSoundSampleRateType, &(mySrcInfo.sampleRate));
	SCSetInfo(myComponent, scSoundSampleSizeType, &(mySrcInfo.sampleSize));
	SCSetInfo(myComponent, scSoundChannelCountType, &(mySrcInfo.numChannels));
	SCSetInfo(myComponent, scSoundCompressionType, &(mySrcInfo.format));
	
	// request sound compression settings from the user; in other words, put up the dialog box
	// (don't let "Image" in the name here fool you: it's just a relic from the fact that the
	// sound compression dialog routines were based on the existing image compression dialog routines)
	myErr = SCRequestImageSettings(myComponent);
	if (myErr == userCanceledErr)
		goto bail;

	//////////
	//
	// retrieve the compression/conversion settings selected by the user
	//
	//////////
	
	SCGetInfo(myComponent, scSoundSampleRateType, &(myDstInfo.sampleRate));
	SCGetInfo(myComponent, scSoundSampleSizeType, &(myDstInfo.sampleSize));
	SCGetInfo(myComponent, scSoundChannelCountType, &(myDstInfo.numChannels));
	SCGetInfo(myComponent, scSoundCompressionType, &(myDstInfo.format));

	// any non-compressed format appears as 'raw ', which is not what the Sound Manager expects
	if (myDstInfo.format == k8BitOffsetBinaryFormat)
		myDstInfo.format = kSoundNotCompressed;

	// zero out fields we're not interested in
	myDstInfo.flags = 0L;
	myDstInfo.sampleCount = 0L;
	myDstInfo.buffer = NULL;
	myDstInfo.reserved = 0L;
	
	//////////
	//
	// save the compressed sound in a new file
	//
	//////////
	
	SndSnip_PromptUserForDiskFileAndSaveCompressed(myRefNum, &mySrcInfo, &myDstInfo, myDataOffset, myNumFrames);
	
bail:

	if (myComponent != NULL)
		CloseComponent(myComponent);
		
	if (myRefNum != 0)
		FSClose(myRefNum);
}


//////////
//
// SndSnip_PromptUserForDiskFileAndSaveCompressed
// Let the user select a new disk file, then convert the specified audio data into that file.
//
// This routine uses the Sound Converter routines introduced in Sound Manager 3.2.
//
//////////

void SndSnip_PromptUserForDiskFileAndSaveCompressed (short theSrcRefNum, SoundComponentData *theSrcInfo, SoundComponentData *theDstInfo, unsigned long theSrcDataOffset, unsigned long theSrcNumFrames)
{
	StandardFileReply			myReply;
	SoundConverter				myConverter = NULL;
	unsigned long				myNumInputFrames, myNumInputBytes, myNumOutputBytes;
	Ptr							myInputBuffer = NULL;
	Ptr							myOutputBuffer = NULL;
	short						myDstRefNum = 0;
	StringPtr 					myPrompt = QTUtils_ConvertCToPascalString(kSaveSoundPrompt);
	StringPtr 					myFileName = QTUtils_ConvertCToPascalString(kSaveSoundFileName);
	OSErr						myErr = noErr;

	//////////
	//
	// prompt the user for a file to put the compressed sound into
	//
	//////////
	
	StandardPutFile(myPrompt, myFileName, &myReply);
	if (!myReply.sfGood)
		goto bail;

	if (myReply.sfReplacing) {
		myErr = FSpDelete(&myReply.sfFile);
		if (myErr != noErr)
			goto bail;
	}

	//////////
	//
	// open a sound converter
	//
	//////////
	
	// perform in non-real time for best quality
	theDstInfo->flags |= kNoRealtimeProcessing;			

	myErr = SoundConverterOpen(theSrcInfo, theDstInfo, &myConverter);
	if (myErr != noErr)
		goto bail;

	myErr = SoundConverterGetBufferSizes(myConverter, kNumberOfTargetBytes, &myNumInputFrames, &myNumInputBytes, &myNumOutputBytes);
	if (myErr != noErr)
		goto bail;

	//////////
	//
	// convert the sound to the desired output format
	//
	//////////
	
	// create the input and output buffers
	myInputBuffer = NewPtrClear(myNumInputBytes);
	if (myInputBuffer == NULL)
		goto bail;

	myOutputBuffer = NewPtrClear(myNumOutputBytes);
	if (myOutputBuffer == NULL)
		goto bail;

	// create and open the output file
	myErr = FSpCreate(&myReply.sfFile, FOUR_CHAR_CODE('TVOD'), kQTFileTypeAIFC, smSystemScript);
	if (myErr != noErr)
		goto bail;

	myErr = FSpOpenDF(&myReply.sfFile, fsRdWrPerm, &myDstRefNum);
	if (myErr != noErr)
		goto bail;

	SetEOF(myDstRefNum, 0L);
	
	// convert the audio data
	myErr = SoundConverterBeginConversion(myConverter);
	if (myErr == noErr) {
		long				myInputFileSize = 0L;
		unsigned long		myActOutputFrames = 0L;
		unsigned long		myActOutputSize = 0L;
		unsigned long		myAddOutputFrames = 0L;
		unsigned long		myAddOutputSize = 0L;
		long				myCurrentFrames = 0L;
		long				myNumFramesLeft = theSrcNumFrames;
		long				myBytesRead = 0L;
		long				myBytesWritten = 0L;
		long				myFramesWritten = 0L;
		
		// set up the input file for reading
		GetEOF(theSrcRefNum, &myInputFileSize);

		myErr = SetFPos(theSrcRefNum, fsFromStart, theSrcDataOffset);
		if (myErr != noErr)
			goto bail;

		myInputFileSize -= theSrcDataOffset;

		// set up a preliminary AIFF header for the output file;
		// we'll update the actual values later....
		myErr = SetupAIFFHeader(myDstRefNum,
								theDstInfo->numChannels,
								theDstInfo->sampleRate,
								theDstInfo->sampleSize,
								theDstInfo->format,
								0L,
								0L);
		if (myErr != noErr)
			goto bail;
		
		myCurrentFrames = myNumInputFrames;
		
		// loop through buffers of size myNumInputFrames
		while (myNumFramesLeft > 0) {
		
			// read a buffer of data from the input file		
			myBytesRead = myNumInputBytes;
			myErr = FSRead(theSrcRefNum, &myBytesRead, myInputBuffer);
			if ((myErr != noErr) && (myErr != eofErr))
				goto bail;

			// convert the buffer
			myErr = SoundConverterConvertBuffer(myConverter,
												myInputBuffer,
												myCurrentFrames,
												myOutputBuffer,
												&myActOutputFrames,
												&myActOutputSize);
			if (myErr != noErr)
				goto bail;

			// write the converted data into the output file
			myErr = FSWrite(myDstRefNum, (long *)&myActOutputSize, myOutputBuffer);
			if (myErr != noErr)
				goto bail;
	
			myBytesWritten += myActOutputSize;
			myFramesWritten += myActOutputFrames;
		
			// calculate remaining frames and adjust myCurrentFrames for last buffer
			myNumFramesLeft -= myCurrentFrames;
			if (myNumFramesLeft < myNumInputFrames)
				myCurrentFrames = myNumFramesLeft;
		}

		// end the conversion, and see if we get back a few more bytes of data	
		myErr = SoundConverterEndConversion(myConverter, myOutputBuffer, &myAddOutputFrames, &myAddOutputSize);
		if (myErr != noErr)
			goto bail;
	
		// if we got any more data, write it to disk	
		if (myAddOutputFrames != 0L) {
			myErr = FSWrite(myDstRefNum, (long *)&myAddOutputSize, myOutputBuffer);
			if (myErr != noErr)
				goto bail;
		}
		
		myErr = SetFPos(myDstRefNum, fsFromStart, 0);
		if (myErr != noErr)
			goto bail;
	
		// update AIFF header
		myErr = SetupAIFFHeader(
							myDstRefNum,
							theDstInfo->numChannels,
							theDstInfo->sampleRate,
							theDstInfo->sampleSize,
							theDstInfo->format,
							myBytesWritten + myAddOutputSize,
							myFramesWritten + myAddOutputFrames);
	}

bail:

	free(myPrompt);
	free(myFileName);

	if (myConverter != NULL)
		SoundConverterClose(myConverter);	

	if (myInputBuffer != NULL)
		DisposePtr(myInputBuffer);

	if (myOutputBuffer != NULL)
		DisposePtr(myOutputBuffer);
		
	if (myDstRefNum != 0)
		FSClose(myDstRefNum);

#if TARGET_OS_MAC
	// make sure the file data gets written out to disk
	FlushVol("\p", myReply.sfFile.vRefNum);
#endif
}
