From 3d53aabdc81a7e5d5932b14334dc58e2306f7c40 Mon Sep 17 00:00:00 2001 From: CARFAC Team Date: Sat, 11 Nov 2023 08:28:49 -0800 Subject: [PATCH] In CARFAC_Design, make IHC default to two_cap for v2, and improve some doc strings. Also add RELEASE_NOTES.md to briefly document the changes for v2. PiperOrigin-RevId: 581548755 --- matlab/CARFAC_Design.m | 79 ++++++++++++++++++++++++++++++++++------- matlab/CARFAC_Test.m | 67 +++++++++++++++++++++++++--------- matlab/RELEASE_NOTES.md | 34 ++++++++++++++++++ 3 files changed, 151 insertions(+), 29 deletions(-) create mode 100644 matlab/RELEASE_NOTES.md diff --git a/matlab/CARFAC_Design.m b/matlab/CARFAC_Design.m index 830d613..4342499 100644 --- a/matlab/CARFAC_Design.m +++ b/matlab/CARFAC_Design.m @@ -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 @@ -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), ... @@ -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 @@ -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); @@ -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; @@ -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 diff --git a/matlab/CARFAC_Test.m b/matlab/CARFAC_Test.m index 5415c00..8001072 100644 --- a/matlab/CARFAC_Test.m +++ b/matlab/CARFAC_Test.m @@ -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); @@ -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. @@ -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. @@ -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); @@ -581,7 +615,6 @@ dB_change, expected_change); end end -report_status(status, 'test_whole_carfac') return @@ -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); @@ -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); @@ -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); diff --git a/matlab/RELEASE_NOTES.md b/matlab/RELEASE_NOTES.md new file mode 100644 index 0000000..a0bb112 --- /dev/null +++ b/matlab/RELEASE_NOTES.md @@ -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. +