annotate matlab/bmm/carfac/CARFAC_Design.m @ 648:1c2a5868f23a

Fix memory leak in CARFAC. Also get rid of most uses of auto, which tend to hurt readability unless the type name is particularly long, especially when it masks pointers.
author ronw@google.com
date Tue, 11 Jun 2013 21:41:53 +0000
parents 7c82250c8dd2
children
rev   line source
tom@513 1 % Copyright 2012 Google Inc. All Rights Reserved.
tom@455 2 % Author: Richard F. Lyon
tom@455 3 %
tom@455 4 % This Matlab file is part of an implementation of Lyon's cochlear model:
tom@455 5 % "Cascade of Asymmetric Resonators with Fast-Acting Compression"
tom@455 6 % to supplement Lyon's upcoming book "Human and Machine Hearing"
tom@455 7 %
tom@455 8 % Licensed under the Apache License, Version 2.0 (the "License");
tom@455 9 % you may not use this file except in compliance with the License.
tom@455 10 % You may obtain a copy of the License at
tom@455 11 %
tom@455 12 % http://www.apache.org/licenses/LICENSE-2.0
tom@455 13 %
tom@455 14 % Unless required by applicable law or agreed to in writing, software
tom@455 15 % distributed under the License is distributed on an "AS IS" BASIS,
tom@455 16 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
tom@455 17 % See the License for the specific language governing permissions and
tom@455 18 % limitations under the License.
tom@455 19
dicklyon@623 20 function CF = CARFAC_Design(n_ears, fs, ...
dicklyon@623 21 CF_CAR_params, CF_AGC_params, CF_IHC_params)
dicklyon@623 22 % function CF = CARFAC_Design(n_ears, fs, ...
dicklyon@623 23 % CF_CAR_params, CF_AGC_params, CF_IHC_params)
tom@455 24 %
tom@455 25 % This function designs the CARFAC (Cascade of Asymmetric Resonators with
tom@455 26 % Fast-Acting Compression); that is, it take bundles of parameters and
tom@455 27 % computes all the filter coefficients needed to run it.
tom@455 28 %
tom@455 29 % fs is sample rate (per second)
dicklyon@473 30 % CF_CAR_params bundles all the pole-zero filter cascade parameters
tom@455 31 % CF_AGC_params bundles all the automatic gain control parameters
tom@455 32 % CF_IHC_params bundles all the inner hair cell parameters
tom@455 33 %
tom@455 34 % See other functions for designing and characterizing the CARFAC:
tom@455 35 % [naps, CF] = CARFAC_Run(CF, input_waves)
tom@455 36 % transfns = CARFAC_Transfer_Functions(CF, to_channels, from_channels)
tom@455 37 %
tom@455 38 % Defaults to Glasberg & Moore's ERB curve:
tom@455 39 % ERB_break_freq = 1000/4.37; % 228.833
tom@455 40 % ERB_Q = 1000/(24.7*4.37); % 9.2645
tom@455 41 %
tom@455 42 % All args are defaultable; for sample/default args see the code; they
tom@455 43 % make 96 channels at default fs = 22050, 114 channels at 44100.
tom@455 44
dicklyon@500 45 if nargin < 1
dicklyon@500 46 n_ears = 1; % if more than 1, make them identical channels;
dicklyon@500 47 % then modify the design if necessary for different reasons
dicklyon@500 48 end
dicklyon@500 49
dicklyon@500 50 if nargin < 2
dicklyon@500 51 fs = 22050;
dicklyon@500 52 end
dicklyon@500 53
dicklyon@500 54 if nargin < 3
dicklyon@500 55 CF_CAR_params = struct( ...
dicklyon@506 56 'velocity_scale', 0.1, ... % for the velocity nonlinearity
dicklyon@502 57 'v_offset', 0.04, ... % offset gives a quadratic part
dicklyon@500 58 'min_zeta', 0.10, ... % minimum damping factor in mid-freq channels
dicklyon@504 59 'max_zeta', 0.35, ... % maximum damping factor in mid-freq channels
dicklyon@500 60 'first_pole_theta', 0.85*pi, ...
dicklyon@500 61 'zero_ratio', sqrt(2), ... % how far zero is above pole
dicklyon@500 62 'high_f_damping_compression', 0.5, ... % 0 to 1 to compress zeta
dicklyon@500 63 'ERB_per_step', 0.5, ... % assume G&M's ERB formula
dicklyon@500 64 'min_pole_Hz', 30, ...
dicklyon@500 65 'ERB_break_freq', 165.3, ... % Greenwood map's break freq.
dicklyon@500 66 'ERB_Q', 1000/(24.7*4.37)); % Glasberg and Moore's high-cf ratio
dicklyon@500 67 end
dicklyon@500 68
dicklyon@492 69 if nargin < 4
dicklyon@500 70 CF_AGC_params = struct( ...
dicklyon@500 71 'n_stages', 4, ...
dicklyon@627 72 'time_constants', 0.002 * 4.^(0:3), ...
dicklyon@500 73 'AGC_stage_gain', 2, ... % gain from each stage to next slower stage
dicklyon@504 74 'decimation', [8, 2, 2, 2], ... % how often to update the AGC states
dicklyon@627 75 'AGC1_scales', 1.0 * sqrt(2).^(0:3), ... % in units of channels
dicklyon@627 76 'AGC2_scales', 1.65 * sqrt(2).^(0:3), ... % spread more toward base
dicklyon@500 77 'AGC_mix_coeff', 0.5);
dicklyon@500 78 end
dicklyon@500 79
dicklyon@500 80 if nargin < 5
tom@455 81 % HACK: these constant control the defaults
dicklyon@606 82 one_cap = 1; % bool; 1 for Allen model, as text states we use
tom@455 83 just_hwr = 0; % book; 0 for normal/fancy IHC; 1 for HWR
tom@455 84 if just_hwr
dicklyon@504 85 CF_IHC_params = struct('just_hwr', 1, ... % just a simple HWR
dicklyon@504 86 'ac_corner_Hz', 20);
tom@455 87 else
tom@455 88 if one_cap
tom@455 89 CF_IHC_params = struct( ...
dicklyon@462 90 'just_hwr', just_hwr, ... % not just a simple HWR
tom@455 91 'one_cap', one_cap, ... % bool; 0 for new two-cap hack
tom@455 92 'tau_lpf', 0.000080, ... % 80 microseconds smoothing twice
tom@455 93 'tau_out', 0.0005, ... % depletion tau is pretty fast
dicklyon@504 94 'tau_in', 0.010, ... % recovery tau is slower
dicklyon@504 95 'ac_corner_Hz', 20);
tom@455 96 else
tom@455 97 CF_IHC_params = struct( ...
dicklyon@462 98 'just_hwr', just_hwr, ... % not just a simple HWR
tom@455 99 'one_cap', one_cap, ... % bool; 0 for new two-cap hack
tom@455 100 'tau_lpf', 0.000080, ... % 80 microseconds smoothing twice
dicklyon@495 101 'tau1_out', 0.010, ... % depletion tau is pretty fast
tom@455 102 'tau1_in', 0.020, ... % recovery tau is slower
dicklyon@495 103 'tau2_out', 0.0025, ... % depletion tau is pretty fast
dicklyon@504 104 'tau2_in', 0.005, ... % recovery tau is slower
dicklyon@504 105 'ac_corner_Hz', 20);
tom@455 106 end
tom@455 107 end
tom@455 108 end
tom@455 109
tom@455 110
tom@455 111
tom@455 112 % first figure out how many filter stages (PZFC/CARFAC channels):
dicklyon@473 113 pole_Hz = CF_CAR_params.first_pole_theta * fs / (2*pi);
tom@455 114 n_ch = 0;
dicklyon@473 115 while pole_Hz > CF_CAR_params.min_pole_Hz
tom@455 116 n_ch = n_ch + 1;
dicklyon@473 117 pole_Hz = pole_Hz - CF_CAR_params.ERB_per_step * ...
dicklyon@492 118 ERB_Hz(pole_Hz, CF_CAR_params.ERB_break_freq, CF_CAR_params.ERB_Q);
tom@455 119 end
tom@455 120 % Now we have n_ch, the number of channels, so can make the array
tom@455 121 % and compute all the frequencies again to put into it:
tom@455 122 pole_freqs = zeros(n_ch, 1);
dicklyon@473 123 pole_Hz = CF_CAR_params.first_pole_theta * fs / (2*pi);
tom@455 124 for ch = 1:n_ch
tom@455 125 pole_freqs(ch) = pole_Hz;
dicklyon@473 126 pole_Hz = pole_Hz - CF_CAR_params.ERB_per_step * ...
dicklyon@492 127 ERB_Hz(pole_Hz, CF_CAR_params.ERB_break_freq, CF_CAR_params.ERB_Q);
tom@455 128 end
dicklyon@623 129 % Now we have n_ch, the number of channels, and pole_freqs array.
tom@455 130
dicklyon@467 131 max_channels_per_octave = log(2) / log(pole_freqs(1)/pole_freqs(2));
dicklyon@467 132
dicklyon@623 133 % Convert to include an ear_array, each w coeffs and state...
dicklyon@500 134 CAR_coeffs = CARFAC_DesignFilters(CF_CAR_params, fs, pole_freqs);
dicklyon@500 135 AGC_coeffs = CARFAC_DesignAGC(CF_AGC_params, fs, n_ch);
dicklyon@500 136 IHC_coeffs = CARFAC_DesignIHC(CF_IHC_params, fs, n_ch);
dicklyon@623 137
dicklyon@623 138 % Copy same designed coeffs into each ear (can do differently in the
dicklyon@623 139 % future).
dicklyon@500 140 for ear = 1:n_ears
dicklyon@500 141 ears(ear).CAR_coeffs = CAR_coeffs;
dicklyon@500 142 ears(ear).AGC_coeffs = AGC_coeffs;
dicklyon@500 143 ears(ear).IHC_coeffs = IHC_coeffs;
dicklyon@500 144 end
dicklyon@500 145
tom@455 146 CF = struct( ...
tom@455 147 'fs', fs, ...
dicklyon@467 148 'max_channels_per_octave', max_channels_per_octave, ...
dicklyon@473 149 'CAR_params', CF_CAR_params, ...
tom@455 150 'AGC_params', CF_AGC_params, ...
tom@455 151 'IHC_params', CF_IHC_params, ...
tom@455 152 'n_ch', n_ch, ...
tom@455 153 'pole_freqs', pole_freqs, ...
dicklyon@500 154 'ears', ears, ...
dicklyon@500 155 'n_ears', n_ears );
tom@455 156
tom@455 157
dicklyon@473 158
tom@455 159 %% Design the filter coeffs:
dicklyon@473 160 function CAR_coeffs = CARFAC_DesignFilters(CAR_params, fs, pole_freqs)
tom@455 161
tom@455 162 n_ch = length(pole_freqs);
tom@455 163
tom@455 164 % the filter design coeffs:
dicklyon@506 165 % scalars first:
dicklyon@473 166 CAR_coeffs = struct( ...
dicklyon@473 167 'n_ch', n_ch, ...
dicklyon@473 168 'velocity_scale', CAR_params.velocity_scale, ...
dicklyon@506 169 'v_offset', CAR_params.v_offset ...
dicklyon@462 170 );
tom@455 171
dicklyon@498 172 % don't really need these zero arrays, but it's a clue to what fields
dicklyon@498 173 % and types are need in ohter language implementations:
dicklyon@473 174 CAR_coeffs.r1_coeffs = zeros(n_ch, 1);
dicklyon@473 175 CAR_coeffs.a0_coeffs = zeros(n_ch, 1);
dicklyon@473 176 CAR_coeffs.c0_coeffs = zeros(n_ch, 1);
dicklyon@473 177 CAR_coeffs.h_coeffs = zeros(n_ch, 1);
dicklyon@473 178 CAR_coeffs.g0_coeffs = zeros(n_ch, 1);
tom@455 179
tom@455 180 % zero_ratio comes in via h. In book's circuit D, zero_ratio is 1/sqrt(a),
tom@455 181 % and that a is here 1 / (1+f) where h = f*c.
tom@455 182 % solve for f: 1/zero_ratio^2 = 1 / (1+f)
tom@455 183 % zero_ratio^2 = 1+f => f = zero_ratio^2 - 1
dicklyon@473 184 f = CAR_params.zero_ratio^2 - 1; % nominally 1 for half-octave
tom@455 185
tom@455 186 % Make pole positions, s and c coeffs, h and g coeffs, etc.,
tom@455 187 % which mostly depend on the pole angle theta:
tom@455 188 theta = pole_freqs .* (2 * pi / fs);
tom@455 189
dicklyon@469 190 c0 = sin(theta);
dicklyon@469 191 a0 = cos(theta);
dicklyon@469 192
tom@455 193 % different possible interpretations for min-damping r:
dicklyon@473 194 % r = exp(-theta * CF_CAR_params.min_zeta).
dicklyon@469 195 % Compress theta to give somewhat higher Q at highest thetas:
dicklyon@473 196 ff = CAR_params.high_f_damping_compression; % 0 to 1; typ. 0.5
dicklyon@469 197 x = theta/pi;
dicklyon@504 198
dicklyon@469 199 zr_coeffs = pi * (x - ff * x.^3); % when ff is 0, this is just theta,
dicklyon@469 200 % and when ff is 1 it goes to zero at theta = pi.
dicklyon@504 201 max_zeta = CAR_params.max_zeta;
dicklyon@504 202 CAR_coeffs.r1_coeffs = (1 - zr_coeffs .* max_zeta); % "r1" for the max-damping condition
dicklyon@469 203
dicklyon@473 204 min_zeta = CAR_params.min_zeta;
dicklyon@504 205 % Increase the min damping where channels are spaced out more, by pulling
dicklyon@504 206 % 25% of the way toward ERB_Hz/pole_freqs (close to 0.1 at high f)
dicklyon@504 207 min_zetas = min_zeta + 0.25*(ERB_Hz(pole_freqs, ...
dicklyon@492 208 CAR_params.ERB_break_freq, CAR_params.ERB_Q) ./ pole_freqs - min_zeta);
dicklyon@504 209 CAR_coeffs.zr_coeffs = zr_coeffs .* ...
dicklyon@504 210 (max_zeta - min_zetas); % how r relates to undamping
tom@455 211
tom@455 212 % undamped coupled-form coefficients:
dicklyon@473 213 CAR_coeffs.a0_coeffs = a0;
dicklyon@473 214 CAR_coeffs.c0_coeffs = c0;
tom@455 215
tom@455 216 % the zeros follow via the h_coeffs
dicklyon@469 217 h = c0 .* f;
dicklyon@473 218 CAR_coeffs.h_coeffs = h;
tom@455 219
dicklyon@469 220 % for unity gain at min damping, radius r; only used in CARFAC_Init:
dicklyon@504 221 relative_undamping = ones(n_ch, 1); % max undamping to start
dicklyon@473 222 % this function needs to take CAR_coeffs even if we haven't finished
dicklyon@469 223 % constucting it by putting in the g0_coeffs:
dicklyon@504 224 CAR_coeffs.g0_coeffs = CARFAC_Stage_g(CAR_coeffs, relative_undamping);
tom@455 225
tom@455 226
tom@455 227 %% the AGC design coeffs:
dicklyon@473 228 function AGC_coeffs = CARFAC_DesignAGC(AGC_params, fs, n_ch)
tom@455 229
dicklyon@473 230 n_AGC_stages = AGC_params.n_stages;
tom@455 231
tom@455 232 % AGC1 pass is smoothing from base toward apex;
dicklyon@498 233 % AGC2 pass is back, which is done first now (in double exp. version)
tom@455 234 AGC1_scales = AGC_params.AGC1_scales;
tom@455 235 AGC2_scales = AGC_params.AGC2_scales;
tom@455 236
dicklyon@462 237 decim = 1;
dicklyon@462 238
dicklyon@462 239 total_DC_gain = 0;
dicklyon@623 240
dicklyon@623 241 %%
dicklyon@623 242 % Convert to vector of AGC coeffs
dicklyon@623 243 AGC_coeffs = struct([]);
tom@455 244 for stage = 1:n_AGC_stages
dicklyon@623 245 AGC_coeffs(stage).n_ch = n_ch;
dicklyon@623 246 AGC_coeffs(stage).n_AGC_stages = n_AGC_stages;
dicklyon@623 247 AGC_coeffs(stage).AGC_stage_gain = AGC_params.AGC_stage_gain;
dicklyon@623 248
dicklyon@623 249 AGC_coeffs(stage).decimation = AGC_params.decimation(stage);
dicklyon@464 250 tau = AGC_params.time_constants(stage); % time constant in seconds
dicklyon@464 251 decim = decim * AGC_params.decimation(stage); % net decim to this stage
tom@455 252 % epsilon is how much new input to take at each update step:
dicklyon@623 253 AGC_coeffs(stage).AGC_epsilon = 1 - exp(-decim / (tau * fs));
dicklyon@623 254
dicklyon@462 255 % effective number of smoothings in a time constant:
dicklyon@464 256 ntimes = tau * (fs / decim); % typically 5 to 50
dicklyon@463 257
dicklyon@463 258 % decide on target spread (variance) and delay (mean) of impulse
dicklyon@463 259 % response as a distribution to be convolved ntimes:
dicklyon@464 260 % TODO (dicklyon): specify spread and delay instead of scales???
dicklyon@463 261 delay = (AGC2_scales(stage) - AGC1_scales(stage)) / ntimes;
dicklyon@463 262 spread_sq = (AGC1_scales(stage)^2 + AGC2_scales(stage)^2) / ntimes;
dicklyon@463 263
dicklyon@500 264 % get pole positions to better match intended spread and delay of
dicklyon@464 265 % [[geometric distribution]] in each direction (see wikipedia)
dicklyon@463 266 u = 1 + 1 / spread_sq; % these are based on off-line algebra hacking.
dicklyon@463 267 p = u - sqrt(u^2 - 1); % pole that would give spread if used twice.
dicklyon@463 268 dp = delay * (1 - 2*p +p^2)/2;
dicklyon@463 269 polez1 = p - dp;
dicklyon@463 270 polez2 = p + dp;
dicklyon@623 271 AGC_coeffs(stage).AGC_polez1 = polez1;
dicklyon@623 272 AGC_coeffs(stage).AGC_polez2 = polez2;
dicklyon@462 273
dicklyon@464 274 % try a 3- or 5-tap FIR as an alternative to the double exponential:
dicklyon@464 275 n_taps = 0;
dicklyon@464 276 FIR_OK = 0;
dicklyon@464 277 n_iterations = 1;
dicklyon@464 278 while ~FIR_OK
dicklyon@464 279 switch n_taps
dicklyon@464 280 case 0
dicklyon@464 281 % first attempt a 3-point FIR to apply once:
dicklyon@464 282 n_taps = 3;
dicklyon@464 283 case 3
dicklyon@464 284 % second time through, go wider but stick to 1 iteration
dicklyon@464 285 n_taps = 5;
dicklyon@464 286 case 5
dicklyon@464 287 % apply FIR multiple times instead of going wider:
dicklyon@464 288 n_iterations = n_iterations + 1;
dicklyon@464 289 if n_iterations > 16
dicklyon@464 290 error('Too many n_iterations in CARFAC_DesignAGC');
dicklyon@464 291 end
dicklyon@464 292 otherwise
dicklyon@464 293 % to do other n_taps would need changes in CARFAC_Spatial_Smooth
dicklyon@464 294 % and in Design_FIR_coeffs
dicklyon@464 295 error('Bad n_taps in CARFAC_DesignAGC');
dicklyon@462 296 end
dicklyon@464 297 [AGC_spatial_FIR, FIR_OK] = Design_FIR_coeffs( ...
dicklyon@464 298 n_taps, spread_sq, delay, n_iterations);
dicklyon@462 299 end
dicklyon@464 300 % when FIR_OK, store the resulting FIR design in coeffs:
dicklyon@623 301 AGC_coeffs(stage).AGC_spatial_iterations = n_iterations;
dicklyon@623 302 AGC_coeffs(stage).AGC_spatial_FIR = AGC_spatial_FIR;
dicklyon@623 303 AGC_coeffs(stage).AGC_spatial_n_taps = n_taps;
dicklyon@462 304
dicklyon@464 305 % accumulate DC gains from all the stages, accounting for stage_gain:
dicklyon@462 306 total_DC_gain = total_DC_gain + AGC_params.AGC_stage_gain^(stage-1);
dicklyon@462 307
dicklyon@464 308 % TODO (dicklyon) -- is this the best binaural mixing plan?
dicklyon@462 309 if stage == 1
dicklyon@623 310 AGC_coeffs(stage).AGC_mix_coeffs = 0;
dicklyon@462 311 else
dicklyon@623 312 AGC_coeffs(stage).AGC_mix_coeffs = AGC_params.AGC_mix_coeff / ...
dicklyon@462 313 (tau * (fs / decim));
dicklyon@462 314 end
tom@455 315 end
tom@455 316
dicklyon@623 317 % adjust stage 1 detect_scale to be the reciprocal DC gain of the AGC filters:
dicklyon@623 318 AGC_coeffs(1).detect_scale = 1 / total_DC_gain;
dicklyon@464 319
dicklyon@464 320
dicklyon@464 321 %%
dicklyon@623 322 function [FIR, OK] = Design_FIR_coeffs(n_taps, delay_variance, ...
dicklyon@623 323 mean_delay, n_iter)
dicklyon@623 324 % function [FIR, OK] = Design_FIR_coeffs(n_taps, delay_variance, ...
dicklyon@623 325 % mean_delay, n_iter)
dicklyon@623 326 % The smoothing function is a space-domain smoothing, but it considered
dicklyon@623 327 % here by analogy to time-domain smoothing, which is why its potential
dicklyon@623 328 % off-centeredness is called a delay. Since it's a smoothing filter, it is
dicklyon@623 329 % also analogous to a discrete probability distribution (a p.m.f.), with
dicklyon@623 330 % mean corresponding to delay and variance corresponding to squared spatial
dicklyon@623 331 % spread (in samples, or channels, and the square thereof, respecitively).
dicklyon@623 332 % Here we design a filter implementation's coefficient via the method of
dicklyon@623 333 % moment matching, so we get the intended delay and spread, and don't worry
dicklyon@623 334 % too much about the shape of the distribution, which will be some kind of
dicklyon@623 335 % blob not too far from Gaussian if we run several FIR iterations.
dicklyon@464 336
dicklyon@464 337 % reduce mean and variance of smoothing distribution by n_iterations:
dicklyon@623 338 mean_delay = mean_delay / n_iter;
dicklyon@623 339 delay_variance = delay_variance / n_iter;
dicklyon@464 340 switch n_taps
dicklyon@464 341 case 3
dicklyon@464 342 % based on solving to match mean and variance of [a, 1-a-b, b]:
dicklyon@623 343 a = (delay_variance + mean_delay*mean_delay - mean_delay) / 2;
dicklyon@623 344 b = (delay_variance + mean_delay*mean_delay + mean_delay) / 2;
dicklyon@464 345 FIR = [a, 1 - a - b, b];
dicklyon@464 346 OK = FIR(2) >= 0.2;
dicklyon@464 347 case 5
dicklyon@464 348 % based on solving to match [a/2, a/2, 1-a-b, b/2, b/2]:
dicklyon@623 349 a = ((delay_variance + mean_delay*mean_delay)*2/5 - mean_delay*2/3) / 2;
dicklyon@623 350 b = ((delay_variance + mean_delay*mean_delay)*2/5 + mean_delay*2/3) / 2;
dicklyon@464 351 % first and last coeffs are implicitly duplicated to make 5-point FIR:
dicklyon@464 352 FIR = [a/2, 1 - a - b, b/2];
dicklyon@464 353 OK = FIR(2) >= 0.1;
dicklyon@464 354 otherwise
dicklyon@464 355 error('Bad n_taps in AGC_spatial_FIR');
dicklyon@464 356 end
dicklyon@462 357
tom@455 358
tom@455 359 %% the IHC design coeffs:
dicklyon@473 360 function IHC_coeffs = CARFAC_DesignIHC(IHC_params, fs, n_ch)
tom@455 361
tom@455 362 if IHC_params.just_hwr
dicklyon@500 363 IHC_coeffs = struct( ...
dicklyon@500 364 'n_ch', n_ch, ...
dicklyon@500 365 'just_hwr', 1);
tom@455 366 else
tom@455 367 if IHC_params.one_cap
dicklyon@504 368 ro = 1 / CARFAC_Detect(10); % output resistance at a very high level
dicklyon@495 369 c = IHC_params.tau_out / ro;
dicklyon@495 370 ri = IHC_params.tau_in / c;
dicklyon@495 371 % to get steady-state average, double ro for 50% duty cycle
dicklyon@495 372 saturation_output = 1 / (2*ro + ri);
dicklyon@495 373 % also consider the zero-signal equilibrium:
dicklyon@495 374 r0 = 1 / CARFAC_Detect(0);
dicklyon@495 375 current = 1 / (ri + r0);
dicklyon@495 376 cap_voltage = 1 - current * ri;
dicklyon@473 377 IHC_coeffs = struct( ...
dicklyon@473 378 'n_ch', n_ch, ...
tom@455 379 'just_hwr', 0, ...
tom@455 380 'lpf_coeff', 1 - exp(-1/(IHC_params.tau_lpf * fs)), ...
dicklyon@495 381 'out_rate', ro / (IHC_params.tau_out * fs), ...
tom@455 382 'in_rate', 1 / (IHC_params.tau_in * fs), ...
dicklyon@495 383 'one_cap', IHC_params.one_cap, ...
dicklyon@495 384 'output_gain', 1/ (saturation_output - current), ...
dicklyon@495 385 'rest_output', current / (saturation_output - current), ...
dicklyon@495 386 'rest_cap', cap_voltage);
dicklyon@495 387 % one-channel state for testing/verification:
dicklyon@495 388 IHC_state = struct( ...
dicklyon@495 389 'cap_voltage', IHC_coeffs.rest_cap, ...
dicklyon@495 390 'lpf1_state', 0, ...
dicklyon@495 391 'lpf2_state', 0, ...
dicklyon@500 392 'ihc_accum', 0);
dicklyon@499 393 else
dicklyon@504 394 ro = 1 / CARFAC_Detect(10); % output resistance at a very high level
dicklyon@495 395 c2 = IHC_params.tau2_out / ro;
dicklyon@495 396 r2 = IHC_params.tau2_in / c2;
dicklyon@495 397 c1 = IHC_params.tau1_out / r2;
dicklyon@495 398 r1 = IHC_params.tau1_in / c1;
dicklyon@495 399 % to get steady-state average, double ro for 50% duty cycle
dicklyon@495 400 saturation_output = 1 / (2*ro + r2 + r1);
dicklyon@495 401 % also consider the zero-signal equilibrium:
dicklyon@495 402 r0 = 1 / CARFAC_Detect(0);
dicklyon@495 403 current = 1 / (r1 + r2 + r0);
dicklyon@495 404 cap1_voltage = 1 - current * r1;
dicklyon@495 405 cap2_voltage = cap1_voltage - current * r2;
tom@455 406 IHC_coeffs = struct(...
dicklyon@473 407 'n_ch', n_ch, ...
tom@455 408 'just_hwr', 0, ...
tom@455 409 'lpf_coeff', 1 - exp(-1/(IHC_params.tau_lpf * fs)), ...
tom@455 410 'out1_rate', 1 / (IHC_params.tau1_out * fs), ...
tom@455 411 'in1_rate', 1 / (IHC_params.tau1_in * fs), ...
dicklyon@495 412 'out2_rate', ro / (IHC_params.tau2_out * fs), ...
tom@455 413 'in2_rate', 1 / (IHC_params.tau2_in * fs), ...
dicklyon@495 414 'one_cap', IHC_params.one_cap, ...
dicklyon@495 415 'output_gain', 1/ (saturation_output - current), ...
dicklyon@495 416 'rest_output', current / (saturation_output - current), ...
dicklyon@495 417 'rest_cap2', cap2_voltage, ...
dicklyon@495 418 'rest_cap1', cap1_voltage);
dicklyon@495 419 % one-channel state for testing/verification:
dicklyon@495 420 IHC_state = struct( ...
dicklyon@495 421 'cap1_voltage', IHC_coeffs.rest_cap1, ...
dicklyon@495 422 'cap2_voltage', IHC_coeffs.rest_cap2, ...
dicklyon@495 423 'lpf1_state', 0, ...
dicklyon@495 424 'lpf2_state', 0, ...
dicklyon@495 425 'ihc_accum', 0);
tom@455 426 end
tom@455 427 end
dicklyon@504 428 % one more late addition that applies to all cases:
dicklyon@504 429 IHC_coeffs.ac_coeff = 2 * pi * IHC_params.ac_corner_Hz / fs;
tom@455 430
tom@455 431 %%
tom@455 432 % default design result, running this function with no args, should look
tom@455 433 % like this, before CARFAC_Init puts state storage into it:
tom@455 434 %
dicklyon@606 435
tom@455 436 % CF = CARFAC_Design
dicklyon@504 437 % CAR_params = CF.CAR_params
dicklyon@504 438 % AGC_params = CF.AGC_params
dicklyon@504 439 % IHC_params = CF.IHC_params
dicklyon@504 440 % CAR_coeffs = CF.ears(1).CAR_coeffs
dicklyon@504 441 % AGC_coeffs = CF.ears(1).AGC_coeffs
dicklyon@627 442 % AGC_coeffs(1)
dicklyon@627 443 % AGC_coeffs(2)
dicklyon@627 444 % AGC_coeffs(3)
dicklyon@627 445 % AGC_coeffs(4)
dicklyon@504 446 % IHC_coeffs = CF.ears(1).IHC_coeffs
dicklyon@504 447
dicklyon@504 448 % CF =
dicklyon@469 449 % fs: 22050
dicklyon@495 450 % max_channels_per_octave: 12.2709
dicklyon@495 451 % CAR_params: [1x1 struct]
dicklyon@469 452 % AGC_params: [1x1 struct]
dicklyon@469 453 % IHC_params: [1x1 struct]
dicklyon@495 454 % n_ch: 71
dicklyon@495 455 % pole_freqs: [71x1 double]
dicklyon@504 456 % ears: [1x1 struct]
dicklyon@504 457 % n_ears: 1
dicklyon@504 458 % CAR_params =
dicklyon@507 459 % velocity_scale: 0.1000
dicklyon@504 460 % v_offset: 0.0400
dicklyon@472 461 % min_zeta: 0.1000
dicklyon@504 462 % max_zeta: 0.3500
dicklyon@469 463 % first_pole_theta: 2.6704
dicklyon@469 464 % zero_ratio: 1.4142
dicklyon@469 465 % high_f_damping_compression: 0.5000
dicklyon@469 466 % ERB_per_step: 0.5000
dicklyon@469 467 % min_pole_Hz: 30
dicklyon@495 468 % ERB_break_freq: 165.3000
dicklyon@495 469 % ERB_Q: 9.2645
dicklyon@504 470 % AGC_params =
tom@455 471 % n_stages: 4
tom@455 472 % time_constants: [0.0020 0.0080 0.0320 0.1280]
tom@455 473 % AGC_stage_gain: 2
dicklyon@462 474 % decimation: [8 2 2 2]
dicklyon@627 475 % AGC1_scales: [1 1.4142 2.0000 2.8284]
dicklyon@627 476 % AGC2_scales: [1.6500 2.3335 3.3000 4.6669]
dicklyon@469 477 % AGC_mix_coeff: 0.5000
dicklyon@504 478 % IHC_params =
dicklyon@504 479 % just_hwr: 0
dicklyon@606 480 % one_cap: 1
dicklyon@504 481 % tau_lpf: 8.0000e-05
dicklyon@606 482 % tau_out: 5.0000e-04
dicklyon@606 483 % tau_in: 0.0100
dicklyon@504 484 % ac_corner_Hz: 20
dicklyon@504 485 % CAR_coeffs =
dicklyon@495 486 % n_ch: 71
dicklyon@606 487 % velocity_scale: 0.1000
dicklyon@504 488 % v_offset: 0.0400
dicklyon@495 489 % r1_coeffs: [71x1 double]
dicklyon@495 490 % a0_coeffs: [71x1 double]
dicklyon@495 491 % c0_coeffs: [71x1 double]
dicklyon@495 492 % h_coeffs: [71x1 double]
dicklyon@495 493 % g0_coeffs: [71x1 double]
dicklyon@495 494 % zr_coeffs: [71x1 double]
dicklyon@504 495 % AGC_coeffs =
dicklyon@627 496 % 1x4 struct array with fields:
dicklyon@627 497 % n_ch
dicklyon@627 498 % n_AGC_stages
dicklyon@627 499 % AGC_stage_gain
dicklyon@627 500 % decimation
dicklyon@627 501 % AGC_epsilon
dicklyon@627 502 % AGC_polez1
dicklyon@627 503 % AGC_polez2
dicklyon@627 504 % AGC_spatial_iterations
dicklyon@627 505 % AGC_spatial_FIR
dicklyon@627 506 % AGC_spatial_n_taps
dicklyon@627 507 % AGC_mix_coeffs
dicklyon@627 508 % detect_scale
dicklyon@627 509 % ans =
dicklyon@495 510 % n_ch: 71
dicklyon@495 511 % n_AGC_stages: 4
dicklyon@462 512 % AGC_stage_gain: 2
dicklyon@627 513 % decimation: 8
dicklyon@627 514 % AGC_epsilon: 0.1659
dicklyon@627 515 % AGC_polez1: 0.1737
dicklyon@627 516 % AGC_polez2: 0.2472
dicklyon@627 517 % AGC_spatial_iterations: 1
dicklyon@627 518 % AGC_spatial_FIR: [0.2856 0.3108 0.4036]
dicklyon@627 519 % AGC_spatial_n_taps: 3
dicklyon@627 520 % AGC_mix_coeffs: 0
dicklyon@504 521 % detect_scale: 0.0667
dicklyon@627 522 % ans =
dicklyon@627 523 % n_ch: 71
dicklyon@627 524 % n_AGC_stages: 4
dicklyon@627 525 % AGC_stage_gain: 2
dicklyon@627 526 % decimation: 2
dicklyon@627 527 % AGC_epsilon: 0.0867
dicklyon@627 528 % AGC_polez1: 0.1845
dicklyon@627 529 % AGC_polez2: 0.2365
dicklyon@627 530 % AGC_spatial_iterations: 1
dicklyon@627 531 % AGC_spatial_FIR: [0.2994 0.3178 0.3828]
dicklyon@627 532 % AGC_spatial_n_taps: 3
dicklyon@627 533 % AGC_mix_coeffs: 0.0454
dicklyon@627 534 % detect_scale: []
dicklyon@627 535 % ans =
dicklyon@627 536 % n_ch: 71
dicklyon@627 537 % n_AGC_stages: 4
dicklyon@627 538 % AGC_stage_gain: 2
dicklyon@627 539 % decimation: 2
dicklyon@627 540 % AGC_epsilon: 0.0443
dicklyon@627 541 % AGC_polez1: 0.1921
dicklyon@627 542 % AGC_polez2: 0.2288
dicklyon@627 543 % AGC_spatial_iterations: 1
dicklyon@627 544 % AGC_spatial_FIR: [0.3099 0.3212 0.3689]
dicklyon@627 545 % AGC_spatial_n_taps: 3
dicklyon@627 546 % AGC_mix_coeffs: 0.0227
dicklyon@627 547 % detect_scale: []
dicklyon@627 548 % ans =
dicklyon@627 549 % n_ch: 71
dicklyon@627 550 % n_AGC_stages: 4
dicklyon@627 551 % AGC_stage_gain: 2
dicklyon@627 552 % decimation: 2
dicklyon@627 553 % AGC_epsilon: 0.0224
dicklyon@627 554 % AGC_polez1: 0.1975
dicklyon@627 555 % AGC_polez2: 0.2235
dicklyon@627 556 % AGC_spatial_iterations: 1
dicklyon@627 557 % AGC_spatial_FIR: [0.3177 0.3230 0.3594]
dicklyon@627 558 % AGC_spatial_n_taps: 3
dicklyon@627 559 % AGC_mix_coeffs: 0.0113
dicklyon@627 560 % detect_scale: []
dicklyon@504 561 % IHC_coeffs =
dicklyon@495 562 % n_ch: 71
dicklyon@495 563 % just_hwr: 0
dicklyon@495 564 % lpf_coeff: 0.4327
dicklyon@606 565 % out_rate: 0.0996
dicklyon@606 566 % in_rate: 0.0045
dicklyon@606 567 % one_cap: 1
dicklyon@606 568 % output_gain: 49.3584
dicklyon@606 569 % rest_output: 1.0426
dicklyon@606 570 % rest_cap: 0.5360
dicklyon@504 571 % ac_coeff: 0.0057
dicklyon@627 572