diff carfac/sai.cc @ 642:20f64146c2ce

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
children 8b70f4cf00c7
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/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);
+    }
+  }
+}