//////////
//
//	File:		QTEffects.c
//
//	Contains:	QuickTime video effect support for QuickTime movies.
//				Based on existing samples QTShowEffect, MakeEffectMovie, and AddEffectSeg.
//
//	Written by:	Tim Monroe
//
//	Copyright:	© 1997-2001 by Apple Computer, Inc., all rights reserved.
//
//	Change History (most recent first):
//
//	   <1>	 	11/06/97	rtm		first file; parts from QTShowEffect, MakeEffectMovie, and AddEffectSeg
//	   
//
//////////

//////////
//
// header files
//
//////////

#include "QTEffects.h"


//////////
//
// global variables
//
//////////

QTParameterDialog			gEffectsDialog = 0L;			// identifier for the standard parameter dialog box
int							gNumberOfSteps = k30StepsCount;

QTAtomContainer				gEffectDesc = NULL;			// effects description
QTAtomContainer				gEffectList = NULL;
PicHandle					gPosterA = NULL;
PicHandle					gPosterB = NULL;
Movie						gSrcMovies[kMaxNumSources] = {NULL, NULL, NULL};
Track						gSrcTracks[kMaxNumSources] = {NULL, NULL, NULL};
UInt16						gSpecCount = 0;		
Boolean						gCopyMovieMedia = false;		// should we copy the movie media into the new effects movie?
Boolean						gDoneWithDialog = false;		// are we done using the effects parameters dialog box?


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// High-level functions.
//
// These functions are called by QTApp_HandleMenu.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////
//
// QTEffects_MakeFireMovie
// Make a movie containing the fire effect.
//
//////////

void QTEffects_MakeFireMovie (void)
{
	FSSpec					myFile;
	Boolean					myIsSelected = false;
	Boolean					myIsReplacing = false;	
	StringPtr 				myPrompt = QTUtils_ConvertCToPascalString(kEffectsSaveMoviePrompt);
	StringPtr 				myFileName = QTUtils_ConvertCToPascalString(kEffectsFireMovieFileName);
	Movie					myMovie = NULL;
	short					myMovieRefNum = kInvalidFileRefNum;
	short					myResID = movieInDataForkResID;
	Track					myEffectTrack = NULL;
	Media					myEffectMedia = NULL;
	QTAtomContainer			myEffectDesc = NULL;
	ImageDescriptionHandle	mySampleDesc = NULL;
	TimeValue				mySampleTime = 0;
	long					myFlags = createMovieFileDeleteCurFile | createMovieFileDontCreateResFile;
	OSType					myType = FOUR_CHAR_CODE('none');
	OSErr					myErr = noErr;

	// ask the user for the name of the new movie file
	QTFrame_PutFile(myPrompt, myFileName, &myFile, &myIsSelected, &myIsReplacing);
	if (!myIsSelected)
		goto bail;				// deal with user cancelling

	// create a movie file for the destination movie
	myErr = CreateMovieFile(&myFile, sigMoviePlayer, smSystemScript, myFlags, &myMovieRefNum, &myMovie);
	if (myErr != noErr)
		goto bail;
	
	// select the "no controller" movie controller
	myType = EndianU32_NtoB(myType);
	SetUserDataItem(GetMovieUserData(myMovie), &myType, sizeof(myType), kUserDataMovieControllerType, 1);
		
	//////////
	//
	// create the effects track
	//
	//////////
	
	myEffectTrack = NewMovieTrack(myMovie, IntToFixed(kDefaultTrackWidth), IntToFixed(kDefaultTrackHeight), kNoVolume);
	if (myEffectTrack == NULL)
		goto bail;

	myEffectMedia = NewTrackMedia(myEffectTrack, VideoMediaType, kOneSecond, NULL, 0);
	if (myEffectMedia == NULL)
		goto bail;

	//////////
	//
	// create the sample description
	//
	//////////
	
	mySampleDesc = EffectsUtils_MakeSampleDescription(kFireCodecType, kDefaultTrackWidth, kDefaultTrackHeight);
	if (mySampleDesc == NULL)
		goto bail;
	
	//////////
	//
	// create the effect description
	//
	//////////
	
	myEffectDesc = EffectsUtils_CreateEffectDescription(kFireCodecType, kSourceNoneName, kSourceNoneName, kSourceNoneName);
	if (myEffectDesc == NULL)
		goto bail;
	
	// add a parameter atom to the effects sample
	if (0) {
		long		myRate = 11;	// this one goes to 11....
		
		myRate = EndianS32_NtoB(myRate);
		myErr = QTInsertChild(myEffectDesc, kParentAtomIsContainer, FOUR_CHAR_CODE('decy'), 1, 0, sizeof(myRate), &myRate, NULL);
	}
	
	//////////
	//
	// add the effect description as a sample to the effects track media
	//
	//////////
	
	myErr = BeginMediaEdits(myEffectMedia);
	if (myErr != noErr)
		goto bail;

	myErr = AddMediaSample(myEffectMedia, myEffectDesc, 0, GetHandleSize(myEffectDesc), kEffectMovieDuration, (SampleDescriptionHandle)mySampleDesc, 1, 0, &mySampleTime);
	if (myErr != noErr)
		goto bail;

	myErr = EndMediaEdits(myEffectMedia);
	if (myErr != noErr)
		goto bail;

	myErr = InsertMediaIntoTrack(myEffectTrack, 0, mySampleTime, kEffectMovieDuration, fixed1);
	if (myErr != noErr)
		goto bail;

	AddMovieResource(myMovie, myMovieRefNum, &myResID, NULL);
	
bail:
	if (myMovieRefNum != kInvalidFileRefNum)
		CloseMovieFile(myMovieRefNum);

	if (myMovie != NULL)
		DisposeMovie(myMovie);
	
	if (myEffectDesc != NULL)
		QTDisposeAtomContainer(myEffectDesc);
	
	if (mySampleDesc != NULL)
		DisposeHandle((Handle)mySampleDesc);
	
	free(myPrompt);
	free(myFileName);

	return;
}


//////////
//
// QTEffects_AddFilmNoiseToMovie
// Add the film noise effect to the (first video track in the) specified movie.
//
//////////

