changeset 516:68c15d43fcc8

Added MATLAB code for Lyon's CAR-FAC filter cascade.
author tom@acousticscale.org
date Wed, 15 Feb 2012 21:26:40 +0000
parents 49b7b984e957
children aa282a2b61bb
files trunk/matlab/bmm/carfac/CARFAC_AGCStep.m trunk/matlab/bmm/carfac/CARFAC_Design.m trunk/matlab/bmm/carfac/CARFAC_Detect.m trunk/matlab/bmm/carfac/CARFAC_FilterStep.m trunk/matlab/bmm/carfac/CARFAC_IHCStep.m trunk/matlab/bmm/carfac/CARFAC_Init.m trunk/matlab/bmm/carfac/CARFAC_Run.m trunk/matlab/bmm/carfac/CARFAC_SAI.m trunk/matlab/bmm/carfac/CARFAC_binaural.m trunk/matlab/bmm/carfac/CARFAC_hacking.m trunk/matlab/bmm/carfac/ERB_Hz.m trunk/matlab/bmm/carfac/MultiScaleSmooth.m trunk/matlab/bmm/carfac/SmoothDoubleExponential.m
diffstat 13 files changed, 1237 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/trunk/matlab/bmm/carfac/CARFAC_AGCStep.m	Wed Feb 15 21:26:40 2012 +0000
@@ -0,0 +1,82 @@
+% Copyright 2012, Google, Inc.
+% Author: Richard F. Lyon
+%
+% This Matlab file is part of an implementation of Lyon's cochlear model:
+% "Cascade of Asymmetric Resonators with Fast-Acting Compression"
+% to supplement Lyon's upcoming book "Human and Machine Hearing"
+%
+% Licensed under the Apache License, Version 2.0 (the "License");
+% you may not use this file except in compliance with the License.
+% You may obtain a copy of the License at
+%
+%     http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS,
+% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+% See the License for the specific language governing permissions and
+% limitations under the License.
+
+function state = CARFAC_AGCStep(AGC_coeffs, avg_detects, state)
+% function state = CARFAC_AGCStep(AGC_coeffs, avg_detects, state)
+%
+% one time step (at decimated low AGC rate) of the AGC state update
+
+n_AGC_stages = length(AGC_coeffs.AGC_epsilon);
+n_mics = length(state);
+n_ch = size(state(1).AGC_sum, 1);  % number of channels
+
+optimize_for_mono = n_mics == 1;  % mono optimization
+if ~optimize_for_mono
+  stage_sum = zeros(n_ch, 1);
+end
+
+for stage = 1:n_AGC_stages
+  if ~optimize_for_mono  % skip if mono
+    if stage > 1
+      prev_stage_mean = stage_sum / n_mics;
+    end
+    stage_sum(:) = 0;  % sum accumulating over mics at this stage
+  end
+  epsilon = AGC_coeffs.AGC_epsilon(stage);  % for this stage's LPF pole
+  polez1 = AGC_coeffs.AGC1_polez(stage);
+  polez2 = AGC_coeffs.AGC2_polez(stage);
+  for mic = 1:n_mics
+    if stage == 1
+      AGC_in = AGC_coeffs.detect_scale * avg_detects(:,mic);
+      AGC_in = max(0, AGC_in);  % don't let neg inputs in
+    else
+      % prev. stage mixed with prev_stage_sum
+      if optimize_for_mono
+        % Mono optimization ignores AGC_mix_coeff,
+        % assuming all(prev_stage_mean == AGC_memory(:, stage - 1));
+        % but we also don't even allocate or compute the sum or mean.
+        AGC_in = AGC_coeffs.AGC_stage_gain * ...
+          state(mic).AGC_memory(:, stage - 1);
+      else
+        AGC_in = AGC_coeffs.AGC_stage_gain * ...
+          (AGC_coeffs.AGC_mix_coeff * prev_stage_mean + ...
+            (1 - AGC_coeffs.AGC_mix_coeff) * ...
+              state(mic).AGC_memory(:, stage - 1));
+      end
+    end
+    AGC_stage = state(mic).AGC_memory(:, stage);
+    % first-order recursive smooting filter update:
+    AGC_stage = AGC_stage + epsilon * (AGC_in - AGC_stage);
+
+    % spatially spread it; using diffusion coeffs like in smooth1d
+    AGC_stage = SmoothDoubleExponential(AGC_stage, polez1, polez2);
+
+    state(mic).AGC_memory(:, stage) = AGC_stage;
+    if stage == 1
+      state(mic).sum_AGC = AGC_stage;
+    else
+      state(mic).sum_AGC = state(mic).sum_AGC + AGC_stage;
+    end
+    if ~optimize_for_mono
+      stage_sum = stage_sum + AGC_stage;
+    end
+  end
+end
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/trunk/matlab/bmm/carfac/CARFAC_Design.m	Wed Feb 15 21:26:40 2012 +0000
@@ -0,0 +1,342 @@
+% Copyright 2012, Google, Inc.
+% Author: Richard F. Lyon
+%
+% This Matlab file is part of an implementation of Lyon's cochlear model:
+% "Cascade of Asymmetric Resonators with Fast-Acting Compression"
+% to supplement Lyon's upcoming book "Human and Machine Hearing"
+%
+% Licensed under the Apache License, Version 2.0 (the "License");
+% you may not use this file except in compliance with the License.
+% You may obtain a copy of the License at
+%
+%     http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS,
+% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+% See the License for the specific language governing permissions and
+% limitations under the License.
+
+function CF = CARFAC_Design(fs, CF_filter_params, ...
+  CF_AGC_params, ERB_break_freq, ERB_Q, CF_IHC_params)
+% function CF = CARFAC_Design(fs, CF_filter_params, ...
+%   CF_AGC_params, ERB_break_freq, ERB_Q, CF_IHC_params)
+%
+% This function designs the CARFAC (Cascade of Asymmetric Resonators with
+% Fast-Acting Compression); that is, it take bundles of parameters and
+% computes all the filter coefficients needed to run it.
+%
+% fs is sample rate (per second)
+% CF_filter_params bundles all the pole-zero filter cascade parameters
+% CF_AGC_params bundles all the automatic gain control parameters
+% CF_IHC_params bundles all the inner hair cell parameters
+%
+% See other functions for designing and characterizing the CARFAC:
+% [naps, CF] = CARFAC_Run(CF, input_waves)
+% transfns = CARFAC_Transfer_Functions(CF, to_channels, from_channels)
+%
+% Defaults to Glasberg & Moore's ERB curve:
+% ERB_break_freq = 1000/4.37;  % 228.833
+% ERB_Q = 1000/(24.7*4.37);    % 9.2645
+%
+% All args are defaultable; for sample/default args see the code; they
+% make 96 channels at default fs = 22050, 114 channels at 44100.
+
+if nargin < 6
+  % HACK: these constant control the defaults
+  one_cap = 0;         % bool; 0 for new two-cap hack
+  just_hwr = 0;        % book; 0 for normal/fancy IHC; 1 for HWR
+  if just_hwr
+    CF_IHC_params = struct('just_hwr', 1);  % just a simple HWR
+  else
+    if one_cap
+      CF_IHC_params = struct( ...
+        'just_hwr', 0, ...        % not just a simple HWR
+        'one_cap', one_cap, ...   % bool; 0 for new two-cap hack
+        'tau_lpf', 0.000080, ...  % 80 microseconds smoothing twice
+        'tau_out', 0.0005, ...    % depletion tau is pretty fast
+        'tau_in', 0.010 );        % recovery tau is slower
+    else
+      CF_IHC_params = struct( ...
+        'just_hwr', 0, ...        % not just a simple HWR
+        'one_cap', one_cap, ...   % bool; 0 for new two-cap hack
+        'tau_lpf', 0.000080, ...  % 80 microseconds smoothing twice
+        'tau1_out', 0.020, ...    % depletion tau is pretty fast
+        'tau1_in', 0.020, ...     % recovery tau is slower
+        'tau2_out', 0.005, ...   % depletion tau is pretty fast
+        'tau2_in', 0.005 );        % recovery tau is slower
+    end
+  end
+end
+
+if nargin < 5
+  %	Ref: Glasberg and Moore: Hearing Research, 47 (1990), 103-138
+  % ERB = 24.7 * (1 + 4.37 * CF_Hz / 1000);
+  ERB_Q = 1000/(24.7*4.37);  % 9.2645
+  if nargin < 4
+    ERB_break_freq = 1000/4.37;  % 228.833
+  end
+end
+
+if nargin < 3
+  CF_AGC_params = struct( ...
+    'n_stages', 4, ...
+    'time_constants', [1, 4, 16, 64]*0.002, ...
+    'AGC_stage_gain', 2, ...  % gain from each stage to next slower stage
+    'decimation', 16, ...  % how often to update the AGC states
+    'AGC1_scales', [1, 2, 3, 4]*1, ...   % in units of channels
+    'AGC2_scales', [1, 2, 3, 4]*1.25, ... % spread more toward base
+    'detect_scale', 0.15, ...  % the desired damping range
+    'AGC_mix_coeff', 0.25);
+end
+
+if nargin < 2
+  CF_filter_params = struct( ...
+    'velocity_scale', 0.2, ...  % for the cubic nonlinearity
+    'min_zeta', 0.12, ...
+    'first_pole_theta', 0.78*pi, ...
+    'zero_ratio', sqrt(2), ...
+    'ERB_per_step', 0.3333, ... % assume G&M's ERB formula
+    'min_pole_Hz', 40 );
+end
+
+if nargin < 1
+  fs = 22050;
+end
+
+% first figure out how many filter stages (PZFC/CARFAC channels):
+pole_Hz = CF_filter_params.first_pole_theta * fs / (2*pi);
+n_ch = 0;
+while pole_Hz > CF_filter_params.min_pole_Hz
+  n_ch = n_ch + 1;
+  pole_Hz = pole_Hz - CF_filter_params.ERB_per_step * ...
+    ERB_Hz(pole_Hz, ERB_break_freq, ERB_Q);
+end
+% Now we have n_ch, the number of channels, so can make the array
+% and compute all the frequencies again to put into it:
+pole_freqs = zeros(n_ch, 1);
+pole_Hz = CF_filter_params.first_pole_theta * fs / (2*pi);
+for ch = 1:n_ch
+  pole_freqs(ch) = pole_Hz;
+  pole_Hz = pole_Hz - CF_filter_params.ERB_per_step * ...
+    ERB_Hz(pole_Hz, ERB_break_freq, ERB_Q);
+end
+% now we have n_ch, the number of channels, and pole_freqs array
+
+CF = struct( ...
+  'fs', fs, ...
+  'filter_params', CF_filter_params, ...
+  'AGC_params', CF_AGC_params, ...
+  'IHC_params', CF_IHC_params, ...
+  'n_ch', n_ch, ...
+  'pole_freqs', pole_freqs, ...
+  'filter_coeffs', CARFAC_DesignFilters(CF_filter_params, fs, pole_freqs), ...
+  'AGC_coeffs', CARFAC_DesignAGC(CF_AGC_params, fs), ...
+  'IHC_coeffs', CARFAC_DesignIHC(CF_IHC_params, fs), ...
+  'n_mics', 0 );
+
+% adjust the AGC_coeffs to account for IHC saturation level to get right
+% damping change as specified in CF.AGC_params.detect_scale
+CF.AGC_coeffs.detect_scale = CF.AGC_params.detect_scale / ...
+  (CF.IHC_coeffs.saturation_output * CF.AGC_coeffs.AGC_gain);
+
+%% Design the filter coeffs:
+function filter_coeffs = CARFAC_DesignFilters(filter_params, fs, pole_freqs)
+
+n_ch = length(pole_freqs);
+
+% the filter design coeffs:
+
+filter_coeffs = struct('velocity_scale', filter_params.velocity_scale);
+
+filter_coeffs.r_coeffs = zeros(n_ch, 1);
+filter_coeffs.a_coeffs = zeros(n_ch, 1);
+filter_coeffs.c_coeffs = zeros(n_ch, 1);
+filter_coeffs.h_coeffs = zeros(n_ch, 1);
+filter_coeffs.g_coeffs = zeros(n_ch, 1);
+
+% zero_ratio comes in via h.  In book's circuit D, zero_ratio is 1/sqrt(a),
+% and that a is here 1 / (1+f) where h = f*c.
+% solve for f:  1/zero_ratio^2 = 1 / (1+f)
+% zero_ratio^2 = 1+f => f = zero_ratio^2 - 1
+f = filter_params.zero_ratio^2 - 1;  % nominally 1 for half-octave
+
+% Make pole positions, s and c coeffs, h and g coeffs, etc.,
+% which mostly depend on the pole angle theta:
+theta = pole_freqs .* (2 * pi / fs);
+
+% different possible interpretations for min-damping r:
+% r = exp(-theta * CF_filter_params.min_zeta).
+% Using sin gives somewhat higher Q at highest thetas.
+r = (1 - sin(theta) * filter_params.min_zeta);
+filter_coeffs.r_coeffs = r;
+
+% undamped coupled-form coefficients:
+filter_coeffs.a_coeffs = cos(theta);
+filter_coeffs.c_coeffs = sin(theta);
+
+% the zeros follow via the h_coeffs
+h = sin(theta) .* f;
+filter_coeffs.h_coeffs = h;
+
+r2 = r;  % aim for unity DC gain at min damping, here; or could try r^2
+filter_coeffs.g_coeffs = 1 ./ (1 + h .* r2 .* sin(theta) ./ ...
+  (1 - 2 * r2 .* cos(theta) + r2 .^ 2));
+
+
+%% the AGC design coeffs:
+function AGC_coeffs = CARFAC_DesignAGC(AGC_params, fs)
+
+AGC_coeffs = struct('AGC_stage_gain', AGC_params.AGC_stage_gain, ...
+  'AGC_mix_coeff', AGC_params.AGC_mix_coeff);
+
+
+% AGC1 pass is smoothing from base toward apex;
+% AGC2 pass is back, which is done first now
+AGC1_scales = AGC_params.AGC1_scales;
+AGC2_scales = AGC_params.AGC2_scales;
+
+n_AGC_stages = AGC_params.n_stages;
+AGC_coeffs.AGC_epsilon = zeros(1, n_AGC_stages);  % the 1/(tau*fs) roughly
+decim = AGC_params.decimation;
+gain = 0;
+for stage = 1:n_AGC_stages
+  tau = AGC_params.time_constants(stage);
+  % epsilon is how much new input to take at each update step:
+  AGC_coeffs.AGC_epsilon(stage) = 1 - exp(-decim / (tau * fs));
+  % and these are the smoothing scales and poles for decimated rate:
+  ntimes = tau * (fs / decim);  % effective number of smoothings
+  % divide the spatial variance by effective number of smoothings:
+  t = (AGC1_scales(stage)^2) / ntimes;  % adjust scale for diffusion
+  AGC_coeffs.AGC1_polez(stage) = 1 + 1/t - sqrt((1+1/t)^2 - 1);
+  t = (AGC2_scales(stage)^2) / ntimes;  % adjust scale for diffusion
+  AGC_coeffs.AGC2_polez(stage) = 1 + 1/t - sqrt((1+1/t)^2 - 1);
+  gain = gain + AGC_params.AGC_stage_gain^(stage-1);
+end
+
+AGC_coeffs.AGC_gain = gain;
+
+%% the IHC design coeffs:
+function IHC_coeffs = CARFAC_DesignIHC(IHC_params, fs)
+
+if IHC_params.just_hwr
+  IHC_coeffs = struct('just_hwr', 1);
+  IHC_coeffs.saturation_output = 10;  % HACK: assume some max out
+else
+  if IHC_params.one_cap
+    IHC_coeffs = struct(...
+      'just_hwr', 0, ...
+      'lpf_coeff', 1 - exp(-1/(IHC_params.tau_lpf * fs)), ...
+      'out_rate', 1 / (IHC_params.tau_out * fs), ...
+      'in_rate', 1 / (IHC_params.tau_in * fs), ...
+      'one_cap', IHC_params.one_cap);
+  else
+    IHC_coeffs = struct(...
+      'just_hwr', 0, ...
+      'lpf_coeff', 1 - exp(-1/(IHC_params.tau_lpf * fs)), ...
+      'out1_rate', 1 / (IHC_params.tau1_out * fs), ...
+      'in1_rate', 1 / (IHC_params.tau1_in * fs), ...
+      'out2_rate', 1 / (IHC_params.tau2_out * fs), ...
+      'in2_rate', 1 / (IHC_params.tau2_in * fs), ...
+      'one_cap', IHC_params.one_cap);
+  end
+  
+  % run one channel to convergence to get rest state:
+  IHC_coeffs.rest_output = 0;
+  IHC_state = struct( ...
+    'cap_voltage', 0, ...
+    'cap1_voltage', 0, ...
+    'cap2_voltage', 0, ...
+    'lpf1_state', 0, ...
+    'lpf2_state', 0, ...
+    'ihc_accum', 0);
+  
+  IHC_in = 0;
+  for k = 1:30000
+    [IHC_out, IHC_state] = CARFAC_IHCStep(IHC_in, IHC_coeffs, IHC_state);
+  end
+  
+  IHC_coeffs.rest_output = IHC_out;
+  IHC_coeffs.rest_cap = IHC_state.cap_voltage;
+  IHC_coeffs.rest_cap1 = IHC_state.cap1_voltage;
+  IHC_coeffs.rest_cap2 = IHC_state.cap2_voltage;
+  
+  LARGE = 2;
+  IHC_in = LARGE;  % "Large" saturating input to IHC; make it alternate
+  for k = 1:30000
+    [IHC_out, IHC_state] = CARFAC_IHCStep(IHC_in, IHC_coeffs, IHC_state);
+    prev_IHC_out = IHC_out;
+    IHC_in = -IHC_in;
+  end
+  
+  IHC_coeffs.saturation_output = (IHC_out + prev_IHC_out) / 2;
+end
+
+%%
+% default design result, running this function with no args, should look
+% like this, before CARFAC_Init puts state storage into it:
+%
+% CF = CARFAC_Design
+% CF.filter_params
+% CF.AGC_params
+% CF.filter_coeffs
+% CF.AGC_coeffs
+% CF.IHC_coeffs
+%
+% CF =
+%                fs: 22050
+%     filter_params: [1x1 struct]
+%        AGC_params: [1x1 struct]
+%        IHC_params: [1x1 struct]
+%              n_ch: 96
+%        pole_freqs: [96x1 double]
+%     filter_coeffs: [1x1 struct]
+%        AGC_coeffs: [1x1 struct]
+%        IHC_coeffs: [1x1 struct]
+%            n_mics: 0
+% ans =
+%       velocity_scale: 0.2000
+%             min_zeta: 0.1200
+%     first_pole_theta: 2.4504
+%           zero_ratio: 1.4142
+%         ERB_per_step: 0.3333
+%          min_pole_Hz: 40
+% ans =
+%           n_stages: 4
+%     time_constants: [0.0020 0.0080 0.0320 0.1280]
+%     AGC_stage_gain: 2
+%         decimation: 16
+%        AGC1_scales: [1 2 3 4]
+%        AGC2_scales: [1.2500 2.5000 3.7500 5]
+%       detect_scale: 0.1500
+%      AGC_mix_coeff: 0.2500
+% ans =
+%     velocity_scale: 0.2000
+%           r_coeffs: [96x1 double]
+%           a_coeffs: [96x1 double]
+%           c_coeffs: [96x1 double]
+%           h_coeffs: [96x1 double]
+%           g_coeffs: [96x1 double]
+% ans =
+%     AGC_stage_gain: 2
+%      AGC_mix_coeff: 0.2500
+%        AGC_epsilon: [0.3043 0.0867 0.0224 0.0057]
+%         AGC1_polez: [0.1356 0.1356 0.0854 0.0417]
+%         AGC2_polez: [0.1872 0.1872 0.1227 0.0623]
+%           AGC_gain: 15
+%       detect_scale: 0.0630
+% ans =
+%             lpf_coeff: 0.4327
+%             out1_rate: 0.0023
+%              in1_rate: 0.0023
+%             out2_rate: 0.0091
+%              in2_rate: 0.0091
+%               one_cap: 0
+%           rest_output: 0.0365
+%              rest_cap: 0
+%             rest_cap1: 0.9635
+%             rest_cap2: 0.9269
+%     saturation_output: 0.1587
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/trunk/matlab/bmm/carfac/CARFAC_Detect.m	Wed Feb 15 21:26:40 2012 +0000
@@ -0,0 +1,92 @@
+% Copyright 2012, Google, Inc.
+% Author: Richard F. Lyon
+%
+% This Matlab file is part of an implementation of Lyon's cochlear model:
+% "Cascade of Asymmetric Resonators with Fast-Acting Compression"
+% to supplement Lyon's upcoming book "Human and Machine Hearing"
+%
+% Licensed under the Apache License, Version 2.0 (the "License");
+% you may not use this file except in compliance with the License.
+% You may obtain a copy of the License at
+%
+%     http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS,
+% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+% See the License for the specific language governing permissions and
+% limitations under the License.
+
+function conductance = CARFAC_Detect(x_in)
+% function conductance = CARFAC_detect(x_in)
+% An IHC-like sigmoidal detection nonlinearity for the CARFAC.
+% Resulting conductance is in about [0...1.3405]
+
+
+a = 0.175;   % offset of low-end tail into neg x territory
+% this parameter is adjusted for the book, to make the 20% DC
+% response threshold at 0.1
+
+set = x_in > -a;
+z = x_in(set) + a;
+
+% zero is the final answer for many points:
+conductance = zeros(size(x_in));
+conductance(set) = z.^3 ./ (z.^3 + z.^2 + 0.1);
+
+
+%% other things I tried:
+%
+% % zero is the final answer for many points:
+% conductance = zeros(size(x_in));
+%
+% order = 4;  % 3 is a little cheaper; 4 has continuous second deriv.
+%
+% % thresholds and terms involving just a, b, s are scalar ops; x are vectors
+%
+% switch order
+%   case 3
+%     a = 0.15;  % offset of low-end tail into neg x territory
+%     b = 1; % 0.44;   % width of poly segment
+%     slope = 0.7;
+%
+%     threshold1 = -a;
+%     threshold2 = b - a;
+%
+%     set2 = x_in > threshold2;
+%     set1 = x_in > threshold1 & ~set2;
+%
+%     s = slope/(2*b - 3/2*b^2);  % factor to make slope at breakpoint
+%     t = s * (b^2 - (b^3) / 2);
+%
+%     x = x_in(set1) - threshold1;
+%     conductance(set1) = s * x .* (x - x .* x / 2);  % x.^2 - 0.5x.^3
+%
+%     x = x_in(set2) - threshold2;
+%     conductance(set2) = t + slope * x ./ (1 + x);
+%
+%
+%   case 4
+%     a = 0.24;  % offset of low-end tail into neg x territory
+%     b = 0.57;   % width of poly segment; 0.5 to end at zero curvature,
+%     a = 0.18;  % offset of low-end tail into neg x territory
+%     b = 0.57;   % width of poly segment; 0.5 to end at zero curvature,
+%     % 0.57 to approx. match curvature of the upper segment.
+%     threshold1 = -a;
+%     threshold2 = b - a;
+%
+%
+%     set2 = x_in > threshold2;
+%     set1 = x_in > threshold1 & ~set2;
+%
+%     s = 1/(3*b^2 - 4*b^3);  % factor to make slope 1 at breakpoint
+%     t = s * (b^3 - b^4);
+%
+%     x = x_in(set1) - threshold1;
+%     conductance(set1) = s * x .* x .* (x - x .* x);  % x.^3 - x.^4
+%
+%     x = x_in(set2) - threshold2;
+%     conductance(set2) = t + x ./ (1 + x);
+%
+% end
+%
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/trunk/matlab/bmm/carfac/CARFAC_FilterStep.m	Wed Feb 15 21:26:40 2012 +0000
@@ -0,0 +1,60 @@
+% Copyright 2012, Google, Inc.
+% Author: Richard F. Lyon
+%
+% This Matlab file is part of an implementation of Lyon's cochlear model:
+% "Cascade of Asymmetric Resonators with Fast-Acting Compression"
+% to supplement Lyon's upcoming book "Human and Machine Hearing"
+%
+% Licensed under the Apache License, Version 2.0 (the "License");
+% you may not use this file except in compliance with the License.
+% You may obtain a copy of the License at
+%
+%     http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS,
+% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+% See the License for the specific language governing permissions and
+% limitations under the License.
+
+function [zY, state] = CARFAC_FilterStep(x_in, filter_coeffs, state)
+% function [zY, state] = CARFAC_FilterStep(x_in, filter_coeffs, state)
+%
+% One sample-time update step for the filter part of the CARFAC.
+
+% Use each stage previous Y as input to next:
+inputs = [x_in; state.zY_memory(1:(end-1))];
+
+% Local nonlinearity zA and AGC feedback zB reduce pole radius:
+zA = state.zA_memory;
+zB = state.zB_memory + state.dzB_memory; % AGC interpolation
+r0 = filter_coeffs.r_coeffs;
+r = r0 - filter_coeffs.c_coeffs .* (zA + zB);
+
+% now reduce state by r and rotate with the fixed cos/sin coeffs:
+z1 = r .* (filter_coeffs.a_coeffs .* state.z1_memory - ...
+  filter_coeffs.c_coeffs .* state.z2_memory);
+z1 = z1 + inputs;
+z2 = r .* (filter_coeffs.c_coeffs .* state.z1_memory + ...
+  filter_coeffs.a_coeffs .* state.z2_memory);
+
+% update the "velocity" for cubic nonlinearity, into zA:
+zA = (((state.z2_memory - z2) .* filter_coeffs.velocity_scale) - 0.2) .^ 2;
+
+velocity_damp_max = 1/16;
+zA = velocity_damp_max * zA ./ (1 + zA);  % soft max at velocity_damp_max
+
+% Get outputs from inputs and new z2 values:
+zY = filter_coeffs.g_coeffs .* (inputs + filter_coeffs.h_coeffs .* z2);
+
+% put new state back in place of old
+state.z1_memory = z1;
+state.z2_memory = z2;
+state.zA_memory = zA;
+state.zB_memory = zB;
+state.zY_memory = zY;
+
+% accum the straight hwr version for the sake of AGC range:
+hwr_detect = max(0, zY);  % detect with HWR
+state.detect_accum = state.detect_accum + hwr_detect;
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/trunk/matlab/bmm/carfac/CARFAC_IHCStep.m	Wed Feb 15 21:26:40 2012 +0000
@@ -0,0 +1,62 @@
+% Copyright 2012, Google, Inc.
+% Author: Richard F. Lyon
+%
+% This Matlab file is part of an implementation of Lyon's cochlear model:
+% "Cascade of Asymmetric Resonators with Fast-Acting Compression"
+% to supplement Lyon's upcoming book "Human and Machine Hearing"
+%
+% Licensed under the Apache License, Version 2.0 (the "License");
+% you may not use this file except in compliance with the License.
+% You may obtain a copy of the License at
+%
+%     http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS,
+% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+% See the License for the specific language governing permissions and
+% limitations under the License.
+
+function [ihc_out, state] = CARFAC_IHCStep(filters_out, coeffs, state);
+% function [ihc_out, state] = CARFAC_IHCStep(filters_out, coeffs, state);
+%
+% One sample-time update of inner-hair-cell (IHC) model, including the
+% detection nonlinearity and one or two capacitor state variables.
+
+just_hwr = coeffs.just_hwr;
+
+if just_hwr
+  ihc_out = max(0, filters_out);
+  state.ihc_accum = state.ihc_accum + max(0, ihc_out);
+else
+  one_cap = coeffs.one_cap;
+
+  detect = CARFAC_Detect(filters_out/2);  % detect with HWR or so
+
+  if one_cap
+    ihc_out = detect .* state.cap_voltage;
+    state.cap_voltage = state.cap_voltage - ihc_out .* coeffs.out_rate + ...
+      (1 - state.cap_voltage) .* coeffs.in_rate;
+  else
+    % change to 2-cap version more like Meddis's:
+    ihc_out = detect .* state.cap2_voltage;
+    state.cap1_voltage = state.cap1_voltage - ...
+      (state.cap1_voltage - state.cap2_voltage) .* coeffs.out1_rate + ...
+      (1 - state.cap1_voltage) .* coeffs.in1_rate;
+
+    state.cap2_voltage = state.cap2_voltage - ihc_out .* coeffs.out2_rate + ...
+      (state.cap1_voltage - state.cap2_voltage) .* coeffs.in2_rate;
+  end
+
+  % smooth it twice with LPF:
+
+  state.lpf1_state = state.lpf1_state + coeffs.lpf_coeff * ...
+    (ihc_out - state.lpf1_state);
+
+  state.lpf2_state = state.lpf2_state + coeffs.lpf_coeff * ...
+    (state.lpf1_state - state.lpf2_state);
+
+  ihc_out = state.lpf2_state;
+
+  state.ihc_accum = state.ihc_accum + max(0, ihc_out - coeffs.rest_output);
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/trunk/matlab/bmm/carfac/CARFAC_Init.m	Wed Feb 15 21:26:40 2012 +0000
@@ -0,0 +1,87 @@
+% Copyright 2012, Google, Inc.
+% Author: Richard F. Lyon
+%
+% This Matlab file is part of an implementation of Lyon's cochlear model:
+% "Cascade of Asymmetric Resonators with Fast-Acting Compression"
+% to supplement Lyon's upcoming book "Human and Machine Hearing"
+%
+% Licensed under the Apache License, Version 2.0 (the "License");
+% you may not use this file except in compliance with the License.
+% You may obtain a copy of the License at
+%
+%     http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS,
+% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+% See the License for the specific language governing permissions and
+% limitations under the License.
+
+function CF_struct = CARFAC_Init(CF_struct, n_mics)
+% function CF_struct = CARFAC_Init(CF_struct, n_mics)
+%
+% Initialize state for n_mics channels (default 1).
+% This allocates and zeros all the state vector storage in the CF_struct.
+
+% TODO (dicklyon): Review whether storing state in the same struct as
+% the design is a good thing, or whether we want another
+% level of object.  I like fewer structs and class types.
+
+if nargin < 2
+  n_mics = 1;  % monaural
+end
+
+% % this is probably what I'd do in the C++ version:
+% if CF_struct.n_mics ~= n_mics;
+%   % free the state and make new number of channels
+%   % make a struct arrray, one element per mic channel, numbered:
+%   for k = 1:n_mics
+%     CF_struct.state(k)  = struct('mic_number', k);
+%   end
+% end
+% But this code doesn't work because I don't understand struct arrays.
+
+% For now I don't ever free anything if n_mics is reduced;
+% so be sure to respect n_mics, not the size of the state struct array.
+
+AGC_time_constants = CF_struct.AGC_params.time_constants;
+n_AGC_stages = length(AGC_time_constants);
+
+CF_struct.n_mics = n_mics;
+CF_struct.k_mod_decim = 0;  % time index phase, cumulative over segments
+
+% This zeroing grows the struct array as needed:
+for mic = 1:n_mics
+  CF_struct.filter_state(mic).z1_memory = zeros(CF_struct.n_ch, 1);
+  CF_struct.filter_state(mic).z2_memory = zeros(CF_struct.n_ch, 1);
+  % cubic loop
+  CF_struct.filter_state(mic).zA_memory = zeros(CF_struct.n_ch, 1);
+  % AGC interp
+  CF_struct.filter_state(mic).zB_memory = zeros(CF_struct.n_ch, 1);
+  % AGC incr
+  CF_struct.filter_state(mic).dzB_memory = zeros(CF_struct.n_ch, 1);
+  CF_struct.filter_state(mic).zY_memory = zeros(CF_struct.n_ch, 1);
+  CF_struct.filter_state(mic).detect_accum = zeros(CF_struct.n_ch, 1);
+  % AGC loop filters' state:
+  % HACK init
+  CF_struct.AGC_state(mic).AGC_memory = zeros(CF_struct.n_ch, n_AGC_stages);
+  CF_struct.AGC_state(mic).AGC_sum = zeros(CF_struct.n_ch, 1);
+  % IHC state:
+  if CF_struct.IHC_coeffs.just_hwr
+    CF_struct.IHC_state(mic).ihc_accum = zeros(CF_struct.n_ch, 1);
+  else
+    CF_struct.IHC_state(mic).cap_voltage = ...
+      CF_struct.IHC_coeffs(mic).rest_cap * ones(CF_struct.n_ch, 1);
+    CF_struct.IHC_state(mic).cap1_voltage = ...
+      CF_struct.IHC_coeffs(mic).rest_cap1 * ones(CF_struct.n_ch, 1);
+    CF_struct.IHC_state(mic).cap2_voltage = ...
+      CF_struct.IHC_coeffs(mic).rest_cap2 * ones(CF_struct.n_ch, 1);
+    CF_struct.IHC_state(mic).lpf1_state = ...
+      CF_struct.IHC_coeffs(mic).rest_output * zeros(CF_struct.n_ch, 1);
+    CF_struct.IHC_state(mic).lpf2_state = ...
+      CF_struct.IHC_coeffs(mic).rest_output * zeros(CF_struct.n_ch, 1);
+    CF_struct.IHC_state(mic).ihc_accum = zeros(CF_struct.n_ch, 1);
+  end
+end
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/trunk/matlab/bmm/carfac/CARFAC_Run.m	Wed Feb 15 21:26:40 2012 +0000
@@ -0,0 +1,148 @@
+% Copyright 2012, Google, Inc.
+% Author: Richard F. Lyon
+%
+% This Matlab file is part of an implementation of Lyon's cochlear model:
+% "Cascade of Asymmetric Resonators with Fast-Acting Compression"
+% to supplement Lyon's upcoming book "Human and Machine Hearing"
+%
+% Licensed under the Apache License, Version 2.0 (the "License");
+% you may not use this file except in compliance with the License.
+% You may obtain a copy of the License at
+%
+%     http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS,
+% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+% See the License for the specific language governing permissions and
+% limitations under the License.
+
+function [naps, CF, decim_naps] = CARFAC_Run ...
+  (CF, input_waves, AGC_plot_fig_num)
+% function [naps, CF, CF.cum_k, decim_naps] = CARFAC_Run ...
+%    (CF, input_waves, CF.cum_k, AGC_plot_fig_num)
+% This function runs the CARFAC; that is, filters a 1 or more channel
+% sound input to make one or more neural activity patterns (naps).
+%
+% The CF struct holds the filterbank design and state; if you want to
+% break the input up into segments, you need to use the updated CF
+% to keep the state between segments.
+%
+% input_waves is a column vector if there's just one audio channel;
+% more generally, it has a row per time sample, a column per audio channel.
+%
+% naps has a row per time sample, a column per filterbank channel, and
+% a layer per audio channel if more than 1.
+% decim_naps is like naps but time-decimated by the int CF.decimation.
+%
+% the input_waves are assumed to be sampled at the same rate as the
+% CARFAC is designed for; a resampling may be needed before calling this.
+%
+% The function works as an outer iteration on time, updating all the
+% filters and AGC states concurrently, so that the different channels can
+% interact easily.  The inner loops are over filterbank channels, and
+% this level should be kept efficient.
+%
+% See other functions for designing and characterizing the CARFAC:
+% CF = CARFAC_Design(fs, CF_filter_params, CF_AGC_params, n_mics)
+% transfns = CARFAC_Transfer_Functions(CF, to_chans, from_chans)
+
+[n_samp, n_mics] = size(input_waves);
+n_ch = CF.n_ch;
+
+if nargin < 3
+  AGC_plot_fig_num = 0;
+end
+
+if n_mics ~= CF.n_mics
+  error('bad number of input_waves channels passed to CARFAC_Run')
+end
+
+% pull coeffs out of struct first, into local vars for convenience
+decim = CF.AGC_params.decimation;
+
+naps = zeros(n_samp, n_ch, n_mics);
+if nargout > 2
+  % make decimated detect output:
+  decim_naps = zeros(ceil(n_samp/decim), CF.n_ch, CF.n_mics);
+else
+  decim_naps = [];
+end
+
+decim_k = 0;
+
+sum_abs_response = 0;
+
+for k = 1:n_samp
+  CF.k_mod_decim = mod(CF.k_mod_decim + 1, decim);  % global time phase
+  % at each time step, possibly handle multiple channels
+  for mic = 1:n_mics
+    [filters_out, CF.filter_state(mic)] = CARFAC_FilterStep( ...
+      input_waves(k, mic), CF.filter_coeffs, CF.filter_state(mic));
+
+    % update IHC state & output on every time step, too
+    [ihc_out, CF.IHC_state(mic)] = CARFAC_IHCStep( ...
+      filters_out, CF.IHC_coeffs, CF.IHC_state(mic));
+
+%     sum_abs_response = sum_abs_response + abs(filters_out);
+
+    naps(k, :, mic) = ihc_out;  % output to neural activity pattern
+  end
+
+  % conditionally update all the AGC stages and channels now:
+  if CF.k_mod_decim == 0
+    % just for the plotting option:
+    decim_k = decim_k + 1;   % index of decimated signal for display
+    if ~isempty(decim_naps)
+      for mic = 1:n_mics
+        % this is HWR out of filters, not IHCs
+        avg_detect = CF.filter_state(mic).detect_accum / decim;
+        % This HACK is the IHC version:
+        avg_detect = CF.IHC_state(mic).ihc_accum / decim;  % for cochleagram
+        decim_naps(decim_k, :, mic) = avg_detect;  % for cochleagram
+%         decim_naps(decim_k, :, mic) = sum_abs_response / decim;  % HACK for mechanical out ABS
+%         sum_abs_response(:) = 0;
+      end
+    end
+
+    % get the avg_detects to connect filter_state to AGC_state:
+    avg_detects = zeros(n_ch, n_mics);
+    for mic = 1:n_mics
+%       % mechanical response from filter output through HWR as AGC in:
+%       avg_detects(:, mic) = CF.filter_state(mic).detect_accum / decim;
+      CF.filter_state(mic).detect_accum(:) = 0;  % zero the detect accumulator
+      % New HACK, IHC output relative to rest as input to AGC:
+      avg_detects(:, mic) = CF.IHC_state(mic).ihc_accum / decim;
+      CF.IHC_state(mic).ihc_accum(:) = 0;  % zero the detect accumulator
+    end
+
+    % run the AGC update step:
+    CF.AGC_state = CARFAC_AGCStep(CF.AGC_coeffs, avg_detects, CF.AGC_state);
+
+    % connect the feedback from AGC_state to filter_state:
+    for mic = 1:n_mics
+      new_damping = CF.AGC_state(mic).sum_AGC;
+%       max_damping = 0.15;  % HACK
+%       new_damping = min(new_damping, max_damping);
+      % set the delta needed to get to new_damping:
+      CF.filter_state(mic).dzB_memory = ...
+        (new_damping - CF.filter_state(mic).zB_memory) ...
+          / decim;
+    end
+
+    if AGC_plot_fig_num
+      figure(AGC_plot_fig_num); hold off
+      maxsum = 0;
+      for mic = 1:n_mics
+        plot(CF.AGC_state(mic).AGC_memory)
+        agcsum = sum(CF.AGC_state(mic).AGC_memory, 2);
+        maxsum(mic) = max(maxsum, max(agcsum));
+        hold on
+        plot(agcsum, 'k-')
+      end
+      axis([0, CF.n_ch, 0, max(0.001, maxsum)]);
+      drawnow
+    end
+  end
+end
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/trunk/matlab/bmm/carfac/CARFAC_SAI.m	Wed Feb 15 21:26:40 2012 +0000
@@ -0,0 +1,70 @@
+% Copyright 2012, Google, Inc.
+% Author: Richard F. Lyon
+%
+% This Matlab file is part of an implementation of Lyon's cochlear model:
+% "Cascade of Asymmetric Resonators with Fast-Acting Compression"
+% to supplement Lyon's upcoming book "Human and Machine Hearing"
+%
+% Licensed under the Apache License, Version 2.0 (the "License");
+% you may not use this file except in compliance with the License.
+% You may obtain a copy of the License at
+%
+%     http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS,
+% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+% See the License for the specific language governing permissions and
+% limitations under the License.
+
+function [CF, sai] = CARFAC_SAI(CF, k, n_mics, naps, sai)
+% function sai = CARFAC_SAI(CF_struct, n_mics, naps, sai)
+%
+% Calculate the Stabilized Auditory Image from naps
+%
+
+  threshold_alpha = CF.sai_params.threshold_alpha;
+  threshold_jump = CF.sai_params.threshold_jump_factor;
+  threshold_offset = CF.sai_params.threshold_jump_offset;
+
+  sai2 = reshape(sai,CF.sai_params.sai_width * CF.n_ch,n_mics);
+  naps2 = reshape(naps,CF.n_samp * CF.n_ch,n_mics);
+
+  for mic = 1:n_mics
+    data = naps(k, :, mic)';
+    above_threshold = (CF.sai_state(mic).lastdata > ...
+                       CF.sai_state(mic).thresholds) & ...
+                      (CF.sai_state(mic).lastdata > data);
+    CF.sai_state(mic).thresholds(above_threshold) = ...
+        data(above_threshold) * threshold_jump + threshold_offset;
+    CF.sai_state(mic).thresholds(~above_threshold) = ...
+        CF.sai_state(mic).thresholds(~above_threshold) * threshold_alpha;
+    CF.sai_state(mic).lastdata = data;
+
+    % Update SAI image with strobe data.
+    othermic = 3 - mic;
+
+    % Channels that are above the threhsold
+    above_ch = find(above_threshold);
+
+    % If we are above the threshold, set the trigger index and reset the
+    % sai_index
+    CF.sai_state(mic).trigger_index(above_ch) = k;
+    CF.sai_state(mic).sai_index(above_ch) = 1;
+
+    % Copy the right data from the nap to the sai
+    chans = (1:CF.n_ch)';
+    fromindices = CF.sai_state(mic).trigger_index() + (chans - 1) * CF.n_samp;
+    toindices = min((CF.sai_state(mic).sai_index() + (chans - 1) * ...
+                     CF.sai_params.sai_width), ...
+                     CF.sai_params.sai_width * CF.n_ch);
+    sai2(toindices,mic) = naps2(fromindices,othermic);
+
+    CF.sai_state(mic).trigger_index(:) = CF.sai_state(mic).trigger_index(:) + 1;
+    CF.sai_state(mic).sai_index(:) = CF.sai_state(mic).sai_index(:) + 1;
+
+  end
+
+  sai = reshape(sai2,CF.sai_params.sai_width,CF.n_ch,n_mics);
+  naps = reshape(naps2,CF.n_samp, CF.n_ch,n_mics);
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/trunk/matlab/bmm/carfac/CARFAC_binaural.m	Wed Feb 15 21:26:40 2012 +0000
@@ -0,0 +1,60 @@
+% Copyright 2012, Google, Inc.
+% Author: Richard F. Lyon
+%
+% This Matlab file is part of an implementation of Lyon's cochlear model:
+% "Cascade of Asymmetric Resonators with Fast-Acting Compression"
+% to supplement Lyon's upcoming book "Human and Machine Hearing"
+%
+% Licensed under the Apache License, Version 2.0 (the "License");
+% you may not use this file except in compliance with the License.
+% You may obtain a copy of the License at
+%
+%     http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS,
+% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+% See the License for the specific language governing permissions and
+% limitations under the License.
+
+% Test/demo hacking for audio/hearing/filterbanks/carfac/matlab/ stuff
+
+clear variables
+
+agc_plot_fig_num = 1;
+
+tic
+
+%test_signal = wavread('pbav4p-22050.wav');
+test_signal = wavread('plan-twochannel-1ms.wav')
+%test_signal = wavread('binaural.wav')
+%test_signal = test_signal(20000:2);  % trim for a faster test
+
+CF_struct = CARFAC_Design;  % default design
+cum_k = 0;  % not doing segments, so just count from 0
+
+% Run mono, then stereo test:
+n_mics = 2
+CF_struct = CARFAC_Init(CF_struct, n_mics);
+
+[nap, CF_struct, cum_k, nap_decim] = ...
+    CARFAC_Run(CF_struct, test_signal, cum_k, agc_plot_fig_num);
+
+% Display results for 1 or 2 mics:
+for mic = 1:n_mics
+  smooth_nap = nap_decim(:, :, mic);
+  figure(mic + n_mics)  % Makes figures 2, 3, and 4
+  image(63 * ((smooth_nap)' .^ 0.5))
+
+  colormap(1 - gray);
+end
+
+toc
+
+% Show resulting data, even though M-Lint complains:
+cum_k
+CF_struct
+CF_struct.filter_state
+CF_struct.AGC_state
+min_max = [min(nap(:)), max(nap(:))]
+min_max_decim = [min(nap_decim(:)), max(nap_decim(:))]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/trunk/matlab/bmm/carfac/CARFAC_hacking.m	Wed Feb 15 21:26:40 2012 +0000
@@ -0,0 +1,85 @@
+% Copyright 2012, Google, Inc.
+% Author: Richard F. Lyon
+%
+% This Matlab file is part of an implementation of Lyon's cochlear model:
+% "Cascade of Asymmetric Resonators with Fast-Acting Compression"
+% to supplement Lyon's upcoming book "Human and Machine Hearing"
+%
+% Licensed under the Apache License, Version 2.0 (the "License");
+% you may not use this file except in compliance with the License.
+% You may obtain a copy of the License at
+%
+%     http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS,
+% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+% See the License for the specific language governing permissions and
+% limitations under the License.
+
+%% Test/demo hacking for CARFAC Matlab stuff:
+
+clear variables
+
+%%
+file_signal = wavread('plan.wav');
+
+% file_signal = file_signal(9000+(1:10000));  % trim for a faster test
+file_signal = file_signal(9300+(1:5000));  % trim for a faster test
+
+% repeat with negated signal to compare responses:
+file_signal = [file_signal; -file_signal];
+
+% make a long test signal by repeating at different levels:
+test_signal = [];
+for dB = -60:20:40  % -80:20:60
+  test_signal = [test_signal; file_signal * 10^(dB/20)];
+end
+
+%%
+CF_struct = CARFAC_Design;  % default design
+
+%% Run mono, then stereo test:
+
+agc_plot_fig_num = 6;
+
+for n_mics = 1  % :2
+  CF_struct = CARFAC_Init(CF_struct, n_mics);
+
+  [nap, CF_struct, nap_decim] = CARFAC_Run(CF_struct, ...
+    test_signal, agc_plot_fig_num);
+
+%   nap = deskew(nap);  % deskew doesn't make much difference
+
+  MultiScaleSmooth(nap_decim, 10);
+
+%   nap_decim = nap;
+%   nap_decim = smooth1d(nap_decim, 1);
+%   nap_decim = nap_decim(1:8:size(nap_decim, 1), :);
+
+  % Display results for 1 or 2 mics:
+  for mic = 1:n_mics
+    smooth_nap = nap_decim(:, :, mic);
+    if n_mics == 1
+      mono_max = max(smooth_nap(:));
+    end
+    figure(3 + mic + n_mics)  % Makes figures 5, ...
+    image(63 * ((max(0, smooth_nap)/mono_max)' .^ 0.5))
+    title('smooth nap from nap decim')
+    colormap(1 - gray);
+  end
+
+  % Show resulting data, even though M-Lint complains:
+  CF_struct
+  CF_struct.k_mod_decim
+  CF_struct.filter_state
+  CF_struct.AGC_state
+  min_max = [min(nap(:)), max(nap(:))]
+  min_max_decim = [min(nap_decim(:)), max(nap_decim(:))]
+
+  % For the 2-channel pass, add a silent second channel:
+  test_signal = [test_signal, zeros(size(test_signal))];
+end
+
+% Expected result:  Figure 3 looks like figure 2, a tiny bit darker.
+% and figure 4 is empty (all zero)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/trunk/matlab/bmm/carfac/ERB_Hz.m	Wed Feb 15 21:26:40 2012 +0000
@@ -0,0 +1,15 @@
+function ERB = ERB_Hz(CF_Hz, ERB_break_freq, ERB_Q)
+% function ERB = ERB_Hz(CF_Hz, ERB_break_freq, ERB_Q)
+%
+% Auditory filter nominal Equivalent Rectangular Bandwidth
+%	Ref: Glasberg and Moore: Hearing Research, 47 (1990), 103-138
+% ERB = 24.7 * (1 + 4.37 * CF_Hz / 1000);
+
+if nargin < 3
+  ERB_Q = 1000/(24.7*4.37);  % 9.2645
+  if nargin < 2
+    ERB_break_freq = 1000/4.37;  % 228.833
+  end
+end
+
+ERB = (ERB_break_freq + CF_Hz) / ERB_Q;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/trunk/matlab/bmm/carfac/MultiScaleSmooth.m	Wed Feb 15 21:26:40 2012 +0000
@@ -0,0 +1,70 @@
+% Copyright 2012, Google, Inc.
+% Author: Richard F. Lyon
+%
+% This Matlab file is part of an implementation of Lyon's cochlear model:
+% "Cascade of Asymmetric Resonators with Fast-Acting Compression"
+% to supplement Lyon's upcoming book "Human and Machine Hearing"
+%
+% Licensed under the Apache License, Version 2.0 (the "License");
+% you may not use this file except in compliance with the License.
+% You may obtain a copy of the License at
+%
+%     http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS,
+% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+% See the License for the specific language governing permissions and
+% limitations under the License.
+
+function MultiScaleSmooth(waves, n_scales)
+% function MultiScaleSmooth(waves, n_scales)
+%
+% Let's take columns as waveforms, and smooth them to different scales;
+% these inputs can be carfac NAPs, for example, and the peaks of the
+% smoothed versions can be used as trigger events, even tracking back
+% to less-smoothed versions.
+% And we'll deciamte 2:1 at every other smoothing.
+%
+% Until we decide what we want, we'll just plot things, one plot per scale.
+
+fig_offset1 = 10;
+fig_offset2 = 30;
+fig_offset3 = 50;
+
+if nargin < 2
+  n_scales = 20;
+end
+
+smoothed = waves;
+
+for scale_no = 1:n_scales
+
+  if mod(scale_no, 2) == 1
+    newsmoothed = filter([1, 1]/2, 1, smoothed);
+    diffsmoothed = max(0, smoothed - newsmoothed);
+    smoothed = newsmoothed;
+  else
+    newsmoothed = filter([1, 2, 1]/4, 1, smoothed);
+    diffsmoothed = max(0, smoothed - newsmoothed);
+    smoothed = newsmoothed(1:2:end, :);
+  end
+
+  figure(scale_no + fig_offset1)
+  imagesc(smoothed')
+
+  figure(scale_no + fig_offset2)
+  plot(mean(smoothed, 2));
+
+  drawnow
+  pause(1)
+
+end
+
+
+function waves = deskew(waves)
+
+for col = 1:size(waves, 2)
+  waves(1:(end-col+1), col) = waves(col:end, col);
+end
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/trunk/matlab/bmm/carfac/SmoothDoubleExponential.m	Wed Feb 15 21:26:40 2012 +0000
@@ -0,0 +1,64 @@
+% Copyright 2012, Google, Inc.
+% Author: Richard F. Lyon
+%
+% This Matlab file is part of an implementation of Lyon's cochlear model:
+% "Cascade of Asymmetric Resonators with Fast-Acting Compression"
+% to supplement Lyon's upcoming book "Human and Machine Hearing"
+%
+% Licensed under the Apache License, Version 2.0 (the "License");
+% you may not use this file except in compliance with the License.
+% You may obtain a copy of the License at
+%
+%     http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS,
+% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+% See the License for the specific language governing permissions and
+% limitations under the License.
+
+function signal_vecs = SmoothDoubleExponential(signal_vecs, ...
+  polez1, polez2, fast_matlab_way)
+% function signal_vecs = SmoothDoubleExponential(signal_vecs, ...
+%   polez1, polez2, fast_matlab_way)
+%
+% Smooth the input column vectors in signal_vecs using forward
+% and backwards one-pole smoothing filters, backwards first, with
+% approximately reflecting edge conditions.
+%
+% It will be done with Matlab's filter function if "fast_matlab_way"
+% is nonzero or defaulted; use 0 to test the algorithm for how to do it
+% in sequential c code.
+
+if nargin < 4
+  fast_matlab_way = 1;
+  % can also use the slow way with explicit loop like we'll do in C++
+end
+
+if fast_matlab_way
+  [junk, Z_state] = filter(1-polez1, [1, -polez1], ...
+    signal_vecs((end-10):end, :));  % initialize state from 10 points
+  [signal_vecs(end:-1:1), Z_state] = filter(1-polez2, [1, -polez2], ...
+    signal_vecs(end:-1:1), Z_state*polez2/polez1);
+  signal_vecs = filter(1-polez1, [1, -polez1], signal_vecs, ...
+    Z_state*polez1/polez2);
+else
+  npts = size(signal_vecs, 1);
+  state = zeros(size(signal_vecs, 2));
+  for index = npts-10:npts
+    input = signal_vecs(index, :);
+    state = state + (1 - polez1) * (input - state);
+  end
+  % smooth backward with polez2, starting with state from above:
+  for index = npts:-1:1
+    input = signal_vecs(index, :);
+    state = state + (1 - polez2) * (input - state);
+    signal_vecs(index, :) = state;
+  end
+  % smooth forward with polez1, starting with state from above:
+  for index = 1:npts
+    input = signal_vecs(index, :);
+    state = state + (1 - polez1) * (input - state);
+    signal_vecs(index, :) = state;
+  end
+end