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 @ 33:161913b595ae

History | View | Annotate | Download (55.5 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]=...
1475
    checkDescriptors(globalStimParams, stimComponents)
1476

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

    
1485
sampleRate=globalStimParams.FS;
1486

    
1487
globalStimParams.nSignalPoints=...
1488
    round(globalStimParams.overallDuration* sampleRate);
1489

    
1490
% optional field (ears)
1491
try
1492
    globalStimParams.ears;
1493
catch
1494
    % default: dichotic.
1495
    globalStimParams.ears='dichotic';
1496
end
1497

    
1498
% audioOutCorrection is optional
1499
% audioOutCorrection is a scalar for reducing the sound
1500
try
1501
    globalStimParams.audioOutCorrection;
1502
catch
1503
    %      set to 1 if omitted
1504
    globalStimParams.audioOutCorrection=1;
1505
end
1506

    
1507
try
1508
    globalStimParams.doPlay;
1509
catch
1510
    % default plays sound only if explicitly requested
1511
    globalStimParams.doPlay=0;
1512
end
1513

    
1514
try
1515
    globalStimParams.doPlot;
1516
catch
1517
    % no plotting unless explicitly requested
1518
    globalStimParams.doPlot=0;
1519
end
1520

    
1521
[ears nComponentSounds]=size(stimComponents);
1522

    
1523
for ear=1:2 % 1=left/ 2=right
1524

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

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

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

    
1555
        try, stimComponents(ear,componentNo).endSilence; catch, stimComponents(ear,componentNo).endSilence=-1; end
1556

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

    
1570
        % end silence is measured to fit into the global duration
1571
        if stimComponents(ear,componentNo).endSilence==-1,
1572
            stimComponents(ear,componentNo).endSilence=...
1573
                globalStimParams.overallDuration-...
1574
                stimComponents(ear,componentNo).beginSilence -...
1575
                stimComponents(ear,componentNo).toneDuration;
1576
            
1577
            endSilenceNpoints=stimComponents(ear,componentNo).endSilence...
1578
                *sampleRate;
1579
        end
1580
        if stimComponents(ear,componentNo).endSilence<0
1581
            globalStimParams
1582
            descriptorError( 'component durations greater than overallDuration', stimComponents, ear, componentNo)
1583
        end
1584

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

    
1591
        % avoid annoying error message for single stimulus component
1592
        if ears==1 && nComponentSounds==1
1593
            globalStimParams.overallDuration= totalDuration;
1594
        end
1595

    
1596
        % check total duration
1597
        totalSignalPoints= round(totalDuration* sampleRate);
1598
        if totalSignalPoints  >globalStimParams.nSignalPoints
1599
            descriptorError( 'Signal component duration does not match overall duration ', stimComponents, ear, componentNo)
1600
        end
1601

    
1602
        if isfield(stimComponents(ear,componentNo), 'filter')
1603
            if ~isequal(length(stimComponents(ear,componentNo).filter), 3)
1604
                descriptorError( 'Filter parameter must have three elements ', stimComponents, ear, componentNo)
1605
            end
1606
        end
1607
    end % component
1608
    % ??
1609
    if strcmp(globalStimParams.ears,'monoticL') | strcmp(globalStimParams.ears, 'monoticR'), break, end
1610
end		% ear
1611

    
1612

    
1613
%-------------------------------------------------------------------- descriptorError
1614
function descriptorError( msg, stimComponents, ear, componentNo)
1615
stimComponents(ear, componentNo)
1616

    
1617
disp(' *********** **************** ************')
1618
disp([ '...Error in stimComponents description: '])
1619
disp([msg ])
1620
disp(['Ear = ' num2str(ear) ' component No ' num2str(componentNo)])
1621
disp(' *********** **************** ************')
1622
error('myError ')
1623

    
1624

    
1625
%-------------------------------------------------------------------- normalize
1626
function [normalizedSignal, gain]= normalize(signal)
1627
% normalize (signal)
1628
maxSignal=max(max(signal));
1629
minSignal=min(min(signal));
1630
if -minSignal>maxSignal, normalizingFactor=-minSignal; else normalizingFactor=maxSignal; end
1631
normalizingFactor=1.01*normalizingFactor;
1632
gain= 20*log10(normalizingFactor);
1633
normalizedSignal=signal/normalizingFactor;
1634

    
1635

    
1636
%--------------------------------------------------------------------Butterworth
1637
function y=Butterworth (x, dt, fl, fu, order)
1638
% Butterworth (x, dt, fu, fl, order)
1639
% Taken from Yuel and beauchamp page 261
1640
% NB error in their table for K (see their text)
1641
% x is original signal
1642
% fu, fl upper and lower cutoff
1643
% order is the number of times the filter is applied
1644

    
1645
q=(pi*dt*(fu-fl));
1646
J=1/(1+ cot(q));
1647
K= (2*cos(pi*dt*(fu+fl)))/(1+tan(q)*cos(q));
1648
L= (tan(q)-1)/(tan(q)+1);
1649
b=[J -J];
1650
a=[1 -K  -L];
1651
for i=1:order
1652
    y=filter(b, a, x);
