/*=============================================================================
	CASMPTETimeBase.cpp
	
	$Log: CASMPTETimeBase.cpp,v $
	Revision 1.5  2005/01/21 00:44:39  asynth
	add const keyword
	
	Revision 1.4  2004/09/29 00:10:21  dwyatt
	rename ParseFrame -> HMSFToAbsoluteFrame
	
	Revision 1.3  2004/09/22 23:51:14  dwyatt
	comments
	
	Revision 1.2  2004/06/14 21:45:09  dwyatt
	renaming & cleanup
	
	Revision 1.1  2004/05/06 01:48:57  dwyatt
	initial checkin
	
	created Tue May 04 2004, Doug Wyatt
	Copyright (c) 2004 Apple Computer, Inc.  All Rights Reserved

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

#include "CASMPTETimeBase.h"
#include "CAException.h"

/*
enum
{
    kSMPTETimeType24        = 0,
    kSMPTETimeType25        = 1,
    kSMPTETimeType30Drop    = 2,
    kSMPTETimeType30        = 3,
    kSMPTETimeType2997      = 4,
    kSMPTETimeType2997Drop  = 5,
    kSMPTETimeType60        = 6,
    kSMPTETimeType5994      = 7
};
*/

static const SInt8 gMTCTypes[] = { 0, 1, 2, 3, 3, 2, -1, -1 };	// map our constants to MIDI Time Code representations
static const double gFramesPerSecond[] = { 24., 25., 29.97, 30., 29.97, 29.97 * .999 /* ????? */, 60., 59.94 };
static const UInt8 gFormatFramesPerSecond[] = { 24, 25, 30, 30, 30, 30, 60, 60 };

bool	CASMPTETimeBase::SetFormat(UInt32	inSMPTEFormat)
{
	if (inSMPTEFormat > kSMPTETimeType5994)
		return false;

	mMTCType = gMTCTypes[inSMPTEFormat];
	mIsDropFrame = (mMTCType == 2);
	mFramesPerSecond = gFramesPerSecond[inSMPTEFormat];
	mFormatFramesPerSecond = gFormatFramesPerSecond[inSMPTEFormat];
	mFormat = inSMPTEFormat;
	return true;
}

/*
	http://www.phatnav.com/wiki/wiki.phtml?title=SMPTE_time_code

	To correct this, drop frame SMPTE timecode drops frame numbers 0 and 1 of the first second of every minute, and includes them when the number of minutes is divisible by ten. This almost perfectly compensates for the difference in rate, leaving a residual timing error of roughly 86.4 microseconds per day, an error of only 0.001 ppm. Note: only timecode frame numbers are dropped. Video frames continue in sequence.
	
	NOTE: drop frame calculations all assume 30 fps = 1800 frames/minute = 18000 frames/10 minutes
*/

void	CASMPTETimeBase::SecondsToSMPTETime(Float64					inSeconds,
											UInt16					inSubframeDivisor,
											SMPTETime &				outSMPTETime) const
{
	// time in subframes
	int bitsPerSecond = inSubframeDivisor * mFormatFramesPerSecond;
 	int absBits = int(inSeconds * bitsPerSecond + 0.5); // round
	int bitsInDay = bitsPerSecond * 60 * 60 * 24;
	while (absBits < 0)
		absBits += bitsInDay;
	while (absBits >= bitsInDay)
		absBits -= bitsInDay;
	
	// if it's drop frame, add back a number of frames to fix up representation
	if (mIsDropFrame) { // drop frame
		int bitsPer10Minutes = inSubframeDivisor * 17982;	// 18000 - 18 dropped
		// how many 10-minute periods? count 18 dropped frames for each
		int minutes10 = (absBits / bitsPer10Minutes);
		int extraFrames = 18 * minutes10;
		int frames10 = (absBits / inSubframeDivisor) % 17982;  // position in 10-minute cycle
		if (frames10 >= 1800) {
			// at or past the first minute
			// minute 0 has 1800 frames, subsequent minutes have 1798 frames
			// so add 2 frames per minute
			extraFrames += 2 + 2 * ((frames10 - 1800) / 1798);
		}
		absBits = int(inSeconds * bitsPerSecond + 0.5) + extraFrames * inSubframeDivisor;
	}
	int div;	
	div = (bitsPerSecond * 60 * 60);
	outSMPTETime.mHours = absBits / div;
	absBits %= div;
	
	div = (bitsPerSecond * 60);
	outSMPTETime.mMinutes = absBits / div;
	absBits %= div;
	
	outSMPTETime.mSeconds = absBits / bitsPerSecond;
	absBits %= bitsPerSecond;
	
	outSMPTETime.mFrames = absBits / inSubframeDivisor;
	outSMPTETime.mSubframes = absBits %= inSubframeDivisor;
	outSMPTETime.mSubframeDivisor = inSubframeDivisor;
	
	outSMPTETime.mType = mFormat;
}



