Mercurial > hg > map
diff utilities/stimulusCreate.m @ 0:f233164f4c86
first commit
author | Ray Meddis <rmeddis@essex.ac.uk> |
---|---|
date | Fri, 27 May 2011 13:19:21 +0100 |
parents | |
children | 1a502830d462 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/utilities/stimulusCreate.m Fri May 27 13:19:21 2011 +0100 @@ -0,0 +1,1767 @@ +function [audio, msg, time]=stimulusCreate(globalStimParams,... + stimComponents, doPlot) +% updated June 2007 +% the only authorised version of stimulus create is the version to be found +% in MAP1_6. Other versions are copies!! +% +% for a simple tone you need +% +% % Mandatory structure fields +% globalStimParams.FS=100000; +% globalStimParams.overallDuration=.1; % s +% doPlot=1; +% +% stim.type='tone'; +% stim.phases='sin'; +% stim.toneDuration=.05;; +% stim.frequencies=500; +% stim.amplitudesdB=50; +% stim.beginSilence=.01; +% stim.endSilence=-1; +% stim.rampOnDur=.002; +% stim.rampOffDur=-1; +% [audio, msg]=stimulusCreate(globalStimParams, stim, doPlot); +% +% % or for multi-component stimuli +% % Mandatory structure fields +% globalStimParams.FS=100000; +% globalStimParams.overallDuration=.1; % s +% ear=1; +% componentNo=1; +% +% stimComponents(ear, componentNo).type='tone'; +% stimComponents(ear, componentNo).phases='sin'; +% stimComponents(ear, componentNo).toneDuration=.05;; +% stimComponents(ear, componentNo).frequencies=500; +% stimComponents(ear, componentNo).amplitudesdB=50; +% stimComponents(ear, componentNo).beginSilence=.01; +% stimComponents(ear, componentNo).endSilence=-1; +% stimComponents(ear, componentNo).rampOnDur=.002; +% stimComponents(ear, componentNo).rampOffDur=-1; +% +% % All components are forced to have the same overall duration and sample rate +% +% +% [audio, msg]=stimulusCreate(globalStimParams, stimComponents); +% +% Optional fields +% .ears overides ear setting by component +% globalStimParams.ears='monoticL'; % 'monoticL', 'monoticR', 'diotic', 'dichotic' +% +% globalStimParams.filter = [leftfl leftfu rightfl right fu] +% filter is applied separately to left and right combined sounds +% +% correction factor is applied to all signals to compensate for differences in output devices. +% audioOutCorrection is a scalar +% globalStimParams.audioOutCorrection=2; +% +% globalStimParams.FFT= 1; % {0, 1} +% globalStimParams.doPlot=1; % {0, 1} +% globalStimParams.doPlay=1; % {0, 1} +% +% stimComponents(ear, componentNo).filter=[100 10000 2] % [lower, upper, order] applies to an +% individual component +% +% Output arguments: +% audio is a stereo signal, a 2-column vector +% +% +% stim.type='noise'; % {'IRN', 'irn', 'noise', 'pinkNoise'} +% stim.toneDuration=.05;; +% stim.amplitudesdB=50; +% stim.beginSilence=.01; +% stim.endSilence=-1; +% stim.rampOnDur=.002; +% stim.rampOffDur=-1; +% [audio, msg]=stimulusCreate(globalStimParams, stim); +% +% % for IRN only +% stim.type='IRN'; +% stim.toneDuration=.05;; +% stim.amplitudesdB=50; +% stim.beginSilence=.01; +% stim.endSilence=-1; +% stim.rampOnDur=.002; +% stim.rampOffDur=-1; +% stim.niterations = 8; %0 for white noise +% stim.delay = 1/150; +% stim.irnGain = 1; +% [audio, msg]=stimulusCreate(globalStimParams, stim);stimComponents.clickWidth; +% +% stim.type='clicks'; % uses frequencies for duty cycle +% stim.toneDuration=.05;; +% stim.frequencies=500; +% stim.amplitudesdB=50; +% stim.beginSilence=.01; +% stim.endSilence=-1; +% stim.rampOnDur=.002; +% stim.rampOffDur=-1; +% [audio, msg]=stimulusCreate(globalStimParams, stim); +% stimComponents.clickWidth=.0001; % default= dt +% stimComponents.clickHeight= 20e-6; % default= 28e-6 * 10^(stimComponents.amplitudesdB/20); +% stim.clickTimes=[.4 .6]; % times in cylce when clicks occur, default = 1 +% +% + +msg=''; %error messages; no message is a good message + +% plotting can be set as a separate argument or as a globalstimParams +% variable. this is for backwards compatibility only +if nargin>2, + globalStimParams.doPlot=doPlot; +end + +% stimComponents(1,1).endSilence=-1; % end silence is always computed + +% perform checks and set defaults +[globalStimParams, stimComponents]=checkDescriptors(globalStimParams, stimComponents); + + +% create empty stereo audio of appropriate length +audio=zeros(globalStimParams.nSignalPoints, 2); +% signal=zeros(globalStimParams.nSignalPoints, 1); +dt=globalStimParams.dt; + +[Nears nComponentSounds]=size(stimComponents); +for ear=1:Nears % left/ right + % combinedSignal is the sum of all sounds in one ear + % it is a column vector + combinedSignal=zeros(globalStimParams.nSignalPoints,1); + + % find valid components + % if a component is empty, it is not a validComponent and is ignored + validStimComponents=[]; + for i=1:nComponentSounds + if ~isempty(stimComponents(ear,i).type) + validStimComponents=[validStimComponents i]; + end + end + + for componentNo=validStimComponents + % compute individual components before adding + stim=stimComponents(ear,componentNo); + switch stim.type + case 'tone' + stimulus=maketone(globalStimParams, stim); + + case 'fmTone' + stimulus=makeFMtone(globalStimParams, stim); + + case 'OHIO' + stim.beginSilence=0; + stimulus=makeOHIOtones(globalStimParams, stim); + + case 'transposedStimulus' + stim.beginSilence=0; % necessary because of recursion + stimulus=makeTransposedStimulus(globalStimParams, stim); + + case { 'noise', 'pinkNoise'} + stimulus=makeNoise(globalStimParams, stim); + + case { 'whiteNoise'} + stimulus=makeWhiteNoise(globalStimParams, stim); + + case {'IRN', 'irn'} + stimulus=makeIRN(globalStimParams, stim); + + case {'RPN'} + stimulus=makeRPN(globalStimParams, stim); + + case 'clicks' + stimulus=makeClicks(globalStimParams, stim); + + case 'PressnitzerClicks' + % kxx clicks + % k is 1/clickRepeatFrequency + stimulus=makePressnitzerClicks(globalStimParams, stimComponents); + + case 'PressnitzerABxClicks' + % kxx clicks + % k is 1/clickRepeatFrequency + stimulus=makePressnitzerABxClicks(globalStimParams, stimComponents); + + case 'ABxClicks' + % A=rand*k, B=k-A, x=rand*k. + stimulus=makeABxClicks(globalStimParams, stimComponents); + + case 'YostClicks' + % kxx clicks + % k is 1/clickRepeatFrequency + stimulus=makeYostClicks(globalStimParams, stimComponents); + + case 'kxxClicks' + % kxx clicks + % k is 1/clickRepeatFrequency + stimulus=makeKxxClicks(globalStimParams, stimComponents); + + +% case 'babble' +% % NB random start in a long file +% [stimulus sampleRate]= wavread('babble'); +% nPoints=round(sampleRate*... +% stimComponents(ear,componentNo).toneDuration); +% start=round(rand(1,1)*(length(stimulus)-nPoints)); +% stimulus=stimulus(start:start+nPoints-1); +% rms= 20*log10((mean(stimulus.^2).^0.5)/20e-6); +% dBSPLrms=stimComponents(ear,componentNo).amplitudesdB; +% gain=10.^((dBSPLrms-rms)/20); +% stimulus=stimulus'*gain; + + case 'speech' + [stimulus sampleRate]= wavread('speech'); + stimulus=stimulus'; + nPoints=sampleRate*stimComponents(ear,componentNo).toneDuration; + if nPoints > length(stimulus) + initialSilence=zeros(1,nPoints-length(stimulus)); + else + initialSilence=[]; + start=round(rand(1,1)*(length(stimulus)-nPoints)); + stimulus=stimulus(start:start+nPoints-1); + end + rms= 20*log10((mean(stimulus.^2).^0.5)/20e-6); + dBSPLrms=stimComponents(ear,componentNo).amplitudesdB; + gain=10.^((dBSPLrms-rms)/20); + stimulus=stimulus*gain; + stimulus=[stimulus initialSilence ]; + + + case 'file' + % component already read from file and stored in stimulus. Insert it here + % additional code for establishing signal rms level + % NB signal is always mono at this stage + + stimulus=stim.stimulus; + dBSPL=stim.amplitudesdB; + + nPoints=round(stim.toneDuration/dt); + [r c]=size(stimulus); + if r>c, stimulus=stimulus'; end % secure horizontal vector + stimulus=stimulus(1,1:nPoints); % only mono taken from file + + try + % dB rms + rms= 20*log10((mean(stimulus.^2).^0.5)/20e-6); + % special request to set fixed rms for stimulus + dBSPLrms=stimComponents(ear,componentNo).amplitudesdBrms; + if ~(dBSPLrms==-1) + gain=10.^((dBSPLrms-rms)/20); + stimulus=stimulus*gain; + end + catch + % If no request for rms level is made + % set dB as peak amp + [stimulus gain]= normalize(stimulus); + dBSPL=stimComponents(ear,componentNo).amplitudesdB; + if ~(dBSPL==-1) + amplitude=28e-6*10.^(dBSPL/20); + stimulus=stimulus*amplitude; + end + end + + case 'none' + % no stimulus + stimulus=zeros(1,round(stim.toneDuration/dt)); + + case 'digitStrings' + % select a digit string at random anduse as target + files=dir(['..' filesep '..' filesep 'multithresholdResources\digitStrings']); + files=files(3:end); + nFiles=length(files); + fileNo=ceil(nFiles*rand); + fileData=files(fileNo); + fileName=['..\..\multithresholdResources\digitStrings\' fileData.name]; + [stimulus sampleRate]=wavread(fileName); + stimulus=stimulus(:,1)'; % make into a row vector + % estimate the extend of endsilence padding + nPoints=sampleRate*... + stimComponents(ear,componentNo).toneDuration; + if nPoints > length(stimulus) + endSilence=zeros(1,nPoints-length(stimulus)); + else + % or truncate the file + endSilence=[]; + stimulus=stimulus(1:nPoints); + end + % compute rms before adding silence + rms= 20*log10((mean(stimulus.^2).^0.5)/20e-6); + dBSPLrms=stimComponents(ear,componentNo).amplitudesdB; + gain=10.^((dBSPLrms-rms)/20); + stimulus=stimulus*gain; + stimulus=[stimulus endSilence]; + global stimulusParameters + stimulusParameters.digitString=fileName(end-7:end-5); + + otherwise + switch stim.type(end-5:end) + % any file name with 'Babble' at the end is a + % multiThreshold file + case 'Babble' + % one of the many babbles is selected. + % NB random start in a long file + % stim.type should contain the name of the babble file + fileName=['..' filesep '..' filesep ... + 'multithresholdResources' filesep ... + 'backgrounds and maskers'... + filesep stim.type]; + + [stimulus sampleRate]= wavread(fileName); + if ~isequal(sampleRate, globalStimParams.FS) + % NB the file will read but will disagree with + % tone stimuli or other files read + msg= ['error: file sample rate disagrees ' ... + 'with sample rate requested in paradigm'... + ' file (' ... + num2str(globalStimParams.FS) ').']; + error(msg); + end + nPoints=round(sampleRate*... + stimComponents(ear,componentNo).toneDuration); + start=round(rand(1,1)*(length(stimulus)-nPoints)); + stimulus=stimulus(start:start+nPoints-1); + rms= 20*log10((mean(stimulus.^2).^0.5)/20e-6); + dBSPLrms=stimComponents(ear,componentNo).amplitudesdB; + gain=10.^((dBSPLrms-rms)/20); + stimulus=stimulus'*gain; + + otherwise + % stim.type may be the name of a file to be read + % play from beginning for stimulus duration + try + [stimulus sampleRate]= wavread([stim.type '.wav']); + catch + error(['stimulusCreate: unrecognised stimulus type -> '... + stim.type]) + end + if ~isequal(sampleRate, globalStimParams.FS) + % NB the file will read but will disagree with + % tone stimuli or other files read + msg= ['error: file sample rate disagrees ' ... + 'with sample rate requested in paradigm'... + ' file (' ... + num2str(globalStimParams.FS) ').']; + error(msg); + end + stimulus=stimulus'; % make into a row vector + % estimate the extend of endsilence padding + nPoints=sampleRate*... + stimComponents(ear,componentNo).toneDuration; + if nPoints > length(stimulus) + endSilence=zeros(1,nPoints-length(stimulus)); + else + % or truncate the file + endSilence=[]; + stimulus=stimulus(1:nPoints); + end + % compute rms before adding silence + rms= 20*log10((mean(stimulus.^2).^0.5)/20e-6); + dBSPLrms=stimComponents(ear,componentNo).amplitudesdB; + gain=10.^((dBSPLrms-rms)/20); + stimulus=stimulus*gain; + stimulus=[stimulus endSilence]; + end + end + + % NB stimulus is a row vector now! + % audio and combinedSignal were column vectors + % signal will be a row vector + + % filter stimulus + try + % if filter field is present, [lower upper order] + if stim.filter(1)>0 % 0 means don't filter + stimulus=Butterworth (stimulus, dt, stim.filter(1), ... + stim.filter(2), stim.filter(3)); + + end + catch + end + + + % apply amplitude modulation + if isfield(stim,'AMfrequency') & isfield(stim,'AMdepth') + if stim.AMfrequency>0 & stim.AMdepth>0 + time=dt:dt:dt*length(stimulus); + modulator=sin(2*pi*stim.AMfrequency*time); + modulator=modulator*stim.AMdepth/100 + 1; % depth is percent + stimulus=stimulus.*modulator/2; + end + end + + % Basic stimulus is now created. + % Add trappings, ramps, silences to main stimulus + rampOnTime=0; %ramp starts at the beginning of the stimulus + rampOffTime=stim.toneDuration-stim.rampOffDur; + if stim.rampOnDur>0.0001 + stimulus=applyRampOn(stimulus, stim.rampOnDur, rampOnTime, 1/dt); + stimulus=applyRampOff(stimulus, stim.rampOffDur, rampOffTime, 1/dt); + end + if stim.rampOnDur<-0.0001 % apply Gaussian ramp + stimulus=applyGaussianRamps(stimulus, -stim.rampOnDur, 1/dt); + end + + % Initial silence + % start with a signal of the right length consisting of zeros + signal=zeros(1, globalStimParams.nSignalPoints); + % identify start of stimulus + insertStimulusAt=round(stim.beginSilence/dt)+1; + % add stimulus + endOfStimulus=insertStimulusAt+length(stimulus)-1; + if endOfStimulus<=globalStimParams.nSignalPoints + signal(1, insertStimulusAt: endOfStimulus)=stimulus; + else + error('stimulusCreate: tone too long to fit into the overall duration') + end + + % time signature + time=dt:dt:dt*length(signal); + % figure(22), plot(signal), title([num2str(ear) ' - ' num2str(componentNo)]),pause (1) + + try + % create a column vector and trap if no signal has been created + signal=signal'; + % also traps if signals are not the same length + combinedSignal=combinedSignal+signal; + % figure(21), plot(combinedSignal), title([num2str(ear) ' - ' num2str(componentNo)]),pause (1) + catch + % dump everything because there is a problem + globalStimParams + [ear componentNo] + stim + size(combinedSignal) + size(signal) + [ size(initialSilence) size(signal) size(endSilence)] + [ ear componentNo... + round(globalStimParams.overallDuration*globalStimParams.FS)... + round(stim.beginSilence*globalStimParams.FS)... + round(stim.toneDuration*globalStimParams.FS) ... + round(stim.endSilence*globalStimParams.FS)... + ( round(globalStimParams.overallDuration*globalStimParams.FS)... + -round(stim.beginSilence*globalStimParams.FS)... + -round(stim.toneDuration*globalStimParams.FS) ... + -round(stim.endSilence*globalStimParams.FS))... + ] + error(' trouble in stimulusCreate: signals are the wrong length ') + end + + end % component no + + audio(:,ear)= combinedSignal; + + % FFT + try + if globalStimParams.FFT + showFFT (audio, dt) + end + catch + end + + +end % ear + +switch globalStimParams.ears + % normally the signals are created in appropriate ears but .ears can + % overide this to produce a mono signal. + case 'monoticL'; + % combine left and right ears to make a mono signal in the left ear + audio(:,1)=audio(:,1)+audio(:,2); + audio(:,2)=zeros(globalStimParams.nSignalPoints, 1); + + case 'monoticR'; + % combine left and right ears to make a mono signal in the right ear + audio(:,2)=audio(:,1)+audio(:,2); + audio(:,1)=zeros(globalStimParams.nSignalPoints, 1); + + case 'diotic'; + % combine left and right ears to make a mono signal in both ears + bothEarsCombined=audio(:,1)+audio(:,2); + audio(:,2)=bothEarsCombined; + audio(:,1)=bothEarsCombined; + + otherwise + % Any other denomination produces no effect here +end + +% Plotting as required +if globalStimParams.doPlot + figure(9), clf + plot(time,audio(:,1)'), hold on, + if Nears>1 + offSet=(max(audio(:,1))+max(audio(:,2)))/10; + offSet=2*offSet+max(audio(:,1))+max(audio(:,2)); + plot(time,audio(:,2)'+offSet,'r'), hold off + end + ylabel('left right') +end + +% Calibration +% all signals are divided by this correction factor +% peakAmp=globalStimParams.audioOutCorrection; % microPascals = 100 dB SPL + +audio=audio/globalStimParams.audioOutCorrection; + +if globalStimParams.doPlay + wavplay(audio,globalStimParams.FS) +end +% all Done +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + + +%---------------------------------------------------- maketone +function tone=maketone(globalStimParams, stimComponents) +% maketone generates a stimComponents tone +% tone=maketone(dt, frequencies, toneDuration, dBSPL, phases) +% tone is returned in Pascals +% frequencies is a list of frequencies +% phase is list of phases or 'sin', 'cos', 'alt' +% +% defaults: +% phase = sin +% dBSPL=56 dB SPL + +dt=globalStimParams.dt; +frequencies=stimComponents.frequencies; +toneDuration=stimComponents.toneDuration; +dBSPL=stimComponents.amplitudesdB; +phases=stimComponents.phases; + +if ischar(phases) + switch phases + case 'sin' + phases= zeros(1,length(frequencies)); + case 'cos' + phases= pi/2*ones(1,length(frequencies)); + case 'alt' + phases= repmat([0 pi/2], 1, floor(length(frequencies)/2)); + if length(phases)<length(frequencies) + phases=[phases 0]; + end + case {'ran', 'rand'} + phases= 2*pi*rand(1,length(frequencies)); + end +end + +if length(phases)==1, phases=repmat(phases(1), 1, length(frequencies)); end +if length(phases)<length(frequencies) + error('makeTone:phase specification must have the same length as frequencies') +end + +if length(dBSPL)==1, dBSPL=repmat(dBSPL(1), 1, length(frequencies)); end +if length(dBSPL)<length(dBSPL) + error('makeTone:dBSPL specification must have the same length as frequencies') +end + +time=dt:dt:toneDuration; +amplitudes=28e-6* 10.^(dBSPL/20); + +tone=zeros(size(time)); +for i=1:length(frequencies) + frequency=frequencies(i); + phase=phases(i); + tone=tone+amplitudes(i)*sin(2*pi*frequency*time+phase); +end + +% figure(1), plot(time, signal) + + +%---------------------------------------------------- makeOHIOtones +function stimulus=makeOHIOtones(globalStimParams, stimComponents) + +% Generates a stimulus consisting of one or more 10-ms tones +% Tones are presented at 10-ms intervals +% Each tone has its own amplitude and its own ramp. + +frequencies=stimComponents.frequencies; +amplitudesdB=stimComponents.amplitudesdB; +nFrequencies=length(frequencies); + +dt=globalStimParams.dt; +toneDuration=.010; +time=dt:dt:toneDuration; + +rampDuration=stimComponents.rampOnDur; +rampTime=dt:dt:rampDuration; +ramp=[0.5*(1+cos(2*pi*rampTime/(2*rampDuration)+pi)) ones(1,length(time)-length(rampTime))]; +ramp=ramp.*fliplr(ramp); + +stimulus= zeros(1,round((toneDuration+globalStimParams.beginSilences(end))/dt)+1); + +for i=1:nFrequencies + toneBeginPTR=round(globalStimParams.beginSilences(i)/dt)+1; + + frequency=frequencies(i); + dBSPL=amplitudesdB(i); + amplitude=28e-6* 10.^(dBSPL/20); + tone=amplitude*sin(2*pi*frequency*time); + tone=tone.*ramp; + stimulus(toneBeginPTR:toneBeginPTR+length(tone)-1)=stimulus(toneBeginPTR:toneBeginPTR+length(tone)-1)+tone; +end +% figure(2), plot( stimulus') + + +%---------------------------------------------------- makeFMtone +function tone=makeFMtone(globalStimParams, stimComponents) +% maketone generates a stimComponents tone +% tone=maketone(dt, frequencies, toneDuration, dBSPL, phases) +% tone is returned in Pascals +% frequencies is a list of frequencies +% phase is list of phases or 'sin', 'cos', 'alt' +% +% defaults: +% phase = sin +% dBSPL=56 dB SPL + +dt=globalStimParams.dt; +frequencies=stimComponents.frequencies; +toneDuration=stimComponents.toneDuration; +dBSPL=stimComponents.amplitudesdB; +phases=stimComponents.phases; +fmDepth=stimComponents.fmDepth; +fmFrequency=stimComponents.fmFrequency; + +if ischar(phases) + switch phases + case 'sin' + phases= zeros(1,length(frequencies)); + case 'cos' + phases= pi/2*ones(1,length(frequencies)); + case 'alt' + phases= repmat([0 pi/2], 1, floor(length(frequencies)/2)); + if length(phases)<length(frequencies) + phases=[phases 0]; + end + end +end + +if length(phases)==1, phases=repmat(phases(1), 1, length(frequencies)); end +if length(phases)<length(frequencies) + error('makeTone:phase specification must have the same length as frequencies') +end + +if length(dBSPL)==1, dBSPL=repmat(dBSPL(1), 1, length(frequencies)); end +if length(dBSPL)<length(dBSPL) + error('makeTone:dBSPL specification must have the same length as frequencies') +end + +time=dt:dt:toneDuration; +amplitudes=28e-6* 10.^(dBSPL/20); + +tone=zeros(size(time)); +for i=1:length(frequencies) + frequency=frequencies(i); + phase=phases(i); + tone=tone+amplitudes(i)*sin(2*pi*frequency*time+phase + fmDepth*sin(2*pi*fmFrequency*time)); +end + +% figure(1), plot(time, signal) + +%----------------------------------------------------makeTransposedStimulus +function [transposedStimulus]=makeTransposedStimulus(globalStimParams, stim) +% globalStimParams.FS=100000; +% globalStimParams.overallDuration=.1; % s +% globalStimParams.doPlot=1; +% +% stim.type='transposedStimulus'; +% stim.phases='sin'; +% stim.toneDuration=.05;; +% stim.frequencies=500; +% stim.amplitudesdB=50; +% stim.beginSilence=.01; +% stim.endSilence=-1; +% stim.rampOnDur=.002; +% stim.rampOffDur=-1; +% +% stim.transCarrierFreq=4000; +% stim.transModFreq=100; + +transposedStimulus=[]; +% make envelope of transposed tone +for i=1:length(stim.transModFreq) + stim.type='tone'; + stim.frequencies=stim.transModFreq(i); + stim.endsilence=-1; stim.beginSilence=0; + [envelope, msg]=stimulusCreate(globalStimParams, stim); % NB recursive + envelope=envelope(:,1); % mono + % HW rectify + envelope(find(envelope<0))=0; + % LP filter + maxEnvelope=max(envelope); + envelope=UTIL_Butterworth (envelope, globalStimParams.dt, 10, .2*stim.transModFreq(i), 2); + envelope=envelope*maxEnvelope/max(envelope); + + % make the carrier + stim.frequencies=stim.transCarrierFreq(i); + stim.endsilence=-1; stim.beginSilence=0; + [audio, msg]=stimulusCreate(globalStimParams, stim); + carrier=audio(:,1); + x= (carrier.*envelope)'; + % base amplitude on peak of unmodulated carrier + x=x/max(carrier); + transposedStimulus=[transposedStimulus; x]; +end +transposedStimulus=sum(transposedStimulus,1); +% clf,plot(transposedStimulus) + +%--------------------------------------------------------------------makeClicks +function clickTrain=makeClicks(globalStimParams, stimComponents) +% makeClicks(F0, clickTimes, duration, FS); +% F0 specifies the repetition rate of the click sequence +% If F0=-1, a single click is generated at the start of the duration of the signal +% +% clickTimes a are fractions of the period +% and specify when the click appears in the period +% A click time of 1 is reset to zero. +% if the clicktime plus the click width is greater than the period, no click is generated +% clicks are treated as 20 microPascal high before amplification +% unless otherwise specified in stimComponents.clickHeight +% click width is dt unless specified in stimComponents.clickWidth +% +% for regular pulse train set clicktimes=1 or zero; +% FS is the sampling interval; +% CarlyonClickTrain(100, [.4 1], 40, 441000); + +FS=globalStimParams.FS; % sample rate +dt=1/FS; + +try,clickWidth=stimComponents.clickWidth;catch, clickWidth=dt; end +try,clickHeight=stimComponents.clickHeight; catch, clickHeight=28e-6 * 10^(stimComponents.amplitudesdB/20); end +try, clickTimes=stimComponents.clickTimes; catch, clickTimes=1; end + +% clickTimes are the times in a cycle that the click +% occurs +checkClickTimes=clickTimes-1; +if max(checkClickTimes)>1 + msg= 'click times must be <= than 1 (period)'; + return +end + +if clickWidth>=1/stimComponents.clickRepeatFrequency + msg= 'click width is too great for frequency'; + return +end + +duration=stimComponents.toneDuration; +totalLength=round(stimComponents.toneDuration/dt); +F0=stimComponents.clickRepeatFrequency; +F0=round(F0/dt)*dt; +if F0==-1 % single click required + F0=1/duration; + repetitions=1; + clickStartTimes=1; %clicktimes are fractions of a period +else + repetitions=round(F0*duration)-1; + duration=repetitions/F0; + clickStartTimes=clickTimes; +end +% if a clickTime=1 (end of duty period) set it to the beginning +clickStartTimes(clickStartTimes==1)=0; + +period=1/F0; +time=dt:dt:period; +nPoints=length(time); +signal=zeros(1,nPoints); +dBSPL=stimComponents.amplitudesdB; + +% compute click train for a single cycle +clickWidthNpoints=round(clickWidth*FS); +for i=1:length(clickStartTimes) + % if clickStartTimes(i)<clickWidth + % clickStartTimes(i)=dt; + % end + clickTime=round(period*clickStartTimes(i)/dt -dt); + % clicks are treated as 20 microPascal high + if clickTime+clickWidthNpoints<length(signal) + signal(clickTime+1:clickTime+clickWidthNpoints)=clickHeight; + end +end + +clickTrain=repmat(signal, 1, repetitions); + +if length(clickTrain)>totalLength + clickTrain=clickTrain(1:totalLength); +elseif length(clickTrain)<totalLength + timeToAdd=zeros(1,round((totalLength-length(clickTrain)))); + clickTrain=[clickTrain timeToAdd]; + % figure (1), plot(clickTrain) +end + +%----------------------------------------------------------------makePressnitzerClicks +function signal=makePressnitzerClicks(globalStimParams, stimComponents) +% PressnitzerClicks(k,duration,dt) +% Generates a sequence of clicks with intervals kxx +% where x= rand*k/2 +% This is not the same as Kaernbach and Demany clicks + +FS=globalStimParams.FS; % sample rate +dt=1/FS; + +% obligatory parameter +try + k=stimComponents.k; +catch + error('PressnitzerClicks: field ''k'' is missing from stimcomponent') +end + +% optional parameters +if isfield(stimComponents,'clickWidth') + clickWidth=stimComponents.clickWidth; +else + clickWidth=dt; +end +clickWidthNpoints=round(clickWidth*FS); + +if isfield(stimComponents,'clickHeight') + clickHeight=stimComponents.clickHeight; +else + clickHeight=28e-6 * 10^(stimComponents.amplitudesdB/20); +end + +duration=stimComponents.toneDuration; + +signalLength=round(duration/dt); +signal=zeros(1,signalLength); +kInterval=round(k/dt); +halfk=k/2; +signal(1)=clickHeight; +timeIdx=0; +while timeIdx<signalLength + % first interval = k + clickTime=timeIdx+kInterval; + signal(clickTime:clickTime+clickWidthNpoints)=clickHeight; + timeIdx=timeIdx+kInterval; + + % second interval = 0 : k/2 + intervalx1=round(rand*halfk/dt); + clickTime=timeIdx+intervalx1; + signal(clickTime:clickTime+clickWidthNpoints)=clickHeight; + timeIdx=timeIdx+intervalx1; + + % third interval = 0 : k/2 + intervalx1=round(rand*halfk/dt); + clickTime=timeIdx+intervalx1; + signal(clickTime:clickTime+clickWidthNpoints)=clickHeight; + timeIdx=timeIdx+intervalx1; +end + +signal=signal(1:signalLength); +% figure(1), plot(dt:dt:duration,signal) + +%----------------------------------------------------------------makePressnitzerABXClicks +function signal=makePressnitzerABxClicks(globalStimParams, stimComponents) +% Generates a sequence of clicks with intervals ABx +% AB interval is 2*k +% where A= rand* k +% B= k-A +% x= k/2 +% These are second order clicks + +FS=globalStimParams.FS; % sample rate +dt=1/FS; + +% obligatory parameter +try + k=stimComponents.k; +catch + error('PressnitzerClicks: field ''k'' is missing from stimcomponent') +end + +% optional parameters +if isfield(stimComponents,'clickWidth') + clickWidth=stimComponents.clickWidth; +else + clickWidth=dt; +end +clickWidthNpoints=round(clickWidth*FS); + +if isfield(stimComponents,'clickHeight') + clickHeight=stimComponents.clickHeight; +else + clickHeight=28e-6 * 10^(stimComponents.amplitudesdB/20); +end + +duration=stimComponents.toneDuration; + +signalLength=round(duration/dt); +signal=zeros(1,2*signalLength); % allow for overrun +ABinterval=k/dt; % i.e. the number of dt steps +randomInterval=ABinterval/2; +signal(1)=clickHeight; +time=0; +while time<signalLength + % first interval = A + intervalA=rand*ABinterval; + clickTime=round(time+intervalA)+1; % +1 avoids zero index + signal(clickTime:clickTime+clickWidthNpoints)=clickHeight; + time=time+intervalA; + + % second interval = B + intervalB=ABinterval-intervalA; + clickTime=round(time+intervalB)+1; + signal(clickTime:clickTime+clickWidthNpoints-1)=clickHeight; + time=time+intervalB; + + % third interval = 0 : k/2 + intervalx1=rand*randomInterval; % mean random interval=k + clickTime=round(time+intervalx1)+1; + signal(clickTime:clickTime+clickWidthNpoints-1)=clickHeight; + time=time+intervalx1; +end + +signal=signal(1:signalLength); +% figure(1), plot(dt:dt:duration,signal) + +%-----------------------------------------------------makeABxClicks +function signal=makeABxClicks(globalStimParams, stimComponents) +% Generates a sequence of clicks with intervals ABx +% AB interval is 2*k +% where A= rand* k +% B= k-A +% x= rand*2*k +% These are second order clicks + +FS=globalStimParams.FS; % sample rate +dt=1/FS; + +% obligatory parameter +try + k=stimComponents.k; +catch + error('PressnitzerClicks: field ''k'' is missing from stimcomponent') +end + +% optional parameters +if isfield(stimComponents,'clickWidth') + clickWidth=stimComponents.clickWidth; +else + clickWidth=dt; +end +clickWidthNpoints=round(clickWidth*FS); + +if isfield(stimComponents,'clickHeight') + clickHeight=stimComponents.clickHeight; +else + clickHeight=28e-6 * 10^(stimComponents.amplitudesdB/20); +end + +duration=stimComponents.toneDuration; + +signalLength=round(duration/dt); +signal=zeros(1,2*signalLength); % allow for overrun +ABinterval=2*k/dt; +randomInterval=ABinterval; +signal(1)=clickHeight; +timeIdx=0; +while timeIdx<signalLength + % first interval = A + intervalA=round(rand*ABinterval); + clickTime=timeIdx+intervalA+1; + signal(clickTime:clickTime+clickWidthNpoints-1)=clickHeight; + timeIdx=timeIdx+intervalA; + + % second interval = B + intervalB=round(ABinterval-intervalA); + clickTime=timeIdx+intervalB; + signal(clickTime:clickTime+clickWidthNpoints-1)=clickHeight; + timeIdx=timeIdx+intervalB; + + % third interval = 0 : k + intervalx1=round(rand*randomInterval); % mean random interval=k + clickTime=timeIdx+intervalx1; + signal(clickTime:clickTime+clickWidthNpoints-1)=clickHeight; + timeIdx=timeIdx+intervalx1; +end + +signal=signal(1:signalLength); +% figure(1), plot(dt:dt:duration,signal) + +%----------------------------------------------------------------makeYostClicks +function signal=makeYostClicks(globalStimParams, stimComponents) +% Generates a shuffled sequence of clicks with intervals kxxxx +% where max(x)= 2*k +% and there are n occurrences of x +% this section requires: +% stimComponents.k +% stimComponents.nXs +% stimComponents.toneDuration +% optional: +% stimComponents.clickWidth %useful because width depends on dt +% stimComponents.clickHeight %best left to amplitude rescaling later + +FS=globalStimParams.FS; % sample rate +dt=1/FS; + +% obligatory parameters +try + k=stimComponents.k; +catch + error('makeYostClicks: field ''k'' is missing from stimComponents') +end + +try + nXs=stimComponents.nXs; +catch + error('makeYostClicks: field ''nXs'' is missing from stimComponents') +end + +try + shuffled=stimComponents.shuffled; +catch + error('makeYostClicks: field ''shuffled'' is missing from stimComponents') +end + +try + duration=stimComponents.toneDuration; +catch + error('makeYostClicks: field ''toneDuration'' is missing from stimComponents') +end + +% optional parameters +if isfield(stimComponents,'clickWidth') + clickWidth=stimComponents.clickWidth; +else + clickWidth=dt; +end +clickWidthNpoints=round(clickWidth*FS); + +if isfield(stimComponents,'clickHeight') + clickHeight=stimComponents.clickHeight; +else + clickHeight=28e-6 * 10^(stimComponents.amplitudesdB/20); +end + +kInterval=round(k/dt); +twoK=k*2; % max width of x interval + +signalLength=round(duration/dt); +signal=zeros(1,signalLength); + +timeIdx=0; +intervalCount=0; +while timeIdx<signalLength + timeIdx=timeIdx+kInterval; + if timeIdx>signalLength, break,end + intervalCount=intervalCount+1; + intervals(intervalCount)=kInterval; + + % repeat x intervals as required + if nXs>0 + for nX=1:nXs + xInterval=round(rand*twoK/dt); + timeIdx=timeIdx+xInterval; + if timeIdx>signalLength, break,end + intervalCount=intervalCount+1; + intervals(intervalCount)=xInterval; + end + end + if timeIdx>signalLength, break,end +end + +% shuffle intervals +if shuffled + randomNumbers=rand(1,length(intervals)); + [randomNumbers idx]=sort(randomNumbers); + intervals=intervals(idx); + idx=intervals>0; + intervals=intervals(idx); +end + +intervalCount=length(intervals); +signal(1)=clickHeight; +clickTime=0; +for i=1:intervalCount + clickTime=clickTime+intervals(i); + signal(clickTime:clickTime+clickWidthNpoints)=clickHeight; +end +signal=signal(1:signalLength); +% figure(1), plot(dt:dt:duration,signal) + +%--------------------------------------------------------------------makeKxxClicks +function signal=makeKxxClicks(globalStimParams, stimComponents) +% Click train consists of kkxxx.. sequences +% k is the duration of a fixed interval (seconds) +% random intervals are distributed 0 : 2* k (NB not like Pressnitzer clicks) +% nKs is the number of successive 'k' intervals +% nXs is the number of random intervals between k sequences +% sequences of 3 x intervals > k are replaced with new sequences +% shuffled causes all intervals to be reordered randomly +% NB 1/k is the mean click rate + +FS=globalStimParams.FS; % sample rate +dt=1/FS; + +try + k=stimComponents.k; % duration (s) of fixed intervals +catch + error('makeYostClicks: field ''k'' is missing from stimComponents') +end + +try + duration=stimComponents.toneDuration; +catch + error('makeYostClicks: field ''duration'' is missing from stimComponents') +end + +if isfield(stimComponents,'clickWidth') + clickWidth=stimComponents.clickWidth; +else + clickWidth=dt; +end + +if isfield(stimComponents,'clickHeight') + clickHeight=stimComponents.clickHeight; +else + clickHeight=28e-6 * 10^(stimComponents.amplitudesdB/20); +end + + +if isfield(stimComponents,'order') + order=stimComponents.order; +else + order=1; +end + +if isfield(stimComponents,'nKs') + nKs=stimComponents.nKs; +else + nKs=1; +end + +if isfield(stimComponents,'nXs') + nXs=stimComponents.nXs; +else + nXs=1; +end + +if isfield(stimComponents,'shuffled') + shuffled=stimComponents.shuffled; +else + shuffled=1; +end + +kLength=round(k/dt); % fixed interval +xLength=2*kLength; % maximum random interval +requiredSignalLength=round(duration/dt); +intervalsPerCycle=(nKs+nXs); +cycleLength=nKs*kLength+nXs*xLength; +% more cycles to allow for uncertainty +nCycles=5*round(requiredSignalLength/cycleLength); +nIntervals=nCycles*intervalsPerCycle; + +% random intervals +if nXs>0 + xIntervals=floor(rand(1,nIntervals)*2*kLength); + % weed out triple intervals > 2*k + rogues=1; + while sum(rogues) + y=(xIntervals>kLength); + rogues=(sum([y(1:end-2)' y(2:end-1)' y(3:end)'],2)>2); + xIntervals(rogues)=floor(rand*2*kLength); + end + xIntervals=reshape(xIntervals,nCycles,intervalsPerCycle); +else + xIntervals=[]; +end + +% insert constrained (k) intervals +if nKs>0 + switch order + case 1 + kIntervals=floor(ones(nCycles,nKs)*kLength); + case 2 + nKs=1; % force this + kIntervals=floor(rand(nCycles,1)*kLength); + kIntervals=[kIntervals kLength-kIntervals]; + end +else + kIntervals=[]; +end + +% combine fixed and random +intervals=[kIntervals xIntervals(:,nKs+1:end)]; +% make a single array; +[r c]=size(intervals); +intervals=reshape(intervals',1,r*c); + +% shuffle intervals +if shuffled + randomNumbers=rand(1,length(intervals)); + [randomNumbers idx]=sort(randomNumbers); + intervals=intervals(idx); + idx=intervals>0; + intervals=intervals(idx); +end + +% convert intervals to clicks +clickTimes=cumsum(intervals); +signal(clickTimes)=clickHeight; +signal=signal(1:requiredSignalLength); +% figure(1), clf, plot(signal) + + + +%--------------------------------------------------------------------makeNoise +function noise=makeNoise(globalStimParams, stimComponents) +% FS in Hz, noiseDuration in s, delay in s; +% noise is returned with overall level dB(rms) = amplitudesdB +% +% % You need +% +% stim.type='noise'; % or 'IRN', or 'pinkNoise' +% stim.toneDuration=.05; +% stim.amplitudesdB=50; +% stim.beginSilence=.01; +% stim.endSilence=-1; +% stim.rampOnDur=.002; +% stim.rampOffDur=-1; +% +% % Mandatory structure fields +% globalStimParams.FS=100000; +% globalStimParams.overallDuration=.1; % s +% globalStimParams.doPlot=1; +% globalStimParams.doPlay=1; +% +% [audio, msg]=stimulusCreate(globalStimParams, stim, ); +% +% % local +% stim.type='noise'; % or 'IRN' +% +FS=globalStimParams.FS; +noiseDuration= stimComponents.toneDuration; +npts=round(noiseDuration*FS); +noise=randn(1,npts); % NB randn (normally distributed) + +switch stimComponents.type + case 'pinkNoise' + % noise=UTIL_lowpassFilterFreq(noise, 100, 1/FS); + noise=UTIL_bandPassFilter(noise, 1, 100, 200, 1/FS,[]); +end + +rms=(mean(noise.^2)^.5); %should be 20 microPascals for 0 dB SPL +adjust=20e-6/rms; +noise=noise*adjust; +rms=(mean(noise.^2)^.5); +amplitude=10.^(stimComponents.amplitudesdB/20); +noise=amplitude*noise; +% rms=(mean(noise.^2)^.5); +% dBnoise=20*log10(rms/20e-6) + + +%--------------------------------------------------------------------makeWhiteNoise +function noise=makeWhiteNoise(globalStimParams, stimComponents) +% FS in Hz, noiseDuration in s, delay in s; +% noise is bandpass filtered between 100 and 10000 Hz +% spectrum level (dB/Hz) is 40 dB below nominal level. +% noise is returned with dB(rms) = amplitudesdB +% +% % You need +% +% stim.type='noise'; % or 'IRN', or 'pinkNoise' +% stim.toneDuration=.05; +% stim.amplitudesdB=50; +% stim.beginSilence=.01; +% stim.endSilence=-1; +% stim.rampOnDur=.002; +% stim.rampOffDur=-1; +% +% % Mandatory structure fields +% globalStimParams.FS=100000; +% globalStimParams.overallDuration=.1; % s +% globalStimParams.doPlot=1; +% globalStimParams.doPlay=1; +% +% [audio, msg]=stimulusCreate(globalStimParams, stim, ); +% +% % local +% stim.type='noise'; % or 'IRN' +% +FS=globalStimParams.FS; +noiseDuration= stimComponents.toneDuration; +npts=round(noiseDuration*FS); +noise=randn(1,npts); + +noise=UTIL_bandPassFilter (noise, 6, 100, 10000, 1/FS, []); + +rms=(mean(noise.^2)^.5); %should be 20 microPascals for 0 dB SPL +adjust=20e-6/rms; +noise=noise*adjust; +rms=(mean(noise.^2)^.5); +amplitude=10.^(stimComponents.amplitudesdB/20); +noise=amplitude*noise; +% rms=(mean(noise.^2)^.5); +% dBnoise=20*log10(rms/20e-6) + + +%-----------------------------------------------------------------makeIRN +function noise=makeIRN(globalStimParams, stimComponents) +% FS in Hz, noiseDuration in s, delay in s; +% noise is returned with dB(rms) = amplitudesdB +% +% % You need +% +% stim.type='noise'; % or 'IRN', or 'pinkNoise' +% stim.toneDuration=.05; +% stim.amplitudesdB=50; +% stim.beginSilence=.01; +% stim.endSilence=-1; +% stim.rampOnDur=.002; +% stim.rampOffDur=-1; +% +% % Mandatory structure fields +% globalStimParams.FS=100000; +% globalStimParams.overallDuration=.1; % s +% globalStimParams.doPlot=1; +% globalStimParams.doPlay=1; +% +% [audio, msg]=stimulusCreate(globalStimParams, stim, ); +% +% % local +% stim.type='noise'; % or 'IRN' +% % for IRN only +% stim.niterations = 8; %0 for white noise +% stim.delay = 1/150; +% stim.irnGain = 1; +% +FS=globalStimParams.FS; +noiseDuration= stimComponents.toneDuration; + +nIterations=stimComponents.niterations; +if nIterations==0 + % white noise is specified as nIterations=1 + nIterations=1; + IRNgain=0; + delay=0.01; % dummy +else + % IRN + delay=stimComponents.delay; + IRNgain=stimComponents.irnGain; +end + +npts=round(noiseDuration*FS); +dels=round(delay*FS); +noise=randn(1,npts); + +%fringe=nIterations*dels; +%npts=npts+fringe; + +for i=1:nIterations, + dnoise=[noise(dels+1:npts) noise(1:dels)]; + dnoise=dnoise.*IRNgain; + noise=noise+dnoise; +end; + +switch stimComponents.type + case 'pinkNoise' + noise=UTIL_lowpassFilterFreq(noise, 10000, 1/FS); +end + +rms=(mean(noise.^2)^.5); %should be 20 microPascals for 0 dB SPL +adjust=20e-6/rms; +noise=noise*adjust; +rms=(mean(noise.^2)^.5); +amplitude=10.^(stimComponents.amplitudesdB/20); +noise=amplitude*noise; +% rms=(mean(noise.^2)^.5); +% dBnoise=20*log10(rms/20e-6) + +%------------------------------------------------------------------ makeRPN +function RPN=makeRPN(globalStimParams, stim) +% 'period' is a collection of samples - AAABCD +% you need +% +% stim.type='RPN'; +% stim.toneDuration=.2; +% stim.amplitudesdB=50; +% stim.beginSilence=.01; +% stim.rampOnDur=.002; +% stim.rampOffDur=-1; +% +% stim.sampleDuration=.005; %200 Hz pitch +% stim.nSimilarSamples=5; % pitch strength +% stim.nIndependentSamples=1% dilutes strength +% +% % Mandatory structure fields +% globalStimParams.FS=44100; +% globalStimParams.overallDuration=.21; % s +% +% globalStimParams.doPlot=1; +% globalStimParams.doPlay=1; +% [audio, msg]=stimulusCreate(globalStimParams, stim); + +FS=globalStimParams.FS; +ptsPerSample=floor(stim.sampleDuration*FS); + +samplesPerPeriod=stim.nSimilarSamples+stim.nIndependentSamples; +periodDuration=samplesPerPeriod*stim.sampleDuration; + +totalNumPeriods=2*floor(stim.toneDuration/periodDuration); % longer than necessary +if totalNumPeriods<1 + error('stimulusCreate: RPN, stimulus duration needs to be longer') +end + +RPN=[]; +for j=1:totalNumPeriods + noise=randn(1,ptsPerSample); + for i=1:stim.nSimilarSamples + RPN=[RPN noise]; + end + + for i=1:stim.nIndependentSamples + noise=randn(1,ptsPerSample); + RPN=[RPN noise]; + end +end + +targetStimulusLength=round(stim.toneDuration/FS); +RPN=RPN(1:floor(stim.toneDuration*FS)); % take enough for stimulus + +rms=(mean(RPN.^2)^.5); %should be 20 microPascals for 0 dB SPL +adjust=20e-6/rms; +RPN=RPN*adjust; +rms=(mean(RPN.^2)^.5); +amplitude=10.^(stim.amplitudesdB/20); +RPN=amplitude*RPN; +% rms=(mean(noise.^2)^.5); +% dBnoise=20*log10(rms/20e-6) + +%--------------------------------------------------------------------applyRampOn +function signal=applyRampOn(signal, rampDur, rampOnTime, sampleRate) +%applyRampOn applies raised cosine ramp +%rampOntime is the time at which the ramp begins +%At all other times the mask has a value of 1 +%signal=applyRampOn(signal, rampDur, rampOnTime, sampleRate) + +rampDurPoints=round(rampDur*sampleRate); +rampOn= (1+cos(pi:pi/(rampDurPoints-1): 2*pi))/2'; + +sigDurPoints=length(signal); +mask(1:sigDurPoints)=1; +rampOnStartIndex=round(rampOnTime*sampleRate+1); +mask(rampOnStartIndex: rampOnStartIndex+ rampDurPoints-1)=rampOn; +signal=signal.*mask; +%plot(mask) + +%--------------------------------------------------------------------applyRampOff +function signal=applyRampOff(signal, rampDur, rampOffTime, sampleRate) +%applyRampOn applies raised cosine squared ramp +%rampOffTime is the time at which the ramp begins +%At all other times the mask has a value of 1 +% signal=applyRampOff(signal, rampDur, rampOffTime, sampleRate) + +rampDurPoints=round(rampDur*sampleRate); +rampOff= (1+cos(0:pi/(rampDurPoints-1): pi))/2'; + +sigDurPoints=length(signal); +mask=ones(1,sigDurPoints); +rampOffStartIndex=round(rampOffTime*sampleRate+1); +mask(rampOffStartIndex: round(rampOffStartIndex+ rampDurPoints-1))=rampOff; +if length(mask)>sigDurPoints, mask=mask(1:sigDurPoints); end +signal=signal.*mask; +%plot(mask) + +function signal=applyGaussianRamps(signal, sigma, sampleRate) +dt=1/sampleRate; +time=dt:dt:dt*length(signal); +ramp=1-exp(-time.^2/(2*sigma^2)); +% apply onset ramp +signal=signal.*ramp; +% apply offset ramp +ramp=fliplr(ramp); +signal=signal.*ramp; + + + +%--------------------------------------------------------------------checkDescriptors +function [globalStimParams, stimComponents]=checkDescriptors(globalStimParams, stimComponents); + +try + % if FS exists, it takes priority + globalStimParams.dt=1/globalStimParams.FS; +catch + % otherwise set FS using dt + globalStimParams.FS=1/globalStimParams.dt; +end + +globalStimParams.nSignalPoints=round(globalStimParams.overallDuration/globalStimParams.dt); + +% optional field (ears) +try + globalStimParams.ears; +catch + % default: dichotic. + globalStimParams.ears='dichotic'; +end + +% audioOutCorrection is optional +% audioOutCorrection is a scalar for reducing the sound +try + globalStimParams.audioOutCorrection; +catch + % set to 1 if omitted + globalStimParams.audioOutCorrection=1; +end + +try + globalStimParams.doPlay; +catch + % default plays sound only if explicitly requested + globalStimParams.doPlay=0; +end + +try + globalStimParams.doPlot; +catch + % no plotting unless explicitly requested + globalStimParams.doPlot=0; +end + + + +[ears nComponentSounds]=size(stimComponents); + +for ear=1:2 % 1=left/ 2=right + + % create a list of components whose type is specified + % if no type is specified assume that it is an empty specification + % this is allowed + validStimComponents=[]; + for i=1:nComponentSounds + try + if ~isempty(stimComponents(ear,i).type) + validStimComponents=[validStimComponents i]; + end + catch + end + end + + for componentNo=validStimComponents + % If no AM filed is present, create it for completeness + if ~isfield(stimComponents(ear,componentNo),'AMfrequency') |... + ~isfield(stimComponents(ear,componentNo),'AMdepth') + stimComponents(ear,componentNo).AMfrequency=0; + stimComponents(ear,componentNo).AMdepth=0; + end + + % all signals must have durations, amplitudes and ramps + if ... + isempty(stimComponents(ear,componentNo).type) |... + isempty(stimComponents(ear,componentNo).toneDuration) |... + isempty(stimComponents(ear,componentNo).amplitudesdB) |... + isempty(stimComponents(ear,componentNo).rampOnDur) + descriptorError( 'missing stimComponent descriptor', stimComponents, ear, componentNo) + end + + try, stimComponents(ear,componentNo).endSilence; catch, stimComponents(ear,componentNo).endSilence=-1; end + + % ramp checks do not apply to file input + if ~strcmp(stimComponents(ear,componentNo).type, 'file') + % match offset ramp to onset if not explicitly specified + if stimComponents(ear,componentNo).rampOffDur==-1, + stimComponents(ear,componentNo).rampOffDur=stimComponents(ear,componentNo).rampOnDur; + end + % ramps must be shorter than the stimulus + if stimComponents(ear,componentNo).rampOffDur> stimComponents(ear,componentNo).toneDuration | ... + stimComponents(ear,componentNo).rampOnDur> stimComponents(ear,componentNo).toneDuration + descriptorError( 'ramp longer than sound component', stimComponents, ear, componentNo) + end + end + + % end silence is measured to fit into the global duration + if stimComponents(ear,componentNo).endSilence==-1, + endSilenceNpoints=... + globalStimParams.nSignalPoints ... + -round(stimComponents(ear,componentNo).beginSilence*globalStimParams.FS)... + -round(stimComponents(ear,componentNo).toneDuration*globalStimParams.FS); + stimComponents(ear,componentNo).endSilence=endSilenceNpoints/globalStimParams.FS; + % if endSilence < 0, we have a problem + if stimComponents(ear,componentNo).endSilence<0 + globalStimParams + descriptorError( 'component durations greater than overallDuration', stimComponents, ear, componentNo) + end + end + + % check overall duration of this component against global duration + totalDuration= ... + stimComponents(ear,componentNo).beginSilence+... + stimComponents(ear,componentNo).toneDuration+... + stimComponents(ear,componentNo).endSilence; + + % avoid annoying error message for single stimulus component + if ears==1 && nComponentSounds==1 + globalStimParams.overallDuration= totalDuration; + end + + + if round(totalDuration*globalStimParams.FS)>round(globalStimParams.overallDuration*globalStimParams.FS) + globalStimParams + descriptorError( 'Component durations greater than overallDuration', stimComponents, ear, componentNo) + end + + % check total duration + totalSignalPoints= round((stimComponents(ear,componentNo).beginSilence+ stimComponents(ear,componentNo).toneDuration+... + stimComponents(ear,componentNo).endSilence)/globalStimParams.dt); + if totalSignalPoints >globalStimParams.nSignalPoints + descriptorError( 'Signal component duration does not match overall duration ', stimComponents, ear, componentNo) + end + + % no ramps for clicks please! + % if strcmp(stimComponents(ear,componentNo).type, 'clicks') & stimComponents(ear,componentNo).clickRepeatFrequency==-1 + % if strcmp(stimComponents(ear,componentNo).type, 'clicks') + % stimComponents(ear,componentNo).rampOnDur=0; + % stimComponents(ear,componentNo).rampOffDur=0; + % end + + if isfield(stimComponents(ear,componentNo), 'filter') + if ~isequal(length(stimComponents(ear,componentNo).filter), 3) + descriptorError( 'Filter parameter must have three elements ', stimComponents, ear, componentNo) + end + end + end % component + % ?? + if strcmp(globalStimParams.ears,'monoticL') | strcmp(globalStimParams.ears, 'monoticR'), break, end +end % ear + + +%-------------------------------------------------------------------- descriptorError +function descriptorError( msg, stimComponents, ear, componentNo) +stimComponents(ear, componentNo) + +disp(' *********** **************** ************') +disp([ '...Error in stimComponents description: ']) +disp([msg ]) +disp(['Ear = ' num2str(ear) ' component No ' num2str(componentNo)]) +disp(' *********** **************** ************') +error('myError ') + + +%-------------------------------------------------------------------- normalize +function [normalizedSignal, gain]= normalize(signal) +% normalize (signal) +maxSignal=max(max(signal)); +minSignal=min(min(signal)); +if -minSignal>maxSignal, normalizingFactor=-minSignal; else normalizingFactor=maxSignal; end +normalizingFactor=1.01*normalizingFactor; +gain= 20*log10(normalizingFactor); +normalizedSignal=signal/normalizingFactor; + + +%--------------------------------------------------------------------Butterworth +function y=Butterworth (x, dt, fl, fu, order) +% Butterworth (x, dt, fu, fl, order) +% Taken from Yuel and beauchamp page 261 +% NB error in their table for K (see their text) +% x is original signal +% fu, fl upper and lower cutoff +% order is the number of times the filter is applied + +q=(pi*dt*(fu-fl)); +J=1/(1+ cot(q)); +K= (2*cos(pi*dt*(fu+fl)))/(1+tan(q)*cos(q)); +L= (tan(q)-1)/(tan(q)+1); +b=[J -J]; +a=[1 -K -L]; +for i=1:order + y=filter(b, a, x); + x=y; +end + + +% -------------------------------------------------------- UTIL_amp2dB +function [y] = UTIL_amp2dB (x, ref) +% Calculates a dB (ref. ref) value 'y' from a peak amplitude number 'x'. +% if ref omitted treat as dB +% Check the number of arguments that are passed in. +if (nargin < 2) + ref=1; +end +if (nargin > 2) + error ('Too many arguments'); +end + +% Check arguments. +if x < 0.0 + error ('Can not calculate the log10 of a negative number'); +elseif x == 0.0 + warning ('log10 of zero. The result is set to -1000.0'); + y = -1000.0; +else + % Do calculations. + y = 20.0 * log10(x/(sqrt(2)*ref)); + +end + +%-------------------------------------------------------------------- FFT +function showFFT (getFFTsignal, dt) +color='r'; +figure(2), clf, +hold off + +% trim initial silence +idx=find(getFFTsignal>0); +if ~isempty(idx) + getFFTsignal=getFFTsignal(idx(1):end); +end +%trim final silence +getFFTsignal=getFFTsignal(end:-1:1); +idx=find(getFFTsignal>0); +if ~isempty(idx) + getFFTsignal=getFFTsignal(idx(1):end); + getFFTsignal=getFFTsignal(end:-1:1); +end + +% Analyse make stimComponents length a power of 2 +x=length(getFFTsignal); +squareLength=2; +while squareLength<=x + squareLength=squareLength*2; +end +squareLength=round(squareLength/2); +getFFTsignal=getFFTsignal(1:squareLength); +n=length(getFFTsignal); + +minf=100; maxf=20000; + +fft_result = fft(getFFTsignal, n); % Compute FFT of the input signal. +fft_power = fft_result .* conj(fft_result);% / n; % Compute power spectrum. Dividing by 'n' we get the power spectral density. +fft_phase = angle(fft_result); % Compute the phase spectrum. + +frequencies = (1/dt)*(1:n/2)/n; +fft_power=fft_power(1:length(fft_power)/2); % remove mirror frequencies +fft_phase=fft_phase(1:length(fft_phase)/2); % remove mirror frequencies +fft_powerdB = UTIL_amp2dB (fft_power, max(fft_power)); % convert to dB +% jags=find(diff(fft_phase)>0); % unwrap phase +% for i=jags, fft_phase(i+1:end)=fft_phase(i+1:end)-2*pi; end + +xlim([minf maxf]) +semilogx(frequencies, fft_powerdB-max(fft_powerdB), color) +ylim([ -20 5]) + + + +function y=UTIL_lowpassFilterFreq(x, cutOffFrequency, dt) +% UTIL_lowpassFilterFreq multi-channel filter +% +% Usage: +% output=UTIL_lowpassFilterFreq(input, cutOffFrequency, dt) +% +% cutoff should be around 100 Hz for audio work +% dt should be <1/50000 s for audio work +% +% Attenuation is - 6 dB per octave above cutoff. + + +sampleRate=1/dt; + +if 4*cutOffFrequency>sampleRate + 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' ]) + cutOffFrequency=sampleRate/4; +end + +tau=1/(2*pi*cutOffFrequency); + +y=zeros(size(x)); +[numChannels numPoints]=size(x); +for i=1:numChannels + y(i,:)=filter([dt/tau], [1 -(1-dt/tau)], x(i,:)); +end + +