rmeddis@38
|
1 classdef cEssexAid
|
rmeddis@38
|
2 %ESSEXAID_WRAPCLASS Wrapper for the EssexAid - Nick Clark July 2011
|
rmeddis@38
|
3 % This class wraps up the EssexAid algorithm function that processes
|
rmeddis@38
|
4 % each block of samples. This wrapper closely emulates the GUI used
|
rmeddis@38
|
5 % in the lab and runs stimuli through the exact same algorithm used
|
rmeddis@38
|
6 % in the lab. It even includes a helper function to generate C code
|
rmeddis@38
|
7 % from the algorithm for use in a real-time framework.
|
rmeddis@38
|
8
|
rmeddis@38
|
9
|
rmeddis@38
|
10 %% *********************************************************
|
rmeddis@38
|
11 % properties _ _
|
rmeddis@38
|
12 % | | (_)
|
rmeddis@38
|
13 % _ __ _ __ ___ _ __ ___ _ __| |_ _ ___ ___
|
rmeddis@38
|
14 % | '_ \| '__/ _ \| '_ \ / _ \ '__| __| |/ _ \/ __|
|
rmeddis@38
|
15 % | |_) | | | (_) | |_) | __/ | | |_| | __/\__ \
|
rmeddis@38
|
16 % | .__/|_| \___/| .__/ \___|_| \__|_|\___||___/
|
rmeddis@38
|
17 % | | | |
|
rmeddis@38
|
18 % |_| |_|
|
rmeddis@38
|
19 %************************************************************
|
rmeddis@38
|
20
|
rmeddis@38
|
21 %% **********************************************************
|
rmeddis@38
|
22 % Public properties - can be set by user
|
rmeddis@38
|
23 %************************************************************
|
rmeddis@38
|
24 properties(Access = public)
|
rmeddis@38
|
25 sr = 48e3;
|
rmeddis@38
|
26 numSamples = 1024; %MAX=6912, LAB_USE=48
|
rmeddis@38
|
27 stimulusUSER
|
rmeddis@38
|
28
|
rmeddis@38
|
29 %------------------------------------------------------------------
|
rmeddis@38
|
30 % Params for audiometric freqs 250, 500, 1000, 2000, 4000, 8000 Hz
|
rmeddis@38
|
31 %------------------------------------------------------------------
|
rmeddis@38
|
32 audiometry_dB= [ 0; 0; 0; 0; 0; 0]; %Pure tone threshold in dB SPL
|
rmeddis@38
|
33 mainGain_dB = [ 0; 0; 0; 0; 0; 0]; %Gain applied at audiometric frequencies
|
rmeddis@38
|
34 TC_dBHL = [40; 40; 40; 40; 40; 40]; %Compression thresholds (in dB HL from 2nd filt)
|
rmeddis@38
|
35 TM_dBHL = [10; 10; 10; 10; 10; 10]; %MOC thresholds (in dB OUTPUT from 2nd filt)
|
rmeddis@38
|
36 DRNLc = [ 0.2; 0.2; 0.2; 0.2; 0.2; 0.2]; %Compression exponent at audiometric frequencies
|
rmeddis@38
|
37
|
rmeddis@38
|
38 %------------------------------------------------------------------
|
rmeddis@38
|
39 % Dynamic compression properties
|
rmeddis@38
|
40 %------------------------------------------------------------------
|
rmeddis@38
|
41 ARtau = 60e-3; %decay time constant
|
rmeddis@38
|
42 ARthreshold_dB = 85; %dB SPL (input signal level) =>200 to disable
|
rmeddis@38
|
43 MOCtau = 450e-3; %Time constant in Seconds
|
rmeddis@38
|
44 MOCfactor = 0.5; %dB attenuation applied to the input per dB exceeding output threshold
|
rmeddis@38
|
45
|
rmeddis@38
|
46 %------------------------------------------------------------------
|
rmeddis@38
|
47 % Band filtering properties
|
rmeddis@38
|
48 %------------------------------------------------------------------
|
rmeddis@38
|
49 bwOct = 1/2; %1/1, 1/2, 1/3, 1/4, 1/5
|
rmeddis@38
|
50 filterOrder = 2 %BUTTER=2, GTF=3
|
rmeddis@38
|
51 useGTF = false; %If false, revert to butterworth
|
rmeddis@38
|
52 end
|
rmeddis@38
|
53
|
rmeddis@38
|
54 %% **********************************************************
|
rmeddis@38
|
55 % Read only properties that are not dependent
|
rmeddis@38
|
56 %************************************************************
|
rmeddis@38
|
57 properties(SetAccess = private)
|
rmeddis@38
|
58 MOCrecord
|
rmeddis@38
|
59 end
|
rmeddis@38
|
60
|
rmeddis@38
|
61 %% **********************************************************
|
rmeddis@38
|
62 % Constant properties
|
rmeddis@38
|
63 %************************************************************
|
rmeddis@38
|
64 properties(Constant = true, Hidden = true)
|
rmeddis@38
|
65 numAudiometricFreqs = 6;
|
rmeddis@38
|
66 end
|
rmeddis@38
|
67
|
rmeddis@38
|
68 %% **********************************************************
|
rmeddis@38
|
69 % Dependent visable properties - calculated at runtime
|
rmeddis@38
|
70 %************************************************************
|
rmeddis@38
|
71 properties(Dependent = true, Hidden = false)
|
rmeddis@38
|
72 channelBFs %= 250 * 2.^((0:fNmax)'*params.bwOct);
|
rmeddis@38
|
73 numChannels %= numel(channelBFs);
|
rmeddis@38
|
74 aidOPnice %aid output reformatted to be exactly the same dimensions as the input stimulus
|
rmeddis@38
|
75 end
|
rmeddis@38
|
76
|
rmeddis@38
|
77 %% **********************************************************
|
rmeddis@38
|
78 % Dependent invisable properties - calculated at runtime
|
rmeddis@38
|
79 %************************************************************
|
rmeddis@38
|
80 properties(Dependent = true, Hidden = true)
|
rmeddis@38
|
81 TC_dBO_INTERP % Compression threshold in terms of 2nd filter o/p in dB SPL
|
rmeddis@38
|
82 TM_dBO_INTERP % MOC threshold in terms of 2nd filter o/p in dB SPL
|
rmeddis@38
|
83 bwOct_INTERP
|
rmeddis@38
|
84 DRNLb_INTERP %= ( 2e-5 .* 10.^(TCdBO/20)) .^ (1-DRNLc) ;
|
rmeddis@38
|
85 DRNLc_INTERP
|
rmeddis@38
|
86 mainGain_INTERP %Interp'd and in linear units
|
rmeddis@38
|
87
|
rmeddis@38
|
88 ARthresholdPa %= 20e-6*10^(ARthreshold_dB/20);% Pa thresh for triggering AR
|
rmeddis@38
|
89 stimulusINTERNAL %input stimulus in correct format for the Aid algo
|
rmeddis@38
|
90 end
|
rmeddis@38
|
91
|
rmeddis@38
|
92 %% **********************************************************
|
rmeddis@38
|
93 % Protected properties - The user never needs to set
|
rmeddis@38
|
94 %************************************************************
|
rmeddis@38
|
95 properties(Access = protected)
|
rmeddis@38
|
96 aidOP
|
rmeddis@38
|
97 emlc_z
|
rmeddis@38
|
98
|
rmeddis@38
|
99 %--------------------------------------------------------------
|
rmeddis@38
|
100 % ENUMERATIONS USED IN THE FRAME PROCESSOR
|
rmeddis@38
|
101 %--------------------------------------------------------------
|
rmeddis@38
|
102 enumC_ARb = 0;
|
rmeddis@38
|
103 enumC_ARa = 2;
|
rmeddis@38
|
104 enumC_MOCb = 4;
|
rmeddis@38
|
105 enumC_MOCa = 6;
|
rmeddis@38
|
106
|
rmeddis@38
|
107 % enumC_BPb1 = 8;
|
rmeddis@38
|
108 % enumC_BPa1 = 13;
|
rmeddis@38
|
109 % enumC_BPb2 = 18;
|
rmeddis@38
|
110 % enumC_BPa2 = 23;
|
rmeddis@38
|
111 % enumC_BPb3 = 28;
|
rmeddis@38
|
112 % enumC_BPa3 = 33;
|
rmeddis@38
|
113 % enumC_BPb4 = 38;
|
rmeddis@38
|
114 % enumC_BPa4 = 43;
|
rmeddis@38
|
115
|
rmeddis@38
|
116 enumS_AR = 0;
|
rmeddis@38
|
117
|
rmeddis@38
|
118 % enumS_MOC1 = 1;
|
rmeddis@38
|
119 % enumS_BPin_1_1 = 2;
|
rmeddis@38
|
120 % enumS_BPin_2_1 = 6;
|
rmeddis@38
|
121 % enumS_BPout_1_1 = 10;
|
rmeddis@38
|
122 % enumS_BPout_2_1 = 14;
|
rmeddis@38
|
123 %
|
rmeddis@38
|
124 % enumS_MOC2 = 18;
|
rmeddis@38
|
125 % enumS_BPin_1_2 = 19;
|
rmeddis@38
|
126 % enumS_BPin_2_2 = 23;
|
rmeddis@38
|
127 % enumS_BPout_1_2 = 27;
|
rmeddis@38
|
128 % enumS_BPout_2_2 = 31;
|
rmeddis@38
|
129 % ...
|
rmeddis@38
|
130 end
|
rmeddis@38
|
131
|
rmeddis@38
|
132 %% **********************************************************
|
rmeddis@38
|
133 % methods _ _ _
|
rmeddis@38
|
134 % | | | | | |
|
rmeddis@38
|
135 % _ __ ___ ___| |_| |__ ___ __| |___
|
rmeddis@38
|
136 %| '_ ` _ \ / _ \ __| '_ \ / _ \ / _` / __|
|
rmeddis@38
|
137 %| | | | | | __/ |_| | | | (_) | (_| \__ \
|
rmeddis@38
|
138 %|_| |_| |_|\___|\__|_| |_|\___/ \__,_|___/
|
rmeddis@38
|
139 %************************************************************
|
rmeddis@38
|
140
|
rmeddis@38
|
141 methods
|
rmeddis@38
|
142 %% **********************************************************
|
rmeddis@38
|
143 % Constructor
|
rmeddis@38
|
144 %************************************************************
|
rmeddis@38
|
145 function obj = EssexAid_WrapClass(sr, stimulus)
|
rmeddis@38
|
146
|
rmeddis@38
|
147 if nargin > 0
|
rmeddis@38
|
148 obj.sr = sr;
|
rmeddis@38
|
149 end
|
rmeddis@38
|
150
|
rmeddis@38
|
151 if nargin > 1
|
rmeddis@38
|
152 obj.stimulusUSER = stimulus;
|
rmeddis@38
|
153 else
|
rmeddis@38
|
154 obj.stimulusUSER = obj.pipSequence(obj.sr);
|
rmeddis@38
|
155 end
|
rmeddis@38
|
156 end
|
rmeddis@38
|
157
|
rmeddis@38
|
158 %% **********************************************************
|
rmeddis@38
|
159 % Get method for channelBFs
|
rmeddis@38
|
160 %************************************************************
|
rmeddis@38
|
161 function value = get.channelBFs(obj)
|
rmeddis@38
|
162 fNmax = 5/obj.bwOct;
|
rmeddis@38
|
163 value = 250 * 2.^((0:fNmax)'*obj.bwOct);
|
rmeddis@38
|
164 end
|
rmeddis@38
|
165
|
rmeddis@38
|
166 %% **********************************************************
|
rmeddis@38
|
167 % Get method for numChannels
|
rmeddis@38
|
168 %************************************************************
|
rmeddis@38
|
169 function value = get.numChannels(obj)
|
rmeddis@38
|
170 value = numel(obj.channelBFs);
|
rmeddis@38
|
171 end
|
rmeddis@38
|
172
|
rmeddis@38
|
173 %% **********************************************************
|
rmeddis@38
|
174 % Get method for ARthresholdPa
|
rmeddis@38
|
175 %************************************************************
|
rmeddis@38
|
176 function value = get.ARthresholdPa(obj)
|
rmeddis@38
|
177 value = 20e-6*10^(obj.ARthreshold_dB/20);% Pa thresh for triggering AR
|
rmeddis@38
|
178 end
|
rmeddis@38
|
179
|
rmeddis@38
|
180 %% **********************************************************
|
rmeddis@38
|
181 % Get method for TC_dBO_INTERP
|
rmeddis@38
|
182 %************************************************************
|
rmeddis@38
|
183 function value = get.TC_dBO_INTERP(obj)
|
rmeddis@38
|
184 TC_dBO = obj.audiometry_dB - obj.mainGain_dB + obj.TC_dBHL;
|
rmeddis@38
|
185 value = obj.interpPars(TC_dBO, obj.numChannels);
|
rmeddis@38
|
186 end
|
rmeddis@38
|
187
|
rmeddis@38
|
188 %% **********************************************************
|
rmeddis@38
|
189 % Get method for TM_dBO_INTERP
|
rmeddis@38
|
190 %************************************************************
|
rmeddis@38
|
191 function value = get.TM_dBO_INTERP(obj)
|
rmeddis@38
|
192 TM_dBO = obj.audiometry_dB - obj.mainGain_dB + obj.TM_dBHL;
|
rmeddis@38
|
193 value = obj.interpPars(TM_dBO, obj.numChannels);
|
rmeddis@38
|
194 end
|
rmeddis@38
|
195
|
rmeddis@38
|
196 %% **********************************************************
|
rmeddis@38
|
197 % Get method for bwOct_INTERP
|
rmeddis@38
|
198 %************************************************************
|
rmeddis@38
|
199 function value = get.bwOct_INTERP(obj)
|
rmeddis@38
|
200 value = repmat(obj.bwOct, 1, obj.numChannels);
|
rmeddis@38
|
201 end
|
rmeddis@38
|
202
|
rmeddis@38
|
203 %% **********************************************************
|
rmeddis@38
|
204 % Get method for DRNLb_INTERP
|
rmeddis@38
|
205 %************************************************************
|
rmeddis@38
|
206 function value = get.DRNLb_INTERP(obj)
|
rmeddis@38
|
207 value = ( 2e-5 .* 10.^(obj.TC_dBO_INTERP/20)) .^ (1-obj.DRNLc_INTERP);
|
rmeddis@38
|
208 end
|
rmeddis@38
|
209
|
rmeddis@38
|
210 %% **********************************************************
|
rmeddis@38
|
211 % Get method for DRNLc_INTERP
|
rmeddis@38
|
212 %************************************************************
|
rmeddis@38
|
213 function value = get.DRNLc_INTERP(obj)
|
rmeddis@38
|
214 value = obj.interpPars(obj.DRNLc, obj.numChannels);
|
rmeddis@38
|
215 end
|
rmeddis@38
|
216
|
rmeddis@38
|
217 %% **********************************************************
|
rmeddis@38
|
218 % Get method for mainGain_INTERP
|
rmeddis@38
|
219 %************************************************************
|
rmeddis@38
|
220 function value = get.mainGain_INTERP(obj)
|
rmeddis@38
|
221 mainGainLin = 10.^(obj.mainGain_dB/20); %lin units
|
rmeddis@38
|
222 value = obj.interpPars(mainGainLin, obj.numChannels);
|
rmeddis@38
|
223 end
|
rmeddis@38
|
224
|
rmeddis@38
|
225 %% ***********************************************************
|
rmeddis@38
|
226 % Get method for stimulus
|
rmeddis@38
|
227 % -----------------------
|
rmeddis@38
|
228 % The hearing aid expects a stereo signal, as the MOC control is
|
rmeddis@38
|
229 % linked for left and right channels. It would be more efficient to
|
rmeddis@38
|
230 % use a mono version of the aid for simulation in Matlab. However,
|
rmeddis@38
|
231 % I always want to use the exact same code for the hardware in the
|
rmeddis@38
|
232 % lab and current simulations. This code will make a mono signal
|
rmeddis@38
|
233 % stereo if needs be and/or rotate to 2xN array.
|
rmeddis@38
|
234 %*************************************************************
|
rmeddis@38
|
235 function value = get.stimulusINTERNAL(obj)
|
rmeddis@38
|
236 [nRows, nCols] = size(obj.stimulusUSER);
|
rmeddis@38
|
237
|
rmeddis@38
|
238 % Assume that the stimulus duration is greater than 2 samples.
|
rmeddis@38
|
239 % Therefore the number of channels is the min dim.
|
rmeddis@38
|
240 [nChans, I] = min([nRows nCols]);
|
rmeddis@38
|
241
|
rmeddis@38
|
242 if nChans == 2
|
rmeddis@38
|
243 if I == 2
|
rmeddis@38
|
244 value = obj.stimulusUSER;
|
rmeddis@38
|
245 else
|
rmeddis@38
|
246 value = obj.stimulusUSER';
|
rmeddis@38
|
247 end
|
rmeddis@38
|
248 elseif nChans == 1 %Just to be explicit
|
rmeddis@38
|
249 if I == 2
|
rmeddis@38
|
250 value = [obj.stimulusUSER obj.stimulusUSER];
|
rmeddis@38
|
251 else
|
rmeddis@38
|
252 value = [obj.stimulusUSER; obj.stimulusUSER]';
|
rmeddis@38
|
253 end
|
rmeddis@38
|
254 end
|
rmeddis@38
|
255 end
|
rmeddis@38
|
256
|
rmeddis@38
|
257 %% ***********************************************************
|
rmeddis@38
|
258 % Get method for aid output
|
rmeddis@38
|
259 % -----------------------
|
rmeddis@38
|
260 % This get method is linked to the above internal stimulus method
|
rmeddis@38
|
261 % and allows the user to extract the hearing aid output in exactly
|
rmeddis@38
|
262 % the same shape and size as the original input stimulus. This is
|
rmeddis@38
|
263 % very useful for the speech recognition work and presumably
|
rmeddis@38
|
264 % for multithreshold also.
|
rmeddis@38
|
265 %*************************************************************
|
rmeddis@38
|
266 function value = get.aidOPnice(obj)
|
rmeddis@38
|
267 if ~isempty(obj.aidOP)
|
rmeddis@38
|
268 [nRows, nCols] = size(obj.stimulusUSER);
|
rmeddis@38
|
269
|
rmeddis@38
|
270 % Assume that the stimulus duration is greater than 2 samples.
|
rmeddis@38
|
271 % Therefore the number of channels is the min dim.
|
rmeddis@38
|
272 [nChans, I] = min([nRows nCols]);
|
rmeddis@38
|
273
|
rmeddis@38
|
274 %** The aid output will ALWAYS be a 2xN array **
|
rmeddis@38
|
275 %The fist job is to remove trailing zeros that may have been
|
rmeddis@38
|
276 %introduced by the framing process
|
rmeddis@38
|
277 aidOPtruncated = obj.aidOP(:, 1:max([nRows nCols]));
|
rmeddis@38
|
278
|
rmeddis@38
|
279 %The next task is to arrange the op like the ip
|
rmeddis@38
|
280 if nChans == 2
|
rmeddis@38
|
281 if I == 1
|
rmeddis@38
|
282 value = aidOPtruncated;
|
rmeddis@38
|
283 else
|
rmeddis@38
|
284 value = aidOPtruncated';
|
rmeddis@38
|
285 end
|
rmeddis@38
|
286 elseif nChans == 1 %Just to be explicit
|
rmeddis@38
|
287 if I == 1
|
rmeddis@38
|
288 value = aidOPtruncated(1,:);
|
rmeddis@38
|
289 else
|
rmeddis@38
|
290 value = aidOPtruncated(1,:)';
|
rmeddis@38
|
291 end
|
rmeddis@38
|
292 end
|
rmeddis@38
|
293 else % ---- of if isempty statement
|
rmeddis@38
|
294 value = [];
|
rmeddis@38
|
295 end
|
rmeddis@38
|
296 end
|
rmeddis@38
|
297
|
rmeddis@38
|
298 %% ***********************************************************
|
rmeddis@38
|
299 % *** Set methods ***
|
rmeddis@38
|
300 % -----------------------
|
rmeddis@38
|
301 % This is a bunch of unexciting error hunting functions. They also
|
rmeddis@38
|
302 % flush the aid output if any parameters change. Therefore,
|
rmeddis@38
|
303 % processStim will have to be called explicity by the user once
|
rmeddis@38
|
304 % again.
|
rmeddis@38
|
305 %*************************************************************
|
rmeddis@38
|
306 function obj = set.stimulusUSER(obj,value)
|
rmeddis@38
|
307 [nRows, nCols] = size(value);
|
rmeddis@38
|
308
|
rmeddis@38
|
309 % Assume that the stimulus duration is greater than 2 samples.
|
rmeddis@38
|
310 % Therefore the number of channels is the min dim.
|
rmeddis@38
|
311 nChans = min([nRows nCols]);
|
rmeddis@38
|
312 assert(nChans<3 && nChans, 'Number of stimulus channels must be 1 or 2')
|
rmeddis@38
|
313
|
rmeddis@38
|
314 obj = obj.flushAidData; %flush any previous hearing aid data if the input stimulus changes
|
rmeddis@38
|
315 obj.stimulusUSER = value;
|
rmeddis@38
|
316 end
|
rmeddis@38
|
317 function obj = set.sr(obj,value)
|
rmeddis@38
|
318 assert(value>=20e3 && value<=192e3, 'sr must be between 20 and 192 kHz')
|
rmeddis@38
|
319 obj = obj.flushAidData;
|
rmeddis@38
|
320 obj.sr = value;
|
rmeddis@38
|
321 end
|
rmeddis@38
|
322 function obj = set.numSamples(obj,value)
|
rmeddis@38
|
323 assert(value>=48 && value<=6912, 'must be between 48 and 6912 samples')
|
rmeddis@38
|
324 obj = obj.flushAidData;
|
rmeddis@38
|
325 obj.numSamples = value;
|
rmeddis@38
|
326 end
|
rmeddis@38
|
327 function obj = set.audiometry_dB(obj,value)
|
rmeddis@38
|
328 [nRows,nCols] = size(value);
|
rmeddis@38
|
329 assert(nRows==obj.numAudiometricFreqs && nCols==1, 'must be 6x1 column vector') %#ok<MCSUP>
|
rmeddis@38
|
330 obj = obj.flushAidData;
|
rmeddis@38
|
331 obj.audiometry_dB = value;
|
rmeddis@38
|
332 end
|
rmeddis@38
|
333 function obj = set.mainGain_dB(obj,value)
|
rmeddis@38
|
334 [nRows,nCols] = size(value);
|
rmeddis@38
|
335 assert(nRows==obj.numAudiometricFreqs && nCols==1, 'must be 6x1 column vector') %#ok<MCSUP>
|
rmeddis@38
|
336 obj = obj.flushAidData;
|
rmeddis@38
|
337 obj.mainGain_dB = value;
|
rmeddis@38
|
338 end
|
rmeddis@38
|
339 function obj = set.TC_dBHL(obj,value)
|
rmeddis@38
|
340 [nRows,nCols] = size(value);
|
rmeddis@38
|
341 assert(nRows==obj.numAudiometricFreqs && nCols==1, 'must be 6x1 column vector') %#ok<MCSUP>
|
rmeddis@38
|
342 obj = obj.flushAidData;
|
rmeddis@38
|
343 obj.TC_dBHL = value;
|
rmeddis@38
|
344 end
|
rmeddis@38
|
345 function obj = set.TM_dBHL(obj,value)
|
rmeddis@38
|
346 [nRows,nCols] = size(value);
|
rmeddis@38
|
347 assert(nRows==obj.numAudiometricFreqs && nCols==1, 'must be 6x1 column vector') %#ok<MCSUP>
|
rmeddis@38
|
348 obj = obj.flushAidData;
|
rmeddis@38
|
349 obj.TM_dBHL = value;
|
rmeddis@38
|
350 end
|
rmeddis@38
|
351 function obj = set.DRNLc(obj,value)
|
rmeddis@38
|
352 [nRows,nCols] = size(value);
|
rmeddis@38
|
353 assert(nRows==obj.numAudiometricFreqs && nCols==1, 'must be 6x1 column vector') %#ok<MCSUP>
|
rmeddis@38
|
354 assert(all(value)>=0 && all(value)<=1, 'all DRNLc values must be between 0 and 1')
|
rmeddis@38
|
355 obj = obj.flushAidData;
|
rmeddis@38
|
356 obj.DRNLc = value;
|
rmeddis@38
|
357 end
|
rmeddis@38
|
358 function obj = set.ARtau(obj,value)
|
rmeddis@38
|
359 assert(value>=1e-3 && value<=1, 'must be between 1e-3 and 1s')
|
rmeddis@38
|
360 obj = obj.flushAidData;
|
rmeddis@38
|
361 obj.ARtau = value;
|
rmeddis@38
|
362 end
|
rmeddis@38
|
363 function obj = set.ARthreshold_dB(obj,value)
|
rmeddis@38
|
364 assert(value>0, 'set AR to a high value to disable it')
|
rmeddis@38
|
365 obj = obj.flushAidData;
|
rmeddis@38
|
366 obj.ARthreshold_dB = value;
|
rmeddis@38
|
367 end
|
rmeddis@38
|
368 function obj = set.MOCtau(obj,value)
|
rmeddis@38
|
369 assert(value>=1e-3 && value<=2, 'must be between 1e-3 and 2s')
|
rmeddis@38
|
370 obj = obj.flushAidData;
|
rmeddis@38
|
371 obj.MOCtau = value;
|
rmeddis@38
|
372 end
|
rmeddis@38
|
373 function obj = set.MOCfactor(obj,value)
|
rmeddis@38
|
374 assert(value>=0 && value<=1, 'must be between 0 and 1')
|
rmeddis@38
|
375 obj = obj.flushAidData;
|
rmeddis@38
|
376 obj.MOCfactor = value;
|
rmeddis@38
|
377 end
|
rmeddis@38
|
378 function obj = set.bwOct(obj,value)
|
rmeddis@38
|
379 assert(value==1/1 || value==1/2 || value==1/3 || value==1/4 || value==1/5, 'must be one of 1./(1:5)')
|
rmeddis@38
|
380 obj = obj.flushAidData;
|
rmeddis@38
|
381 obj.bwOct = value;
|
rmeddis@38
|
382 end
|
rmeddis@38
|
383 function obj = set.filterOrder(obj,value)
|
rmeddis@38
|
384 assert(value>0 && value<5, 'must be one of 1:4')
|
rmeddis@38
|
385 obj = obj.flushAidData;
|
rmeddis@38
|
386 obj.filterOrder = value;
|
rmeddis@38
|
387 end
|
rmeddis@38
|
388 function obj = set.useGTF(obj,value)
|
rmeddis@38
|
389 obj = obj.flushAidData;
|
rmeddis@38
|
390 obj.useGTF = value;
|
rmeddis@38
|
391 end
|
rmeddis@38
|
392
|
rmeddis@38
|
393 %% **********************************************************
|
rmeddis@38
|
394 % flushAidData
|
rmeddis@38
|
395 % This second function is a workaround allowing a set method to
|
rmeddis@38
|
396 % change another property value.
|
rmeddis@38
|
397 %************************************************************
|
rmeddis@38
|
398 function obj = flushAidData(obj)
|
rmeddis@38
|
399 obj.aidOP = [];
|
rmeddis@38
|
400 obj.MOCrecord = [];
|
rmeddis@38
|
401 end
|
rmeddis@38
|
402
|
rmeddis@38
|
403
|
rmeddis@38
|
404 %% **********************************************************
|
rmeddis@38
|
405 % OVERLOADED plot method
|
rmeddis@38
|
406 %************************************************************
|
rmeddis@38
|
407 function plot(obj)
|
rmeddis@38
|
408 clf
|
rmeddis@38
|
409 sig2dBSPL = @(sig)20*log10(abs(sig/20e-6)+(1/(2^32)));
|
rmeddis@38
|
410 dt = 1/obj.sr;
|
rmeddis@38
|
411 tAxis = dt:dt:dt*size(obj.stimulusINTERNAL,1);
|
rmeddis@38
|
412
|
rmeddis@38
|
413 subplot(2,1,1)
|
rmeddis@38
|
414 plot(tAxis(1:length(obj.stimulusUSER)), sig2dBSPL(obj.stimulusUSER), 'k')
|
rmeddis@38
|
415 if ~isempty(obj.aidOPnice)
|
rmeddis@38
|
416 hold on
|
rmeddis@38
|
417 plot(tAxis(1:length(obj.stimulusUSER)), sig2dBSPL(obj.aidOPnice), 'r')
|
rmeddis@38
|
418 end
|
rmeddis@38
|
419 ylim([0 100])
|
rmeddis@38
|
420 xlim([0 tAxis(length(obj.stimulusUSER))])
|
rmeddis@38
|
421 title('Level response')
|
rmeddis@38
|
422 xlabel('Time in seconds')
|
rmeddis@38
|
423 ylabel('Level in dB SPL')
|
rmeddis@38
|
424
|
rmeddis@38
|
425 subplot(2,1,2)
|
rmeddis@38
|
426 if ~isempty(obj.MOCrecord)
|
rmeddis@38
|
427 imagesc(tAxis, 1:obj.numChannels, flipud(-20*log10(obj.MOCrecord)))
|
rmeddis@38
|
428 colorbar
|
rmeddis@38
|
429 end
|
rmeddis@38
|
430 title('MOC attenuation')
|
rmeddis@38
|
431 xlabel('Time in seconds')
|
rmeddis@38
|
432 ylabel('Band frequency in Hz')
|
rmeddis@38
|
433 numSpacers = 1 + (obj.numChannels-numel(obj.DRNLc)) / (numel(obj.DRNLc)-1);
|
rmeddis@38
|
434 set(gca, 'YTick', 1:numSpacers:obj.numChannels);
|
rmeddis@38
|
435 set(gca, 'YTickLabel', num2str(flipud([250; 500; 1000; 2000; 4000; 8000])));
|
rmeddis@38
|
436 end% ------ OVERLOADED plot method
|
rmeddis@38
|
437
|
rmeddis@38
|
438 %% **********************************************************
|
rmeddis@38
|
439 % OVERLOADED soundsc method
|
rmeddis@38
|
440 %************************************************************
|
rmeddis@38
|
441 function soundsc(obj)
|
rmeddis@38
|
442 soundsc(obj.aidOPnice, obj.sr)
|
rmeddis@38
|
443 end
|
rmeddis@38
|
444
|
rmeddis@38
|
445 %% **********************************************************
|
rmeddis@38
|
446 % processStim
|
rmeddis@38
|
447 %************************************************************
|
rmeddis@38
|
448 function obj = processStim(obj)
|
rmeddis@38
|
449 %--------------------------------------------------------------
|
rmeddis@38
|
450 % EMULATION OF THE GUI PARAMETER CONVERSIONS
|
rmeddis@38
|
451 %--------------------------------------------------------------
|
rmeddis@38
|
452 biggestNumSamples = obj.numSamples;
|
rmeddis@38
|
453
|
rmeddis@38
|
454 filterStatesL = (zeros(3000,1));
|
rmeddis@38
|
455 filterStatesR = filterStatesL;
|
rmeddis@38
|
456 filterCoeffs = (zeros(5000,1));
|
rmeddis@38
|
457
|
rmeddis@38
|
458 %filter coefficients
|
rmeddis@38
|
459 ARcutOff=1/(2*pi*obj.ARtau);
|
rmeddis@38
|
460 [b,a] = butter(1,ARcutOff/(obj.sr/2));
|
rmeddis@38
|
461 filterCoeffs(obj.enumC_ARb+1:obj.enumC_ARb+2) = b;
|
rmeddis@38
|
462 filterCoeffs(obj.enumC_ARa+1:obj.enumC_ARa+2) = a;
|
rmeddis@38
|
463
|
rmeddis@38
|
464 MOCcutOff=1/(2*pi*obj.MOCtau);
|
rmeddis@38
|
465 [bMOC,aMOC] = butter(1,MOCcutOff/(obj.sr/2));
|
rmeddis@38
|
466 filterCoeffs(obj.enumC_MOCb+1:obj.enumC_MOCb+2) = bMOC;
|
rmeddis@38
|
467 filterCoeffs(obj.enumC_MOCa+1:obj.enumC_MOCa+2) = aMOC;
|
rmeddis@38
|
468
|
rmeddis@38
|
469
|
rmeddis@38
|
470 for filterCount = 1:obj.numChannels
|
rmeddis@38
|
471 %-----------------------------------
|
rmeddis@38
|
472 % nonlinear path - filter bws
|
rmeddis@38
|
473 %-----------------------------------
|
rmeddis@38
|
474 lowerCutOff=obj.channelBFs(filterCount)*2^(-obj.bwOct_INTERP(filterCount)/2);
|
rmeddis@38
|
475 upperCutOff=obj.channelBFs(filterCount)*2^( obj.bwOct_INTERP(filterCount)/2);
|
rmeddis@38
|
476
|
rmeddis@38
|
477 if obj.useGTF
|
rmeddis@38
|
478 bwHz = upperCutOff - lowerCutOff;
|
rmeddis@38
|
479 [b_DRNL,a_DRNL] = obj.gammatone(bwHz, obj.channelBFs(filterCount), 1/obj.sr);
|
rmeddis@38
|
480 filterCoeffs(10*(filterCount-1)+9 :10*(filterCount-1)+10) = b_DRNL;
|
rmeddis@38
|
481 filterCoeffs(10*(filterCount-1)+14:10*(filterCount-1)+16) = a_DRNL;
|
rmeddis@38
|
482 else
|
rmeddis@38
|
483 [b_DRNL,a_DRNL] = butter(2,[lowerCutOff upperCutOff]/(obj.sr/2));
|
rmeddis@38
|
484 filterCoeffs(10*(filterCount-1)+9 :10*(filterCount-1)+13) = b_DRNL;
|
rmeddis@38
|
485 filterCoeffs(10*(filterCount-1)+14:10*(filterCount-1)+18) = a_DRNL;
|
rmeddis@38
|
486 end
|
rmeddis@38
|
487 end
|
rmeddis@38
|
488
|
rmeddis@38
|
489 %--------------------------------------------------------------
|
rmeddis@38
|
490 % EMULATION OF THE IO CALLBACK THREAD
|
rmeddis@38
|
491 %--------------------------------------------------------------
|
rmeddis@38
|
492 frameBufferL = buffer(obj.stimulusINTERNAL(:,1), obj.numSamples);
|
rmeddis@38
|
493 frameBufferR = buffer(obj.stimulusINTERNAL(:,2), obj.numSamples);
|
rmeddis@38
|
494 nFrames = size(frameBufferL,2);
|
rmeddis@38
|
495
|
rmeddis@38
|
496 pad = zeros(1,biggestNumSamples-obj.numSamples);
|
rmeddis@38
|
497 ARampL=ones(1,biggestNumSamples);
|
rmeddis@38
|
498 ARampR = ARampL;
|
rmeddis@38
|
499 MOCcontrol = ones(obj.numChannels, biggestNumSamples);
|
rmeddis@38
|
500
|
rmeddis@38
|
501 peakIPL = zeros(5,1);
|
rmeddis@38
|
502 peakOPL = peakIPL;
|
rmeddis@38
|
503 rmsIPL = peakIPL;
|
rmeddis@38
|
504 rmsOPL = peakIPL;
|
rmeddis@38
|
505
|
rmeddis@38
|
506 peakIPR = peakIPL;
|
rmeddis@38
|
507 peakOPR = peakIPL;
|
rmeddis@38
|
508 rmsIPR = peakIPL;
|
rmeddis@38
|
509 rmsOPR = peakIPL;
|
rmeddis@38
|
510
|
rmeddis@38
|
511 MOCend = zeros(obj.numChannels,1);
|
rmeddis@38
|
512
|
rmeddis@38
|
513 op = [];
|
rmeddis@38
|
514 moc= [];
|
rmeddis@38
|
515 for nn = 1:nFrames
|
rmeddis@38
|
516 frameBufferPadL = [frameBufferL(:,nn)' pad];
|
rmeddis@38
|
517 frameBufferPadR = [frameBufferR(:,nn)' pad];
|
rmeddis@38
|
518
|
rmeddis@38
|
519 [ outBufferL, outBufferR, filterStatesL, filterStatesR, ARampL, ARampR, MOCend, peakIPL, peakOPL, rmsIPL, rmsOPL, peakIPR, peakOPR, rmsIPR, rmsOPR, MOCcontrol ] =...
|
rmeddis@38
|
520 EssexAidProcessVFrameSwitchable( ...
|
rmeddis@38
|
521 frameBufferPadL,...
|
rmeddis@38
|
522 frameBufferPadR,...
|
rmeddis@38
|
523 filterStatesL,...
|
rmeddis@38
|
524 filterStatesR,...
|
rmeddis@38
|
525 filterCoeffs,...
|
rmeddis@38
|
526 obj.numChannels,...
|
rmeddis@38
|
527 obj.numSamples,...
|
rmeddis@38
|
528 ARampL,...
|
rmeddis@38
|
529 ARampR,...
|
rmeddis@38
|
530 obj.ARthresholdPa,...
|
rmeddis@38
|
531 obj.filterOrder,...
|
rmeddis@38
|
532 obj.DRNLb_INTERP,...
|
rmeddis@38
|
533 obj.DRNLc_INTERP,...
|
rmeddis@38
|
534 obj.TM_dBO_INTERP,...
|
rmeddis@38
|
535 obj.MOCfactor,...
|
rmeddis@38
|
536 peakIPL,...
|
rmeddis@38
|
537 peakOPL,...
|
rmeddis@38
|
538 rmsIPL,...
|
rmeddis@38
|
539 rmsOPL,...
|
rmeddis@38
|
540 peakIPR,...
|
rmeddis@38
|
541 peakOPR,...
|
rmeddis@38
|
542 rmsIPR,...
|
rmeddis@38
|
543 rmsOPR,...
|
rmeddis@38
|
544 MOCend,...
|
rmeddis@38
|
545 MOCcontrol,...
|
rmeddis@38
|
546 obj.mainGain_INTERP,...
|
rmeddis@38
|
547 obj.useGTF);
|
rmeddis@38
|
548
|
rmeddis@38
|
549
|
rmeddis@38
|
550 outBuffer = ( [outBufferL(:, 1:obj.numSamples); outBufferR(:, 1:obj.numSamples)] );
|
rmeddis@38
|
551 op = [op outBuffer]; %#ok<AGROW>
|
rmeddis@38
|
552 moc= [moc MOCcontrol]; %#ok<AGROW>
|
rmeddis@38
|
553
|
rmeddis@38
|
554 end %End of frame processing emulation loop
|
rmeddis@38
|
555 obj.aidOP = op;
|
rmeddis@38
|
556 obj.MOCrecord=moc;
|
rmeddis@38
|
557
|
rmeddis@38
|
558
|
rmeddis@38
|
559 end %End of process stim method
|
rmeddis@38
|
560
|
rmeddis@38
|
561 end %End of methods block
|
rmeddis@38
|
562
|
rmeddis@38
|
563 %% *********************************************************
|
rmeddis@38
|
564 % _ _ _ _ _ _
|
rmeddis@38
|
565 % | | | | (_) | | | | | |
|
rmeddis@38
|
566 % ___| |_ __ _| |_ _ ___ _ __ ___ ___| |_| |__ ___ __| |___
|
rmeddis@38
|
567 % / __| __/ _` | __| |/ __| | '_ ` _ \ / _ \ __| '_ \ / _ \ / _` / __|
|
rmeddis@38
|
568 % \__ \ || (_| | |_| | (__ | | | | | | __/ |_| | | | (_) | (_| \__ \
|
rmeddis@38
|
569 % |___/\__\__,_|\__|_|\___| |_| |_| |_|\___|\__|_| |_|\___/ \__,_|___/
|
rmeddis@38
|
570 %************************************************************
|
rmeddis@38
|
571
|
rmeddis@38
|
572 methods(Static)
|
rmeddis@38
|
573 %% ********************************************************
|
rmeddis@38
|
574 % pipOut - sequence of tone pips at various levels
|
rmeddis@38
|
575 %**********************************************************
|
rmeddis@38
|
576 function pipOut = pipSequence(sampleRate, freq, dBlevs, pulseDur, silDur)
|
rmeddis@38
|
577 if nargin < 5
|
rmeddis@38
|
578 silDur = 0.3;
|
rmeddis@38
|
579 end
|
rmeddis@38
|
580 if nargin < 4
|
rmeddis@38
|
581 pulseDur = 0.1;
|
rmeddis@38
|
582 end
|
rmeddis@38
|
583 if nargin < 3
|
rmeddis@38
|
584 dBlevs = 20:20:100;
|
rmeddis@38
|
585 end
|
rmeddis@38
|
586 if nargin < 2
|
rmeddis@38
|
587 freq = 500;
|
rmeddis@38
|
588 end
|
rmeddis@38
|
589 if nargin < 1
|
rmeddis@38
|
590 sampleRate = 48e3;
|
rmeddis@38
|
591 end
|
rmeddis@38
|
592
|
rmeddis@38
|
593 dt = 1/sampleRate;
|
rmeddis@38
|
594 tAxis = dt:dt:pulseDur;
|
rmeddis@38
|
595 sPulse = sin(2*pi*freq*tAxis);
|
rmeddis@38
|
596 sPulse = sPulse./sqrt(mean(sPulse.^2));
|
rmeddis@38
|
597 rms2dBspl = @(dBspl)20e-6*10^(dBspl/20); %sneaky short-hand function by (ab)using function handles
|
rmeddis@38
|
598 zPad = zeros(1,ceil(sampleRate*silDur));
|
rmeddis@38
|
599
|
rmeddis@38
|
600 pipOut = [];
|
rmeddis@38
|
601 for nn = 1:numel(dBlevs)
|
rmeddis@38
|
602 pipOut = [ pipOut sPulse*rms2dBspl(dBlevs(nn)) zPad]; %#ok<AGROW>
|
rmeddis@38
|
603 end
|
rmeddis@38
|
604
|
rmeddis@38
|
605 end% ------ OF pipSequence
|
rmeddis@38
|
606
|
rmeddis@38
|
607 %% ********************************************************
|
rmeddis@38
|
608 % interpPars - Linear interpolation of given parameter to mimic GUI
|
rmeddis@38
|
609 % fitting functionality.
|
rmeddis@38
|
610 %**********************************************************
|
rmeddis@38
|
611 function fullArray = interpPars(shortArray, numBands)
|
rmeddis@38
|
612 nGUIbands = numel(shortArray);
|
rmeddis@38
|
613 if numBands == nGUIbands
|
rmeddis@38
|
614 fullArray = shortArray;
|
rmeddis@38
|
615 else
|
rmeddis@38
|
616 numSpacers = (numBands-nGUIbands) / (nGUIbands-1);
|
rmeddis@38
|
617 fullArray = shortArray(1);
|
rmeddis@38
|
618 for nn = 2:nGUIbands
|
rmeddis@38
|
619 fullArray = [fullArray,...
|
rmeddis@38
|
620 repmat(mean([shortArray(nn) shortArray(nn-1)]),1,numSpacers),...
|
rmeddis@38
|
621 shortArray(nn)]; %#ok<AGROW>
|
rmeddis@38
|
622 end
|
rmeddis@38
|
623 end
|
rmeddis@38
|
624 end% ----- OF interpPars
|
rmeddis@38
|
625
|
rmeddis@38
|
626 %% ********************************************************
|
rmeddis@38
|
627 % gammatone - get filter coefficients
|
rmeddis@38
|
628 %**********************************************************
|
rmeddis@38
|
629 function [b,a] = gammatone(bw, cf, dt)
|
rmeddis@38
|
630 phi = 2 * pi * bw * dt;
|
rmeddis@38
|
631 theta = 2 * pi * cf * dt;
|
rmeddis@38
|
632 cos_theta = cos(theta);
|
rmeddis@38
|
633 sin_theta = sin(theta);
|
rmeddis@38
|
634 alpha = -exp(-phi) * cos_theta;
|
rmeddis@38
|
635 b0 = 1.0;
|
rmeddis@38
|
636 b1 = 2 * alpha;
|
rmeddis@38
|
637 b2 = exp(-2 * phi);
|
rmeddis@38
|
638 z1 = (1 + alpha * cos_theta) - (alpha * sin_theta) * 1i;
|
rmeddis@38
|
639 z2 = (1 + b1 * cos_theta) - (b1 * sin_theta) * 1i;
|
rmeddis@38
|
640 z3 = (b2 * cos(2 * theta)) - (b2 * sin(2 * theta)) * 1i;
|
rmeddis@38
|
641 tf = (z2 + z3) / z1;
|
rmeddis@38
|
642 a0 = abs(tf);
|
rmeddis@38
|
643 a1 = alpha * a0;
|
rmeddis@38
|
644
|
rmeddis@38
|
645 a = [b0, b1, b2];
|
rmeddis@38
|
646 b = [a0, a1];
|
rmeddis@38
|
647 end% ------ OF gammatone
|
rmeddis@38
|
648 end% ------ OF static methods
|
rmeddis@38
|
649
|
rmeddis@38
|
650 end %End of classdef
|
rmeddis@38
|
651
|