Mercurial > hg > aimc
changeset 684:49af9a8d5a53
Initial translation of SAI code to C++.
This only implements the simple (as opposed to the layered) SAI, as
implemented in SAI_Run.m.
author | ronw@google.com |
---|---|
date | Fri, 31 May 2013 21:46:48 +0000 |
parents | 66688b9f8853 |
children | d0612798f6de |
files | trunk/carfac/SConstruct trunk/carfac/carfac_common.h trunk/carfac/carfac_test.cc trunk/carfac/sai.cc trunk/carfac/sai.h trunk/carfac/sai_test.cc |
diffstat | 6 files changed, 301 insertions(+), 19 deletions(-) [+] |
line wrap: on
line diff
--- a/trunk/carfac/SConstruct Wed May 29 20:33:06 2013 +0000 +++ b/trunk/carfac/SConstruct Fri May 31 21:46:48 2013 +0000 @@ -43,22 +43,6 @@ import commands import os -carfac_sources = [ - 'carfac_common.cc', - 'agc_params.h', - 'agc_coeffs.h', - 'agc_state.h', - 'carfac_output.cc', - 'car_params.h', - 'car_coeffs.h', - 'ihc_params.h', - 'car_state.h', - 'ihc_coeffs.h', - 'ihc_state.h', - 'ear.cc', - 'carfac.cc' - ] - env = Environment(CPPPATH=[os.environ['EIGEN_PATH']]) GCC_VERSION = commands.getoutput(env['CXX'] + ' -dumpversion') if GCC_VERSION.startswith('4.6'): @@ -66,6 +50,22 @@ else: env.MergeFlags(['-std=c++11']) +carfac_sources = [ + 'agc_coeffs.h', + 'agc_params.h', + 'agc_state.h', + 'car_coeffs.h', + 'carfac.cc', + 'carfac_common.cc', + 'carfac_output.cc', + 'car_params.h', + 'car_state.h', + 'ear.cc', + 'ihc_coeffs.h', + 'ihc_params.h', + 'ihc_state.h', + 'sai.cc', + ] env.Library(target = 'carfac', source = carfac_sources) env.Command('tmp/libgtest.a', [], @@ -75,8 +75,12 @@ 'cd tmp && cmake . && make', ]) -test_program = env.Program(target = 'carfac_test', - source = ['carfac_test.cc'], +test_sources = [ + 'carfac_test.cc', + 'sai_test.cc', + ] +test_program = env.Program(target = 'test', + source = test_sources, LIBS = ['carfac', 'gtest', 'gtest_main', 'pthread'], LIBPATH = ['.', 'tmp']) test_alias = Alias('test', [test_program], test_program[0].abspath)
--- a/trunk/carfac/carfac_common.h Wed May 29 20:33:06 2013 +0000 +++ b/trunk/carfac/carfac_common.h Fri May 31 21:46:48 2013 +0000 @@ -60,6 +60,7 @@ // A typedef is used to define a one-dimensional Eigen array with the same // precision level as FPType. typedef Eigen::Array<FPType, Dynamic, 1> FloatArray; +typedef Eigen::Array<FPType, Dynamic, Dynamic> Float2dArray; // A fixed value of PI is defined throughout the project. static const FPType kPi = 3.141592653589793238;
--- a/trunk/carfac/carfac_test.cc Wed May 29 20:33:06 2013 +0000 +++ b/trunk/carfac/carfac_test.cc Fri May 31 21:46:48 2013 +0000 @@ -23,7 +23,8 @@ #include <string> #include <fstream> #include <vector> -#include <gtest/gtest.h> + +#include "gtest/gtest.h" #include "car_params.h" #include "ihc_params.h" #include "agc_params.h"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/trunk/carfac/sai.cc Fri May 31 21:46:48 2013 +0000 @@ -0,0 +1,100 @@ +// Copyright 2013, Google, Inc. +// Author: Ron Weiss <ronw@google.com> +// +// This C++ file is part of an implementation of Lyon's cochlear model: +// "Cascade of Asymmetric Resonators with Fast-Acting Compression" +// to supplement Lyon's upcoming book "Human and Machine Hearing" +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <assert.h> + +#include "sai.h" + +SAI::SAI(const SAIParams& params) : params_(params) { + assert(params_.window_width > params_.width && + "SAI window_width must be larger than width."); + + int buffer_width = params_.width + + static_cast<int>((1 + static_cast<float>(params_.n_window_pos - 1)/2) * + params_.window_width); + input_buffer_.setZero(params_.n_ch, buffer_width); + output_buffer_.setZero(params_.n_ch, params_.width); + + window_.setLinSpaced(params_.window_width, kPi / params_.window_width, kPi) + .sin(); +} + +void SAI::RunSegment(const std::vector<FloatArray>& input, + Float2dArray* output_frame) { + assert(!input.empty() || input.size() <= params_.window_width && + "Unexpected input size."); + assert(input[0].size() == params_.n_ch && + "Unexpected input frame size."); + + // Append new data to the input buffer. + int n_shift = input.size(); + int shift_width = input_buffer_.cols() - n_shift; + input_buffer_.topLeftCorner(params_.n_ch, shift_width).swap( + input_buffer_.block(0, n_shift, params_.n_ch, shift_width)); + for (int i = 0; i < input.size(); ++i) { + input_buffer_.block(0, shift_width + i, input[i].size(), 1) = input[i]; + } + // Zero-pad the buffer if necessary. + if (input.size() < params_.window_width) { + int pad_width = params_.window_width - input.size(); + input_buffer_.topRightCorner(params_.n_ch, pad_width).setZero(); + } + + StabilizeSegment(input_buffer_, &output_buffer_); + *output_frame = output_buffer_; +} + +void SAI::StabilizeSegment(const Float2dArray& input_buffer, + Float2dArray* output_buffer) const { + // Windows are always approximately 50% overlapped. + float window_hop = params_.window_width / 2; + int window_start = (input_buffer.cols() - params_.window_width) - + (params_.n_window_pos - 1) * window_hop; + int window_range_start = window_start - params_.future_lags - 1; + int offset_range_start = window_start - params_.width; + assert(offset_range_start >= 0); + for (int i = 0; i < params_.n_ch; ++i) { + // TODO(ronw): Rename this here and in the Matlab code since the + // input doesn't have to contain naps. + const FloatArray& nap_wave = input_buffer.row(i); + // TODO(ronw): Smooth row. + + for (int w = 0; w < params_.n_window_pos; ++w) { + int current_window_offset = w * window_hop; + // Choose a trigger point. + int trigger_time; + const FloatArray& trigger_window = + nap_wave.segment(window_range_start + current_window_offset, + params_.window_width); + FPType peak_val = (trigger_window * window_).maxCoeff(&trigger_time); + if (peak_val <= 0) { + peak_val = window_.maxCoeff(&trigger_time); + } + trigger_time += current_window_offset; + + // Blend the window following the trigger into the output + // buffer, weighted according to the the trigger strength (0.05 + // to near 1.0). + FPType alpha = (0.025 + peak_val) / (0.5 + peak_val); + output_buffer->row(i) *= 1 - alpha; + output_buffer->row(i) += alpha * + nap_wave.segment(trigger_time + offset_range_start, params_.width); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/trunk/carfac/sai.h Fri May 31 21:46:48 2013 +0000 @@ -0,0 +1,81 @@ +// Copyright 2013, Google, Inc. +// Author: Ron Weiss <ronw@google.com> +// +// This C++ file is part of an implementation of Lyon's cochlear model: +// "Cascade of Asymmetric Resonators with Fast-Acting Compression" +// to supplement Lyon's upcoming book "Human and Machine Hearing" +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CARFAC_SAI_H_ +#define CARFAC_SAI_H_ + +#include <vector> + +// TODO(ronw): Rename this file to common.h +#include "carfac_common.h" + +// Design parameters for a single SAI. +struct SAIParams { + // Number of channels (height) of the SAI. + int n_ch; + + // TODO(ronw): Consider parameterizing this as past_lags and + // future_lags, with width == past_lags + 1 + future_lags. + // + // Total width (i.e. number of lag samples) of the SAI. + int width; + // Number of lag samples that should come from the future. + int future_lags; + // Number of windows (triggers) to consider during each SAI frame. + int n_window_pos; + + // TODO(ronw): more carefully define terms "window" and "frame" + + // Size of the window to compute. + int window_width; + + FPType channel_smoothing_scale; +}; + +class SAI { + public: + explicit SAI(const SAIParams& params); + + // Fill output_frame with a params_.n_ch by params_.width SAI frame + // computed from the given input frames. + // + // The input should have dimensionality of params_.window_width by + // params_.n_ch. Inputs containing too few frames are zero-padded. + // FIXME: Float2DArray input type would be less awkward. + void RunSegment(const std::vector<FloatArray>& input, + Float2dArray* output_output_frame); + + private: + // Process successive windows within input_buffer, choose trigger + // points, and blend each window into output_buffer. + void StabilizeSegment(const Float2dArray& input_buffer, + Float2dArray* output_buffer) const; + + SAIParams params_; + // Window function to apply before selecting a trigger point. + // Size: params_.window_width. + FloatArray window_; + // Buffer to store a large enough window of input frames to compute + // a full SAI frame. Size: params_.n_ch by params_.buffer_width. + Float2dArray input_buffer_; + // Output frame buffer. Size: params_.n_ch by params_.width. + Float2dArray output_buffer_; +}; + +#endif // CARFAC_SAI_H_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/trunk/carfac/sai_test.cc Fri May 31 21:46:48 2013 +0000 @@ -0,0 +1,95 @@ +// Copyright 2013, Google, Inc. +// Author: Ron Weiss <ronw@google.com> +// +// This C++ file is part of an implementation of Lyon's cochlear model: +// "Cascade of Asymmetric Resonators with Fast-Acting Compression" +// to supplement Lyon's upcoming book "Human and Machine Hearing" +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "sai.h" + +#include <iostream> +#include <vector> + +#include "gtest/gtest.h" + +using testing::Values; +using std::vector; + +vector<FloatArray> CreateZeroSegment(int n_ch, int length) { + vector<FloatArray> segment; + for (int i = 0; i < length; ++i) { + segment.push_back(FloatArray::Zero(n_ch)); + } + return segment; +} + +bool HasPeakAt(const Float2dArray& frame, int index) { + if (index == 0) { + return frame(index) > frame(index + 1); + } else if (index == frame.size() - 1) { + return frame(index) > frame(index - 1); + } + return frame(index) > frame(index + 1) && frame(index) > frame(index - 1); +} + +class SAIPeriodicInputTest + : public testing::TestWithParam<std::tr1::tuple<int, int>> { + protected: + void SetUp() { + period_ = std::tr1::get<0>(GetParam()); + phase_ = std::tr1::get<1>(GetParam()); + } + + int period_; + int phase_; +}; + +TEST_P(SAIPeriodicInputTest, SingleChannelPulseTrain) { + vector<FloatArray> segment = CreateZeroSegment(1, 38); + for (int i = phase_; i < segment.size(); i += period_) { + segment[i](0) = 1; + } + + SAIParams sai_params; + sai_params.window_width = segment.size(); + sai_params.n_ch = 1; + sai_params.width = 15; + // Half of the SAI should come from the future. + // sai_params.future_lags = sai_params.width / 2; + sai_params.future_lags = 0; + sai_params.n_window_pos = 2; + + SAI sai(sai_params); + Float2dArray sai_frame; + sai.RunSegment(segment, &sai_frame); + + // The output should have peaks at the same positions, regardless of + // input phase. + for (int i = sai_frame.size() - 1; i >= 0 ; i -= period_) { + EXPECT_TRUE(HasPeakAt(sai_frame, i)); + } + + for (int i = 0; i < segment.size(); ++i) { + std::cout << segment[i](0) << " "; + } + std::cout << "\n"; + for (int i = 0; i < sai_frame.size(); ++i) { + std::cout << sai_frame(i) << " "; + } + std::cout << "\n"; +} +INSTANTIATE_TEST_CASE_P(PeriodicInputVariations, SAIPeriodicInputTest, + testing::Combine(Values(25, 10, 5, 2), // periods. + Values(0, 3))); // phases.