annotate trunk/matlab/bmm/carfac/SAI_DesignLayers.m @ 615:2b2ef398b557

Add files for making log-lag SAI from CARFAC's NAP output. The file SAI_RunLayered.m dumps frames to PNG files. The hacking script calls ffmpeg to assemble them with the soundtrack into a movie.
author dicklyon@google.com
date Thu, 09 May 2013 03:48:44 +0000
parents
children 2e456754fe20
rev   line source
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@615 20 function [layer_array, total_width] = SAI_DesignLayers( ...
dicklyon@615 21 n_layers, width_per_layer)
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
dicklyon@615 45 % Elevate these to a param struct?
dicklyon@615 46 if nargin < 1
dicklyon@615 47 n_layers = 11
dicklyon@615 48 end
dicklyon@615 49 if nargin < 2
dicklyon@615 50 width_per_layer = 32; % resolution "half life" in space
dicklyon@615 51 end
dicklyon@615 52 future_lags = 3 * width_per_layer;
dicklyon@615 53 width_first_layer = future_lags + 2 * width_per_layer;
dicklyon@615 54 width_extra_last_layer = 2 * width_per_layer;
dicklyon@615 55 left_overlap = 15;
dicklyon@615 56 right_overlap = 15;
dicklyon@615 57 first_window_width = 400; % or maybe use seglen? or 0.020 * fs?
dicklyon@615 58 min_window_width = 2*width_per_layer; % or somewhere on that order
dicklyon@615 59 window_exponent = 1.4;
dicklyon@615 60 alpha_max = 0.5;
dicklyon@615 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@615 69 ones(1, width_first_layer)];
dicklyon@615 70
dicklyon@615 71 % Each layer needs a lag_warp for a portion of that, divided by
dicklyon@615 72 % 2^(layer-1), where the portion includes some overlap into its neighbors
dicklyon@615 73 % with higher layer numbers on left, lower on right.
dicklyon@615 74
dicklyon@615 75 % Layer 1, rightmost, representing recent, current and near-future (negative
dicklyon@615 76 % lag) relative to trigger time, has 1 NAP sample per SAI sample. Other
dicklyon@615 77 % layers map more than one NAP sample into 1 SAI sample. Layer 2 is
dicklyon@615 78 % computed as 2X decimated, 2 NAP samples per SAI sample, but then gets
dicklyon@615 79 % interpolated to between 1 and 2 (and outside that range in the overlap
dicklyon@615 80 % regions) to connect up smoothly. Each layer is another 2X decimated.
dicklyon@615 81 % The last layer limits out at 1 (representing 2^(n_layers) SAI samples)
dicklyon@615 82 % at the width_extra_last_layer SAI samples that extend to the far past.
dicklyon@615 83
dicklyon@615 84 layer_array = []; % to hold a struct array
dicklyon@615 85 for layer = 1:n_layers
dicklyon@615 86 layer_array(layer).width = width_per_layer;
dicklyon@615 87 layer_array(layer).left_overlap = left_overlap;
dicklyon@615 88 layer_array(layer).right_overlap = right_overlap;
dicklyon@615 89 layer_array(layer).future_lags = 0;
dicklyon@615 90 % Layer decimation factors: 1 1 1 1 2 2 2 4 4 4 8 ...
dicklyon@615 91 layer_array(layer).update_interval = max(1, 2 ^ floor((layer - 2) / 3));
dicklyon@615 92 end
dicklyon@615 93 % Patch up the exceptions.
dicklyon@615 94 layer_array(1).width = width_first_layer;
dicklyon@615 95 layer_array(end).width = layer_array(end).width + width_extra_last_layer;
dicklyon@615 96 layer_array(1).right_overlap = 0;
dicklyon@615 97 layer_array(end).left_overlap = 0;
dicklyon@615 98 layer_array(1).future_lags = future_lags;
dicklyon@615 99
dicklyon@615 100 % For each layer, working backwards, from left, find the locations they
dicklyon@615 101 % they render into in the final SAI.
dicklyon@615 102 offset = 0;
dicklyon@615 103 for layer = n_layers:-1:1
dicklyon@615 104 width = layer_array(layer).width;
dicklyon@615 105 left = layer_array(layer).left_overlap;
dicklyon@615 106 right = layer_array(layer).right_overlap;
dicklyon@615 107
dicklyon@615 108 % Size of the vectors needed.
dicklyon@615 109 n_final_lags = left + width + right;
dicklyon@615 110 layer_array(layer).n_final_lags = n_final_lags;
dicklyon@615 111
dicklyon@615 112 % Integer indices into the final composite SAI for this layer.
dicklyon@615 113 target_indices = ((1 - left):(width + right)) + offset;
dicklyon@615 114 layer_array(layer).target_indices = target_indices;
dicklyon@615 115
dicklyon@615 116 % Make a blending coefficient alpha, ramped in the overlap zone.
dicklyon@615 117 alpha = ones(1, n_final_lags);
dicklyon@615 118 alpha(1:left) = alpha(1:left) .* (1:left)/(left + 1);
dicklyon@615 119 alpha(end + 1 - (1:right)) = ...
dicklyon@615 120 alpha(end + 1 - (1:right)) .* (1:right)/(right + 1);
dicklyon@615 121 layer_array(layer).alpha = alpha * alpha_max;
dicklyon@615 122
dicklyon@615 123 offset = offset + width; % total width from left through this layer.
dicklyon@615 124 end
dicklyon@615 125 total_width = offset; % Return size of SAI this will make.
dicklyon@615 126
dicklyon@615 127 % for each layer, fill in its lag-resampling function for interp1:
dicklyon@615 128 for layer = 1:n_layers
dicklyon@615 129 width = layer_array(layer).width;
dicklyon@615 130 left = layer_array(layer).left_overlap;
dicklyon@615 131 right = layer_array(layer).right_overlap;
dicklyon@615 132
dicklyon@615 133 % Still need to adjust this to make lags match at edges:
dicklyon@615 134 target_indices = layer_array(layer).target_indices;
dicklyon@615 135 samples_per = NAP_samples_per_SAI_sample(target_indices);
dicklyon@615 136 % Accumulate lag backwards from the zero-lag point, convert to units of
dicklyon@615 137 % samples in the current layer.
dicklyon@615 138 lag_curve = (cumsum(samples_per(end:-1:1))) / 2^(layer-1);
dicklyon@615 139 lag_curve = lag_curve(end:-1:1); % Turn it back to corrent order.
dicklyon@615 140 % Now adjust it to match the zero-lag point or a lag-point from
dicklyon@615 141 % previous layer, and reverse it back into place.
dicklyon@615 142 if layer == 1
dicklyon@615 143 lag_adjust = lag_curve(end) - 0;
dicklyon@615 144 else
dicklyon@615 145 % Align right edge to previous layer's left edge, adjusting for 2X
dicklyon@615 146 % scaling factor difference.
dicklyon@615 147 lag_adjust = lag_curve(end - right) - last_left_lag / 2;
dicklyon@615 148 end
dicklyon@615 149 lag_curve = lag_curve - lag_adjust;
dicklyon@615 150 % lag_curve is now offsets from right end of layer's frame.
dicklyon@615 151 layer_array(layer).lag_curve = lag_curve;
dicklyon@615 152 % Specify number of point to generate in pre-warp frame.
dicklyon@615 153 layer_array(layer).frame_width = ceil(1 + lag_curve(1));
dicklyon@615 154 if layer < n_layers % to avoid the left = 0 unused end case.
dicklyon@615 155 % A point to align next layer to.
dicklyon@615 156 last_left_lag = lag_curve(left) - layer_array(layer).future_lags;
dicklyon@615 157 end
dicklyon@615 158
dicklyon@615 159 % Specify a good window width (in history buffer, for picking triggers)
dicklyon@615 160 % in samples for this layer, exponentially approaching minimum.
dicklyon@615 161 layer_array(layer).window_width = round(min_window_width + ...
dicklyon@615 162 first_window_width / window_exponent^(layer - 1));
dicklyon@615 163
dicklyon@615 164 % Say about how long the history buffer needs to be to shift any trigger
dicklyon@615 165 % location in the range of the window to a fixed location. Assume
dicklyon@615 166 % using two window placements overlapped 50%.
dicklyon@615 167 n_triggers = 2;
dicklyon@615 168 layer_array(layer).buffer_width = layer_array(layer).frame_width + ...
dicklyon@615 169 ceil((1 + (n_triggers - 1)/2) * layer_array(layer).window_width);
dicklyon@615 170 end
dicklyon@615 171
dicklyon@615 172 return
dicklyon@615 173