function ClutMovieDemo
% ClutMovieDemo
%
% ClutMovieDemo displays a counterphase flickering grating, by lookup
% table animation.
% 
% Historically, lookup table animation was an important technique in
% producing dynamic computer images. However, computers have gotten a lot
% faster, and today one can often just show an arbitrary movie, without
% having to think about the restrictions imposed by lookup table
% tricks. See MovieDemo.
% 
% The first three sections of the code, below, are pure Matlab. They
% create variables containing a sinusoidal image (step 1) and a set of
% color lookup tables to animate the image (step 3). Steps 4 to 6 use
% Psychophysics Toolbox routines to display and animate the image.
% 
% If you set "useCal" to 1 (i.e. true) below, then ClutMovieDemo uses the
% PsychCal routines to do gamma correction of your monitor. A generic
% calibration data file is shipped with the toolbox. For a real experiment
% you'll want to replace it with calibration data for your particular
% monitor.
% 
% If you set "useRush" to 1 (i.e. true) below, then ClutMovieDemo uses
% Rush to minimize interruption of the dynamic display.
% 
% NOTE: To get fast reliable timing in Matlab loops it is vital that you
% load all the functions (M files and MEX files) into memory BEFORE the
% loop starts. Matlab automatically loads functions as it executes them,
% but reading them from disk is slow. If you use Rush, which we encourage,
% then this recommendation becomes a requirement, because any attempt to
% access files while Rushing will hang forever.
% 
% See PsychCal,Rush,MovieDemo.

% 7/31/96	dhb	Wrote it.
% 8/4/96	dhb	Wrote calibrated version.
% 2/1/97	dhb	Updated calling conventions.
%			dhb	Changed framesPerClut to delayPerClut for clarity.
% 5/31/97	dgp	Added notes about Rush and MovieDemo.
% 1/12/98	dgp	Restrict array size, if necessary, for compatibility with Student Matlab.
% 3/1/98	dgp	Folded ClutMovieCalDemo and ClutMovieTimeTest into ClutMovieDemo.
% 3/9/98	dgp	Turn-off divide-by-zero warning.
%				Identify computer, video card, and software, to help interpret bug reports.
% 3/10/98	dgp	Added usePeekBlanking option. This used to be permanently on, but in the actual experiments it's
%				usually preferable to run at higher priority, which precludes the use of PeekBlanking.
% 3/27/98	dgp	Renamed SetGamma to SetGammaMethod.
% 7/21/98	dgp Make sure all Rushed functions are in memory.
% 4/3/99	dgp Force 8-bit mode since the program assumes it.
% 4/08/02   awi Added platform conditionals to exclude Screen 'Computer', 'ClutMovie', & 'VideoCard' calls on Windows.  
% 4/13/02   dgp Cosmetic. Updated call to Screen 'Computer' to receive struct instead of list.
% 4/13/02   awi Replaced DeviceToSettings with new name PrimaryToSettings.
% 4/14/02   dhb Removed all references to obsolete Screen('ClutMovie').
% 6/29/02   dgp Use new version of Screen VideoCard.
% 11/5/02   dhb, ly  Replace SetClut with LoadClut.

whichScreen=max(Screen('Screens'));
useCal=logical(1);		% option to enable gamma correction.
useRush=logical(1);		% option to use Rush to minimize interruptions.
usePeekBlanking=logical(0);	% option to use 'PeekBlanking' for extra diagnostic information, but runs at lower priority.
fprintf('ClutMovieDemo demonstrates the use of lookup table animation to display a flickering grating\n');
if useCal
	fprintf(' - with gamma correction by PsychCal routines\n');
end
if useRush
	fprintf(' - with interrupts minimized by Rush\n');
end
if usePeekBlanking
	fprintf(' - with frame counts provided by PeekBlanking\n');
end
hz=FrameRate(whichScreen);

% Check for limitation of Student Matlab
[c,maxElements]=computer;	
maxSize=floor(sqrt(maxElements));

if strcmp(computer,'MAC2')
	% Describe the computer
	comp=Screen('Computer');
	fprintf('%s''s %s, %s, %s\n',comp.owner,comp.model,comp.system,comp.vm);
end

% Describe screen
fprintf('Screen %d: ',whichScreen);
if strcmp(computer,'MAC2')
	card=Screen(whichScreen,'VideoCard');
	fprintf('%s (%s version %s)',card.cardNameLong,card.driverName,card.driverVersion);
	if length(card.slotName)>0
		fprintf(' in slot %s',card.slotName);
	end
end
fprintf('\n');

% STEP 1: Create a sinusoidal image in a Matlab matrix. The grating is
% scaled between 1 and 255. The creation happens in two steps. First
% create a sinusoidal vector, then replicate this vector to produce a
% sinusoidal image. The replication is done by an outer product. This is
% not the fastest way to do it in Matlab, but it is a little clearer.
nPixels = min(256,maxSize);
cyclesPerImage = 4;
sinVector = 1+(1+sin(2*pi*cyclesPerImage*(1:nPixels)/nPixels))/2*254;
sinImage = ones(nPixels,1)*sinVector;

% STEP 2: Load calibration files. We will use the default calibration, but
% you can specify an argument to LoadCalFile to override this. The call to
% SetGammaMethod determines how the gamma table is used at run time. An
% argument of zero means that exhaustive table search is used. Here we
% have to do a lot of conversions, so it's quicker to build an inverse
% gamma table and use this at run time. An argument of 1 to SetGammaMethod
% does this.
if useCal
	fprintf('Loading calibration file. . . . ');
	calCM = LoadCalFile(whichScreen);
	calCM = SetGammaMethod(calCM,1);
	fprintf('Done.\n');
end

