//
//	File:		QTSndTween.c
//
//	Contains:	Sound tweening support for QuickTime movies.
//
//	Written by:	Tim Monroe
//				based largely on the tween sample code in the QuickTime 2.5 Developers Guide
//				and the sample code QT3DTween.c.
//
//	Copyright:	© 1998 by Apple Computer, Inc., all rights reserved.
//
//	Change History (most recent first):
//
//	   <3>	 	03/21/00	rtm		made changes for running under CarbonLib
//	   <2>	 	05/26/98	rtm		set the time scale of the sound track media to that of the movie,
//									using revised code from Peter Hoddie 
//	   <1>	 	04/10/98	rtm		first file; revised to personal coding style
//	   
//
// This sample shows how to modify an existing QuickTime movie so that the volume of its sound track
// is gradually increased (or decreased) as the movie plays. We do this by adding a tween track to
// the movie and linking the tween track to the existing sound track.
// 
// NOTES:
//
// *** (1) ***
// For complete information on creating tween media tracks, see the chapter "Tween Media Handler Components"
// in the QuickTime 2.5 or 3.0 Developers Guide.
//
//

#include <FixMath.h>
#include <Sound.h>

#include "QTSndTween.h"

// global variables
// this variable determines what kind of tween data we add to the movie

Boolean				gTweenLoToHigh = true;		// do we start at no volume and end at full volume?


//////////
//
// QTSndTween_AddTweenTrackToMovie
// Add a tween track to a QuickTIme movie.
//
//////////

