dicklyon@615
|
1 % Copyright 2013, Google, Inc.
|
dicklyon@615
|
2 % Author: Richard F. Lyon
|
dicklyon@615
|
3 %
|
dicklyon@615
|
4 % This Matlab file is part of an implementation of Lyon's cochlear model:
|
dicklyon@615
|
5 % "Cascade of Asymmetric Resonators with Fast-Acting Compression"
|
dicklyon@615
|
6 % to supplement Lyon's upcoming book "Human and Machine Hearing"
|
dicklyon@615
|
7 %
|
dicklyon@615
|
8 % Licensed under the Apache License, Version 2.0 (the "License");
|
dicklyon@615
|
9 % you may not use this file except in compliance with the License.
|
dicklyon@615
|
10 % You may obtain a copy of the License at
|
dicklyon@615
|
11 %
|
dicklyon@615
|
12 % http://www.apache.org/licenses/LICENSE-2.0
|
dicklyon@615
|
13 %
|
dicklyon@615
|
14 % Unless required by applicable law or agreed to in writing, software
|
dicklyon@615
|
15 % distributed under the License is distributed on an "AS IS" BASIS,
|
dicklyon@615
|
16 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
dicklyon@615
|
17 % See the License for the specific language governing permissions and
|
dicklyon@615
|
18 % limitations under the License.
|
dicklyon@615
|
19
|
dicklyon@665
|
20 function [layer_array, total_width, lag_period_samples] = SAI_DesignLayers( ...
|
dicklyon@665
|
21 n_layers, width_per_layer, seglen)
|
dicklyon@615
|
22 % function [layer_array, total_width] = SAI_DesignLayers( ...
|
dicklyon@615
|
23 % n_layers, width_per_layer)
|
dicklyon@615
|
24 %
|
dicklyon@615
|
25 % The layer_array is a struct array containing an entry for each layer
|
dicklyon@615
|
26 % in a layer of power-of-2 decimated pieces of SAI that get composited
|
dicklyon@615
|
27 % into a log-lag SAI.
|
dicklyon@615
|
28 % Each struct has the following fields:
|
dicklyon@615
|
29 % .width - number of pixels occupied in the final composite SAI,
|
dicklyon@615
|
30 % not counting the overlap into pixels counted for other layers.
|
dicklyon@615
|
31 % .target_indices - column indices in the final composite SAI,
|
dicklyon@615
|
32 % counting the overlap region(s).
|
dicklyon@615
|
33 % .lag_curve - for each point in the final composite SAI, the float index
|
dicklyon@615
|
34 % in the layer's buffer to interp from.
|
dicklyon@615
|
35 % .alpha - the blending coefficent, mostly 1, tapering toward 0 in the overlap
|
dicklyon@615
|
36 % region(s).
|
dicklyon@615
|
37 % Layer 1 has no overlap to it right, and layer n_layers has none to its
|
dicklyon@615
|
38 % left, but sizes of the target_indices, lag_curve, and alpha vectors are
|
dicklyon@615
|
39 % otherwise width + left_overlap + right_overlap. The total width of the
|
dicklyon@615
|
40 % final composite SAI is the sum of the widths.
|
dicklyon@615
|
41 % Other fields could be added to hold state, such as history buffers for
|
dicklyon@615
|
42 % each layer, or those could go in state struct array...
|
dicklyon@615
|
43
|
dicklyon@615
|
44 % Elevate these to a param struct?
|
dicklyon@615
|
45 if nargin < 1
|
dicklyon@665
|
46 n_layers = 12;
|
dicklyon@615
|
47 end
|
dicklyon@615
|
48 if nargin < 2
|
dicklyon@665
|
49 width_per_layer = 24; % resolution "half life" in space; half-semitones
|
dicklyon@615
|
50 end
|
dicklyon@665
|
51 future_lags = 0 * width_per_layer;
|
dicklyon@665
|
52 width_extra_last_layer = 0 * width_per_layer;
|
dicklyon@665
|
53 left_overlap = 10;
|
dicklyon@665
|
54 right_overlap = 10;
|
dicklyon@665
|
55 first_window_width = seglen; % Less would be a problem.
|
dicklyon@619
|
56 min_window_width = 1*width_per_layer; % or somewhere on that order
|
dicklyon@615
|
57 window_exponent = 1.4;
|
dicklyon@619
|
58 alpha_max = 1;
|
dicklyon@615
|
59
|
dicklyon@665
|
60 width_first_layer = future_lags + 2 * width_per_layer;
|
dicklyon@665
|
61
|
dicklyon@615
|
62 % Start with NAP_samples_per_SAI_sample, declining to 1 from here:
|
dicklyon@615
|
63 max_samples_per = 2^(n_layers - 1);
|
dicklyon@615
|
64 % Construct the overall lag-warping function:
|
dicklyon@615
|
65 NAP_samples_per_SAI_sample = [ ...
|
dicklyon@615
|
66 max_samples_per * ones(1, width_extra_last_layer), ...
|
dicklyon@615
|
67 max_samples_per * ...
|
dicklyon@615
|
68 2 .^ (-(1:(width_per_layer * (n_layers - 1))) / width_per_layer), ...
|
dicklyon@665
|
69 ones(1, width_first_layer), ]; % w/o future for now.
|
dicklyon@665
|
70
|
dicklyon@665
|
71 lag_period_samples = cumsum(NAP_samples_per_SAI_sample(end:-1:1));
|
dicklyon@665
|
72 lag_period_samples = lag_period_samples(end:-1:1); % Put it back in order.
|
dicklyon@665
|
73 lag_period_samples = lag_period_samples - lag_period_samples(end - future_lags);
|
dicklyon@615
|
74
|
dicklyon@615
|
75 % Each layer needs a lag_warp for a portion of that, divided by
|
dicklyon@615
|
76 % 2^(layer-1), where the portion includes some overlap into its neighbors
|
dicklyon@615
|
77 % with higher layer numbers on left, lower on right.
|
dicklyon@615
|
78
|
dicklyon@615
|
79 % Layer 1, rightmost, representing recent, current and near-future (negative
|
dicklyon@615
|
80 % lag) relative to trigger time, has 1 NAP sample per SAI sample. Other
|
dicklyon@615
|
81 % layers map more than one NAP sample into 1 SAI sample. Layer 2 is
|
dicklyon@615
|
82 % computed as 2X decimated, 2 NAP samples per SAI sample, but then gets
|
dicklyon@615
|
83 % interpolated to between 1 and 2 (and outside that range in the overlap
|
dicklyon@615
|
84 % regions) to connect up smoothly. Each layer is another 2X decimated.
|
dicklyon@615
|
85 % The last layer limits out at 1 (representing 2^(n_layers) SAI samples)
|
dicklyon@615
|
86 % at the width_extra_last_layer SAI samples that extend to the far past.
|
dicklyon@615
|
87
|
dicklyon@615
|
88 layer_array = []; % to hold a struct array
|
dicklyon@615
|
89 for layer = 1:n_layers
|
dicklyon@615
|
90 layer_array(layer).width = width_per_layer;
|
dicklyon@615
|
91 layer_array(layer).left_overlap = left_overlap;
|
dicklyon@615
|
92 layer_array(layer).right_overlap = right_overlap;
|
dicklyon@615
|
93 layer_array(layer).future_lags = 0;
|
dicklyon@615
|
94 % Layer decimation factors: 1 1 1 1 2 2 2 4 4 4 8 ...
|
dicklyon@615
|
95 layer_array(layer).update_interval = max(1, 2 ^ floor((layer - 2) / 3));
|
dicklyon@615
|
96 end
|
dicklyon@615
|
97 % Patch up the exceptions.
|
dicklyon@615
|
98 layer_array(1).width = width_first_layer;
|
dicklyon@615
|
99 layer_array(end).width = layer_array(end).width + width_extra_last_layer;
|
dicklyon@615
|
100 layer_array(1).right_overlap = 0;
|
dicklyon@615
|
101 layer_array(end).left_overlap = 0;
|
dicklyon@615
|
102 layer_array(1).future_lags = future_lags;
|
dicklyon@615
|
103
|
dicklyon@615
|
104 % For each layer, working backwards, from left, find the locations they
|
dicklyon@615
|
105 % they render into in the final SAI.
|
dicklyon@615
|
106 offset = 0;
|
dicklyon@615
|
107 for layer = n_layers:-1:1
|
dicklyon@615
|
108 width = layer_array(layer).width;
|
dicklyon@615
|
109 left = layer_array(layer).left_overlap;
|
dicklyon@615
|
110 right = layer_array(layer).right_overlap;
|
dicklyon@615
|
111
|
dicklyon@615
|
112 % Size of the vectors needed.
|
dicklyon@615
|
113 n_final_lags = left + width + right;
|
dicklyon@615
|
114 layer_array(layer).n_final_lags = n_final_lags;
|
dicklyon@615
|
115
|
dicklyon@615
|
116 % Integer indices into the final composite SAI for this layer.
|
dicklyon@615
|
117 target_indices = ((1 - left):(width + right)) + offset;
|
dicklyon@615
|
118 layer_array(layer).target_indices = target_indices;
|
dicklyon@615
|
119
|
dicklyon@615
|
120 % Make a blending coefficient alpha, ramped in the overlap zone.
|
dicklyon@615
|
121 alpha = ones(1, n_final_lags);
|
dicklyon@615
|
122 alpha(1:left) = alpha(1:left) .* (1:left)/(left + 1);
|
dicklyon@615
|
123 alpha(end + 1 - (1:right)) = ...
|
dicklyon@615
|
124 alpha(end + 1 - (1:right)) .* (1:right)/(right + 1);
|
dicklyon@615
|
125 layer_array(layer).alpha = alpha * alpha_max;
|
dicklyon@615
|
126
|
dicklyon@615
|
127 offset = offset + width; % total width from left through this layer.
|
dicklyon@619
|
128
|
dicklyon@619
|
129 % Smooth across channels a little before picking triggers:
|
dicklyon@619
|
130 layer_array(layer).channel_smoothing_scale = 0.25*(layer-1);
|
dicklyon@615
|
131 end
|
dicklyon@615
|
132 total_width = offset; % Return size of SAI this will make.
|
dicklyon@615
|
133
|
dicklyon@615
|
134 % for each layer, fill in its lag-resampling function for interp1:
|
dicklyon@615
|
135 for layer = 1:n_layers
|
dicklyon@615
|
136 width = layer_array(layer).width;
|
dicklyon@615
|
137 left = layer_array(layer).left_overlap;
|
dicklyon@615
|
138 right = layer_array(layer).right_overlap;
|
dicklyon@615
|
139
|
dicklyon@615
|
140 % Still need to adjust this to make lags match at edges:
|
dicklyon@615
|
141 target_indices = layer_array(layer).target_indices;
|
dicklyon@615
|
142 samples_per = NAP_samples_per_SAI_sample(target_indices);
|
dicklyon@615
|
143 % Accumulate lag backwards from the zero-lag point, convert to units of
|
dicklyon@615
|
144 % samples in the current layer.
|
dicklyon@615
|
145 lag_curve = (cumsum(samples_per(end:-1:1))) / 2^(layer-1);
|
dicklyon@615
|
146 lag_curve = lag_curve(end:-1:1); % Turn it back to corrent order.
|
dicklyon@615
|
147 % Now adjust it to match the zero-lag point or a lag-point from
|
dicklyon@615
|
148 % previous layer, and reverse it back into place.
|
dicklyon@615
|
149 if layer == 1
|
dicklyon@615
|
150 lag_adjust = lag_curve(end) - 0;
|
dicklyon@615
|
151 else
|
dicklyon@615
|
152 % Align right edge to previous layer's left edge, adjusting for 2X
|
dicklyon@615
|
153 % scaling factor difference.
|
dicklyon@615
|
154 lag_adjust = lag_curve(end - right) - last_left_lag / 2;
|
dicklyon@615
|
155 end
|
dicklyon@615
|
156 lag_curve = lag_curve - lag_adjust;
|
dicklyon@615
|
157 % lag_curve is now offsets from right end of layer's frame.
|
dicklyon@615
|
158 layer_array(layer).lag_curve = lag_curve;
|
dicklyon@615
|
159 % Specify number of point to generate in pre-warp frame.
|
dicklyon@615
|
160 layer_array(layer).frame_width = ceil(1 + lag_curve(1));
|
dicklyon@615
|
161 if layer < n_layers % to avoid the left = 0 unused end case.
|
dicklyon@615
|
162 % A point to align next layer to.
|
dicklyon@615
|
163 last_left_lag = lag_curve(left) - layer_array(layer).future_lags;
|
dicklyon@615
|
164 end
|
dicklyon@615
|
165
|
dicklyon@615
|
166 % Specify a good window width (in history buffer, for picking triggers)
|
dicklyon@615
|
167 % in samples for this layer, exponentially approaching minimum.
|
dicklyon@615
|
168 layer_array(layer).window_width = round(min_window_width + ...
|
dicklyon@615
|
169 first_window_width / window_exponent^(layer - 1));
|
dicklyon@615
|
170
|
dicklyon@615
|
171 % Say about how long the history buffer needs to be to shift any trigger
|
dicklyon@615
|
172 % location in the range of the window to a fixed location. Assume
|
dicklyon@615
|
173 % using two window placements overlapped 50%.
|
dicklyon@615
|
174 n_triggers = 2;
|
dicklyon@619
|
175 layer_array(layer).n_window_pos = n_triggers;
|
dicklyon@615
|
176 layer_array(layer).buffer_width = layer_array(layer).frame_width + ...
|
dicklyon@619
|
177 floor((1 + (n_triggers - 1)/2) * layer_array(layer).window_width);
|
dicklyon@665
|
178 % Make sure it's big enough for next layer to shift in what it wants.
|
dicklyon@665
|
179 n_shift = ceil(seglen / (2.0^(layer - 1)));
|
dicklyon@665
|
180 if layer_array(layer).buffer_width < 6 + n_shift;
|
dicklyon@665
|
181 layer_array(layer).buffer_width = 6 + n_shift;
|
dicklyon@665
|
182 end
|
dicklyon@615
|
183 end
|
dicklyon@615
|
184
|
dicklyon@615
|
185 return
|
dicklyon@615
|
186
|