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 @ 0:f233164f4c86

History | View | Annotate | Download (56.3 KB)

1
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
    wavplay(audio,globalStimParams.FS)
504
end
505
% all Done
506
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
507

    
508

    
509

    
510
%---------------------------------------------------- maketone
511
function tone=maketone(globalStimParams, stimComponents)
512
% maketone generates a stimComponents tone
513
% tone=maketone(dt, frequencies, toneDuration, dBSPL, phases)
514
% tone is returned in Pascals
515
% frequencies is a list of frequencies
516
% phase is list of phases or 'sin', 'cos', 'alt'
517
%
518
% defaults:
519
%  phase = sin
520
%  dBSPL=56 dB SPL
521

    
522
dt=globalStimParams.dt;
523
frequencies=stimComponents.frequencies;
524
toneDuration=stimComponents.toneDuration;
525
dBSPL=stimComponents.amplitudesdB;
526
phases=stimComponents.phases;
527

    
528
if ischar(phases)
529
    switch phases
530
        case 'sin'
531
            phases= zeros(1,length(frequencies));
532
        case 'cos'
533
            phases= pi/2*ones(1,length(frequencies));
534
        case 'alt'
535
            phases= repmat([0 pi/2], 1, floor(length(frequencies)/2));
536
            if length(phases)<length(frequencies)
537
                phases=[phases 0];
538
            end
539
        case {'ran', 'rand'}
540
            phases= 2*pi*rand(1,length(frequencies));
541
    end
542
end
543

    
544
if length(phases)==1, phases=repmat(phases(1), 1, length(frequencies)); end
545
if length(phases)<length(frequencies)
546
    error('makeTone:phase specification must have the same length as frequencies')
547
end
548

    
549
if length(dBSPL)==1, dBSPL=repmat(dBSPL(1), 1, length(frequencies)); end
550
if length(dBSPL)<length(dBSPL)
551
    error('makeTone:dBSPL specification must have the same length as frequencies')
552
end
553

    
554
time=dt:dt:toneDuration;
555
amplitudes=28e-6* 10.^(dBSPL/20);
556

    
557
tone=zeros(size(time));
558
for i=1:length(frequencies)
559
    frequency=frequencies(i);
560
    phase=phases(i);
561
    tone=tone+amplitudes(i)*sin(2*pi*frequency*time+phase);
562
end
563

    
564
% figure(1), plot(time, signal)
565

    
566

    
567
%---------------------------------------------------- makeOHIOtones
568
function stimulus=makeOHIOtones(globalStimParams, stimComponents)
569

    
570
% Generates a stimulus consisting of one or more 10-ms tones
571
% Tones are presented at 10-ms intervals
572
% Each tone has its own amplitude and its own ramp.
573

    
574
frequencies=stimComponents.frequencies;
575
amplitudesdB=stimComponents.amplitudesdB;
576
nFrequencies=length(frequencies);
577

    
578
dt=globalStimParams.dt;
579
toneDuration=.010;
580
time=dt:dt:toneDuration;
581

    
582
rampDuration=stimComponents.rampOnDur;
583
rampTime=dt:dt:rampDuration;
584
ramp=[0.5*(1+cos(2*pi*rampTime/(2*rampDuration)+pi)) ones(1,length(time)-length(rampTime))];
585
ramp=ramp.*fliplr(ramp);
586

    
587
stimulus= zeros(1,round((toneDuration+globalStimParams.beginSilences(end))/dt)+1);
588

    
589
for i=1:nFrequencies
590
    toneBeginPTR=round(globalStimParams.beginSilences(i)/dt)+1;
591

    
592
    frequency=frequencies(i);
593
    dBSPL=amplitudesdB(i);
594
    amplitude=28e-6* 10.^(dBSPL/20);
595
    tone=amplitude*sin(2*pi*frequency*time);
596
    tone=tone.*ramp;
597
    stimulus(toneBeginPTR:toneBeginPTR+length(tone)-1)=stimulus(toneBeginPTR:toneBeginPTR+length(tone)-1)+tone;
