ronw@642: // Copyright 2013, Google, Inc. ronw@642: // Author: Ron Weiss ronw@642: // ronw@642: // This C++ file is part of an implementation of Lyon's cochlear model: ronw@642: // "Cascade of Asymmetric Resonators with Fast-Acting Compression" ronw@642: // to supplement Lyon's upcoming book "Human and Machine Hearing" ronw@642: // ronw@642: // Licensed under the Apache License, Version 2.0 (the "License"); ronw@642: // you may not use this file except in compliance with the License. ronw@642: // You may obtain a copy of the License at ronw@642: // ronw@642: // http://www.apache.org/licenses/LICENSE-2.0 ronw@642: // ronw@642: // Unless required by applicable law or agreed to in writing, software ronw@642: // distributed under the License is distributed on an "AS IS" BASIS, ronw@642: // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ronw@642: // See the License for the specific language governing permissions and ronw@642: // limitations under the License. ronw@642: ronw@642: #include ronw@642: ronw@642: #include "sai.h" ronw@642: ronw@642: SAI::SAI(const SAIParams& params) : params_(params) { ronw@642: assert(params_.window_width > params_.width && ronw@642: "SAI window_width must be larger than width."); ronw@642: ronw@642: int buffer_width = params_.width + ronw@642: static_cast((1 + static_cast(params_.n_window_pos - 1)/2) * ronw@642: params_.window_width); ronw@642: input_buffer_.setZero(params_.n_ch, buffer_width); ronw@642: output_buffer_.setZero(params_.n_ch, params_.width); ronw@642: ronw@642: window_.setLinSpaced(params_.window_width, kPi / params_.window_width, kPi) ronw@642: .sin(); ronw@642: } ronw@642: alexbrandmeyer@643: void SAI::RunSegment(const std::vector& input, alexbrandmeyer@643: ArrayXX* output_frame) { ronw@642: assert(!input.empty() || input.size() <= params_.window_width && ronw@642: "Unexpected input size."); ronw@642: assert(input[0].size() == params_.n_ch && ronw@642: "Unexpected input frame size."); ronw@642: ronw@642: // Append new data to the input buffer. ronw@642: int n_shift = input.size(); ronw@642: int shift_width = input_buffer_.cols() - n_shift; ronw@642: input_buffer_.topLeftCorner(params_.n_ch, shift_width).swap( ronw@642: input_buffer_.block(0, n_shift, params_.n_ch, shift_width)); ronw@642: for (int i = 0; i < input.size(); ++i) { ronw@642: input_buffer_.block(0, shift_width + i, input[i].size(), 1) = input[i]; ronw@642: } ronw@642: // Zero-pad the buffer if necessary. ronw@642: if (input.size() < params_.window_width) { ronw@642: int pad_width = params_.window_width - input.size(); ronw@642: input_buffer_.topRightCorner(params_.n_ch, pad_width).setZero(); ronw@642: } ronw@642: ronw@642: StabilizeSegment(input_buffer_, &output_buffer_); ronw@642: *output_frame = output_buffer_; ronw@642: } ronw@642: alexbrandmeyer@643: void SAI::StabilizeSegment(const ArrayXX& input_buffer, alexbrandmeyer@643: ArrayXX* output_buffer) const { ronw@642: // Windows are always approximately 50% overlapped. ronw@642: float window_hop = params_.window_width / 2; ronw@642: int window_start = (input_buffer.cols() - params_.window_width) - ronw@642: (params_.n_window_pos - 1) * window_hop; ronw@642: int window_range_start = window_start - params_.future_lags - 1; ronw@642: int offset_range_start = window_start - params_.width; ronw@642: assert(offset_range_start >= 0); ronw@642: for (int i = 0; i < params_.n_ch; ++i) { ronw@642: // TODO(ronw): Rename this here and in the Matlab code since the ronw@642: // input doesn't have to contain naps. alexbrandmeyer@643: const ArrayX& nap_wave = input_buffer.row(i); ronw@642: // TODO(ronw): Smooth row. ronw@642: ronw@642: for (int w = 0; w < params_.n_window_pos; ++w) { ronw@642: int current_window_offset = w * window_hop; ronw@642: // Choose a trigger point. ronw@642: int trigger_time; alexbrandmeyer@643: const ArrayX& trigger_window = ronw@642: nap_wave.segment(window_range_start + current_window_offset, ronw@642: params_.window_width); ronw@642: FPType peak_val = (trigger_window * window_).maxCoeff(&trigger_time); ronw@642: if (peak_val <= 0) { ronw@642: peak_val = window_.maxCoeff(&trigger_time); ronw@642: } ronw@642: trigger_time += current_window_offset; ronw@642: ronw@642: // Blend the window following the trigger into the output ronw@642: // buffer, weighted according to the the trigger strength (0.05 ronw@642: // to near 1.0). ronw@642: FPType alpha = (0.025 + peak_val) / (0.5 + peak_val); ronw@642: output_buffer->row(i) *= 1 - alpha; ronw@642: output_buffer->row(i) += alpha * ronw@642: nap_wave.segment(trigger_time + offset_range_start, params_.width); ronw@642: } ronw@642: } ronw@642: }