view multithreshold 1.46/subjGUI_MT.m @ 38:c2204b18f4a2 tip

End nov big change
author Ray Meddis <rmeddis@essex.ac.uk>
date Mon, 28 Nov 2011 13:34:28 +0000
parents 25d53244d5c8
children
line wrap: on
line source
function varargout = subjGUI_MT(varargin)

% Begin initialization code - DO NOT EDIT
gui_Singleton = 1;
gui_State = struct('gui_Name',       mfilename, ...
    'gui_Singleton',  gui_Singleton, ...
    'gui_OpeningFcn', @subjGUI_MT_OpeningFcn, ...
    'gui_OutputFcn',  @subjGUI_MT_OutputFcn, ...
    'gui_LayoutFcn',  [] , ...
    'gui_Callback',   []);
if nargin && isstr(varargin{1})
    gui_State.gui_Callback = str2func(varargin{1});
end

if nargout
    [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
    gui_mainfcn(gui_State, varargin{:});
end
% End initialization code - DO NOT EDIT

% --- Executes just before subjGUI_MT is made visible.
function subjGUI_MT_OpeningFcn(hObject, eventdata, handles, varargin)

% Choose default command line output for subjGUI_MT
handles.output = hObject;
initializeGUI(handles)
guidata(hObject, handles);

function varargout = subjGUI_MT_OutputFcn(hObject, eventdata, handles)
% Get default command line output from handles structure
varargout{1} = handles.output;

% -----------------------------------------------------initializeGUI
function initializeGUI(handles)
global experiment
global subjectGUIHandles expGUIhandles
addpath (['..' filesep 'MAP'], ['..' filesep 'utilities'], ...
    ['..' filesep 'parameterStore'],  ['..' filesep 'wavFileStore'],...
    ['..' filesep 'testPrograms'])

dbstop if error

% subjectGUI size and location         % [left bottom width height]
scrnsize=get(0,'screensize');
set(0, 'units','pixels')
switch experiment.ear
    % use default size unless...
    case {'MAPmodel',  'MAPmodelMultich', 'MAPmodelSingleCh', ...
            'statsModelLogistic','statsModelRareEvent'}
        % 	subjectGUI not needed for modelling so minimize subject GUI
        set(gcf, 'units','pixels')
        y=[0*scrnsize(3) 0.8*scrnsize(4) 0.1*scrnsize(3) 0.2*scrnsize(4)];
        set(gcf,'position',y, 'color',[.871 .961 .996])

    case 'MAPmodelListen',
        % 	subjectGUI is needed for display purposes. Make it large
        set(gcf, 'units','pixels')
        y=[.665*scrnsize(3) 0.02*scrnsize(4) ...
            0.33*scrnsize(3) 0.5*scrnsize(4)]; % alongside
        set(gcf,'position',y, 'color',[.871 .961 .996])
end

switch experiment.ear
    case{'left', 'right','diotic', 'dichoticLeft','dichoticRight'}
        % Look to see if the button box exists and, if so, initialise it
        buttonBoxIntitialize		% harmless if no button box attached
end

% clear display of previous mean values. This is a new measurement series
axes(expGUIhandles.axes4), cla
reset (expGUIhandles.axes4)

% handles needed in non-callback routines below
subjectGUIHandles=handles;

% start immediately
startNewExperiment(handles, expGUIhandles)
% This is the end of the experiment. Exit here and return to ExpGUI.

% ----------------------------------------------------- startNewExperiment
function startNewExperiment(handles, expGUIhandles)
% An experiment consists of a series of 'runs'.
% Resets all relevant variables at the beginning of a new experiment.
global experiment stimulusParameters betweenRuns

% 'start new experiment' button is the only valid action now
experiment.status='waitingForStart';

switch experiment.threshEstMethod
    % add appropriate labels to subject GUI buttons
    case {'2I2AFC++', '2I2AFC+++'}
        set(handles.pushbutton3,'string','')
        set(handles.pushbutton2,'string','2')
        set(handles.pushbutton1,'string','1')
        set(handles.pushbutton0,'string','0')
    case {'MaxLikelihood', 'oneIntervalUpDown'}
        if stimulusParameters.includeCue
            set(handles.pushbutton3,'string','')
            set(handles.pushbutton2,'string','2')
            set(handles.pushbutton1,'string','1')
            set(handles.pushbutton0,'string','0')
        else
            set(handles.pushbutton3,'string','')
            set(handles.pushbutton2,'string','YES')
            set(handles.pushbutton1,'string','NO')
            set(handles.pushbutton0,'string','')
        end
end

switch experiment.paradigm
    case 'discomfort'
        set(handles.pushbutton3,'string','')
        set(handles.pushbutton2,'string','uncomfortable')
        set(handles.pushbutton1,'string','loud')
        set(handles.pushbutton0,'string','comfortable')
        experiment.allowCatchTrials=0;
end

% experiment.subjGUIfontSize is set on expGUI
set(handles.pushbutton3,'FontSize',experiment.subjGUIfontSize)
set(handles.pushbutton2,'FontSize',experiment.subjGUIfontSize)
set(handles.pushbutton1,'FontSize',experiment.subjGUIfontSize)
set(handles.pushbutton0,'FontSize',experiment.subjGUIfontSize)
set(handles.pushbuttoNotSure,'FontSize',experiment.subjGUIfontSize)
set(handles.pushbuttonGO,'FontSize',experiment.subjGUIfontSize)
set(handles.textMSG,'FontSize',experiment.subjGUIfontSize)

set(handles.pushbutton19,'visible','off') % unused button

% start command window summary of progress
fprintf(' \n ----------- NEW MEASUREMENTS\n')
disp(['paradigm:   ' experiment.paradigm])
cla(expGUIhandles.axes1)
cla(expGUIhandles.axes2)
cla(expGUIhandles.axes4)
cla(expGUIhandles.axes5)

experiment.stop=0;              % status of 'stop' button
experiment.pleaseRepeat=0;      % status of 'repeat' button
experiment.buttonBoxStatus='not busy';

% date and time and replace ':' with '_'
date=datestr(now);idx=findstr(':',date);date(idx)='_';
experiment.date=date;
timeNow=clock; betweenRuns.timeNow= timeNow;
experiment.timeAtStart=[num2str(timeNow(4)) ':' num2str(timeNow(5))];
experiment.minElapsed=0;

% unpack catch trial rates. The rate declines from the start rate
%  to the base rate using a time constant.
stimulusParameters.catchTrialRate=stimulusParameters.catchTrialRates(1);
stimulusParameters.catchTrialBaseRate=...
    stimulusParameters.catchTrialRates(2);
stimulusParameters.catchTrialTimeConstant=...
    stimulusParameters.catchTrialRates(3);
if stimulusParameters.catchTrialBaseRate==0
    stimulusParameters.catchTrialRate=0;
end

% for human measurements only, identify the start catch trial rate
switch experiment.ear
    case{'left', 'right','diotic', 'dichoticLeft','dichoticRight'}
        fprintf('stimulusParameters.catchTrialRate= %6.3f\n', ...
            stimulusParameters.catchTrialRate)
end

% Reset betweenRuns parameters. this occurs only at experiment start
% withinRuns values are reset in 'startNewRun'
% this approach creates a more readable structure summary printout.
betweenRuns.thresholds=[];
betweenRuns.thresholds_mean=[];
betweenRuns.thresholds_median=[];
betweenRuns.forceThresholds=[];
betweenRuns.observationCount=[];
betweenRuns.catchTrials=[];
betweenRuns.timesOfFirstReversals=[];
betweenRuns.bestThresholdTracks=[];
betweenRuns.levelTracks=[];
betweenRuns.responseTracks=[];
betweenRuns.slopeKTracks=[];
betweenRuns.gainTracks=[];
betweenRuns.VminTracks=[];
betweenRuns.bestGain=[];
betweenRuns.bestVMin=[];
betweenRuns.bestPaMin=[];
betweenRuns.bestLogisticM=[];
betweenRuns.bestLogisticK=[];
betweenRuns.resets=0;
betweenRuns.runNumber=0;

% Up to two parameters can be changed between runs
% Find the variable parameters and randomize them
% e.g. 'variableList1 = stimulusParameters.targetFrequency;'
eval(['variableList1=stimulusParameters.' betweenRuns.variableName1 ';']);
eval(['variableList2=stimulusParameters.' betweenRuns.variableName2 ';']);
nVar1=length(variableList1);
nVar2=length(variableList2);

% Create two sequence vectors to represent the sequence of var1 and var2
% values. 'var1' changes most rapidly.
switch betweenRuns.randomizeSequence
    % {'randomize within blocks', 'fixed sequence',...
    %  'randomize across blocks'}
    case 'fixed sequence'
        var1Sequence=repmat(betweenRuns.variableList1, 1,nVar2);
        var2Sequence=reshape(repmat(betweenRuns.variableList2, ...
            nVar1,1),1,nVar1*nVar2);
    case 'randomize within blocks'
        % the blocks are not randomized
        var1Sequence=betweenRuns.variableList1;
        ranNums=rand(1, length(var1Sequence)); [x idx]=sort(ranNums);
        var1Sequence=var1Sequence(idx);
        betweenRuns.variableList1=variableList1(idx);
        var1Sequence=repmat(var1Sequence, 1,nVar2);
        var2Sequence=reshape(repmat(betweenRuns.variableList2, nVar1,1)...
            ,1,nVar1*nVar2);
    case 'randomize across blocks'
        var1Sequence=repmat(betweenRuns.variableList1, 1,nVar2);
        var2Sequence=reshape(repmat(betweenRuns.variableList2, nVar1,1),...
            1,nVar1*nVar2);
        ranNums=rand(1, nVar1*nVar2);
        [x idx]=sort(ranNums);
        var1Sequence=var1Sequence(idx);
        var2Sequence=var2Sequence(idx);
        % there should be one start value for every combination
        %  of var1/ var2. In principle this allows these values to be
        % programmed. Not currently in use.
        stimulusParameters.WRVstartValues=...
            stimulusParameters.WRVstartValues(idx);
end
betweenRuns.var1Sequence=var1Sequence;
betweenRuns.var2Sequence=var2Sequence;

% caught out vector needs to be linked to the length of the whole sequence
betweenRuns.caughtOut=zeros(1,length(var1Sequence));

disp('planned sequence:')
if min(var1Sequence)>1
    % use decidaml places only if necessary
    disp([betweenRuns.variableName1 ': ' num2str(var1Sequence,'%6.0f')  ])
else
    disp([betweenRuns.variableName1 ': ' num2str(var1Sequence,'%8.3f')  ])
end
if min(var1Sequence)>1
    disp([betweenRuns.variableName2 ': ' num2str(var2Sequence,'%6.0f') ])
else
    disp([betweenRuns.variableName2 ': ' num2str(var2Sequence,'%8.3f') ])
end

fprintf('\nvariable1 \t  variable2\t  \n')
fprintf('%s \t  %s\t  Threshold  \n',betweenRuns.variableName1,...
    betweenRuns.variableName2)

% Light up 'GO' on subjGUI and advise.
set(handles.editdigitInput,'visible','off')
switch experiment.ear
    case {'statsModelLogistic', 'statsModelRareEvent',...
            'MAPmodel',  'MAPmodelMultiCh','MAPmodelSingleCh'}
        % no changes required if model used
    otherwise
        set(handles.pushbuttonGO,'backgroundcolor','y')
        set(handles.pushbuttonGO,'visible','on')
        set(handles.frame1,'visible','off')
        set(handles.textMSG,'backgroundcolor', 'w')
        msg=[{'Ready to start new Experiment'}, {' '}, {'Please, click on the GO button'}];
        set(handles.textMSG,'string', msg)

        set(handles.pushbuttoNotSure,'visible','off')
        set(handles.pushbuttonWrongButton,'visible','off')
        set(handles.pushbutton3,'visible','off')
        set(handles.pushbutton2,'visible','off')
        set(handles.pushbutton1,'visible','off')
        set(handles.pushbutton0,'visible','off')
        pause(.1) % to allow display to be drawn
end

% Selecting the 'GO' button is the only valid operation action now
experiment.status='waitingForGO'; 	% i.e. waiting for new run

% control is now either manual, model (MAP) or randomization
switch experiment.ear
    case {'MAPmodel','MAPmodelMultiCh','MAPmodelSingleCh','MAPmodelListen'}                                     % MAP model is now the subject
        stimulusParameters.calibrationdB=0;             % Pascals required!
        MAPmodelRunsGUI(handles)
        % model is now the subject
    case  {'statsModelLogistic', 'statsModelRareEvent'}
        % no catch trials for the statistical model
        stimulusParameters.catchTrialBaseRate=0;
        stimulusParameters.catchTrialRate=0;
        statsModelRunsGUI(handles)
    otherwise
        %manual operation; wait for user to click on 'GO'
end

% Experiment complete (after MAP or randomization)
% return to  'initializeGUI' and then back to expGUI
% Manual control finds its own way home. Program control assumed when
% the user hits the GO button

% -----------------------------------------------------------------   startNewRun
function startNewRun(handles)
% There are many ways to arrive here.
%  Under manual control this is achieved by hitting the GO button
%   either via the button box or a mouse click
%  MAP and randomization methods call this too

global experiment stimulusParameters betweenRuns withinRuns expGUIhandles
global LevittControl rareEvent errormsg

figure(handles.figure1) % guarantee subject GUI visibility

% ignore call if program is not ready
if ~strcmp(experiment.status,'waitingForGO'), return, end

set(handles.pushbuttonGO,'visible','off')

% append message to expGUI message box to alert experimenter that the user
% is active
addToMsg('Starting new trial',0)

cla(expGUIhandles.axes1),  title(''); % stimulus
cla(expGUIhandles.axes2),  title(''); % WRV track
drawnow

betweenRuns.runNumber=betweenRuns.runNumber + 1;

withinRuns.trialNumber=1;
withinRuns.variableValue=...
    stimulusParameters.WRVstartValues(betweenRuns.runNumber);
% add random jitter to start level
if ~experiment.singleShot
    % SS or single shot allows the user to precisely set the WRV
    withinRuns.variableValue=withinRuns.variableValue +...
        (rand-0.5)*stimulusParameters.jitterStartdB;
end

withinRuns.peaks=[];
withinRuns.troughs=[];
withinRuns.levelList=[];
withinRuns.meanEstTrack=[];
withinRuns.bestSlopeK=[];
withinRuns.bestGain=[];
withinRuns.bestVMin=[];
withinRuns.forceThreshold=NaN;
withinRuns.responseList=[];
withinRuns.caughtOut=0;
withinRuns.wrongButton=0;
withinRuns.catchTrialCount=0;
withinRuns.thresholdEstimateTrack=[];

withinRuns.beginningOfPhase2=0;
withinRuns.nowInPhase2=0;
withinRuns.thisIsRepeatTrial=0;

rareEvent.Euclid=NaN;
rareEvent.bestGain=NaN;
rareEvent.bestVMin=NaN;
rareEvent.thresholddB=0;
rareEvent.bestPaMindB=NaN;
rareEvent.predictionLevels=[];
rareEvent.predictionsRE=[];

LevittControl.sequence=[];

% on-screen count of number of runs still to complete
trialsToGo=length(betweenRuns.var1Sequence)-betweenRuns.runNumber;
set(handles.toGoCounter,'string', trialsToGo);

switch experiment.threshEstMethod
    case {'2I2AFC++', '2I2AFC+++'}
        % For 2I2AFC the buttons need to be on the screen ab initio
        Levitt2      % inititalize Levitt2 procedure
end

switch experiment.ear
    case{'left', 'right','diotic', 'dichoticLeft','dichoticRight'}
        % allow subject time to recover from 'go' press
        pause(experiment.clickToStimulusPause)
end

errormsg=nextStimulus(handles);				% get the show on the road

% terminate if there is any kind of problem
if ~isempty(errormsg)
    % e.g. limits exceeded, clipping
    disp(errormsg)
    runCompleted(handles)
    return
end
% return route is variable (see intro to this function)

% -----------------------------------------------------buttonBox_callback
function buttonBox_callback(obj, info)
% deals with a button press on the button box.

global experiment
global serobj
global subjectGUIHandles

% do not accept callback if one is already in process
if strcmp(experiment.buttonBoxStatus,'busy')
    disp(' ignored button press')
    return % to quiescent state
end
experiment.buttonBoxStatus='busy';

% identify the code of the button pressed
buttonPressedNo = fscanf(serobj,'%c',1);

% This is the map from the button to the Cedrus codes
switch experiment.buttonBoxType
    case 'horizontal'
        pbGo='7'; 		pb0='1';
        pb1='2';		pb2='3';
        pbRepeat='4';	pbWrong='6';	pbBlank='5';
    case 'square'
        pbGo='7';		pb0='1';
        pb1='3';		pb2='4';
        pbRepeat='8';	pbWrong='6';	pbBlank='5';
end

% decide what to do
switch experiment.status
    case {'presentingStimulus', 'waitingForStart', 'trialcompleted', ...
            'endOfExperiment'}
        disp(' ignored button press')

    case 'waitingForGO'
        % i.e. waiting for new run
        if strcmp(buttonPressedNo,pbGo)			% only GO button  accepted
            startNewRun(subjectGUIHandles)
        else
            disp(' ignored button press')
        end

    case 'waitingForResponse'
        % response to stimuli
        switch buttonPressedNo
            case pb0						% button 0 (top left)
                switch experiment.threshEstMethod
                    case {'2I2AFC++', '2I2AFC+++'}
                        disp(' ignored button press')
                    otherwise
                        set(subjectGUIHandles.pushbutton0,...
                            'backgroundcolor','r')
                        pause(.1)
                        set(subjectGUIHandles.pushbutton0,...
                            'backgroundcolor',get(0,...
                            'defaultUicontrolBackgroundColor'))
                        userSelects0or1(subjectGUIHandles)
                end

            case pb1						% button 1 (bottom left)
                switch experiment.threshEstMethod
                    case {'2I2AFC++', '2I2AFC+++'}
                        userSelects0or1(subjectGUIHandles)
                    otherwise
                        set(subjectGUIHandles.pushbutton1,...
                            'backgroundcolor','r')
                        pause(.1)
                        set(subjectGUIHandles.pushbutton1,...
                            'backgroundcolor',get(0,...
                            'defaultUicontrolBackgroundColor'))
                        userSelects0or1(subjectGUIHandles)
                end

            case pb2						% button 2 (bottom right)
                switch experiment.threshEstMethod
                    case {'2I2AFC++', '2I2AFC+++'}
                        userSelects2 (subjectGUIHandles)
                    otherwise
                        set(subjectGUIHandles.pushbutton2,...
                            'backgroundcolor','r')
                        pause(.1)
                        set(subjectGUIHandles.pushbutton2,...
                            'backgroundcolor',get(0,...
                            'defaultUicontrolBackgroundColor'))
                        userSelects2 (subjectGUIHandles)
                end

            case pbRepeat                   % extreme right button
                switch experiment.threshEstMethod
                    case {'2I2AFC++', '2I2AFC+++'}
                        disp(' ignored button press')
                    otherwise

                        set(subjectGUIHandles.pushbuttoNotSure,...
                            'backgroundcolor','r')
                        pause(.1)
                        set(subjectGUIHandles.pushbuttoNotSure,...
                            'backgroundcolor',get(0,...
                            'defaultUicontrolBackgroundColor'))
                        userSelectsPleaseRepeat (subjectGUIHandles)
                end

            case {pbWrong, pbBlank}
                disp(' ignored button press')

            otherwise						% unrecognised button
                disp('ignored button press')
        end									% end (button press number)
    otherwise
        disp('ignored button press')
end											% experiment status

% button box remains 'busy' until after the stimulus has been presented
experiment.buttonBoxStatus='not busy';

% -------------------------------------------------- pushbuttonGO_Callback
function pushbuttonGO_Callback(hObject, eventdata, handles)
% This is a mouse click path
% GO function is also called directly from button box
%  and from MAP model and stats model

set(handles.pushbuttonGO,'visible','off')
startNewRun(handles)

% ---------------------------------------------------pushbutton0_Callback
function pushbutton0_Callback(hObject, eventdata, handles)
global experiment
% This is a mouse click path

% ignore 0 button if 2I2AFC used
if findstr(experiment.threshEstMethod,'2I2AFC')
    return  % to quiescent state
end

% userDoesNotHearTarget(handles)		% only possible interpretation
userDecides(handles, false)

% -------------------------------------------------- pushbutton1_Callback
function pushbutton1_Callback(hObject, eventdata, handles)
userSelects0or1(handles)				% also called from buttonBox

% ---------------------------------------------------pushbutton2_Callback
function pushbutton2_Callback(hObject, eventdata, handles)
userSelects2(handles)					% also called from buttonBox

% --------------------------------------------- pushbuttoNotSure_Callback
function pushbuttoNotSure_Callback(hObject, eventdata, handles)
userSelectsPleaseRepeat(handles)		% also called from buttonBox

% -------------------------------------------------- pushbutton3_Callback
function pushbutton3_Callback(hObject, eventdata, handles)

% ------------------------------------------------- pushbutton19_Callback
function pushbutton19_Callback(hObject, eventdata, handles)
% should be invisible (ignore)

% --------------------------------------- pushbuttonWrongButton_Callback
function pushbuttonWrongButton_Callback(hObject, eventdata, handles)
userSelectsWrongButton(handles)

% --------------------------------------- editdigitInput_Callback
function editdigitInput_Callback(hObject, eventdata, handles)
userSelects0or1(handles)				% after digit string input



% ----------------------------------------------------- userSelects0or1
function userSelects0or1(handles)
global experiment withinRuns

switch experiment.threshEstMethod
    case {'2I2AFC++', '2I2AFC+++'}
        switch withinRuns.stimulusOrder
            case 'targetFirst';
                %                 userHearsTarget(handles)
                userDecides(handles, true)
            otherwise
                %                 userDoesNotHearTarget(handles)
                userDecides(handles, false)
        end
    otherwise
        % single interval
        % 0 or 1 are treated as equivalent (i.e. target is not heard)
        userDecides(handles, false)
end
% return to pushButton1 callback

% ----------------------------------------------------- userSelects2
function userSelects2(handles)
global experiment withinRuns
switch experiment.threshEstMethod
    case {'2I2AFC++', '2I2AFC+++'}
        switch withinRuns.stimulusOrder
            case 'targetSecond';
                %                 userDoesNotHearTarget(handles)
                userDecides(handles, true)
            otherwise
                %                 userHearsTarget(handles)
                userDecides(handles, false)
        end
    otherwise
        % single interval (2 targets heard)
        userDecides(handles, true)
end
% return to pushButton2 callback

% ----------------------------------------------------- ---- userDecides
function userDecides(handles, saidYes)
global experiment stimulusParameters betweenRuns withinRuns
global rareEvent logistic psy levelsBinVector errormsg

if experiment.singleShot
    return  % not clear why this should be here
end

% ignore click if not 'waitingForResponse'
if ~strcmp(experiment.status,'waitingForResponse')
    disp('ignored click')
    return % to userSelects
end

% speech reception threshold
if strcmp(stimulusParameters.targetType,'digitStrings')
    % read triple digits from userGUI
    digitsInput=get(handles.editdigitInput,'string');
    % must be three digits
    if ~(length(digitsInput)==3)
        addToMsg(['error message: Wrong no of digits'], 0, 1)
        set(handles.textMSG,'string', 'Wrong no of digits', ...
            'BackgroundColor','r', 'ForegroundColor', 'w')
        set(handles.editdigitInput,'string','')
        return
    end
    % obtain correct answer from file name
    x=stimulusParameters.digitString;
    idx=find(x=='O'); x(idx)='0'; % replace 'oh' with zero
    disp([x '   ' digitsInput])
    if x==digitsInput
        saidYes=1;  % i.e. correct response
    else
        saidYes=0;  % i.e  wrong response
    end
    set(handles.editdigitInput,'string','')
    set(handles.editdigitInput,'visible','off')
    pause(0.1)
end

% no button presses accepted while processing
experiment.status='processingResponse';

% catch trials. Restart trial if caught
if withinRuns.catchTrial
    if saidYes
        disp('catch trial - caught out')
        withinRuns.caughtOut=withinRuns.caughtOut+1;

        % special: estimate caught out rate by allowing the trial
        %  to continue after catch
        if stimulusParameters.catchTrialBaseRate==0.5
            %  To use this facility, set the catchTrialRate and the
            %   catchTrialBaseRate both to 0.5
            %    update false positive rate
            betweenRuns.caughtOut(betweenRuns.runNumber)=...
                withinRuns.caughtOut;
            plotProgressThisTrial(handles)
            nextStimulus(handles);
            return
        end

        % Punishment: caught out restarts the trial
        set(handles.frame1,'backgroundColor','r')
        set(handles.pushbuttonGO, ...
            'visible','on', 'backgroundcolor','y') % and go again
        msg=[{'Start again: catch trial error'}, {' '},...
            {'Please,click on the GO button'}];
        set(handles.textMSG,'string',msg)
        [y,fs]=wavread('ding.wav');
        if ispc
            wavplay(y/100,fs)
        else
            sound(y/100,fs)
        end
            

        % raise catch trial rate temporarily.
        %  this is normally reduced on each new trial (see GO)
        stimulusParameters.catchTrialRate=...
            stimulusParameters.catchTrialRate+0.1;
        if stimulusParameters.catchTrialRate>0.5
            stimulusParameters.catchTrialRate=0.5;
        end
        fprintf('stimulusParameters.catchTrialRate= %6.3f\n', ...
            stimulusParameters.catchTrialRate)

        betweenRuns.caughtOut(betweenRuns.runNumber)=...
            1+betweenRuns.caughtOut(betweenRuns.runNumber);
        betweenRuns.runNumber=betweenRuns.runNumber-1;
        experiment.status='waitingForGO';
        return % unwind and wait for button press
    else % (said No)
        % user claims not to have heard target.
        % This is good as it was not present.
        % So, repeat the stimulus (possibly with target)
        %  and behave as if the last trial did not occur
        errormsg=nextStimulus(handles);

        % terminate if there is any kind of problem
        if ~isempty(errormsg)
            % e.g. limits exceeded, clipping
            disp(['Error nextStimulus: ' errormsg])
            runCompleted(handles)
            return
        end
        return      % no further action - next trial
    end
end     % of catch trial

% Real target: analyse the response, make tracks and define next stim.

% Define response and update response list
if saidYes
    % target was heard, so response=1;
    withinRuns.responseList=[withinRuns.responseList 1];	% 'heard it!'
else
    % target was not hear heard, so response=0;
    withinRuns.responseList=[withinRuns.responseList 0];
end
withinRuns.levelList=[withinRuns.levelList withinRuns.variableValue];
trialNumber=length(withinRuns.responseList);

% keep track of peaks and troughs;
% identify direction of change during initial period
if saidYes
    % default step size before first reversal
    WRVinitialStep=-stimulusParameters.WRVinitialStep;
    WRVsmallStep=-stimulusParameters.WRVsmallStep;
    % if the previous direction was 'less difficult', this must be a peak
    if strcmp(withinRuns.direction,'less difficult') ...
            && length(withinRuns.levelList)>1
        withinRuns.peaks=[withinRuns.peaks withinRuns.variableValue];
    end
    withinRuns.direction='more difficult';
else
    % said 'no'
    % default step size before first reversal
    WRVinitialStep=stimulusParameters.WRVinitialStep;
    WRVsmallStep=stimulusParameters.WRVsmallStep;

    % if the previous direction was 'up', this must be a peak
    if strcmp(withinRuns.direction,'more difficult') ...
            && length(withinRuns.levelList)>1
        withinRuns.troughs=[withinRuns.troughs withinRuns.variableValue];
    end
    withinRuns.direction='less difficult';
end

% phase 2 is all the levels after and incuding the first reversal
%  plus the level before that
% Look for the end of phase 1
if ~withinRuns.nowInPhase2 && length(withinRuns.peaks)+ ...
        length(withinRuns.troughs)>0
    % define phase 2
    withinRuns.beginningOfPhase2=trialNumber-1;
    withinRuns.nowInPhase2=1;
    WRVsmallStep=WRVinitialStep/2;
end

if withinRuns.nowInPhase2
    % keep a record of all levels and responses in phase 2 only
    withinRuns.levelsPhaseTwo=...
        withinRuns.levelList(withinRuns.beginningOfPhase2:end);
    withinRuns.responsesPhaseTwo=...
        withinRuns.responseList(withinRuns.beginningOfPhase2:end);
else
    withinRuns.levelsPhaseTwo=[];
end

% get (or substitute) threshold estimate
switch experiment.threshEstMethod
    case {'2I2AFC++', '2I2AFC+++'}
        % for plotting psychometric function only
        if withinRuns.beginningOfPhase2>0
            [psy, levelsBinVector, logistic, rareEvent]= ...
                bestFitPsychometicFunctions...
                (withinRuns.levelsPhaseTwo,  withinRuns.responsesPhaseTwo);
        end

        if ~isempty(withinRuns.peaks) && ~isempty(withinRuns.troughs)
            thresholdEstimate= ...
                mean([mean(withinRuns.peaks) mean(withinRuns.troughs)]);
        else
            thresholdEstimate=NaN;
        end

    otherwise
        % single interval methods
        try
            % using the s trial after the first reversal
            [psy, levelsBinVector, logistic, rareEvent]= ...
                bestFitPsychometicFunctions(withinRuns.levelsPhaseTwo,...
                withinRuns.responsesPhaseTwo);
        catch
            logistic.bestThreshold=NaN;
        end
end

if withinRuns.nowInPhase2
    % save tracks of threshold estimates for plotting andprinting
    switch experiment.functionEstMethod
        case {'logisticLS', 'logisticML'}
            if withinRuns.nowInPhase2
                withinRuns.meanEstTrack=...
                    [withinRuns.meanEstTrack ...
                    mean(withinRuns.levelsPhaseTwo)];
                withinRuns.thresholdEstimateTrack=...
                    [withinRuns.thresholdEstimateTrack ...
                    logistic.bestThreshold];
            end
        case 'rareEvent'
            withinRuns.meanEstTrack=...
                [withinRuns.meanEstTrack rareEvent.thresholddB];
            withinRuns.thresholdEstimateTrack=...
                [withinRuns.thresholdEstimateTrack logistic.bestThreshold];
        case 'peaksAndTroughs'
            withinRuns.meanEstTrack=...
                [withinRuns.meanEstTrack thresholdEstimate];
            withinRuns.thresholdEstimateTrack=...
                [withinRuns.thresholdEstimateTrack thresholdEstimate];
    end
end

% special discomfort condition
% run is completed when subject hits '2' button
switch experiment.paradigm
    case 'discomfort'
        if saidYes
            runCompleted(handles)
            return
        end
end

% choose the next level for the stimulus
switch experiment.threshEstMethod
    case {'2I2AFC++', '2I2AFC+++'}
        if saidYes
            [WRVinitialStep, msg]=Levitt2('hit', withinRuns.variableValue);
        else
            [WRVinitialStep, msg]=Levitt2('miss',withinRuns.variableValue);
        end

        % empty message means continue as normal
        if ~isempty(msg)
            runCompleted(handles)
            return
        end
        newWRVvalue=withinRuns.variableValue-WRVinitialStep;

    case {'MaxLikelihood', 'oneIntervalUpDown'}
        % run completed by virtue of number of trials
        % or restart because listener is in trouble
        if length(withinRuns.levelsPhaseTwo)== experiment.maxTrials
            % Use bonomial test to decide if there is an imbalance in the
            % number of 'yes'es and 'no's
            yesCount=sum(withinRuns.responseList);
            noCount=length(withinRuns.responseList)-yesCount;
            z=abs(yesCount-noCount)/(yesCount+noCount)^0.5;
            if z>1.96
                betweenRuns.resets=betweenRuns.resets+1;
                disp([ 'reset / z= ' num2str( z)  ...
                    '   Nresets= ' num2str( betweenRuns.resets) ] )
                withinRuns.peaks=[];
                withinRuns.troughs=[];
                withinRuns.levelList=withinRuns.levelList(end);
                withinRuns.meanEstTrack=withinRuns.meanEstTrack(end);
                withinRuns.forceThreshold=NaN;
                withinRuns.responseList=withinRuns.responseList(end);
                withinRuns.beginningOfPhase2=0;
                withinRuns.nowInPhase2=0;
                withinRuns.thresholdEstimateTrack=...
                    withinRuns.thresholdEstimateTrack(end);
            else
                runCompleted(handles)
                return
            end
        end

        % set new value for WRV
        if withinRuns.nowInPhase2
            % phase 2
            currentMeanEst=withinRuns.thresholdEstimateTrack(end);
            switch experiment.threshEstMethod
                case 'MaxLikelihood'
                    newWRVvalue=currentMeanEst;
                case {'oneIntervalUpDown'}
                    newWRVvalue=withinRuns.variableValue+WRVsmallStep;
            end
        else
            % phase 1
            if withinRuns.variableValue+2*WRVinitialStep>...
                    stimulusParameters.WRVlimits(2)
                % use smaller steps when close to maximum
                WRVinitialStep=WRVinitialStep/2;
            end
            newWRVvalue=withinRuns.variableValue+WRVinitialStep;
        end
    otherwise
        error(  'assessment method not recognised')
end

switch experiment.paradigm
    % prevent unrealistic gap durations 'gapDetection' tasks.
    % Note that the gap begins when the ramp ends not when stimulus ends
    case 'gapDetection'
        if newWRVvalue<-2*stimulusParameters.rampDuration
            newWRVvalue=-2*stimulusParameters.rampDuration;
            addToMsg('gap duration fixed at - 2 * ramp!',1, 1)
        end
end

withinRuns.variableValue=newWRVvalue;
withinRuns.trialNumber=withinRuns.trialNumber+1;

% Trial continues
plotProgressThisTrial(handles)

% next stimulus and so the cycle continues
errormsg=nextStimulus(handles);
% after the stimulus is presented, control returns here and the system
% waits for user action.

% terminate if there is any kind of problem
if ~isempty(errormsg)
    % e.g. limits exceeded, clipping
    disp(['Error nextStimulus: ' errormsg])
    runCompleted(handles)
    return
end

% ------------------------------------------------ userSelectsPleaseRepeat
function userSelectsPleaseRepeat(handles)
global experiment withinRuns
% ignore click if not 'waitingForResponse'
if ~strcmp(experiment.status,'waitingForResponse')
    disp('ignored click')
    return
end
% Take no action other than to make a
%  tally of repeat requests
experiment.pleaseRepeat=experiment.pleaseRepeat+1;
withinRuns.thisIsRepeatTrial=1;
nextStimulus(handles);

% ------------------------------------------------ userSelectsWrongButton
function userSelectsWrongButton(handles)
global withinRuns experiment
% restart is the simplest solution for a 'wrong button' request
withinRuns.wrongButton=withinRuns.wrongButton+1;
set(handles.pushbuttonGO, 'visible','on', 'backgroundcolor','y')
msg=[{'Start again: wrong button pressed'}, {' '},...
    {'Please,click on the GO button'}];
set(handles.textMSG,'string',msg)
experiment.status='waitingForGO';

% ------------------------------------------------- plotProgressThisTrial
function plotProgressThisTrial(handles)
% updates GUI: used for all responses

global experiment stimulusParameters betweenRuns withinRuns expGUIhandles
global  psy levelsBinVector binFrequencies rareEvent logistic statsModel

% plot the levelTrack and the threshold track

% Panel 2
% plot the levelList
axes(expGUIhandles.axes2); cla
plot( withinRuns.levelList,'o','markerFaceColor','k'), hold on
% plot the best threshold estimate tracks
if length(withinRuns.meanEstTrack)>=1
    % The length of the levelList is 2 greater than number of thresholds
    ptr=withinRuns.beginningOfPhase2+1;
    plot(ptr: ptr+length(withinRuns.meanEstTrack)-1, ...
        withinRuns.meanEstTrack, 'r')
    plot( ptr: ptr+length(withinRuns.thresholdEstimateTrack)-1, ...
        withinRuns.thresholdEstimateTrack, 'g')
    hold off
    estThresh=withinRuns.thresholdEstimateTrack(end);
    switch experiment.threshEstMethod
        % add appropriate labels to subject GUI buttons
        case {'2I2AFC++', '2I2AFC+++'}
            title([stimulusParameters.WRVname ' = ' ...
                num2str(withinRuns.variableValue, '%5.1f')])
        otherwise
            title([stimulusParameters.WRVname ' = ' ...
                num2str(withinRuns.variableValue, '%5.1f') ...
                ';    TH= ' num2str(estThresh, '%5.1f') ' dB'])
    end
end
xlim([0 experiment.maxTrials+withinRuns.beginningOfPhase2]);
ylim(stimulusParameters.WRVlimits)
ylabel ('dB SPL')
grid on

% Panel 4: Summary of threshold estimates (not used here)
% Estimates from previous runs are set in 'runCompleted'
% It is only necessary to change title showing runs/trials remaining
axes(expGUIhandles.axes4)
runsToGo=length(betweenRuns.var1Sequence)-betweenRuns.runNumber;
if withinRuns.beginningOfPhase2>0
    trialsToGo= experiment.singleIntervalMaxTrials(1) ...
        + withinRuns.beginningOfPhase2- withinRuns.trialNumber;
    title(['trials remaining = ' num2str(trialsToGo) ...
        ':    runs to go= ' num2str(runsToGo)])
end

% plot psychometric function   - panel 5
axes(expGUIhandles.axes5), cla
plot(withinRuns.levelList, withinRuns.responseList,'b.'), hold on
ylim([0 1])
title('')

switch experiment.threshEstMethod
    case {'MaxLikelihood', 'oneIntervalUpDown'}
        if withinRuns.beginningOfPhase2>0
            % display only when in phase 2.
            withinRuns.levelsPhaseTwo=...
                withinRuns.levelList(withinRuns.beginningOfPhase2:end);
            withinRuns.responsesPhaseTwo=...
                withinRuns.responseList(withinRuns.beginningOfPhase2:end);

            % organise data as psychometric function
            [psy, levelsBinVector, binFrequencies]= ...
                psychometricFunction(withinRuns.levelsPhaseTwo,...
                withinRuns.responsesPhaseTwo, experiment.psyBinWidth);

            % Plot the function
            %   point by point with circles of appropiate weighted size
            hold on,
            for i=1:length(psy)
                plot(levelsBinVector(i), psy(i), 'ro', ...
                    'markersize', 50*binFrequencies(i)/sum(binFrequencies))
            end
            % save info for later
            betweenRuns.psychometicFunction{betweenRuns.runNumber}=...
                [levelsBinVector; psy];

            % fitPsychometric functions is  computed in 'userDecides'
            % plot(rareEvent.predictionLevels, rareEvent.predictionsRE,'k')
            plot(logistic.predictionLevels, logistic.predictionsLOG, 'r')
            plot(rareEvent.predictionLevels, rareEvent.predictionsRE, 'k')
            if ~isnan(logistic.bestThreshold )
                xlim([ 0 100 ])
                title(['k= ' num2str(logistic.bestK, '%6.2f') ' g= '...
                    num2str(rareEvent.bestGain,'%6.3f') '  A=' ...
                    num2str(rareEvent.bestVMin,'%8.1f')])
            else
                title(' ')
            end

            switch experiment.ear
                %plot green line for statsModel a priori model
                case 'statsModelLogistic'
                    % plot proTem logistic (green) used by stats model
                    p= 1./(1+exp(-statsModel.logisticSlope...
                        *(levelsBinVector-logistic.bestThreshold)));
                    if experiment.psyFunSlope<0, p=1-p;end
                    titleText=[ ',  statsModel: logistic'];
                    hold on,    plot(levelsBinVector, p,'g')
                case  'statsModelRareEvent'
                    pressure=28*10.^(levelsBinVector/20);
                    p=1-exp(-stimulusParameters.targetDuration...
                        *(statsModel.rareEvenGain...
                        * pressure-statsModel.rareEventVmin));
                    p(p<0)=0;
                    if experiment.psyFunSlope<0, p=1-p;end
                    hold on,    plot(levelsBinVector, p,'g')
            end %(estMethod)
        end

    otherwise           % 2A2IFC
        message3= ...
            ([ 'peaks='  num2str(withinRuns.peaks) ...
            'troughs='  num2str(withinRuns.troughs)]);
        ylimRM([-0.1 1.1])	% 0=no / 1=yes
        set(gca,'ytick',[0 1], 'yTickLabel', {'no';'yes'})
        ylabel('psychometric function'), xlabel('target level')
        if length(levelsBinVector)>1
            xlim([ min(levelsBinVector) max(levelsBinVector)])
            xlim([ 0 100])
        end
end

% command window summary
% Accumulate things to say in the message window
message1= (['responses:      ' num2str(withinRuns.responseList,'%9.0f')]);
switch experiment.paradigm
    % more decimal places needed on GUI
    case { 'gapDetection', 'frequencyDiscrimination', 'forwardMaskingD'}
        message2= ([stimulusParameters.WRVname  ...
            ':       ' num2str(withinRuns.levelList,'%7.3f')]);
        message3= (['Thresh (logistic mean):   ' ...
            num2str(withinRuns.thresholdEstimateTrack,'%7.3f')]);
    otherwise
        message2= ([stimulusParameters.WRVname ':      ' ...
            num2str(withinRuns.levelList,'%7.1f')]);
        message3= (['Thresh (logistic mean):   ' ...
            num2str(withinRuns.thresholdEstimateTrack,'%7.1f')]);
end

addToMsg(str2mat(message1, message2, message3), 0)

% -----------------------------------------------------runCompleted
function runCompleted(handles)
% Used at the end of each run
global experiment stimulusParameters betweenRuns withinRuns
global rareEvent expGUIhandles
% disp('run completed')

experiment.status='runCompleted';
% quick update after final trial just to make sure
plotProgressThisTrial(handles)

switch experiment.ear
    case {'statsModelLogistic', 'statsModelRareEvent','MAPmodel', ...
            'MAPmodelMultiCh','MAPmodelSingleCh', 'MAPmodelListen'}
        % no changes required if model used
    otherwise
        set(handles.frame1,'visible','off')
        set(handles.pushbuttoNotSure,'visible','off')
        set(handles.pushbuttonWrongButton,'visible','off')
        set(handles.pushbutton3,'visible','off')
        set(handles.pushbutton2,'visible','off')
        set(handles.pushbutton1,'visible','off')
        set(handles.pushbutton0,'visible','off')
        set(handles.pushbuttonGO,'visible','off')
end

if isnan(withinRuns.forceThreshold)
    % the experiment has been aborted for some reason
    threshold=withinRuns.forceThreshold;
    stdev=NaN;
    logistic.bestK=NaN;
    logistic.bestThreshold=NaN;
    medianThreshold=NaN;
    meanThreshold=NaN;
else
    % use only phase 2 levels and responses for calculating thresholds
    withinRuns.levelsPhaseTwo=...
        withinRuns.levelList(withinRuns.beginningOfPhase2:end);
    withinRuns.responsesPhaseTwo=...
        withinRuns.responseList(withinRuns.beginningOfPhase2:end);
    [psy, levelsPhaseTwoBinVector, logistic, rareEvent]= ...
        bestFitPsychometicFunctions...
        (withinRuns.levelsPhaseTwo, withinRuns.responsesPhaseTwo);

    % plot final psychometric function
    axes(expGUIhandles.axes5),cla
    hold on, plot(rareEvent.predictionLevels, rareEvent.predictionsRE, 'k')
    hold on, plot(logistic.predictionLevels, logistic.predictionsLOG, 'r')
    % organise data as psychometric function
    [psy, levelsBinVector, binFrequencies]= ...
        psychometricFunction(withinRuns.levelsPhaseTwo,...
        withinRuns.responsesPhaseTwo, experiment.psyBinWidth);
    %   point by point with circles of appropiate weighted size
    hold on,
    for i=1:length(psy)
        plot(levelsBinVector(i), psy(i), 'ro', ...
            'markersize', 50*binFrequencies(i)/sum(binFrequencies))
    end

    % experimental
    medianThreshold=median(withinRuns.levelsPhaseTwo);
    warning off
    meanThreshold=mean(withinRuns.levelsPhaseTwo);

    % identify the current threshold estimate
    switch experiment.paradigm
        case 'discomfort'
            % most recent value (not truely a mean value)
            threshold=withinRuns.levelList(end);
            stdev=NaN;
        otherwise
            switch experiment.threshEstMethod
                case {'MaxLikelihood', 'oneIntervalUpDown'}
                    % last value in the list
                    %                     threshold=withinRuns.meanEstTrack(end);
                    threshold=withinRuns.thresholdEstimateTrack(end);
                    stdev=NaN;

                case {'2I2AFC++', '2I2AFC+++'}
                    % use peaks and troughs
                    try		% there may not be enough values to use
                        peaksUsed=experiment.peaksUsed;
                        threshold=...
                            mean(...
                            [withinRuns.peaks(end-peaksUsed+1:end) ...
                            withinRuns.troughs(end-peaksUsed+1:end)]);
                        stdev=...
                            std([withinRuns.peaks(end-peaksUsed +1:end) ...
                            withinRuns.troughs(end-peaksUsed:end)]);
                    catch
                        threshold=NaN;
                        stdev=NaN;
                    end
            end
    end
end

% Store thresholds
betweenRuns.thresholds=[betweenRuns.thresholds threshold];
betweenRuns.thresholds_mean=[betweenRuns.thresholds_mean meanThreshold];
betweenRuns.thresholds_median=...
    [betweenRuns.thresholds_median medianThreshold];
betweenRuns.forceThresholds=...
    [betweenRuns.forceThresholds withinRuns.forceThreshold];

% count observations after the startup phase for record keeping
betweenRuns.observationCount=...
    [betweenRuns.observationCount length(withinRuns.levelList)];
betweenRuns.timesOfFirstReversals=...
    [betweenRuns.timesOfFirstReversals withinRuns.beginningOfPhase2];
betweenRuns.catchTrials=...
    [betweenRuns.catchTrials withinRuns.catchTrialCount];

% add variable length tracks to cell arrays
if withinRuns.beginningOfPhase2>0
    betweenRuns.bestThresholdTracks{length(betweenRuns.thresholds)}=...
        withinRuns.thresholdEstimateTrack;
    betweenRuns.levelTracks{length(betweenRuns.thresholds)}=...
        withinRuns.levelList(withinRuns.beginningOfPhase2:end);
    betweenRuns.responseTracks{length(betweenRuns.thresholds)}=...
        withinRuns.responseList(withinRuns.beginningOfPhase2:end);
else
    betweenRuns.bestThresholdTracks{length(betweenRuns.thresholds)}=[];
    betweenRuns.levelTracks{length(betweenRuns.thresholds)}=[];
    betweenRuns.responseTracks{length(betweenRuns.thresholds)}=[];
end

betweenRuns.bestGain=[betweenRuns.bestGain rareEvent.bestGain];
betweenRuns.bestVMin=[betweenRuns.bestVMin rareEvent.bestVMin];
betweenRuns.bestPaMin=[betweenRuns.bestPaMin rareEvent.bestPaMindB];
betweenRuns.bestLogisticM=...
    [betweenRuns.bestLogisticM logistic.bestThreshold];
betweenRuns.bestLogisticK=[betweenRuns.bestLogisticK logistic.bestK];

resultsSoFar=[betweenRuns.var1Sequence(betweenRuns.runNumber)'...
    betweenRuns.var2Sequence(betweenRuns.runNumber)'...
    betweenRuns.thresholds(betweenRuns.runNumber)'
    ];

fprintf('%10.3f \t%10.3f \t%10.1f  \n', resultsSoFar')

switch experiment.ear
    case {'left', 'right', 'diotic', 'dichoticLeft','dichoticRight'}
        disp(['caught out= ' num2str(betweenRuns.caughtOut)])
end

% plot history of thresholds in panel 4
axes(expGUIhandles.axes4), cla
plotColors='rgbmckywrgbmckyw';
for i=1:length(betweenRuns.thresholds)
    faceColor=plotColors(floor(i/length(betweenRuns.variableList1)-.01)+1);
    switch betweenRuns.variableName1
        case {'targetFrequency', 'maskerRelativeFrequency'}
            if min(betweenRuns.var1Sequence)>0
                semilogx(betweenRuns.var1Sequence(i), ...
                    betweenRuns.thresholds(i),  'o', ...
                    'markerSize', 5,'markerFaceColor',faceColor)
            else
                plot(betweenRuns.var1Sequence(1:betweenRuns.runNumber),  ...
                    betweenRuns.thresholds,  'o', ...
                    'markerSize', 5,'markerFaceColor',faceColor)
                plot(betweenRuns.var1Sequence(i),  ...
                    betweenRuns.thresholds(i),  'o', ...
                    'markerSize', 5,'markerFaceColor',faceColor)
            end
        otherwise
            plot(betweenRuns.var1Sequence(i),  ...
                betweenRuns.thresholds(i),  'o', 'markerSize', 5,...
                'markerFaceColor',faceColor)
    end
    hold on
end
xlimRM([ min(betweenRuns.variableList1) max(betweenRuns.variableList1) ])
ylim(stimulusParameters.WRVlimits)
ylabel('thresholds (dB)')
xlabel(betweenRuns.variableName1)
set(gca,'ytick', [0 20 40 60 80 100])
try
    % problems if only one x value
    set(gca,'XTick', sort(betweenRuns.variableList1))
catch
end
grid on, set(gca,'XMinorGrid', 'off')

% final run?
if betweenRuns.runNumber==length(betweenRuns.var1Sequence)
    % yes, end of experiment
    fileName=['savedData/' experiment.name experiment.date ...
        experiment.paradigm];
    % 	save (fileName, 'experiment', 'stimulusParameters', 'betweenRuns', 'withinRuns', 'variableNames', 'paradigmNames', 'LevittControl')
    disp('Experiment completed')

    % update subject GUI to acknowledge end of run
    subjGUImsg=[{'Experiment completed'}, {' '}, {'Thank you!'}];
    set(handles.textMSG,'string', subjGUImsg   )
    % play 'Tada'
    [y,fs,nbits]=wavread('TADA.wav');
    musicGain=10^(stimulusParameters.musicLeveldB/20);
    y=y*musicGain;
    if ispc, wavplay(y/100,fs, 'async'), else sound(y/100,fs, 'async'), end

    % update experimenter GUI
    addToMsg('Experiment completed.',1)

    set(expGUIhandles.pushbuttonSave,'visible','on')
    printReport
    experiment.status='endOfExperiment';
    return
else
    % No, hang on.
    switch experiment.ear
        case {'statsModelLogistic', 'statsModelRareEvent','MAPmodel', ...
                'MAPmodelMultiCh','MAPmodelSingleCh', 'MAPmodelListen'}
            % no changes required if model used
        otherwise
            % decrement catchTrialRate towards baseRate
            stimulusParameters.catchTrialRate=...
                stimulusParameters.catchTrialBaseRate + ...
                (stimulusParameters.catchTrialRate...
                -stimulusParameters.catchTrialBaseRate)...
                *(1-exp(-stimulusParameters.catchTrialTimeConstant));
            fprintf('stimulusParameters.catchTrialRate= %6.3f\n', ...
                stimulusParameters.catchTrialRate)

            % and go again
            set(handles.pushbuttonGO,'backgroundcolor','y')
            set(handles.frame1,'visible','off')
            set(handles.pushbuttonGO,'visible','on')
            msg=[{'Ready to start new trial'}, {' '},...
                {'Please,click on the GO button'}];
            set(handles.textMSG,'string',msg)
    end
    experiment.status='waitingForGO';
    %     fprintf('\n')

    [y,fs,nbits]=wavread('CHIMES.wav');
    musicGain=10^(stimulusParameters.musicLeveldB/20);
    y=y*musicGain;
    if ispc, wavplay(y/100,fs, 'async'), else sound(y/100,fs, 'async'), end

end

% -----------------------------------------------------MAPmodelRunsGUI
% The computer presses the buttons
function 	MAPmodelRunsGUI(handles)
global experiment stimulusParameters method expGUIhandles
global AN_IHCsynapseParams
method=[];

while strcmp(experiment.status,'waitingForGO')
    % no catch trials for MAP model
    experiment.allowCatchTrials=0;

    % initiates run and plays first stimulus and it returns
    %  without waiting for button press
    startNewRun(handles)

    % show sample Rate on GUI; it must be set in MAPparams ##??
    set(expGUIhandles.textsampleRate,'string',...
        num2str(stimulusParameters.sampleRate))

    if experiment.singleShot % ##??
        AN_IHCsynapseParams.plotSynapseContents=1;
    else
        AN_IHCsynapseParams.plotSynapseContents=0;
    end

    % continuous loop until the program stops itself
    while strcmp(experiment.status,'waitingForResponse')
        %  NB at this point the stimulus has been played
        pause(0.1)  % to allow interrupt with CTRL/C

        switch experiment.ear
            case { 'MAPmodelListen'}
                % flash the buttons to show model response
                set(handles.pushbutton1,'backgroundcolor','y','visible','on')
                set(handles.pushbutton2,'backgroundcolor','y','visible','on')
        end

        % Analayse the current stimulus using MAP
        [modelResponse earObject]= MAPmodel;

        if experiment.stop || experiment.singleShot
            % trap for single trial or user interrupt using 'stop' button.
            experiment.status= 'waitingForStart';
%             experiment.stop=0;
            errormsg='manually stopped';
            addToMsg(errormsg,1)
            return
        end

        switch modelResponse
            case 1
                %     userDoesNotHearTarget(handles)
                switch experiment.ear
                    case {'MAPmodelListen'}
                        % illuminate appropriate button
                        set(handles.pushbutton1,...
                            'backgroundcolor','r','visible','on')
                        set(handles.pushbutton2,'backgroundcolor','y')
                end
                userDecides(handles, false)
                if experiment.singleShot, return, end

            case 2
                %   userHearsTarget(handles)
                switch experiment.ear
                    case {'MAPmodelListen'}
                        % illuminate appropriate button (DEMO only)
                        set(handles.pushbutton2,'backgroundcolor',...
                            'r','visible','on')
                        set(handles.pushbutton1,'backgroundcolor','y')
                end

                switch experiment.paradigm
                    case 'discomfort'
                        % always treat discomfort as 'not heard'
                        userDecides(handles, false)
                    otherwise
                        userDecides(handles, true)
                end
            otherwise
                % probably an abort
                return
        end
    end
end

% -------------------------------------------------------MAPmodel
function [modelResponse, MacGregorResponse]=MAPmodel

global experiment stimulusParameters audio withinRuns
% global outerMiddleEarParams DRNLParams AN_IHCsynapseParams
global ICoutput dtSpikes dt savedBFlist ANprobRateOutput expGUIhandles
global paramChanges

savePath=path;
addpath(['..' filesep 'MAP'], ['..' filesep 'utilities'])
modelResponse=[];
MacGregorResponse=[];

% mono only (column vector)
audio=audio(:,1)';

% if stop button pressed earlier
if experiment.stop
    errormsg='manually stopped';
    addToMsg(errormsg,1)
    return
end

% ---------------------------------------------- run Model
MAPparamsName=experiment.name;
showPlotsAndDetails=experiment.MAPplot;

% important buried constant ##??
AN_spikesOrProbability='spikes';
% AN_spikesOrProbability='probability';

% [response, method]=MAPsequenceSeg(audio, method, 1:8);

if sum(strcmp(experiment.ear,{'MAPmodelMultiCh', 'MAPmodelListen'}))
    % use BFlist specified in MAPparams file
    BFlist= -1;
else
    BFlist=stimulusParameters.targetFrequency;
end
paramChanges=get(expGUIhandles.editparamChanges,'string');
% convert from string to a cell array
eval(paramChanges);

MAP1_14(audio, stimulusParameters.sampleRate, BFlist,...
    MAPparamsName, AN_spikesOrProbability, paramChanges);

if showPlotsAndDetails
    options.printModelParameters=0;
    options.showModelOutput=1;
    options.printFiringRates=1;
    options.showACF=0;
    options.showEfferent=0;
    options.surfProbability=0;
    showMapOptions.surfSpikes=0;
    UTIL_showMAP(options)
end

% No response,  probably caused by hitting 'stop' button
if strcmp(AN_spikesOrProbability,'spikes') && isempty(ICoutput)
    return
end
% ---------------------------------------------------------- end model run

if strcmp(AN_spikesOrProbability,'spikes')
    MacGregorResponse= sum(ICoutput,1);                 % use IC
    dt=dtSpikes;
    time=dt:dt:dt*length(MacGregorResponse);
else
    % for one channel, ANprobResponse=ANprobRateOutput
    % for multi channel take strongest in any epoch
    nChannels=length(savedBFlist);
    % use only HSR fibers
    ANprobRateOutput=ANprobRateOutput(end-nChannels+1:end,:);
    time=dt:dt:dt*length(ANprobRateOutput);
end

% group delay on unit response - these values are iffy
windowOnsetDelay= 0.004;
windowOffsetDelay= 0.022; % long ringing time

% now find the response of the MacGregor model during 
%  the target presentation + group delay
switch experiment.threshEstMethod
    case {'2I2AFC++', '2I2AFC+++'}
        idx= time>stimulusParameters.testTargetBegins+windowOnsetDelay ...
            & time<stimulusParameters.testTargetEnds+windowOffsetDelay;
        nSpikesTrueWindow=sum(MacGregorResponse(:,idx));
        idx=find(time>stimulusParameters.testNonTargetBegins+windowOnsetDelay ...
            & time<stimulusParameters.testNonTargetEnds+windowOffsetDelay);
        if strcmp(AN_spikesOrProbability,'spikes')
            nSpikesFalseWindow=sum(MacGregorResponse(:,idx));
        else
            nSpikesDuringTarget=mean(ANprobRateOutput(end,idx));
            % compare with spontaneous rate
            if nSpikesDuringTarget>ANprobRateOutput(end,1)+10
                nSpikesDuringTarget=1;  % i.e. at leastone MacG spike
            else
                nSpikesDuringTarget=0;
            end
        end
        % nSpikesDuringTarget is +ve when more spikes are found
        %   in the target window
        difference= nSpikesTrueWindow-nSpikesFalseWindow;

        if difference>0
            % hit
            nSpikesDuringTarget=experiment.MacGThreshold+1;
        elseif    difference<0
            % miss (wrong choice)
            nSpikesDuringTarget=experiment.MacGThreshold-1;
        else
            if rand>0.5
                % hit (random choice)
                nSpikesDuringTarget=experiment.MacGThreshold+1;
            else
                % miss (random choice)
                nSpikesDuringTarget=experiment.MacGThreshold-1;
            end
        end
        disp(['level target dummy decision: ' ...
            num2str([withinRuns.variableValue nSpikesTrueWindow ...
            nSpikesFalseWindow  nSpikesDuringTarget], '%4.0f') ] )
    otherwise
        
        % single interval up/down
        idx=find(time>stimulusParameters.testTargetBegins +windowOnsetDelay...
            & time<stimulusParameters.testTargetEnds+windowOffsetDelay);
        if strcmp(AN_spikesOrProbability,'spikes')
            nSpikesDuringTarget=sum(MacGregorResponse(:,idx));
            timeX=time(idx);
        else
            % probability, use channel with the highest average rate
            nSpikesDuringTarget=max(mean(ANprobRateOutput(:,idx),2));
            if nSpikesDuringTarget>ANprobRateOutput(end,1)+10
                nSpikesDuringTarget=1;
            else
                nSpikesDuringTarget=0;
            end

        end
end

if experiment.MAPplot
    % add vertical lines to indicate target region
    figure(99), subplot(6,1,6)
    hold on
    yL=get(gca,'YLim');
    plot([stimulusParameters.testTargetBegins + windowOnsetDelay ...
        stimulusParameters.testTargetBegins   + windowOnsetDelay],yL,'r')
    plot([stimulusParameters.testTargetEnds   + windowOffsetDelay ...
        stimulusParameters.testTargetEnds     + windowOffsetDelay],yL,'r')
end

% specify unambiguous response
if nSpikesDuringTarget>experiment.MacGThreshold
    modelResponse=2;    % stimulus detected
else
    modelResponse=1;    % nothing heard (default)
end

path(savePath)

% -----------------------------------------------------statsModelRunsGUI
% The computer presses the buttons
function 	statsModelRunsGUI(handles)
% Decision are made at random using a prescribe statistical function
% to set probabilities as a function of signal level.
global experiment

experiment.allowCatchTrials=0;

while strcmp(experiment.status,'waitingForGO')
    % i.e. waiting for new run
    if experiment.stop
        % user has requested an abort
        experiment.status= 'waitingForStart';
        addToMsg('manually stopped',1)
        return
    end

    % initiates run and plays first stimulus and it returns
    %  without waiting for button press
    % NB stimulus is not actually generated (for speed)
    startNewRun(handles)

    while strcmp(experiment.status,'waitingForResponse')
        % create artificial response here
        modelResponse=statsModelGetResponse;
        switch modelResponse
            case 1
                %                 userDoesNotHearTarget(handles)
                userDecides(handles, false)
            case 2
                %                 userHearsTarget(handles)
                userDecides(handles, true)
        end
    end
end

% -----------------------------------------------------statsModelGetResponse
function modelResponse=statsModelGetResponse(handles)
global experiment  withinRuns  statsModel stimulusParameters
% use the generating function to decide if a detection occurs or not

% pause(0.1)  % to allow stopping with CTRL/C but slows things down

% first compute the probability that a detection occurs
switch experiment.ear
    case {'statsModelLogistic'}
        prob= 1./(1+exp(-statsModel.logisticSlope.*(withinRuns.variableValue-statsModel.logisticMean)));
        %         if experiment.psyFunSlope<0,
        %             prob=1-prob;
        %         end

    case 'statsModelRareEvent'
        if experiment.psyFunSlope<0
            addToMsg('statsModelRareEvent cannot be used with negative slope',0)
            error('statsModelRareEvent cannot be used with negative slope')
        end

        % standard formula is prob = 1 – exp(-d (g P – A))
        % here A->A;  To find Pmin use A/gain
        pressure=28*10^(withinRuns.variableValue/20);
        gain=statsModel.rareEvenGain;
        A=statsModel.rareEventVmin;
        d=stimulusParameters.targetDuration;
        gP_Vmin=gain*pressure-A;
        if gP_Vmin>0
            prob=1-exp(-d*(gP_Vmin));
        else
            prob=0;
        end
end

% Use the probability to choose whether or not a detection has occurred
switch experiment.threshEstMethod
    case {'MaxLikelihood', 'oneIntervalUpDown'}
        if rand<prob
            modelResponse=2; %bingo
        else
            modelResponse=1; %nothing heard
        end

    case {'2I2AFC++', '2I2AFC+++'}
        if rand<prob
            modelResponse=2; %bingo
        else %if the stimulus is not audible, take a 50:50 chance of getting it right
            if rand<0.5
                modelResponse=2; %bingo
            else
                modelResponse=1; %nothing heard
            end
        end
end


% ------------------------------------------------------- printTabTable
function printTabTable(M, headers)
% printTabTable prints a matrix as a table with tabs
%headers are optional
%headers=strvcat('firstname', 'secondname')
%  printTabTable([1 2; 3 4],strvcat('a1','a2'));

if nargin>1
    [r c]=size(headers);
    for no=1:r
        fprintf('%s\t',headers(no,:))
    end
    fprintf('\n')
end

[r c]=size(M);

for row=1:r
    for col=1:c
        if row==1 && col==1 && M(1,1)==-1000
            %   Print nothing (tab follows below)
        else
            fprintf('%s',num2str(M(row,col)))
        end
        if col<c
            fprintf('\t')
        end
    end
    fprintf('\n')
end

% ------------------------------------------------------- xlimRM
function xlimRM(x)
try
    xlim([x(1) x(2)])
catch
end

% ------------------------------------------------------- ylimRM
function ylimRM(x)
try
    ylim([x(1) x(2)])
catch
end


function editdigitInput_CreateFcn(hObject, eventdata, handles)

% Hint: edit controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end


% -----------------------------------------------------buttonBoxIntitialize
function buttonBoxIntitialize
% initialize button box
global  serobj
try
    fclose(serobj);
catch
end

try
    % !!! button boxes in booths are connected to COM2. User beware of
    % connection port.
    serobj = serial('COM2') ;           	% Creating serial port object
    serobj.Baudrate = 9600;           		% Set the baud rate at the specific value
    set(serobj, 'Parity', 'none') ;     	% Set parity as none
    set(serobj, 'Databits', 8) ;          	% set the number of data bits
    set(serobj, 'StopBits', 1) ;         	% set number of stop bits as 1
    set(serobj, 'Terminator', 'CR') ; 		% set the terminator value to carriage return
    set(serobj, 'InputBufferSize', 512) ;  	% Buffer for read operation, default it is 512
    set(serobj,'timeout',10);           	% 10 sec timeout on button press
    set(serobj, 'ReadAsyncMode', 'continuous')
    set(serobj, 'BytesAvailableFcn', @buttonBox_callback)
    set(serobj, 'BytesAvailableFcnCount', 1)
    set(serobj, 'BytesAvailableFcnMode', 'byte')
    % set(serobj, 'BreakInterruptFcn', '@buttonBox_Calback')

    fopen(serobj);
    buttonBoxStatus=get(serobj,'status');
catch
    disp('** no button box found - use mouse **')
end