bool	CASMPTETimeBase::SMPTETimeToSeconds(const SMPTETime &		inSMPTETime,
											Float64 &				outSeconds) const
{
	if (inSMPTETime.mHours > 23 || inSMPTETime.mMinutes > 59 || inSMPTETime.mSeconds > 59 || inSMPTETime.mFrames >= mFormatFramesPerSecond || inSMPTETime.mSubframes >= inSMPTETime.mSubframeDivisor)
		return false;

	// first, compute it assuming no dropped frames
	int frames = (inSMPTETime.mFrames + mFormatFramesPerSecond * // frames
						(inSMPTETime.mSeconds + 60 * // seconds
						(inSMPTETime.mMinutes + 60 *  // minutes
						 inSMPTETime.mHours))); // hours
	// now, figure out the number of dropped frames
	if (mIsDropFrame) {
		frames -= inSMPTETime.mHours * 6 * 18; // hours, 18 frames for each 10 minutes
		int minutes = inSMPTETime.mMinutes;
		frames -= (minutes / 10) * 18; // 18 frames per 10-minute period
		minutes %= 10;
		if (minutes) {
			if (inSMPTETime.mFrames < 2)
				return false;   // illegal -- dropped frame
			frames -= minutes * 2;
		}
	}
	int bits = inSMPTETime.mSubframes + inSMPTETime.mSubframeDivisor * frames;
	outSeconds = double(bits) / double(mFormatFramesPerSecond * inSMPTETime.mSubframeDivisor);
	return true;
}

// increment a 32-bit MTC SMPTE frame representation
// HHMMSSFF (nibbles)
SMPTE_HMSF  CASMPTETimeBase::AdvanceFrame(SMPTE_HMSF hmsf, int nToAdvance) const
{
	while (--nToAdvance >= 0) {
		hmsf += 1;
		if ((hmsf & 0xFF) == UInt32(mFormatFramesPerSecond)) {
			// next second
			hmsf &= ~0xFF;
			hmsf += 0x100;
			if ((hmsf & 0xFF00) == (60 << 8)) {
				// 60 seconds
				hmsf &= ~0xFF00;
				hmsf += 0x10000;
				if ((hmsf & 0xFF0000) == (60 << 16)) {
					// 60 minutes
					hmsf &= ~0xFF0000;
					hmsf += 0x1000000;
					if ((hmsf & 0x1F000000) == (24 << 24))
						// 24 hours
						hmsf &= ~0x1F000000;
				}
			}
		}
		if (mIsDropFrame &&
				(hmsf & 0xFFFE) == 0 && 	// either of first two frames of first second
				((hmsf & 0x00FF0000) >> 16) % 10 != 0)	// not the first of a 10-minute period
			hmsf += 2; // drop the two frames
	}
	return hmsf;
}

// convert SMPTE representation as four bytes in a UInt32 to number of frames since 0
UInt32	CASMPTETimeBase::HMSFToAbsoluteFrame(SMPTE_HMSF hmsf) const
{
	// first, compute it assuming no dropped frames
	UInt32 frames = (hmsf & 0xFF) + mFormatFramesPerSecond * ( // frames
						((hmsf >> 8) & 0xFF) + 60 * ( // seconds
						((hmsf >> 16) & 0xFF) + 60 * ( // minutes
						 (hmsf >> 24) & 0x1F))); // hours
	// now, figure out the number of dropped frames
	if (mIsDropFrame) {
		frames -= ((hmsf & 0x1F000000) >> 24) * 6 * 18; // hours
		UInt16 minutes = (hmsf & 0x00FF0000) >> 16;
		frames -= (minutes / 10) * 18; // 10-minute periods
		minutes %= 10;
		if (minutes)
			frames -= minutes * 2;
	}
	return frames;
}
