view trunk/carfac/carfac_test.cc @ 672:a9694d0bb55a

Fix scons build of library and test. Deleted main.cc since libgtest comes with a corresponding main library. Everything compiles and the tests run on Ubuntu, but the tests all fail.
author ronw@google.com
date Thu, 23 May 2013 18:12:22 +0000
parents 933cf18d9a59
children 7f424c1a8b78
line wrap: on
line source
//
//  carfac_test.cc
//  CARFAC Open Source C++ Library
//
//  Created by Alex Brandmeyer on 5/22/13.
//
// 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 "carfac_test.h"

// Three helper functions are defined here for loading the test data generated
// by the Matlab version of CARFAC.
// This loads one-dimensional FloatArrays from single-column text files.
FloatArray LoadTestData(const std::string filename, const int number_points) {
  std::string fullfile = TEST_SRC_DIR + filename;
  std::ifstream file(fullfile.c_str());
  FPType myarray[number_points];
  FloatArray output(number_points);
  if (file.is_open()) {
    for (int i = 0; i < number_points; ++i) {
      file >> myarray[i];
      output(i) = myarray[i];
    }
  }
  return output;
}

// This loads two-dimensional FloatArrays from multi-column text files.
std::vector<FloatArray> Load2dTestData(const std::string filename, const int rows,
                            const int columns) {
  std::string fullfile = TEST_SRC_DIR + filename;
  std::ifstream file(fullfile.c_str());
  FPType myarray[rows][columns];
  std::vector<FloatArray> output;
  output.resize(rows);
  for (auto& timepoint : output) {
    timepoint.resize(columns);
  }
  if (file.is_open()) {
    for (int i = 0; i < rows; ++i) {
      for (int j = 0; j < columns; ++j) {
        file >> myarray[i][j];
        output[i](j) = myarray[i][j];
      }
    }
  }
  return output;
}

// This loads two dimensional vectors of audio data using data generated in
// Matlab using the wavread() function.
std::vector<std::vector<float>> Load2dAudioVector(std::string filename,
                                                  int timepoints,
                                                  int channels) {
  std::string fullfile = TEST_SRC_DIR + filename;
  std::ifstream file(fullfile.c_str());
  std::vector<std::vector<float>> output;
  output.resize(channels);
  for (auto& channel : output) {
    channel.resize(timepoints);
  }
  if (file.is_open()) {
    for (int i = 0; i < timepoints; ++i) {
      for (int j = 0; j < channels; ++j) {
        file >> output[j][i];
      }
    }
  }
  return output;
}

// The first test verifies that the resulting CAR coefficients are the same as
// in Matlab when using the default CAR parameter set.
TEST(CARFACTest, CARCoeffs_Test){
  // These initialze the CAR Params and Coeffs objects needed for this test.
  CARParams car_params;
  CARCoeffs car_coeffs;
  FPType fs = 22050.0;
  // We calculate the pole frequencies and number of channels in the same way
  // as in the CARFAC 'Design' method.
  int n_ch = 0;
  FPType pole_hz = car_params.first_pole_theta_ * fs / (2 * PI);
  while (pole_hz > car_params.min_pole_hz_) {
    n_ch++;
    pole_hz = pole_hz - car_params.erb_per_step_ *
    ERBHz(pole_hz, car_params.erb_break_freq_, car_params.erb_q_);
  }
  FloatArray pole_freqs(n_ch);
  pole_hz = car_params.first_pole_theta_ * fs / (2 * PI);
  for (int ch = 0; ch < n_ch; ++ch) {
    pole_freqs(ch) = pole_hz;
    pole_hz = pole_hz - car_params.erb_per_step_ *
    ERBHz(pole_hz, car_params.erb_break_freq_, car_params.erb_q_);
  }
  // This initializes the CAR coeffecients object and runs the design method.
  car_coeffs.Design(car_params, 22050, pole_freqs);
  // Now we go through each set of coefficients to verify that the values are
  // the same as in MATLAB.
  std::string filename;
  FloatArray output;
  
  ASSERT_EQ(car_coeffs.v_offset_, 0.04);
  ASSERT_EQ(car_coeffs.velocity_scale_, 0.1);
  
  filename = "r1_coeffs.txt";
  output = LoadTestData(filename, n_ch);
  for (int i = 0; i < n_ch; ++i) {
    ASSERT_NEAR(output(i), car_coeffs.r1_coeffs_(i), PRECISION_LEVEL);
  }
  
  filename = "a0_coeffs.txt";
  output = LoadTestData(filename, n_ch);
  for (int i = 0; i < n_ch; ++i) {
    ASSERT_NEAR(output(i), car_coeffs.a0_coeffs_(i), PRECISION_LEVEL);
  }
  
  filename = "c0_coeffs.txt";
  output = LoadTestData(filename, n_ch);
  for (int i = 0; i < n_ch; ++i) {
    ASSERT_NEAR(output(i), car_coeffs.c0_coeffs_(i), PRECISION_LEVEL);
  }
  
  filename = "zr_coeffs.txt";
  output = LoadTestData(filename, n_ch);
  for (int i = 0; i < n_ch; ++i) {
    ASSERT_NEAR(output(i), car_coeffs.zr_coeffs_(i), PRECISION_LEVEL);
  }
  
  filename = "h_coeffs.txt";
  output = LoadTestData(filename, n_ch);
  for (int i = 0; i < n_ch; ++i) {
    ASSERT_NEAR(output(i), car_coeffs.h_coeffs_(i), PRECISION_LEVEL);
  }
  
  filename = "g0_coeffs.txt";
  output = LoadTestData(filename, n_ch);
  for (int i = 0; i < n_ch; ++i) {
    ASSERT_NEAR(output(i), car_coeffs.g0_coeffs_(i), PRECISION_LEVEL);
  }
}

