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 / utilities / stimulusCreate.m @ 38:c2204b18f4a2
History | View | Annotate | Download (56.5 KB)
| 1 | 0:f233164f4c86 | rmeddis | function [audio, msg, time]=stimulusCreate(globalStimParams,... |
|---|---|---|---|
| 2 | stimComponents, doPlot) |
||
| 3 | % updated June 2007 |
||
| 4 | % the only authorised version of stimulus create is the version to be found |
||
| 5 | % in MAP1_6. Other versions are copies!! |
||
| 6 | % |
||
| 7 | % for a simple tone you need |
||
| 8 | % |
||
| 9 | % % Mandatory structure fields |
||
| 10 | % globalStimParams.FS=100000; |
||
| 11 | % globalStimParams.overallDuration=.1; % s |
||
| 12 | % doPlot=1; |
||
| 13 | % |
||
| 14 | % stim.type='tone'; |
||
| 15 | % stim.phases='sin'; |
||
| 16 | % stim.toneDuration=.05;; |
||
| 17 | % stim.frequencies=500; |
||
| 18 | % stim.amplitudesdB=50; |
||
| 19 | % stim.beginSilence=.01; |
||
| 20 | % stim.endSilence=-1; |
||
| 21 | % stim.rampOnDur=.002; |
||
| 22 | % stim.rampOffDur=-1; |
||
| 23 | % [audio, msg]=stimulusCreate(globalStimParams, stim, doPlot); |
||
| 24 | % |
||
| 25 | % % or for multi-component stimuli |
||
| 26 | % % Mandatory structure fields |
||
| 27 | % globalStimParams.FS=100000; |
||
| 28 | % globalStimParams.overallDuration=.1; % s |
||
| 29 | % ear=1; |
||
| 30 | % componentNo=1; |
||
| 31 | % |
||
| 32 | % stimComponents(ear, componentNo).type='tone'; |
||
| 33 | % stimComponents(ear, componentNo).phases='sin'; |
||
| 34 | % stimComponents(ear, componentNo).toneDuration=.05;; |
||
| 35 | % stimComponents(ear, componentNo).frequencies=500; |
||
| 36 | % stimComponents(ear, componentNo).amplitudesdB=50; |
||
| 37 | % stimComponents(ear, componentNo).beginSilence=.01; |
||
| 38 | % stimComponents(ear, componentNo).endSilence=-1; |
||
| 39 | % stimComponents(ear, componentNo).rampOnDur=.002; |
||
| 40 | % stimComponents(ear, componentNo).rampOffDur=-1; |
||
| 41 | % |
||
| 42 | % % All components are forced to have the same overall duration and sample rate |
||
| 43 | % |
||
| 44 | % |
||
| 45 | % [audio, msg]=stimulusCreate(globalStimParams, stimComponents); |
||
| 46 | % |
||
| 47 | % Optional fields |
||
| 48 | % .ears overides ear setting by component |
||
| 49 | % globalStimParams.ears='monoticL'; % 'monoticL', 'monoticR', 'diotic', 'dichotic' |
||
| 50 | % |
||
| 51 | % globalStimParams.filter = [leftfl leftfu rightfl right fu] |
||
| 52 | % filter is applied separately to left and right combined sounds |
||
| 53 | % |
||
| 54 | % correction factor is applied to all signals to compensate for differences in output devices. |
||
| 55 | % audioOutCorrection is a scalar |
||
| 56 | % globalStimParams.audioOutCorrection=2; |
||
| 57 | % |
||
| 58 | % globalStimParams.FFT= 1; % {0, 1}
|
||
| 59 | % globalStimParams.doPlot=1; % {0, 1}
|
||
| 60 | % globalStimParams.doPlay=1; % {0, 1}
|
||
| 61 | % |
||
| 62 | % stimComponents(ear, componentNo).filter=[100 10000 2] % [lower, upper, order] applies to an |
||
| 63 | % individual component |
||
| 64 | % |
||
| 65 | % Output arguments: |
||
| 66 | % audio is a stereo signal, a 2-column vector |
||
| 67 | % |
||
| 68 | % |
||
| 69 | % stim.type='noise'; % {'IRN', 'irn', 'noise', 'pinkNoise'}
|
||
| 70 | % stim.toneDuration=.05;; |
||
| 71 | % stim.amplitudesdB=50; |
||
| 72 | % stim.beginSilence=.01; |
||
| 73 | % stim.endSilence=-1; |
||
| 74 | % stim.rampOnDur=.002; |
||
| 75 | % stim.rampOffDur=-1; |
||
| 76 | % [audio, msg]=stimulusCreate(globalStimParams, stim); |
||
| 77 | % |
||
| 78 | % % for IRN only |
||
| 79 | % stim.type='IRN'; |
||
| 80 | % stim.toneDuration=.05;; |
||
| 81 | % stim.amplitudesdB=50; |
||
| 82 | % stim.beginSilence=.01; |
||
| 83 | % stim.endSilence=-1; |
||
| 84 | % stim.rampOnDur=.002; |
||
| 85 | % stim.rampOffDur=-1; |
||
| 86 | % stim.niterations = 8; %0 for white noise |
||
| 87 | % stim.delay = 1/150; |
||
| 88 | % stim.irnGain = 1; |
||
| 89 | % [audio, msg]=stimulusCreate(globalStimParams, stim);stimComponents.clickWidth; |
||
| 90 | % |
||
| 91 | % stim.type='clicks'; % uses frequencies for duty cycle |
||
| 92 | % stim.toneDuration=.05;; |
||
| 93 | % stim.frequencies=500; |
||
| 94 | % stim.amplitudesdB=50; |
||
| 95 | % stim.beginSilence=.01; |
||
| 96 | % stim.endSilence=-1; |
||
| 97 | % stim.rampOnDur=.002; |
||
| 98 | % stim.rampOffDur=-1; |
||
| 99 | % [audio, msg]=stimulusCreate(globalStimParams, stim); |
||
| 100 | % stimComponents.clickWidth=.0001; % default= dt |
||
| 101 | % stimComponents.clickHeight= 20e-6; % default= 28e-6 * 10^(stimComponents.amplitudesdB/20); |
||
| 102 | % stim.clickTimes=[.4 .6]; % times in cylce when clicks occur, default = 1 |
||
| 103 | % |
||
| 104 | % |
||
| 105 | |||
| 106 | msg=''; %error messages; no message is a good message |
||
| 107 | |||
| 108 | % plotting can be set as a separate argument or as a globalstimParams |
||
| 109 | % variable. this is for backwards compatibility only |
||
| 110 | if nargin>2, |
||
| 111 | globalStimParams.doPlot=doPlot; |
||
| 112 | end |
||
| 113 | |||
| 114 | % stimComponents(1,1).endSilence=-1; % end silence is always computed |
||
| 115 | |||
| 116 | % perform checks and set defaults |
||
| 117 | [globalStimParams, stimComponents]=checkDescriptors(globalStimParams, stimComponents); |
||
| 118 | |||
| 119 | |||
| 120 | % create empty stereo audio of appropriate length |
||
| 121 | audio=zeros(globalStimParams.nSignalPoints, 2); |
||
| 122 | % signal=zeros(globalStimParams.nSignalPoints, 1); |
||
| 123 | dt=globalStimParams.dt; |
||
| 124 | |||
| 125 | [Nears nComponentSounds]=size(stimComponents); |
||
| 126 | for ear=1:Nears % left/ right |
||
| 127 | % combinedSignal is the sum of all sounds in one ear |
||
| 128 | % it is a column vector |
||
| 129 | combinedSignal=zeros(globalStimParams.nSignalPoints,1); |
||
| 130 | |||
| 131 | % find valid components |
||
| 132 | % if a component is empty, it is not a validComponent and is ignored |
||
| 133 | validStimComponents=[]; |
||
| 134 | for i=1:nComponentSounds |
||
| 135 | if ~isempty(stimComponents(ear,i).type) |
||
| 136 | validStimComponents=[validStimComponents i]; |
||
| 137 | end |
||
| 138 | end |
||
| 139 | |||
| 140 | for componentNo=validStimComponents |
||
| 141 | % compute individual components before adding |
||
| 142 | stim=stimComponents(ear,componentNo); |
||
| 143 | switch stim.type |
||
| 144 | case 'tone' |
||
| 145 | stimulus=maketone(globalStimParams, stim); |
||
| 146 | |||
| 147 | case 'fmTone' |
||
| 148 | stimulus=makeFMtone(globalStimParams, stim); |
||
| 149 | |||
| 150 | case 'OHIO' |
||
| 151 | stim.beginSilence=0; |
||
| 152 | stimulus=makeOHIOtones(globalStimParams, stim); |
||
| 153 | |||
| 154 | case 'transposedStimulus' |
||
| 155 | stim.beginSilence=0; % necessary because of recursion |
||
| 156 | stimulus=makeTransposedStimulus(globalStimParams, stim); |
||
| 157 | |||
| 158 | case { 'noise', 'pinkNoise'}
|
||
| 159 | stimulus=makeNoise(globalStimParams, stim); |
||
| 160 | |||
| 161 | case { 'whiteNoise'}
|
||
| 162 | stimulus=makeWhiteNoise(globalStimParams, stim); |
||
| 163 | |||
| 164 | case {'IRN', 'irn'}
|
||
| 165 | stimulus=makeIRN(globalStimParams, stim); |
||
| 166 | |||
| 167 | case {'RPN'}
|
||
| 168 | stimulus=makeRPN(globalStimParams, stim); |
||
| 169 | |||
| 170 | case 'clicks' |
||
| 171 | stimulus=makeClicks(globalStimParams, stim); |
||
| 172 | |||
| 173 | case 'PressnitzerClicks' |
||
| 174 | % kxx clicks |
||
| 175 | % k is 1/clickRepeatFrequency |
||
| 176 | stimulus=makePressnitzerClicks(globalStimParams, stimComponents); |
||
| 177 | |||
| 178 | case 'PressnitzerABxClicks' |
||
| 179 | % kxx clicks |
||
| 180 | % k is 1/clickRepeatFrequency |
||
| 181 | stimulus=makePressnitzerABxClicks(globalStimParams, stimComponents); |
||
| 182 | |||
| 183 | case 'ABxClicks' |
||
| 184 | % A=rand*k, B=k-A, x=rand*k. |
||
| 185 | stimulus=makeABxClicks(globalStimParams, stimComponents); |
||
| 186 | |||
| 187 | case 'YostClicks' |
||
| 188 | % kxx clicks |
||
| 189 | % k is 1/clickRepeatFrequency |
||
| 190 | stimulus=makeYostClicks(globalStimParams, stimComponents); |
||
| 191 | |||
| 192 | case 'kxxClicks' |
||
| 193 | % kxx clicks |
||
| 194 | % k is 1/clickRepeatFrequency |
||
| 195 | stimulus=makeKxxClicks(globalStimParams, stimComponents); |
||
| 196 | |||
| 197 | |||
| 198 | % case 'babble' |
||
| 199 | % % NB random start in a long file |
||
| 200 | % [stimulus sampleRate]= wavread('babble');
|
||
| 201 | % nPoints=round(sampleRate*... |
||
| 202 | % stimComponents(ear,componentNo).toneDuration); |
||
| 203 | % start=round(rand(1,1)*(length(stimulus)-nPoints)); |
||
| 204 | % stimulus=stimulus(start:start+nPoints-1); |
||
| 205 | % rms= 20*log10((mean(stimulus.^2).^0.5)/20e-6); |
||
| 206 | % dBSPLrms=stimComponents(ear,componentNo).amplitudesdB; |
||
| 207 | % gain=10.^((dBSPLrms-rms)/20); |
||
| 208 | % stimulus=stimulus'*gain; |
||
| 209 | |||
| 210 | case 'speech' |
||
| 211 | [stimulus sampleRate]= wavread('speech');
|
||
| 212 | stimulus=stimulus'; |
||
| 213 | nPoints=sampleRate*stimComponents(ear,componentNo).toneDuration; |
||
| 214 | if nPoints > length(stimulus) |
||
| 215 | initialSilence=zeros(1,nPoints-length(stimulus)); |
||
| 216 | else |
||
| 217 | initialSilence=[]; |
||
| 218 | start=round(rand(1,1)*(length(stimulus)-nPoints)); |
||
| 219 | stimulus=stimulus(start:start+nPoints-1); |
||
| 220 | end |
||
| 221 | rms= 20*log10((mean(stimulus.^2).^0.5)/20e-6); |
||
| 222 | dBSPLrms=stimComponents(ear,componentNo).amplitudesdB; |
||
| 223 | gain=10.^((dBSPLrms-rms)/20); |
||
| 224 | stimulus=stimulus*gain; |
||
| 225 | stimulus=[stimulus initialSilence ]; |
||
| 226 | |||
| 227 | |||
| 228 | case 'file' |
||
| 229 | % component already read from file and stored in stimulus. Insert it here |
||
| 230 | % additional code for establishing signal rms level |
||
| 231 | % NB signal is always mono at this stage |
||
| 232 | |||
| 233 | stimulus=stim.stimulus; |
||
| 234 | dBSPL=stim.amplitudesdB; |
||
| 235 | |||
| 236 | nPoints=round(stim.toneDuration/dt); |
||
| 237 | [r c]=size(stimulus); |
||
| 238 | if r>c, stimulus=stimulus'; end % secure horizontal vector |
||
| 239 | stimulus=stimulus(1,1:nPoints); % only mono taken from file |
||
| 240 | |||
| 241 | try |
||
| 242 | % dB rms |
||
| 243 | rms= 20*log10((mean(stimulus.^2).^0.5)/20e-6); |
||
| 244 | % special request to set fixed rms for stimulus |
||
| 245 | dBSPLrms=stimComponents(ear,componentNo).amplitudesdBrms; |
||
| 246 | if ~(dBSPLrms==-1) |
||
| 247 | gain=10.^((dBSPLrms-rms)/20); |
||
| 248 | stimulus=stimulus*gain; |
||
| 249 | end |
||
| 250 | catch |
||
| 251 | % If no request for rms level is made |
||
| 252 | % set dB as peak amp |
||
| 253 | [stimulus gain]= normalize(stimulus); |
||
| 254 | dBSPL=stimComponents(ear,componentNo).amplitudesdB; |
||
| 255 | if ~(dBSPL==-1) |
||
| 256 | amplitude=28e-6*10.^(dBSPL/20); |
||
| 257 | stimulus=stimulus*amplitude; |
||
| 258 | end |
||
| 259 | end |
||
| 260 | |||
| 261 | case 'none' |
||
| 262 | % no stimulus |
||
| 263 | stimulus=zeros(1,round(stim.toneDuration/dt)); |
||
| 264 | |||
| 265 | case 'digitStrings' |
||
| 266 | % select a digit string at random anduse as target |
||
| 267 | files=dir(['..' filesep '..' filesep 'multithresholdResources\digitStrings']); |
||
| 268 | files=files(3:end); |
||
| 269 | nFiles=length(files); |
||
| 270 | fileNo=ceil(nFiles*rand); |
||
| 271 | fileData=files(fileNo); |
||
| 272 | fileName=['..\..\multithresholdResources\digitStrings\' fileData.name]; |
||
| 273 | [stimulus sampleRate]=wavread(fileName); |
||
| 274 | stimulus=stimulus(:,1)'; % make into a row vector |
||
| 275 | % estimate the extend of endsilence padding |
||
| 276 | nPoints=sampleRate*... |
||
| 277 | stimComponents(ear,componentNo).toneDuration; |
||
| 278 | if nPoints > length(stimulus) |
||
| 279 | endSilence=zeros(1,nPoints-length(stimulus)); |
||
| 280 | else |
||
| 281 | % or truncate the file |
||
| 282 | endSilence=[]; |
||
| 283 | stimulus=stimulus(1:nPoints); |
||
| 284 | end |
||
| 285 | % compute rms before adding silence |
||
| 286 | rms= 20*log10((mean(stimulus.^2).^0.5)/20e-6); |
||
| 287 | dBSPLrms=stimComponents(ear,componentNo).amplitudesdB; |
||
| 288 | gain=10.^((dBSPLrms-rms)/20); |
||
| 289 | stimulus=stimulus*gain; |
||
| 290 | stimulus=[stimulus endSilence]; |
||
| 291 | global stimulusParameters |
||
| 292 | stimulusParameters.digitString=fileName(end-7:end-5); |
||
| 293 | |||
| 294 | otherwise |
||
| 295 | switch stim.type(end-5:end) |
||
| 296 | % any file name with 'Babble' at the end is a |
||
| 297 | % multiThreshold file |
||
| 298 | case 'Babble' |
||
| 299 | % one of the many babbles is selected. |
||
| 300 | % NB random start in a long file |
||
| 301 | % stim.type should contain the name of the babble file |
||
| 302 | fileName=['..' filesep '..' filesep ... |
||
| 303 | 'multithresholdResources' filesep ... |
||
| 304 | 'backgrounds and maskers'... |
||
| 305 | filesep stim.type]; |
||
| 306 | |||
| 307 | [stimulus sampleRate]= wavread(fileName); |
||
| 308 | if ~isequal(sampleRate, globalStimParams.FS) |
||
| 309 | % NB the file will read but will disagree with |
||
| 310 | % tone stimuli or other files read |
||
| 311 | msg= ['error: file sample rate disagrees ' ... |
||
| 312 | 'with sample rate requested in paradigm'... |
||
| 313 | ' file (' ...
|
||
| 314 | num2str(globalStimParams.FS) ').']; |
||
| 315 | error(msg); |
||
| 316 | end |
||
| 317 | nPoints=round(sampleRate*... |
||
| 318 | stimComponents(ear,componentNo).toneDuration); |
||
| 319 | start=round(rand(1,1)*(length(stimulus)-nPoints)); |
||
| 320 | stimulus=stimulus(start:start+nPoints-1); |
||
| 321 | rms= 20*log10((mean(stimulus.^2).^0.5)/20e-6); |
||
| 322 | dBSPLrms=stimComponents(ear,componentNo).amplitudesdB; |
||
| 323 | gain=10.^((dBSPLrms-rms)/20); |
||
| 324 | stimulus=stimulus'*gain; |
||
| 325 | |||
| 326 | otherwise |
||
| 327 | % stim.type may be the name of a file to be read |
||
| 328 | % play from beginning for stimulus duration |
||
| 329 | try |
||
| 330 | [stimulus sampleRate]= wavread([stim.type '.wav']); |
||
| 331 | catch |
||
| 332 | error(['stimulusCreate: unrecognised stimulus type -> '... |
||
| 333 | stim.type]) |
||
| 334 | end |
||
| 335 | if ~isequal(sampleRate, globalStimParams.FS) |
||
| 336 | % NB the file will read but will disagree with |
||
| 337 | % tone stimuli or other files read |
||
| 338 | msg= ['error: file sample rate disagrees ' ... |
||
| 339 | 'with sample rate requested in paradigm'... |
||
| 340 | ' file (' ...
|
||
| 341 | num2str(globalStimParams.FS) ').']; |
||
| 342 | error(msg); |
||
| 343 | end |
||
| 344 | stimulus=stimulus'; % make into a row vector |
||
| 345 | % estimate the extend of endsilence padding |
||
| 346 | nPoints=sampleRate*... |
||
| 347 | stimComponents(ear,componentNo).toneDuration; |
||
| 348 | if nPoints > length(stimulus) |
||
| 349 | endSilence=zeros(1,nPoints-length(stimulus)); |
||
| 350 | else |
||
| 351 | % or truncate the file |
||
| 352 | endSilence=[]; |
||
| 353 | stimulus=stimulus(1:nPoints); |
||
| 354 | end |
||
| 355 | % compute rms before adding silence |
||
| 356 | rms= 20*log10((mean(stimulus.^2).^0.5)/20e-6); |
||
| 357 | dBSPLrms=stimComponents(ear,componentNo).amplitudesdB; |
||
| 358 | gain=10.^((dBSPLrms-rms)/20); |
||
| 359 | stimulus=stimulus*gain; |
||
| 360 | stimulus=[stimulus endSilence]; |
||
| 361 | end |
||
| 362 | end |
||
| 363 | |||
| 364 | % NB stimulus is a row vector now! |
||
| 365 | % audio and combinedSignal were column vectors |
||
| 366 | % signal will be a row vector |
||
| 367 | |||
| 368 | % filter stimulus |
||
| 369 | try |
||
| 370 | % if filter field is present, [lower upper order] |
||
| 371 | if stim.filter(1)>0 % 0 means don't filter |
||
| 372 | stimulus=Butterworth (stimulus, dt, stim.filter(1), ... |
||
| 373 | stim.filter(2), stim.filter(3)); |
||
| 374 | |||
| 375 | end |
||
| 376 | catch |
||
| 377 | end |
||
| 378 | |||
| 379 | |||
| 380 | % apply amplitude modulation |
||
| 381 | if isfield(stim,'AMfrequency') & isfield(stim,'AMdepth') |
||
| 382 | if stim.AMfrequency>0 & stim.AMdepth>0 |
||
| 383 | time=dt:dt:dt*length(stimulus); |
||
| 384 | modulator=sin(2*pi*stim.AMfrequency*time); |
||
| 385 | modulator=modulator*stim.AMdepth/100 + 1; % depth is percent |
||
| 386 | stimulus=stimulus.*modulator/2; |
||
| 387 | end |
||
| 388 | end |
||
| 389 | |||
| 390 | % Basic stimulus is now created. |
||
| 391 | % Add trappings, ramps, silences to main stimulus |
||
| 392 | rampOnTime=0; %ramp starts at the beginning of the stimulus |
||
| 393 | rampOffTime=stim.toneDuration-stim.rampOffDur; |
||
| 394 | if stim.rampOnDur>0.0001 |
||
| 395 | stimulus=applyRampOn(stimulus, stim.rampOnDur, rampOnTime, 1/dt); |
||
| 396 | stimulus=applyRampOff(stimulus, stim.rampOffDur, rampOffTime, 1/dt); |
||
| 397 | end |
||
| 398 | if stim.rampOnDur<-0.0001 % apply Gaussian ramp |
||
| 399 | stimulus=applyGaussianRamps(stimulus, -stim.rampOnDur, 1/dt); |
||
| 400 | end |
||
| 401 | |||
| 402 | 38:c2204b18f4a2 | rmeddis | % begin silence |
| 403 | 0:f233164f4c86 | rmeddis | % start with a signal of the right length consisting of zeros |
| 404 | signal=zeros(1, globalStimParams.nSignalPoints); |
||
| 405 | % identify start of stimulus |
||
| 406 | insertStimulusAt=round(stim.beginSilence/dt)+1; |
||
| 407 | % add stimulus |
||
| 408 | endOfStimulus=insertStimulusAt+length(stimulus)-1; |
||
| 409 | if endOfStimulus<=globalStimParams.nSignalPoints |
||
| 410 | signal(1, insertStimulusAt: endOfStimulus)=stimulus; |
||
| 411 | else |
||
| 412 | error('stimulusCreate: tone too long to fit into the overall duration')
|
||
| 413 | end |
||
| 414 | |||
| 415 | % time signature |
||
| 416 | time=dt:dt:dt*length(signal); |
||
| 417 | 38:c2204b18f4a2 | rmeddis | % figure(22), plot(signal), title([num2str(ear) ' - ' num2str(componentNo)]),pause (1) |
| 418 | 0:f233164f4c86 | rmeddis | |
| 419 | try |
||
| 420 | % create a column vector and trap if no signal has been created |
||
| 421 | signal=signal'; |
||
| 422 | % also traps if signals are not the same length |
||
| 423 | combinedSignal=combinedSignal+signal; |
||
| 424 | % figure(21), plot(combinedSignal), title([num2str(ear) ' - ' num2str(componentNo)]),pause (1) |
||
| 425 | catch |
||
| 426 | % dump everything because there is a problem |
||
| 427 | globalStimParams |
||
| 428 | [ear componentNo] |
||
| 429 | stim |
||
| 430 | size(combinedSignal) |
||
| 431 | size(signal) |
||
| 432 | [ size(initialSilence) size(signal) size(endSilence)] |
||
| 433 | [ ear componentNo... |
||
| 434 | round(globalStimParams.overallDuration*globalStimParams.FS)... |
||
| 435 | round(stim.beginSilence*globalStimParams.FS)... |
||
| 436 | round(stim.toneDuration*globalStimParams.FS) ... |
||
| 437 | round(stim.endSilence*globalStimParams.FS)... |
||
| 438 | ( round(globalStimParams.overallDuration*globalStimParams.FS)... |
||
| 439 | -round(stim.beginSilence*globalStimParams.FS)... |
||
| 440 | -round(stim.toneDuration*globalStimParams.FS) ... |
||
| 441 | -round(stim.endSilence*globalStimParams.FS))... |
||
| 442 | ] |
||
| 443 | error(' trouble in stimulusCreate: signals are the wrong length ')
|
||
| 444 | end |
||
| 445 | |||
| 446 | end % component no |
||
| 447 | |||
| 448 | audio(:,ear)= combinedSignal; |
||
| 449 | |||
| 450 | % FFT |
||
| 451 | try |
||
| 452 | if globalStimParams.FFT |
||
| 453 | showFFT (audio, dt) |
||
| 454 | end |
||
| 455 | catch |
||
| 456 | end |
||
| 457 | |||
| 458 | |||
| 459 | end % ear |
||
| 460 | |||
| 461 | switch globalStimParams.ears |
||
| 462 | % normally the signals are created in appropriate ears but .ears can |
||
| 463 | % overide this to produce a mono signal. |
||
| 464 | case 'monoticL'; |
||
| 465 | % combine left and right ears to make a mono signal in the left ear |
||
| 466 | audio(:,1)=audio(:,1)+audio(:,2); |
||
| 467 | audio(:,2)=zeros(globalStimParams.nSignalPoints, 1); |
||
| 468 | |||
| 469 | case 'monoticR'; |
||
| 470 | % combine left and right ears to make a mono signal in the right ear |
||
| 471 | audio(:,2)=audio(:,1)+audio(:,2); |
||
| 472 | audio(:,1)=zeros(globalStimParams.nSignalPoints, 1); |
||
| 473 | |||
| 474 | case 'diotic'; |
||
| 475 | % combine left and right ears to make a mono signal in both ears |
||
| 476 | bothEarsCombined=audio(:,1)+audio(:,2); |
||
| 477 | audio(:,2)=bothEarsCombined; |
||
| 478 | audio(:,1)=bothEarsCombined; |
||
| 479 | |||
| 480 | otherwise |
||
| 481 | % Any other denomination produces no effect here |
||
| 482 | end |
||
| 483 | |||
| 484 | % Plotting as required |
||
| 485 | if globalStimParams.doPlot |
||
| 486 | figure(9), clf |
||
| 487 | plot(time,audio(:,1)'), hold on, |
||
| 488 | if Nears>1 |
||
| 489 | offSet=(max(audio(:,1))+max(audio(:,2)))/10; |
||
| 490 | offSet=2*offSet+max(audio(:,1))+max(audio(:,2)); |
||
| 491 | plot(time,audio(:,2)'+offSet,'r'), hold off |
||
| 492 | end |
||
| 493 | ylabel('left right')
|
||
| 494 | end |
||
| 495 | |||
| 496 | % Calibration |
||
| 497 | % all signals are divided by this correction factor |
||
| 498 | % peakAmp=globalStimParams.audioOutCorrection; % microPascals = 100 dB SPL |
||
| 499 | |||
| 500 | audio=audio/globalStimParams.audioOutCorrection; |
||
| 501 | |||
| 502 | if globalStimParams.doPlay |
||
| 503 | 35:25d53244d5c8 | rmeddis | if ispc |
| 504 | wavplay(audio,globalStimParams.FS) |
||
| 505 | else |
||
| 506 | sound(audio,globalStimParams.FS) |
||
| 507 | end |
||
| 508 | |||
| 509 | 0:f233164f4c86 | rmeddis | wavplay(audio,globalStimParams.FS) |
| 510 | end |
||
| 511 | % all Done |
||
| 512 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
||
| 513 | |||
| 514 | |||
| 515 | |||
| 516 | %---------------------------------------------------- maketone |
||
| 517 | function tone=maketone(globalStimParams, stimComponents) |
||
| 518 | % maketone generates a stimComponents tone |
||
| 519 | % tone=maketone(dt, frequencies, toneDuration, dBSPL, phases) |
||
| 520 | % tone is returned in Pascals |
||
| 521 | % frequencies is a list of frequencies |
||
| 522 | % phase is list of phases or 'sin', 'cos', 'alt' |
||
| 523 | % |
||
| 524 | % defaults: |
||
| 525 | % phase = sin |
||
| 526 | % dBSPL=56 dB SPL |
||
| 527 | |||
| 528 | dt=globalStimParams.dt; |
||
| 529 | frequencies=stimComponents.frequencies; |
||
| 530 | toneDuration=stimComponents.toneDuration; |
||
| 531 | dBSPL=stimComponents.amplitudesdB; |
||
| 532 | phases=stimComponents.phases; |
||
| 533 | |||
| 534 | if ischar(phases) |
||
| 535 | switch phases |
||
| 536 | case 'sin' |
||
| 537 | phases= zeros(1,length(frequencies)); |
||
| 538 | case 'cos' |
||
| 539 | phases= pi/2*ones(1,length(frequencies)); |
||
| 540 | case 'alt' |
||
| 541 | phases= repmat([0 pi/2], 1, floor(length(frequencies)/2)); |
||
| 542 | if length(phases)<length(frequencies) |
||
| 543 | phases=[phases 0]; |
||
| 544 | end |
||
| 545 | case {'ran', 'rand'}
|
||
| 546 | phases= 2*pi*rand(1,length(frequencies)); |
||
| 547 | end |
||
| 548 | end |
||
| 549 | |||
| 550 | if length(phases)==1, phases=repmat(phases(1), 1, length(frequencies)); end |
||
| 551 | if length(phases)<length(frequencies) |
||
| 552 | error('makeTone:phase specification must have the same length as frequencies')
|
||
| 553 | end |
||
| 554 | |||
| 555 | if length(dBSPL)==1, dBSPL=repmat(dBSPL(1), 1, length(frequencies)); end |
||
| 556 | if length(dBSPL)<length(dBSPL) |
||
| 557 | error('makeTone:dBSPL specification must have the same length as frequencies')
|
||
| 558 | end |
||
| 559 | |||
| 560 | time=dt:dt:toneDuration; |
||
| 561 | amplitudes=28e-6* 10.^(dBSPL/20); |
||
| 562 | |||
| 563 | tone=zeros(size(time)); |
||
| 564 | for i=1:length(frequencies) |
||
| 565 | frequency=frequencies(i); |
||
| 566 | phase=phases(i); |
||
| 567 | tone=tone+amplitudes(i)*sin(2*pi*frequency*time+phase); |
||
| 568 | end |
||
| 569 | |||
| 570 | % figure(1), plot(time, signal) |
||
| 571 | |||
| 572 | |||
| 573 | %---------------------------------------------------- makeOHIOtones |
||
| 574 | function stimulus=makeOHIOtones(globalStimParams, stimComponents) |
||
| 575 | |||
| 576 | % Generates a stimulus consisting of one or more 10-ms tones |
||
| 577 | 38:c2204b18f4a2 | rmeddis | % The length of the list of frequencies determines the numberof tones |
| 578 | % Tones are either presented at 10-ms intervals or simultaneously |
||
| 579 | % all tones are individually ramped |
||
| 580 | 0:f233164f4c86 | rmeddis | % Each tone has its own amplitude and its own ramp. |
| 581 | |||
| 582 | frequencies=stimComponents.frequencies; |
||
| 583 | amplitudesdB=stimComponents.amplitudesdB; |
||
| 584 | nFrequencies=length(frequencies); |
||
| 585 | |||
| 586 | 38:c2204b18f4a2 | rmeddis | if amplitudesdB==-100 |
| 587 | % catch trial |
||
| 588 | amplitudesdB=repmat(-100,1,nFrequencies); |
||
| 589 | end |
||
| 590 | |||
| 591 | 0:f233164f4c86 | rmeddis | dt=globalStimParams.dt; |
| 592 | 38:c2204b18f4a2 | rmeddis | |
| 593 | 0:f233164f4c86 | rmeddis | toneDuration=.010; |
| 594 | time=dt:dt:toneDuration; |
||
| 595 | |||
| 596 | 38:c2204b18f4a2 | rmeddis | % fixed ramp, silenceDuration, toneDuration |
| 597 | rampDuration=0.005; |
||
| 598 | 0:f233164f4c86 | rmeddis | rampTime=dt:dt:rampDuration; |
| 599 | 38:c2204b18f4a2 | rmeddis | ramp=[0.5*(1+cos(2*pi*rampTime/(2*rampDuration)+pi)) ... |
| 600 | ones(1,length(time)-length(rampTime))]; |
||
| 601 | 0:f233164f4c86 | rmeddis | ramp=ramp.*fliplr(ramp); |
| 602 | |||
| 603 | 38:c2204b18f4a2 | rmeddis | silenceDuration=0.010; |
| 604 | silenceDurationLength=round(silenceDuration/dt); |
||
| 605 | initialSilence=zeros(1,silenceDurationLength); |
||
| 606 | |||
| 607 | silenceToneDuration=toneDuration + silenceDuration; |
||
| 608 | silenceToneDurationLength=round(silenceToneDuration/dt); |
||
| 609 | |||
| 610 | % OHIO spect produces simultaneous tones |
||
| 611 | if strcmp(stimComponents.OHIOtype,'OHIOspect') |
||
| 612 | totalDuration=silenceToneDuration; |
||
| 613 | else |
||
| 614 | totalDuration=silenceToneDuration*nFrequencies; |
||
| 615 | end |
||
| 616 | |||
| 617 | totalDurationLength=round(totalDuration/dt); |
||
| 618 | stimulus=zeros(1,totalDurationLength); |
||
| 619 | toneBeginPTR=1; |
||
| 620 | 0:f233164f4c86 | rmeddis | |
| 621 | for i=1:nFrequencies |
||
| 622 | frequency=frequencies(i); |
||
| 623 | dBSPL=amplitudesdB(i); |
||
| 624 | amplitude=28e-6* 10.^(dBSPL/20); |
||
| 625 | tone=amplitude*sin(2*pi*frequency*time); |
||
| 626 | tone=tone.*ramp; |
||
| 627 | 38:c2204b18f4a2 | rmeddis | % stimulus is normally zeros except for OHIOspect |
| 628 | stimulus(toneBeginPTR:toneBeginPTR+silenceToneDurationLength-1)=... |
||
| 629 | [initialSilence tone]+... |
||
| 630 | stimulus(toneBeginPTR:toneBeginPTR+silenceToneDurationLength-1); |
||
| 631 | if ~strcmp(stimComponents.OHIOtype,'OHIOspect') |
||
| 632 | toneBeginPTR=toneBeginPTR+silenceToneDurationLength; |
||
| 633 | end |
||
| 634 | 0:f233164f4c86 | rmeddis | end |
| 635 | % figure(2), plot( stimulus') |
||
| 636 | |||
| 637 | |||
| 638 | %---------------------------------------------------- makeFMtone |
||
| 639 | function tone=makeFMtone(globalStimParams, stimComponents) |
||
| 640 | % maketone generates a stimComponents tone |
||
| 641 | % tone=maketone(dt, frequencies, toneDuration, dBSPL, phases) |
||
| 642 | % tone is returned in Pascals |
||
| 643 | % frequencies is a list of frequencies |
||
| 644 | % phase is list of phases or 'sin', 'cos', 'alt' |
||
| 645 | % |
||
| 646 | % defaults: |
||
| 647 | % phase = sin |
||
| 648 | % dBSPL=56 dB SPL |
||
| 649 | |||
| 650 | dt=globalStimParams.dt; |
||
| 651 | frequencies=stimComponents.frequencies; |
||
| 652 | toneDuration=stimComponents.toneDuration; |
||
| 653 | dBSPL=stimComponents.amplitudesdB; |
||
| 654 | phases=stimComponents.phases; |
||
| 655 | fmDepth=stimComponents.fmDepth; |
||
| 656 | fmFrequency=stimComponents.fmFrequency; |
||
| 657 | |||
| 658 | if ischar(phases) |
||
| 659 | switch phases |
||
| 660 | case 'sin' |
||
| 661 | phases= zeros(1,length(frequencies)); |
||
| 662 | case 'cos' |
||
| 663 | phases= pi/2*ones(1,length(frequencies)); |
||
| 664 | case 'alt' |
||
| 665 | phases= repmat([0 pi/2], 1, floor(length(frequencies)/2)); |
||
| 666 | if length(phases)<length(frequencies) |
||
| 667 | phases=[phases 0]; |
||
| 668 | end |
||
| 669 | end |
||
| 670 | end |
||
| 671 | |||
| 672 | if length(phases)==1, phases=repmat(phases(1), 1, length(frequencies)); end |
||
| 673 | if length(phases)<length(frequencies) |
||
| 674 | error('makeTone:phase specification must have the same length as frequencies')
|
||
| 675 | end |
||
| 676 | |||
| 677 | if length(dBSPL)==1, dBSPL=repmat(dBSPL(1), 1, length(frequencies)); end |
||
| 678 | if length(dBSPL)<length(dBSPL) |
||
| 679 | error('makeTone:dBSPL specification must have the same length as frequencies')
|
||
| 680 | end |
||
| 681 | |||
| 682 | time=dt:dt:toneDuration; |
||
| 683 | amplitudes=28e-6* 10.^(dBSPL/20); |
||
| 684 | |||
| 685 | tone=zeros(size(time)); |
||
| 686 | for i=1:length(frequencies) |
||
| 687 | frequency=frequencies(i); |
||
| 688 | phase=phases(i); |
||
| 689 | tone=tone+amplitudes(i)*sin(2*pi*frequency*time+phase + fmDepth*sin(2*pi*fmFrequency*time)); |
||
| 690 | end |
||
| 691 | |||
| 692 | % figure(1), plot(time, signal) |
||
| 693 | |||
| 694 | %----------------------------------------------------makeTransposedStimulus |
||
| 695 | function [transposedStimulus]=makeTransposedStimulus(globalStimParams, stim) |
||
| 696 | % globalStimParams.FS=100000; |
||
| 697 | % globalStimParams.overallDuration=.1; % s |
||
| 698 | % globalStimParams.doPlot=1; |
||
| 699 | % |
||
| 700 | % stim.type='transposedStimulus'; |
||
| 701 | % stim.phases='sin'; |
||
| 702 | % stim.toneDuration=.05;; |
||
| 703 | % stim.frequencies=500; |
||
| 704 | % stim.amplitudesdB=50; |
||
| 705 | % stim.beginSilence=.01; |
||
| 706 | % stim.endSilence=-1; |
||
| 707 | % stim.rampOnDur=.002; |
||
| 708 | % stim.rampOffDur=-1; |
||
| 709 | % |
||
| 710 | % stim.transCarrierFreq=4000; |
||
| 711 | % stim.transModFreq=100; |
||
| 712 | |||
| 713 | transposedStimulus=[]; |
||
| 714 | % make envelope of transposed tone |
||
| 715 | for i=1:length(stim.transModFreq) |
||
| 716 | stim.type='tone'; |
||
| 717 | stim.frequencies=stim.transModFreq(i); |
||
| 718 | stim.endsilence=-1; stim.beginSilence=0; |
||
| 719 | [envelope, msg]=stimulusCreate(globalStimParams, stim); % NB recursive |
||
| 720 | envelope=envelope(:,1); % mono |
||
| 721 | % HW rectify |
||
| 722 | envelope(find(envelope<0))=0; |
||
| 723 | % LP filter |
||
| 724 | maxEnvelope=max(envelope); |
||
| 725 | envelope=UTIL_Butterworth (envelope, globalStimParams.dt, 10, .2*stim.transModFreq(i), 2); |
||
| 726 | envelope=envelope*maxEnvelope/max(envelope); |
||
| 727 | |||
| 728 | % make the carrier |
||
| 729 | stim.frequencies=stim.transCarrierFreq(i); |
||
| 730 | stim.endsilence=-1; stim.beginSilence=0; |
||
| 731 | [audio, msg]=stimulusCreate(globalStimParams, stim); |
||
| 732 | carrier=audio(:,1); |
||
| 733 | x= (carrier.*envelope)'; |
||
| 734 | % base amplitude on peak of unmodulated carrier |
||
| 735 | x=x/max(carrier); |
||
| 736 | transposedStimulus=[transposedStimulus; x]; |
||
| 737 | end |
||
| 738 | transposedStimulus=sum(transposedStimulus,1); |
||
| 739 | % clf,plot(transposedStimulus) |
||
| 740 | |||
| 741 | %--------------------------------------------------------------------makeClicks |
||
| 742 | function clickTrain=makeClicks(globalStimParams, stimComponents) |
||
| 743 | % makeClicks(F0, clickTimes, duration, FS); |
||
| 744 | % F0 specifies the repetition rate of the click sequence |
||
| 745 | % If F0=-1, a single click is generated at the start of the duration of the signal |
||
| 746 | % |
||
| 747 | % clickTimes a are fractions of the period |
||
| 748 | % and specify when the click appears in the period |
||
| 749 | % A click time of 1 is reset to zero. |
||
| 750 | % if the clicktime plus the click width is greater than the period, no click is generated |
||
| 751 | % clicks are treated as 20 microPascal high before amplification |
||
| 752 | % unless otherwise specified in stimComponents.clickHeight |
||
| 753 | % click width is dt unless specified in stimComponents.clickWidth |
||
| 754 | % |
||
| 755 | % for regular pulse train set clicktimes=1 or zero; |
||
| 756 | % FS is the sampling interval; |
||
| 757 | % CarlyonClickTrain(100, [.4 1], 40, 441000); |
||
| 758 | |||
| 759 | FS=globalStimParams.FS; % sample rate |
||
| 760 | dt=1/FS; |
||
| 761 | |||
| 762 | try,clickWidth=stimComponents.clickWidth;catch, clickWidth=dt; end |
||
| 763 | try,clickHeight=stimComponents.clickHeight; catch, clickHeight=28e-6 * 10^(stimComponents.amplitudesdB/20); end |
||
| 764 | try, clickTimes=stimComponents.clickTimes; catch, clickTimes=1; end |
||
| 765 | |||
| 766 | % clickTimes are the times in a cycle that the click |
||
| 767 | % occurs |
||
| 768 | checkClickTimes=clickTimes-1; |
||
| 769 | if max(checkClickTimes)>1 |
||
| 770 | msg= 'click times must be <= than 1 (period)'; |
||
| 771 | return |
||
| 772 | end |
||
| 773 | |||
| 774 | if clickWidth>=1/stimComponents.clickRepeatFrequency |
||
| 775 | msg= 'click width is too great for frequency'; |
||
| 776 | return |
||
| 777 | end |
||
| 778 | |||
| 779 | duration=stimComponents.toneDuration; |
||
| 780 | totalLength=round(stimComponents.toneDuration/dt); |
||
| 781 | F0=stimComponents.clickRepeatFrequency; |
||
| 782 | F0=round(F0/dt)*dt; |
||
| 783 | if F0==-1 % single click required |
||
| 784 | F0=1/duration; |
||
| 785 | repetitions=1; |
||
| 786 | clickStartTimes=1; %clicktimes are fractions of a period |
||
| 787 | else |
||
| 788 | repetitions=round(F0*duration)-1; |
||
| 789 | duration=repetitions/F0; |
||
| 790 | clickStartTimes=clickTimes; |
||
| 791 | end |
||
| 792 | % if a clickTime=1 (end of duty period) set it to the beginning |
||
| 793 | clickStartTimes(clickStartTimes==1)=0; |
||
| 794 | |||
| 795 | period=1/F0; |
||
| 796 | time=dt:dt:period; |
||
| 797 | nPoints=length(time); |
||
| 798 | signal=zeros(1,nPoints); |
||
| 799 | dBSPL=stimComponents.amplitudesdB; |
||
| 800 | |||
| 801 | % compute click train for a single cycle |
||
| 802 | clickWidthNpoints=round(clickWidth*FS); |
||
| 803 | for i=1:length(clickStartTimes) |
||
| 804 | % if clickStartTimes(i)<clickWidth |
||
| 805 | % clickStartTimes(i)=dt; |
||
| 806 | % end |
||
| 807 | clickTime=round(period*clickStartTimes(i)/dt -dt); |
||
| 808 | % clicks are treated as 20 microPascal high |
||
| 809 | if clickTime+clickWidthNpoints<length(signal) |
||
| 810 | signal(clickTime+1:clickTime+clickWidthNpoints)=clickHeight; |
||
| 811 | end |
||
| 812 | end |
||
| 813 | |||
| 814 | clickTrain=repmat(signal, 1, repetitions); |
||
| 815 | |||
| 816 | if length(clickTrain)>totalLength |
||
| 817 | clickTrain=clickTrain(1:totalLength); |
||
| 818 | elseif length(clickTrain)<totalLength |
||
| 819 | timeToAdd=zeros(1,round((totalLength-length(clickTrain)))); |
||
| 820 | clickTrain=[clickTrain timeToAdd]; |
||
| 821 | % figure (1), plot(clickTrain) |
||
| 822 | end |
||
| 823 | |||
| 824 | %----------------------------------------------------------------makePressnitzerClicks |
||
| 825 | function signal=makePressnitzerClicks(globalStimParams, stimComponents) |
||
| 826 | % PressnitzerClicks(k,duration,dt) |
||
| 827 | % Generates a sequence of clicks with intervals kxx |
||
| 828 | % where x= rand*k/2 |
||
| 829 | % This is not the same as Kaernbach and Demany clicks |
||
| 830 | |||
| 831 | FS=globalStimParams.FS; % sample rate |
||
| 832 | dt=1/FS; |
||
| 833 | |||
| 834 | % obligatory parameter |
||
| 835 | try |
||
| 836 | k=stimComponents.k; |
||
| 837 | catch |
||
| 838 | error('PressnitzerClicks: field ''k'' is missing from stimcomponent')
|
||
| 839 | end |
||
| 840 | |||
| 841 | % optional parameters |
||
| 842 | if isfield(stimComponents,'clickWidth') |
||
| 843 | clickWidth=stimComponents.clickWidth; |
||
| 844 | else |
||
| 845 | clickWidth=dt; |
||
| 846 | end |
||
| 847 | clickWidthNpoints=round(clickWidth*FS); |
||
| 848 | |||
| 849 | if isfield(stimComponents,'clickHeight') |
||
| 850 | clickHeight=stimComponents.clickHeight; |
||
| 851 | else |
||
| 852 | clickHeight=28e-6 * 10^(stimComponents.amplitudesdB/20); |
||
| 853 | end |
||
| 854 | |||
| 855 | duration=stimComponents.toneDuration; |
||
| 856 | |||
| 857 | signalLength=round(duration/dt); |
||
| 858 | signal=zeros(1,signalLength); |
||
| 859 | kInterval=round(k/dt); |
||
| 860 | halfk=k/2; |
||
| 861 | signal(1)=clickHeight; |
||
| 862 | timeIdx=0; |
||
| 863 | while timeIdx<signalLength |
||
| 864 | % first interval = k |
||
| 865 | clickTime=timeIdx+kInterval; |
||
| 866 | signal(clickTime:clickTime+clickWidthNpoints)=clickHeight; |
||
| 867 | timeIdx=timeIdx+kInterval; |
||
| 868 | |||
| 869 | % second interval = 0 : k/2 |
||
| 870 | intervalx1=round(rand*halfk/dt); |
||
| 871 | clickTime=timeIdx+intervalx1; |
||
| 872 | signal(clickTime:clickTime+clickWidthNpoints)=clickHeight; |
||
| 873 | timeIdx=timeIdx+intervalx1; |
||
| 874 | |||
| 875 | % third interval = 0 : k/2 |
||
| 876 | intervalx1=round(rand*halfk/dt); |
||
| 877 | clickTime=timeIdx+intervalx1; |
||
| 878 | signal(clickTime:clickTime+clickWidthNpoints)=clickHeight; |
||
| 879 | timeIdx=timeIdx+intervalx1; |
||
| 880 | end |
||
| 881 | |||
| 882 | signal=signal(1:signalLength); |
||
| 883 | % figure(1), plot(dt:dt:duration,signal) |
||
| 884 | |||
| 885 | %----------------------------------------------------------------makePressnitzerABXClicks |
||
| 886 | function signal=makePressnitzerABxClicks(globalStimParams, stimComponents) |
||
| 887 | % Generates a sequence of clicks with intervals ABx |
||
| 888 | % AB interval is 2*k |
||
| 889 | % where A= rand* k |
||
| 890 | % B= k-A |
||
| 891 | % x= k/2 |
||
| 892 | % These are second order clicks |
||
| 893 | |||
| 894 | FS=globalStimParams.FS; % sample rate |
||
| 895 | dt=1/FS; |
||
| 896 | |||
| 897 | % obligatory parameter |
||
| 898 | try |
||
| 899 | k=stimComponents.k; |
||
| 900 | catch |
||
| 901 | error('PressnitzerClicks: field ''k'' is missing from stimcomponent')
|
||
| 902 | end |
||
| 903 | |||
| 904 | % optional parameters |
||
| 905 | if isfield(stimComponents,'clickWidth') |
||
| 906 | clickWidth=stimComponents.clickWidth; |
||
| 907 | else |
||
| 908 | clickWidth=dt; |
||
| 909 | end |
||
| 910 | clickWidthNpoints=round(clickWidth*FS); |
||
| 911 | |||
| 912 | if isfield(stimComponents,'clickHeight') |
||
| 913 | clickHeight=stimComponents.clickHeight; |
||
| 914 | else |
||
| 915 | clickHeight=28e-6 * 10^(stimComponents.amplitudesdB/20); |
||
| 916 | end |
||
| 917 | |||
| 918 | duration=stimComponents.toneDuration; |
||
| 919 | |||
| 920 | signalLength=round(duration/dt); |
||
| 921 | signal=zeros(1,2*signalLength); % allow for overrun |
||
| 922 | ABinterval=k/dt; % i.e. the number of dt steps |
||
| 923 | randomInterval=ABinterval/2; |
||
| 924 | signal(1)=clickHeight; |
||
| 925 | time=0; |
||
| 926 | while time<signalLength |
||
| 927 | % first interval = A |
||
| 928 | intervalA=rand*ABinterval; |
||
| 929 | clickTime=round(time+intervalA)+1; % +1 avoids zero index |
||
| 930 | signal(clickTime:clickTime+clickWidthNpoints)=clickHeight; |
||
| 931 | time=time+intervalA; |
||
| 932 | |||
| 933 | % second interval = B |
||
| 934 | intervalB=ABinterval-intervalA; |
||
| 935 | clickTime=round(time+intervalB)+1; |
||
| 936 | signal(clickTime:clickTime+clickWidthNpoints-1)=clickHeight; |
||
| 937 | time=time+intervalB; |
||
| 938 | |||
| 939 | % third interval = 0 : k/2 |
||
| 940 | intervalx1=rand*randomInterval; % mean random interval=k |
||
| 941 | clickTime=round(time+intervalx1)+1; |
||
| 942 | signal(clickTime:clickTime+clickWidthNpoints-1)=clickHeight; |
||
| 943 | time=time+intervalx1; |
||
| 944 | end |
||
| 945 | |||
| 946 | signal=signal(1:signalLength); |
||
| 947 | % figure(1), plot(dt:dt:duration,signal) |
||
| 948 | |||
| 949 | %-----------------------------------------------------makeABxClicks |
||
| 950 | function signal=makeABxClicks(globalStimParams, stimComponents) |
||
| 951 | % Generates a sequence of clicks with intervals ABx |
||
| 952 | % AB interval is 2*k |
||
| 953 | % where A= rand* k |
||
| 954 | % B= k-A |
||
| 955 | % x= rand*2*k |
||
| 956 | % These are second order clicks |
||
| 957 | |||
| 958 | FS=globalStimParams.FS; % sample rate |
||
| 959 | dt=1/FS; |
||
| 960 | |||
| 961 | % obligatory parameter |
||
| 962 | try |
||
| 963 | k=stimComponents.k; |
||
| 964 | catch |
||
| 965 | error('PressnitzerClicks: field ''k'' is missing from stimcomponent')
|
||
| 966 | end |
||
| 967 | |||
| 968 | % optional parameters |
||
| 969 | if isfield(stimComponents,'clickWidth') |
||
| 970 | clickWidth=stimComponents.clickWidth; |
||
| 971 | else |
||
| 972 | clickWidth=dt; |
||
| 973 | end |
||
| 974 | clickWidthNpoints=round(clickWidth*FS); |
||
| 975 | |||
| 976 | if isfield(stimComponents,'clickHeight') |
||
| 977 | clickHeight=stimComponents.clickHeight; |
||
| 978 | else |
||
| 979 | clickHeight=28e-6 * 10^(stimComponents.amplitudesdB/20); |
||
| 980 | end |
||
| 981 | |||
| 982 | duration=stimComponents.toneDuration; |
||
| 983 | |||
| 984 | signalLength=round(duration/dt); |
||
| 985 | signal=zeros(1,2*signalLength); % allow for overrun |
||
| 986 | ABinterval=2*k/dt; |
||
| 987 | randomInterval=ABinterval; |
||
| 988 | signal(1)=clickHeight; |
||
| 989 | timeIdx=0; |
||
| 990 | while timeIdx<signalLength |
||
| 991 | % first interval = A |
||
| 992 | intervalA=round(rand*ABinterval); |
||
| 993 | clickTime=timeIdx+intervalA+1; |
||
| 994 | signal(clickTime:clickTime+clickWidthNpoints-1)=clickHeight; |
||
| 995 | timeIdx=timeIdx+intervalA; |
||
| 996 | |||
| 997 | % second interval = B |
||
| 998 | intervalB=round(ABinterval-intervalA); |
||
| 999 | clickTime=timeIdx+intervalB; |
||
| 1000 | signal(clickTime:clickTime+clickWidthNpoints-1)=clickHeight; |
||
| 1001 | timeIdx=timeIdx+intervalB; |
||
| 1002 | |||
| 1003 | % third interval = 0 : k |
||
| 1004 | intervalx1=round(rand*randomInterval); % mean random interval=k |
||
| 1005 | clickTime=timeIdx+intervalx1; |
||
| 1006 | signal(clickTime:clickTime+clickWidthNpoints-1)=clickHeight; |
||
| 1007 | timeIdx=timeIdx+intervalx1; |
||
| 1008 | end |
||
| 1009 | |||
| 1010 | signal=signal(1:signalLength); |
||
| 1011 | % figure(1), plot(dt:dt:duration,signal) |
||
| 1012 | |||
| 1013 | %----------------------------------------------------------------makeYostClicks |
||
| 1014 | function signal=makeYostClicks(globalStimParams, stimComponents) |
||
| 1015 | % Generates a shuffled sequence of clicks with intervals kxxxx |
||
| 1016 | % where max(x)= 2*k |
||
| 1017 | % and there are n occurrences of x |
||
| 1018 | % this section requires: |
||
| 1019 | % stimComponents.k |
||
| 1020 | % stimComponents.nXs |
||
| 1021 | % stimComponents.toneDuration |
||
| 1022 | % optional: |
||
| 1023 | % stimComponents.clickWidth %useful because width depends on dt |
||
| 1024 | % stimComponents.clickHeight %best left to amplitude rescaling later |
||
| 1025 | |||
| 1026 | FS=globalStimParams.FS; % sample rate |
||
| 1027 | dt=1/FS; |
||
| 1028 | |||
| 1029 | % obligatory parameters |
||
| 1030 | try |
||
| 1031 | k=stimComponents.k; |
||
| 1032 | catch |
||
| 1033 | error('makeYostClicks: field ''k'' is missing from stimComponents')
|
||
| 1034 | end |
||
| 1035 | |||
| 1036 | try |
||
| 1037 | nXs=stimComponents.nXs; |
||
| 1038 | catch |
||
| 1039 | error('makeYostClicks: field ''nXs'' is missing from stimComponents')
|
||
| 1040 | end |
||
| 1041 | |||
| 1042 | try |
||
| 1043 | shuffled=stimComponents.shuffled; |
||
| 1044 | catch |
||
| 1045 | error('makeYostClicks: field ''shuffled'' is missing from stimComponents')
|
||
| 1046 | end |
||
| 1047 | |||
| 1048 | try |
||
| 1049 | duration=stimComponents.toneDuration; |
||
| 1050 | catch |
||
| 1051 | error('makeYostClicks: field ''toneDuration'' is missing from stimComponents')
|
||
| 1052 | end |
||
| 1053 | |||
| 1054 | % optional parameters |
||
| 1055 | if isfield(stimComponents,'clickWidth') |
||
| 1056 | clickWidth=stimComponents.clickWidth; |
||
| 1057 | else |
||
| 1058 | clickWidth=dt; |
||
| 1059 | end |
||
| 1060 | clickWidthNpoints=round(clickWidth*FS); |
||
| 1061 | |||
| 1062 | if isfield(stimComponents,'clickHeight') |
||
| 1063 | clickHeight=stimComponents.clickHeight; |
||
| 1064 | else |
||
| 1065 | clickHeight=28e-6 * 10^(stimComponents.amplitudesdB/20); |
||
| 1066 | end |
||
| 1067 | |||
| 1068 | kInterval=round(k/dt); |
||
| 1069 | twoK=k*2; % max width of x interval |
||
| 1070 | |||
| 1071 | signalLength=round(duration/dt); |
||
| 1072 | signal=zeros(1,signalLength); |
||
| 1073 | |||
| 1074 | timeIdx=0; |
||
| 1075 | intervalCount=0; |
||
| 1076 | while timeIdx<signalLength |
||
| 1077 | timeIdx=timeIdx+kInterval; |
||
| 1078 | if timeIdx>signalLength, break,end |
||
| 1079 | intervalCount=intervalCount+1; |
||
| 1080 | intervals(intervalCount)=kInterval; |
||
| 1081 | |||
| 1082 | % repeat x intervals as required |
||
| 1083 | if nXs>0 |
||
| 1084 | for nX=1:nXs |
||
| 1085 | xInterval=round(rand*twoK/dt); |
||
| 1086 | timeIdx=timeIdx+xInterval; |
||
| 1087 | if timeIdx>signalLength, break,end |
||
| 1088 | intervalCount=intervalCount+1; |
||
| 1089 | intervals(intervalCount)=xInterval; |
||
| 1090 | end |
||
| 1091 | end |
||
| 1092 | if timeIdx>signalLength, break,end |
||
| 1093 | end |
||
| 1094 | |||
| 1095 | % shuffle intervals |
||
| 1096 | if shuffled |
||
| 1097 | randomNumbers=rand(1,length(intervals)); |
||
| 1098 | [randomNumbers idx]=sort(randomNumbers); |
||
| 1099 | intervals=intervals(idx); |
||
| 1100 | idx=intervals>0; |
||
| 1101 | intervals=intervals(idx); |
||
| 1102 | end |
||
| 1103 | |||
| 1104 | intervalCount=length(intervals); |
||
| 1105 | signal(1)=clickHeight; |
||
| 1106 | clickTime=0; |
||
| 1107 | for i=1:intervalCount |
||
| 1108 | clickTime=clickTime+intervals(i); |
||
| 1109 | signal(clickTime:clickTime+clickWidthNpoints)=clickHeight; |
||
| 1110 | end |
||
| 1111 | signal=signal(1:signalLength); |
||
| 1112 | % figure(1), plot(dt:dt:duration,signal) |
||
| 1113 | |||
| 1114 | %--------------------------------------------------------------------makeKxxClicks |
||
| 1115 | function signal=makeKxxClicks(globalStimParams, stimComponents) |
||
| 1116 | % Click train consists of kkxxx.. sequences |
||
| 1117 | % k is the duration of a fixed interval (seconds) |
||
| 1118 | % random intervals are distributed 0 : 2* k (NB not like Pressnitzer clicks) |
||
| 1119 | % nKs is the number of successive 'k' intervals |
||
| 1120 | % nXs is the number of random intervals between k sequences |
||
| 1121 | % sequences of 3 x intervals > k are replaced with new sequences |
||
| 1122 | % shuffled causes all intervals to be reordered randomly |
||
| 1123 | % NB 1/k is the mean click rate |
||
| 1124 | |||
| 1125 | FS=globalStimParams.FS; % sample rate |
||
| 1126 | dt=1/FS; |
||
| 1127 | |||
| 1128 | try |
||
| 1129 | k=stimComponents.k; % duration (s) of fixed intervals |
||
| 1130 | catch |
||
| 1131 | error('makeYostClicks: field ''k'' is missing from stimComponents')
|
||
| 1132 | end |
||
| 1133 | |||
| 1134 | try |
||
| 1135 | duration=stimComponents.toneDuration; |
||
| 1136 | catch |
||
| 1137 | error('makeYostClicks: field ''duration'' is missing from stimComponents')
|
||
| 1138 | end |
||
| 1139 | |||
| 1140 | if isfield(stimComponents,'clickWidth') |
||
| 1141 | clickWidth=stimComponents.clickWidth; |
||
| 1142 | else |
||
| 1143 | clickWidth=dt; |
||
| 1144 | end |
||
| 1145 | |||
| 1146 | if isfield(stimComponents,'clickHeight') |
||
| 1147 | clickHeight=stimComponents.clickHeight; |
||
| 1148 | else |
||
| 1149 | clickHeight=28e-6 * 10^(stimComponents.amplitudesdB/20); |
||
| 1150 | end |
||
| 1151 | |||
| 1152 | |||
| 1153 | if isfield(stimComponents,'order') |
||
| 1154 | order=stimComponents.order; |
||
| 1155 | else |
||
| 1156 | order=1; |
||
| 1157 | end |
||
| 1158 | |||
| 1159 | if isfield(stimComponents,'nKs') |
||
| 1160 | nKs=stimComponents.nKs; |
||
| 1161 | else |
||
| 1162 | nKs=1; |
||
| 1163 | end |
||
| 1164 | |||
| 1165 | if isfield(stimComponents,'nXs') |
||
| 1166 | nXs=stimComponents.nXs; |
||
| 1167 | else |
||
| 1168 | nXs=1; |
||
| 1169 | end |
||
| 1170 | |||
| 1171 | if isfield(stimComponents,'shuffled') |
||
| 1172 | shuffled=stimComponents.shuffled; |
||
| 1173 | else |
||
| 1174 | shuffled=1; |
||
| 1175 | end |
||
| 1176 | |||
| 1177 | kLength=round(k/dt); % fixed interval |
||
| 1178 | xLength=2*kLength; % maximum random interval |
||
| 1179 | requiredSignalLength=round(duration/dt); |
||
| 1180 | intervalsPerCycle=(nKs+nXs); |
||
| 1181 | cycleLength=nKs*kLength+nXs*xLength; |
||
| 1182 | % more cycles to allow for uncertainty |
||
| 1183 | nCycles=5*round(requiredSignalLength/cycleLength); |
||
| 1184 | nIntervals=nCycles*intervalsPerCycle; |
||
| 1185 | |||
| 1186 | % random intervals |
||
| 1187 | if nXs>0 |
||
| 1188 | xIntervals=floor(rand(1,nIntervals)*2*kLength); |
||
| 1189 | % weed out triple intervals > 2*k |
||
| 1190 | rogues=1; |
||
| 1191 | while sum(rogues) |
||
| 1192 | y=(xIntervals>kLength); |
||
| 1193 | rogues=(sum([y(1:end-2)' y(2:end-1)' y(3:end)'],2)>2); |
||
| 1194 | xIntervals(rogues)=floor(rand*2*kLength); |
||
| 1195 | end |
||
| 1196 | xIntervals=reshape(xIntervals,nCycles,intervalsPerCycle); |
||
| 1197 | else |
||
| 1198 | xIntervals=[]; |
||
| 1199 | end |
||
| 1200 | |||
| 1201 | % insert constrained (k) intervals |
||
| 1202 | if nKs>0 |
||
| 1203 | switch order |
||
| 1204 | case 1 |
||
| 1205 | kIntervals=floor(ones(nCycles,nKs)*kLength); |
||
| 1206 | case 2 |
||
| 1207 | nKs=1; % force this |
||
| 1208 | kIntervals=floor(rand(nCycles,1)*kLength); |
||
| 1209 | kIntervals=[kIntervals kLength-kIntervals]; |
||
| 1210 | end |
||
| 1211 | else |
||
| 1212 | kIntervals=[]; |
||
| 1213 | end |
||
| 1214 | |||
| 1215 | % combine fixed and random |
||
| 1216 | intervals=[kIntervals xIntervals(:,nKs+1:end)]; |
||
| 1217 | % make a single array; |
||
| 1218 | [r c]=size(intervals); |
||
| 1219 | intervals=reshape(intervals',1,r*c); |
||
| 1220 | |||
| 1221 | % shuffle intervals |
||
| 1222 | if shuffled |
||
| 1223 | randomNumbers=rand(1,length(intervals)); |
||
| 1224 | [randomNumbers idx]=sort(randomNumbers); |
||
| 1225 | intervals=intervals(idx); |
||
| 1226 | idx=intervals>0; |
||
| 1227 | intervals=intervals(idx); |
||
| 1228 | end |
||
| 1229 | |||
| 1230 | % convert intervals to clicks |
||
| 1231 | clickTimes=cumsum(intervals); |
||
| 1232 | signal(clickTimes)=clickHeight; |
||
| 1233 | signal=signal(1:requiredSignalLength); |
||
| 1234 | % figure(1), clf, plot(signal) |
||
| 1235 | |||
| 1236 | |||
| 1237 | |||
| 1238 | %--------------------------------------------------------------------makeNoise |
||
| 1239 | function noise=makeNoise(globalStimParams, stimComponents) |
||
| 1240 | % FS in Hz, noiseDuration in s, delay in s; |
||
| 1241 | % noise is returned with overall level dB(rms) = amplitudesdB |
||
| 1242 | % |
||
| 1243 | % % You need |
||
| 1244 | % |
||
| 1245 | % stim.type='noise'; % or 'IRN', or 'pinkNoise' |
||
| 1246 | % stim.toneDuration=.05; |
||
| 1247 | % stim.amplitudesdB=50; |
||
| 1248 | % stim.beginSilence=.01; |
||
| 1249 | % stim.endSilence=-1; |
||
| 1250 | % stim.rampOnDur=.002; |
||
| 1251 | % stim.rampOffDur=-1; |
||
| 1252 | % |
||
| 1253 | % % Mandatory structure fields |
||
| 1254 | % globalStimParams.FS=100000; |
||
| 1255 | % globalStimParams.overallDuration=.1; % s |
||
| 1256 | % globalStimParams.doPlot=1; |
||
| 1257 | % globalStimParams.doPlay=1; |
||
| 1258 | % |
||
| 1259 | % [audio, msg]=stimulusCreate(globalStimParams, stim, ); |
||
| 1260 | % |
||
| 1261 | % % local |
||
| 1262 | % stim.type='noise'; % or 'IRN' |
||
| 1263 | % |
||
| 1264 | FS=globalStimParams.FS; |
||
| 1265 | noiseDuration= stimComponents.toneDuration; |
||
| 1266 | npts=round(noiseDuration*FS); |
||
| 1267 | noise=randn(1,npts); % NB randn (normally distributed) |
||
| 1268 | |||
| 1269 | switch stimComponents.type |
||
| 1270 | case 'pinkNoise' |
||
| 1271 | % noise=UTIL_lowpassFilterFreq(noise, 100, 1/FS); |
||
| 1272 | noise=UTIL_bandPassFilter(noise, 1, 100, 200, 1/FS,[]); |
||
| 1273 | end |
||
| 1274 | |||
| 1275 | rms=(mean(noise.^2)^.5); %should be 20 microPascals for 0 dB SPL |
||
| 1276 | adjust=20e-6/rms; |
||
| 1277 | noise=noise*adjust; |
||
| 1278 | rms=(mean(noise.^2)^.5); |
||
| 1279 | amplitude=10.^(stimComponents.amplitudesdB/20); |
||
| 1280 | noise=amplitude*noise; |
||
| 1281 | % rms=(mean(noise.^2)^.5); |
||
| 1282 | % dBnoise=20*log10(rms/20e-6) |
||
| 1283 | |||
| 1284 | |||
| 1285 | %--------------------------------------------------------------------makeWhiteNoise |
||
| 1286 | function noise=makeWhiteNoise(globalStimParams, stimComponents) |
||
| 1287 | % FS in Hz, noiseDuration in s, delay in s; |
||
| 1288 | % noise is bandpass filtered between 100 and 10000 Hz |
||
| 1289 | % spectrum level (dB/Hz) is 40 dB below nominal level. |
||
| 1290 | % noise is returned with dB(rms) = amplitudesdB |
||
| 1291 | % |
||
| 1292 | % % You need |
||
| 1293 | % |
||
| 1294 | % stim.type='noise'; % or 'IRN', or 'pinkNoise' |
||
| 1295 | % stim.toneDuration=.05; |
||
| 1296 | % stim.amplitudesdB=50; |
||
| 1297 | % stim.beginSilence=.01; |
||
| 1298 | % stim.endSilence=-1; |
||
| 1299 | % stim.rampOnDur=.002; |
||
| 1300 | % stim.rampOffDur=-1; |
||
| 1301 | % |
||
| 1302 | % % Mandatory structure fields |
||
| 1303 | % globalStimParams.FS=100000; |
||
| 1304 | % globalStimParams.overallDuration=.1; % s |
||
| 1305 | % globalStimParams.doPlot=1; |
||
| 1306 | % globalStimParams.doPlay=1; |
||
| 1307 | % |
||
| 1308 | % [audio, msg]=stimulusCreate(globalStimParams, stim, ); |
||
| 1309 | % |
||
| 1310 | % % local |
||
| 1311 | % stim.type='noise'; % or 'IRN' |
||
| 1312 | % |
||
| 1313 | FS=globalStimParams.FS; |
||
| 1314 | noiseDuration= stimComponents.toneDuration; |
||
| 1315 | npts=round(noiseDuration*FS); |
||
| 1316 | noise=randn(1,npts); |
||
| 1317 | |||
| 1318 | noise=UTIL_bandPassFilter (noise, 6, 100, 10000, 1/FS, []); |
||
| 1319 | |||
| 1320 | rms=(mean(noise.^2)^.5); %should be 20 microPascals for 0 dB SPL |
||
| 1321 | adjust=20e-6/rms; |
||
| 1322 | noise=noise*adjust; |
||
| 1323 | rms=(mean(noise.^2)^.5); |
||
| 1324 | amplitude=10.^(stimComponents.amplitudesdB/20); |
||
| 1325 | noise=amplitude*noise; |
||
| 1326 | % rms=(mean(noise.^2)^.5); |
||
| 1327 | % dBnoise=20*log10(rms/20e-6) |
||
| 1328 | |||
| 1329 | |||
| 1330 | %-----------------------------------------------------------------makeIRN |
||
| 1331 | function noise=makeIRN(globalStimParams, stimComponents) |
||
| 1332 | % FS in Hz, noiseDuration in s, delay in s; |
||
| 1333 | % noise is returned with dB(rms) = amplitudesdB |
||
| 1334 | % |
||
| 1335 | % % You need |
||
| 1336 | % |
||
| 1337 | % stim.type='noise'; % or 'IRN', or 'pinkNoise' |
||
| 1338 | % stim.toneDuration=.05; |
||
| 1339 | % stim.amplitudesdB=50; |
||
| 1340 | % stim.beginSilence=.01; |
||
| 1341 | % stim.endSilence=-1; |
||
| 1342 | % stim.rampOnDur=.002; |
||
| 1343 | % stim.rampOffDur=-1; |
||
| 1344 | % |
||
| 1345 | % % Mandatory structure fields |
||
| 1346 | % globalStimParams.FS=100000; |
||
| 1347 | % globalStimParams.overallDuration=.1; % s |
||
| 1348 | % globalStimParams.doPlot=1; |
||
| 1349 | % globalStimParams.doPlay=1; |
||
| 1350 | % |
||
| 1351 | % [audio, msg]=stimulusCreate(globalStimParams, stim, ); |
||
| 1352 | % |
||
| 1353 | % % local |
||
| 1354 | % stim.type='noise'; % or 'IRN' |
||
| 1355 | % % for IRN only |
||
| 1356 | % stim.niterations = 8; %0 for white noise |
||
| 1357 | % stim.delay = 1/150; |
||
| 1358 | % stim.irnGain = 1; |
||
| 1359 | % |
||
| 1360 | FS=globalStimParams.FS; |
||
| 1361 | noiseDuration= stimComponents.toneDuration; |
||
| 1362 | |||
| 1363 | nIterations=stimComponents.niterations; |
||
| 1364 | if nIterations==0 |
||
| 1365 | % white noise is specified as nIterations=1 |
||
| 1366 | nIterations=1; |
||
| 1367 | IRNgain=0; |
||
| 1368 | delay=0.01; % dummy |
||
| 1369 | else |
||
| 1370 | % IRN |
||
| 1371 | delay=stimComponents.delay; |
||
| 1372 | IRNgain=stimComponents.irnGain; |
||
| 1373 | end |
||
| 1374 | |||
| 1375 | npts=round(noiseDuration*FS); |
||
| 1376 | dels=round(delay*FS); |
||
| 1377 | noise=randn(1,npts); |
||
| 1378 | |||
| 1379 | %fringe=nIterations*dels; |
||
| 1380 | %npts=npts+fringe; |
||
| 1381 | |||
| 1382 | for i=1:nIterations, |
||
| 1383 | dnoise=[noise(dels+1:npts) noise(1:dels)]; |
||
| 1384 | dnoise=dnoise.*IRNgain; |
||
| 1385 | noise=noise+dnoise; |
||
| 1386 | end; |
||
| 1387 | |||
| 1388 | switch stimComponents.type |
||
| 1389 | case 'pinkNoise' |
||
| 1390 | noise=UTIL_lowpassFilterFreq(noise, 10000, 1/FS); |
||
| 1391 | end |
||
| 1392 | |||
| 1393 | rms=(mean(noise.^2)^.5); %should be 20 microPascals for 0 dB SPL |
||
| 1394 | adjust=20e-6/rms; |
||
| 1395 | noise=noise*adjust; |
||
| 1396 | rms=(mean(noise.^2)^.5); |
||
| 1397 | amplitude=10.^(stimComponents.amplitudesdB/20); |
||
| 1398 | noise=amplitude*noise; |
||
| 1399 | % rms=(mean(noise.^2)^.5); |
||
| 1400 | % dBnoise=20*log10(rms/20e-6) |
||
| 1401 | |||
| 1402 | %------------------------------------------------------------------ makeRPN |
||
| 1403 | function RPN=makeRPN(globalStimParams, stim) |
||
| 1404 | % 'period' is a collection of samples - AAABCD |
||
| 1405 | % you need |
||
| 1406 | % |
||
| 1407 | % stim.type='RPN'; |
||
| 1408 | % stim.toneDuration=.2; |
||
| 1409 | % stim.amplitudesdB=50; |
||
| 1410 | % stim.beginSilence=.01; |
||
| 1411 | % stim.rampOnDur=.002; |
||
| 1412 | % stim.rampOffDur=-1; |
||
| 1413 | % |
||
| 1414 | % stim.sampleDuration=.005; %200 Hz pitch |
||
| 1415 | % stim.nSimilarSamples=5; % pitch strength |
||
| 1416 | % stim.nIndependentSamples=1% dilutes strength |
||
| 1417 | % |
||
| 1418 | % % Mandatory structure fields |
||
| 1419 | % globalStimParams.FS=44100; |
||
| 1420 | % globalStimParams.overallDuration=.21; % s |
||
| 1421 | % |
||
| 1422 | % globalStimParams.doPlot=1; |
||
| 1423 | % globalStimParams.doPlay=1; |
||
| 1424 | % [audio, msg]=stimulusCreate(globalStimParams, stim); |
||
| 1425 | |||
| 1426 | FS=globalStimParams.FS; |
||
| 1427 | ptsPerSample=floor(stim.sampleDuration*FS); |
||
| 1428 | |||
| 1429 | samplesPerPeriod=stim.nSimilarSamples+stim.nIndependentSamples; |
||
| 1430 | periodDuration=samplesPerPeriod*stim.sampleDuration; |
||
| 1431 | |||
| 1432 | totalNumPeriods=2*floor(stim.toneDuration/periodDuration); % longer than necessary |
||
| 1433 | if totalNumPeriods<1 |
||
| 1434 | error('stimulusCreate: RPN, stimulus duration needs to be longer')
|
||
| 1435 | end |
||
| 1436 | |||
| 1437 | RPN=[]; |
||
| 1438 | for j=1:totalNumPeriods |
||
| 1439 | noise=randn(1,ptsPerSample); |
||
| 1440 | for i=1:stim.nSimilarSamples |
||
| 1441 | RPN=[RPN noise]; |
||
| 1442 | end |
||
| 1443 | |||
| 1444 | for i=1:stim.nIndependentSamples |
||
| 1445 | noise=randn(1,ptsPerSample); |
||
| 1446 | RPN=[RPN noise]; |
||
| 1447 | end |
||
| 1448 | end |
||
| 1449 | |||
| 1450 | targetStimulusLength=round(stim.toneDuration/FS); |
||
| 1451 | RPN=RPN(1:floor(stim.toneDuration*FS)); % take enough for stimulus |
||
| 1452 | |||
| 1453 | rms=(mean(RPN.^2)^.5); %should be 20 microPascals for 0 dB SPL |
||
| 1454 | adjust=20e-6/rms; |
||
| 1455 | RPN=RPN*adjust; |
||
| 1456 | rms=(mean(RPN.^2)^.5); |
||
| 1457 | amplitude=10.^(stim.amplitudesdB/20); |
||
| 1458 | RPN=amplitude*RPN; |
||
| 1459 | % rms=(mean(noise.^2)^.5); |
||
| 1460 | % dBnoise=20*log10(rms/20e-6) |
||
| 1461 | |||
| 1462 | %--------------------------------------------------------------------applyRampOn |
||
| 1463 | function signal=applyRampOn(signal, rampDur, rampOnTime, sampleRate) |
||
| 1464 | %applyRampOn applies raised cosine ramp |
||
| 1465 | %rampOntime is the time at which the ramp begins |
||
| 1466 | %At all other times the mask has a value of 1 |
||
| 1467 | %signal=applyRampOn(signal, rampDur, rampOnTime, sampleRate) |
||
| 1468 | |||
| 1469 | rampDurPoints=round(rampDur*sampleRate); |
||
| 1470 | rampOn= (1+cos(pi:pi/(rampDurPoints-1): 2*pi))/2'; |
||
| 1471 | |||
| 1472 | sigDurPoints=length(signal); |
||
| 1473 | mask(1:sigDurPoints)=1; |
||
| 1474 | rampOnStartIndex=round(rampOnTime*sampleRate+1); |
||
| 1475 | mask(rampOnStartIndex: rampOnStartIndex+ rampDurPoints-1)=rampOn; |
||
| 1476 | signal=signal.*mask; |
||
| 1477 | %plot(mask) |
||
| 1478 | |||
| 1479 | %--------------------------------------------------------------------applyRampOff |
||
| 1480 | function signal=applyRampOff(signal, rampDur, rampOffTime, sampleRate) |
||
| 1481 | %applyRampOn applies raised cosine squared ramp |
||
| 1482 | %rampOffTime is the time at which the ramp begins |
||
| 1483 | %At all other times the mask has a value of 1 |
||
| 1484 | % signal=applyRampOff(signal, rampDur, rampOffTime, sampleRate) |
||
| 1485 | |||
| 1486 | rampDurPoints=round(rampDur*sampleRate); |
||
| 1487 | rampOff= (1+cos(0:pi/(rampDurPoints-1): pi))/2'; |
||
| 1488 | |||
| 1489 | sigDurPoints=length(signal); |
||
| 1490 | mask=ones(1,sigDurPoints); |
||
| 1491 | rampOffStartIndex=round(rampOffTime*sampleRate+1); |
||
| 1492 | mask(rampOffStartIndex: round(rampOffStartIndex+ rampDurPoints-1))=rampOff; |
||
| 1493 | if length(mask)>sigDurPoints, mask=mask(1:sigDurPoints); end |
||
| 1494 | signal=signal.*mask; |
||
| 1495 | %plot(mask) |
||
| 1496 | |||
| 1497 | function signal=applyGaussianRamps(signal, sigma, sampleRate) |
||
| 1498 | dt=1/sampleRate; |
||
| 1499 | time=dt:dt:dt*length(signal); |
||
| 1500 | ramp=1-exp(-time.^2/(2*sigma^2)); |
||
| 1501 | % apply onset ramp |
||
| 1502 | signal=signal.*ramp; |
||
| 1503 | % apply offset ramp |
||
| 1504 | ramp=fliplr(ramp); |
||
| 1505 | signal=signal.*ramp; |
||
| 1506 | |||
| 1507 | |||
| 1508 | |||
| 1509 | %--------------------------------------------------------------------checkDescriptors |
||
| 1510 | 30:1a502830d462 | rmeddis | function [globalStimParams, stimComponents]=... |
| 1511 | checkDescriptors(globalStimParams, stimComponents) |
||
| 1512 | 0:f233164f4c86 | rmeddis | |
| 1513 | try |
||
| 1514 | % if FS exists, it takes priority |
||
| 1515 | globalStimParams.dt=1/globalStimParams.FS; |
||
| 1516 | catch |
||
| 1517 | % otherwise set FS using dt |
||
| 1518 | globalStimParams.FS=1/globalStimParams.dt; |
||
| 1519 | end |
||
| 1520 | |||
| 1521 | 30:1a502830d462 | rmeddis | sampleRate=globalStimParams.FS; |
| 1522 | |||
| 1523 | globalStimParams.nSignalPoints=... |
||
| 1524 | round(globalStimParams.overallDuration* sampleRate); |
||
| 1525 | 0:f233164f4c86 | rmeddis | |
| 1526 | % optional field (ears) |
||
| 1527 | try |
||
| 1528 | globalStimParams.ears; |
||
| 1529 | catch |
||
| 1530 | % default: dichotic. |
||
| 1531 | globalStimParams.ears='dichotic'; |
||
| 1532 | end |
||
| 1533 | |||
| 1534 | % audioOutCorrection is optional |
||
| 1535 | % audioOutCorrection is a scalar for reducing the sound |
||
| 1536 | try |
||
| 1537 | globalStimParams.audioOutCorrection; |
||
| 1538 | catch |
||
| 1539 | % set to 1 if omitted |
||
| 1540 | globalStimParams.audioOutCorrection=1; |
||
| 1541 | end |
||
| 1542 | |||
| 1543 | try |
||
| 1544 | globalStimParams.doPlay; |
||
| 1545 | catch |
||
| 1546 | % default plays sound only if explicitly requested |
||
| 1547 | globalStimParams.doPlay=0; |
||
| 1548 | end |
||
| 1549 | |||
| 1550 | try |
||
| 1551 | globalStimParams.doPlot; |
||
| 1552 | catch |
||
| 1553 | % no plotting unless explicitly requested |
||
| 1554 | globalStimParams.doPlot=0; |
||
| 1555 | end |
||
| 1556 | |||
| 1557 | [ears nComponentSounds]=size(stimComponents); |
||
| 1558 | |||
| 1559 | for ear=1:2 % 1=left/ 2=right |
||
| 1560 | |||
| 1561 | % create a list of components whose type is specified |
||
| 1562 | % if no type is specified assume that it is an empty specification |
||
| 1563 | % this is allowed |
||
| 1564 | validStimComponents=[]; |
||
| 1565 | for i=1:nComponentSounds |
||
| 1566 | try |
||
| 1567 | if ~isempty(stimComponents(ear,i).type) |
||
| 1568 | validStimComponents=[validStimComponents i]; |
||
| 1569 | end |
||
| 1570 | catch |
||
| 1571 | end |
||
| 1572 | end |
||
| 1573 | |||
| 1574 | for componentNo=validStimComponents |
||
| 1575 | % If no AM filed is present, create it for completeness |
||
| 1576 | if ~isfield(stimComponents(ear,componentNo),'AMfrequency') |... |
||
| 1577 | ~isfield(stimComponents(ear,componentNo),'AMdepth') |
||
| 1578 | stimComponents(ear,componentNo).AMfrequency=0; |
||
| 1579 | stimComponents(ear,componentNo).AMdepth=0; |
||
| 1580 | end |
||
| 1581 | |||
| 1582 | % all signals must have durations, amplitudes and ramps |
||
| 1583 | if ... |
||
| 1584 | isempty(stimComponents(ear,componentNo).type) |... |
||
| 1585 | isempty(stimComponents(ear,componentNo).toneDuration) |... |
||
| 1586 | isempty(stimComponents(ear,componentNo).amplitudesdB) |... |
||
| 1587 | isempty(stimComponents(ear,componentNo).rampOnDur) |
||
| 1588 | descriptorError( 'missing stimComponent descriptor', stimComponents, ear, componentNo) |
||
| 1589 | end |
||
| 1590 | |||
| 1591 | try, stimComponents(ear,componentNo).endSilence; catch, stimComponents(ear,componentNo).endSilence=-1; end |
||
| 1592 | |||
| 1593 | % ramp checks do not apply to file input |
||
| 1594 | if ~strcmp(stimComponents(ear,componentNo).type, 'file') |
||
| 1595 | % match offset ramp to onset if not explicitly specified |
||
| 1596 | if stimComponents(ear,componentNo).rampOffDur==-1, |
||
| 1597 | stimComponents(ear,componentNo).rampOffDur=stimComponents(ear,componentNo).rampOnDur; |
||
| 1598 | end |
||
| 1599 | % ramps must be shorter than the stimulus |
||
| 1600 | if stimComponents(ear,componentNo).rampOffDur> stimComponents(ear,componentNo).toneDuration | ... |
||
| 1601 | stimComponents(ear,componentNo).rampOnDur> stimComponents(ear,componentNo).toneDuration |
||
| 1602 | descriptorError( 'ramp longer than sound component', stimComponents, ear, componentNo) |
||
| 1603 | end |
||
| 1604 | end |
||
| 1605 | |||
| 1606 | % end silence is measured to fit into the global duration |
||
| 1607 | if stimComponents(ear,componentNo).endSilence==-1, |
||
| 1608 | 30:1a502830d462 | rmeddis | stimComponents(ear,componentNo).endSilence=... |
| 1609 | globalStimParams.overallDuration-... |
||
| 1610 | stimComponents(ear,componentNo).beginSilence -... |
||
| 1611 | stimComponents(ear,componentNo).toneDuration; |
||
| 1612 | |||
| 1613 | endSilenceNpoints=stimComponents(ear,componentNo).endSilence... |
||
| 1614 | *sampleRate; |
||
| 1615 | end |
||
| 1616 | if stimComponents(ear,componentNo).endSilence<0 |
||
| 1617 | globalStimParams |
||
| 1618 | descriptorError( 'component durations greater than overallDuration', stimComponents, ear, componentNo) |
||
| 1619 | 0:f233164f4c86 | rmeddis | end |
| 1620 | |||
| 1621 | % check overall duration of this component against global duration |
||
| 1622 | totalDuration= ... |
||
| 1623 | stimComponents(ear,componentNo).beginSilence+... |
||
| 1624 | stimComponents(ear,componentNo).toneDuration+... |
||
| 1625 | stimComponents(ear,componentNo).endSilence; |
||
| 1626 | |||
| 1627 | % avoid annoying error message for single stimulus component |
||
| 1628 | if ears==1 && nComponentSounds==1 |
||
| 1629 | globalStimParams.overallDuration= totalDuration; |
||
| 1630 | end |
||
| 1631 | |||
| 1632 | % check total duration |
||
| 1633 | 30:1a502830d462 | rmeddis | totalSignalPoints= round(totalDuration* sampleRate); |
| 1634 | 0:f233164f4c86 | rmeddis | if totalSignalPoints >globalStimParams.nSignalPoints |
| 1635 | descriptorError( 'Signal component duration does not match overall duration ', stimComponents, ear, componentNo) |
||
| 1636 | end |
||
| 1637 | |||
| 1638 | if isfield(stimComponents(ear,componentNo), 'filter') |
||
| 1639 | if ~isequal(length(stimComponents(ear,componentNo).filter), 3) |
||
| 1640 | descriptorError( 'Filter parameter must have three elements ', stimComponents, ear, componentNo) |
||
| 1641 | end |
||
| 1642 | end |
||
| 1643 | end % component |
||
| 1644 | % ?? |
||
| 1645 | if strcmp(globalStimParams.ears,'monoticL') | strcmp(globalStimParams.ears, 'monoticR'), break, end |
||
| 1646 | end % ear |
||
| 1647 | |||
| 1648 | |||
| 1649 | %-------------------------------------------------------------------- descriptorError |
||
| 1650 | function descriptorError( msg, stimComponents, ear, componentNo) |
||
| 1651 | stimComponents(ear, componentNo) |
||
| 1652 | |||
| 1653 | disp(' *********** **************** ************')
|
||
| 1654 | disp([ '...Error in stimComponents description: ']) |
||
| 1655 | disp([msg ]) |
||
| 1656 | disp(['Ear = ' num2str(ear) ' component No ' num2str(componentNo)]) |
||
| 1657 | disp(' *********** **************** ************')
|
||
| 1658 | error('myError ')
|
||
| 1659 | |||
| 1660 | |||
| 1661 | %-------------------------------------------------------------------- normalize |
||
| 1662 | function [normalizedSignal, gain]= normalize(signal) |
||
| 1663 | % normalize (signal) |
||
| 1664 | maxSignal=max(max(signal)); |
||
| 1665 | minSignal=min(min(signal)); |
||
| 1666 | if -minSignal>maxSignal, normalizingFactor=-minSignal; else normalizingFactor=maxSignal; end |
||
| 1667 | normalizingFactor=1.01*normalizingFactor; |
||
| 1668 | gain= 20*log10(normalizingFactor); |
||
| 1669 | normalizedSignal=signal/normalizingFactor; |
||
| 1670 | |||
| 1671 | |||
| 1672 | %--------------------------------------------------------------------Butterworth |
||
| 1673 | function y=Butterworth (x, dt, fl, fu, order) |
||
| 1674 | % Butterworth (x, dt, fu, fl, order) |
||
| 1675 | % Taken from Yuel and beauchamp page 261 |
||
| 1676 | % NB error in their table for K (see their text) |
||
| 1677 | % x is original signal |
||
| 1678 | % fu, fl upper and lower cutoff |
||
| 1679 | % order is the number of times the filter is applied |
||
| 1680 | |||
| 1681 | q=(pi*dt*(fu-fl)); |
||
| 1682 | J=1/(1+ cot(q)); |
||
| 1683 | K= (2*cos(pi*dt*(fu+fl)))/(1+tan(q)*cos(q)); |
||
| 1684 | L= (tan(q)-1)/(tan(q)+1); |
||
| 1685 | b=[J -J]; |
||
| 1686 | a=[1 -K -L]; |
||
| 1687 | for i=1:order |
||
| 1688 | y=filter(b, a, x); |
||
| 1689 | x=y; |
||
| 1690 | end |
||
| 1691 | |||
| 1692 | |||
| 1693 | % -------------------------------------------------------- UTIL_amp2dB |
||
| 1694 | function [y] = UTIL_amp2dB (x, ref) |
||
| 1695 | % Calculates a dB (ref. ref) value 'y' from a peak amplitude number 'x'. |
||
| 1696 | % if ref omitted treat as dB |
||
| 1697 | % Check the number of arguments that are passed in. |
||
| 1698 | if (nargin < 2) |
||
| 1699 | ref=1; |
||
| 1700 | end |
||
| 1701 | if (nargin > 2) |
||
| 1702 | error ('Too many arguments');
|
||
| 1703 | end |
||
| 1704 | |||
| 1705 | % Check arguments. |
||
| 1706 | if x < 0.0 |
||
| 1707 | error ('Can not calculate the log10 of a negative number');
|
||
| 1708 | elseif x == 0.0 |
||
| 1709 | warning ('log10 of zero. The result is set to -1000.0');
|
||
| 1710 | y = -1000.0; |
||
| 1711 | else |
||
| 1712 | % Do calculations. |
||
| 1713 | y = 20.0 * log10(x/(sqrt(2)*ref)); |
||
| 1714 | |||
| 1715 | end |
||
| 1716 | |||
| 1717 | %-------------------------------------------------------------------- FFT |
||
| 1718 | function showFFT (getFFTsignal, dt) |
||
| 1719 | color='r'; |
||
| 1720 | figure(2), clf, |
||
| 1721 | hold off |
||
| 1722 | |||
| 1723 | % trim initial silence |
||
| 1724 | idx=find(getFFTsignal>0); |
||
| 1725 | if ~isempty(idx) |
||
| 1726 | getFFTsignal=getFFTsignal(idx(1):end); |
||
| 1727 | end |
||
| 1728 | %trim final silence |
||
| 1729 | getFFTsignal=getFFTsignal(end:-1:1); |
||
| 1730 | idx=find(getFFTsignal>0); |
||
| 1731 | if ~isempty(idx) |
||
| 1732 | getFFTsignal=getFFTsignal(idx(1):end); |
||
| 1733 | getFFTsignal=getFFTsignal(end:-1:1); |
||
| 1734 | end |
||
| 1735 | |||
| 1736 | % Analyse make stimComponents length a power of 2 |
||
| 1737 | x=length(getFFTsignal); |
||
| 1738 | squareLength=2; |
||
| 1739 | while squareLength<=x |
||
| 1740 | squareLength=squareLength*2; |
||
| 1741 | end |
||
| 1742 | squareLength=round(squareLength/2); |
||
| 1743 | getFFTsignal=getFFTsignal(1:squareLength); |
||
| 1744 | n=length(getFFTsignal); |
||
| 1745 | |||
| 1746 | minf=100; maxf=20000; |
||
| 1747 | |||
| 1748 | fft_result = fft(getFFTsignal, n); % Compute FFT of the input signal. |
||
| 1749 | fft_power = fft_result .* conj(fft_result);% / n; % Compute power spectrum. Dividing by 'n' we get the power spectral density. |
||
| 1750 | fft_phase = angle(fft_result); % Compute the phase spectrum. |
||
| 1751 | |||
| 1752 | frequencies = (1/dt)*(1:n/2)/n; |
||
| 1753 | fft_power=fft_power(1:length(fft_power)/2); % remove mirror frequencies |
||
| 1754 | fft_phase=fft_phase(1:length(fft_phase)/2); % remove mirror frequencies |
||
| 1755 | fft_powerdB = UTIL_amp2dB (fft_power, max(fft_power)); % convert to dB |
||
| 1756 | % jags=find(diff(fft_phase)>0); % unwrap phase |
||
| 1757 | % for i=jags, fft_phase(i+1:end)=fft_phase(i+1:end)-2*pi; end |
||
| 1758 | |||
| 1759 | xlim([minf maxf]) |
||
| 1760 | semilogx(frequencies, fft_powerdB-max(fft_powerdB), color) |
||
| 1761 | ylim([ -20 5]) |
||
| 1762 | |||
| 1763 | |||
| 1764 | |||
| 1765 | function y=UTIL_lowpassFilterFreq(x, cutOffFrequency, dt) |
||
| 1766 | % UTIL_lowpassFilterFreq multi-channel filter |
||
| 1767 | % |
||
| 1768 | % Usage: |
||
| 1769 | % output=UTIL_lowpassFilterFreq(input, cutOffFrequency, dt) |
||
| 1770 | % |
||
| 1771 | % cutoff should be around 100 Hz for audio work |
||
| 1772 | % dt should be <1/50000 s for audio work |
||
| 1773 | % |
||
| 1774 | % Attenuation is - 6 dB per octave above cutoff. |
||
| 1775 | |||
| 1776 | |||
| 1777 | sampleRate=1/dt; |
||
| 1778 | |||
| 1779 | if 4*cutOffFrequency>sampleRate |
||
| 1780 | warning(['UTIL_lowpassFilterFreq: sample rate ' num2str(1/dt) ' is too low for this BF. Sampling rate should be >' num2str(4*cutOffFrequency) 'or cutoff (' num2str(4*cutOffFrequency) ') should be lower' ])
|
||
| 1781 | cutOffFrequency=sampleRate/4; |
||
| 1782 | end |
||
| 1783 | |||
| 1784 | tau=1/(2*pi*cutOffFrequency); |
||
| 1785 | |||
| 1786 | y=zeros(size(x)); |
||
| 1787 | [numChannels numPoints]=size(x); |
||
| 1788 | for i=1:numChannels |
||
| 1789 | y(i,:)=filter([dt/tau], [1 -(1-dt/tau)], x(i,:)); |
||
| 1790 | end |
||
| 1791 |