void QTEffects_AddFilmNoiseToMovie (Movie theMovie)
{
	ImageDescriptionHandle	mySampleDesc = NULL;
	Track					mySrcTrack = NULL;
	Track					myTrack = NULL;
	Media					myMedia = NULL;
	QTAtomContainer			myInputMap = NULL;
	QTAtomContainer			myEffectDesc = NULL;
	TimeValue				mySampleTime;
	Fixed					myWidth, myHeight;
	OSErr					myErr = noErr;

	if (theMovie == NULL)
		goto bail;

	// get the first visual track in the movie
	mySrcTrack = GetMovieIndTrackType(theMovie, 1, kCharacteristicCanSendVideo, movieTrackCharacteristic | movieTrackEnabledOnly);
	if (mySrcTrack == NULL)
		goto bail;

	// get the width and height of that track
	GetTrackDimensions(mySrcTrack, &myWidth, &myHeight);	
	
	//////////
	//
	// create the effects track
	//
	//////////
	
	myTrack = NewMovieTrack(theMovie, myWidth, myHeight, kNoVolume);
	if (myTrack == NULL)
		goto bail;
		
	myMedia = NewTrackMedia(myTrack, VideoMediaType, kOneSecond, NULL, 0);
	if (myMedia == NULL)
		goto bail;

	//////////
	//
	// create the sample description
	//
	//////////
	
	mySampleDesc = EffectsUtils_MakeSampleDescription(kFilmNoiseImageFilterType, FixedToInt(myWidth), FixedToInt(myHeight));
	if (mySampleDesc == NULL)
		goto bail;
	
	//////////
	//
	// create the effect description
	//
	//////////
	
	myEffectDesc = EffectsUtils_CreateEffectDescription(kFilmNoiseImageFilterType, kSourceOneName, kSourceNoneName, kSourceNoneName);
	if (myEffectDesc == NULL)
		goto bail;

	//////////
	//
	// add the effect description as a sample to the effects track media
	//
	//////////

	myErr = BeginMediaEdits(myMedia);
	if (myErr != noErr)
		goto bail;

	myErr = AddMediaSample(myMedia, (Handle)myEffectDesc, 0, GetHandleSize((Handle)myEffectDesc), GetMediaDuration(GetTrackMedia(mySrcTrack)), (SampleDescriptionHandle)mySampleDesc, 1, 0, &mySampleTime);
	if (myErr != noErr)
		goto bail;

	myErr = EndMediaEdits(myMedia);
	if (myErr != noErr)
		goto bail;
	
	//////////
	//
	// create the input map and add references for the first effects track
	//
	//////////
	
	myErr = QTNewAtomContainer(&myInputMap);
	if (myErr != noErr)
		goto bail;
		
	myErr = EffectsUtils_AddTrackReferenceToInputMap(myInputMap, myTrack, mySrcTrack, kSourceOneName);
	if (myErr != noErr)
		goto bail;
		
	// add the input map to the effects track
	myErr = SetMediaInputMap(myMedia, myInputMap);
	if (myErr != noErr)
		goto bail;

	//////////
	//
	// add the media to the track
	//
	//////////

	myErr = InsertMediaIntoTrack(myTrack, 0, mySampleTime, GetMediaDuration(myMedia), fixed1);
	
bail:		
	if (myEffectDesc != NULL)
		QTDisposeAtomContainer(myEffectDesc);
	
	if (mySampleDesc != NULL)
		DisposeHandle((Handle)mySampleDesc);
	
	if (myInputMap != NULL)
		QTDisposeAtomContainer(myInputMap);
	
	return;
}


//////////
//
// QTEffects_AddFilmNoiseToImage
// Add the film noise effect to the image associated with the specified window object.
//
//////////

void QTEffects_AddFilmNoiseToImage (WindowObject theWindowObject)
{
	ApplicationDataHdl			myAppData = NULL;
	GraphicsImportComponent		myImporter = NULL;
	Rect						myRect;
	
	if (theWindowObject == NULL)
		return;

	myAppData = (ApplicationDataHdl)(**theWindowObject).fAppData;
	if (myAppData == NULL)
		return;
	
	myImporter = (**theWindowObject).fGraphicsImporter;
	if (myImporter == NULL)
		return;

	GraphicsImportGetBoundsRect(myImporter, &myRect);

	// set up the initial state
	(**myAppData).fSampleDescription = EffectsUtils_MakeSampleDescription(kImageEffectType, myRect.right - myRect.left, myRect.bottom - myRect.top);
	(**myAppData).fEffectDescription = EffectsUtils_CreateEffectDescription(kImageEffectType, kSourceOneName, kSourceNoneName, kSourceNoneName);
	(**myAppData).fEffectType        = kImageEffectType;
	(**myAppData).fEffectSequenceID  = 0L;
	(**myAppData).fTimeBase          = NULL;
	
	QTEffects_SetUpEffectSequence(theWindowObject);
}


//////////
//
// QTEffects_MakePenguinMovie
// Make a movie that cross-fades the penguin picture in from a white initial frame.
//
//////////

void QTEffects_MakePenguinMovie (void)
{
	ImageDescriptionHandle	mySampleDesc = NULL;
	short					myMovieRefNum = kInvalidFileRefNum;
	short					myResID = movieInDataForkResID;
	Movie					myMovie = NULL;
	Track					myTrack = NULL;
	Track					mySrc1Track = NULL;
	Track					mySrc2Track = NULL;
	Media					myMedia;
	GWorldPtr				myGW1 = NULL;
	GWorldPtr				myGW2 = NULL;
	FSSpec					myFile;
	Boolean					myIsSelected = false;
	Boolean					myIsReplacing = false;	
	QTAtomContainer			myInputMap = NULL;
	QTAtomContainer			myEffectDesc = NULL;
	TimeValue				mySampleTime;
	long					myFlags = createMovieFileDeleteCurFile | createMovieFileDontCreateResFile;
	StringPtr 				myPrompt = QTUtils_ConvertCToPascalString(kEffectsSaveMoviePrompt);
	StringPtr 				myFileName = QTUtils_ConvertCToPascalString(kEffectsPenguinMovieFileName);
	OSErr					myErr = noErr;
	
	// ask the user for the name of the new movie file
	QTFrame_PutFile(myPrompt, myFileName, &myFile, &myIsSelected, &myIsReplacing);
	if (!myIsSelected)
		goto bail;				// deal with user cancelling

	// create a movie file for the destination movie
	myErr = CreateMovieFile(&myFile, sigMoviePlayer, smCurrentScript, myFlags, &myMovieRefNum, &myMovie);
	if (myErr != noErr)
		goto bail;
	
	//////////
	//
	// create the effects track
	//
	//////////
	
	myTrack = NewMovieTrack(myMovie, IntToFixed(kPenguinTrackWidth), IntToFixed(kPenguinTrackHeight), kNoVolume);
	if (myTrack == NULL)
		goto bail;
		
	myMedia = NewTrackMedia(myTrack, VideoMediaType, kOneSecond, NULL, 0);
	if (myMedia == NULL)
		goto bail;

	//////////
	//
	// create the sample description
	//
	//////////
	
	mySampleDesc = EffectsUtils_MakeSampleDescription(kCrossFadeTransitionType, kPenguinTrackWidth, kPenguinTrackHeight);
	if (mySampleDesc == NULL)
		goto bail;

	//////////
	//
	// create the effect description
	//
	//////////
	
	myEffectDesc = EffectsUtils_CreateEffectDescription(kCrossFadeTransitionType, kSourceOneName, kSourceTwoName, kSourceNoneName);
	if (myEffectDesc == NULL)
		goto bail;

	//////////
	//
	// add the video tracks of the source pictures to the effects movie
	//
	//////////
	
	myErr = EffectsUtils_GetPictResourceAsGWorld(kWhiteRectID, kPenguinTrackWidth, kPenguinTrackHeight, 0, &myGW1);
	if (myErr != noErr)
		goto bail;

	myErr = EffectsUtils_GetPictResourceAsGWorld(kPenguinPictID, kPenguinTrackWidth, kPenguinTrackHeight, 0, &myGW2);
	if (myErr != noErr)
		goto bail;

	// the video tracks used as sources for the effect should start at the same time as the effects track
	// and end at the same time as the effects track
	myErr = EffectsUtils_AddVideoTrackFromGWorld(&myMovie, myGW1, &mySrc1Track, 0, kEffectMovieDuration, kPenguinTrackWidth, kPenguinTrackHeight);
	if (myErr != noErr)
		goto bail;
		
	myErr = EffectsUtils_AddVideoTrackFromGWorld(&myMovie, myGW2, &mySrc2Track, 0, kEffectMovieDuration, kPenguinTrackWidth, kPenguinTrackHeight);
	if (myErr != noErr)
		goto bail;

	//////////
	//
	// add the effect description as a sample to the effects track media
	//
	//////////

	myErr = BeginMediaEdits(myMedia);
	if (myErr != noErr)
		goto bail;

	myErr = AddMediaSample(myMedia, (Handle)myEffectDesc, 0, GetHandleSize((Handle)myEffectDesc), kEffectMovieDuration, (SampleDescriptionHandle)mySampleDesc, 1, 0, &mySampleTime);
	if (myErr != noErr)
		goto bail;

	myErr = EndMediaEdits(myMedia);
	if (myErr != noErr)
		goto bail;
	
	//////////
	//
	// create the input map and add references for the first effects track
	//
	//////////
	
	myErr = QTNewAtomContainer(&myInputMap);
	if (myErr != noErr)
		goto bail;
		
	myErr = EffectsUtils_AddTrackReferenceToInputMap(myInputMap, myTrack, mySrc1Track, kSourceOneName);
	if (myErr != noErr)
		goto bail;
		
	myErr = EffectsUtils_AddTrackReferenceToInputMap(myInputMap, myTrack, mySrc2Track, kSourceTwoName);
	if (myErr != noErr)
		goto bail;

	// add the input map to the effects track
	myErr = SetMediaInputMap(myMedia, myInputMap);
	if (myErr != noErr)
		goto bail;

	//////////
	//
	// add the media to the track
	//
	//////////
	
	myErr = InsertMediaIntoTrack(myTrack, 0, mySampleTime, GetMediaDuration(myMedia), fixed1);
	if (myErr != noErr)
		goto bail;
		
	// put the movie resource into the file
	myErr = AddMovieResource(myMovie, myMovieRefNum, &myResID, NULL);
	
bail:		
	if (myGW1 != NULL)
		DisposeGWorld(myGW1);
		
	if (myGW2 != NULL)
		DisposeGWorld(myGW2);
		
	if (mySampleDesc != NULL)
		DisposeHandle((Handle)mySampleDesc);
	
	if (myMovieRefNum != kInvalidFileRefNum)
		CloseMovieFile(myMovieRefNum);

	if (myMovie != NULL)
		DisposeMovie(myMovie);

	if (myInputMap != NULL)
		QTDisposeAtomContainer(myInputMap);
	
	free(myPrompt);
	free(myFileName);

	return;
}


