function  [data,P] = PLExp1(sbj_number,DB_spec)

%PLEXP1 -- Perceptual Learning Experiment 1
%
%  [data,P] = PLExp1(sbj_number,[DB_spec])
%
%  SBJ_NUMBER is a number that uniquely identifies the subject.
%  DB_SPEC is either a structure or a string specifying a file name.
%  Both arguments are passed to the function FIND_SBJ_DESCR to
%  determine the subject's group, session, data file name, etc.
%  When not supplied, DB_SPEC defaults to 'PLE_subjects'.
%
%  P is the 'master parameter structure'.
%  DATA is a matrix with one row per trial and with 19 columns:
%    1. subject number
%    2. subject group
%    3. session (day)
%    4. block
%    5. overall trial number
%    6. block trial number
%    7. time stamp
%    8. noise orientation (1=negative, 2=positive)
%    9. Gabor orientation (1=negative, 2=positive)
%   10. difficulty level (max Gabor contrast)
%   11. position (1=above fixation, 2=below fixation)
%   12. actual noise orientation (in degrees)
%   13. actual Gabor orientation (in degrees)
%   14. actual Gabor peak contrast
%   15. actual position (in pixels relative to fixation at center screen)
%   16. response (1='negative', 2='positive', 0='bad key')
%   17. correct (1 if response==Gabor_orientation, 0 otherwise)
%   18. response time (in msec from the onset of the stimulus)
%   19. exit code (0='successful trial', 1='bad key', 2='missed frame')
%  The same data are written in a file with name such as 'sbj001.dat'.
%
%  See also IMPORT_PLE_DATA, DO_PLE_TRIAL, MAKE_PLE_STIM, PILOT2.

%  Original coding by Alexander Petrov, UC Irvine.
%  $Revision: 2.0 $  $Date: 2002/01/08 18:18 $

%-- Print herald
outs = 1 ;   % output stream = standard output
fprintf(outs,'\nPerceptual learning experiment, ver. 1.\n') ;

%-- Set random generator seed
seed = round(100*sum(clock)) ;    % see RAND's help page
rand('state',seed) ;
fprintf(outs,'\nRandom generator seed set to %d.\n',seed) ;


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%   ACQUAINT WITH PARTICIPANT   %%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

if (nargin < 2)  DB_spec = 'PLE_subjects' ; end    % default

%-- Open the subject database
if (ischar(DB_spec))
   fprintf(outs,'Consulting the participant data base %s.mat...\n',...
           DB_spec) ;
   DB = open_sbj_database(DB_spec) ;
elseif (isstruct(DB_spec))
   DB = DB_spec ;
else
   error('DB_SPEC must be either a file name or a structure array.') ;
end

%-- Look for entries with this particular subject number
sbj_descr = find_sbj_descr(sbj_number,DB) ;
if (isempty(sbj_descr))
   fprintf(outs,'It appears that we have a new participant.\n') ;
   %- Let's see how many subjects in each group we have so far
   group = [DB(:).group]' ;
   if (sum(group==1) < sum(group==2))
      group = 1 ;
   elseif (sum(group==1) > sum(group==2))
      group = 2 ;
   else   % equal number of Ss in each group so far
      group = floor(2*rand) + 1 ;      % break ties randomly
   end
   %- Make a new 'subject descriptor' and save it in the database
   sbj_descr = new_sbj_descr(sbj_number,group) ;  % ask for name, etc.
   DB = update_sbj_database(sbj_descr,DB_spec) ;  % write file as side eff
%else information about this subject successfully retrieved from DB
end

%- Make sure all fields of SBJ_DESCR are correct
sbj_descr    % print on console as a structure
k = input('Is this information correct? (y) ','s') ;
if (isempty(k))  k = 'y' ;  end   % default
if (~ismember(k,'yY'))
   fprintf(outs,'\n\nYou may edit the SBJ_DESCR variable.\n') ;
   fprintf(outs,'Type ''return'' to continue.\n\n') ;
   keyboard ;
end