OSErr QTSndTween_AddTweenTrackToMovie (Movie theMovie)
{
	Track						mySndTrack = NULL;
	Track						myTrack;
	Media						myMedia;
	StandardFileReply			myReply;
	SampleDescriptionHandle		mySampleDesc = NULL;
	QTAtomContainer				mySample = NULL;
	QTAtomContainer				myInputMap = NULL;
	QTAtom						myAtom = 0;
	QTAtom						myInputAtom = 0;
	short						myTweenData[2];
	long						myRefIndex;
	TimeRecord					myTimeRec;
	OSErr						myErr = noErr;

	if (theMovie == NULL)
		goto bail;
		
	//////////
	//
	// get the (first) sound track from the movie
	//
	//////////
	
	mySndTrack = GetMovieIndTrackType(theMovie, 1, AudioMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly);
	if (mySndTrack == NULL)
		goto bail;

	//////////
	//
	// create the tween track and media, and a sample to contain the tween data
	//
	//////////

	// create the tween track and media
	myTrack = NewMovieTrack(theMovie, 0, 0, kNoVolume);
	myMedia = NewTrackMedia(myTrack, TweenMediaType, kTweenTimeScale, NULL, 0);

	// create a new sample; this sample will hold the tween data
	myErr = QTNewAtomContainer(&mySample);
	if (myErr != noErr)
		goto bail;
		
	myTweenData[0] = gTweenLoToHigh ? EndianU16_NtoB(0) 	: EndianU16_NtoB(512);
	myTweenData[1] = gTweenLoToHigh ? EndianU16_NtoB(512)	: EndianU16_NtoB(0);
		
	// make a tween entry for that data;
	// a tween entry is a parent atom that contains leaf atoms describing the tweening operation
	myErr = QTSndTween_AddTweenEntryToSample(mySample, kSoundTweenID, kTweenTypeShort, &myTweenData, sizeof(myTweenData));
	if (myErr != noErr)
		goto bail;
		
	// create the sample description
	mySampleDesc = (SampleDescriptionHandle)NewHandleClear(sizeof(SampleDescription));
	if (mySampleDesc == NULL)
		goto bail;
	
	(**mySampleDesc).descSize = sizeof(SampleDescription);

	// add the tween sample to the media
	BeginMediaEdits(myMedia);

	// set the time scale of the media to that of the movie
	myTimeRec.value.lo = GetTrackDuration(mySndTrack);
	myTimeRec.value.hi = 0;
	myTimeRec.scale = GetMovieTimeScale(theMovie);
	ConvertTimeScale(&myTimeRec, GetMediaTimeScale(myMedia));

	myErr = AddMediaSample(myMedia, mySample, 0, GetHandleSize(mySample), myTimeRec.value.lo, (SampleDescriptionHandle)mySampleDesc, 1, 0, NULL);
	if (myErr != noErr)
		goto bail;

	EndMediaEdits(myMedia);

	// add the media to the track
	InsertMediaIntoTrack(myTrack, 0, 0, GetMediaDuration(myMedia), fixed1);

	// dispose of some things we no longer need
	QTDisposeAtomContainer(mySample);
	DisposeHandle((Handle)mySampleDesc);

	//////////
	//
	// create a link between the sound track and the tween track, and update the sound track's input map
	//
	//////////
	
	// first, create a new atom container
	myErr = QTNewAtomContainer(&myInputMap);
	if (myErr != noErr)
		goto bail;
	
	// for *each* tween entry in the tween media sample, 	
	// create a link between the sound track and the tween track, and add an entry to the input map
	
	myErr = AddTrackReference(mySndTrack, myTrack, kTrackModifierReference, &myRefIndex);
	if (myErr != noErr)
		goto bail;
		
	myErr = QTSndTween_AddTweenEntryToInputMap(myInputMap, myRefIndex, kSoundTweenID, kTrackModifierTypeVolume, NULL);
	if (myErr != noErr)
		goto bail;

    // attach the input map to the sound media
    myErr = SetMediaInputMap(GetTrackMedia(mySndTrack), myInputMap);
	if (myErr != noErr)
		goto bail;
	
    // dispose of the input map
    QTDisposeAtomContainer(myInputMap);
	
	//////////
	//
	// finish up
	//
	//////////
	
	// save the new movie file; if you are running under CarbonLib, change this to use Navigation Services
	StandardPutFile("\pSave Movie as:", "\pNewMovie.mov", &myReply); 
	if (myReply.sfGood) {
		FlattenMovieData(theMovie, flattenAddMovieToDataFork, &myReply.sfFile, FOUR_CHAR_CODE('TVOD'), smSystemScript, createMovieFileDeleteCurFile);
		myErr = GetMoviesError();
	}

bail:
	return(myErr);
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Tween utilities.
//
// Use these functions add tween entries to samples or to add attributes to tween entries.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////
//
// QTSndTween_AddTweenEntryToSample
// Add a tween entry to the specified sample.
//
// A tween entry defines a set of values for a single tweening operation. A tween entry is a parent atom
// whose children define the tween data type, the tween data, and additional attributes of the operation.
//
// The data specified in the theData parameter is assumed to be in big-endian format.
//
//////////

OSErr QTSndTween_AddTweenEntryToSample (QTAtomContainer theSample, QTAtomID theID, QTAtomType theType, void *theData, long theDataSize)
{
	OSErr				myErr = noErr;
	QTAtom				myAtom;
	
	// create an entry for this tween in the sample
	myErr = QTInsertChild(theSample, kParentAtomIsContainer, kTweenEntry, theID, 0, 0, NULL, &myAtom);
	if (myErr != noErr)
		goto bail;
	
	// set the type of this tween entry
	theType = EndianU32_NtoB(theType);
	myErr = QTInsertChild(theSample, myAtom, kTweenType, 1, 0, sizeof(theType), &theType, NULL);
	if (myErr != noErr)
		goto bail;
	
	// set the data for this tween entry
	myErr = QTInsertChild(theSample, myAtom, kTweenData, 1, 0, theDataSize, theData, NULL);
	
bail:
	return(myErr);
}


//////////
//
// QTSndTween_AddTweenEntryToInputMap
// Add a tween entry to the specified input map.
//
//////////

OSErr QTSndTween_AddTweenEntryToInputMap (QTAtomContainer theInputMap, long theRefIndex, long theID, OSType theType, char *theName)
{
	QTAtom				myInputAtom;
	OSErr				myErr = noErr;
	
	// add an entry to the input map
	myErr = QTInsertChild(theInputMap, kParentAtomIsContainer, kTrackModifierInput, theRefIndex, 0, 0, NULL, &myInputAtom);
	if (myErr != noErr)
		goto bail;
	
	// add two child atoms to the parent atom;
	// these atoms define the type of the modifier input and the ID of the tween entry atom
	theType = EndianU32_NtoB(theType);
	myErr = QTInsertChild(theInputMap, myInputAtom, kTrackModifierType, 1, 0, sizeof(OSType), &theType, NULL);
	if (myErr != noErr)
		goto bail;

	theID = EndianU32_NtoB(theID);
	myErr = QTInsertChild(theInputMap, myInputAtom, kInputMapSubInputID, 1, 0, sizeof(long), &theID, NULL);
	if (myErr != noErr)
		goto bail;
		
	// set the name of the input atom
	if (theName != NULL) {
		long		myLength = 1;
		Ptr			myPtr = theName;
		UInt16		*myShort;

		// determine the length of the name string
		while (*myPtr++)
			myLength++;

		// convert the name string to the proper endian format
		myPtr = theName;
		while (*myPtr) {
			myShort = (UInt16 *)myPtr;
			*myPtr = EndianU16_NtoB(*myShort);
			myPtr = myPtr + 2;	// point to next word
		}

		myErr = QTInsertChild(theInputMap, myInputAtom, kTrackModifierInputName, 1, 0, myLength, theName, NULL);
	}
	
bail:
	return(myErr);
}