% STEP 3: Create a set of color lookup tables (cluts) for animating the
% grating. Each clut is a 256 by three matrix. We compute a set of
% cluts that will produce one cycle of counterphase flicker. These
% are all stored in a single large matrix (theCluts). We also create
% a uniform clut (offClut) to initialize the display. The variable
% nCluts determines the number of temporal quantization intervals.
% The single call to PrimaryToSettings converts from the linear device
% primary space to the actual desired clut entries.
nCluts = min(80,floor(maxElements/(256*3)));
theCluts = zeros(256,3,nCluts);
theContrasts = sin(2*pi*(0:nCluts-1)/nCluts);
fprintf('Computing %g cluts:',nCluts);
for i = 1:nCluts
	if mod(i,10)==1
		fprintf(' %d',i);
	end
	contrast = theContrasts(i);
	% Matlab 5.2b has a bug in LINSPACE. If the first 2 args are equal it
	% returns an empty matrix. Supposedly this will be fixed in the released
	% version of 5.2. As a work-around we prevent contrast from being exactly
	% zero.
	if contrast==0
		contrast=0.00001;
	end
	lowValDev = (1-contrast)/2;
	highValDev = (1+contrast)/2;
	if useCal
		coordsDev = linspace(lowValDev,highValDev,256)';
		clutEntriesDev = [coordsDev coordsDev coordsDev];
		theCluts(:,:,i) = PrimaryToSettings(calCM,clutEntriesDev')';
	else
		clutEntries = round(linspace(lowValDev*255,highValDev*255,256)');
		theCluts(:,:,i) = [clutEntries clutEntries clutEntries];
	end
end
if useCal
	offClut = ones(256,1)*PrimaryToSettings(calCM,[0.5 0.5 0.5]')';
else
	offClut = ones(256,3)*128;
end
fprintf('. Done.\n');

% STEP 4: Open up the a window on the screen, initialize the clut,
% and draw a grating onto the screen. This is done by calling the
% Screen function of the Psychophysics Toolbox. The Screen function
% performs a variety of display tasks according to its text argument.
[window,screenRect] = Screen(whichScreen,'OpenWindow',128,[],8);
%Screen(window,'SetClut',offClut);
LoadClut(window,offClut);
Screen(window,'PutImage',sinImage);

% STEP 5: Animate the clut.  Depending of value set for useRush,
% the code is either run directly (useRush == 0) or through the
% Rush function at higher priority (useRush == 1).
framesPerClut=1;
nCycles=4;
HideCursor;
clutCounter = 1;
s0=0;f0=0;f=0;s=0;rem(1,1);
if ~useRush
	for j=1	% Matlab precompiles loops
		Screen(window,'WaitBlanking');
		[f0,s0]=Screen(window,'PeekBlanking');
		for i=1:nCluts*nCycles
			%Screen(window,'SetClut',theCluts(:,:,clutCounter));
			LoadClut(window,theCluts(:,:,clutCounter));
			clutCounter=rem(clutCounter,nCluts)+1;
			Screen(window,'WaitBlanking',framesPerClut-1);
		end
		[f,s]=Screen(window,'PeekBlanking');
		%Screen(window,'SetClut',offClut);
		LoadClut(window,offClut);
	end
else
	string=        'for j=1;';
	string=[string 'Screen(window,''WaitBlanking'');'];
	if usePeekBlanking
		string=[string '[f0,s0]=Screen(window,''PeekBlanking'');'];
	else
		string=[string 's0=GetSecs;f0=0;f=0;'];
	end
	string=[string 'for i=1:nCluts*nCycles;'];
	%string=[string 'Screen(window,''SetClut'',theCluts(:,:,clutCounter));'];
	string =[string 'LoadClut(window,theCluts(:,:,clutCounter));'];
	string=[string 'clutCounter=rem(clutCounter,nCluts)+1;'];
	string=[string 'Screen(window,''WaitBlanking'',framesPerClut-1);'];
	string=[string 'end;'];
	if usePeekBlanking
		string=[string '[f,s]=Screen(window,''PeekBlanking'');'];
	else
		string=[string 's=GetSecs;'];
	end
	%string=[string 'Screen(window,''SetClut'',offClut);'];
	string=[string 'LoadClut(window,offClut);'];
	string=[string 'end;'];
	if usePeekBlanking
		p=MaxPriority(window,'WaitBlanking','GetSecs','PeekBlanking');
	else
		p=MaxPriority(window,'WaitBlanking','GetSecs');
	end
	Screen('Screens');GetSecs;rem(2,2); % make sure all RUSHed functions are in memory.
	Rush(string,p);
end
f=f-f0;
s=s-s0;
name='Matlab loop';
clutsShown=nCluts*nCycles;

Screen(window,'FillRect',128);
ShowCursor;

% Report timing results.
fprintf('\n%s at priority %g:\n',name,p);
fprintf('Showed %g cluts at %g framesPerClut on your %.0f Hz display, ',clutsShown,framesPerClut,hz);
fRequested=nCluts*nCycles*framesPerClut;
fprintf('which took \n%.0f%% as long as expected. ',100*s*hz/fRequested);
if usePeekBlanking
	old=warning;
	warning off;
	fprintf('(PeekBlanking says the time-based duration, \n');
	fprintf('secs*FrameRate, in frames, was %.0f%% of the blanking-interrupt-based\n',100*s*hz/f);
	fprintf('frame count. Exceeding 100%% may indicate that some monitor frames\n');
	fprintf('weren''t counted because the video driver occasionally blocked the\n');
	fprintf('blanking interrupt. See ScreenTest.)\n');
	warning(old);
else
	fprintf('\n');
end

% STEP 6: Close the window.
Screen(window,'Close');
WaitSecs(1);	% pause between runs


