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 @ 35:25d53244d5c8

History | View | Annotate | Download (55.6 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
        % Initial silence
403
        % 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
        %     figure(22), plot(signal), title([num2str(ear) ' - ' num2str(componentNo)]),pause (1)
418
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
% Tones are presented at 10-ms intervals
578
% Each tone has its own amplitude and its own ramp.
579
580
frequencies=stimComponents.frequencies;
581
amplitudesdB=stimComponents.amplitudesdB;
582
nFrequencies=length(frequencies);
583
584
dt=globalStimParams.dt;
585
toneDuration=.010;
586
time=dt:dt:toneDuration;
587
588
rampDuration=stimComponents.rampOnDur;
589
rampTime=dt:dt:rampDuration;
590
ramp=[0.5*(1+cos(2*pi*rampTime/(2*rampDuration)+pi)) ones(1,length(time)-length(rampTime))];
591
ramp=ramp.*fliplr(ramp);
592
593
stimulus= zeros(1,round((toneDuration+globalStimParams.beginSilences(end))/dt)+1);
594
595
for i=1:nFrequencies
596
    toneBeginPTR=round(globalStimParams.beginSilences(i)/dt)+1;
597
598
    frequency=frequencies(i);
599
    dBSPL=amplitudesdB(i);
600
    amplitude=28e-6* 10.^(dBSPL/20);
601
    tone=amplitude*sin(2*pi*frequency*time);
602
    tone=tone.*ramp;
603
    stimulus(toneBeginPTR:toneBeginPTR+length(tone)-1)=stimulus(toneBeginPTR:toneBeginPTR+length(tone)-1)+tone;
604
end
605
% figure(2), plot( stimulus')
606
607
608
%---------------------------------------------------- makeFMtone
609
function tone=makeFMtone(globalStimParams, stimComponents)
610
% maketone generates a stimComponents tone
611
% tone=maketone(dt, frequencies, toneDuration, dBSPL, phases)
612
% tone is returned in Pascals
613
% frequencies is a list of frequencies
614
% phase is list of phases or 'sin', 'cos', 'alt'
615
%
616
% defaults:
617
%  phase = sin
618
%  dBSPL=56 dB SPL
619
620
dt=globalStimParams.dt;
621
frequencies=stimComponents.frequencies;
622
toneDuration=stimComponents.toneDuration;
623
dBSPL=stimComponents.amplitudesdB;
624
phases=stimComponents.phases;
625
fmDepth=stimComponents.fmDepth;
626
fmFrequency=stimComponents.fmFrequency;
627
628
if ischar(phases)
629
    switch phases
630
        case 'sin'
631
            phases= zeros(1,length(frequencies));
632
        case 'cos'
633
            phases= pi/2*ones(1,length(frequencies));
634
        case 'alt'
635
            phases= repmat([0 pi/2], 1, floor(length(frequencies)/2));
636
            if length(phases)<length(frequencies)
637
                phases=[phases 0];
638
            end
639
    end
640
end
641
642
if length(phases)==1, phases=repmat(phases(1), 1, length(frequencies)); end
643
if length(phases)<length(frequencies)
644
    error('makeTone:phase specification must have the same length as frequencies')
645
end
646
647
if length(dBSPL)==1, dBSPL=repmat(dBSPL(1), 1, length(frequencies)); end
648
if length(dBSPL)<length(dBSPL)
649
    error('makeTone:dBSPL specification must have the same length as frequencies')
650
end
651
652
time=dt:dt:toneDuration;
653
amplitudes=28e-6* 10.^(dBSPL/20);
654
655
tone=zeros(size(time));
656
for i=1:length(frequencies)
657
    frequency=frequencies(i);
658
    phase=phases(i);
659
    tone=tone+amplitudes(i)*sin(2*pi*frequency*time+phase + fmDepth*sin(2*pi*fmFrequency*time));
660
end
661
662
% figure(1), plot(time, signal)
663
664
%----------------------------------------------------makeTransposedStimulus
665
function [transposedStimulus]=makeTransposedStimulus(globalStimParams, stim)
666
% globalStimParams.FS=100000;
667
% globalStimParams.overallDuration=.1;  % s
668
% globalStimParams.doPlot=1;
669
%
670
% stim.type='transposedStimulus';
671
% stim.phases='sin';
672
% stim.toneDuration=.05;;
673
% stim.frequencies=500;
674
% stim.amplitudesdB=50;
675
% stim.beginSilence=.01;
676
% stim.endSilence=-1;
677
% stim.rampOnDur=.002;
678
% stim.rampOffDur=-1;
679
%
680
% stim.transCarrierFreq=4000;
681
% stim.transModFreq=100;
682
683
transposedStimulus=[];
684
% make envelope of transposed tone
685
for i=1:length(stim.transModFreq)
686
    stim.type='tone';
687
    stim.frequencies=stim.transModFreq(i);
688
    stim.endsilence=-1;     stim.beginSilence=0;
689
    [envelope, msg]=stimulusCreate(globalStimParams, stim);    % NB recursive
690
    envelope=envelope(:,1);   % mono
691
    % HW rectify
692
    envelope(find(envelope<0))=0;
693
    % LP filter
694
    maxEnvelope=max(envelope);
695
    envelope=UTIL_Butterworth (envelope, globalStimParams.dt, 10, .2*stim.transModFreq(i), 2);
696
    envelope=envelope*maxEnvelope/max(envelope);
697
698
    % make the carrier
699
    stim.frequencies=stim.transCarrierFreq(i);
700
    stim.endsilence=-1;    stim.beginSilence=0;
701
    [audio, msg]=stimulusCreate(globalStimParams, stim);
702
    carrier=audio(:,1);
703
    x= (carrier.*envelope)';
704
    % base amplitude on peak of unmodulated carrier
705
    x=x/max(carrier);
706
    transposedStimulus=[transposedStimulus; x];
707
end
708
transposedStimulus=sum(transposedStimulus,1);
709
% clf,plot(transposedStimulus)
710
711
%--------------------------------------------------------------------makeClicks
712
function clickTrain=makeClicks(globalStimParams, stimComponents)
713
% makeClicks(F0, clickTimes, duration, FS);
714
% F0 specifies the repetition rate of the click sequence
715
% If F0=-1, a single click is generated at the start of the duration of the signal
716
%
717
% clickTimes a are fractions of the period
718
%  and specify when the click appears in the period
719
% A click time of 1 is reset to zero.
720
% if the clicktime plus the click width is greater than the period, no click is generated
721
% clicks are treated as 20 microPascal high before amplification
722
%  unless otherwise specified in stimComponents.clickHeight
723
% click width is dt unless specified in stimComponents.clickWidth
724
%
725
% for regular pulse train set clicktimes=1 or zero;
726
% FS is the sampling interval;
727
% CarlyonClickTrain(100, [.4 1], 40, 441000);
728
729
FS=globalStimParams.FS; % sample rate
730
dt=1/FS;
731
732
try,clickWidth=stimComponents.clickWidth;catch, clickWidth=dt; end
733
try,clickHeight=stimComponents.clickHeight; catch, clickHeight=28e-6 * 10^(stimComponents.amplitudesdB/20); end
734
try, clickTimes=stimComponents.clickTimes; catch, clickTimes=1; end
735
736
% clickTimes are the times in a cycle that the click
737
% occurs
738
checkClickTimes=clickTimes-1;
739
if max(checkClickTimes)>1
740
    msg= 'click times must be <= than 1 (period)';
741
    return
742
end
743
744
if clickWidth>=1/stimComponents.clickRepeatFrequency
745
    msg= 'click width is too great for frequency';
746
    return
747
end
748
749
duration=stimComponents.toneDuration;
750
totalLength=round(stimComponents.toneDuration/dt);
751
F0=stimComponents.clickRepeatFrequency;
752
F0=round(F0/dt)*dt;
753
if F0==-1 % single click required
754
    F0=1/duration;
755
    repetitions=1;
756
    clickStartTimes=1; %clicktimes are fractions of a period
757
else
758
    repetitions=round(F0*duration)-1;
759
    duration=repetitions/F0;
760
    clickStartTimes=clickTimes;
761
end
762
% if a clickTime=1 (end of duty period) set it to the beginning
763
clickStartTimes(clickStartTimes==1)=0;
764
765
period=1/F0;
766
time=dt:dt:period;
767
nPoints=length(time);
768
signal=zeros(1,nPoints);
769
dBSPL=stimComponents.amplitudesdB;
770
771
% compute click train for a single cycle
772
clickWidthNpoints=round(clickWidth*FS);
773
for i=1:length(clickStartTimes)
774
    %     if clickStartTimes(i)<clickWidth
775
    %         clickStartTimes(i)=dt;
776
    %     end
777
    clickTime=round(period*clickStartTimes(i)/dt -dt);
778
    % clicks are treated as 20 microPascal high
779
    if clickTime+clickWidthNpoints<length(signal)
780
        signal(clickTime+1:clickTime+clickWidthNpoints)=clickHeight;
781
    end
782
end
783
784
clickTrain=repmat(signal, 1, repetitions);
785
786
if length(clickTrain)>totalLength
787
    clickTrain=clickTrain(1:totalLength);
788
elseif length(clickTrain)<totalLength
789
    timeToAdd=zeros(1,round((totalLength-length(clickTrain))));
790
    clickTrain=[clickTrain timeToAdd];
791
    % figure (1), plot(clickTrain)
792
end
793
794
%----------------------------------------------------------------makePressnitzerClicks
795
function signal=makePressnitzerClicks(globalStimParams, stimComponents)
796
% PressnitzerClicks(k,duration,dt)
797
% Generates a sequence of clicks with intervals kxx
798
%  where x= rand*k/2
799
% This is not the same as Kaernbach and Demany clicks
800
801
FS=globalStimParams.FS; % sample rate
802
dt=1/FS;
803
804
% obligatory parameter
805
try
806
    k=stimComponents.k;
807
catch
808
    error('PressnitzerClicks: field ''k'' is missing from stimcomponent')
809
end
810
811
% optional parameters
812
if isfield(stimComponents,'clickWidth')
813
    clickWidth=stimComponents.clickWidth;
814
else
815
    clickWidth=dt;
816
end
817
clickWidthNpoints=round(clickWidth*FS);
818
819
if isfield(stimComponents,'clickHeight')
820
    clickHeight=stimComponents.clickHeight;
821
else
822
    clickHeight=28e-6 * 10^(stimComponents.amplitudesdB/20);
823
end
824
825
duration=stimComponents.toneDuration;
826
827
signalLength=round(duration/dt);
828
signal=zeros(1,signalLength);
829
kInterval=round(k/dt);
830
halfk=k/2;
831
signal(1)=clickHeight;
832
timeIdx=0;
833
while timeIdx<signalLength
834
    % first interval = k
835
    clickTime=timeIdx+kInterval;
836
    signal(clickTime:clickTime+clickWidthNpoints)=clickHeight;
837
    timeIdx=timeIdx+kInterval;
838
839
    % second interval = 0 : k/2
840
    intervalx1=round(rand*halfk/dt);
841
    clickTime=timeIdx+intervalx1;
842
    signal(clickTime:clickTime+clickWidthNpoints)=clickHeight;
843
    timeIdx=timeIdx+intervalx1;
844
845
    % third interval = 0 : k/2
846
    intervalx1=round(rand*halfk/dt);
847
    clickTime=timeIdx+intervalx1;
848
    signal(clickTime:clickTime+clickWidthNpoints)=clickHeight;
849
    timeIdx=timeIdx+intervalx1;
850
end
851
852
signal=signal(1:signalLength);
853
% figure(1),	plot(dt:dt:duration,signal)
854
855
%----------------------------------------------------------------makePressnitzerABXClicks
856
function signal=makePressnitzerABxClicks(globalStimParams, stimComponents)
857
% Generates a sequence of clicks with intervals ABx
858
% AB interval is 2*k
859
% where A= rand* k
860
%       B= k-A
861
%       x= k/2
862
% These are second order clicks
863
864
FS=globalStimParams.FS; % sample rate
865
dt=1/FS;
866
867
% obligatory parameter
868
try
869
    k=stimComponents.k;
870
catch
871
    error('PressnitzerClicks: field ''k'' is missing from stimcomponent')
872
end
873
874
% optional parameters
875
if isfield(stimComponents,'clickWidth')
876
    clickWidth=stimComponents.clickWidth;
877
else
878
    clickWidth=dt;
879
end
880
clickWidthNpoints=round(clickWidth*FS);
881
882
if isfield(stimComponents,'clickHeight')
883
    clickHeight=stimComponents.clickHeight;
884
else
885
    clickHeight=28e-6 * 10^(stimComponents.amplitudesdB/20);
886
end
887
888
duration=stimComponents.toneDuration;
889
890
signalLength=round(duration/dt);
891
signal=zeros(1,2*signalLength); % allow for overrun
892
ABinterval=k/dt;                % i.e. the number of dt steps
893
randomInterval=ABinterval/2;
894
signal(1)=clickHeight;
895
time=0;
896
while time<signalLength
897
    % first interval = A
898
    intervalA=rand*ABinterval;
899
    clickTime=round(time+intervalA)+1;   % +1 avoids zero index
900
    signal(clickTime:clickTime+clickWidthNpoints)=clickHeight;
901
    time=time+intervalA;
902
903
    % second interval = B
904
    intervalB=ABinterval-intervalA;
905
    clickTime=round(time+intervalB)+1;
906
    signal(clickTime:clickTime+clickWidthNpoints-1)=clickHeight;
907
    time=time+intervalB;
908
909
    % third interval = 0 : k/2
910
    intervalx1=rand*randomInterval;    % mean random interval=k
911
    clickTime=round(time+intervalx1)+1;
912
    signal(clickTime:clickTime+clickWidthNpoints-1)=clickHeight;
913
    time=time+intervalx1;
914
end
915
916
signal=signal(1:signalLength);
917
% figure(1),	plot(dt:dt:duration,signal)
918
919
%-----------------------------------------------------makeABxClicks
920
function signal=makeABxClicks(globalStimParams, stimComponents)
921
% Generates a sequence of clicks with intervals ABx
922
% AB interval is 2*k
923
% where A= rand* k
924
%       B= k-A
925
%       x= rand*2*k
926
% These are second order clicks
927
928
FS=globalStimParams.FS; % sample rate
929
dt=1/FS;
930
931
% obligatory parameter
932
try
933
    k=stimComponents.k;
934
catch
935
    error('PressnitzerClicks: field ''k'' is missing from stimcomponent')
936
end
937
938
% optional parameters
939
if isfield(stimComponents,'clickWidth')
940
    clickWidth=stimComponents.clickWidth;
941
else
942
    clickWidth=dt;
943
end
944
clickWidthNpoints=round(clickWidth*FS);
945
946
if isfield(stimComponents,'clickHeight')
947
    clickHeight=stimComponents.clickHeight;
948
else
949
    clickHeight=28e-6 * 10^(stimComponents.amplitudesdB/20);
950
end
951
952
duration=stimComponents.toneDuration;
953
954
signalLength=round(duration/dt);
955
signal=zeros(1,2*signalLength); % allow for overrun
956
ABinterval=2*k/dt;
957
randomInterval=ABinterval;
958
signal(1)=clickHeight;
959
timeIdx=0;
960
while timeIdx<signalLength
961
    % first interval = A
962
    intervalA=round(rand*ABinterval);
963
    clickTime=timeIdx+intervalA+1;
964
    signal(clickTime:clickTime+clickWidthNpoints-1)=clickHeight;
965
    timeIdx=timeIdx+intervalA;
966
967
    % second interval = B
968
    intervalB=round(ABinterval-intervalA);
969
    clickTime=timeIdx+intervalB;
970
    signal(clickTime:clickTime+clickWidthNpoints-1)=clickHeight;
971
    timeIdx=timeIdx+intervalB;
972
973
    % third interval = 0 : k
974
    intervalx1=round(rand*randomInterval);    % mean random interval=k
975
    clickTime=timeIdx+intervalx1;
976
    signal(clickTime:clickTime+clickWidthNpoints-1)=clickHeight;
977
    timeIdx=timeIdx+intervalx1;
978
end
979
980
signal=signal(1:signalLength);
981
% figure(1),	plot(dt:dt:duration,signal)
982
983
%----------------------------------------------------------------makeYostClicks
984
function signal=makeYostClicks(globalStimParams, stimComponents)
985
% Generates a shuffled sequence of clicks with intervals kxxxx
986
%  where max(x)= 2*k
987
%  and there are n occurrences of x
988
% this section requires:
989
%  stimComponents.k
990
%  stimComponents.nXs
991
%  stimComponents.toneDuration
992
% optional:
993
%  stimComponents.clickWidth	%useful because width depends on dt
994
%  stimComponents.clickHeight	%best left to amplitude rescaling later
995
996
FS=globalStimParams.FS; % sample rate
997
dt=1/FS;
998
999
% obligatory parameters
1000
try
1001
    k=stimComponents.k;
1002
catch
1003
    error('makeYostClicks: field ''k'' is missing from stimComponents')
1004
end
1005
1006
try
1007
    nXs=stimComponents.nXs;
1008
catch
1009
    error('makeYostClicks: field ''nXs'' is missing from stimComponents')
1010
end
1011
1012
try
1013
    shuffled=stimComponents.shuffled;
1014
catch
1015
    error('makeYostClicks: field ''shuffled'' is missing from stimComponents')
1016
end
1017
1018
try
1019
    duration=stimComponents.toneDuration;
1020
catch
1021
    error('makeYostClicks: field ''toneDuration'' is missing from stimComponents')
1022
end
1023
1024
% optional parameters
1025
if isfield(stimComponents,'clickWidth')
1026
    clickWidth=stimComponents.clickWidth;
1027
else
1028
    clickWidth=dt;
1029
end
1030
clickWidthNpoints=round(clickWidth*FS);
1031
1032
if isfield(stimComponents,'clickHeight')
1033
    clickHeight=stimComponents.clickHeight;
1034
else
1035
    clickHeight=28e-6 * 10^(stimComponents.amplitudesdB/20);
1036
end
1037
1038
kInterval=round(k/dt);
1039
twoK=k*2;				% max width of x interval
1040
1041
signalLength=round(duration/dt);
1042
signal=zeros(1,signalLength);
1043
1044
timeIdx=0;
1045
intervalCount=0;
1046
while timeIdx<signalLength
1047
    timeIdx=timeIdx+kInterval;
1048
    if timeIdx>signalLength, break,end
1049
    intervalCount=intervalCount+1;
1050
    intervals(intervalCount)=kInterval;
1051
1052
    % repeat x intervals as required
1053
    if nXs>0
1054
        for nX=1:nXs
1055
            xInterval=round(rand*twoK/dt);
1056
            timeIdx=timeIdx+xInterval;
1057
            if timeIdx>signalLength, break,end
1058
            intervalCount=intervalCount+1;
1059
            intervals(intervalCount)=xInterval;
1060
        end
1061
    end
1062
    if timeIdx>signalLength, break,end
1063
end
1064
1065
% shuffle intervals
1066
if shuffled
1067
    randomNumbers=rand(1,length(intervals));
1068
    [randomNumbers idx]=sort(randomNumbers);
1069
    intervals=intervals(idx);
1070
    idx=intervals>0;
1071
    intervals=intervals(idx);
1072
end
1073
1074
intervalCount=length(intervals);
1075
signal(1)=clickHeight;
1076
clickTime=0;
1077
for i=1:intervalCount
1078
    clickTime=clickTime+intervals(i);
1079
    signal(clickTime:clickTime+clickWidthNpoints)=clickHeight;
1080
end
1081
signal=signal(1:signalLength);
1082
%  figure(1),	plot(dt:dt:duration,signal)
1083
1084
%--------------------------------------------------------------------makeKxxClicks
1085
function signal=makeKxxClicks(globalStimParams, stimComponents)
1086
% Click train consists of kkxxx.. sequences
1087
% k is the duration of a fixed interval (seconds)
1088
% random intervals are distributed 0 : 2* k (NB not like Pressnitzer clicks)
1089
% nKs is the number of successive 'k' intervals
1090
% nXs is the number of random intervals between k sequences
1091
% sequences of 3 x intervals > k are replaced with new sequences
1092
% shuffled causes all intervals to be reordered randomly
1093
% NB 1/k is the mean click rate
1094
1095
FS=globalStimParams.FS; % sample rate
1096
dt=1/FS;
1097
1098
try
1099
    k=stimComponents.k;		% duration (s) of fixed intervals
1100
catch
1101
    error('makeYostClicks: field ''k'' is missing from stimComponents')
1102
end
1103
1104
try
1105
    duration=stimComponents.toneDuration;
1106
catch
1107
    error('makeYostClicks: field ''duration'' is missing from stimComponents')
1108
end
1109
1110
if isfield(stimComponents,'clickWidth')
1111
    clickWidth=stimComponents.clickWidth;
1112
else
1113
    clickWidth=dt;
1114
end
1115
1116
if isfield(stimComponents,'clickHeight')
1117
    clickHeight=stimComponents.clickHeight;
1118
else
1119
    clickHeight=28e-6 * 10^(stimComponents.amplitudesdB/20);
1120
end
1121
1122
1123
if isfield(stimComponents,'order')
1124
    order=stimComponents.order;
1125
else
1126
    order=1;
1127
end
1128
1129
if isfield(stimComponents,'nKs')
1130
    nKs=stimComponents.nKs;
1131
else
1132
    nKs=1;
1133
end
1134
1135
if isfield(stimComponents,'nXs')
1136
    nXs=stimComponents.nXs;
1137
else
1138
    nXs=1;
1139
end
1140
1141
if isfield(stimComponents,'shuffled')
1142
    shuffled=stimComponents.shuffled;
1143
else
1144
    shuffled=1;
1145
end
1146
1147
kLength=round(k/dt);    % fixed interval
1148
xLength=2*kLength;      % maximum random interval
1149
requiredSignalLength=round(duration/dt);
1150
intervalsPerCycle=(nKs+nXs);
1151
cycleLength=nKs*kLength+nXs*xLength;
1152
% more cycles to allow for uncertainty
1153
nCycles=5*round(requiredSignalLength/cycleLength);
1154
nIntervals=nCycles*intervalsPerCycle;
1155
1156
% random intervals
1157
if nXs>0
1158
    xIntervals=floor(rand(1,nIntervals)*2*kLength);
1159
    % weed out triple intervals > 2*k
1160
    rogues=1;
1161
    while sum(rogues)
1162
        y=(xIntervals>kLength);
1163
        rogues=(sum([y(1:end-2)' y(2:end-1)' y(3:end)'],2)>2);
1164
        xIntervals(rogues)=floor(rand*2*kLength);
1165
    end
1166
    xIntervals=reshape(xIntervals,nCycles,intervalsPerCycle);
1167
else
1168
    xIntervals=[];
1169
end
1170
1171
% insert constrained (k) intervals
1172
if nKs>0
1173
    switch order
1174
        case 1
1175
            kIntervals=floor(ones(nCycles,nKs)*kLength);
1176
        case 2
1177
            nKs=1; % force this
1178
            kIntervals=floor(rand(nCycles,1)*kLength);
1179
            kIntervals=[kIntervals kLength-kIntervals];
1180
    end
1181
else
1182
    kIntervals=[];
1183
end
1184
1185
% combine fixed and random
1186
intervals=[kIntervals xIntervals(:,nKs+1:end)];
1187
% make a single array;
1188
[r c]=size(intervals);
1189
intervals=reshape(intervals',1,r*c);
1190
1191
% shuffle intervals
1192
if shuffled
1193
    randomNumbers=rand(1,length(intervals));
1194
    [randomNumbers idx]=sort(randomNumbers);
1195
    intervals=intervals(idx);
1196
    idx=intervals>0;
1197
    intervals=intervals(idx);
1198
end
1199
1200
% convert intervals to clicks
1201
clickTimes=cumsum(intervals);
1202
signal(clickTimes)=clickHeight;
1203
signal=signal(1:requiredSignalLength);
1204
% figure(1), clf, plot(signal)
1205
1206
1207
1208
%--------------------------------------------------------------------makeNoise
1209
function noise=makeNoise(globalStimParams, stimComponents)
1210
% FS in Hz, noiseDuration in s, delay in s;
1211
% noise is returned with overall level dB(rms) = amplitudesdB
1212
%
1213
% % You need
1214
%
1215
% stim.type='noise'; % or 'IRN', or 'pinkNoise'
1216
% stim.toneDuration=.05;
1217
% stim.amplitudesdB=50;
1218
% stim.beginSilence=.01;
1219
% stim.endSilence=-1;
1220
% stim.rampOnDur=.002;
1221
% stim.rampOffDur=-1;
1222
%
1223
% % Mandatory structure fields
1224
% globalStimParams.FS=100000;
1225
% globalStimParams.overallDuration=.1;  % s
1226
% globalStimParams.doPlot=1;
1227
% globalStimParams.doPlay=1;
1228
%
1229
% [audio, msg]=stimulusCreate(globalStimParams, stim, );
1230
%
1231
% % local
1232
% stim.type='noise'; % or 'IRN'
1233
%
1234
FS=globalStimParams.FS;
1235
noiseDuration= stimComponents.toneDuration;
1236
npts=round(noiseDuration*FS);
1237
noise=randn(1,npts);    % NB randn (normally distributed)
1238
1239
switch stimComponents.type
1240
    case  'pinkNoise'
1241
        %         noise=UTIL_lowpassFilterFreq(noise, 100, 1/FS);
1242
        noise=UTIL_bandPassFilter(noise, 1, 100, 200, 1/FS,[]);
1243
end
1244
1245
rms=(mean(noise.^2)^.5);  %should be 20 microPascals for 0 dB SPL
1246
adjust=20e-6/rms;
1247
noise=noise*adjust;
1248
rms=(mean(noise.^2)^.5);
1249
amplitude=10.^(stimComponents.amplitudesdB/20);
1250
noise=amplitude*noise;
1251
% rms=(mean(noise.^2)^.5);
1252
% dBnoise=20*log10(rms/20e-6)
1253
1254
1255
%--------------------------------------------------------------------makeWhiteNoise
1256
function noise=makeWhiteNoise(globalStimParams, stimComponents)
1257
% FS in Hz, noiseDuration in s, delay in s;
1258
% noise is bandpass filtered between 100 and 10000 Hz
1259
% spectrum level (dB/Hz) is 40 dB below nominal level.
1260
% noise is returned with dB(rms) = amplitudesdB
1261
%
1262
% % You need
1263
%
1264
% stim.type='noise'; % or 'IRN', or 'pinkNoise'
1265
% stim.toneDuration=.05;
1266
% stim.amplitudesdB=50;
1267
% stim.beginSilence=.01;
1268
% stim.endSilence=-1;
1269
% stim.rampOnDur=.002;
1270
% stim.rampOffDur=-1;
1271
%
1272
% % Mandatory structure fields
1273
% globalStimParams.FS=100000;
1274
% globalStimParams.overallDuration=.1;  % s
1275
% globalStimParams.doPlot=1;
1276
% globalStimParams.doPlay=1;
1277
%
1278
% [audio, msg]=stimulusCreate(globalStimParams, stim, );
1279
%
1280
% % local
1281
% stim.type='noise'; % or 'IRN'
1282
%
1283
FS=globalStimParams.FS;
1284
noiseDuration= stimComponents.toneDuration;
1285
npts=round(noiseDuration*FS);
1286
noise=randn(1,npts);
1287
1288
noise=UTIL_bandPassFilter (noise, 6, 100, 10000, 1/FS, []);
1289
1290
rms=(mean(noise.^2)^.5);  %should be 20 microPascals for 0 dB SPL
1291
adjust=20e-6/rms;
1292
noise=noise*adjust;
1293
rms=(mean(noise.^2)^.5);
1294
amplitude=10.^(stimComponents.amplitudesdB/20);
1295
noise=amplitude*noise;
1296
% rms=(mean(noise.^2)^.5);
1297
% dBnoise=20*log10(rms/20e-6)
1298
1299
1300
%-----------------------------------------------------------------makeIRN
1301
function noise=makeIRN(globalStimParams, stimComponents)
1302
% FS in Hz, noiseDuration in s, delay in s;
1303
% noise is returned with dB(rms) = amplitudesdB
1304
%
1305
% % You need
1306
%
1307
% stim.type='noise'; % or 'IRN', or 'pinkNoise'
1308
% stim.toneDuration=.05;
1309
% stim.amplitudesdB=50;
1310
% stim.beginSilence=.01;
1311
% stim.endSilence=-1;
1312
% stim.rampOnDur=.002;
1313
% stim.rampOffDur=-1;
1314
%
1315
% % Mandatory structure fields
1316
% globalStimParams.FS=100000;
1317
% globalStimParams.overallDuration=.1;  % s
1318
% globalStimParams.doPlot=1;
1319
% globalStimParams.doPlay=1;
1320
%
1321
% [audio, msg]=stimulusCreate(globalStimParams, stim, );
1322
%
1323
% % local
1324
% stim.type='noise'; % or 'IRN'
1325
% % for IRN only
1326
% stim.niterations = 8;   %0 for white noise
1327
% stim.delay = 1/150;
1328
% stim.irnGain = 1;
1329
%
1330
FS=globalStimParams.FS;
1331
noiseDuration= stimComponents.toneDuration;
1332
1333
nIterations=stimComponents.niterations;
1334
if nIterations==0
1335
    % white noise is specified as nIterations=1
1336
    nIterations=1;
1337
    IRNgain=0;
1338
    delay=0.01; % dummy
1339
else
1340
    % IRN
1341
    delay=stimComponents.delay;
1342
    IRNgain=stimComponents.irnGain;
1343
end
1344
1345
npts=round(noiseDuration*FS);
1346
dels=round(delay*FS);
1347
noise=randn(1,npts);
1348
1349
%fringe=nIterations*dels;
1350
%npts=npts+fringe;
1351
1352
for i=1:nIterations,
1353
    dnoise=[noise(dels+1:npts) noise(1:dels)];
1354
    dnoise=dnoise.*IRNgain;
1355
    noise=noise+dnoise;
1356
end;
1357
1358
switch stimComponents.type
1359
    case  'pinkNoise'
1360
        noise=UTIL_lowpassFilterFreq(noise, 10000, 1/FS);
1361
end
1362
1363
rms=(mean(noise.^2)^.5);  %should be 20 microPascals for 0 dB SPL
1364
adjust=20e-6/rms;
1365
noise=noise*adjust;
1366
rms=(mean(noise.^2)^.5);
1367
amplitude=10.^(stimComponents.amplitudesdB/20);
1368
noise=amplitude*noise;
1369
% rms=(mean(noise.^2)^.5);
1370
% dBnoise=20*log10(rms/20e-6)
1371
1372
%------------------------------------------------------------------ makeRPN
1373
function RPN=makeRPN(globalStimParams, stim)
1374
% 'period' is a collection of samples - AAABCD
1375
% you need
1376
%
1377
% stim.type='RPN';
1378
% stim.toneDuration=.2;
1379
% stim.amplitudesdB=50;
1380
% stim.beginSilence=.01;
1381
% stim.rampOnDur=.002;
1382
% stim.rampOffDur=-1;
1383
%
1384
% stim.sampleDuration=.005;  %200 Hz pitch
1385
% stim.nSimilarSamples=5;   % pitch strength
1386
% stim.nIndependentSamples=1% dilutes strength
1387
%
1388
% % Mandatory structure fields
1389
%  globalStimParams.FS=44100;
1390
%  globalStimParams.overallDuration=.21;  % s
1391
%
1392
% globalStimParams.doPlot=1;
1393
% globalStimParams.doPlay=1;
1394
% [audio, msg]=stimulusCreate(globalStimParams, stim);
1395
1396
FS=globalStimParams.FS;
1397
ptsPerSample=floor(stim.sampleDuration*FS);
1398
1399
samplesPerPeriod=stim.nSimilarSamples+stim.nIndependentSamples;
1400
periodDuration=samplesPerPeriod*stim.sampleDuration;
1401
1402
totalNumPeriods=2*floor(stim.toneDuration/periodDuration);  % longer than necessary
1403
if totalNumPeriods<1
1404
    error('stimulusCreate: RPN, stimulus duration needs to be longer')
1405
end
1406
1407
RPN=[];
1408
for j=1:totalNumPeriods
1409
    noise=randn(1,ptsPerSample);
1410
    for i=1:stim.nSimilarSamples
1411
        RPN=[RPN noise];
1412
    end
1413
1414
    for i=1:stim.nIndependentSamples
1415
        noise=randn(1,ptsPerSample);
1416
        RPN=[RPN noise];
1417
    end
1418
end
1419
1420
targetStimulusLength=round(stim.toneDuration/FS);
1421
RPN=RPN(1:floor(stim.toneDuration*FS));     % take enough for stimulus
1422
1423
rms=(mean(RPN.^2)^.5);  %should be 20 microPascals for 0 dB SPL
1424
adjust=20e-6/rms;
1425
RPN=RPN*adjust;
1426
rms=(mean(RPN.^2)^.5);
1427
amplitude=10.^(stim.amplitudesdB/20);
1428
RPN=amplitude*RPN;
1429
% rms=(mean(noise.^2)^.5);
1430
% dBnoise=20*log10(rms/20e-6)
1431
1432
%--------------------------------------------------------------------applyRampOn
1433
function signal=applyRampOn(signal, rampDur, rampOnTime, sampleRate)
1434
%applyRampOn applies raised cosine ramp
1435
%rampOntime is the time at which the ramp begins
1436
%At all other times the mask has a value of 1
1437
%signal=applyRampOn(signal, rampDur, rampOnTime, sampleRate)
1438
1439
rampDurPoints=round(rampDur*sampleRate);
1440
rampOn= (1+cos(pi:pi/(rampDurPoints-1): 2*pi))/2';
1441
1442
sigDurPoints=length(signal);
1443
mask(1:sigDurPoints)=1;
1444
rampOnStartIndex=round(rampOnTime*sampleRate+1);
1445
mask(rampOnStartIndex: rampOnStartIndex+ rampDurPoints-1)=rampOn;
1446
signal=signal.*mask;
1447
%plot(mask)
1448
1449
%--------------------------------------------------------------------applyRampOff
1450
function signal=applyRampOff(signal, rampDur, rampOffTime, sampleRate)
1451
%applyRampOn applies raised cosine squared ramp
1452
%rampOffTime is the time at which the ramp begins
1453
%At all other times the mask has a value of 1
1454
% signal=applyRampOff(signal, rampDur, rampOffTime, sampleRate)
1455
1456
rampDurPoints=round(rampDur*sampleRate);
1457
rampOff= (1+cos(0:pi/(rampDurPoints-1): pi))/2';
1458
1459
sigDurPoints=length(signal);
1460
mask=ones(1,sigDurPoints);
1461
rampOffStartIndex=round(rampOffTime*sampleRate+1);
1462
mask(rampOffStartIndex: round(rampOffStartIndex+ rampDurPoints-1))=rampOff;
1463
if length(mask)>sigDurPoints, mask=mask(1:sigDurPoints); end
1464
signal=signal.*mask;
1465
%plot(mask)
1466
1467
function signal=applyGaussianRamps(signal, sigma, sampleRate)
1468
dt=1/sampleRate;
1469
time=dt:dt:dt*length(signal);
1470
ramp=1-exp(-time.^2/(2*sigma^2));
1471
% apply onset ramp
1472
signal=signal.*ramp;
1473
% apply offset ramp
1474
ramp=fliplr(ramp);
1475
signal=signal.*ramp;
1476
1477
1478
1479
%--------------------------------------------------------------------checkDescriptors
1480 30:1a502830d462 rmeddis
function [globalStimParams, stimComponents]=...
1481
    checkDescriptors(globalStimParams, stimComponents)
1482 0:f233164f4c86 rmeddis
1483
try
1484
    % if FS exists, it takes priority
1485
    globalStimParams.dt=1/globalStimParams.FS;
1486
catch
1487
    % otherwise set FS using dt
1488
    globalStimParams.FS=1/globalStimParams.dt;
1489
end
1490
1491 30:1a502830d462 rmeddis
sampleRate=globalStimParams.FS;
1492
1493
globalStimParams.nSignalPoints=...
1494
    round(globalStimParams.overallDuration* sampleRate);
1495 0:f233164f4c86 rmeddis
1496
% optional field (ears)
1497
try
1498
    globalStimParams.ears;
1499
catch
1500
    % default: dichotic.
1501
    globalStimParams.ears='dichotic';
1502
end
1503
1504
% audioOutCorrection is optional
1505
% audioOutCorrection is a scalar for reducing the sound
1506
try
1507
    globalStimParams.audioOutCorrection;
1508
catch
1509
    %      set to 1 if omitted
1510
    globalStimParams.audioOutCorrection=1;
1511
end
1512
1513
try
1514
    globalStimParams.doPlay;
1515
catch
1516
    % default plays sound only if explicitly requested
1517
    globalStimParams.doPlay=0;
1518
end
1519
1520
try
1521
    globalStimParams.doPlot;
1522
catch
1523
    % no plotting unless explicitly requested
1524
    globalStimParams.doPlot=0;
1525
end
1526
1527
[ears nComponentSounds]=size(stimComponents);
1528
1529
for ear=1:2 % 1=left/ 2=right
1530
1531
    % create a list of components whose type is specified
1532
    % if no type is specified assume that it is an empty specification
1533
    % this is allowed
1534
    validStimComponents=[];
1535
    for i=1:nComponentSounds
1536
        try
1537
            if ~isempty(stimComponents(ear,i).type)
1538
                validStimComponents=[validStimComponents i];
1539
            end
1540
        catch
1541
        end
1542
    end
1543
1544
    for componentNo=validStimComponents
1545
        % If no AM filed is present, create it for completeness
1546
        if ~isfield(stimComponents(ear,componentNo),'AMfrequency') |...
1547
                ~isfield(stimComponents(ear,componentNo),'AMdepth')
1548
            stimComponents(ear,componentNo).AMfrequency=0;
1549
            stimComponents(ear,componentNo).AMdepth=0;
1550
        end
1551
1552
        % all signals must have durations, amplitudes and ramps
1553
        if ...
1554
                isempty(stimComponents(ear,componentNo).type) |...
1555
                isempty(stimComponents(ear,componentNo).toneDuration) |...
1556
                isempty(stimComponents(ear,componentNo).amplitudesdB) |...
1557
                isempty(stimComponents(ear,componentNo).rampOnDur)
1558
            descriptorError( 'missing stimComponent descriptor', stimComponents, ear, componentNo)
1559
        end
1560
1561
        try, stimComponents(ear,componentNo).endSilence; catch, stimComponents(ear,componentNo).endSilence=-1; end
1562
1563
        % ramp checks do not apply to file input
1564
        if ~strcmp(stimComponents(ear,componentNo).type, 'file')
1565
            % match offset ramp to onset if not explicitly specified
1566
            if stimComponents(ear,componentNo).rampOffDur==-1,
1567
                stimComponents(ear,componentNo).rampOffDur=stimComponents(ear,componentNo).rampOnDur;
1568
            end
1569
            % ramps must be shorter than the stimulus
1570
            if stimComponents(ear,componentNo).rampOffDur> stimComponents(ear,componentNo).toneDuration | ...
1571
                    stimComponents(ear,componentNo).rampOnDur> stimComponents(ear,componentNo).toneDuration
1572
                descriptorError( 'ramp longer than sound component', stimComponents, ear, componentNo)
1573
            end
1574
        end
1575
1576
        % end silence is measured to fit into the global duration
1577
        if stimComponents(ear,componentNo).endSilence==-1,
1578 30:1a502830d462 rmeddis
            stimComponents(ear,componentNo).endSilence=...
1579
                globalStimParams.overallDuration-...
1580
                stimComponents(ear,componentNo).beginSilence -...
1581
                stimComponents(ear,componentNo).toneDuration;
1582
1583
            endSilenceNpoints=stimComponents(ear,componentNo).endSilence...
1584
                *sampleRate;
1585
        end
1586
        if stimComponents(ear,componentNo).endSilence<0
1587
            globalStimParams
1588
            descriptorError( 'component durations greater than overallDuration', stimComponents, ear, componentNo)
1589 0:f233164f4c86 rmeddis
        end
1590
1591
        % check overall duration of this component against global duration
1592
        totalDuration= ...
1593
            stimComponents(ear,componentNo).beginSilence+...
1594
            stimComponents(ear,componentNo).toneDuration+...
1595
            stimComponents(ear,componentNo).endSilence;
1596
1597
        % avoid annoying error message for single stimulus component
1598
        if ears==1 && nComponentSounds==1
1599
            globalStimParams.overallDuration= totalDuration;
1600
        end
1601
1602
        % check total duration
1603 30:1a502830d462 rmeddis
        totalSignalPoints= round(totalDuration* sampleRate);
1604 0:f233164f4c86 rmeddis
        if totalSignalPoints  >globalStimParams.nSignalPoints
1605
            descriptorError( 'Signal component duration does not match overall duration ', stimComponents, ear, componentNo)
1606
        end
1607
1608
        if isfield(stimComponents(ear,componentNo), 'filter')
1609
            if ~isequal(length(stimComponents(ear,componentNo).filter), 3)
1610
                descriptorError( 'Filter parameter must have three elements ', stimComponents, ear, componentNo)
1611
            end
1612
        end
1613
    end % component
1614
    % ??
1615
    if strcmp(globalStimParams.ears,'monoticL') | strcmp(globalStimParams.ears, 'monoticR'), break, end
1616
end		% ear
1617
1618
1619
%-------------------------------------------------------------------- descriptorError
1620
function descriptorError( msg, stimComponents, ear, componentNo)
1621
stimComponents(ear, componentNo)
1622
1623
disp(' *********** **************** ************')
1624
disp([ '...Error in stimComponents description: '])
1625
disp([msg ])
1626
disp(['Ear = ' num2str(ear) ' component No ' num2str(componentNo)])
1627
disp(' *********** **************** ************')
1628
error('myError ')
1629
1630
1631
%-------------------------------------------------------------------- normalize
1632
function [normalizedSignal, gain]= normalize(signal)
1633
% normalize (signal)
1634
maxSignal=max(max(signal));
1635
minSignal=min(min(signal));
1636
if -minSignal>maxSignal, normalizingFactor=-minSignal; else normalizingFactor=maxSignal; end
1637
normalizingFactor=1.01*normalizingFactor;
1638
gain= 20*log10(normalizingFactor);
1639
normalizedSignal=signal/normalizingFactor;
1640
1641
1642
%--------------------------------------------------------------------Butterworth
1643
function y=Butterworth (x, dt, fl, fu, order)
1644
% Butterworth (x, dt, fu, fl, order)
1645
% Taken from Yuel and beauchamp page 261
1646
% NB error in their table for K (see their text)
1647
% x is original signal
1648
% fu, fl upper and lower cutoff
1649
% order is the number of times the filter is applied
1650
1651
q=(pi*dt*(fu-fl));
1652
J=1/(1+ cot(q));
1653
K= (2*cos(pi*dt*(fu+fl)))/(1+tan(q)*cos(q));
1654
L= (tan(q)-1)/(tan(q)+1);
1655
b=[J -J];
1656
a=[1 -K  -L];
1657
for i=1:order
1658
    y=filter(b, a, x);
1659
    x=y;
1660
end
1661
1662
1663
% -------------------------------------------------------- UTIL_amp2dB
1664
function [y] = UTIL_amp2dB (x, ref)
1665
% Calculates a dB (ref. ref) value 'y' from a peak amplitude number 'x'.
1666
% if ref omitted treat as dB
1667
% Check the number of arguments that are passed in.
1668
if (nargin < 2)
1669
    ref=1;
1670
end
1671
if (nargin > 2)
1672
    error ('Too many arguments');
1673
end
1674
1675
% Check arguments.
1676
if x < 0.0
1677
    error ('Can not calculate the log10 of a negative number');
1678
elseif x == 0.0
1679
    warning ('log10 of zero.  The result is set to -1000.0');
1680
    y = -1000.0;
1681
else
1682
    % Do calculations.
1683
    y = 20.0 * log10(x/(sqrt(2)*ref));
1684
1685
end
1686
1687
%-------------------------------------------------------------------- FFT
1688
function showFFT (getFFTsignal, dt)
1689
color='r';
1690
figure(2), clf,
1691
hold off
1692
1693
% trim initial silence
1694
idx=find(getFFTsignal>0);
1695
if ~isempty(idx)
1696
    getFFTsignal=getFFTsignal(idx(1):end);
1697
end
1698
%trim final silence
1699
getFFTsignal=getFFTsignal(end:-1:1);
1700
idx=find(getFFTsignal>0);
1701
if ~isempty(idx)
1702
    getFFTsignal=getFFTsignal(idx(1):end);
1703
    getFFTsignal=getFFTsignal(end:-1:1);
1704
end
1705
1706
% Analyse make stimComponents length a power of 2
1707
x=length(getFFTsignal);
1708
squareLength=2;
1709
while squareLength<=x
1710
    squareLength=squareLength*2;
1711
end
1712
squareLength=round(squareLength/2);
1713
getFFTsignal=getFFTsignal(1:squareLength);
1714
n=length(getFFTsignal);
1715
1716
minf=100; maxf=20000;
1717
1718
fft_result = fft(getFFTsignal, n);				% Compute FFT of the input signal.
1719
fft_power = fft_result .* conj(fft_result);% / n;	% Compute power spectrum.  Dividing by 'n' we get the power spectral density.
1720
fft_phase = angle(fft_result);			% Compute the phase spectrum.
1721
1722
frequencies = (1/dt)*(1:n/2)/n;
1723
fft_power=fft_power(1:length(fft_power)/2); % remove mirror frequencies
1724
fft_phase=fft_phase(1:length(fft_phase)/2); % remove mirror frequencies
1725
fft_powerdB = UTIL_amp2dB (fft_power, max(fft_power)); % convert to dB
1726
%     jags=find(diff(fft_phase)>0); % unwrap phase
1727
%     for i=jags, fft_phase(i+1:end)=fft_phase(i+1:end)-2*pi; end
1728
1729
xlim([minf maxf])
1730
semilogx(frequencies, fft_powerdB-max(fft_powerdB), color)
1731
ylim([ -20 5])
1732
1733
1734
1735
function y=UTIL_lowpassFilterFreq(x, cutOffFrequency, dt)
1736
% UTIL_lowpassFilterFreq multi-channel filter
1737
%
1738
% Usage:
1739
% output=UTIL_lowpassFilterFreq(input, cutOffFrequency, dt)
1740
%
1741
% cutoff should be around 100 Hz for audio work
1742
% dt should be <1/50000 s for audio work
1743
%
1744
% Attenuation 	       is - 6 dB per octave above cutoff.
1745
1746
1747
sampleRate=1/dt;
1748
1749
if 4*cutOffFrequency>sampleRate
1750
    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' ])
1751
    cutOffFrequency=sampleRate/4;
1752
end
1753
1754
tau=1/(2*pi*cutOffFrequency);
1755
1756
y=zeros(size(x));
1757
[numChannels numPoints]=size(x);
1758
for i=1:numChannels
1759
    y(i,:)=filter([dt/tau], [1 -(1-dt/tau)], x(i,:));
1760
end
1761