ronw@642
|
1 // Copyright 2013, Google, Inc.
|
ronw@642
|
2 // Author: Ron Weiss <ronw@google.com>
|
ronw@642
|
3 //
|
ronw@642
|
4 // This C++ file is part of an implementation of Lyon's cochlear model:
|
ronw@642
|
5 // "Cascade of Asymmetric Resonators with Fast-Acting Compression"
|
ronw@642
|
6 // to supplement Lyon's upcoming book "Human and Machine Hearing"
|
ronw@642
|
7 //
|
ronw@642
|
8 // Licensed under the Apache License, Version 2.0 (the "License");
|
ronw@642
|
9 // you may not use this file except in compliance with the License.
|
ronw@642
|
10 // You may obtain a copy of the License at
|
ronw@642
|
11 //
|
ronw@642
|
12 // http://www.apache.org/licenses/LICENSE-2.0
|
ronw@642
|
13 //
|
ronw@642
|
14 // Unless required by applicable law or agreed to in writing, software
|
ronw@642
|
15 // distributed under the License is distributed on an "AS IS" BASIS,
|
ronw@642
|
16 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
ronw@642
|
17 // See the License for the specific language governing permissions and
|
ronw@642
|
18 // limitations under the License.
|
ronw@642
|
19
|
ronw@647
|
20 #include "sai.h"
|
ronw@647
|
21
|
ronw@642
|
22 #include <assert.h>
|
ronw@642
|
23
|
ronw@642
|
24 SAI::SAI(const SAIParams& params) : params_(params) {
|
ronw@650
|
25 Redesign(params);
|
ronw@650
|
26 }
|
ronw@650
|
27
|
ronw@650
|
28 void SAI::Redesign(const SAIParams& params) {
|
ronw@642
|
29 assert(params_.window_width > params_.width &&
|
ronw@642
|
30 "SAI window_width must be larger than width.");
|
ronw@642
|
31
|
ronw@642
|
32 int buffer_width = params_.width +
|
ronw@642
|
33 static_cast<int>((1 + static_cast<float>(params_.n_window_pos - 1)/2) *
|
ronw@642
|
34 params_.window_width);
|
ronw@642
|
35 input_buffer_.setZero(params_.n_ch, buffer_width);
|
ronw@642
|
36 output_buffer_.setZero(params_.n_ch, params_.width);
|
ronw@642
|
37
|
ronw@642
|
38 window_.setLinSpaced(params_.window_width, kPi / params_.window_width, kPi)
|
ronw@642
|
39 .sin();
|
ronw@642
|
40 }
|
ronw@642
|
41
|
ronw@650
|
42 void SAI::Reset() {
|
ronw@650
|
43 input_buffer_.setZero();
|
ronw@650
|
44 output_buffer_.setZero();
|
ronw@650
|
45 }
|
ronw@650
|
46
|
alexbrandmeyer@643
|
47 void SAI::RunSegment(const std::vector<ArrayX>& input,
|
alexbrandmeyer@643
|
48 ArrayXX* output_frame) {
|
ronw@642
|
49 assert(!input.empty() || input.size() <= params_.window_width &&
|
ronw@642
|
50 "Unexpected input size.");
|
ronw@642
|
51 assert(input[0].size() == params_.n_ch &&
|
ronw@642
|
52 "Unexpected input frame size.");
|
ronw@642
|
53
|
ronw@642
|
54 // Append new data to the input buffer.
|
ronw@642
|
55 int n_shift = input.size();
|
ronw@642
|
56 int shift_width = input_buffer_.cols() - n_shift;
|
ronw@642
|
57 input_buffer_.topLeftCorner(params_.n_ch, shift_width).swap(
|
ronw@642
|
58 input_buffer_.block(0, n_shift, params_.n_ch, shift_width));
|
ronw@642
|
59 for (int i = 0; i < input.size(); ++i) {
|
ronw@642
|
60 input_buffer_.block(0, shift_width + i, input[i].size(), 1) = input[i];
|
ronw@642
|
61 }
|
ronw@642
|
62 // Zero-pad the buffer if necessary.
|
ronw@642
|
63 if (input.size() < params_.window_width) {
|
ronw@642
|
64 int pad_width = params_.window_width - input.size();
|
ronw@642
|
65 input_buffer_.topRightCorner(params_.n_ch, pad_width).setZero();
|
ronw@642
|
66 }
|
ronw@642
|
67
|
ronw@642
|
68 StabilizeSegment(input_buffer_, &output_buffer_);
|
ronw@642
|
69 *output_frame = output_buffer_;
|
ronw@642
|
70 }
|
ronw@642
|
71
|
alexbrandmeyer@643
|
72 void SAI::StabilizeSegment(const ArrayXX& input_buffer,
|
alexbrandmeyer@643
|
73 ArrayXX* output_buffer) const {
|
ronw@642
|
74 // Windows are always approximately 50% overlapped.
|
ronw@642
|
75 float window_hop = params_.window_width / 2;
|
ronw@642
|
76 int window_start = (input_buffer.cols() - params_.window_width) -
|
ronw@642
|
77 (params_.n_window_pos - 1) * window_hop;
|
ronw@642
|
78 int window_range_start = window_start - params_.future_lags - 1;
|
ronw@642
|
79 int offset_range_start = window_start - params_.width;
|
ronw@642
|
80 assert(offset_range_start >= 0);
|
ronw@642
|
81 for (int i = 0; i < params_.n_ch; ++i) {
|
ronw@642
|
82 // TODO(ronw): Rename this here and in the Matlab code since the
|
ronw@642
|
83 // input doesn't have to contain naps.
|
alexbrandmeyer@643
|
84 const ArrayX& nap_wave = input_buffer.row(i);
|
ronw@642
|
85 // TODO(ronw): Smooth row.
|
ronw@642
|
86
|
ronw@642
|
87 for (int w = 0; w < params_.n_window_pos; ++w) {
|
ronw@642
|
88 int current_window_offset = w * window_hop;
|
ronw@642
|
89 // Choose a trigger point.
|
ronw@642
|
90 int trigger_time;
|
alexbrandmeyer@643
|
91 const ArrayX& trigger_window =
|
ronw@642
|
92 nap_wave.segment(window_range_start + current_window_offset,
|
ronw@642
|
93 params_.window_width);
|
ronw@642
|
94 FPType peak_val = (trigger_window * window_).maxCoeff(&trigger_time);
|
ronw@642
|
95 if (peak_val <= 0) {
|
ronw@642
|
96 peak_val = window_.maxCoeff(&trigger_time);
|
ronw@642
|
97 }
|
ronw@642
|
98 trigger_time += current_window_offset;
|
ronw@642
|
99
|
ronw@642
|
100 // Blend the window following the trigger into the output
|
ronw@642
|
101 // buffer, weighted according to the the trigger strength (0.05
|
ronw@642
|
102 // to near 1.0).
|
ronw@642
|
103 FPType alpha = (0.025 + peak_val) / (0.5 + peak_val);
|
ronw@642
|
104 output_buffer->row(i) *= 1 - alpha;
|
ronw@642
|
105 output_buffer->row(i) += alpha *
|
ronw@642
|
106 nap_wave.segment(trigger_time + offset_range_start, params_.width);
|
ronw@642
|
107 }
|
ronw@642
|
108 }
|
ronw@642
|
109 }
|