//////////
//
// QTEffects_AddEffectToMovieSegment
// Add the specified effect to occur at the specified time and duration.
//
//////////

OSErr QTEffects_AddEffectToMovieSegment (Movie theMovie, OSType theEffectType, long theNumSources, TimeValue theStartTime, TimeValue theDuration)
{
	Track					myVidTrack1 = NULL;
	Track					myVidTrack2 = NULL;
	Track					mySrcTrack1 = NULL;
	Track					mySrcTrack2 = NULL;
	Media					mySrcMedia1 = NULL;
	Media					mySrcMedia2 = NULL;
	Track					myEffectTrack = NULL;
	Media					myEffectMedia = NULL;
	Fixed					myWidth, myHeight;
	TimeScale				myTimeScale;
	TimeValue				mySampleTime;
	Rect					myRect;
	QTAtomContainer			myInputMap = NULL;
	QTAtomContainer			myEffectDesc = NULL;
	ImageDescriptionHandle	mySampleDesc = NULL;
	OSType					myEffectName1 = kSourceNoneName;
	OSType					myEffectName2 = kSourceNoneName;
	short					myLayer;
	OSErr					myErr = noErr;
	
	// make sure we were passed a valid movie
	if (theMovie == NULL)
		return(paramErr);
		
	//////////
	//
	// get some information about the movie
	//
	//////////
	
	myTimeScale = GetMovieTimeScale(theMovie);
	GetMovieBox(theMovie, &myRect);
	myLayer = EffectsUtils_GetFrontmostTrackLayer(theMovie, VideoMediaType);
	
	myWidth = IntToFixed((myRect.right - myRect.left));
	myHeight = IntToFixed((myRect.bottom - myRect.top));

	//////////
	//
	// retrieve the original video track(s), create the effect's source track(s) and media,
	// then set the new source track(s) to reference the data in the original video track(s)
	//
	//////////
	
	switch (theNumSources) {
		case 2:
			myVidTrack2 = GetMovieIndTrackType(theMovie, 2, VideoMediaType, movieTrackMediaType | movieTrackEnabledOnly);
			if (myVidTrack2 == NULL)
				return(paramErr);
			
			mySrcTrack2 = NewMovieTrack(theMovie, myWidth, myHeight, kNoVolume);
			if (mySrcTrack2 == NULL)
				return(paramErr);
				
			mySrcMedia2 = NewTrackMedia(mySrcTrack2, VideoMediaType, myTimeScale, NULL, 0);
			if (mySrcMedia2 == NULL)
				return(paramErr);

#if COPY_MOVIE_MEDIA
			myErr = BeginMediaEdits(mySrcMedia2);
			if (myErr != noErr)
				return(myErr);
#endif			
			myErr = CopyTrackSettings(myVidTrack2, mySrcTrack2);
			myErr = InsertTrackSegment(myVidTrack2, mySrcTrack2, theStartTime, theDuration, theStartTime);
			if (myErr != noErr)
				return(myErr);

#if COPY_MOVIE_MEDIA
			EndMediaEdits(mySrcMedia2);
#endif			
			myEffectName2 = kSourceTwoName;
			
			// note that we fall through here!
				
		case 1:
			myVidTrack1 = GetMovieIndTrackType(theMovie, 1, VideoMediaType, movieTrackMediaType | movieTrackEnabledOnly);
			if (myVidTrack1 == NULL)
				return(paramErr);
				
			mySrcTrack1 = NewMovieTrack(theMovie, myWidth, myHeight, kNoVolume);
			if (mySrcTrack1 == NULL)
				return(paramErr);
				
			mySrcMedia1 = NewTrackMedia(mySrcTrack1, VideoMediaType, myTimeScale, NULL, 0);
			if (mySrcMedia1 == NULL)
				return(paramErr);

#if COPY_MOVIE_MEDIA
			myErr = BeginMediaEdits(mySrcMedia1);
			if (myErr != noErr)
				return(myErr);
#endif			
			myErr = CopyTrackSettings(myVidTrack1, mySrcTrack1);
			myErr = InsertTrackSegment(myVidTrack1, mySrcTrack1, theStartTime, theDuration, theStartTime);
			if (myErr != noErr)
				return(myErr);
			
#if COPY_MOVIE_MEDIA
			EndMediaEdits(mySrcMedia1);
#endif			
			myEffectName1 = kSourceOneName;
			
			break;
			
		case 0:
			// for 0-source effects, we don't need to create a new source track
			break;
	
		default:
			return(paramErr);
	}
	
	//////////
	//
	// create the effects track and media
	//
	//////////

	myEffectTrack = NewMovieTrack(theMovie, myWidth, myHeight, kNoVolume);
	if (myEffectTrack == NULL)
		return(GetMoviesError());
		
	myEffectMedia = NewTrackMedia(myEffectTrack, VideoMediaType, myTimeScale, NULL, 0);
	if (myEffectMedia == NULL)
		return(GetMoviesError());
	
	// create an effect sample description
	mySampleDesc = EffectsUtils_MakeSampleDescription(theEffectType, FixedToInt(myWidth), FixedToInt(myHeight));
	if (mySampleDesc == NULL)
		goto bail;

	// create an effect description
	myEffectDesc = EffectsUtils_CreateEffectDescription(theEffectType, myEffectName1, myEffectName2, kSourceNoneName);

	// add the effect description as a sample to the effects track media
	BeginMediaEdits(myEffectMedia);

	myErr = AddMediaSample(myEffectMedia, (Handle)myEffectDesc, 0, GetHandleSize((Handle)myEffectDesc), theDuration, (SampleDescriptionHandle)mySampleDesc, 1, 0, &mySampleTime);
	if (myErr != noErr)
		goto bail;

	EndMediaEdits(myEffectMedia);
	
	// add the media sample to the effects track
	myErr = InsertMediaIntoTrack(myEffectTrack, theStartTime, mySampleTime, theDuration, fixed1);
	if (myErr != noErr)
		goto bail;

	//////////
	//
	// create the input map and add references for the source track(s)
	//
	//////////

	myErr = QTNewAtomContainer(&myInputMap);
	if (myErr != noErr)
		goto bail;
	
	if (mySrcTrack1 != NULL) {	
		myErr = EffectsUtils_AddTrackReferenceToInputMap(myInputMap, myEffectTrack, mySrcTrack1, kSourceOneName);
		if (myErr != noErr)
			goto bail;
	}
		
	if (mySrcTrack2 != NULL) {	
		myErr = EffectsUtils_AddTrackReferenceToInputMap(myInputMap, myEffectTrack, mySrcTrack2, kSourceTwoName);
		if (myErr != noErr)
			goto bail;
	}
		
	// add the input map to the effects track
	myErr = SetMediaInputMap(myEffectMedia, myInputMap);
	if (myErr != noErr)
		goto bail;

	//////////
	//
	// do any required positioning and graphics mode manipulation
	//
	//////////

	SetTrackLayer(myEffectTrack, myLayer - 1);	// in front of any existing video track
	
	switch (theNumSources) {
		case 2:
			break;
			
		case 1:
			break;
			
		case 0: {
			RGBColor	myColor;
			
			myColor.red = 0;		// (good for fire, not so good for clouds)
			myColor.green = 0;
			myColor.blue = 0;
			
			MediaSetGraphicsMode(GetMediaHandler(myEffectMedia), transparent, &myColor);
			break;
		}
	}
	
bail:
	if (mySampleDesc != NULL)
		DisposeHandle((Handle)mySampleDesc);
	
	if (myInputMap != NULL)
		QTDisposeAtomContainer(myInputMap);
	
	return(myErr);
}