// The second test verifies that the IHC coefficient calculations result in the
// same set of values as in the Matlab version of the CARFAC.
TEST(CARFACTest, IHCCoeffs_Test){
  IHCParams ihc_params;
  IHCCoeffs ihc_coeffs;
  FPType fs = 22050.0;
  ihc_coeffs.Design(ihc_params, fs);
  
  std::string filename = "ihc_coeffs.txt";
  FloatArray output = LoadTestData(filename, 9);
  
  // The sequence of the individual coefficients is determined using the
  // CARFAC_GenerateTestData() function in the Matlab version, with all of the
  // parameters placed in a single output file for convenience.
  bool just_hwr = output(0);
  FPType lpf_coeff = output(1);
  FPType out_rate = output(2);
  FPType in_rate = output(3);
  bool one_cap = output(4);
  FPType output_gain = output(5);
  FPType rest_output = output(6);
  FPType rest_cap = output(7);
  FPType ac_coeff = output(8);
  
  // Once we have the Matlab values initialized, we can compare them to the
  // output of the IHCCoeffs 'Design' method.
  ASSERT_EQ(just_hwr, ihc_coeffs.just_hwr_);
  ASSERT_NEAR(lpf_coeff, ihc_coeffs.lpf_coeff_, PRECISION_LEVEL);
  ASSERT_NEAR(out_rate, ihc_coeffs.out1_rate_, PRECISION_LEVEL);
  ASSERT_NEAR(in_rate, ihc_coeffs.in1_rate_, PRECISION_LEVEL);
  ASSERT_EQ(one_cap, ihc_coeffs.one_cap_);
  ASSERT_NEAR(output_gain, ihc_coeffs.output_gain_, PRECISION_LEVEL);
  ASSERT_NEAR(rest_output, ihc_coeffs.rest_output_, PRECISION_LEVEL);
  ASSERT_NEAR(rest_cap, ihc_coeffs.rest_cap1_, PRECISION_LEVEL);
  ASSERT_NEAR(ac_coeff, ihc_coeffs.ac_coeff_, PRECISION_LEVEL);
}


