function MakeQTMovie(cmd,arg, arg2)
% function MakeQTMovie(cmd, arg, arg2)
% Create a QuickTime movie from a bunch of figures (and an optional sound).
%
% Syntax: MakeQTMovie cmd [arg]
% The following commands are supported:
%	addfigure - Add snapshot of current figure to movie
% 	addaxes - Add snapshot of current axes to movie
%	addmatrix data - Add a matrix to movie (convert to jpeg with imwrite)
%	addmatrixsc data - Add a matrix to movie (convert to jpeg with imwrite)
%		(automatically scales image data)
%	addsound data [sr] - Add sound to movie (only monaural for now)
%		(third argument is the sound's sample rate.)
%	cleanup - Remove the temporary files
%	demo - Create a demonstration movie
% 	finish - Finish movie, write out QT file
%	framerate fps - Set movies frame rate [Default is 10 fps]
%	quality # - Set JPEG quality (between 0 and 1)
% 	size [# #] - Set plot size to [width height]
% 	start filename - Start creating a movie with this name
% The start command must be called first to provide a movie name.
% The finish command must be called last to write out the movie
% data. All other commands can be called in any order.  Only one
% movie can be created at a time.
%
% This code is published as Interval Technical Report #1999-066
% The latest copy can be found at 
%	http://web.interval.com/papers/1999-066/
% (c) Copyright Malcolm Slaney, Interval Research, March 1999.

% This is experimental software and is being provided to Licensee
% 'AS IS.'  Although the software has been tested on Macintosh, SGI, 
% Linux, and Windows machines, Interval makes no warranties relating
% to the software's performance on these or any other platforms.
%
% Disclaimer
% THIS SOFTWARE IS BEING PROVIDED TO YOU 'AS IS.'  INTERVAL MAKES
% NO EXPRESS, IMPLIED OR STATUTORY WARRANTY OF ANY KIND FOR THE
% SOFTWARE INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY OF
% PERFORMANCE, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
% IN NO EVENT WILL INTERVAL BE LIABLE TO LICENSEE OR ANY THIRD
% PARTY FOR ANY DAMAGES, INCLUDING LOST PROFITS OR OTHER INCIDENTAL
% OR CONSEQUENTIAL DAMAGES, EVEN IF INTERVAL HAS BEEN ADVISED OF
% THE POSSIBLITY THEREOF.
%
%   This software program is owned by Interval Research
% Corporation, but may be used, reproduced, modified and
% distributed by Licensee.  Licensee agrees that any copies of the
% software program will contain the same proprietary notices and
% warranty disclaimers which appear in this software program.

% This program uses the Matlab imwrite routine to convert each image
% frame into JPEG.  After first reserving 8 bytes for a header that points
% to the movie description, all the compressed images and the sound are 
% added to the movie file.  When the 'finish' method is called then the
% first 8 bytes of the header are rewritten to indicate the size of the 
% movie data, and then the movie header ('moov structure') is written 
% to the output file.
%
% This routine creates files according to the QuickTime file format as
% described in the appendix of 
%	"Quicktime (Inside MacIntosh)," Apple Computer Incorporated, 
%	Addison-Wesley Pub Co; ISBN: 0201622017, April 1993.
% I appreciate help that I received from Lee Fyock (MathWorks) and Aaron 
% Hertzmann (Interval) in debugging and testing this work.

% Changes:
% July 5, 1999 - Removed stss atom since it upset PC version of QuickTime
% November 11, 1999 - Fixed quality bug in addmatrix.  Added addmatrixsc.
% March 7, 2000 - by Jordan Rosenthal (jr@ece.gatech.edu), Added truecolor 
%    capability when running in Matlab 5.3 changed some help comments, fixed 
%    some bugs, vectorized some code.
% April 7, 2000 - by Malcolm.  Cleaned up axis/figure code and fixed(?) SGI
%    playback problems.  Added user data atom to give version information.
%    Fixed sound format problems.
% April 10, 2000 - by Malcolm. Fixed problem with SGI (at least) and B&W
%    addmatrix.

if nargin < 1
	fprintf('Syntax: MakeQTMovie cmd [arg]\n')
	fprintf('The following commands are supported:\n');
	fprintf('	addfigure - Add snapshot of current figure to movie\n')
	fprintf('	addaxes - Add snapshot of current axes to movie\n')
	fprintf('	addmatrix data - Add a matrix to movie ');
			fprintf('(convert to jpeg)\n')
	fprintf('	addmatrixsc data - Add a matrix to movie ');
			fprintf('(scale and convert to jpeg)\n')
	fprintf('	addsound data - Add sound samples ');
			fprintf('(with optional rate)\n')
	fprintf('	demo - Show this program in action\n');
	fprintf('	finish - Finish movie, write out QT file\n');
	fprintf('	framerate # - Set movie frame rate ');
			fprintf('(default is 10fps)\n');
	fprintf('	quality # - Set JPEG quality (between 0 and 1)\n');
	fprintf('	size [# #] - Set plot size to [width height]\n');
	fprintf('	start filename - Start making a movie with ');
			fprintf('this name\n');
	return;
end

global MakeQTMovieStatus
MakeDefaultQTMovieStatus;		% Needed first time, ignored otherwise

switch lower(cmd)
case {'addframe','addplot','addfigure','addaxes'}
	switch lower(cmd)
	case {'addframe','addfigure'}
		hObj = gcf;		% Add the entire figure (with all axes)
	otherwise
		hObj = gca;		% Add what's inside the current axis
	end
	frame = getframe(hObj);
	[I,map] = frame2im(frame);
	if ImageSizeChanged(size(I)) > 0
		return;
	end
	if isempty(map)
					% RGB image
		imwrite(I,MakeQTMovieStatus.imageTmp, 'jpg', 'Quality', ...
		 MakeQTMovieStatus.spatialQual*100);
	else
					% Indexed image
		writejpg_map(MakeQTMovieStatus.imageTmp, I, map);
	end
	[pos, len] = AddFileToMovie;
	n = MakeQTMovieStatus.frameNumber + 1;
	MakeQTMovieStatus.frameNumber = n;
	MakeQTMovieStatus.frameStarts(n) = pos;
	MakeQTMovieStatus.frameLengths(n) = len;
   
%% Allow images to be added by doing:
%%	MakeQTMovie('addimage', '/path/to/file.jpg');
%% This case adapted from addmatrix.  Thanks to 
%% Stephen Eglen <stephen@cogsci.ed.ac.uk> for this idea.
case 'addimage'
	if nargin < 2
		fprintf('MakeQTMovie error: Need to specify a filename with ');
		fprintf('the image command.\n');
		return;
	end

        %% Check to see that the image is the correct size.  Do
        %% this by reading in the image and then checking its size.
	%% tim - temporary image.
        tim = imread(arg); tim_size = size(tim);
	
	fprintf('Image %s size %d %d\n', arg, tim_size(1), tim_size(2));
 	if ImageSizeChanged(tim_size) > 0
 		return;
 	end
	[pos, len] = AddFileToMovie(arg);
	n = MakeQTMovieStatus.frameNumber + 1;
	MakeQTMovieStatus.frameNumber = n;
	MakeQTMovieStatus.frameStarts(n) = pos;
	MakeQTMovieStatus.frameLengths(n) = len;

case 'addmatrix'
	if nargin < 2
		fprintf('MakeQTMovie error: Need to specify a matrix with ');
		fprintf('the addmatrix command.\n');
		return;
	end
	if ImageSizeChanged(size(arg)) > 0
		return;
	end
					% Work around a bug, at least on the
					% SGIs, which causes JPEGs to be 
					% written which can't be read with the
					% SGI QT.  Turn the B&W image into a
					% color matrix.
	if ndims(arg) < 3
		arg(:,:,2) = arg;
		arg(:,:,3) = arg(:,:,1);
	end
	imwrite(arg, MakeQTMovieStatus.imageTmp, 'jpg', 'Quality', ...
		MakeQTMovieStatus.spatialQual*100);
	[pos, len] = AddFileToMovie;
	n = MakeQTMovieStatus.frameNumber + 1;
	MakeQTMovieStatus.frameNumber = n;
	MakeQTMovieStatus.frameStarts(n) = pos;
	MakeQTMovieStatus.frameLengths(n) = len;
	
case 'addmatrixsc'
	if nargin < 2
		fprintf('MakeQTMovie error: Need to specify a matrix with ');
		fprintf('the addmatrix command.\n');
		return;
	end
	if ImageSizeChanged(size(arg)) > 0
		return;
	end
	arg = arg - min(min(arg));
	arg = arg / max(max(arg));
					% Work around a bug, at least on the
					% SGIs, which causes JPEGs to be 
					% written which can't be read with the
					% SGI QT.  Turn the B&W image into a
					% color matrix.
	if ndims(arg) < 3
		arg(:,:,2) = arg;
		arg(:,:,3) = arg(:,:,1);
	end
	imwrite(arg, MakeQTMovieStatus.imageTmp, 'jpg', 'Quality', ...
		MakeQTMovieStatus.spatialQual*100);
	[pos, len] = AddFileToMovie;
	n = MakeQTMovieStatus.frameNumber + 1;
	MakeQTMovieStatus.frameNumber = n;
	MakeQTMovieStatus.frameStarts(n) = pos;
	MakeQTMovieStatus.frameLengths(n) = len;

case 'addsound'
	if nargin < 2
		fprintf('MakeQTMovie error: Need to specify a sound array ');
		fprintf('with the addsound command.\n');
		return;
	end
					% Do stereo someday???
	OpenMovieFile
	MakeQTMovieStatus.soundLength = length(arg);
	arg = round(arg/max(max(abs(arg)))*32765);
	negs = find(arg<0);
	arg(negs) = arg(negs) + 65536;

	sound = mb16(arg);
	MakeQTMovieStatus.soundStart = ftell(MakeQTMovieStatus.movieFp);
	MakeQTMovieStatus.soundLen = length(sound);
	fwrite(MakeQTMovieStatus.movieFp, sound, 'uchar');
	if nargin < 3
		arg2 = 22050;
	end
	MakeQTMovieStatus.soundRate = arg2;
	
case 'cleanup'
	if isstruct(MakeQTMovieStatus)
		if ~isempty(MakeQTMovieStatus.movieFp)
			fclose(MakeQTMovieStatus.movieFp);
			MakeQTMovieStatus.movieFp = [];
		end
		if ~isempty(MakeQTMovieStatus.imageTmp) & ...
		   exist(MakeQTMovieStatus.imageTmp,'file') > 0
			delete(MakeQTMovieStatus.imageTmp);
			MakeQTMovieStatus.imageTmp = [];
		end
	end
	MakeQTMovieStatus = [];

case 'debug'
	fprintf('Current Movie Data:\n');
	fprintf('    %d frames at %d fps\n', MakeQTMovieStatus.frameNumber, ...
					MakeQTMovieStatus.frameRate);
	starts = MakeQTMovieStatus.frameStarts;
	if length(starts) > 10, starts = starts(1:10);, end;
	lens = MakeQTMovieStatus.frameLengths;
	if length(lens) > 10, lens = lens(1:10);, end;
	fprintf('         Start: %6d      Size: %6d\n', [starts; lens]);
	fprintf('    Movie Image Size: %dx%d\n', ...
		MakeQTMovieStatus.imageSize(2), ...);
		MakeQTMovieStatus.imageSize(1));
	if length(MakeQTMovieStatus.soundStart) > 0  
		fprintf('    Sound: %d samples at %d Hz sampling rate ', ...
			MakeQTMovieStatus.soundLength, ...
			MakeQTMovieStatus.soundRate);
		fprintf('at %d.\n', MakeQTMovieStatus.soundStart);
	else
		fprintf('    Sound: No sound track\n');
	end
	fprintf('    Temporary files for images: %s\n', ...
		MakeQTMovieStatus.imageTmp);
	fprintf('    Final movie name: %s\n', MakeQTMovieStatus.movieName);
	fprintf('    Compression Quality: %g\n', ...
		MakeQTMovieStatus.spatialQual);


case 'demo'
	clf
	fps = 10;
	movieLength = 10;
	sr = 22050;
	fn = 'test.mov';
	fprintf('Creating the movie %s.\n', fn);
	MakeQTMovie('start',fn);
	MakeQTMovie('size', [160 120]);
	MakeQTMovie('quality', 1.0);
	theSound = [];
	for i=1:movieLength
		plot(sin((1:100)/4+i));
		MakeQTMovie('addaxes');
		theSound = [theSound sin(440/sr*2*pi*(2^(i/12))*(1:sr/fps))];
	end
	MakeQTMovie('framerate', fps);
	MakeQTMovie('addsound', theSound, sr);
	MakeQTMovie('finish');

case {'finish','close'}
	AddQTHeader;
	MakeQTMovie('cleanup')			% Remove temporary files

case 'framerate'
	if nargin < 2
		fprintf('MakeQTMovie error: Need to specify the ');
		fprintf('frames/second with the framerate command.\n');
		return;
	end
        if ischar(arg);  arg = str2num(arg); end
	MakeQTMovieStatus.frameRate = arg;

case 'help'
	MakeQTMovie				% To get help message.

case 'size'
						% Size is off by one on the
						% Mac.
	if nargin < 2
		fprintf('MakeQTMovie error: Need to specify a vector with ');
		fprintf('the size command.\n');
		return;
	end
	if length(arg) ~= 2
		error('MakeQTMovie: Error, must supply 2 element size.');
	end
	oldUnits = get(gcf,'units');
	set(gcf,'units','pixels');
	cursize = get(gcf, 'position');
	cursize(3) = arg(1);
	cursize(4) = arg(2);
	set(gcf, 'position', cursize);
	set(gcf,'units',oldUnits);

case 'start'
	if nargin < 2
		fprintf('MakeQTMovie error: Need to specify a file name ');
		fprintf('with start command.\n');
		return;
	end
	MakeQTMovie('cleanup');
	MakeDefaultQTMovieStatus;
	MakeQTMovieStatus.movieName = arg;
	
case 'test'
	clf
	MakeQTMovieStatus = [];
	MakeQTMovie('start','test.mov');
 	MakeQTMovie('size', [320 240]);
	MakeQTMovie('quality', 1.0);
	subplot(2,2,1);
	for i=1:10
		plot(sin((1:100)/4+i));
		MakeQTMovie('addfigure');
	end
	MakeQTMovie('framerate', 10);
	MakeQTMovie('addsound', sin(1:5000), 22050);
	MakeQTMovie('debug');
	MakeQTMovie('finish');
	
case 'quality'
	if nargin < 2
		fprintf('MakeQTMovie error: Need to specify a quality ');
		fprintf('(between 0-1) with the quality command.\n');
		return;
	end
	MakeQTMovieStatus.spatialQual = arg;

otherwise
	fprintf('MakeQTMovie: Unknown method %s.\n', cmd);
end

%%%%%%%%%%%%%%%  MakeDefaultQTMovieStatus %%%%%%%%%%%%%%%%%
% Make the default movie status structure.
function MakeDefaultQTMovieStatus
global MakeQTMovieStatus
if isempty(MakeQTMovieStatus)
   MakeQTMovieStatus = struct(...
      'frameRate', 10, ...	% frames per second
      'frameStarts', [], ...  % Starting byte position
      'frameLengths', [], ...
      'timeScale', 10, ...	% How much faster does time run?
      'soundRate', 22050, ... % Sound Sample Rate
      'soundStart', [], ...	% Starting byte position
      'soundLength', 0, ...
      'soundChannels', 1, ...	% Number of channels
      'frameNumber', 0, ...
      'movieFp', [], ...		% File pointer 
      'imageTmp', tempname, ...
      'movieName', 'output.mov', ...
      'imageSize', [0 0], ...
      'trackNumber', 0, ...
      'timeScaleExpansion', 100, ...
      'spatialQual', 1.0);	% Between 0.0 and 1.0
end


%%%%%%%%%%%%%%%  ImageSizeChanged %%%%%%%%%%%%%%%%%
% Check to see if the image size has changed.  This m-file can't 
% deal with that, so we'll return an error.
function err = ImageSizeChanged(newsize)
global MakeQTMovieStatus

newsize = newsize(1:2);			% Don't care about RGB info, if present
oldsize = MakeQTMovieStatus.imageSize;
err = 0;

if sum(oldsize) == 0
	MakeQTMovieStatus.imageSize = newsize;
else
	if sum(newsize ~= oldsize) > 0
		fprintf('MakeQTMovie Error: New image size');
		fprintf('(%dx%d) doesn''t match old size (%dx%d)\n', ...
			newsize(1), newsize(2), oldsize(1), oldsize(2));
		fprintf('   Can''t add this image to the movie.\n');
		err = 1;
	end
end

%%%%%%%%%%%%%%%  AddFileToMovie %%%%%%%%%%%%%%%%%
% OK, we've saved out an image file.  Now add it to the end of the movie
% file we are creating.
% We'll copy the JPEG file in 16kbyte chunks to the end of the movie file.
% Keep track of the start and end byte position in the file so we can put
% the right information into the QT header.
function [pos, len] = AddFileToMovie(imageTmp)
global MakeQTMovieStatus
OpenMovieFile
if nargin < 1
	imageTmp = MakeQTMovieStatus.imageTmp;
end
fp = fopen(imageTmp, 'rb');
if fp < 0
	error('Could not reopen QT image temporary file.');
end

len = 0;
pos = ftell(MakeQTMovieStatus.movieFp);
while 1
	data = fread(fp, 1024*16, 'uchar');
	if isempty(data)
		break;
	end
	cnt = fwrite(MakeQTMovieStatus.movieFp, data, 'uchar');
	len = len + cnt;
end
fclose(fp);

%%%%%%%%%%%%%%%  AddQTHeader %%%%%%%%%%%%%%%%%
% Go back and write the atom information that allows
% QuickTime to skip the image and sound data and find
% its movie description information.
function AddQTHeader()
global MakeQTMovieStatus

pos = ftell(MakeQTMovieStatus.movieFp);
header = moov_atom;
cnt = fwrite(MakeQTMovieStatus.movieFp, header, 'uchar');
fseek(MakeQTMovieStatus.movieFp, 0, -1);
cnt = fwrite(MakeQTMovieStatus.movieFp, mb32(pos), 'uchar');
fclose(MakeQTMovieStatus.movieFp);
MakeQTMovieStatus.movieFp = [];

%%%%%%%%%%%%%%%  OpenMovieFile %%%%%%%%%%%%%%%%%
% Open a new movie file.  Write out the initial QT header.  We'll fill in
% the correct length later.
function OpenMovieFile
global MakeQTMovieStatus
if isempty(MakeQTMovieStatus.movieFp)
	fp = fopen(MakeQTMovieStatus.movieName, 'wb');
	if fp < 0
		error('Could not open QT movie output file.');
	end
	MakeQTMovieStatus.movieFp = fp;
	cnt = fwrite(fp, [mb32(0) mbstring('mdat')], 'uchar');
end

%%%%%%%%%%%%%%%  writejpg_map %%%%%%%%%%%%%%%%%
% Like the imwrite routine, but first pass the image data through the indicated
% RGB map.
function writejpg_map(name,I,map)
global MakeQTMovieStatus

[y,x] = size(I);

% Force values to be valid indexes.  This fixes a bug that occasionally 
% occurs in frame2im in Matlab 5.2 which incorrectly produces values of I 
% equal to zero.
I = max(1,min(I,size(map,1)));

rgb = zeros(y, x, 3);
t = zeros(y,x);
t(:) = map(I(:),1)*255; rgb(:,:,1) = t;
t(:) = map(I(:),2)*255; rgb(:,:,2) = t;
t(:) = map(I(:),3)*255; rgb(:,:,3) = t;

imwrite(uint8(rgb),name,'jpeg','Quality',MakeQTMovieStatus.spatialQual*100);

%%%%%%%%%%%%%%%  SetAtomSize %%%%%%%%%%%%%%%%%
% Fill in the size of the atom
function y=SetAtomSize(x)
y = x;
y(1:4) = mb32(length(x));

%%%%%%%%%%%%%%%  mb32 %%%%%%%%%%%%%%%%%
% Make a vector from a 32 bit integer
function y = mb32(x)				
if size(x,1) > size(x,2)
	x = x';
end

y = [bitand(bitshift(x,-24),255); ...
     bitand(bitshift(x,-16),255); ...
     bitand(bitshift(x, -8),255); ...
     bitand(x,              255)];
y = y(:)';

%%%%%%%%%%%%%%%  mb16 %%%%%%%%%%%%%%%%%
% Make a vector from a 16 bit integer
function y = mb16(x)
if size(x,1) > size(x,2)
	x = x';
end

y = [bitand(bitshift(x, -8),255); ...
     bitand(x,              255)];
y = y(:)';

%%%%%%%%%%%%%%%  mb8 %%%%%%%%%%%%%%%%%
% Make a vector from a 8 bit integer
function y = mb8(x)
if size(x,1) > size(x,2)
	x = x';
end

y = [bitand(x,              255)];
y = y(:)';

%
% The following routines all create atoms necessary
% to describe a QuickTime Movie. The basic idea is to
% fill in the necessary data, all converted to 8 bit 
% characters, then fix it up later with SetAtomSize so
% that it has the correct header.  (This is easier than
% counting by hand.)

%%%%%%%%%%%%%%%  mbstring %%%%%%%%%%%%%%%%%
% Make a vector from a character string
function y = mbstring(s)
y = double(s);


%%%%%%%%%%%%%%%  dinf_atom %%%%%%%%%%%%%%%%%
function y = dinf_atom()
y = SetAtomSize([mb32(0) mbstring('dinf') dref_atom]);

%%%%%%%%%%%%%%%  dref_atom %%%%%%%%%%%%%%%%%
function y = dref_atom()
y = SetAtomSize([mb32(0) mbstring('dref') mb32(0) mb32(1) ...
		mb32(12) mbstring('alis') mb32(1)]);

%%%%%%%%%%%%%%%  edts_atom %%%%%%%%%%%%%%%%%
function y = edts_atom(add_sound_p)
global MakeQTMovieStatus
fixed1 = bitshift(1,16);			% Fixed point 1
if add_sound_p > 0
	duration = MakeQTMovieStatus.soundLength / ...
			MakeQTMovieStatus.soundRate * ...
			MakeQTMovieStatus.timeScale;
else
	duration = MakeQTMovieStatus.frameNumber / ...
			MakeQTMovieStatus.frameRate * ...
			MakeQTMovieStatus.timeScale;
end
duration = ceil(duration);

y = [mb32(0) ...				% Atom Size
     mbstring('edts') ...			% Atom Name
     SetAtomSize([mb32(0) ...			% Atom Size
		  mbstring('elst') ...		% Atom Name
		  mb32(0) ...			% Version/Flags
		  mb32(1) ...			% Number of entries
		  mb32(duration) ...		% Length of this track
		  mb32(0) ...			% Time
		  mb32(fixed1)])];		% Rate
y = SetAtomSize(y);

%%%%%%%%%%%%%%%  hdlr_atom %%%%%%%%%%%%%%%%%
function y = hdlr_atom(component_type, sub_type)
if strcmp(sub_type, 'vide')
	type_string = 'Apple Video Media Handler';
elseif strcmp(sub_type, 'alis')
	type_string = 'Apple Alias Data Handler';
elseif strcmp(sub_type, 'soun')
	type_string = 'Apple Sound Media Handler';
end

y = [mb32(0) ...				% Atom Size
     mbstring('hdlr') ...			% Atom Name
     mb32(0) ...				% Version and Flags
     mbstring(component_type) ...		% Component Name
     mbstring(sub_type) ...			% Sub Type Name
     mbstring('appl') ...			% Component manufacturer
     mb32(0) ...				% Component flags
     mb32(0) ...				% Component flag mask
     mb8(length(type_string)) ...		% Type Name byte count
     mbstring(type_string)];			% Type Name
y = SetAtomSize(y);

%%%%%%%%%%%%%%%  mdhd_atom %%%%%%%%%%%%%%%%%
function y = mdhd_atom(add_sound_p)
global MakeQTMovieStatus

if add_sound_p
	data = [mb32(MakeQTMovieStatus.soundRate)  ...
		mb32(MakeQTMovieStatus.soundLength)];
else
	data = [mb32(MakeQTMovieStatus.frameRate * ...
			MakeQTMovieStatus.timeScaleExpansion)  ...
		mb32(MakeQTMovieStatus.frameNumber * ...
			MakeQTMovieStatus.timeScaleExpansion)];
end

y = [mb32(0) mbstring('mdhd') ...		% Atom Header
     mb32(0) ...
     mb32(round(now*3600*24)) ...		% Creation time
     mb32(round(now*3600*24)) ...		% Modification time
     data ...
     mb16(0) mb16(0)];
y = SetAtomSize(y);
 
%%%%%%%%%%%%%%%  mdia_atom %%%%%%%%%%%%%%%%%
function y = mdia_atom(add_sound_p)
global MakeQTMovieStatus
 
if add_sound_p
	hdlr = hdlr_atom('mhlr', 'soun');
else
	hdlr = hdlr_atom('mhlr', 'vide');
end

y = [mb32(0) mbstring('mdia') ...		% Atom Header
     mdhd_atom(add_sound_p) ...
     hdlr ...					% Handler Atom
     minf_atom(add_sound_p)];
y = SetAtomSize(y);


%%%%%%%%%%%%%%%  minf_atom %%%%%%%%%%%%%%%%%
function y = minf_atom(add_sound_p)
global MakeQTMovieStatus
 
if add_sound_p
	data = smhd_atom;
else
	data = vmhd_atom;
end

y = [mb32(0) mbstring('minf') ...		% Atom Header
     data ...
     hdlr_atom('dhlr','alis') ...
     dinf_atom ...
     stbl_atom(add_sound_p)];
y = SetAtomSize(y);

%%%%%%%%%%%%%%%  moov_atom %%%%%%%%%%%%%%%%%
function y=moov_atom
global MakeQTMovieStatus
MakeQTMovieStatus.timeScale = MakeQTMovieStatus.frameRate * ...
				MakeQTMovieStatus.timeScaleExpansion;

if MakeQTMovieStatus.soundLength > 0
	sound = trak_atom(1);
else
	sound = [];
end

y = [mb32(0) mbstring('moov') ...
     mvhd_atom udat_atom sound trak_atom(0) ];
y = SetAtomSize(y);

%%%%%%%%%%%%%%%  mvhd_atom %%%%%%%%%%%%%%%%%
function y=mvhd_atom
global MakeQTMovieStatus

fixed1 = bitshift(1,16);			% Fixed point 1
frac1 = bitshift(1,30);				% Fractional 1
if length(MakeQTMovieStatus.soundStart) > 0		
	NumberOfTracks = 2;
else
	NumberOfTracks = 1;
end

					% Need to make sure its longer
					% of movie and sound lengths
MovieDuration = max(MakeQTMovieStatus.frameNumber / ...
			MakeQTMovieStatus.frameRate, ...
		    MakeQTMovieStatus.soundLength / ...
			MakeQTMovieStatus.soundRate);
MovieDuration = ceil(MovieDuration * MakeQTMovieStatus.timeScale);

y = [mb32(0) ...			% Size
     mbstring('mvhd') ...		% Movie Data
     mb32(0) ...			% Version and Flags
     mb32(0) ...			% Creation Time (unknown)
     mb32(0) ...			% Modification Time (unknown)
     mb32(MakeQTMovieStatus.timeScale) ...	% Movie's Time Scale
     mb32(MovieDuration) ...		% Movie Duration 
     mb32(fixed1) ...			% Preferred Rate
     mb16(255) ...			% Preferred Volume
     mb16(0) ...			% Fill
     mb32(0) ...			% Fill
     mb32(0) ...			% Fill
     mb32(fixed1) mb32(0) mb32(0) ...	% Transformation matrix (identity)
     mb32(0) mb32(fixed1) mb32(0) ...
     mb32(0) mb32(0) mb32(frac1) ...
     mb32(0) ...			% Preview Time
     mb32(0) ...			% Preview Duration
     mb32(0) ...			% Poster Time
     mb32(0) ...			% Selection Time
     mb32(0) ...			% Selection Duration
     mb32(0) ...			% Current Time
     mb32(NumberOfTracks)];		% Video and/or Sound?

y = SetAtomSize(y);

%%%%%%%%%%%%%%%  raw_image_description %%%%%%%%%%%%%%%%%
function y = raw_image_description()
global MakeQTMovieStatus

fixed1 = bitshift(1,16);			% Fixed point 1
codec = [12 'Photo - JPEG                   '];

y = [mb32(0) mbstring('jpeg') ...		% Atom Header
     mb32(0) mb16(0) mb16(0) mb16(0) mb16(1) ...
     mbstring('appl') ...
     mb32(1023) ...				% Temporal Quality (perfect)
     mb32(floor(1023*MakeQTMovieStatus.spatialQual)) ...
     mb16(MakeQTMovieStatus.imageSize(2)) ...
     mb16(MakeQTMovieStatus.imageSize(1)) ...
     mb32(fixed1 * 72) mb32(fixed1 * 72) ...
     mb32(0) ...
     mb16(0) ...
     mbstring(codec) ...
     mb16(24) mb16(65535)];
y = SetAtomSize(y);


%%%%%%%%%%%%%%%  raw_sound_description %%%%%%%%%%%%%%%%%
function y = raw_sound_description()
global MakeQTMovieStatus
y = [mb32(0) mbstring('twos') ...		% Atom Header
     mb32(0) mb16(0) mb16(0) mb16(0) mb16(0) ...
     mb32(0) ...
     mb16(MakeQTMovieStatus.soundChannels) ...
     mb16(16) ...				% 16 bits per sample
     mb16(0) mb16(0) ...
     mb32(round(MakeQTMovieStatus.soundRate*65536))];
y = SetAtomSize(y);


%%%%%%%%%%%%%%%  smhd_atom %%%%%%%%%%%%%%%%%
function y = smhd_atom()
y = SetAtomSize([mb32(0) mbstring('smhd') mb32(0) mb16(0) mb16(0)]);

%%%%%%%%%%%%%%%  stbl_atom %%%%%%%%%%%%%%%%%
% Removed the stss atom since it seems to upset the PC version of QT
% and it is empty so it doesn't add anything.
% Malcolm - July 5, 1999
function y = stbl_atom(add_sound_p)
y = [mb32(0) mbstring('stbl') ...		% Atom Header
     stsd_atom(add_sound_p) ...
     stts_atom(add_sound_p) ...
     stsc_atom(add_sound_p) ...
     stsz_atom(add_sound_p) ...
     stco_atom(add_sound_p)];
y = SetAtomSize(y);

%%%%%%%%%%%%%%%  stco_atom %%%%%%%%%%%%%%%%%
function y = stco_atom(add_sound_p)
global MakeQTMovieStatus
if add_sound_p
	y = [mb32(0) mbstring('stco') mb32(0) mb32(1) ...
	     mb32(MakeQTMovieStatus.soundStart)];
else
	y = [mb32(0) mbstring('stco') mb32(0) ...
	     mb32(MakeQTMovieStatus.frameNumber) ...
	     mb32(MakeQTMovieStatus.frameStarts)];
end
y = SetAtomSize(y);

%%%%%%%%%%%%%%%  stsc_atom %%%%%%%%%%%%%%%%%
function y = stsc_atom(add_sound_p)
global MakeQTMovieStatus
if add_sound_p
	samplesperchunk = MakeQTMovieStatus.soundLength;
else
	samplesperchunk = 1;
end

y = [mb32(0) mbstring('stsc') mb32(0) mb32(1)  ...
     mb32(1) mb32(samplesperchunk) mb32(1)];
y = SetAtomSize(y);

%%%%%%%%%%%%%%%  stsd_atom %%%%%%%%%%%%%%%%%
function y = stsd_atom(add_sound_p)
if add_sound_p
	desc = raw_sound_description;
else
	desc = raw_image_description;
end

y = [mb32(0) mbstring('stsd') mb32(0) mb32(1) desc];
y = SetAtomSize(y);

%%%%%%%%%%%%%%%  stss_atom %%%%%%%%%%%%%%%%%
function y = stss_atom()
y = SetAtomSize([mb32(0) mbstring('stss') mb32(0) mb32(0)]);

%%%%%%%%%%%%%%%  stsz_atom %%%%%%%%%%%%%%%%%
function y = stsz_atom(add_sound_p)
global MakeQTMovieStatus
if add_sound_p
	y = [mb32(0) mbstring('stsz') mb32(0) mb32(2) ...
	     mb32(MakeQTMovieStatus.soundLength)];
else
	y = [mb32(0) mbstring('stsz') mb32(0) mb32(0) ...
	     mb32(MakeQTMovieStatus.frameNumber) ...
	     mb32(MakeQTMovieStatus.frameLengths)];
end
y = SetAtomSize(y);

%%%%%%%%%%%%%%%  stts_atom %%%%%%%%%%%%%%%%%
function y = stts_atom(add_sound_p)
global MakeQTMovieStatus
if add_sound_p
	count_duration = [mb32(MakeQTMovieStatus.soundLength) mb32(1)];
else
	count_duration = [mb32(MakeQTMovieStatus.frameNumber) ...
		mb32(MakeQTMovieStatus.timeScaleExpansion)];
end

y = SetAtomSize([mb32(0) mbstring('stts') mb32(0) mb32(1) count_duration]);

%%%%%%%%%%%%%%%  trak_atom %%%%%%%%%%%%%%%%%
function y = trak_atom(add_sound_p)
global MakeQTMovieStatus

y = [mb32(0) mbstring('trak') ...		% Atom Header
	tkhd_atom(add_sound_p) ...		% Track header
	edts_atom(add_sound_p) ...		% Edit List
	mdia_atom(add_sound_p)];
y = SetAtomSize(y);

%%%%%%%%%%%%%%%  tkhd_atom %%%%%%%%%%%%%%%%%
function y = tkhd_atom(add_sound_p)
global MakeQTMovieStatus

fixed1 = bitshift(1,16);			% Fixed point 1
frac1 = bitshift(1,30);				% Fractional 1 (CHECK THIS)

if add_sound_p > 0
	duration = MakeQTMovieStatus.soundLength / ...
			MakeQTMovieStatus.soundRate * ...
			MakeQTMovieStatus.timeScale;
else
	duration = MakeQTMovieStatus.frameNumber / ...
			MakeQTMovieStatus.frameRate * ...
			MakeQTMovieStatus.timeScale;
end
duration = ceil(duration);

y = [mb32(0) mbstring('tkhd') ...	% Atom Header
     mb32(15) ...			% Version and flags
     mb32(round(now*3600*24)) ...	% Creation time
     mb32(round(now*3600*24)) ...	% Modification time
     mb32(MakeQTMovieStatus.trackNumber) ...
     mb32(0) ...
     mb32(duration) ...			% Track duration
     mb32(0) mb32(0) ...		% Offset and priority
     mb16(0) mb16(0) mb16(255) mb16(0) ...	% Layer, Group, Volume, fill
     mb32(fixed1) mb32(0) mb32(0) ...	% Transformation matrix (identity)
     mb32(0) mb32(fixed1) mb32(0) ...
     mb32(0) mb32(0) mb32(frac1)];

if add_sound_p
	y = [y mb32(0) mb32(0)];	% Zeros for sound
else
	y = [y mb32(fliplr(MakeQTMovieStatus.imageSize)*fixed1)];
end
y= SetAtomSize(y);

MakeQTMovieStatus.trackNumber = MakeQTMovieStatus.trackNumber + 1;

%%%%%%%%%%%%%%%  udat_atom %%%%%%%%%%%%%%%%%
function y = udat_atom()
atfmt = [64 double('fmt')];
atday = [64 double('day')];

VersionString = 'Matlab MakeQTMovie version April 7, 2000';

y = [mb32(0) mbstring('udta') ...
	SetAtomSize([mb32(0) atfmt mbstring(['Created ' VersionString])]) ...
	SetAtomSize([mb32(0) atday '  ' date])];
y = SetAtomSize(y);


%%%%%%%%%%%%%%%  vmhd_atom %%%%%%%%%%%%%%%%%
function y = vmhd_atom()

y = SetAtomSize([mb32(0) mbstring('vmhd') mb32(0) ...
    mb16(64) ...			% Graphics Mode
    mb16(0) mb16(0) mb16(0)]);		% Op Color
