function hz=BlankingInterruptRate(windowPtrOrScreenNumber,secs)
% hz=BlankingInterruptRate(windowPtrOrScreenNumber,[secs])
% Quickly and accurately measures the frames per second of the "blanking"
% interrupt associated with the specified screen. (In nearly all cases the
% "blanking" interrupt occurs at blanking time, but Michael Bache reported
% that for one powerbook model the "blanking" interrupt in fact occurs at
% each System Tick, with no relation at all to the actual display.) The
% optional argument "secs" specifies how long to spend doing the
% measurement (for each screen/window), but we always wait for at least
% one frame; default of 0.1 sec yields accuracy of 0.1 Hz or better.
% 
% The argument "windowPtrOrScreenNumber" can be an array of such;
% BlankingInterruptRate will return a corresponding array of frame rates.
% 
% HOW WE DO IT: PeekBlanking provides the time of occurrence of blanking
% interrupts. Occasionally a blanking interrupt may be delayed by
% unrelated interruptions. The measured time between interrupts will be
% shortened if the first interrupt is delayed, and lengthened if the
% second interrupt is delayed. We minimize the occurrence of unrelated
% interruptions by using Rush. We reduce the loss of accuracy by timing a
% multi-frame interval. 
% 
% Usually you'll want to call FrameRate instead. It's more reliable if
% what you really want to know is the display's frame rate.
% 
% See FrameRate and ScreenTest.
% 
% Denis Pelli 28 February 1998

% 2/28/98 dgp Wrote it.
% 3/1/98  dgp Allow windowPtrOrScreenNumber to be an array.
% 3/14/98  dgp Add secs argument.
% 7/14/98  dgp Renamed from "FrameRate" to "BlankingInterruptRate".
% 7/17/98	dgp   Using enhanced Rush, use easy-to-read cell array for string.
% 7/21/98	dgp We were getting confusing warnings when running on ixMicro TwinTurbo card because
%			it suppresses interrupts for the whole frame when you call SetClut. WaitBlanking 23
%			wasn't allowing any interrupts so the interrupt-driven frame and time meters didn't advance.
%			So, when SetClutDriverWaitsForBlanking is true, we abandon interrupt-based timing and instead
%			use the new count and time that are saved at the end of every SetClut. This can be done
%			at high priority, resulting in a very accurate measurement.
% 7/21/98 dgp Make sure all Rushed functions are in memory.
% 8/1/98 dgp Always return the interrupt rate. Ignore SetClut.
% 8/1/98 dgp Tidy up the warning.

if nargin<1 | nargin>2 | nargout>1 | ~isa(windowPtrOrScreenNumber,'double') | isempty(windowPtrOrScreenNumber)
	error('Usage: hz=BlankingInterruptRate(windowPtrOrScreenNumber,[secs])');
end
if nargin<2
	secs=0.1;
end
if length(windowPtrOrScreenNumber)>1
	% windowPtrOrScreenNumber is array
	hz=windowPtrOrScreenNumber;
	for i=1:length(windowPtrOrScreenNumber(:))
		hz(i)=BlankingInterruptRate(windowPtrOrScreenNumber(i),secs);
	end
else
	% windowPtrOrScreenNumber is scalar
	w=windowPtrOrScreenNumber;
	reps=1;
	frames=ceil(max(1,secs*75/reps)); % assume roughly 75 Hz in choosing how many frames to time.
	ii=0;f0=0;s0=0;f=0;s=0;Screen('Screens');GetSecs; % load into memory
	string={
		'for ii=1;'
			'Screen(w,''WaitBlanking'');'
			'secs=GetSecs;'
			'[f0,s0]=Screen(w,''PeekBlanking'');'
			'Screen(w,''WaitBlanking'',frames);'
			'secs=GetSecs-secs;'
			'[f,s]=Screen(w,''PeekBlanking'');'
		'end;'
	};
	% time blanking interrupt
	waitBlankingAlwaysCallsSetClut=Screen(w,'Preference','WaitBlankingAlwaysCallsSetClut',0);
	interruptPunchesBlankingClock=Screen(w,'Preference','InterruptPunchesBlankingClock',1);
	p=MaxPriority(w,'WaitBlanking','PeekBlanking','GetSecs','BlankingInterrupt');
	r=zeros(1,reps);
	for i=1:reps
		Rush(string,p);
		f=f-f0;
		s=s-s0;
		old=warning;
		warning off
		r(i)=f/s;
		warning(old)
		if r(i)==0 | ~isfinite(r(i))
			string=sprintf('During Screen(w,''WaitBlanking'',%.0f), rushed at priority %.1g, the \nframe counter advanced %d and time advanced %.0f ms.',frames,p,f,s*1000);
			if f==0
				string=sprintf('%s The frame counter \nisn''t advancing.',string);
			else
				string=sprintf('%s Either WaitBlanking \nisn''t waiting or GetSecs isn''t advancing. (s0=%f)',string,s0);
			end
			warning(string);
		end
	end
	hz=median(r);
	% restore settings
	Screen(w,'Preference','WaitBlankingAlwaysCallsSetClut',waitBlankingAlwaysCallsSetClut);
	Screen(w,'Preference','InterruptPunchesBlankingClock',interruptPunchesBlankingClock);
end