TEST(CARFACTest, AGCCoeffs_Test) {
  AGCParams agc_params;
  std::vector<AGCCoeffs> agc_coeffs;
  std::vector<FloatArray> output;
  output.resize(agc_params.n_stages_);
  std::string filename = "agc_coeffs_1.txt";
  output[0] = LoadTestData(filename, 14);
  filename = "agc_coeffs_2.txt";
  output[1] = LoadTestData(filename, 14);
  filename = "agc_coeffs_3.txt";
  output[2] = LoadTestData(filename, 14);
  filename = "agc_coeffs_4.txt";
  output[3] = LoadTestData(filename, 14);
  agc_coeffs.resize(agc_params.n_stages_);
  // We initialize the AGC stages in the same was as in Ear::Init.
  FPType fs = 22050.0;
  FPType previous_stage_gain = 0.0;
  FPType decim = 1.0;
  for (int stage = 0; stage < agc_params.n_stages_; ++stage) {
    agc_coeffs[stage].Design(agc_params, stage, fs, previous_stage_gain, decim);
    previous_stage_gain = agc_coeffs[stage].agc_gain_;
    decim = agc_coeffs[stage].decim_;
  }
  // Now we run through the individual coefficients and verify that they're the
  // same as in Matlab.
  for (int stage = 0; stage < agc_params.n_stages_; ++stage) {
    int n_agc_stages = output[stage](1);
    FPType agc_stage_gain = output[stage](2);
    int decimation = output[stage](3);
    FPType agc_epsilon = output[stage](4);
    FPType agc_polez1 = output[stage](5);
    FPType agc_polez2 = output[stage](6);
    int agc_spatial_iterations = output[stage](7);
    FPType agc_spatial_fir_1 = output[stage](8);
    FPType agc_spatial_fir_2 = output[stage](9);
    FPType agc_spatial_fir_3 = output[stage](10);
    int agc_spatial_n_taps = output[stage](11);
    FPType agc_mix_coeffs = output[stage](12);
    FPType detect_scale = output[stage](13);
    
    ASSERT_EQ(n_agc_stages, agc_coeffs[stage].n_agc_stages_);
    ASSERT_NEAR(agc_stage_gain, agc_coeffs[stage].agc_stage_gain_,
                PRECISION_LEVEL);
    ASSERT_EQ(decimation, agc_coeffs[stage].decimation_);
    ASSERT_NEAR(agc_epsilon, agc_coeffs[stage].agc_epsilon_, PRECISION_LEVEL);
    ASSERT_NEAR(agc_polez1, agc_coeffs[stage].agc_pole_z1_, PRECISION_LEVEL);
    ASSERT_NEAR(agc_polez2, agc_coeffs[stage].agc_pole_z2_, PRECISION_LEVEL);
    ASSERT_EQ(agc_spatial_iterations,
              agc_coeffs[stage].agc_spatial_iterations_);
    ASSERT_NEAR(agc_spatial_fir_1, agc_coeffs[stage].agc_spatial_fir_[0],
                PRECISION_LEVEL);
    ASSERT_NEAR(agc_spatial_fir_2, agc_coeffs[stage].agc_spatial_fir_[1],
                PRECISION_LEVEL);
    ASSERT_EQ(agc_spatial_n_taps,
              agc_coeffs[stage].agc_spatial_n_taps_);
    ASSERT_NEAR(agc_spatial_fir_3, agc_coeffs[stage].agc_spatial_fir_[2],
                PRECISION_LEVEL);
    ASSERT_NEAR(agc_mix_coeffs, agc_coeffs[stage].agc_mix_coeffs_,
                PRECISION_LEVEL);
    
    // The last stage will have the correct detect_scale_ value on the basis of
    // the total gain accumlated over the stages.
    if (stage == agc_params.n_stages_ - 1) {
      ASSERT_NEAR(detect_scale, agc_coeffs[stage].detect_scale_,
                  PRECISION_LEVEL);
    }
  }
}