//////////
//
// QTEffects_MakeSpriteEffectMovie
// Create a movie with a sprite that uses an effect for its image.
//
//////////

void QTEffects_MakeSpriteEffectMovie (void)
{
	Movie					myMovie = NULL;
	Track					myTrack = NULL;
	Media					myMedia = NULL;
	FSSpec					myFile;
	Boolean					myIsSelected = false;
	Boolean					myIsReplacing = false;	
	StringPtr 				myPrompt = QTUtils_ConvertCToPascalString(kEffectsSaveMoviePrompt);
	StringPtr 				myFileName = QTUtils_ConvertCToPascalString(kEffectsPenguinMovieFileName);
	long					myFlags = createMovieFileDeleteCurFile | createMovieFileDontCreateResFile;
	OSType					myType = FOUR_CHAR_CODE('none');
	short					myMovieRefNum = kInvalidFileRefNum;
	short					myResID = movieInDataForkResID;
	OSErr					myErr = noErr;

	//////////
	//
	// create a new movie file
	//
	//////////

	// prompt the user for the destination file name
	QTFrame_PutFile(myPrompt, myFileName, &myFile, &myIsSelected, &myIsReplacing);
	if (!myIsSelected)
		goto bail;

	// create a movie file for the destination movie
	myErr = CreateMovieFile(&myFile, sigMoviePlayer, smSystemScript, myFlags, &myMovieRefNum, &myMovie);
	if (myErr != noErr)
		goto bail;
	
	// select the "no controller" movie controller
	myType = EndianU32_NtoB(myType);
	SetUserDataItem(GetMovieUserData(myMovie), &myType, sizeof(myType), kUserDataMovieControllerType, 1);
	
	//////////
	//
	// create the sprite track and media
	//
	//////////
	
	myTrack = NewMovieTrack(myMovie, IntToFixed(kPenguinTrackWidth), IntToFixed(kPenguinTrackHeight), kFullVolume);
	if (myTrack == NULL)
		goto bail;

	myMedia = NewTrackMedia(myTrack, SpriteMediaType, kSpriteMediaTimeScale, NULL, 0);
	if (myMedia == NULL)
		goto bail;

	//////////
	//
	// add the appropriate samples to the sprite media
	//
	//////////
	
	myErr = BeginMediaEdits(myMedia);
	if (myErr != noErr)
		goto bail;

	QTEffects_AddPenguinMovieSamplesToMedia(myMedia);
	
	myErr = EndMediaEdits(myMedia);
	if (myErr != noErr)
		goto bail;
	
	// add the media to the track
	InsertMediaIntoTrack(myTrack, 0, 0, GetMediaDuration(myMedia), fixed1);
		
	//////////
	//
	// set the sprite track properties
	//
	//////////
	
	QTEffects_SetTrackProperties(myMedia);
	
	//////////
	//
	// add the movie resource to the movie file
	//
	//////////
	
	myErr = AddMovieResource(myMovie, myMovieRefNum, &myResID, myFile.name);
		
bail:
	if (myMovieRefNum != kInvalidFileRefNum)
		CloseMovieFile(myMovieRefNum);

	if (myMovie != NULL)
		DisposeMovie(myMovie);
		
	free(myPrompt);
	free(myFileName);

	return;
}


//////////
//
// QTEffects_PromptUserForFilesAndMakeEffectMovie
// Let the user select some movies, then apply the effect to them.
//
// If the user cancels the first file-open dialog box, there are zero sources.
// If the user cancels the second file-open dialog box, there is one source.
// 
//////////

void QTEffects_PromptUserForFilesAndMakeEffectMovie (void)
{
	QTFrameFileFilterUPP	myFileFilterUPP = NULL;
	FSSpec					mySpecs[kMaxNumSources];
	int						mySpecCount;
	OSType 					myTypeList[] = {kQTFileTypeMovie};
	short					myNumTypes = 1;
	OSErr					myErr = noErr;
	
#if TARGET_OS_MAC
	myNumTypes = 0;
#endif

	myFileFilterUPP = QTFrame_GetFileFilterUPP((ProcPtr)QTFrame_FilterFiles);

	// ask for up to kMaxNumSources movie files;
	// accept early cancels; they just mean there are fewer input movies
	mySpecCount = 0;
	while (mySpecCount < kMaxNumSources) {
		FSSpec	myFSSpec;

		myTypeList[0] = MovieFileType;

		myErr = QTFrame_GetOneFileWithPreview(myNumTypes, (QTFrameTypeListPtr)myTypeList, &myFSSpec, myFileFilterUPP);
		if (myErr != noErr)
			break;	// the user doesn't want any more source movies
	
		// save the FSSpec from the reply information
		mySpecs[mySpecCount] = myFSSpec;
		
		mySpecCount++;
	}
	
	QTEffects_DisplayDialogForSources(mySpecs, mySpecCount);
	
	if (myFileFilterUPP != NULL)
		DisposeNavObjectFilterUPP(myFileFilterUPP);
}


////////////////////
//
// QTEffects_AddRippleEffectAsSpriteImage
// Add the ripple effect as the image override of the specified sprite image ID.
//
////////////////////