sbj_number = sbj_descr.sbj_number ;
group = sbj_descr.group ;
session = sbj_descr.session ;
data_filename = sbj_descr.filename ;


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%      EXPERIMENTAL DESIGN      %%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Each participant completes 8 days (or 'sessions') of 1200+ trials each.
% Each session consists of 4 blocks of 300+ trials each.
%
% The stimuli are Gabor patches embedded in noise. The noise is filtered
% to have a predominant orientation that can be either positive or 
% negative (see parameter P.noise_orient below). All stimuli within a 
% block have the same noise orientation. Thus there are two kinds of
% blocks: As and Bs.  Block A (or '1') uses P.noise_orient(1) (which
% is negative 15 degrees, see below) while block B (or '2') uses
% P.noise_orient(2) (positive 15 degrees, measured from the vertical).
%
% The experiment has an overall ABAB design, with roughly 2 days in
% each condition. The participants are randomly assigned to two groups.
% The respective schedules are as follows (1 row = 1 session = 4 blocks):

schedule{1} = [1 2 2 2 ; 2 2 2 2 ; 2 1 1 1 ; 1 1 1 1 ;...  %ABBB BBBB...
               1 2 2 2 ; 2 2 2 2 ; 2 1 1 1 ; 1 1 1 2 ] ;   %     ...AAAB
schedule{2} = 3 - schedule{1} ;                  % counterbalance groups

curr_schedule = schedule{group}(session,:) ;

% There are two Gabor orientations (see parameter P.Gabor_orient below).
% The task of the participants is to indicate what Gabor orientation is
% presented on each trial. The main dependent variable is the proportion
% of correct responses. The main independent variable is just time.
% The accuracy (proportion correct) is expected to improve with time
% due to perceptual learning. The purpose of the experiment is to
% determine whether there is any transfer and/or interference across
% blocks with different backgrounds (A vs. B).
% 
% Once the background orientation is fixed within a block, the stimuli
% vary along the following three dimensions: 
%  - Gabor orientation (with 2 levels, positive and negative)
%  - Gabor peak contrast (with 3 levels, see P.Gabor_Cpeak below)
%  - presentation position on the screen (with 2 levels, see P.position)
% Thus there are 12 different stimuli altogether. Each block contains
% 25 replications of each stimulus, making 300 trials total. The actual
% number of trials may be slightly greater than that because invalid
% trials (e.g. when the subject presses a bad key) are repeated.

levels = {[1 2],...     % indices in P.Gabor_orient, see DO_PLE_TRIAL
          [1 2 3],...   % indices in P.Gabor_Cpeak , see MAKE_PLE_STIM
          [1 2]  } ;    % indices in P.position

%-- Session parameters
replications = 25 ;
trials_per_block = 12 * replications ;                 % 2x3x2=12 stimuli
N_trials = trials_per_block * length(curr_schedule) ;  % 300x4=1200
if (session==1)
   N_demo_trials = 10 ;
else
   N_demo_trials = 2 ;
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%   EQUIPMENT CHARACTERISTICS   %%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% NANAO Technology FlexScan 6600 paperwhite monitor with a P4 phosphor.
% Driven by the internal videocard of a Power Macintosh 7300/200.
% A special circuit combines two 8-bit output channels of the video-
% card to produce 24*256=6144 distinct gray levels (see MAKE_LINEAR_LUT).

