text: /***********************************************************************
   * AUTHOR: David McPaul
   *   FILE: TimeCode.cpp
   *  DESCR: Handles all TimeCode functions.
   ***********************************************************************/
  #include <TimeCode.h>
  #include "MediaDebug.h"
  #include <string.h>
  
  status_t us_to_timecode(bigtime_t micros, int * hours, int * minutes, int * seconds, int * frames, const timecode_info * code)
  {
  	int32 l_frames;
  
  	CALLED();
  
  	if (code) {
  		// We have a valid timecode_info
  		switch (code->type) {
  			case	B_TIMECODE_DEFAULT:		// NTSC
  			case	B_TIMECODE_30_DROP_2:	// NTSC
  				l_frames = int32((((micros % 1000000) * 29.97) / 1000000) + (micros / 1000000 * 29.97));
  				break;
  			case	B_TIMECODE_30_DROP_4:	// Brazil
  				l_frames = int32((((micros % 1000000) * 29.95) / 1000000) + (micros / 1000000 * 29.95));
  				break;
  			default:
  				l_frames = (((micros % 1000000) * code->fps_div) / 1000000) + (micros / 1000000 * code->fps_div);
  				break;
  		};
  	} else {
  		// Convert us to frames
  		l_frames = int32((((micros % 1000000) * 29.97) / 1000000) + (micros / 1000000 * 29.97));
  	}
  
  	return frames_to_timecode(l_frames, hours, minutes, seconds, frames, code);
  }
  
  status_t timecode_to_us(int hours, int minutes, int seconds, int frames, bigtime_t * micros, const timecode_info * code)
  {
  	int32 l_frames;
  
  	CALLED();
  
  	if (timecode_to_frames(hours, minutes, seconds, frames, &l_frames, code) == B_OK) {
  
  		if (code) {
  			// We have a valid timecode_info
  			switch (code->type) {
  				case	B_TIMECODE_DEFAULT:		// NTSC
  				case	B_TIMECODE_30_DROP_2:	// NTSC
  					*micros = bigtime_t(l_frames * ((1000000 / 29.97) + 0.5));
  					break;
  				case	B_TIMECODE_30_DROP_4:	// Brazil
  					*micros = bigtime_t(l_frames * ((1000000 / 29.95) + 0.5));
  					break;
  				default:
  					*micros = l_frames * 1000000 / code->fps_div;
  					break;
  			};
  
  		} else {
  			*micros = bigtime_t(l_frames * ((1000000 / 29.97) + 0.5));
  		}
  
  		return B_OK;
  	}
  	return B_ERROR;
  }
  
  status_t frames_to_timecode(int32 l_frames, int * hours, int * minutes, int * seconds, int * frames, const timecode_info * code)
  {
  	int fps_div;
  	int total_mins;
  	int32 extra_frames;
  	int32 extra_frames2;
  
  	CALLED();
  
  	if (code) {
  		// We have a valid timecode_info so use it
  		fps_div = code->fps_div;
  
  		if (code->every_nth > 0) {
  			// Handle Drop Frames format
  
  			total_mins = l_frames / fps_div / 60;
  
  			// "every_nth" minute we gain "drop_frames" "except_nth" minute
  			extra_frames = code->drop_frames*(total_mins/code->every_nth) - code->drop_frames*(total_mins/code->except_nth);
  			l_frames += extra_frames;
  
  			total_mins = l_frames / fps_div / 60;
  			extra_frames2 = code->drop_frames*(total_mins/code->every_nth) - code->drop_frames*(total_mins/code->except_nth);
  
  			// Gaining frames may mean that we gain more frames so we keep adjusting until no more to adjust
  			while (extra_frames != extra_frames2) {
  				l_frames += extra_frames2 - extra_frames;
  				extra_frames = extra_frames2;
  
  				total_mins = l_frames / fps_div / 60;
  				extra_frames2 = code->drop_frames*(total_mins/code->every_nth) - code->drop_frames*(total_mins/code->except_nth);
  			}
  
  			// l_frames should now include all adjusted frames
  		}
  	} else {
  		// no timecode_info so set default NTSC :-(
  		fps_div = 30;		// NTSC Default
  		total_mins = l_frames / fps_div / 60;
  
  		// "every_nth" minute we gain "drop_frames" "except_nth" minute
  		extra_frames = 2*(total_mins) - 2*(total_mins/10);
  		l_frames += extra_frames;
  
  		total_mins = l_frames / fps_div / 60;
  		extra_frames2 = 2*(total_mins) - 2*(total_mins/10);
  
  		// Gaining frames may mean that we gain more frames so we keep adjusting until no more to adjust
  		while (extra_frames != extra_frames2) {
  			l_frames += extra_frames2 - extra_frames;
  			extra_frames = extra_frames2;
  
  			total_mins = l_frames / fps_div / 60;
  			extra_frames2 = 2*(total_mins) - 2*(total_mins/10);
  		}
  	}
  
  	// convert frames to seconds leaving the remainder as frames
  	*seconds = l_frames / fps_div;		// DIV
  	*frames = l_frames % fps_div;		// MOD
  
  	// Normalise to Hours Minutes Seconds Frames
  	*minutes = *seconds / 60;
  	*seconds = *seconds % 60;
  
  	*hours = *minutes / 60;
  	*minutes = *minutes % 60;
  
  	return B_OK;
  }
  
  status_t timecode_to_frames(int hours, int minutes, int seconds, int frames, int32 * l_frames, const timecode_info * code)
  {
  	int fps_div;
  	int total_mins;
  	int32 extra_frames;
  
  	CALLED();
  
  	if (code) {
  		// We have a valid timecode_info
  		fps_div = code->fps_div;
  
  		total_mins = (hours * 60) + minutes;
  		*l_frames = (total_mins * 60) + seconds;
  		*l_frames = (*l_frames * fps_div) + frames;
  
  		// Adjust "every_nth" minute we lose "drop_frames" "except_nth" minute
  		if (code->every_nth > 0) {
  			extra_frames = code->drop_frames*(total_mins/code->every_nth) - code->drop_frames*(total_mins/code->except_nth);
  
  			*l_frames = *l_frames - extra_frames;
  		}
  	} else {
  
  		total_mins = (hours * 60) + minutes;
  		*l_frames = (total_mins * 60) + seconds;
  		*l_frames = (*l_frames * 30) + frames;
  
  		// Adjust "every_nth" minute we lose "drop_frames" "except_nth" minute
  		extra_frames = 2*(total_mins) - 2*(total_mins/10);
  
  		*l_frames = *l_frames - extra_frames;
  	}
  
  	return B_OK;
  }
  
  status_t get_timecode_description(timecode_type type, timecode_info * out_timecode)
  {
  	CALLED();
  
  	out_timecode->type = type;
  	strncpy(out_timecode->format,"%.2ih:%.2im:%.2is.%.2i",31);
  	out_timecode->every_nth = 0;
  	out_timecode->except_nth = 0;
  
  	switch (type) {
  		case	B_TIMECODE_100:
  				strncpy(out_timecode->name,"100 FPS",31);
  				out_timecode->fps_div = 100;
  				break;
  		case	B_TIMECODE_75:			// CD
  				strncpy(out_timecode->name,"CD",31);
  				out_timecode->fps_div = 75;
  				break;
  		case	B_TIMECODE_30:			// MIDI
  				strncpy(out_timecode->name,"MIDI",31);
  				out_timecode->fps_div = 30;
  				break;
  		case	B_TIMECODE_30_DROP_2:	// NTSC
  				strncpy(out_timecode->name,"NTSC",31);
  				out_timecode->fps_div = 30;	// This is supposed to be 29.97fps but can be simulated using the drop frame format below
  				out_timecode->drop_frames = 2; // Drop 2 frames
  				out_timecode->every_nth = 1;	// every 1 minutes
  				out_timecode->except_nth = 10;	// except every 10 minutes
  				break;
  		case	B_TIMECODE_30_DROP_4:	// Brazil
  				strncpy(out_timecode->name,"NTSC Brazil",31);
  				out_timecode->fps_div = 30;
  				out_timecode->drop_frames = 4; // Drop 4 frames
  				out_timecode->every_nth = 1;	// every 1 minutes
  				out_timecode->except_nth = 10;	// except every 10 minutes
  				break;
  		case	B_TIMECODE_25:			// PAL
  				strncpy(out_timecode->name,"PAL",31);
  				out_timecode->fps_div = 25;
  				break;
  		case	B_TIMECODE_24:			// Film
  				strncpy(out_timecode->name,"FILM",31);
  				out_timecode->fps_div = 24;
  				break;
  		case	B_TIMECODE_18:			// Super8
  				strncpy(out_timecode->name,"Super 8",31);
  				out_timecode->fps_div = 18;
  				break;
  		default:
  				strncpy(out_timecode->name,"NTSC",31);
  				out_timecode->fps_div = 30;	// This is supposed to be 29.97fps but can be simulated using the drop frame format below
  				out_timecode->drop_frames = 2; // Drop 2 frames
  				out_timecode->every_nth = 1;	// every 1 minutes
  				out_timecode->except_nth = 10;	// except every 10 minutes
  				break;
  	}
  
  	return B_OK;
  }
  
  status_t count_timecodes()
  {
  	CALLED();
  	return 8;	// Is this right?
  }
  
  /*************************************************************
   * public BTimeCode
   *************************************************************/
  
  
  BTimeCode::BTimeCode()
  {
  	CALLED();
  }
  
  
  BTimeCode::BTimeCode(bigtime_t us,
  					 timecode_type type)
  {
  	CALLED();
  	if (SetType(type) == B_OK) {
  		SetMicroseconds(us);
  	}
  }
  
  
  BTimeCode::BTimeCode(const BTimeCode &clone)
  {
  	CALLED();
  	if (SetType(clone.Type()) == B_OK) {
  		SetData(clone.Hours(),clone.Minutes(),clone.Seconds(),clone.Frames());
  	}
  }
  
  
  BTimeCode::BTimeCode(int hours,
  					 int minutes,
  					 int seconds,
  					 int frames,
  					 timecode_type type)
  {
  	CALLED();
  	if (SetType(type) == B_OK) {
  		SetData(hours,minutes,seconds,frames);
  	}
  }
  
  
  BTimeCode::~BTimeCode()
  {
  	CALLED();
  }
  
  
  void
  BTimeCode::SetData(int hours,
  				   int minutes,
  				   int seconds,
  				   int frames)
  {
  	CALLED();
  	fHours = hours;
  	fMinutes = minutes;
  	fSeconds = seconds;
  	fFrames = frames;
  }
  
  
  status_t
  BTimeCode::SetType(timecode_type type)
  {
  	CALLED();
  
  	return get_timecode_description(type, &fInfo);
  }
  
  
  void
  BTimeCode::SetMicroseconds(bigtime_t us)
  {
  	CALLED();
  
  	us_to_timecode(us, &fHours, &fMinutes, &fSeconds, &fFrames, &fInfo);
  }
  
  
  void
  BTimeCode::SetLinearFrames(int32 linear_frames)
  {
  	CALLED();
  
  	frames_to_timecode(linear_frames, &fHours, &fMinutes, &fSeconds, &fFrames, &fInfo);
  }
  
  
  BTimeCode &
  BTimeCode::operator=(const BTimeCode &clone)
  {
  	CALLED();
  	fHours = clone.Hours();
  	fMinutes = clone.Minutes();
  	fSeconds = clone.Seconds();
  	fFrames = clone.Frames();
  	SetType(clone.Type());
  
  	return *this;
  }
  
  
  bool
  BTimeCode::operator==(const BTimeCode &other) const
  {
  	CALLED();
  
  	return ((this->LinearFrames()) == (other.LinearFrames()));
  }
  
  
  bool
  BTimeCode::operator<(const BTimeCode &other) const
  {
  	CALLED();
  
  	return ((this->LinearFrames()) < (other.LinearFrames()));
  }
  
  
  BTimeCode &
  BTimeCode::operator+=(const BTimeCode &other)
  {
  	CALLED();
  
  	this->SetLinearFrames(this->LinearFrames() + other.LinearFrames());
  
  	return *this;
  }
  
  
  BTimeCode &
  BTimeCode::operator-=(const BTimeCode &other)
  {
  	CALLED();
  
  	this->SetLinearFrames(this->LinearFrames() - other.LinearFrames());
  
  	return *this;
  }
  
  
  BTimeCode
  BTimeCode::operator+(const BTimeCode &other) const
  {
  	CALLED();
  	BTimeCode tc(*this);
  	tc += other;
  	return tc;
  }
  
  
  BTimeCode
  BTimeCode::operator-(const BTimeCode &other) const
  {
  	CALLED();
  	BTimeCode tc(*this);
  	tc -= other;
  	return tc;
  }
  
  
  int
  BTimeCode::Hours() const
  {
  	CALLED();
  
  	return fHours;
  }
  
  
  int
  BTimeCode::Minutes() const
  {
  	CALLED();
  
  	return fMinutes;
  }
  
  
  int
  BTimeCode::Seconds() const
  {
  	CALLED();
  
  	return fSeconds;
  }
  
  
  int
  BTimeCode::Frames() const
  {
  	CALLED();
  
  	return fFrames;
  }
  
  
  timecode_type
  BTimeCode::Type() const
  {
  	CALLED();
  
  	return fInfo.type;
  }
  
  
  void
  BTimeCode::GetData(int *out_hours,
  				   int *out_minutes,
  				   int *out_seconds,
  				   int *out_frames,
  				   timecode_type *out_type) const
  {
  	CALLED();
  	*out_hours = fHours;
  	*out_minutes = fMinutes;
  	*out_seconds = fSeconds;
  	*out_frames = fFrames;
  	*out_type = fInfo.type;
  }
  
  
  bigtime_t
  BTimeCode::Microseconds() const
  {
  	bigtime_t us;
  
  	CALLED();
  
  	if (timecode_to_us(fHours,fMinutes,fSeconds,fFrames, &us, &fInfo) == B_OK) {
  		return us;
  	}
  
  	return 0;
  }
  
  
  int32
  BTimeCode::LinearFrames() const
  {
  	int32 linear_frames;
  
  	CALLED();
  
  	if (timecode_to_frames(fHours,fMinutes,fSeconds,fFrames,&linear_frames,&fInfo) == B_OK) {
  		return linear_frames;
  	}
  
  	return 0;
  }
  
  
  void
  BTimeCode::GetString(char *str) const
  {
  	CALLED();
  	sprintf(str,fInfo.format, fHours, fMinutes, fSeconds, fFrames);
  }
  