void QTEffects_AddRippleEffectAsSpriteImage (QTAtomContainer theKeySample, QTAtomID theImageID)
{
	ImageDescriptionHandle		mySampleDesc = NULL;
	QTAtomContainer				myEffectDesc = NULL;
	OSType						myType = kWaterRippleCodecType;
	OSErr						myErr = noErr;
	
	// create a sample description
	mySampleDesc = EffectsUtils_MakeSampleDescription(myType, kPenguinTrackWidth, kPenguinTrackHeight);
	if (mySampleDesc == NULL)
		goto bail;

	// create an effect description
	myEffectDesc = EffectsUtils_CreateEffectDescription(myType, kSourceNoneName, kSourceNoneName, kSourceNoneName);
	if (myEffectDesc == NULL)
		goto bail;

	SpriteUtils_AddCompressedImageToKeyFrameSample(theKeySample, mySampleDesc, GetHandleSize(myEffectDesc), *myEffectDesc, theImageID, NULL, NULL);

bail:
	if (mySampleDesc != NULL)
		DisposeHandle((Handle)mySampleDesc);

	if (myEffectDesc != NULL)
		QTDisposeAtomContainer(myEffectDesc);
		
	return;
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Effects dialog utilities.
//
// Use these functions to work with the effects parameter dialog box.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////
//
// QTEffects_DisplayDialogForSources
// Display the standard effects parameters dialog box for the movies passed in.
//
//////////

OSErr QTEffects_DisplayDialogForSources (FSSpec *theSpecList, UInt16 theSpecCount)
{
	UInt16					myMovieIter;
	OSErr					myErr = noErr;
	
	// make sure that there aren't too many sources
	if (theSpecCount > kMaxNumSources) {
		myErr = paramErr;
		goto bail;
	}
	
	// assign source count to a global, so QTEffects_RespondToDialogSelection has access to it
	gSpecCount = theSpecCount;		
	
	// get the first video track from each movie file
	for (myMovieIter = 0; myMovieIter < theSpecCount; myMovieIter++) {
		short	myRefNum;
		
		// open a movie file using the FSSpec and create a movie from that file
		myErr = OpenMovieFile(&theSpecList[myMovieIter], &myRefNum, 0);
		if (myErr != noErr)
			goto bail;
		
		myErr = NewMovieFromFile(&gSrcMovies[myMovieIter], myRefNum, NULL, NULL, newMovieActive, NULL);
		if (myErr != noErr)
			goto bail;
		
		SetMoviePlayHints(gSrcMovies[myMovieIter], hintsHighQuality, hintsHighQuality);

		// we're done with the movie file, so close it
		CloseMovieFile(myRefNum);
		
		// find the first video track in the source movie
		gSrcTracks[myMovieIter] = GetMovieIndTrackType(gSrcMovies[myMovieIter], 1, kCharacteristicCanSendVideo, movieTrackCharacteristic | movieTrackEnabledOnly);
		if (gSrcTracks[myMovieIter] == NULL)
			goto bail;
	}
	
	// ask the user to select an effect

	myErr = QTNewAtomContainer(&gEffectDesc);
	if (myErr != noErr)
		goto bail;
	
	myErr = QTGetEffectsList(&gEffectList, theSpecCount, theSpecCount, 0);
	if (myErr != noErr)
		goto bail;
	
	// create the effects parameter dialog box, so the user can select an effect
	myErr = QTCreateStandardParameterDialog(gEffectList, gEffectDesc, 0, &gEffectsDialog);
	if (myErr != noErr)
		goto bail;
	
	// insert poster frames into dialog
	if (gSrcTracks[0] != NULL) {
		gPosterA = GetTrackPict(gSrcTracks[0], GetMoviePosterTime(gSrcMovies[0]));
		if (gPosterA != NULL) {
			QTParamPreviewRecord			myPreviewRecord;

			myPreviewRecord.sourcePicture = gPosterA;
			myPreviewRecord.sourceID = 1;
			myErr = QTStandardParameterDialogDoAction(gEffectsDialog, pdActionSetPreviewPicture, &myPreviewRecord);
		}
	}

	if (gSrcTracks[1] != NULL) {
		gPosterB = GetTrackPict(gSrcTracks[1], GetMoviePosterTime(gSrcMovies[1]));
		if (gPosterB != NULL) {
			QTParamPreviewRecord			myPreviewRecord;

			myPreviewRecord.sourcePicture = gPosterB;
			myPreviewRecord.sourceID = 2;
			myErr = QTStandardParameterDialogDoAction(gEffectsDialog, pdActionSetPreviewPicture, &myPreviewRecord);
		}
	}
	
	// now, the frontmost window is the standard effects parameter dialog box;
	// on the Mac, we call QTEffects_HandleEffectsDialogEvents in our main event loop
	// to find and process events targeted at the effects parameter dialog box; on Windows,
	// we need to use a different strategy: we install a modeless dialog callback procedure
	// that is called internally by QTML

#if TARGET_OS_WIN32
	gDoneWithDialog = false;
	
	// force the dialog box to be drawn
	{
		EventRecord			myEvent = {0};
		
		QTEffects_EffectsDialogCallback(&myEvent, FrontWindow(), 0);
	}
	
	SetModelessDialogCallbackProc(FrontWindow(), (QTModelessCallbackUPP)QTEffects_EffectsDialogCallback);
	QTMLSetWindowWndProc(FrontWindow(), QTEffects_CustomDialogWndProc);
#endif
	
bail:
	return(myErr);
}


//////////
//
// QTEffects_RespondToDialogSelection
// If theErr is codecParameterDialogConfirm, make an effects movie.
// If theErr is userCanceledErr, do any necessary clean up.
//
//////////

static void QTEffects_RespondToDialogSelection (OSErr theErr)
{
	short					myMovieRefNum = 0;
	short					myResID = movieInDataForkResID;
	OSType					myEffectCode;
	OSType					mySourceName;
	Fixed					myTrackWidth, myTrackHeight;
	TimeScale				myMovieTimeScale = 600; 
	TimeValue				myEffectDuration = 0;
	TimeValue				mySampleTime = 0;
	FSSpec					myFile;
	Boolean					myIsSelected = false;
	Boolean					myIsReplacing = false;	
	StringPtr 				myPrompt = QTUtils_ConvertCToPascalString(kEffectsSaveMoviePrompt);
	StringPtr 				myFileName = QTUtils_ConvertCToPascalString(kEffectsSaveMovieFileName);
	Movie					myDestMovie = NULL;
	Track					myVideoTracks[kMaxNumSources];
	Track					myEffectTrack = NULL;
	Media					myEffectMedia = NULL;
	UInt16					myMovieIter;
	ImageDescriptionHandle	mySampleDesc = NULL;
	QTAtomContainer			myInputMap = NULL;
	long					myFlags = createMovieFileDeleteCurFile | createMovieFileDontCreateResFile;
	OSErr					myErr = noErr;
	
	//////////
	//
	// standard parameter box has been dismissed; first do any necessary clean-up
	//
	//////////

	gEffectsDialog = 0L;

	// we're finished with the effect list and movie posters
	if (gEffectList != NULL)
		QTDisposeAtomContainer(gEffectList);
	
	if (gPosterA != NULL)
		KillPicture(gPosterA);
		
	if (gPosterB != NULL)
		KillPicture(gPosterB);
	
	// if the user cancelled, bail
	if (theErr == userCanceledErr)
		goto bail;
	
	//////////
	//
	// add to gEffectDesc some atoms naming the sources 
	//
	//////////
		
	if (gSpecCount >= 1) {
		mySourceName = EndianU32_NtoB(kSourceOneName);
		QTInsertChild(gEffectDesc, kParentAtomIsContainer, kEffectSourceName, 1, 0, sizeof(mySourceName), &mySourceName, NULL);
	}
	
	if (gSpecCount >= 2) {
		mySourceName = EndianU32_NtoB(kSourceTwoName);
		QTInsertChild(gEffectDesc, kParentAtomIsContainer, kEffectSourceName, 2, 0, sizeof(mySourceName), &mySourceName, NULL);
	}
	
	if (gSpecCount >= 3) {
		mySourceName = EndianU32_NtoB(kSourceThreeName);
		QTInsertChild(gEffectDesc, kParentAtomIsContainer, kEffectSourceName, 3, 0, sizeof(mySourceName), &mySourceName, NULL);
	}
	
	//////////
	//
	// find out what kind of effect it is
	//
	//////////
		
	myErr = EffectsUtils_GetTypeFromEffectDescription(gEffectDesc, &myEffectCode);
	if (myErr != noErr)
		goto bail;

	//////////
	//
	// create the effects movie
	//
	//////////
	
	// ask the user for the name of the new movie file
	QTFrame_PutFile(myPrompt, myFileName, &myFile, &myIsSelected, &myIsReplacing);
	if (!myIsSelected)
		goto bail;				// deal with user cancelling

	// create a movie file for the destination movie
	myErr = CreateMovieFile(&myFile, sigMoviePlayer, smSystemScript, myFlags, &myMovieRefNum, &myDestMovie);
	if (myErr != noErr)
		goto bail;
	
	// copy the user data and settings from the source to the destination movie;
	// these settings include information like user data
	if (gSpecCount > 0)
		CopyMovieSettings(gSrcMovies[0], myDestMovie);
	
	// convert all the movies to have a common time scale;
	// we pick the largest time scale out of all the source movies, with a minimum of 600
	myMovieTimeScale = 600;
	for (myMovieIter = 0; myMovieIter < gSpecCount; myMovieIter++) {
		if (myMovieTimeScale < GetMovieTimeScale(gSrcMovies[myMovieIter]))
			myMovieTimeScale = GetMovieTimeScale(gSrcMovies[myMovieIter]);
	}
	
	for (myMovieIter = 0; myMovieIter < gSpecCount; myMovieIter++) {
		if (myMovieTimeScale != GetMovieTimeScale(gSrcMovies[myMovieIter]))
			SetMovieTimeScale(gSrcMovies[myMovieIter], myMovieTimeScale);
	}
	
	// the effect duration is the minimum of the length of the tracks
	if (gSpecCount == 0)
		myEffectDuration = myMovieTimeScale * 10;
	else
		myEffectDuration = GetTrackDuration(gSrcTracks[0]);
	
	for (myMovieIter = 1; myMovieIter < gSpecCount; myMovieIter++) {
		if (myEffectDuration > GetTrackDuration(gSrcTracks[myMovieIter]))
			myEffectDuration = GetTrackDuration(gSrcTracks[myMovieIter]);
	}
	
	// default size when there are no video tracks
	myTrackWidth = IntToFixed(kDefaultTrackWidth);
	myTrackHeight = IntToFixed(kDefaultTrackHeight);

	for (myMovieIter = 0; myMovieIter < kMaxNumSources; myMovieIter++) {
		myVideoTracks[myMovieIter] = NULL;
	}
	
	// make the video tracks
	for (myMovieIter = 0; myMovieIter < gSpecCount; myMovieIter++) {
		Fixed	mySrcTrackWidth, mySrcTrackHeight;
		OSType	mySrcMediaType = 0;
		Media	myVideoMedia;

		// myVideoTracks[n] is a clone of gSrcTracks[n]
		GetTrackDimensions(gSrcTracks[myMovieIter], &mySrcTrackWidth, &mySrcTrackHeight);
		
		if ((myMovieIter == 0) || (myTrackWidth < mySrcTrackWidth))
				myTrackWidth = mySrcTrackWidth;
				
		if ((myMovieIter == 0) || (myTrackHeight < mySrcTrackHeight))
				myTrackHeight = mySrcTrackHeight;
		
		GetMediaHandlerDescription(GetTrackMedia(gSrcTracks[myMovieIter]), &mySrcMediaType, NULL, NULL);

		myVideoTracks[myMovieIter] = NewMovieTrack(myDestMovie, mySrcTrackWidth, mySrcTrackHeight, kNoVolume);
		if (myVideoTracks[myMovieIter] == NULL)
			goto bail;

		myVideoMedia = NewTrackMedia(myVideoTracks[myMovieIter], mySrcMediaType, myMovieTimeScale, NULL, 0);
		if (myVideoMedia == NULL)
			goto bail;
		
		if (gCopyMovieMedia) {
			myErr = BeginMediaEdits(myVideoMedia);
			if (myErr != noErr)
				goto bail;
		}

		myErr = CopyTrackSettings(gSrcTracks[myMovieIter], myVideoTracks[myMovieIter]);
		if (myErr != noErr)
			goto bail;

		myErr = InsertTrackSegment(gSrcTracks[myMovieIter], myVideoTracks[myMovieIter], 0, myEffectDuration, 0);
		if (myErr != noErr)
			goto bail;
		
		if (gCopyMovieMedia) {
			myErr = EndMediaEdits(myVideoMedia);
			if (myErr != noErr)
				goto bail;
		}
	}
	
	//////////
	//
	// create the effects track
	//
	//////////
	
	// myEffectTrack is the special track that implements the effect
	myEffectTrack = NewMovieTrack(myDestMovie, myTrackWidth, myTrackHeight, kNoVolume);
	if (myEffectTrack == NULL)
		goto bail;

	myEffectMedia = NewTrackMedia(myEffectTrack, VideoMediaType, myMovieTimeScale, NULL, 0);
	if (myEffectMedia == NULL)
		goto bail;

	// create the sample description
	mySampleDesc = EffectsUtils_MakeSampleDescription(myEffectCode, FixedToInt(myTrackWidth), FixedToInt(myTrackHeight));
	if (mySampleDesc == NULL)
		goto bail;
	
	//////////
	//
	// add the effects sample to the movie
	//
	//////////
	
	myErr = BeginMediaEdits(myEffectMedia);
	if (myErr != noErr)
		goto bail;

	myErr = AddMediaSample(myEffectMedia, gEffectDesc, 0, GetHandleSize(gEffectDesc), myEffectDuration, (SampleDescriptionHandle)mySampleDesc, 1, 0, &mySampleTime);
	if (myErr != noErr)
		goto bail;

	myErr = EndMediaEdits(myEffectMedia);
	if (myErr != noErr)
		goto bail;

	QTDisposeAtomContainer(gEffectDesc);
	DisposeHandle((Handle)mySampleDesc);

	myErr = InsertMediaIntoTrack(myEffectTrack, 0, mySampleTime, myEffectDuration, fixed1);
	if (myErr != noErr)
		goto bail;

	//////////
	//
	// create and add the input map
	//
	//////////

	myErr = QTNewAtomContainer(&myInputMap);
	if (myErr != noErr)
		goto bail;

	// first input
	if (myVideoTracks[0] != NULL)
		EffectsUtils_AddTrackReferenceToInputMap(myInputMap, myEffectTrack, myVideoTracks[0], kSourceOneName);

	if (myVideoTracks[1] != NULL)
		EffectsUtils_AddTrackReferenceToInputMap(myInputMap, myEffectTrack, myVideoTracks[1], kSourceTwoName);

	if (myVideoTracks[2] != NULL)
		EffectsUtils_AddTrackReferenceToInputMap(myInputMap, myEffectTrack, myVideoTracks[2], kSourceThreeName);

	// set the input map
	if (gSpecCount > 0)
		SetMediaInputMap(GetTrackMedia(myEffectTrack), myInputMap);

	//////////
	//
	// finish up
	//
	//////////

	myErr = AddMovieResource(myDestMovie, myMovieRefNum, &myResID, NULL);
	if (myErr != noErr)
		goto bail;
	
	CloseMovieFile(myMovieRefNum);
	
	for (myMovieIter = 0; myMovieIter < gSpecCount; myMovieIter++)
		DisposeMovie(gSrcMovies[myMovieIter]);
		
	DisposeMovie(myDestMovie);
	
bail:
	free(myPrompt);
	free(myFileName);

	if (myInputMap != NULL)
		QTDisposeAtomContainer(myInputMap);

	return;
}


#if TARGET_OS_WIN32
//////////
//
// QTEffects_EffectsDialogCallback
// This function is called by QTML when it processes events for the standard or custom effects parameter dialog box.
// 
//////////

static void QTEffects_EffectsDialogCallback (EventRecord *theEvent, DialogRef theDialog, DialogItemIndex theItemHit)
{
	QTParamDialogEventRecord	myRecord;

	myRecord.theEvent = theEvent;
	myRecord.whichDialog = theDialog;
	myRecord.itemHit = theItemHit;

	if (gEffectsDialog != 0L) {
		QTStandardParameterDialogDoAction(gEffectsDialog, pdActionModelessCallback, &myRecord);
	
		// see if the event is meant for the effects parameter dialog box
		QTEffects_HandleEffectsDialogEvents(theEvent, theItemHit);
	}
}


//////////
//
// QTEffects_CustomDialogWndProc
// Handle messages for the custom effects parameters dialog box.
// 
//////////

LRESULT CALLBACK QTEffects_CustomDialogWndProc (HWND theWnd, UINT theMessage, UINT wParam, LONG lParam)
{
	EventRecord			myEvent = {0};

	if (!gDoneWithDialog && (theMessage == 0x7FFF))
		QTEffects_EffectsDialogCallback(&myEvent, GetNativeWindowPort(theWnd), 0);

	return(DefWindowProc(theWnd, theMessage, wParam, lParam));
}
#endif


//////////
//
// QTEffects_HandleEffectsDialogEvents
// Process events that might be targeted at the standard effects parameter dialog box.
// Return true if the event was completely handled.
// 
//////////

Boolean QTEffects_HandleEffectsDialogEvents (EventRecord *theEvent, DialogItemIndex theItemHit)
{
#pragma unused(theItemHit)
	Boolean			isHandled = false;
	OSErr			myErr = noErr;
	
	// pass the event to the standard effects parameter dialog box handler
	myErr = QTIsStandardParameterDialogEvent(theEvent, gEffectsDialog);
	
	// the result from QTIsStandardParameterDialogEvent tells us how to respond next
	switch (myErr) {
		
		case codecParameterDialogConfirm:
		case userCanceledErr:
			// the user clicked the OK or Cancel button; dismiss the dialog box and respond accordingly
			gDoneWithDialog = true;
			
			if (myErr == codecParameterDialogConfirm)
				QTStandardParameterDialogDoAction(gEffectsDialog, pdActionConfirmDialog, NULL);

			QTDismissStandardParameterDialog(gEffectsDialog);
			gEffectsDialog = 0L;
			QTEffects_RespondToDialogSelection(myErr);
			isHandled = true;
			break;
			
		case noErr:
			// the event was completely handled by QTIsStandardParameterDialogEvent
			isHandled = true;
			break;
			
		case featureUnsupported:
			// the event was not handled by QTIsStandardParameterDialogEvent;
			// let the event be processed normally
			isHandled = false;
			break;
			
		default:
			// the event was not handled by QTIsStandardParameterDialogEvent;
			// do not let the event be processed normally
			isHandled = true;
			break;
	}

	return(isHandled);
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Effects-rendering utilities.
//
// Use these functions to render an effect into a GWorld.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////
//
// QTEffects_InitWindowData
// Do any effects-specific initialization for the specified window.
// 
//////////

Handle QTEffects_InitWindowData (WindowObject theWindowObject)
{
	ApplicationDataHdl			myAppData = NULL;

	// if we already have some window data, dump it
	myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
 	if (myAppData != NULL)
		QTEffects_DumpWindowData(theWindowObject);

	// allocate and initialize our application data
	myAppData = (ApplicationDataHdl)NewHandleClear(sizeof(ApplicationDataRecord));

	return((Handle)myAppData);
}


//////////
//
// QTEffects_DumpWindowData
// Do any effects-specific tear-down for the specified window.
//
//////////

void QTEffects_DumpWindowData (WindowObject theWindowObject)
{
	ApplicationDataHdl		myAppData = NULL;
		
	myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
	if (myAppData != NULL) {
		if ((**myAppData).fGWDesc != NULL)
			DisposeHandle((Handle)(**myAppData).fGWDesc);

		if ((**myAppData).fGW != NULL)
			DisposeGWorld((**myAppData).fGW);

		if ((**myAppData).fSampleDescription != NULL)
			DisposeHandle((Handle)(**myAppData).fSampleDescription);
			
		if ((**myAppData).fEffectDescription != NULL)
			QTDisposeAtomContainer((**myAppData).fEffectDescription);
			
		if ((**myAppData).fEffectSequenceID != 0L)
			CDSequenceEnd((**myAppData).fEffectSequenceID);
			
		if ((**myAppData).fTimeBase != NULL)
			DisposeTimeBase((**myAppData).fTimeBase);

		DisposeHandle((Handle)myAppData);
		(**theWindowObject).fAppData = NULL;
	}
}


//////////
//
// QTEffects_SetUpEffectSequence
// Set up an effects sequence for a one-source effect.
// 
//////////

static OSErr QTEffects_SetUpEffectSequence (WindowObject theWindowObject)
{
	ApplicationDataHdl			myAppData = NULL;
	ImageSequenceDataSource		mySrc = 0;
	PixMapHandle				mySrcPixMap = NULL;
	GraphicsImportComponent		myImporter = NULL;
	Rect						myRect;
 	OSErr						myErr = paramErr;
	
	myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
	if (myAppData == NULL)
		goto bail;

	// if an effect sequence is already set up, end it
	if ((**myAppData).fEffectSequenceID != 0L) {
		CDSequenceEnd((**myAppData).fEffectSequenceID);
		(**myAppData).fEffectSequenceID = 0L;
	}
	
	// if there is a timebase already set up, dispose of it
	if ((**myAppData).fTimeBase != NULL) {
		DisposeTimeBase((**myAppData).fTimeBase);
		(**myAppData).fTimeBase = NULL;
	}
		
	// make an effects sequence
	HLock((Handle)(**myAppData).fEffectDescription);

	// prepare the decompression sequence for playback
	myErr = DecompressSequenceBeginS(
							&(**myAppData).fEffectSequenceID,
							(**myAppData).fSampleDescription,
#if TARGET_CPU_68K
							StripAddress(*(**myAppData).fEffectDescription),
#else
							*(**myAppData).fEffectDescription,
#endif
							GetHandleSize((**myAppData).fEffectDescription),
							(CGrafPtr)QTFrame_GetPortFromWindowReference((**theWindowObject).fWindow),
							NULL,
							NULL,
							NULL,
							ditherCopy,
							NULL,
							0,
							codecNormalQuality,
							NULL);

	HUnlock((Handle)(**myAppData).fEffectDescription);
	if (myErr != noErr)
		goto bail;

	// create the offscreen GWorld holding the original image data
	
	myImporter = (**theWindowObject).fGraphicsImporter;
	if (myImporter == NULL)
		goto bail;

	// set the size of the GWorld
	GraphicsImportGetBoundsRect(myImporter, &myRect);
	
	HLock((Handle)myAppData);

	// allocate a new GWorld
	myErr = QTNewGWorld(&(**myAppData).fGW, 32, &myRect, NULL, NULL, kICMTempThenAppMemory);
	if (myErr != noErr)
		goto bail;
	
	// lock the pixmap
	LockPixels(GetGWorldPixMap((**myAppData).fGW));

	GraphicsImportSetGWorld(myImporter, (**myAppData).fGW, NULL);
	GraphicsImportDraw(myImporter);
	
	// get the pixel maps for the GWorlds
	mySrcPixMap = GetGWorldPixMap((**myAppData).fGW);
	if (mySrcPixMap == NULL)
		goto bail;

	// make the effect source
	if ((**myAppData).fGW == NULL)
		goto bail;
		
	myErr = MakeImageDescriptionForPixMap(mySrcPixMap, &(**myAppData).fGWDesc);
	if (myErr != noErr)
		goto bail;

	myErr = CDSequenceNewDataSource((**myAppData).fEffectSequenceID, &mySrc, kSourceOneName, 1, (Handle)(**myAppData).fGWDesc, NULL, 0);
	if (myErr != noErr)
		goto bail;

	CDSequenceSetSourceData(mySrc, GetPixBaseAddr(mySrcPixMap), (**(**myAppData).fGWDesc).dataSize);

	// create a new time base and associate it with the decompression sequence
	(**myAppData).fTimeBase = NewTimeBase();
	myErr = GetMoviesError();
	if (myErr != noErr)
		goto bail;

	SetTimeBaseRate((**myAppData).fTimeBase, 0);

	myErr = CDSequenceSetTimeBase((**myAppData).fEffectSequenceID, (**myAppData).fTimeBase);

bail:
	HUnlock((Handle)myAppData);

	return(myErr);
}


//////////
//
// QTEffects_RunEffect
// Run the effect: decompress a single step of the effect sequence.
// 
//////////

OSErr QTEffects_RunEffect (WindowObject theWindowObject, TimeValue theTime)
{
	ApplicationDataHdl			myAppData = NULL;
	ICMFrameTimeRecord			myFrameTime;
 	OSErr						myErr = paramErr;
	
	myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
	if (myAppData == NULL)
		goto bail;

	// assertions
	if (((**myAppData).fEffectDescription == NULL) || ((**myAppData).fEffectSequenceID == 0L))
		goto bail;

	// set the timebase time to the step of the sequence to be rendered
	SetTimeBaseValue((**myAppData).fTimeBase, theTime, gNumberOfSteps);

	myFrameTime.value.hi				= 0;
	myFrameTime.value.lo				= theTime;
	myFrameTime.scale					= gNumberOfSteps;
	myFrameTime.base					= 0;
	myFrameTime.duration				= gNumberOfSteps;
	myFrameTime.rate					= 0;
	myFrameTime.recordSize				= sizeof(myFrameTime);
	myFrameTime.frameNumber				= 1;
	myFrameTime.flags					= icmFrameTimeHasVirtualStartTimeAndDuration;
	myFrameTime.virtualStartTime.lo		= 0;
	myFrameTime.virtualStartTime.hi		= 0;
	myFrameTime.virtualDuration			= gNumberOfSteps;
	
	HLock((Handle)(**myAppData).fEffectDescription);

	myErr = DecompressSequenceFrameWhen(
										(**myAppData).fEffectSequenceID,
#if TARGET_CPU_68K
										StripAddress(*((Handle)(**myAppData).fEffectDescription)),
#else
										*((Handle)(**myAppData).fEffectDescription),
#endif
										GetHandleSize((Handle)(**myAppData).fEffectDescription),
										0,
										NULL,
										NULL,
										&myFrameTime);
										
	HUnlock((Handle)(**myAppData).fEffectDescription);
	
bail:
	return(myErr);
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Sprite utilities.
//
// Use these functions to create sprite tracks.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////
//
// QTEffects_AddPenguinMovieSamplesToMedia
// Build the key frame for the penguin sprite movie.
//
//////////

static void QTEffects_AddPenguinMovieSamplesToMedia (Media theMedia)
{
	QTAtomContainer			mySample = NULL;
	QTAtomContainer			mySpriteData = NULL;
	RGBColor				myKeyColor;
	Point					myLocation;
	short					isVisible, myIndex, myLayer;
	OSErr					myErr = noErr;
	
	// create a new, empty key frame sample
	myErr = QTNewAtomContainer(&mySample);
	if (myErr != noErr)
		goto bail;

	myKeyColor.red = myKeyColor.green = myKeyColor.blue = 0xffff;		// white
	
	// add images to the key frame sample
	SpriteUtils_AddPICTImageToKeyFrameSample(mySample, kPenguinPictID, &myKeyColor, 1, NULL, NULL);
	QTEffects_AddRippleEffectAsSpriteImage(mySample, 2);

	myErr = QTNewAtomContainer(&mySpriteData);
	if (myErr != noErr)
		goto bail;

	// the penguin sprite
	myLocation.h 	= 0;
	myLocation.v	= 0;
	isVisible		= true;
	myIndex			= 1;
	myLayer			= 0;
	
	SpriteUtils_SetSpriteData(mySpriteData, &myLocation, &isVisible, &myLayer, &myIndex, NULL, NULL, NULL);
	SpriteUtils_AddSpriteToSample(mySample, mySpriteData, 1);
	
	QTDisposeAtomContainer(mySpriteData);	

	myErr = QTNewAtomContainer(&mySpriteData);
	if (myErr != noErr)
		goto bail;

	// the ripple sprite
	myLocation.h 	= 0;
	myLocation.v	= 0;
	isVisible		= true;
	myIndex			= 2;
	myLayer			= -1;

	SpriteUtils_SetSpriteData(mySpriteData, &myLocation, &isVisible, &myLayer, &myIndex, NULL, NULL, NULL);	
	WiredUtils_AddQTEventAndActionAtoms(mySpriteData, kParentAtomIsContainer, kQTEventMouseClick, kActionSpritePassMouseToCodec, NULL);
	SpriteUtils_AddSpriteToSample(mySample, mySpriteData, 2);

	SpriteUtils_AddSpriteSampleToMedia(theMedia, mySample, kSpriteMediaFrameDurationPenguin, true, NULL);	
	
bail:	
	if (mySample != NULL)
		QTDisposeAtomContainer(mySample);

	if (mySpriteData != NULL)
		QTDisposeAtomContainer(mySpriteData);	
}


//////////
//
// QTEffects_SetTrackProperties
// Set the track properties for the specified sample sprite movie.
//
//////////

void QTEffects_SetTrackProperties (Media theMedia)
{
	QTAtomContainer		myTrackProperties;
	RGBColor			myBackgroundColor;
	Boolean				hasActions;
	OSErr				myErr = noErr;
		
	// add a background color to the sprite track
	myBackgroundColor.red = EndianU16_NtoB(0xffff);
	myBackgroundColor.green = EndianU16_NtoB(0xffff);
	myBackgroundColor.blue = EndianU16_NtoB(0xffff);
	
	myErr = QTNewAtomContainer(&myTrackProperties);
	if (myErr == noErr) {
		QTInsertChild(myTrackProperties, 0, kSpriteTrackPropertyBackgroundColor, 1, 1, sizeof(myBackgroundColor), &myBackgroundColor, NULL);

		// tell the movie controller that this sprite track has actions
		hasActions = true;
		QTInsertChild(myTrackProperties, 0, kSpriteTrackPropertyHasActions, 1, 1, sizeof(hasActions), &hasActions, NULL);
	
		SetMediaPropertyAtom(theMedia, myTrackProperties);
		
		QTDisposeAtomContainer(myTrackProperties);
	}
}

