To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.
The primary repository for this project is hosted at git://github.com/rmeddis/MAP.git .
This repository is a read-only copy which is updated automatically every hour.
root / multithreshold 1.46 / nextStimulus.m @ 31:c54a34161e4a
History | View | Annotate | Download (35.7 KB)
| 1 |
function errormsg=nextStimulus(handles) |
|---|---|
| 2 |
% Handles everything concerned with the stimulus presentation |
| 3 |
% called from startNewRun in subjGUI |
| 4 |
|
| 5 |
global experiment stimulusParameters withinRuns |
| 6 |
experiment.status='presentingStimulus'; |
| 7 |
errormsg=''; |
| 8 |
|
| 9 |
% interrupt by 'stop' button |
| 10 |
% if experiment.stop |
| 11 |
% disp('******** experiment manually stopped *****************')
|
| 12 |
% experiment.status= 'waitingForStart'; |
| 13 |
% errormsg='manually stopped'; |
| 14 |
% addToMsg(errormsg,1) |
| 15 |
% return |
| 16 |
% end |
| 17 |
|
| 18 |
% -----------------------------------------choose catch trials at random |
| 19 |
% catch trials are for subject threshold measurements only |
| 20 |
% this is the only place where withinRuns.catchTrial is set |
| 21 |
if experiment.allowCatchTrials |
| 22 |
if withinRuns.trialNumber==1; |
| 23 |
% first trial is never a catch trial |
| 24 |
withinRuns.catchTrial=0; |
| 25 |
withinRuns.catchTrialCount=0; % reset count on first trial |
| 26 |
elseif withinRuns.trialNumber==2 ... |
| 27 |
&& withinRuns.catchTrialCount==0 |
| 28 |
% second trial is always a catch trial |
| 29 |
withinRuns.catchTrial=1; |
| 30 |
withinRuns.catchTrialCount=1; % this must be the first |
| 31 |
elseif withinRuns.thisIsRepeatTrial |
| 32 |
% for requested repeats do not change catch trial status |
| 33 |
withinRuns.thisIsRepeatTrial=0; % reset toggle |
| 34 |
else |
| 35 |
% choose whether or not to have a catch trial |
| 36 |
R=rand; |
| 37 |
if R<stimulusParameters.catchTrialRate |
| 38 |
% catch trial |
| 39 |
withinRuns.catchTrial=1; |
| 40 |
addToMsg('Catch Trial',1)
|
| 41 |
withinRuns.catchTrialCount=withinRuns.catchTrialCount+1; |
| 42 |
else |
| 43 |
% not a catch trial |
| 44 |
withinRuns.catchTrial=0; |
| 45 |
end |
| 46 |
end |
| 47 |
else |
| 48 |
% no catch trials for statistical evaluations or 2AIFC or (poss) MAP |
| 49 |
withinRuns.catchTrial=0; |
| 50 |
end |
| 51 |
|
| 52 |
%------------ during stimulus presentation show appropriate button images |
| 53 |
switch experiment.ear |
| 54 |
case {'statsModelLogistic', 'statsModelRareEvent',...
|
| 55 |
'MAPmodel', 'MAPmodelMultiCh', 'MAPmodelSingleCh'} |
| 56 |
% no buttons shown |
| 57 |
otherwise |
| 58 |
switch experiment.threshEstMethod |
| 59 |
case {'2I2AFC++', '2I2AFC+++'}
|
| 60 |
%Except for 2I2AFC |
| 61 |
% For 2I2AFC the buttons on the screen ab initio |
| 62 |
set(handles.frame1,'visible','off') |
| 63 |
set(handles.pushbuttonGO,'visible','off') |
| 64 |
set(handles.pushbuttoNotSure,'visible','off') |
| 65 |
set(handles.pushbuttonWrongButton,'visible','off') |
| 66 |
set(handles.pushbutton3,'visible','off') |
| 67 |
set(handles.pushbutton0,'visible','off') |
| 68 |
set(handles.pushbutton1,'visible','on') |
| 69 |
set(handles.pushbutton2,'visible','on') |
| 70 |
drawnow |
| 71 |
otherwise |
| 72 |
% i.e. single interval/ maxLikelihood |
| 73 |
set(handles.frame1,'backgroundColor','w') |
| 74 |
set(handles.frame1,'visible','off') |
| 75 |
set(handles.pushbuttoNotSure,'visible','off') |
| 76 |
set(handles.pushbuttonWrongButton,'visible','off') |
| 77 |
set(handles.pushbutton3,'visible','off') |
| 78 |
set(handles.pushbutton2,'visible','off') |
| 79 |
set(handles.pushbutton1,'visible','off') |
| 80 |
set(handles.pushbutton0,'visible','off') |
| 81 |
pause(.1) |
| 82 |
end |
| 83 |
end |
| 84 |
|
| 85 |
set(handles.textMSG,'BackgroundColor','w', 'ForegroundColor', 'b') |
| 86 |
|
| 87 |
% Now the serious business of crafting and presenting the stimulus |
| 88 |
errormsg= stimulusMakeAndPlay (handles); |
| 89 |
|
| 90 |
if ~isempty(errormsg) |
| 91 |
% e.g. clipping. subjGUI will service the error |
| 92 |
return |
| 93 |
end |
| 94 |
|
| 95 |
% after playing the stimulus, reset the subjectGUI |
| 96 |
switch experiment.ear |
| 97 |
case {'statsModelLogistic', 'statsModelRareEvent',...
|
| 98 |
'MAPmodel', 'MAPmodelMultiCh', 'MAPmodelSingleCh'} |
| 99 |
% no changes required if model used |
| 100 |
% NB these changes do occur is 'MAPmodelListen' is selected |
| 101 |
otherwise |
| 102 |
switch experiment.threshEstMethod |
| 103 |
case {'2I2AFC++', '2I2AFC+++'}
|
| 104 |
% buttons already visible |
| 105 |
otherwise |
| 106 |
% single interval now make buttons visible |
| 107 |
set(handles.frame1,'visible','on') |
| 108 |
set(handles.pushbuttoNotSure,'visible','on') |
| 109 |
% set(handles.pushbuttonWrongButton,'visible','on') |
| 110 |
set(handles.pushbutton0,'visible','on') |
| 111 |
set(handles.pushbutton1,'visible','on') |
| 112 |
set(handles.pushbutton2,'visible','on') |
| 113 |
set(handles.pushbutton3,'visible','on') |
| 114 |
end |
| 115 |
end |
| 116 |
|
| 117 |
switch experiment.paradigm |
| 118 |
case 'SRT' |
| 119 |
set(handles.frame1,'backgroundColor','w') |
| 120 |
set(handles.frame1,'visible','off') |
| 121 |
set(handles.pushbuttoNotSure,'visible','off') |
| 122 |
set(handles.pushbuttonWrongButton,'visible','off') |
| 123 |
set(handles.pushbutton3,'visible','off') |
| 124 |
set(handles.pushbutton2,'visible','off') |
| 125 |
set(handles.pushbutton1,'visible','off') |
| 126 |
set(handles.pushbutton0,'visible','off') |
| 127 |
set(handles.editdigitInput,'visible','on') |
| 128 |
set(handles.editdigitInput,'string',[]) |
| 129 |
pause(.2) |
| 130 |
uicontrol(handles.editdigitInput) |
| 131 |
|
| 132 |
otherwise |
| 133 |
set(handles.editdigitInput,'visible','off') |
| 134 |
end |
| 135 |
|
| 136 |
|
| 137 |
experiment.status='waitingForResponse'; |
| 138 |
% home again |
| 139 |
|
| 140 |
% ------------------------------------------------------------------------------------------stimulusMakeAndPlay |
| 141 |
function errormsg=stimulusMakeAndPlay (handles) |
| 142 |
global experiment stimulusParameters betweenRuns withinRuns expGUIhandles audio |
| 143 |
% creates the stimulus and plays it; there are two stimuli; cue and test |
| 144 |
% called from nextStimulus |
| 145 |
|
| 146 |
errormsg=''; |
| 147 |
|
| 148 |
% first post the subjects instructions on subjGUI |
| 149 |
set(handles.textMSG,'string', stimulusParameters.subjectText) |
| 150 |
|
| 151 |
% select the new levels of the between runs variables |
| 152 |
num=betweenRuns.runNumber; |
| 153 |
cmd=(['stimulusParameters.' betweenRuns.variableName1 '= ' ... |
| 154 |
num2str(betweenRuns.var1Sequence(num)) ';']); |
| 155 |
% e.g. stimulusParameters.targetFrequency= 1000; |
| 156 |
eval(cmd); |
| 157 |
|
| 158 |
cmd=(['stimulusParameters.' betweenRuns.variableName2 '= ' ... |
| 159 |
num2str(betweenRuns.var2Sequence(num)) ';']); |
| 160 |
% e.g. stimulusParameters.targetDuration= 0.1; |
| 161 |
eval(cmd); |
| 162 |
|
| 163 |
switch experiment.paradigm |
| 164 |
% target level may vary between runs |
| 165 |
case {'trainingIFMC', 'TMC','TMC_16ms', 'TMC - ELP', 'IFMC','IFMC_8ms','IFMC_16ms'}
|
| 166 |
idx=floor(num/length(betweenRuns.variableList1)-0.01)+1; |
| 167 |
cmd=(['stimulusParameters.targetLevel = ' ... |
| 168 |
num2str(stimulusParameters.targetLevels(idx)) ';']); |
| 169 |
eval(cmd); |
| 170 |
if withinRuns.trialNumber==1 |
| 171 |
disp(['targetLevel=' num2str(stimulusParameters.targetLevel)]) |
| 172 |
end |
| 173 |
end |
| 174 |
|
| 175 |
|
| 176 |
% for more readable code use shorter variable names; |
| 177 |
% NB these may change below; these are only the starting values |
| 178 |
|
| 179 |
targetType= stimulusParameters.targetType; |
| 180 |
targetDuration= stimulusParameters.targetDuration; |
| 181 |
targetLevel= stimulusParameters.targetLevel; |
| 182 |
targetFrequency= stimulusParameters.targetFrequency; |
| 183 |
|
| 184 |
maskerType= stimulusParameters.maskerType; |
| 185 |
maskerDuration= stimulusParameters.maskerDuration; |
| 186 |
maskerLevel= stimulusParameters.maskerLevel; |
| 187 |
maskerRelativeFrequency= stimulusParameters.maskerRelativeFrequency; |
| 188 |
maskerFrequency= maskerRelativeFrequency*targetFrequency; |
| 189 |
|
| 190 |
gapDuration= stimulusParameters.gapDuration; |
| 191 |
|
| 192 |
rampDuration= stimulusParameters.rampDuration; |
| 193 |
AFCsilenceDuration=stimulusParameters.AFCsilenceDuration; % 2I2AFC gap |
| 194 |
backgroundLevel= stimulusParameters.backgroundLevel; |
| 195 |
|
| 196 |
% Set level of within runs variable |
| 197 |
% this is the first change to one of the values shown above |
| 198 |
cmd=[stimulusParameters.WRVname '= withinRuns.variableValue;' ]; |
| 199 |
% e.g.: maskerLevel= withinRuns.variableValue; |
| 200 |
eval(cmd); |
| 201 |
|
| 202 |
% cue and test stimuli are identical except for a single difference |
| 203 |
% depending on the paradigm |
| 204 |
cueTestDifference= stimulusParameters.cueTestDifference; |
| 205 |
% cue characteristics before adding cue differences |
| 206 |
cueTargetLevel=targetLevel; |
| 207 |
cueMaskerFrequency=maskerFrequency; |
| 208 |
cueMaskerDuration=maskerDuration; |
| 209 |
cueMaskerLevel=maskerLevel; |
| 210 |
cueTargetFrequency=targetFrequency; |
| 211 |
cueGapDuration=gapDuration; |
| 212 |
|
| 213 |
% ----------------------------paradigm sensitive cue and masker settings |
| 214 |
% switch off unwanted components and base cue on target values |
| 215 |
% for catch trials switch off the target |
| 216 |
switch experiment.paradigm |
| 217 |
% OHIO is a temporary special arrangement |
| 218 |
case{'OHIOrand','OHIOspect','OHIOtemp','OHIOspectemp','OHIOabs'}
|
| 219 |
% these values must be set in MAPparamsOHIO |
| 220 |
targetFrequency=experiment.OHIOfrequencies; |
| 221 |
numOHIOtones=stimulusParameters.numOHIOtones; |
| 222 |
maskerType='OHIO'; |
| 223 |
targetType='OHIO'; |
| 224 |
|
| 225 |
% globalStimParams.beginSilences says when each tone begins |
| 226 |
% targetFrequency specifies the frequency of each tone |
| 227 |
% targetLevel is the level of each tone |
| 228 |
switch experiment.paradigm |
| 229 |
case 'OHIOabs' |
| 230 |
% only one tone |
| 231 |
targetFrequency=targetFrequency(numOHIOtones); |
| 232 |
globalStimParams.beginSilences=0.01; |
| 233 |
targetDuration=0.01; |
| 234 |
case 'OHIOrand' |
| 235 |
% select random frequencies from the list |
| 236 |
x=rand(12,1); [x idx]=sort(x); |
| 237 |
targetFrequency=targetFrequency(idx(1:numOHIOtones)); |
| 238 |
thresholds=experiment.OHIOthresholds; |
| 239 |
targetLevel=thresholds(idx(1:numOHIOtones))+targetLevel; |
| 240 |
globalStimParams.beginSilences=0.01:0.02:... |
| 241 |
0.01+0.02*(numOHIOtones-1); |
| 242 |
targetDuration=numOHIOtones*0.02; |
| 243 |
case 'OHIOspect' |
| 244 |
% only one tone with multiple frequencies |
| 245 |
targetFrequency=fliplr(targetFrequency); |
| 246 |
targetFrequency=targetFrequency(1:numOHIOtones); |
| 247 |
thresholds=experiment.OHIOthresholds; |
| 248 |
thresholds=fliplr(thresholds); |
| 249 |
targetLevel=thresholds(1:numOHIOtones)+targetLevel; |
| 250 |
globalStimParams.beginSilences=... |
| 251 |
repmat(0.01, 1, numOHIOtones); |
| 252 |
targetDuration=0.02; |
| 253 |
case 'OHIOspectemp' |
| 254 |
% only one tone with multiple frequencies |
| 255 |
targetFrequency=fliplr(targetFrequency); |
| 256 |
targetFrequency=targetFrequency(1:numOHIOtones); |
| 257 |
thresholds=experiment.OHIOthresholds; |
| 258 |
thresholds=fliplr(thresholds); |
| 259 |
targetLevel=thresholds(1:numOHIOtones)+targetLevel; |
| 260 |
globalStimParams.beginSilences=0.01:0.02:... |
| 261 |
0.01+0.02*(numOHIOtones-1); |
| 262 |
targetDuration=numOHIOtones*0.02; |
| 263 |
case 'OHIOtemp' |
| 264 |
% use only one tone repeatedly |
| 265 |
tonesToUse=10; |
| 266 |
targetFrequency=targetFrequency(tonesToUse); |
| 267 |
targetFrequency=repmat(targetFrequency,1,numOHIOtones); |
| 268 |
thresholds=experiment.OHIOthresholds; |
| 269 |
thresholds=thresholds(tonesToUse); |
| 270 |
targetLevel=repmat(thresholds,1,numOHIOtones)+targetLevel; |
| 271 |
globalStimParams.beginSilences=0.01:0.02:... |
| 272 |
0.01+0.02*(numOHIOtones-1); |
| 273 |
targetDuration=numOHIOtones*0.02; |
| 274 |
end |
| 275 |
% still in OHIO |
| 276 |
% Dummy values to make things work although no masker or cue used |
| 277 |
% target values have changed and this affects the cue values |
| 278 |
cueMaskerLevel=targetLevel; |
| 279 |
cueTargetLevel=targetLevel; |
| 280 |
cueTargetFrequency=targetFrequency; |
| 281 |
cueMaskerFrequency=targetFrequency; |
| 282 |
maskerFrequency=targetFrequency; |
| 283 |
maskerLevel=targetLevel; |
| 284 |
disp(['OHIO frequencies= ' num2str(targetFrequency)]) |
| 285 |
end |
| 286 |
|
| 287 |
% --- set cueTarget level according to assessment method |
| 288 |
% cue-test difference applies only with singleInterval |
| 289 |
switch experiment.threshEstMethod |
| 290 |
case {'2I2AFC++', '2I2AFC+++'}
|
| 291 |
% For 2IFC the cue stimulus (masker + probe) is the 'no' window |
| 292 |
% and the target stimulus (masker+probe) is the 'yes' window |
| 293 |
% the order of presentation is decided at the last minute. |
| 294 |
cueTargetLevel=-100; % the target is never in the 'no' window |
| 295 |
cueMaskerLevel=maskerLevel; % masker level is the same in both |
| 296 |
otherwise |
| 297 |
% 'single interval' or max likelihood |
| 298 |
switch experiment.paradigm |
| 299 |
% cue target is more audible |
| 300 |
case {'training','absThreshold', 'absThreshold_8', ...
|
| 301 |
'TENtest', 'threshold_duration','discomfort',... |
| 302 |
'overShoot','overShootB','overShootMB1', ... |
| 303 |
'overShootMB2', 'OHIO','OHIOabs','OHIOspect'} |
| 304 |
cueTargetLevel=targetLevel+cueTestDifference; |
| 305 |
|
| 306 |
case {'forwardMasking','forwardMaskingD','trainingIFMC', ...
|
| 307 |
'TMC','TMC_16ms', 'TMC - ELP', 'IFMC','IFMC_8ms', 'FMreProbe'} |
| 308 |
% cue masker is weaker to make target more audible |
| 309 |
cueMaskerLevel=maskerLevel-cueTestDifference; |
| 310 |
end |
| 311 |
end |
| 312 |
|
| 313 |
% ----------------------------- catch trial |
| 314 |
if withinRuns.catchTrial |
| 315 |
targetLevel=-100; % no target |
| 316 |
end |
| 317 |
|
| 318 |
% ----------------------------- calibration of sound output |
| 319 |
calibrationCorrectiondB=stimulusParameters.calibrationdB; |
| 320 |
if calibrationCorrectiondB<-50 |
| 321 |
if maskerFrequency==targetFrequency |
| 322 |
load 'calibrationFile' % calibrationFrequency calibrationAttenutation |
| 323 |
idx=find(calibrationFrequency==targetFrequency); |
| 324 |
if isempty(idx) |
| 325 |
error('Calibration bty file; frequency not found')
|
| 326 |
else |
| 327 |
calibrationCorrectiondB=calibrationAttenutation(idx) |
| 328 |
end |
| 329 |
else |
| 330 |
error('calibration by file requested but masker frequency is not the same as target')
|
| 331 |
end |
| 332 |
end |
| 333 |
|
| 334 |
|
| 335 |
% -------------------------------------- Checks on excessive signal level |
| 336 |
|
| 337 |
% clipping is relevant only for soundcard use (not modelling) |
| 338 |
switch experiment.ear |
| 339 |
case {'left', 'right', 'diotic',...
|
| 340 |
'dichotic', 'dioticLeft', 'dichoticRight'} |
| 341 |
experiment.headphonesUsed=1; |
| 342 |
otherwise |
| 343 |
experiment.headphonesUsed=0; |
| 344 |
end |
| 345 |
|
| 346 |
% NB calibration *reduces* the level of the soundCard output |
| 347 |
switch experiment.ear |
| 348 |
case {'left', 'right', 'diotic',...
|
| 349 |
'dichotic', 'dioticLeft', 'dichoticRight'} |
| 350 |
clippingLevel=91+calibrationCorrectiondB; |
| 351 |
soundCardMinimum=clippingLevel-20*log10(2^24); |
| 352 |
otherwise |
| 353 |
clippingLevel=inf; |
| 354 |
soundCardMinimum=-inf; |
| 355 |
end |
| 356 |
|
| 357 |
% Check for extreme WRV values and abort if necessary |
| 358 |
% WRVname specifies the value that changes from trial to trial |
| 359 |
withinRuns.forceThreshold=[]; |
| 360 |
switch stimulusParameters.WRVname |
| 361 |
% check for extreme values. Note that one of the tones might be switched off |
| 362 |
case 'maskerLevel' |
| 363 |
upperLevel=stimulusParameters.WRVlimits(2); |
| 364 |
lowerLevel=stimulusParameters.WRVlimits(1); |
| 365 |
if max(maskerLevel, cueMaskerLevel)> upperLevel |
| 366 |
errormsg=['Level(' num2str(max(maskerLevel,cueMaskerLevel)) ...
|
| 367 |
') is too high ***']; |
| 368 |
withinRuns.forceThreshold=upperLevel; |
| 369 |
withinRuns.forceThreshold=NaN; |
| 370 |
return |
| 371 |
end |
| 372 |
if max(maskerLevel, cueMaskerLevel)< lowerLevel |
| 373 |
errormsg=['Level(' num2str(max(maskerLevel,cueMaskerLevel)) ...
|
| 374 |
') is too low ***']; |
| 375 |
withinRuns.forceThreshold=lowerLevel; |
| 376 |
withinRuns.forceThreshold=NaN; |
| 377 |
return |
| 378 |
end |
| 379 |
|
| 380 |
if max(maskerLevel, cueMaskerLevel)> clippingLevel |
| 381 |
errormsg=['Level(' num2str(max(maskerLevel,cueMaskerLevel)) ...
|
| 382 |
') is clipping ***']; |
| 383 |
withinRuns.forceThreshold=clippingLevel; |
| 384 |
withinRuns.forceThreshold=NaN; |
| 385 |
return |
| 386 |
end |
| 387 |
|
| 388 |
case 'targetLevel' |
| 389 |
upperLevel=stimulusParameters.WRVlimits(2); |
| 390 |
lowerLevel=stimulusParameters.WRVlimits(1); |
| 391 |
if ~withinRuns.catchTrial |
| 392 |
if max(targetLevel, cueTargetLevel)> upperLevel |
| 393 |
errormsg=['target level (' ...
|
| 394 |
num2str(max(targetLevel, cueTargetLevel)) ... |
| 395 |
') is too high ***']; |
| 396 |
withinRuns.forceThreshold=upperLevel; |
| 397 |
withinRuns.forceThreshold=NaN; |
| 398 |
return |
| 399 |
end |
| 400 |
if max(targetLevel, cueTargetLevel)< lowerLevel |
| 401 |
errormsg=['target level (' ...
|
| 402 |
num2str(max(targetLevel, cueTargetLevel)) ... |
| 403 |
') is too low ***']; |
| 404 |
withinRuns.forceThreshold=lowerLevel; |
| 405 |
withinRuns.forceThreshold=NaN; |
| 406 |
return |
| 407 |
end |
| 408 |
if max(targetLevel, cueTargetLevel)> clippingLevel |
| 409 |
errormsg=['target level (' ...
|
| 410 |
num2str(max(targetLevel, cueTargetLevel)) ... |
| 411 |
') is clipping ***']; |
| 412 |
withinRuns.forceThreshold=upperLevel; |
| 413 |
withinRuns.forceThreshold=NaN; |
| 414 |
return |
| 415 |
end |
| 416 |
end |
| 417 |
case 'maskerDuration' |
| 418 |
% this is odd! but harmless |
| 419 |
if max(maskerDuration, cueMaskerDuration)> ... |
| 420 |
stimulusParameters.WRVlimits(2) |
| 421 |
errormsg=['maskerDuration (' ...
|
| 422 |
num2str(max(maskerDuration, cueMaskerDuration))... |
| 423 |
') is too long ***']; |
| 424 |
withinRuns.forceThreshold=stimulusParameters.WRVlimits(2); |
| 425 |
withinRuns.forceThreshold=NaN; |
| 426 |
return |
| 427 |
end |
| 428 |
|
| 429 |
if min(maskerDuration, cueMaskerDuration)... |
| 430 |
< stimulusParameters.WRVlimits(1) |
| 431 |
errormsg=['maskerDuration (' num2str(maskerLevel) ...
|
| 432 |
') too short ***']; |
| 433 |
withinRuns.forceThreshold=stimulusParameters.WRVlimits(1); |
| 434 |
withinRuns.forceThreshold=NaN; |
| 435 |
return |
| 436 |
end |
| 437 |
|
| 438 |
% legacy programming |
| 439 |
case 'gapDuration' |
| 440 |
if gapDuration<0 |
| 441 |
errormsg=['gapDuration (' num2str(gapDuration) ...
|
| 442 |
') is less than zero ***']; |
| 443 |
return |
| 444 |
end |
| 445 |
|
| 446 |
case 'maskerFrequency' |
| 447 |
switch experiment.paradigm |
| 448 |
case 'bandwidth' |
| 449 |
frequency=maskerFrequency'; |
| 450 |
if stimulusParameters.WRVstep<0 |
| 451 |
lowerLevel=stimulusParameters.targetFrequency; |
| 452 |
upperLevel=stimulusParameters.targetFrequency*2; |
| 453 |
else |
| 454 |
lowerLevel=stimulusParameters.targetFrequency/3; |
| 455 |
upperLevel=stimulusParameters.targetFrequency; |
| 456 |
end |
| 457 |
|
| 458 |
if frequency(1)>upperLevel || frequency(1)<lowerLevel |
| 459 |
errormsg=['frequency out of range: ' ... |
| 460 |
num2str(frequency)]; |
| 461 |
withinRuns.forceThreshold=frequency; |
| 462 |
return |
| 463 |
end |
| 464 |
otherwise |
| 465 |
end |
| 466 |
|
| 467 |
case 'maskerRelativeFrequency' |
| 468 |
if maskerRelativeFrequency<stimulusParameters.WRVlimits(1) |
| 469 |
errormsg=['masker frequency (' ...
|
| 470 |
num2str(frequencyDifference) ... |
| 471 |
') is outside WRV limits ***']; |
| 472 |
withinRuns.forceThreshold=stimulusParameters.WRVlimits(1) ; |
| 473 |
return |
| 474 |
end |
| 475 |
if maskerRelativeFrequency>stimulusParameters.WRVlimits(2) |
| 476 |
errormsg=['masker frequency (' ...
|
| 477 |
num2str(frequencyDifference) ... |
| 478 |
') is outside WRV limits ***']; |
| 479 |
withinRuns.forceThreshold=stimulusParameters.WRVlimits(2) ; |
| 480 |
return |
| 481 |
end |
| 482 |
|
| 483 |
end |
| 484 |
|
| 485 |
% --------------------------------Ear ---------------------------------- |
| 486 |
globalStimParams.ears='specified'; |
| 487 |
% ear: 1=left, 2=right |
| 488 |
switch experiment.ear |
| 489 |
case 'left' |
| 490 |
maskerEar=1; |
| 491 |
targetEar=1; |
| 492 |
case 'right' |
| 493 |
maskerEar=2; |
| 494 |
targetEar=2; |
| 495 |
case 'dichoticLeft' |
| 496 |
maskerEar=2; |
| 497 |
targetEar=1; |
| 498 |
case 'dichoticRight' |
| 499 |
maskerEar=1; |
| 500 |
targetEar=2; |
| 501 |
case 'diotic' |
| 502 |
maskerEar=1; |
| 503 |
targetEar=1; |
| 504 |
globalStimParams.ears='diotic'; |
| 505 |
case {'MAPmodel', 'MAPmodelMultiCh', 'MAPmodelSingleCh', 'MAPmodelListen',...
|
| 506 |
'statsModelLogistic', 'statsModelRareEvent'} |
| 507 |
maskerEar=1; |
| 508 |
targetEar=1; |
| 509 |
end |
| 510 |
|
| 511 |
backgroundType=stimulusParameters.backgroundType; |
| 512 |
switch stimulusParameters.backgroundType |
| 513 |
case {'noiseDich', 'pinkNoiseDich'}
|
| 514 |
% case 'Dich' |
| 515 |
% dich means put the background in the ear opposite to the target |
| 516 |
backgroundType=backgroundType(1:end-4); |
| 517 |
switch targetEar |
| 518 |
case 1 |
| 519 |
backgroundEar=2; |
| 520 |
case 2 |
| 521 |
backgroundEar=1; |
| 522 |
end |
| 523 |
otherwise |
| 524 |
% case {'none','noise', 'pinkNoise', 'TEN','babble'}
|
| 525 |
backgroundEar=targetEar; |
| 526 |
end |
| 527 |
|
| 528 |
% ------------------------------- Make Stimulus ------------------- |
| 529 |
% single interval up/down plays cue then target stimulus |
| 530 |
% 2IFC uses cue stimulus as interval with no target |
| 531 |
globalStimParams.FS=stimulusParameters.sampleRate; |
| 532 |
dt=1/stimulusParameters.sampleRate; |
| 533 |
globalStimParams.dt=dt; |
| 534 |
stimulusParameters.dt=dt; % for use later |
| 535 |
|
| 536 |
|
| 537 |
|
| 538 |
globalStimParams.audioOutCorrection=10^(calibrationCorrectiondB/20); |
| 539 |
% the output will be reduced by this amount in stimulusCreate |
| 540 |
% i.e. audio=audio/globalStimParams.audioOutCorrection |
| 541 |
% A 91 dB level will yield a peak amp of 1 for calibration=0 |
| 542 |
% A 91 dB level will yield a peak amp of 0.4467 for calibration=7 |
| 543 |
% A 98 dB level will yield a peak amp of 1 for calibration=7 |
| 544 |
|
| 545 |
precedingSilence=stimulusParameters.stimulusDelay; |
| 546 |
% all stimuli have 20 ms terminal silence. |
| 547 |
% this is clearance for modelling late-ringing targets |
| 548 |
terminalSilence=.03; |
| 549 |
|
| 550 |
% Now compute overall duration of the stimulus |
| 551 |
% note that all endsilence values are set to -1 |
| 552 |
% so that they will fill with terminal silence as required to make |
| 553 |
% components equal in length |
| 554 |
% We need to find the longest possible duration |
| 555 |
duration(1)=precedingSilence+maskerDuration+cueGapDuration... |
| 556 |
+targetDuration+terminalSilence; |
| 557 |
duration(2)=precedingSilence+maskerDuration+gapDuration... |
| 558 |
+targetDuration+ terminalSilence; |
| 559 |
% If the gap is negative we need to ignore it when estimating total length |
| 560 |
duration(3)=precedingSilence+maskerDuration+ terminalSilence; |
| 561 |
globalStimParams.overallDuration=max(duration); |
| 562 |
globalStimParams.nSignalPoints=... |
| 563 |
round(globalStimParams.overallDuration*globalStimParams.FS); |
| 564 |
|
| 565 |
% ----------------------------------------------cue stimulus |
| 566 |
% cue masker |
| 567 |
componentNo=1; |
| 568 |
precedingSilence=stimulusParameters.stimulusDelay; |
| 569 |
stimComponents(maskerEar,componentNo).type=maskerType; |
| 570 |
stimComponents(maskerEar,componentNo).toneDuration=cueMaskerDuration; |
| 571 |
stimComponents(maskerEar,componentNo).frequencies=cueMaskerFrequency; |
| 572 |
stimComponents(maskerEar,componentNo).amplitudesdB=cueMaskerLevel; |
| 573 |
stimComponents(maskerEar,componentNo).beginSilence=precedingSilence; |
| 574 |
stimComponents(maskerEar,componentNo).endSilence=-1; |
| 575 |
stimComponents(maskerEar,componentNo).AMfrequency=0; |
| 576 |
stimComponents(maskerEar,componentNo).AMdepth=0; |
| 577 |
if rampDuration<maskerDuration |
| 578 |
% ramps must be shorter than the signal |
| 579 |
stimComponents(maskerEar,componentNo).rampOnDur=rampDuration; |
| 580 |
stimComponents(maskerEar,componentNo).rampOffDur=rampDuration; |
| 581 |
else |
| 582 |
% or squeeze the ramp in |
| 583 |
stimComponents(maskerEar,componentNo).rampOnDur=maskerDuration/2; |
| 584 |
stimComponents(maskerEar,componentNo).rampOffDur=maskerDuration/2; |
| 585 |
end |
| 586 |
stimComponents(maskerEar,componentNo).phases=... |
| 587 |
stimulusParameters.maskerPhase; |
| 588 |
stimComponents(maskerEar,componentNo).niterations=0; % for IRN only |
| 589 |
% stimComponents(targetEar,componentNo) |
| 590 |
|
| 591 |
% cue target |
| 592 |
componentNo=2; |
| 593 |
precedingSilence=precedingSilence + maskerDuration+cueGapDuration; |
| 594 |
stimComponents(targetEar,componentNo).type=targetType; |
| 595 |
stimComponents(targetEar,componentNo).toneDuration=targetDuration; |
| 596 |
stimComponents(targetEar,componentNo).frequencies=cueTargetFrequency; |
| 597 |
stimComponents(targetEar,componentNo).amplitudesdB=cueTargetLevel; |
| 598 |
stimComponents(targetEar,componentNo).beginSilence=precedingSilence; |
| 599 |
stimComponents(targetEar,componentNo).endSilence=-1; |
| 600 |
stimComponents(targetEar,componentNo).AMfrequency=0; |
| 601 |
stimComponents(targetEar,componentNo).AMdepth=0; |
| 602 |
if rampDuration<targetDuration |
| 603 |
% ramps must be shorter than the signal |
| 604 |
stimComponents(targetEar,componentNo).rampOnDur=rampDuration; |
| 605 |
stimComponents(targetEar,componentNo).rampOffDur=rampDuration; |
| 606 |
else |
| 607 |
stimComponents(targetEar,componentNo).rampOnDur=0; |
| 608 |
stimComponents(targetEar,componentNo).rampOffDur=0; |
| 609 |
end |
| 610 |
stimComponents(targetEar,componentNo).phases=... |
| 611 |
stimulusParameters.targetPhase; |
| 612 |
% stimComponents(targetEar,componentNo) |
| 613 |
|
| 614 |
% background same ear as target |
| 615 |
componentNo=3; |
| 616 |
stimComponents(backgroundEar,componentNo).type=backgroundType; |
| 617 |
switch backgroundType |
| 618 |
case 'TEN' |
| 619 |
fileName=['..' filesep '..' filesep ... |
| 620 |
'multithresholdResources' filesep ... |
| 621 |
'backgrounds and maskers'... |
| 622 |
filesep 'ten.wav']; |
| 623 |
[tenNoise, FS]=wavread(fileName); |
| 624 |
tenNoise=resample(tenNoise, globalStimParams.FS, FS); |
| 625 |
stimComponents(backgroundEar,componentNo).type='file'; |
| 626 |
stimComponents(backgroundEar,componentNo).stimulus=tenNoise'; |
| 627 |
end |
| 628 |
stimComponents(backgroundEar,componentNo).toneDuration=... |
| 629 |
globalStimParams.overallDuration; |
| 630 |
stimComponents(backgroundEar,componentNo).amplitudesdB=backgroundLevel; |
| 631 |
stimComponents(backgroundEar,componentNo).beginSilence=0; |
| 632 |
stimComponents(backgroundEar,componentNo).endSilence=-1; |
| 633 |
stimComponents(backgroundEar,componentNo).AMfrequency=0; |
| 634 |
stimComponents(backgroundEar,componentNo).AMdepth=0; |
| 635 |
stimComponents(backgroundEar,componentNo).rampOnDur=rampDuration; |
| 636 |
stimComponents(backgroundEar,componentNo).rampOffDur=rampDuration; |
| 637 |
|
| 638 |
[cueStimulus, errormsg]=... |
| 639 |
stimulusCreate(globalStimParams, stimComponents, 0); |
| 640 |
if ~isempty(errormsg) % e.g. limits exceeded |
| 641 |
errormsg |
| 642 |
return |
| 643 |
end |
| 644 |
|
| 645 |
% ------------------------------------------ test stimulus |
| 646 |
% masker |
| 647 |
componentNo=1; |
| 648 |
precedingSilence=stimulusParameters.stimulusDelay; |
| 649 |
stimComponents(maskerEar,componentNo).type=maskerType; |
| 650 |
stimComponents(maskerEar,componentNo).toneDuration=maskerDuration; |
| 651 |
stimComponents(maskerEar,componentNo).frequencies=maskerFrequency; |
| 652 |
stimComponents(maskerEar,componentNo).amplitudesdB=maskerLevel; |
| 653 |
stimComponents(maskerEar,componentNo).beginSilence=precedingSilence; |
| 654 |
stimComponents(maskerEar,componentNo).endSilence=-1; |
| 655 |
stimComponents(maskerEar,componentNo).AMfrequency=0; |
| 656 |
stimComponents(maskerEar,componentNo).AMdepth=0; |
| 657 |
if rampDuration<maskerDuration |
| 658 |
% ramps must be shorter than the signal |
| 659 |
stimComponents(maskerEar,componentNo).rampOnDur=rampDuration; |
| 660 |
stimComponents(maskerEar,componentNo).rampOffDur=rampDuration; |
| 661 |
else |
| 662 |
stimComponents(maskerEar,componentNo).rampOnDur=maskerDuration/2; |
| 663 |
stimComponents(maskerEar,componentNo).rampOffDur=maskerDuration/2; |
| 664 |
end |
| 665 |
stimComponents(maskerEar,componentNo).phases=... |
| 666 |
stimulusParameters.maskerPhase; |
| 667 |
stimComponents(maskerEar,componentNo).niterations=0; % for IRN only |
| 668 |
|
| 669 |
% target |
| 670 |
componentNo=2; |
| 671 |
targetDelay=precedingSilence+ maskerDuration+ gapDuration; |
| 672 |
stimComponents(targetEar,componentNo).type=targetType; |
| 673 |
stimComponents(targetEar,componentNo).toneDuration=targetDuration; |
| 674 |
stimComponents(targetEar,componentNo).frequencies=targetFrequency; |
| 675 |
stimComponents(targetEar,componentNo).amplitudesdB=targetLevel; |
| 676 |
stimComponents(targetEar,componentNo).beginSilence=targetDelay; |
| 677 |
stimComponents(targetEar,componentNo).endSilence=-1; |
| 678 |
stimComponents(targetEar,componentNo).AMfrequency=0; |
| 679 |
stimComponents(targetEar,componentNo).AMdepth=0; |
| 680 |
if rampDuration<targetDuration |
| 681 |
% ramps must be shorter than the signal |
| 682 |
stimComponents(targetEar,componentNo).rampOnDur=rampDuration; |
| 683 |
stimComponents(targetEar,componentNo).rampOffDur=rampDuration; |
| 684 |
else |
| 685 |
stimComponents(targetEar,componentNo).rampOnDur=0; |
| 686 |
stimComponents(targetEar,componentNo).rampOffDur=0; |
| 687 |
end |
| 688 |
stimComponents(targetEar,componentNo).phases=stimulusParameters.targetPhase; |
| 689 |
% stimComponents(targetEar,componentNo) |
| 690 |
|
| 691 |
% background same ear as target |
| 692 |
componentNo=3; |
| 693 |
stimComponents(backgroundEar,componentNo).type=backgroundType; |
| 694 |
switch backgroundType |
| 695 |
case 'TEN' |
| 696 |
fileName=['..' filesep '..' filesep ... |
| 697 |
'multithresholdResources' filesep ... |
| 698 |
'backgrounds and maskers'... |
| 699 |
filesep 'ten.wav']; |
| 700 |
[tenNoise, FS]=wavread(fileName); |
| 701 |
|
| 702 |
tenNoise=resample(tenNoise, globalStimParams.FS, FS); |
| 703 |
stimComponents(backgroundEar,componentNo).type='file'; |
| 704 |
stimComponents(backgroundEar,componentNo).stimulus=tenNoise'; |
| 705 |
end |
| 706 |
stimComponents(backgroundEar,componentNo).toneDuration=... |
| 707 |
globalStimParams.overallDuration; |
| 708 |
stimComponents(backgroundEar,componentNo).amplitudesdB=backgroundLevel; |
| 709 |
stimComponents(backgroundEar,componentNo).beginSilence=0; |
| 710 |
stimComponents(backgroundEar,componentNo).endSilence=-1; |
| 711 |
stimComponents(backgroundEar,componentNo).rampOnDur=rampDuration; |
| 712 |
stimComponents(backgroundEar,componentNo).rampOffDur=rampDuration; |
| 713 |
stimComponents(backgroundEar,componentNo).AMfrequency=0; |
| 714 |
stimComponents(backgroundEar,componentNo).AMdepth=0; |
| 715 |
|
| 716 |
% timings used when evaluating MAP peripheral model |
| 717 |
% this is the Slope during which spikes are counted |
| 718 |
switch experiment.paradigm |
| 719 |
case 'gapDetection' |
| 720 |
% gap is the 'target' in this case |
| 721 |
stimulusParameters.testTargetBegins=... |
| 722 |
stimulusParameters.stimulusDelay... |
| 723 |
+stimulusParameters.maskerDuration; |
| 724 |
stimulusParameters.testTargetEnds=... |
| 725 |
stimulusParameters.testTargetBegins+withinRuns.variableValue; |
| 726 |
% case 'SRT' |
| 727 |
% set(handles.editdigitInput,'visible','off') |
| 728 |
otherwise |
| 729 |
stimulusParameters.testTargetBegins=targetDelay; |
| 730 |
stimulusParameters.testTargetEnds=targetDelay+targetDuration; |
| 731 |
end |
| 732 |
|
| 733 |
% ------------------------------------------------------------- play! |
| 734 |
% Create and play stimulus (as required by different paradigms) |
| 735 |
switch experiment.ear |
| 736 |
case {'statsModelLogistic', 'statsModelRareEvent'}
|
| 737 |
audio=[0;0]; % no need to compute stimulus |
| 738 |
|
| 739 |
otherwise % create the stimulus |
| 740 |
[targetStimulus, errormsg]= ... |
| 741 |
stimulusCreate(globalStimParams, stimComponents, 0); |
| 742 |
|
| 743 |
if ~isempty(errormsg) % e.g. limits exceeded |
| 744 |
errormsg |
| 745 |
return |
| 746 |
end |
| 747 |
|
| 748 |
switch experiment.ear |
| 749 |
case {'MAPmodel' , 'MAPmodelMultiCh', 'MAPmodelSingleCh', 'MAPmodelListen'}
|
| 750 |
% model requires no calibration correction; |
| 751 |
% signal is already in Pascals |
| 752 |
globalStimParams.audioOutCorrection=1; |
| 753 |
% use only the targetStimulus for the MAP model |
| 754 |
audio=targetStimulus; |
| 755 |
|
| 756 |
otherwise % left, right diotic dichotic |
| 757 |
if stimulusParameters.includeCue |
| 758 |
audio= [cueStimulus; targetStimulus]; |
| 759 |
else % no cue |
| 760 |
audio=targetStimulus; |
| 761 |
end |
| 762 |
end |
| 763 |
|
| 764 |
% playtime |
| 765 |
% order of the cue and test stimuli varies for 2AFC |
| 766 |
switch experiment.threshEstMethod |
| 767 |
case {'2I2AFC++', '2I2AFC+++'}
|
| 768 |
% intervening silence (currently none; masking delay serves this purpose) |
| 769 |
IAFCinterveningSilence=zeros(round(AFCsilenceDuration/dt),2); |
| 770 |
if rand>0.5 % put test stimulus first |
| 771 |
stimulusParameters.testTargetBegins=targetDelay ; |
| 772 |
stimulusParameters.testTargetEnds= ... |
| 773 |
targetDelay+targetDuration; |
| 774 |
stimulusParameters.testNonTargetBegins=... |
| 775 |
length(cueStimulus)*dt ... |
| 776 |
+ AFCsilenceDuration +targetDelay ; |
| 777 |
stimulusParameters.testNonTargetEnds=... |
| 778 |
length(cueStimulus)*dt ... |
| 779 |
+ AFCsilenceDuration+targetDelay+targetDuration; |
| 780 |
|
| 781 |
set(handles.pushbutton1,'backgroundcolor','r'), drawnow |
| 782 |
y=audioplayer(targetStimulus, globalStimParams.FS, 24); |
| 783 |
playblocking(y) |
| 784 |
set(handles.pushbutton1,'backgroundcolor',... |
| 785 |
get(0,'defaultUicontrolBackgroundColor')), drawnow |
| 786 |
y=audioplayer(IAFCinterveningSilence, ... |
| 787 |
globalStimParams.FS, 24); |
| 788 |
playblocking(y) |
| 789 |
set(handles.pushbutton2,'backgroundcolor','r'), drawnow |
| 790 |
y=audioplayer(cueStimulus, globalStimParams.FS, 24); |
| 791 |
playblocking(y) |
| 792 |
set(handles.pushbutton2,'backgroundcolor',... |
| 793 |
get(0,'defaultUicontrolBackgroundColor')), drawnow |
| 794 |
withinRuns.stimulusOrder='targetFirst'; |
| 795 |
audio= [targetStimulus; IAFCinterveningSilence; ... |
| 796 |
cueStimulus]; % for plotting purposes later |
| 797 |
|
| 798 |
else % put test stimulus second |
| 799 |
stimulusParameters.testTargetBegins=... |
| 800 |
length(cueStimulus)*dt ... |
| 801 |
+ AFCsilenceDuration +targetDelay ; |
| 802 |
stimulusParameters.testTargetEnds=... |
| 803 |
length(cueStimulus)*dt ... |
| 804 |
+ AFCsilenceDuration+targetDelay+targetDuration; |
| 805 |
stimulusParameters.testNonTargetBegins=targetDelay ; |
| 806 |
stimulusParameters.testNonTargetEnds=... |
| 807 |
targetDelay+targetDuration; |
| 808 |
|
| 809 |
set(handles.pushbutton1,'backgroundcolor','r'),drawnow |
| 810 |
y=audioplayer(cueStimulus, globalStimParams.FS, 24); |
| 811 |
playblocking(y) |
| 812 |
set(handles.pushbutton1,'backgroundcolor',... |
| 813 |
get(0,'defaultUicontrolBackgroundColor')), drawnow |
| 814 |
y=audioplayer(IAFCinterveningSilence, ... |
| 815 |
globalStimParams.FS, 24); |
| 816 |
playblocking(y) |
| 817 |
set(handles.pushbutton2,'backgroundcolor','r'), drawnow |
| 818 |
y=audioplayer(targetStimulus, globalStimParams.FS, 24); |
| 819 |
playblocking(y) |
| 820 |
set(handles.pushbutton2,'backgroundcolor',... |
| 821 |
get(0,'defaultUicontrolBackgroundColor')), drawnow |
| 822 |
withinRuns.stimulusOrder='targetSecond'; |
| 823 |
audio= [cueStimulus; IAFCinterveningSilence; ... |
| 824 |
targetStimulus]; % for plotting purposes later |
| 825 |
end |
| 826 |
otherwise % singleInterval |
| 827 |
if strcmp(experiment.ear,'MAPmodel') ... |
| 828 |
|| strcmp(experiment.ear,'MAPmodelMultiCh') ... |
| 829 |
|| strcmp(experiment.ear,'MAPmodelSingleCh') ... |
| 830 |
||strcmp(experiment.ear,'MAPmodelListen') |
| 831 |
% don't play for MAPmodel |
| 832 |
switch experiment.ear |
| 833 |
% except on special request |
| 834 |
case {'MAPmodelListen'}
|
| 835 |
y=audioplayer(audio, globalStimParams.FS, 24); |
| 836 |
playblocking(y) % suspends operations until completed |
| 837 |
end |
| 838 |
else |
| 839 |
y=audioplayer(audio, globalStimParams.FS, 24); |
| 840 |
playblocking(y) |
| 841 |
end % if experiment.ear |
| 842 |
end % switch experiment.threshEstMethod |
| 843 |
end % switch experiment.ear |
| 844 |
|
| 845 |
|
| 846 |
% switch experiment.ear |
| 847 |
% case {'MAPmodel', 'MAPmodelListen', 'MAPmodelMultiCh','MAPmodelSingleCh'}
|
| 848 |
% % save audio for later reference or for input to MAP model |
| 849 |
% wavwrite(audio/max(audio), globalStimParams.FS,32,'stimulus') |
| 850 |
% end |
| 851 |
|
| 852 |
% Panel 1 |
| 853 |
% graphical presentation of the stimulus |
| 854 |
% NB shown *after* the stimulus has been presented |
| 855 |
axes(expGUIhandles.axes1), cla |
| 856 |
% plot is HW rectified and plotted as dB re 28e-6 |
| 857 |
% calibration is ignored |
| 858 |
t=dt:dt:dt*length(audio); |
| 859 |
plot(t,stimulusParameters.calibrationdB+20*log10((abs(audio)+1e-10)/28e-6)) |
| 860 |
% set(gca,'xtick',[]) |
| 861 |
ylim([-20 100]) |
| 862 |
ylabel('stimulus (dB SPL)')
|
| 863 |
xlim([0 t(end)]) |
| 864 |
grid on |
| 865 |
header=[betweenRuns.variableName1 ': ' ... |
| 866 |
num2str(betweenRuns.var1Sequence(betweenRuns.runNumber))]; |
| 867 |
header=[header ' ' num2str(... |
| 868 |
betweenRuns.var2Sequence(betweenRuns.runNumber)) ':' ... |
| 869 |
betweenRuns.variableName2 ]; |
| 870 |
title(header) |
| 871 |
|