// This test verifies the output of the C++ code relative to that of the Matlab
// version using a single segment (441 samples) of audio from the "plan.wav"
// file. The single-channel audio data and different output matrices from Matlab
// are stored in text files and then read into 2d Eigen arrays (for now, this
// should be changed to a vector of FloatArrays... TODO (alexbrandmeyer)). For
// reference, see the CARFAC_GenerateTestData() function in the Matlab branch
// of the repository.
//
// A single Ear object is used along with the code from CARFAC.RunSegment() to
// evaluate the output of the CAR and IHC steps on a sample by sample basis
// relative to the output read in from Matlab. The test passes with 11 degrees
// of precision, with the Matlab data stored using 12 decimals.
//
// TODO (alexbrandmeyer): A subseqent version of this test will operate directly
// on the CARFACOutput structure and will evaluate binaural data.
TEST(CARFACTest, Monaural_Output_Test) {
  std::string filename = "monaural_test_nap.txt";
  std::vector<FloatArray> nap = Load2dTestData(filename, 441, 71);
  filename = "monaural_test_bm.txt";
  std::vector<FloatArray> bm = Load2dTestData(filename, 441, 71);
  filename = "monaural_test_ohc.txt";
  std::vector<FloatArray> ohc = Load2dTestData(filename, 441, 71);
  filename = "monaural_test_agc.txt";
  std::vector<FloatArray> agc = Load2dTestData(filename, 441, 71);
  filename = "file_signal_monaural_test.txt";
  std::vector<std::vector<float>> sound_data = Load2dAudioVector(filename, 441,
                                                                 1);
  // The number of timepoints is determined from the length of the audio
  // segment.
  int32_t n_timepoints = sound_data[0].size();
  
  CARParams car_params;
  IHCParams ihc_params;
  AGCParams agc_params;
  FPType fs = 22050.0;
  int n_ch = 0;
  FPType pole_hz = car_params.first_pole_theta_ * fs / (2 * PI);
  while (pole_hz > car_params.min_pole_hz_) {
    n_ch++;
    pole_hz = pole_hz - car_params.erb_per_step_ *
    ERBHz(pole_hz, car_params.erb_break_freq_, car_params.erb_q_);
  }
  FloatArray pole_freqs(n_ch);
  pole_hz = car_params.first_pole_theta_ * fs / (2 * PI);
  for (int ch = 0; ch < n_ch; ++ch) {
    pole_freqs(ch) = pole_hz;
    pole_hz = pole_hz - car_params.erb_per_step_ *
    ERBHz(pole_hz, car_params.erb_break_freq_, car_params.erb_q_);
  }
  
  // This initializes the CARFAC object and runs the design method.
  Ear ear;
  ear.InitEar(n_ch, fs, pole_freqs, car_params, ihc_params, agc_params);
  
  CARFACOutput seg_output;
  seg_output.InitOutput(1, n_ch, n_timepoints);
  
  // A nested loop structure is used to iterate through the individual samples
  // for each ear (audio channel).
  FloatArray car_out(n_ch);
  FloatArray ihc_out(n_ch);
  FloatArray matlab_car_out(n_ch);
  FloatArray matlab_ihc_out(n_ch);
  bool updated;  // This variable is used by the AGC stage.
  for (int32_t i = 0; i < n_timepoints; ++i) {
    int j = 0;
    // First we create a reference to the current Ear object.
    // This stores the audio sample currently being processed.
    FPType input = sound_data[j][i];
    // Now we apply the three stages of the model in sequence to the current
    // audio sample.
    ear.CARStep(input, &car_out);
    matlab_car_out = bm[i];
    // This step verifies that the ouput of the CAR step is the same at each
    // timepoint and channel as that of the Matlab version.
    for (int channel = 0; channel < n_ch; ++channel) {
      FPType a = matlab_car_out(channel);
      FPType b = car_out(channel);
      ASSERT_NEAR(a, b, PRECISION_LEVEL);
    }
    ear.IHCStep(car_out, &ihc_out);
    matlab_ihc_out = nap[i];
    // This step verifies that the ouput of the IHC step is the same at each
    // timepoint and channel as that of the Matlab version.
    for (int channel = 0; channel < n_ch; ++channel) {
      FPType a = matlab_ihc_out(channel);
      FPType b = ihc_out(channel);
      ASSERT_NEAR(a, b, PRECISION_LEVEL);
    }
    
    updated = ear.AGCStep(ihc_out);
    // These lines assign the output of the model for the current sample
    // to the appropriate data members of the current ear in the output
    // object.
    seg_output.StoreNAPOutput(i, j, ihc_out);
    seg_output.StoreBMOutput(i, j, car_out);
    seg_output.StoreOHCOutput(i, j, ear.za_memory());
    seg_output.StoreAGCOutput(i, j, ear.zb_memory());
    if (updated) {
      FloatArray undamping = 1 - ear.agc_memory(0);
      // This updates the target stage gain for the new damping.
      ear.set_dzb_memory((ear.zr_coeffs() * undamping - ear.zb_memory()) /
                         ear.agc_decimation(0));
      ear.set_dg_memory((ear.StageGValue(undamping) - ear.g_memory()) /
                        ear.agc_decimation(0));
    }
  }
}