Skip to content

Commit

Permalink
In CARFAC_Design, make IHC default to two_cap for v2, and improve som…
Browse files Browse the repository at this point in the history
…e doc strings. Also add RELEASE_NOTES.md to briefly document the changes for v2.

PiperOrigin-RevId: 581548755
  • Loading branch information
CARFAC Team authored and copybara-github committed Nov 11, 2023
1 parent 4622c9e commit 3d53aab
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 29 deletions.
79 changes: 67 additions & 12 deletions matlab/CARFAC_Design.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,40 +17,72 @@
% limitations under the License.

function CF = CARFAC_Design(n_ears, fs, ...
CF_CAR_params, CF_AGC_params, CF_IHC_params)
CF_CAR_params, CF_AGC_params, CF_IHC_params, CF_IHC_keyword)
% function CF = CARFAC_Design(n_ears, fs, ...
% CF_CAR_params, CF_AGC_params, CF_IHC_params)
% CF_CAR_params, CF_AGC_params, CF_IHC_params, CF_IHC_keyword)
%
% 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.
% computes all the filter coefficients needed to run it. Before running,
% CARFAC_Init is needed, to build and initialize the state variables.
%
% n_ears (typically 1 or 2, can be more) is number of sound channels.
% fs is sample rate (per second)
% CF_CAR_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
% Indendent of how many of these are provided, if the last arg is a string
% it will be interpreted as an IHC_style keyword ('just_hwr' or 'one_cap';
% omit or 'two_cap' for default v2 two-cap IHC model.
%
% 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; % 165.3 -- No, default is Greenwood 165.3
% Scale is like Glasberg & Moore's ERB curve, but Greenwood's breakf.
% ERB_break_freq = 165.3; % Not 1000/4.37, 228.8 Hz breakf of Moore.
% ERB_Q = 1000/(24.7*4.37); % 9.2645
% Edit CF.CF_CAR_params and call CARFAC_Design again to change.
% Similarly for changing other things.
%
% All args are defaultable; for sample/default args see the code; they
% make 71 channels at default fs = 22050.
% For "modern" applications we prefer fs = 48000, which makes 84 channels.

switch nargin
case 0
last_arg = [];
case 1
last_arg = n_ears;
case 2
last_arg = fs;
case 3
last_arg = CF_CAR_params;
case 4
last_arg = CF_AGC_params;
case 5
last_arg = CF_IHC_params;
case 6
last_arg = CF_IHC_keyword;
end
if ischar(last_arg) % string is a character array, not a string array.
IHC_keyword = last_arg;
num_args = nargin - 1;
else
IHC_keyword = 'two_cap'; % the CARFAC v2 default
num_args = nargin;
end

if nargin < 1
if num_args < 1
n_ears = 1; % if more than 1, make them identical channels;
% then modify the design if necessary for different reasons
end

if nargin < 2
if num_args < 2
fs = 22050;
end

if nargin < 3
if num_args < 3
CF_CAR_params = struct( ...
'velocity_scale', 0.1, ... % for the velocity nonlinearity
'v_offset', 0.04, ... % offset gives a quadratic part
Expand All @@ -67,7 +99,7 @@
);
end

if nargin < 4
if num_args < 4
CF_AGC_params = struct( ...
'n_stages', 4, ...
'time_constants', 0.002 * 4.^(0:3), ...
Expand All @@ -78,10 +110,19 @@
'AGC_mix_coeff', 0.5);
end

if nargin < 5
% HACK: these constants control the defaults
one_cap = 1; % bool; 1 for Allen model, as text states we use
if num_args < 5
one_cap = 0; % bool; 1 for Allen model, 0 for new default two-cap
just_hwr = 0; % bool; 0 for normal/fancy IHC; 1 for HWR
switch IHC_keyword
case 'just_hwr'
just_hwr = 1; % bool; 0 for normal/fancy IHC; 1 for HWR
case 'one_cap'
one_cap = 1; % bool; 1 for Allen model, as text states we use
case 'two_cap'
% nothing to do; accept the default
otherwise
error('unknown IHC_keyword in CARFAC_Design')
end
CF_IHC_params = struct( ...
'just_hwr', just_hwr, ... % not just a simple HWR
'one_cap', one_cap, ... % bool; 0 for new two-cap hack
Expand Down Expand Up @@ -238,6 +279,7 @@
% Maybe re-do this at Init time?
undamping = CAR_coeffs.OHC_health; % Typically just ones.
% Avoid running this model function at Design time; see tests.
% CAR_coeffs.g0_coeffs = CARFAC_Stage_g(CAR_coeffs, undamping);
CAR_coeffs.g0_coeffs = CARFAC_Design_Stage_g(CAR_coeffs, undamping);


Expand Down Expand Up @@ -440,6 +482,12 @@
'output_gain', 1 / (saturation_current - rest_current), ...
'rest_output', rest_current / (saturation_current - rest_current), ...
'rest_cap', cap_voltage);
% one-channel state for testing/verification:
IHC_state = struct( ...
'cap_voltage', IHC_coeffs.rest_cap, ...
'lpf1_state', 0, ...
'lpf2_state', 0, ...
'ihc_accum', 0);
else
g1max = CARFAC_Detect(10); % receptor conductance at high level
r1min = 1 / g1max;
Expand Down Expand Up @@ -482,5 +530,12 @@
'rest_output', rest_current2 / (saturation_current2 - rest_current2), ...
'rest_cap2', cap2_voltage, ...
'rest_cap1', cap1_voltage);
% one-channel state for testing/verification:
IHC_state = struct( ...
'cap1_voltage', IHC_coeffs.rest_cap1, ...
'cap2_voltage', IHC_coeffs.rest_cap2, ...
'lpf1_state', 0, ...
'lpf2_state', 0, ...
'ihc_accum', 0);
end
end
67 changes: 50 additions & 17 deletions matlab/CARFAC_Test.m
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
status = status | test_IHC2(do_plots);
status = status | test_AGC_steady_state(do_plots);
status = status | test_stage_g_calculation(do_plots);
status = status | test_whole_carfac(do_plots);
status = status | test_whole_carfac1(do_plots);
status = status | test_whole_carfac2(do_plots);
status = status | test_delay_buffer(do_plots);
status = status | test_OHC_health(do_plots);
status = status | test_multiaural_silent_channel_carfac(do_plots);
Expand Down Expand Up @@ -440,7 +441,19 @@
return