598
end
599
% figure(2), plot( stimulus')
600

    
601

    
602
%---------------------------------------------------- makeFMtone
603
function tone=makeFMtone(globalStimParams, stimComponents)
604
% maketone generates a stimComponents tone
605
% tone=maketone(dt, frequencies, toneDuration, dBSPL, phases)
606
% tone is returned in Pascals
607
% frequencies is a list of frequencies
608
% phase is list of phases or 'sin', 'cos', 'alt'
609
%
610
% defaults:
611
%  phase = sin
612
%  dBSPL=56 dB SPL
613

    
614
dt=globalStimParams.dt;
615
frequencies=stimComponents.frequencies;
616
toneDuration=stimComponents.toneDuration;
617
dBSPL=stimComponents.amplitudesdB;
618
phases=stimComponents.phases;
619
fmDepth=stimComponents.fmDepth;
620
fmFrequency=stimComponents.fmFrequency;
621

    
622
if ischar(phases)
623
    switch phases
624
        case 'sin'
625
            phases= zeros(1,length(frequencies));
626
        case 'cos'
627
            phases= pi/2*ones(1,length(frequencies));
628
        case 'alt'
629
            phases= repmat([0 pi/2], 1, floor(length(frequencies)/2));
630
            if length(phases)<length(frequencies)
631
                phases=[phases 0];
632
            end
633
    end
634
end
635

    
636
if length(phases)==1, phases=repmat(phases(1), 1, length(frequencies)); end
637
if length(phases)<length(frequencies)
638
    error('makeTone:phase specification must have the same length as frequencies')
639
end
640

    
641
if length(dBSPL)==1, dBSPL=repmat(dBSPL(1), 1, length(frequencies)); end
642
if length(dBSPL)<length(dBSPL)
643
    error('makeTone:dBSPL specification must have the same length as frequencies')
644
end
645

    
646
time=dt:dt:toneDuration;
647
amplitudes=28e-6* 10.^(dBSPL/20);
648

    
649
tone=zeros(size(time));
650
for i=1:length(frequencies)
651
    frequency=frequencies(i);
652
    phase=phases(i);
653
    tone=tone+amplitudes(i)*sin(2*pi*frequency*time+phase + fmDepth*sin(2*pi*fmFrequency*time));
654
end
655

    
656
% figure(1), plot(time, signal)
657

    
658
%----------------------------------------------------makeTransposedStimulus
659
function [transposedStimulus]=makeTransposedStimulus(globalStimParams, stim)
660
% globalStimParams.FS=100000;
661
% globalStimParams.overallDuration=.1;  % s
662
% globalStimParams.doPlot=1;
663
%
664
% stim.type='transposedStimulus';
665
% stim.phases='sin';
666
% stim.toneDuration=.05;;
667
% stim.frequencies=500;
668
% stim.amplitudesdB=50;
669
% stim.beginSilence=.01;
670
% stim.endSilence=-1;
671
% stim.rampOnDur=.002;
672
% stim.rampOffDur=-1;
673
%
674
% stim.transCarrierFreq=4000;
675
% stim.transModFreq=100;
676

    
677
transposedStimulus=[];
678
% make envelope of transposed tone
679
for i=1:length(stim.transModFreq)
680
    stim.type='tone';
681
    stim.frequencies=stim.transModFreq(i);
682
    stim.endsilence=-1;     stim.beginSilence=0;
683
    [envelope, msg]=stimulusCreate(globalStimParams, stim);    % NB recursive
684
    envelope=envelope(:,1);   % mono
685
    % HW rectify
686
    envelope(find(envelope<0))=0;
687
    % LP filter
688
    maxEnvelope=max(envelope);
689
    envelope=UTIL_Butterworth (envelope, globalStimParams.dt, 10, .2*stim.transModFreq(i), 2);
690
    envelope=envelope*maxEnvelope/max(envelope);
691

    
692
    % make the carrier
693
    stim.frequencies=stim.transCarrierFreq(i);
694
    stim.endsilence=-1;    stim.beginSilence=0;
695
    [audio, msg]=stimulusCreate(globalStimParams, stim);
696
    carrier=audio(:,1);
697
    x= (carrier.*envelope)';
698
    % base amplitude on peak of unmodulated carrier
699
    x=x/max(carrier);
700
    transposedStimulus=[transposedStimulus; x];
701
end
702
transposedStimulus=sum(transposedStimulus,1);
703
% clf,plot(transposedStimulus)
704

    
705
%--------------------------------------------------------------------makeClicks
706
function clickTrain=makeClicks(globalStimParams, stimComponents)
707
% makeClicks(F0, clickTimes, duration, FS);
708
% F0 specifies the repetition rate of the click sequence
709
% If F0=-1, a single click is generated at the start of the duration of the signal
710
%
711
% clickTimes a are fractions of the period
712
%  and specify when the click appears in the period
713
% A click time of 1 is reset to zero.
714
% if the clicktime plus the click width is greater than the period, no click is generated
715
% clicks are treated as 20 microPascal high before amplification
716
%  unless otherwise specified in stimComponents.clickHeight
717
% click width is dt unless specified in stimComponents.clickWidth
718
%
719
% for regular pulse train set clicktimes=1 or zero;
720
% FS is the sampling interval;
721
% CarlyonClickTrain(100, [.4 1], 40, 441000);
722

    
723
FS=globalStimParams.FS; % sample rate
724
dt=1/FS;
725

    
726
try,clickWidth=stimComponents.clickWidth;catch, clickWidth=dt; end
727
try,clickHeight=stimComponents.clickHeight; catch, clickHeight=28e-6 * 10^(stimComponents.amplitudesdB/20); end
728
try, clickTimes=stimComponents.clickTimes; catch, clickTimes=1; end
729

    
730
% clickTimes are the times in a cycle that the click
731
% occurs
732
checkClickTimes=clickTimes-1;
733
if max(checkClickTimes)>1
734
    msg= 'click times must be <= than 1 (period)';
735
    return
736
end
737

    
738
if clickWidth>=1/stimComponents.clickRepeatFrequency
739
    msg= 'click width is too great for frequency';
740
    return
741
end
742

    
743
duration=stimComponents.toneDuration;
744
totalLength=round(stimComponents.toneDuration/dt);
745
F0=stimComponents.clickRepeatFrequency;
746
F0=round(F0/dt)*dt;
747
if F0==-1 % single click required
748
    F0=1/duration;
749
    repetitions=1;
750
    clickStartTimes=1; %clicktimes are fractions of a period
751
else
752
    repetitions=round(F0*duration)-1;
753
    duration=repetitions/F0;
754
    clickStartTimes=clickTimes;
755
end
756
% if a clickTime=1 (end of duty period) set it to the beginning
757
clickStartTimes(clickStartTimes==1)=0;
758

    
759
period=1/F0;
760
time=dt:dt:period;
761
nPoints=length(time);
762
signal=zeros(1,nPoints);
763
dBSPL=stimComponents.amplitudesdB;
764

    
765
% compute click train for a single cycle
766
clickWidthNpoints=round(clickWidth*FS);
767
for i=1:length(clickStartTimes)
768
    %     if clickStartTimes(i)<clickWidth
769
    %         clickStartTimes(i)=dt;
770
    %     end
771
    clickTime=round(period*clickStartTimes(i)/dt -dt);
772
    % clicks are treated as 20 microPascal high
773
    if clickTime+clickWidthNpoints<length(signal)
774
        signal(clickTime+1:clickTime+clickWidthNpoints)=clickHeight;
775
    end
776
end
777

    
778
clickTrain=repmat(signal, 1, repetitions);
779

    
780
if length(clickTrain)>totalLength
781
    clickTrain=clickTrain(1:totalLength);
782
elseif length(clickTrain)<totalLength
783
    timeToAdd=zeros(1,round((totalLength-length(clickTrain))));
784
    clickTrain=[clickTrain timeToAdd];
785
    % figure (1), plot(clickTrain)
786
end
787

    
788
%----------------------------------------------------------------makePressnitzerClicks
789
function signal=makePressnitzerClicks(globalStimParams, stimComponents)
790
% PressnitzerClicks(k,duration,dt)
791
% Generates a sequence of clicks with intervals kxx
792
%  where x= rand*k/2
793
% This is not the same as Kaernbach and Demany clicks
794

    
795
FS=globalStimParams.FS; % sample rate
796
dt=1/FS;
797

    
798
% obligatory parameter
799
try
800
    k=stimComponents.k;
801
catch
802
    error('PressnitzerClicks: field ''k'' is missing from stimcomponent')
803
end
804

    
805
% optional parameters
806
if isfield(stimComponents,'clickWidth')
807
    clickWidth=stimComponents.clickWidth;
808
else
809
    clickWidth=dt;
810
end
811
clickWidthNpoints=round(clickWidth*FS);
812

    
813
if isfield(stimComponents,'clickHeight')
814
    clickHeight=stimComponents.clickHeight;
815
else
816
    clickHeight=28e-6 * 10^(stimComponents.amplitudesdB/20);
817
end
818

    
819
duration=stimComponents.toneDuration;
820

    
821
signalLength=round(duration/dt);
822
signal=zeros(1,signalLength);
823
kInterval=round(k/dt);
824
halfk=k/2;
825
signal(1)=clickHeight;
826
timeIdx=0;
827
while timeIdx<signalLength
828
    % first interval = k
829
    clickTime=timeIdx+kInterval;
830
    signal(clickTime:clickTime+clickWidthNpoints)=clickHeight;
831
    timeIdx=timeIdx+kInterval;
832

    
833
    % second interval = 0 : k/2
834
    intervalx1=round(rand*halfk/dt);
835
    clickTime=timeIdx+intervalx1;
836
    signal(clickTime:clickTime+clickWidthNpoints)=clickHeight;
837
    timeIdx=timeIdx+intervalx1;
838

    
839
    % third 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
end
845

    
846
signal=signal(1:signalLength);
847
% figure(1),	plot(dt:dt:duration,signal)
848

    
849
%----------------------------------------------------------------makePressnitzerABXClicks
850
function signal=makePressnitzerABxClicks(globalStimParams, stimComponents)
851
% Generates a sequence of clicks with intervals ABx
852
% AB interval is 2*k
853
% where A= rand* k
854
%       B= k-A
855
%       x= k/2
856
% These are second order clicks
857

    
858
FS=globalStimParams.FS; % sample rate
859
dt=1/FS;
860

    
861
% obligatory parameter
862
try
863
    k=stimComponents.k;
864
catch
865
    error('PressnitzerClicks: field ''k'' is missing from stimcomponent')
866
end
867

    
868
% optional parameters
869
if isfield(stimComponents,'clickWidth')
870
    clickWidth=stimComponents.clickWidth;
871
else
872
    clickWidth=dt;
873
end
874
clickWidthNpoints=round(clickWidth*FS);
875

    
876
if isfield(stimComponents,'clickHeight')
877
    clickHeight=stimComponents.clickHeight;
878
else
879
    clickHeight=28e-6 * 10^(stimComponents.amplitudesdB/20);
880
end
881

    
882
duration=stimComponents.toneDuration;
883

    
884
signalLength=round(duration/dt);
885
signal=zeros(1,2*signalLength); % allow for overrun
886
ABinterval=k/dt;                % i.e. the number of dt steps
887
randomInterval=ABinterval/2;
888
signal(1)=clickHeight;
889
time=0;
890
while time<signalLength
891
    % first interval = A
892
    intervalA=rand*ABinterval;
893
    clickTime=round(time+intervalA)+1;   % +1 avoids zero index
894
    signal(clickTime:clickTime+clickWidthNpoints)=clickHeight;
895
    time=time+intervalA;
896

    
897
    % second interval = B
898
    intervalB=ABinterval-intervalA;
899
    clickTime=round(time+intervalB)+1;
900
    signal(clickTime:clickTime+clickWidthNpoints-1)=clickHeight;
901
    time=time+intervalB;
902

    
903
    % third interval = 0 : k/2
904
    intervalx1=rand*randomInterval;    % mean random interval=k
905
    clickTime=round(time+intervalx1)+1;
906
    signal(clickTime:clickTime+clickWidthNpoints-1)=clickHeight;
907
    time=time+intervalx1;
908
end
909

    
910
signal=signal(1:signalLength);
911
% figure(1),	plot(dt:dt:duration,signal)
912

    
913
%-----------------------------------------------------makeABxClicks
914
function signal=makeABxClicks(globalStimParams, stimComponents)
915
% Generates a sequence of clicks with intervals ABx
916
% AB interval is 2*k
917
% where A= rand* k
918
%       B= k-A
919
%       x= rand*2*k
920
% These are second order clicks
921

    
922
FS=globalStimParams.FS; % sample rate
923
dt=1/FS;
924

    
925
% obligatory parameter
926
try
927
    k=stimComponents.k;
928
catch
929
    error('PressnitzerClicks: field ''k'' is missing from stimcomponent')
930
end
931

    
932
% optional parameters
933
if isfield(stimComponents,'clickWidth')
934
    clickWidth=stimComponents.clickWidth;
935
else
936
    clickWidth=dt;
937
end
938
clickWidthNpoints=round(clickWidth*FS);
939

    
940
if isfield(stimComponents,'clickHeight')
941
    clickHeight=stimComponents.clickHeight;
942
else
943
    clickHeight=28e-6 * 10^(stimComponents.amplitudesdB/20);
944
end
945

    
946
duration=stimComponents.toneDuration;
947

    
948
signalLength=round(duration/dt);
949
signal=zeros(1,2*signalLength); % allow for overrun
950
ABinterval=2*k/dt;
951
randomInterval=ABinterval;
952
signal(1)=clickHeight;
953
timeIdx=0;
954
while timeIdx<signalLength
955
    % first interval = A
956
    intervalA=round(rand*ABinterval);
957
    clickTime=timeIdx+intervalA+1;
958
    signal(clickTime:clickTime+clickWidthNpoints-1)=clickHeight;
959
    timeIdx=timeIdx+intervalA;
960

    
961
    % second interval = B
962
    intervalB=round(ABinterval-intervalA);
963
    clickTime=timeIdx+intervalB;
964
    signal(clickTime:clickTime+clickWidthNpoints-1)=clickHeight;
965
    timeIdx=timeIdx+intervalB;
966

    
967
    % third interval = 0 : k
968
    intervalx1=round(rand*randomInterval);    % mean random interval=k
969
    clickTime=timeIdx+intervalx1;
970
    signal(clickTime:clickTime+clickWidthNpoints-1)=clickHeight;
971
    timeIdx=timeIdx+intervalx1;
972
end
973

    
974
signal=signal(1:signalLength);
975
% figure(1),	plot(dt:dt:duration,signal)
976

    
977
%----------------------------------------------------------------makeYostClicks
978
function signal=makeYostClicks(globalStimParams, stimComponents)
979
% Generates a shuffled sequence of clicks with intervals kxxxx
980
%  where max(x)= 2*k
981
%  and there are n occurrences of x
982
% this section requires:
983
%  stimComponents.k
984
%  stimComponents.nXs
985
%  stimComponents.toneDuration
986
% optional:
987
%  stimComponents.clickWidth	%useful because width depends on dt
988
%  stimComponents.clickHeight	%best left to amplitude rescaling later
989

    
990
FS=globalStimParams.FS; % sample rate
991
dt=1/FS;
992

    
993
% obligatory parameters
994
try
995
    k=stimComponents.k;
996
catch
997
    error('makeYostClicks: field ''k'' is missing from stimComponents')
998
end
999

    
1000
try
1001
    nXs=stimComponents.nXs;
1002
catch
1003
    error('makeYostClicks: field ''nXs'' is missing from stimComponents')
1004
end
1005

    
1006
try
1007
    shuffled=stimComponents.shuffled;
1008
catch
1009
    error('makeYostClicks: field ''shuffled'' is missing from stimComponents')
1010
end
1011

    
1012
try
1013
    duration=stimComponents.toneDuration;
1014
catch
1015
    error('makeYostClicks: field ''toneDuration'' is missing from stimComponents')
1016
end
1017

    
1018
% optional parameters
1019
if isfield(stimComponents,'clickWidth')
1020
    clickWidth=stimComponents.clickWidth;
1021
else
1022
    clickWidth=dt;
1023
end
1024
clickWidthNpoints=round(clickWidth*FS);
1025

    
1026
if isfield(stimComponents,'clickHeight')
1027
    clickHeight=stimComponents.clickHeight;
1028
else
1029
    clickHeight=28e-6 * 10^(stimComponents.amplitudesdB/20);
1030
end
1031

    
1032
kInterval=round(k/dt);
1033
twoK=k*2;				% max width of x interval
1034

    
1035
signalLength=round(duration/dt);
1036
signal=zeros(1,signalLength);
1037

    
1038
timeIdx=0;
1039
intervalCount=0;
1040
while timeIdx<signalLength
1041
    timeIdx=timeIdx+kInterval;
1042
    if timeIdx>signalLength, break,end
1043
    intervalCount=intervalCount+1;
1044
    intervals(intervalCount)=kInterval;
1045

    
1046
    % repeat x intervals as required
1047
    if nXs>0
1048
        for nX=1:nXs
1049
            xInterval=round(rand*twoK/dt);
1050
            timeIdx=timeIdx+xInterval;
1051
            if timeIdx>signalLength, break,end
1052
            intervalCount=intervalCount+1;
1053
            intervals(intervalCount)=xInterval;
1054
        end
1055
    end
1056
    if timeIdx>signalLength, break,end
1057
end
1058

    
1059
% shuffle intervals
1060
if shuffled
1061
    randomNumbers=rand(1,length(intervals));
1062
    [randomNumbers idx]=sort(randomNumbers);
1063
    intervals=intervals(idx);
1064
    idx=intervals>0;
1065
    intervals=intervals(idx);
1066
end
1067

    
1068
intervalCount=length(intervals);
1069
signal(1)=clickHeight;
1070
clickTime=0;
1071
for i=1:intervalCount
1072
    clickTime=clickTime+intervals(i);
1073
    signal(clickTime:clickTime+clickWidthNpoints)=clickHeight;
1074
end
1075
signal=signal(1:signalLength);
1076
%  figure(1),	plot(dt:dt:duration,signal)
1077

    
1078
%--------------------------------------------------------------------makeKxxClicks
1079
function signal=makeKxxClicks(globalStimParams, stimComponents)
1080
% Click train consists of kkxxx.. sequences
1081
% k is the duration of a fixed interval (seconds)
1082
% random intervals are distributed 0 : 2* k (NB not like Pressnitzer clicks)
1083
% nKs is the number of successive 'k' intervals
1084
% nXs is the number of random intervals between k sequences
1085
% sequences of 3 x intervals > k are replaced with new sequences
1086
% shuffled causes all intervals to be reordered randomly
1087
% NB 1/k is the mean click rate
1088

    
1089
FS=globalStimParams.FS; % sample rate
1090
dt=1/FS;
1091

    
1092
try
1093
    k=stimComponents.k;		% duration (s) of fixed intervals
1094
catch
1095
    error('makeYostClicks: field ''k'' is missing from stimComponents')
1096
end
1097

    
1098
try
1099
    duration=stimComponents.toneDuration;
1100
catch
1101
    error('makeYostClicks: field ''duration'' is missing from stimComponents')
1102
end
1103

    
1104
if isfield(stimComponents,'clickWidth')
1105
    clickWidth=stimComponents.clickWidth;
1106
else
1107
    clickWidth=dt;
1108
end
1109

    
1110
if isfield(stimComponents,'clickHeight')
1111
    clickHeight=stimComponents.clickHeight;
1112
else
1113
    clickHeight=28e-6 * 10^(stimComponents.amplitudesdB/20);
1114
end
1115

    
1116

    
1117
if isfield(stimComponents,'order')
1118
    order=stimComponents.order;
1119
else
1120
    order=1;
1121
end
1122

    
1123
if isfield(stimComponents,'nKs')
1124
    nKs=stimComponents.nKs;
1125
else
1126
    nKs=1;
1127
end
1128

    
1129
if isfield(stimComponents,'nXs')
1130
    nXs=stimComponents.nXs;
1131
else
1132
    nXs=1;
1133
end
1134

    
1135
if isfield(stimComponents,'shuffled')
1136
    shuffled=stimComponents.shuffled;
1137
else
1138
    shuffled=1;
1139
end
1140

    
1141
kLength=round(k/dt);    % fixed interval
1142
xLength=2*kLength;      % maximum random interval
1143
requiredSignalLength=round(duration/dt);
1144
intervalsPerCycle=(nKs+nXs);
1145
cycleLength=nKs*kLength+nXs*xLength;
1146
% more cycles to allow for uncertainty
1147
nCycles=5*round(requiredSignalLength/cycleLength);
1148
nIntervals=nCycles*intervalsPerCycle;
1149

    
1150
% random intervals
1151
if nXs>0
1152
    xIntervals=floor(rand(1,nIntervals)*2*kLength);
1153
    % weed out triple intervals > 2*k
1154
    rogues=1;
1155
    while sum(rogues)
1156
        y=(xIntervals>kLength);
1157
        rogues=(sum([y(1:end-2)' y(2:end-1)' y(3:end)'],2)>2);
1158
        xIntervals(rogues)=floor(rand*2*kLength);
1159
    end
1160
    xIntervals=reshape(xIntervals,nCycles,intervalsPerCycle);
1161
else
1162
    xIntervals=[];
1163
end
1164

    
1165
% insert constrained (k) intervals
1166
if nKs>0
1167
    switch order
1168
        case 1
1169
            kIntervals=floor(ones(nCycles,nKs)*kLength);
1170
        case 2
1171
            nKs=1; % force this
1172
            kIntervals=floor(rand(nCycles,1)*kLength);
1173
            kIntervals=[kIntervals kLength-kIntervals];
1174
    end
1175
else
1176
    kIntervals=[];
1177
end
1178

    
1179
% combine fixed and random
1180
intervals=[kIntervals xIntervals(:,nKs+1:end)];
1181
% make a single array;
1182
[r c]=size(intervals);
1183
intervals=reshape(intervals',1,r*c);
1184

    
1185
% shuffle intervals
1186
if shuffled
1187
    randomNumbers=rand(1,length(intervals));
1188
    [randomNumbers idx]=sort(randomNumbers);
1189
    intervals=intervals(idx);
1190
    idx=intervals>0;
1191
    intervals=intervals(idx);
1192
end
1193

    
1194
% convert intervals to clicks
1195
clickTimes=cumsum(intervals);
1196
signal(clickTimes)=clickHeight;
1197
signal=signal(1:requiredSignalLength);
1198
% figure(1), clf, plot(signal)
1199

    
1200

    
1201

    
1202
%--------------------------------------------------------------------makeNoise
1203
function noise=makeNoise(globalStimParams, stimComponents)
1204
% FS in Hz, noiseDuration in s, delay in s;
1205
% noise is returned with overall level dB(rms) = amplitudesdB
1206
%
1207
% % You need
1208
%
1209
% stim.type='noise'; % or 'IRN', or 'pinkNoise'
1210
% stim.toneDuration=.05;
1211
% stim.amplitudesdB=50;
1212
% stim.beginSilence=.01;
1213
% stim.endSilence=-1;
1214
% stim.rampOnDur=.002;
1215
% stim.rampOffDur=-1;
1216
%
1217
% % Mandatory structure fields
1218
% globalStimParams.FS=100000;
1219
% globalStimParams.overallDuration=.1;  % s
1220
% globalStimParams.doPlot=1;
1221
% globalStimParams.doPlay=1;
1222
%
1223
% [audio, msg]=stimulusCreate(globalStimParams, stim, );
1224
%
1225
% % local
1226
% stim.type='noise'; % or 'IRN'
1227
%
1228
FS=globalStimParams.FS;
1229
noiseDuration= stimComponents.toneDuration;
1230
npts=round(noiseDuration*FS);
1231
noise=randn(1,npts);    % NB randn (normally distributed)
1232

    
1233
switch stimComponents.type
1234
    case  'pinkNoise'
1235
        %         noise=UTIL_lowpassFilterFreq(noise, 100, 1/FS);
1236
        noise=UTIL_bandPassFilter(noise, 1, 100, 200, 1/FS,[]);
1237
end
1238

    
1239
rms=(mean(noise.^2)^.5);  %should be 20 microPascals for 0 dB SPL
1240
adjust=20e-6/rms;
1241
noise=noise*adjust;
1242
rms=(mean(noise.^2)^.5);
1243
amplitude=10.^(stimComponents.amplitudesdB/20);
1244
noise=amplitude*noise;
1245
% rms=(mean(noise.^2)^.5);
1246
% dBnoise=20*log10(rms/20e-6)
1247

    
1248

    
1249
%--------------------------------------------------------------------makeWhiteNoise
1250
function noise=makeWhiteNoise(globalStimParams, stimComponents)
1251
% FS in Hz, noiseDuration in s, delay in s;
1252
% noise is bandpass filtered between 100 and 10000 Hz
1253
% spectrum level (dB/Hz) is 40 dB below nominal level.
1254
% noise is returned with dB(rms) = amplitudesdB
1255
%
1256
% % You need
1257
%
1258
% stim.type='noise'; % or 'IRN', or 'pinkNoise'
1259
% stim.toneDuration=.05;
1260
% stim.amplitudesdB=50;
1261
% stim.beginSilence=.01;
1262
% stim.endSilence=-1;
1263
% stim.rampOnDur=.002;
1264
% stim.rampOffDur=-1;
1265
%
1266
% % Mandatory structure fields
1267
% globalStimParams.FS=100000;
1268
% globalStimParams.overallDuration=.1;  % s
1269
% globalStimParams.doPlot=1;
1270
% globalStimParams.doPlay=1;
1271
%
1272
% [audio, msg]=stimulusCreate(globalStimParams, stim, );
1273
%
1274
% % local
1275
% stim.type='noise'; % or 'IRN'
1276
%
1277
FS=globalStimParams.FS;
1278
noiseDuration= stimComponents.toneDuration;
1279
npts=round(noiseDuration*FS);
1280
noise=randn(1,npts);
1281

    
1282
noise=UTIL_bandPassFilter (noise, 6, 100, 10000, 1/FS, []);
1283

    
1284
rms=(mean(noise.^2)^.5);  %should be 20 microPascals for 0 dB SPL
1285
adjust=20e-6/rms;
1286
noise=noise*adjust;
1287
rms=(mean(noise.^2)^.5);
1288
amplitude=10.^(stimComponents.amplitudesdB/20);
1289
noise=amplitude*noise;
1290
% rms=(mean(noise.^2)^.5);
1291
% dBnoise=20*log10(rms/20e-6)
1292

    
1293

    
1294
%-----------------------------------------------------------------makeIRN
1295
function noise=makeIRN(globalStimParams, stimComponents)
1296
% FS in Hz, noiseDuration in s, delay in s;
1297
% noise is returned with dB(rms) = amplitudesdB
1298
%
1299
% % You need
1300
%
1301
% stim.type='noise'; % or 'IRN', or 'pinkNoise'
1302
% stim.toneDuration=.05;
1303
% stim.amplitudesdB=50;
1304
% stim.beginSilence=.01;
1305
% stim.endSilence=-1;
1306
% stim.rampOnDur=.002;
1307
% stim.rampOffDur=-1;
1308
%
1309
% % Mandatory structure fields
1310
% globalStimParams.FS=100000;
1311
% globalStimParams.overallDuration=.1;  % s
1312
% globalStimParams.doPlot=1;
1313
% globalStimParams.doPlay=1;
1314
%
1315
% [audio, msg]=stimulusCreate(globalStimParams, stim, );
1316
%
1317
% % local
1318
% stim.type='noise'; % or 'IRN'
1319
% % for IRN only
1320
% stim.niterations = 8;   %0 for white noise
1321
% stim.delay = 1/150;
1322
% stim.irnGain = 1;
1323
%
1324
FS=globalStimParams.FS;
1325
noiseDuration= stimComponents.toneDuration;
1326

    
1327
nIterations=stimComponents.niterations;
1328
if nIterations==0
1329
    % white noise is specified as nIterations=1
1330
    nIterations=1;
1331
    IRNgain=0;
1332
    delay=0.01; % dummy
1333
else
1334
    % IRN
1335
    delay=stimComponents.delay;
1336
    IRNgain=stimComponents.irnGain;
1337
end
1338

    
1339
npts=round(noiseDuration*FS);
1340
dels=round(delay*FS);
1341
noise=randn(1,npts);
1342

    
1343
%fringe=nIterations*dels;
1344
%npts=npts+fringe;
1345

    
1346
for i=1:nIterations,
1347
    dnoise=[noise(dels+1:npts) noise(1:dels)];
1348
    dnoise=dnoise.*IRNgain;
1349
    noise=noise+dnoise;
1350
end;
1351

    
1352
switch stimComponents.type
1353
    case  'pinkNoise'
1354
        noise=UTIL_lowpassFilterFreq(noise, 10000, 1/FS);
1355
end
1356

    
1357
rms=(mean(noise.^2)^.5);  %should be 20 microPascals for 0 dB SPL
1358
adjust=20e-6/rms;
1359
noise=noise*adjust;
1360
rms=(mean(noise.^2)^.5);
1361
amplitude=10.^(stimComponents.amplitudesdB/20);
1362
noise=amplitude*noise;
1363
% rms=(mean(noise.^2)^.5);
1364
% dBnoise=20*log10(rms/20e-6)
1365

    
1366
%------------------------------------------------------------------ makeRPN
1367
function RPN=makeRPN(globalStimParams, stim)
1368
% 'period' is a collection of samples - AAABCD
1369
% you need
1370
%
1371
% stim.type='RPN';
1372
% stim.toneDuration=.2;
1373
% stim.amplitudesdB=50;
1374
% stim.beginSilence=.01;
1375
% stim.rampOnDur=.002;
1376
% stim.rampOffDur=-1;
1377
%
1378
% stim.sampleDuration=.005;  %200 Hz pitch
1379
% stim.nSimilarSamples=5;   % pitch strength
1380
% stim.nIndependentSamples=1% dilutes strength
1381
%
1382
% % Mandatory structure fields
1383
%  globalStimParams.FS=44100;
1384
%  globalStimParams.overallDuration=.21;  % s
1385
%
1386
% globalStimParams.doPlot=1;
1387
% globalStimParams.doPlay=1;
1388
% [audio, msg]=stimulusCreate(globalStimParams, stim);
1389

    
1390
FS=globalStimParams.FS;
1391
ptsPerSample=floor(stim.sampleDuration*FS);
1392

    
1393
samplesPerPeriod=stim.nSimilarSamples+stim.nIndependentSamples;
1394
periodDuration=samplesPerPeriod*stim.sampleDuration;
1395

    
1396
totalNumPeriods=2*floor(stim.toneDuration/periodDuration);  % longer than necessary
1397
if totalNumPeriods<1
1398
    error('stimulusCreate: RPN, stimulus duration needs to be longer')
1399
end
1400

    
1401
RPN=[];
1402
for j=1:totalNumPeriods
1403
    noise=randn(1,ptsPerSample);
1404
    for i=1:stim.nSimilarSamples
1405
        RPN=[RPN noise];
1406
    end
1407

    
1408
    for i=1:stim.nIndependentSamples
1409
        noise=randn(1,ptsPerSample);
1410
        RPN=[RPN noise];
1411
    end
1412
end
1413

    
1414
targetStimulusLength=round(stim.toneDuration/FS);
1415
RPN=RPN(1:floor(stim.toneDuration*FS));     % take enough for stimulus
1416

    
1417
rms=(mean(RPN.^2)^.5);  %should be 20 microPascals for 0 dB SPL
1418
adjust=20e-6/rms;
1419
RPN=RPN*adjust;
1420
rms=(mean(RPN.^2)^.5);
1421
amplitude=10.^(stim.amplitudesdB/20);
1422
RPN=amplitude*RPN;
1423
% rms=(mean(noise.^2)^.5);
1424
% dBnoise=20*log10(rms/20e-6)
1425

    
1426
%--------------------------------------------------------------------applyRampOn
1427
function signal=applyRampOn(signal, rampDur, rampOnTime, sampleRate)
1428
%applyRampOn applies raised cosine ramp
1429
%rampOntime is the time at which the ramp begins
1430
%At all other times the mask has a value of 1
1431
%signal=applyRampOn(signal, rampDur, rampOnTime, sampleRate)
1432

    
1433
rampDurPoints=round(rampDur*sampleRate);
1434
rampOn= (1+cos(pi:pi/(rampDurPoints-1): 2*pi))/2';
1435

    
1436
sigDurPoints=length(signal);
1437
mask(1:sigDurPoints)=1;
1438
rampOnStartIndex=round(rampOnTime*sampleRate+1);
1439
mask(rampOnStartIndex: rampOnStartIndex+ rampDurPoints-1)=rampOn;
1440
signal=signal.*mask;
1441
%plot(mask)
1442

    
1443
%--------------------------------------------------------------------applyRampOff
1444
function signal=applyRampOff(signal, rampDur, rampOffTime, sampleRate)
1445
%applyRampOn applies raised cosine squared ramp
1446
%rampOffTime is the time at which the ramp begins
1447
%At all other times the mask has a value of 1
1448
% signal=applyRampOff(signal, rampDur, rampOffTime, sampleRate)
1449

    
1450
rampDurPoints=round(rampDur*sampleRate);
1451
rampOff= (1+cos(0:pi/(rampDurPoints-1): pi))/2';
1452

    
1453
sigDurPoints=length(signal);
1454
mask=ones(1,sigDurPoints);
1455
rampOffStartIndex=round(rampOffTime*sampleRate+1);
1456
mask(rampOffStartIndex: round(rampOffStartIndex+ rampDurPoints-1))=rampOff;
1457
if length(mask)>sigDurPoints, mask=mask(1:sigDurPoints); end
1458
signal=signal.*mask;
1459
%plot(mask)
1460

    
1461
function signal=applyGaussianRamps(signal, sigma, sampleRate)
1462
dt=1/sampleRate;
1463
time=dt:dt:dt*length(signal);
1464
ramp=1-exp(-time.^2/(2*sigma^2));
1465
% apply onset ramp
1466
signal=signal.*ramp;
1467
% apply offset ramp
1468
ramp=fliplr(ramp);
1469
signal=signal.*ramp;
1470

    
1471

    
1472

    
1473
%--------------------------------------------------------------------checkDescriptors
1474
function [globalStimParams, stimComponents]=checkDescriptors(globalStimParams, stimComponents);
1475

    
1476
try
1477
    % if FS exists, it takes priority
1478
    globalStimParams.dt=1/globalStimParams.FS;
1479
catch
1480
    % otherwise set FS using dt
1481
    globalStimParams.FS=1/globalStimParams.dt;
1482
end
1483

    
1484
globalStimParams.nSignalPoints=round(globalStimParams.overallDuration/globalStimParams.dt);
1485

    
1486
% optional field (ears)
1487
try
1488
    globalStimParams.ears;
1489
catch
1490
    % default: dichotic.
1491
    globalStimParams.ears='dichotic';
1492
end
1493

    
1494
% audioOutCorrection is optional
1495
% audioOutCorrection is a scalar for reducing the sound
1496
try
1497
    globalStimParams.audioOutCorrection;
1498
catch
1499
    %      set to 1 if omitted
1500
    globalStimParams.audioOutCorrection=1;
1501
end
1502

    
1503
try
1504
    globalStimParams.doPlay;
1505
catch
1506
    % default plays sound only if explicitly requested
1507
    globalStimParams.doPlay=0;
1508
end
1509

    
1510
try
1511
    globalStimParams.doPlot;
1512
catch
1513
    % no plotting unless explicitly requested
1514
    globalStimParams.doPlot=0;
1515
end
1516

    
1517

    
1518

    
1519
[ears nComponentSounds]=size(stimComponents);
1520

    
1521
for ear=1:2 % 1=left/ 2=right
1522

    
1523
    % create a list of components whose type is specified
1524
    % if no type is specified assume that it is an empty specification
1525
    % this is allowed
1526
    validStimComponents=[];
1527
    for i=1:nComponentSounds
1528
        try
1529
            if ~isempty(stimComponents(ear,i).type)
1530
                validStimComponents=[validStimComponents i];
1531
            end
1532
        catch
1533
        end
1534
    end
1535

    
1536
    for componentNo=validStimComponents
1537
        % If no AM filed is present, create it for completeness
1538
        if ~isfield(stimComponents(ear,componentNo),'AMfrequency') |...
1539
                ~isfield(stimComponents(ear,componentNo),'AMdepth')
1540
            stimComponents(ear,componentNo).AMfrequency=0;
1541
            stimComponents(ear,componentNo).AMdepth=0;
1542
        end
1543

    
1544
        % all signals must have durations, amplitudes and ramps
1545
        if ...
1546
                isempty(stimComponents(ear,componentNo).type) |...
1547
                isempty(stimComponents(ear,componentNo).toneDuration) |...
1548
                isempty(stimComponents(ear,componentNo).amplitudesdB) |...
1549
                isempty(stimComponents(ear,componentNo).rampOnDur)
1550
            descriptorError( 'missing stimComponent descriptor', stimComponents, ear, componentNo)
1551
        end
1552

    
1553
        try, stimComponents(ear,componentNo).endSilence; catch, stimComponents(ear,componentNo).endSilence=-1; end
1554

    
1555
        % ramp checks do not apply to file input
1556
        if ~strcmp(stimComponents(ear,componentNo).type, 'file')
1557
            % match offset ramp to onset if not explicitly specified
1558
            if stimComponents(ear,componentNo).rampOffDur==-1,
1559
                stimComponents(ear,componentNo).rampOffDur=stimComponents(ear,componentNo).rampOnDur;
1560
            end
1561
            % ramps must be shorter than the stimulus
1562
            if stimComponents(ear,componentNo).rampOffDur> stimComponents(ear,componentNo).toneDuration | ...
1563
                    stimComponents(ear,componentNo).rampOnDur> stimComponents(ear,componentNo).toneDuration
1564
                descriptorError( 'ramp longer than sound component', stimComponents, ear, componentNo)
1565
            end
1566
        end
1567

    
1568
        % end silence is measured to fit into the global duration
1569
        if stimComponents(ear,componentNo).endSilence==-1,
1570
            endSilenceNpoints=...
1571
                globalStimParams.nSignalPoints ...
1572
                -round(stimComponents(ear,componentNo).beginSilence*globalStimParams.FS)...
1573
                -round(stimComponents(ear,componentNo).toneDuration*globalStimParams.FS);
1574
            stimComponents(ear,componentNo).endSilence=endSilenceNpoints/globalStimParams.FS;
1575
            % if endSilence < 0, we have a problem
1576
            if stimComponents(ear,componentNo).endSilence<0
1577
                globalStimParams
1578
                descriptorError( 'component durations greater than overallDuration', stimComponents, ear, componentNo)
1579
            end
1580
        end
1581

    
1582
        % check overall duration of this component against global duration
1583
        totalDuration= ...
1584
            stimComponents(ear,componentNo).beginSilence+...
1585
            stimComponents(ear,componentNo).toneDuration+...
1586
            stimComponents(ear,componentNo).endSilence;
1587

    
1588
        % avoid annoying error message for single stimulus component
1589
        if ears==1 && nComponentSounds==1
1590
            globalStimParams.overallDuration= totalDuration;
1591
        end
1592

    
1593

    
1594
        if round(totalDuration*globalStimParams.FS)>round(globalStimParams.overallDuration*globalStimParams.FS)
1595
            globalStimParams
1596
            descriptorError( 'Component durations greater than overallDuration', stimComponents, ear, componentNo)
1597
        end
1598

    
1599
        % check total duration
1600
        totalSignalPoints= round((stimComponents(ear,componentNo).beginSilence+ stimComponents(ear,componentNo).toneDuration+...
1601
            stimComponents(ear,componentNo).endSilence)/globalStimParams.dt);
1602
        if totalSignalPoints  >globalStimParams.nSignalPoints
1603
            descriptorError( 'Signal component duration does not match overall duration ', stimComponents, ear, componentNo)
1604
        end
1605

    
1606
        % no ramps for clicks please!
1607
        %         if strcmp(stimComponents(ear,componentNo).type, 'clicks') & stimComponents(ear,componentNo).clickRepeatFrequency==-1
1608
        %         if strcmp(stimComponents(ear,componentNo).type, 'clicks')
1609
        %             stimComponents(ear,componentNo).rampOnDur=0;
1610
        %             stimComponents(ear,componentNo).rampOffDur=0;
1611
        %         end
1612

    
1613
        if isfield(stimComponents(ear,componentNo), 'filter')
1614
            if ~isequal(length(stimComponents(ear,componentNo).filter), 3)
1615
                descriptorError( 'Filter parameter must have three elements ', stimComponents, ear, componentNo)
1616
            end
1617
        end
1618
    end % component
1619
    % ??
1620
    if strcmp(globalStimParams.ears,'monoticL') | strcmp(globalStimParams.ears, 'monoticR'), break, end
1621
end		% ear
1622

    
1623

    
1624
%-------------------------------------------------------------------- descriptorError
1625
function descriptorError( msg, stimComponents, ear, componentNo)
1626
stimComponents(ear, componentNo)
1627

    
1628
disp(' *********** **************** ************')
1629
disp([ '...Error in stimComponents description: '])
1630
disp([msg ])
1631
disp(['Ear = ' num2str(ear) ' component No ' num2str(componentNo)])
1632
disp(' *********** **************** ************')
1633
error('myError ')
1634

    
1635

    
1636
%-------------------------------------------------------------------- normalize
1637
function [normalizedSignal, gain]= normalize(signal)
1638
% normalize (signal)
1639
maxSignal=max(max(signal));
1640
minSignal=min(min(signal));
1641
if -minSignal>maxSignal, normalizingFactor=-minSignal; else normalizingFactor=maxSignal; end
1642
normalizingFactor=1.01*normalizingFactor;
1643
gain= 20*log10(normalizingFactor);
1644
normalizedSignal=signal/normalizingFactor;
1645

    
1646

    
1647
%--------------------------------------------------------------------Butterworth
1648
function y=Butterworth (x, dt, fl, fu, order)
1649
% Butterworth (x, dt, fu, fl, order)
1650
% Taken from Yuel and beauchamp page 261
1651
% NB error in their table for K (see their text)
1652
% x is original signal
1653
% fu, fl upper and lower cutoff
1654
% order is the number of times the filter is applied
1655

    
1656
q=(pi*dt*(fu-fl));
1657
J=1/(1+ cot(q));
1658
K= (2*cos(pi*dt*(fu+fl)))/(1+tan(q)*cos(q));
1659
L= (tan(q)-1)/(tan(q)+1);
1660
b=[J -J];
1661
a=[1 -K  -L];
1662
for i=1:order
1663
    y=filter(b, a, x);
1664
    x=y;
1665
end
1666

    
1667

    
1668
% -------------------------------------------------------- UTIL_amp2dB
1669
function [y] = UTIL_amp2dB (x, ref)
1670
% Calculates a dB (ref. ref) value 'y' from a peak amplitude number 'x'.
1671
% if ref omitted treat as dB
1672
% Check the number of arguments that are passed in.
1673
if (nargin < 2)
1674
    ref=1;
1675
end
1676
if (nargin > 2)
1677
    error ('Too many arguments');
1678
end
1679

    
1680
% Check arguments.
1681
if x < 0.0
1682
    error ('Can not calculate the log10 of a negative number');
1683
elseif x == 0.0
1684
    warning ('log10 of zero.  The result is set to -1000.0');
1685
    y = -1000.0;
1686
else
1687
    % Do calculations.
1688
    y = 20.0 * log10(x/(sqrt(2)*ref));
1689

    
1690
end
1691

    
1692
%-------------------------------------------------------------------- FFT
1693
function showFFT (getFFTsignal, dt)
1694
color='r';
1695
figure(2), clf,
1696
hold off
1697

    
1698
% trim initial silence
1699
idx=find(getFFTsignal>0);
1700
if ~isempty(idx)
1701
    getFFTsignal=getFFTsignal(idx(1):end);
1702
end
1703
%trim final silence
1704
getFFTsignal=getFFTsignal(end:-1:1);
1705
idx=find(getFFTsignal>0);
1706
if ~isempty(idx)
1707
    getFFTsignal=getFFTsignal(idx(1):end);
1708
    getFFTsignal=getFFTsignal(end:-1:1);
1709
end
1710

    
1711
% Analyse make stimComponents length a power of 2
1712
x=length(getFFTsignal);
1713
squareLength=2;
1714
while squareLength<=x
1715
    squareLength=squareLength*2;
1716
end
1717
squareLength=round(squareLength/2);
1718
getFFTsignal=getFFTsignal(1:squareLength);
1719
n=length(getFFTsignal);
1720

    
1721
minf=100; maxf=20000;
1722

    
1723
fft_result = fft(getFFTsignal, n);				% Compute FFT of the input signal.
1724
fft_power = fft_result .* conj(fft_result);% / n;	% Compute power spectrum.  Dividing by 'n' we get the power spectral density.
1725
fft_phase = angle(fft_result);			% Compute the phase spectrum.
1726

    
1727
frequencies = (1/dt)*(1:n/2)/n;
1728
fft_power=fft_power(1:length(fft_power)/2); % remove mirror frequencies
1729
fft_phase=fft_phase(1:length(fft_phase)/2); % remove mirror frequencies
1730
fft_powerdB = UTIL_amp2dB (fft_power, max(fft_power)); % convert to dB
1731
%     jags=find(diff(fft_phase)>0); % unwrap phase
1732
%     for i=jags, fft_phase(i+1:end)=fft_phase(i+1:end)-2*pi; end
1733

    
1734
xlim([minf maxf])
1735
semilogx(frequencies, fft_powerdB-max(fft_powerdB), color)
1736
ylim([ -20 5])
1737

    
1738

    
1739

    
1740
function y=UTIL_lowpassFilterFreq(x, cutOffFrequency, dt)
1741
% UTIL_lowpassFilterFreq multi-channel filter
1742
%
1743
% Usage:
1744
% output=UTIL_lowpassFilterFreq(input, cutOffFrequency, dt)
1745
%
1746
% cutoff should be around 100 Hz for audio work
1747
% dt should be <1/50000 s for audio work
1748
%
1749
% Attenuation 	       is - 6 dB per octave above cutoff.
1750

    
1751

    
1752
sampleRate=1/dt;
1753

    
1754
if 4*cutOffFrequency>sampleRate
1755
    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' ])
1756
    cutOffFrequency=sampleRate/4;
1757
end
1758

    
1759
tau=1/(2*pi*cutOffFrequency);
1760

    
1761
y=zeros(size(x));
1762
[numChannels numPoints]=size(x);
1763
for i=1:numChannels
1764
    y(i,:)=filter([dt/tau], [1 -(1-dt/tau)], x(i,:));
1765
end
1766

    
1767