1653
    x=y;
1654
end
1655

    
1656

    
1657
% -------------------------------------------------------- UTIL_amp2dB
1658
function [y] = UTIL_amp2dB (x, ref)
1659
% Calculates a dB (ref. ref) value 'y' from a peak amplitude number 'x'.
1660
% if ref omitted treat as dB
1661
% Check the number of arguments that are passed in.
1662
if (nargin < 2)
1663
    ref=1;
1664
end
1665
if (nargin > 2)
1666
    error ('Too many arguments');
1667
end
1668

    
1669
% Check arguments.
1670
if x < 0.0
1671
    error ('Can not calculate the log10 of a negative number');
1672
elseif x == 0.0
1673
    warning ('log10 of zero.  The result is set to -1000.0');
1674
    y = -1000.0;
1675
else
1676
    % Do calculations.
1677
    y = 20.0 * log10(x/(sqrt(2)*ref));
1678

    
1679
end
1680

    
1681
%-------------------------------------------------------------------- FFT
1682
function showFFT (getFFTsignal, dt)
1683
color='r';
1684
figure(2), clf,
1685
hold off
1686

    
1687
% trim initial silence
1688
idx=find(getFFTsignal>0);
1689
if ~isempty(idx)
1690
    getFFTsignal=getFFTsignal(idx(1):end);
1691
end
1692
%trim final silence
1693
getFFTsignal=getFFTsignal(end:-1:1);
1694
idx=find(getFFTsignal>0);
1695
if ~isempty(idx)
1696
    getFFTsignal=getFFTsignal(idx(1):end);
1697
    getFFTsignal=getFFTsignal(end:-1:1);
1698
end
1699

    
1700
% Analyse make stimComponents length a power of 2
1701
x=length(getFFTsignal);
1702
squareLength=2;
1703
while squareLength<=x
1704
    squareLength=squareLength*2;
1705
end
1706
squareLength=round(squareLength/2);
1707
getFFTsignal=getFFTsignal(1:squareLength);
1708
n=length(getFFTsignal);
1709

    
1710
minf=100; maxf=20000;
1711

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

    
1716
frequencies = (1/dt)*(1:n/2)/n;
1717
fft_power=fft_power(1:length(fft_power)/2); % remove mirror frequencies
1718
fft_phase=fft_phase(1:length(fft_phase)/2); % remove mirror frequencies
1719
fft_powerdB = UTIL_amp2dB (fft_power, max(fft_power)); % convert to dB
1720
%     jags=find(diff(fft_phase)>0); % unwrap phase
1721
%     for i=jags, fft_phase(i+1:end)=fft_phase(i+1:end)-2*pi; end
1722

    
1723
xlim([minf maxf])
1724
semilogx(frequencies, fft_powerdB-max(fft_powerdB), color)
1725
ylim([ -20 5])
1726

    
1727

    
1728

    
1729
function y=UTIL_lowpassFilterFreq(x, cutOffFrequency, dt)
1730
% UTIL_lowpassFilterFreq multi-channel filter
1731
%
1732
% Usage:
1733
% output=UTIL_lowpassFilterFreq(input, cutOffFrequency, dt)
1734
%
1735
% cutoff should be around 100 Hz for audio work
1736
% dt should be <1/50000 s for audio work
1737
%
1738
% Attenuation 	       is - 6 dB per octave above cutoff.
1739

    
1740

    
1741
sampleRate=1/dt;
1742

    
1743
if 4*cutOffFrequency>sampleRate
1744
    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' ])
1745
    cutOffFrequency=sampleRate/4;
1746
end
1747

    
1748
tau=1/(2*pi*cutOffFrequency);
1749

    
1750
y=zeros(size(x));
1751
[numChannels numPoints]=size(x);
1752
for i=1:numChannels
1753
    y(i,:)=filter([dt/tau], [1 -(1-dt/tau)], x(i,:));
1754
end
1755

    
1756