function status = test_whole_carfac(do_plots)
function status = test_whole_carfac1(do_plots)
status = test_whole_carfac(do_plots, 'one_cap')
report_status(status, 'test_whole_carfac1')
return


function status = test_whole_carfac2(do_plots)
status = test_whole_carfac(do_plots, 'two_cap')
report_status(status, 'test_whole_carfac2')
return


function status = test_whole_carfac(do_plots, IHC_style)
% Test: Make sure that the AGC adapts to a tone. Test with open-loop
% impulse response.

Expand All @@ -456,7 +469,7 @@
impulse = zeros(round(impulse_dur*fs), 1); % For short impulse wave.
impulse(1) = 1e-4; % Small amplitude impulse to keep it pretty linear

CF = CARFAC_Design(1, fs);
CF = CARFAC_Design(1, fs, IHC_style);
CF = CARFAC_Init(CF);

CF.open_loop = 1; % For measuring impulse response.
Expand Down Expand Up @@ -519,15 +532,36 @@

% Test for change in peak gain after adaptation.
% Golden data table of frequency, channel, peak frequency, delta:
results = [
125, 65, 119.007, 0.264
250, 59, 239.791, 0.986
500, 50, 514.613, 7.309
1000, 39, 1099.436, 31.644
2000, 29, 2038.875, 27.214
4000, 17, 4058.882, 13.823
8000, 3, 8289.883, 3.565
];
switch IHC_style
case 'one_cap'
% Before moving ac coupling into CAR, peaks gains a little different:
% 125, 65, 118.944255, 0.186261
% 250, 59, 239.771898, 0.910003
% 500, 50, 514.606412, 7.243568
% 1000, 39, 1099.433179, 31.608529
% 2000, 29, 2038.873929, 27.242882
% 4000, 17, 4058.881505, 13.865787
% 8000, 3, 8289.882476, 3.574972
results = [
125, 65, 119.007, 0.264
250, 59, 239.791, 0.986
500, 50, 514.613, 7.309
1000, 39, 1099.436, 31.644
2000, 29, 2038.875, 27.214
4000, 17, 4058.882, 13.823
8000, 3, 8289.883, 3.565
];
case 'two_cap'
results = [
125, 65, 119.007, 0.258
250, 59, 239.791, 0.963
500, 50, 514.613, 7.224
1000, 39, 1099.436, 31.373
2000, 29, 2038.875, 26.244
4000, 17, 4058.882, 12.726
8000, 3, 8289.883, 3.212
];
end

% Print data blocks that can be used to update golden test data.
test_cfs = 125 * 2.^(0:6);
Expand Down Expand Up @@ -581,7 +615,6 @@
dB_change, expected_change);
end
end
report_status(status, 'test_whole_carfac')
return


Expand Down Expand Up @@ -672,9 +705,9 @@
c_chord = amplitude * sum(sin(2 * pi * t * freqs), 2);

