Revision: 291
Author:
ro...@google.com
Date: Fri May 31 14:46:48 2013
Log: Initial translation of SAI code to C++.
This only implements the simple (as opposed to the layered) SAI, as
implemented in SAI_Run.m.
http://code.google.com/p/aimc/source/detail?r=291
Added:
/trunk/carfac/sai.cc
/trunk/carfac/sai.h
/trunk/carfac/sai_test.cc
Modified:
/trunk/carfac/SConstruct
/trunk/carfac/carfac_common.h
/trunk/carfac/carfac_test.cc
=======================================
--- /dev/null
+++ /trunk/carfac/sai.cc Fri May 31 14:46:48 2013
@@ -0,0 +1,100 @@
+// Copyright 2013, Google, Inc.
+// Author: Ron Weiss <
ro...@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
+++ /trunk/carfac/sai.h Fri May 31 14:46:48 2013
@@ -0,0 +1,81 @@
+// Copyright 2013, Google, Inc.
+// Author: Ron Weiss <
ro...@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
+++ /trunk/carfac/sai_test.cc Fri May 31 14:46:48 2013
@@ -0,0 +1,95 @@
+// Copyright 2013, Google, Inc.
+// Author: Ron Weiss <
ro...@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.
=======================================
--- /trunk/carfac/SConstruct Wed May 29 08:37:28 2013
+++ /trunk/carfac/SConstruct Fri May 31 14:46:48 2013
@@ -43,29 +43,29 @@
import commands
import os
+env = Environment(CPPPATH=[os.environ['EIGEN_PATH']])
+GCC_VERSION = commands.getoutput(env['CXX'] + ' -dumpversion')
+if GCC_VERSION.startswith('4.6'):
+ env.MergeFlags(['-std=c++0x'])
+else:
+ env.MergeFlags(['-std=c++11'])
+
carfac_sources = [
- 'carfac_common.cc',
+ 'agc_coeffs.h',
'agc_params.h',
- 'agc_coeffs.h',
'agc_state.h',
+ 'car_coeffs.h',
+ 'carfac.cc',
+ 'carfac_common.cc',
'carfac_output.cc',
'car_params.h',
- 'car_coeffs.h',
- 'ihc_params.h',
'car_state.h',
+ 'ear.cc',
'ihc_coeffs.h',
+ 'ihc_params.h',
'ihc_state.h',
- 'ear.cc',
- 'carfac.cc'
+ 'sai.cc',
]
-
-env = Environment(CPPPATH=[os.environ['EIGEN_PATH']])
-GCC_VERSION = commands.getoutput(env['CXX'] + ' -dumpversion')
-if GCC_VERSION.startswith('4.6'):
- env.MergeFlags(['-std=c++0x'])
-else:
- env.MergeFlags(['-std=c++11'])
-
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)
=======================================
--- /trunk/carfac/carfac_common.h Wed May 29 13:33:06 2013
+++ /trunk/carfac/carfac_common.h Fri May 31 14:46:48 2013
@@ -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;
=======================================
--- /trunk/carfac/carfac_test.cc Wed May 29 13:33:06 2013
+++ /trunk/carfac/carfac_test.cc Fri May 31 14:46:48 2013
@@ -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"