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.

Statistics Download as Zip
| Branch: | Revision:

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