binaural_audio = [c_chord zeros(size(t))];
CF = CARFAC_Design(2, fs);
CF = CARFAC_Design(2, fs, 'one_cap'); % Legacy
CF = CARFAC_Init(CF);
MONO_CF = CARFAC_Design(1, fs);
MONO_CF = CARFAC_Design(1, fs, 'one_cap'); % Legacy
MONO_CF = CARFAC_Init(MONO_CF);
[naps, CF, bm_baseline] = CARFAC_Run_Segment(CF, binaural_audio);
[mono_naps, MONO_CF, mono_bm_baseline] = CARFAC_Run_Segment(MONO_CF, c_chord);
Expand Down Expand Up @@ -734,7 +767,7 @@
amplitude = 1e-4; % -80 dBFS, around 20 or 30 dB SPL
noise = amplitude * randn(size(t));
binaural_noise = [noise noise];
CF = CARFAC_Design(2, fs);
CF = CARFAC_Design(2, fs, 'one_cap'); % Legacy
CF = CARFAC_Init(CF);
[naps, CF, bm_baseline] = CARFAC_Run_Segment(CF, binaural_noise);
ear_one_naps = naps(:, :, 1);
Expand All @@ -758,7 +791,7 @@
amplitude = 1e-4; % -80 dBFS, around 20 or 30 dB SPL
noise = amplitude * randn(size(t));

CF = CARFAC_Design(1, fs);
CF = CARFAC_Design(1, fs, 'one_cap'); % Legacy
CF = CARFAC_Init(CF);
[~, CF, bm_baseline] = CARFAC_Run_Segment(CF, noise);

Expand Down
34 changes: 34 additions & 0 deletions matlab/RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# CARFAC v2 Release Notes

The update we refer to as CARFAC v2 (or as lyon2023, as opposed to lyon2011, in the Auditory Model Toolbox adaptation) includes the following changes in the Matlab version (and corresponding changes in the new Numpy and Jax versions, but not yet in the C++):

1. High-pass filter at BM output.
> We moved the 20 Hz highpass filter from IHC to CAR, to roughly model helicotrema reflection that shorts out DC BM response. There is still a fair amount of low-frequency quadratic distortion in the BM output, but not DC. The IHC output (the NAP) is not affected by this change, but the BM output is: recomputing the book's Figure 17.7 shows that the stripe at 0 frequency disappears.
2. Bug fix: stop parameter changes in open-loop mode.
> Previously, if the mode was changed to open-loop while the model was ramping the AGC feedback-controlled filter parameters, the ramping that was intended to interpolate to the new value would continue ramping, extrapolating beyond the intended value. With this bug fix, the ramping is stopped (when calling CARFAC_Run_Segment) in open-loop mode.
3. Linear CAR option.
> Optionally make the OHC nonlinear function just a 1, to make the cascade of asymmetric resonators (CAR) be a linear filterbank, non-distorting if open loop, e.g. for characterizing transfer functions in tests. If the CAR is not run open loop, there will still be parameter adaptation, yielding compression and some odd-order distortion.
4. IHC model change to support receptor potential output.
> We removed the previous (not used by default) two-capacitor inner hair cell (IHC) model, and replaced it by a new one that allows interpreting one of the capacitor voltages as a receptor potential. This new two-cap version is the default; it results in rather minor differences in the IHC output (including a somewhat reduced peak-to-average ratio in the NAP output), and via AGC feedback also very tiny differences in the BM output.
5. Interface for selecting IHC model type.
> The function CARFAC_Design now takes an optional last arg, keyword 'one_cap' or 'just_hwr' to change from the default 'two_cap' inner hair cell model. This makes it easier to get back to the old one-cap IHC model if desired, and easier to make tests that compare them.
6. Delay per stage for better parallelization.
> We optionally include an extra sample delay per stage, to make the CAR step parallelize better, avoiding the "ripple" iteration. Off by default, but a useful option for speeding up execution on parallel architectures.
7. Simplification of stage-gain computation.
> The stage-gain update required evaluation of a ratio of polynomials; it has been replaced by a quadratic polynomial approximation, somewhat more accurate than the one suggested in the book Figure 16.3.
8. AGC spatial smoothing simplification.
> We simplified the AGC spatial smoothing to just the 1-iteration 3-tap spatial FIR filter structure, which was the default and is the one illustrated in the book Figure 19.6. If more spatial smoothing is requested than this filter can handle, the AGC decimation factor will be reduced instead of changing the filter architecture. This simplifies ports to FPGA, Jax, etc.
9. Outer hair cell health.
> Provision for modeling sensorineural hearing loss via the OHC_health parameter vector (a health value between 0 and 1 per channel, multiplying the relative undamping, defaulting to 1).
10. Extensive tests.
> We have added test routines to the open-source code. We have used these to help us keep the Matlab, Numpy, and Jax versions synchronized, and to facilitate updating the C++ version as well, if someone wants to work on that.

0 comments on commit 3d53aab

Please sign in to comment.