whichScreen = 1 ;     % present stimuli on the auxiliary monitor
viewing_dist = 720 ;  % milimeters, +/-10%
mm_pix = 0.5655 ;     % miliimeters/pixel [resol. 640x480, 19" monitor]
deg_pix = mm_pix/viewing_dist * 180/pi ;   % 0.045 degree/pixel, +/-10%

GAMMA = 1.95 ;        % =mean([1.91 1.99])  % on 10-30 and 12-12-0
BRstep = 24 ;         % hardwired in the special circuit

msec_frame = 1000/120 ; % must be 8.3 msec/frame (120 Hz)
pixel_depth = 32 ;      % must be 32 bits
if (~ismember(pixel_depth,SCREEN(whichScreen,'PixelSizes')))
   error('Sorry, I need a screen that supports 32-bit pixel depth.') ;
end
if (SCREEN('Preference','Backgrounding'))
   warning('I turn Backgrounding off because it confuses CharAvail.');
   SCREEN('Preference','Backgrounding',0) ;  % see CharAvail's help page
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%   PARAMETER DEFINITIONS   %%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%-- Main parameters. Determined via pilot studies (see PILOT2.M).
% 64 pixels = 2.88 degrees of visual angle viewed from approx 720 mm.
P.size = [64 64] ;       % pixels, this scales everything else
P.duration = 75 ;        % msec, must be a multiple of MSEC_FRAME
P.Gabor_orient = [-10 +10] .* pi/180 ;
P.noise_orient = [-15 +15] .* pi/180 ;   % max power around +/-15 deg
P.noise_Cpeak = 0.66667 ;                % peak contrast of the noise
P.Gabor_Cpeak = [0.23 0.15 0.10 0.50] ;  % 3 difficulty levels + 1 demo
  % The peak contrasts 0.23, 0.15, and 0.10 were shown to yield approx.
  % 80%, 70%, and 60% correct for novice observers (see PILOT2.M).
  % The last value in the list is for demo trials only.

%-- Gabor parameters. See functions GABOR and GABOR_PARAMS for details
P.Gabor_params.phase = pi/2 ;
P.Gabor_params.freq = 2*deg_pix ;    % 2 cycles/deg --> 1/11.1 cycles/pix
P.Gabor_params.sigma = 0.4/deg_pix ; % 0.4 degrees --> 8.89 pixels

%-- Filtered noise parameters. See functions BUTTERWORTH2 and FILTNOISE.
P.noise_params.sigma = 0.15 ;   % Gaussian with mean 0 and std.dev. SIGMA
P.noise_params.cutoff = 0.20 ;  % see BUTTERWORTH2 for details
P.noise_params.outcut = 4.0 ;   % FILTNOISE outlier cutoff

%-- Lookup table parameters. See IMAGE/LUT_PARAMS and IMAGE/MAKE_SPLIT_LUT.
P.LUT_params.gamma = GAMMA ;
P.LUT_params.BRstep = BRstep ;
P.LUT_params.contrast = 1.0 ;   % use the full dynamic range of the monitor
P.LUT_params.domain = [0 255] ;

%-- Trial parameters
dy = round(5.0/deg_pix) ;         % 5.0 degrees --> 111 pixels
P.position = {[0 -dy],[0 +dy]} ;  % dx=0, dy=+/-111 pixels off fixation
P.times.fix_onset = 0.500 ;     % fixation appears approx 500 msec before
P.times.alert_onset = 0.250 ;   % sound alert approx 250 msec before stim
P.times.fix2alert = P.times.fix_onset - P.times.alert_onset ;
P.times.wrong_delay = 0.100 ;   % during auditory feedback
P.times.ITI = 0.750 ;           % inter-trial interval, seconds
P.keys = {'sdfecSDFEC','jklimJKLIM'} ;    % {negKeys,posKeys}


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%   REUSABLE DATA STRUCTS   %%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%-- Everything happens in the following oval window
Xmin = -floor(P.size(1)/2) ; Ymin = -floor(P.size(2)/2) ;
[X,Y] = meshgrid([1:P.size(1)]+Xmin,[1:P.size(2)]+Ymin) ;
oval = (X.^2)./(Xmin.^2) + (Y.^2)./(Ymin.^2) ;
P.mask_idx = find(oval > 1) ;

%-- Calibrate GABOR_PARAMS so that the peak amplitude is 1.0
params = P.Gabor_params ;
params.amplit = 1.0 ;
params.orient = P.Gabor_orient(1) ;  % just for calibration
A = gabor(X,Y,params) ;
Cpeak = max(abs(A(:))) ;             % Cpeak < 1.0  because PHASE=pi/2
P.Gabor_params.amplit = params.amplit / Cpeak ;   % augment P
params.amplit = P.Gabor_params.amplit ;

%-- Now make one Gabor patch for each orientation and difficulty level
P.Gabor = zeros([P.size length(P.Gabor_orient) length(P.Gabor_Cpeak)]) ;
for k = 1:length(P.Gabor_orient)
   params.orient = P.Gabor_orient(k) ;
   A = gabor(X,Y,params) ;
   for l = 1:length(P.Gabor_Cpeak)
      P.Gabor(:,:,k,l) = A .* P.Gabor_Cpeak(l) ;
   end
end

%-- Prepare filters for later calls to FILTNOISE
[Fx,Fy] = fftmesh(P.size) ;   % filter the REPPIXed noise patch
P.Filter = zeros([P.size length(P.noise_orient)]) ;
for k = 1:length(P.noise_orient)
   theta = P.noise_orient(k) + pi/2 ;     % orientation in Fourier space
   P.Filter(:,:,k) = butterworth2(Fx,Fy,theta,P.noise_params.cutoff) ;
end

%-- Lookup table (constructed here, installed later)
[P.LUT,gray] = make_linear_LUT(LUT_params) ;
P.colors.black = P.LUT_params.domain(1) ;
P.colors.gray = gray ;
P.colors.white = P.LUT_params.domain(2) ;
P.LUT_params.span = min(gray-P.colors.black,P.colors.white-gray) ;

%-- Sounds
P.sounds.alert = MakeBeep(500,0.100) ;  % 500 Hz, 100 msec
P.sounds.wrong = MakeBeep(300,0.150) ;  % 300 Hz, 150 msec

%-- Fixation crosshair
P.fixation = repmat(gray,9,9) ;
P.fixation(:,[4:6]) = P.colors.black ;
P.fixation([4:6],:) = P.colors.black ;
P.blank = repmat(gray,P.size) ;         % used to clear screen


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%    SCREENS AND MOVIES     %%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%-- Open a SCREEN window and set the lookup table
fprintf(outs,'\nOpening screen %d...',whichScreen) ;
[winPtr,winRect] = SCREEN(whichScreen,'OpenWindow',gray,[],pixel_depth) ;
fprintf(outs,'   Done.\nSetting the lookup table...') ;
SCREEN(winPtr,'SetClut',P.LUT) ;
SCREEN(winPtr,'FillRect',gray) ;    % clear the screen
fprintf(outs,'   Done.\n') ;

%-- Convert stimulus duration from milliseconds to video frames
N_frames = round(P.duration / msec_frame) ;
if (abs(N_frames - (P.duration/msec_frame)) > 0.01)
   warning('Stimulus duration is not an integer number of frames.') ;
end
P.times.target_play = (N_frames+1) * msec_frame/1000 ;  % one blank frame

%-- Create one movieStruct for each stimulus position
fprintf(outs,'Creating offscreen buffers...') ;
P.movieStruct(1) = MakeMovieStruct(winPtr,repmat(P.size,2,1),...
                                   P.position{1},[1 2],[N_frames 1]) ;
for k = 2:length(P.position)
   P.movieStruct(k) = CloneMovieStruct(P.movieStruct(1),P.position{k}) ;
end
fprintf(outs,'   Done.\n') ;

%%%%%%  At this point, P is ready to be passed to DO_PLE_TRIAL.  %%%%%%


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%    PREPARATION AND DEMO   %%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%-- Open the data file
fprintf(outs,'Opening data file %s...',data_filename) ;
[data_fid,errmsg] = fopen(data_filename,'at') ;   % mode = append text
if (data_fid == -1)
   warning(errmsg) ;
   fprintf(outs,'Fix the problem and type ''return'' to continue.\n\n') ;
   keyboard ;
else
   fprintf(outs,'   Done.\n') ;
end

%-- Open the sound channel
fprintf(outs,'Opening the sound channel...') ;
SND('Open') ;
fprintf(outs,'   Done.\n') ;

%-- Wait for confirmation before presenting the demo
FlushEvents('keyDown') ;
SCREEN(winPtr,'WindowToFront') ;
ask(winPtr,'Please press Enter to proceed to a short demo... ',...
           P.colors.black,gray,'GetChar') ;
SCREEN(winPtr,'FillRect',gray) ;
WaitSecs(1.000) ;

%-- Demonstration trials -- not recorded in the data file
block = factor_design([2 2],ceil(N_demo_trials/4),1) ;
demo_diff = length(P.Gabor_Cpeak) ;     % demo contrast level
backgr_orient = curr_schedule(1) ;

for tr = 1:N_demo_trials
   spec = [block(tr,1),demo_diff,block(tr,2)] ;   % [orient,diff,pos]
   record = do_PLE_trial(P,spec(1),spec(2),backgr_orient,spec(3)) ;
   WaitSecs(P.times.ITI) ;
end

%-- Wait for confirmation before moving on to the real experiment
FlushEvents('keyDown') ;
SCREEN(winPtr,'WindowToFront') ;
ask(winPtr,'Please press Enter to proceed to the first real trial... ',...
           P.colors.black,gray,'GetChar') ;
SCREEN(winPtr,'FillRect',gray) ;
WaitSecs(1.000) ;


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%        MAIN   LOOP        %%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

data = zeros(N_trials+200,19) ;   % 200 reserve rows
d = zeros(1,19) ;

time_zero = GetSecs ;
exp_tr = 1 ;                % global trial number
for bl = 1:length(curr_schedule)   % for each block
   %- The orientation of the background noise if fixed within each block
   backgr_orient = curr_schedule(bl) ;
   block = factor_design(levels,replications ,1) ;   % randomized
   %- Main trial loop
   tr = 1 ;
   while (tr <= size(block,1))
      % Present a stimulus and get a response from the subject
      spec = block(tr,:) ;    % [Gabor_orient,difficulty,position]
      record = do_PLE_trial(P,spec(1),spec(2),backgr_orient,spec(3)) ;
      % Did everything go OK?
      exitcode = record(9) ;      % see DO_PLE_TRIAL
      if (exitcode ~= 0)          % something went wrong
         block = [block;spec] ;   % repeat same trial at end of block
      end
      % Record data
      d([1 2 3 4]) = [sbj_number group session bl] ;
      d([5 6]) = [exp_tr tr] ;          % expmt & block trial numbers
      d(7) = record(1) - time_zero ;    % time stamp
      d(8) = backgr_orient ;            % noise orientation
      d([9 10 11]) = spec ;             % stimulus specifications
      d(12) = P.noise_orient(record(2))*180/pi ;   % actual NOISE_ORIENT
      d(13) = P.Gabor_orient(record(5))*180/pi ;   % actual GABOR_ORIENT
      d(14) = P.Gabor_Cpeak(record(4)) ;           % actual difficulty
      d(15) = P.position{record(3)}(2) ;           % actual position (dy)
      d(16) = record(6) ;               % response key: 1=left,2=right
      d(17) = record(7) ;               % correct = (resp==Gabor_orient)
      d(18) = record(8) ;               % response time, msec
      d(19) = record(9) ;               % exitcode: 0=OK,1=badKey,2=missFr
      data(exp_tr,:) = d ;
      % Write data file, see IMPORT_PLE_DATA
      fprintf(data_fid,'%02d %2d %2d %2d %4d %4d  ',...
              d(1), d(2), d(3),d(4),d(5),d(6)) ;
      fprintf(data_fid,'%10.3f  %2d %2d %2d %2d  ',...
              d(7), d(8),d(9),d(10),d(11)) ;
      fprintf(data_fid,'%+6.1f %+6.1f %5.2f %+5d   ',...
              d(12),d(13),d(14),d(15)) ;
      fprintf(data_fid,'%2d %2d %6d %2d\n',...
              d(16),d(17),round(d(18)),d(19)) ;
      % Move on to the next trial
      tr = tr+1 ;
      exp_tr = exp_tr+1 ;
      WaitSecs(P.times.ITI) ;
   end  % of the main trial loop within a block
   %- Rest period at the boundary between blocks
   if (bl < length(curr_schedule))        % except the very last block
      FlushEvents('mouseDown','mouseUp') ;
      ask(winPtr,'Please take a break... Click the mouse to continue.',...
          P.colors.black,gray,'GetClicks') ;
      SCREEN(winPtr,'FillRect',gray) ;
      WaitSecs(0.500) ;
   end
end  % of the block loop

data(exp_tr:end,:) = [] ;    % cut out unused reserve DATA rows
fprintf(outs,'\n%d trials completed. %4.1f percent correct.\n\n',...
        exp_tr-1,mean(data(:,17))*100) ;


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%         CLEAN  UP         %%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

fprintf(outs,'Closing data file %s...',data_filename) ;
fclose(data_fid) ;
fprintf(outs,'   Done.\nClosing all screens and screen buffers...') ;
SCREEN('CloseAll') ;
fprintf(outs,'   Done.\nClosing the sound channel...') ;
SND('Close') ;
fprintf(outs,'   Done.\n') ;

%-- Update the subject database, if the name of the file is known
if (ischar(DB_spec))
   fprintf(outs,'Updating the participant data base %s...',DB_spec) ;
   session_log.session = session ;
   session_log.date = date ;            % e.g. '08-Jan-2002'
   session_log.trials = exp_tr-1 ;
   sbj_descr.session_log(end+1) = session_log ;
   sbj_descr.session = session + 1 ;    % prepare for next session
   DB = update_sbj_database(sbj_descr,DB_spec) ;  % write to file
   fprintf(outs,'   Done.\n') ;
end

fprintf(outs,'\nThank you for your participation. Have a nice day!\n\n') ;

%--- Return DATA and P
%%%%% End of file PLEXP1.M
