tomwalters@0: // Copyright 2006-2010, Thomas Walters
tomwalters@0: //
tomwalters@0: // AIM-C: A C++ implementation of the Auditory Image Model
tomwalters@0: // http://www.acousticscale.org/AIMC
tomwalters@0: //
tomwalters@0: // This program is free software: you can redistribute it and/or modify
tomwalters@0: // it under the terms of the GNU General Public License as published by
tomwalters@0: // the Free Software Foundation, either version 3 of the License, or
tomwalters@0: // (at your option) any later version.
tomwalters@0: //
tomwalters@0: // This program is distributed in the hope that it will be useful,
tomwalters@0: // but WITHOUT ANY WARRANTY; without even the implied warranty of
tomwalters@0: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
tomwalters@0: // GNU General Public License for more details.
tomwalters@0: //
tomwalters@0: // You should have received a copy of the GNU General Public License
tomwalters@0: // along with this program. If not, see .
tomwalters@0:
tomwalters@0: /*! \file
tomwalters@0: * \brief SAI module
tomwalters@0: */
tomwalters@0:
tomwalters@0: /*
tomwalters@0: * \author Thomas Walters
tomwalters@0: * \date created 2007/08/29
tomwalters@0: * \version \$Id: ModuleSAI.cc 4 2010-02-03 18:44:58Z tcw $
tomwalters@0: */
tomwalters@0: #include
tomwalters@0:
tomwalters@0: #include "Modules/SAI/ModuleSAI.h"
tomwalters@0:
tomwalters@5: namespace aimc {
tomwalters@0: ModuleSAI::ModuleSAI(Parameters *parameters) : Module(parameters) {
tomwalters@5: module_identifier_ = "weighted_sai";
tomwalters@5: module_type_ = "sai";
tomwalters@0: module_description_ = "Stabilised auditory image";
tomwalters@5: module_version_ = "$Id: ModuleSAI.cc 4 2010-02-03 18:44:58Z tcw $";
tomwalters@0:
tomwalters@0: min_delay_ms_ = parameters_->DefaultFloat("sai.min_delay_ms", 0.0f);
tomwalters@0: max_delay_ms_ = parameters_->DefaultFloat("sai.max_delay_ms", 35.0f);
tomwalters@0: strobe_weight_alpha_ = parameters_->DefaultFloat("sai.strobe_weight_alpha",
tomwalters@0: 0.5f);
tomwalters@0: buffer_memory_decay_ = parameters_->DefaultFloat("sai.buffer_memory_decay",
tomwalters@0: 0.03f);
tomwalters@0: frame_period_ms_ = parameters_->DefaultFloat("sai.frame_period_ms", 20.0f);
tomwalters@0:
tomwalters@5: max_concurrent_strobes_
tomwalters@5: = parameters_->DefaultInt("sai.max_concurrent_strobes", 50);
tomwalters@5:
tomwalters@5: min_strobe_delay_idx_ = 0;
tomwalters@5: max_strobe_delay_idx_ = 0;
tomwalters@0: sai_decay_factor_ = 0.0f;
tomwalters@5: fire_counter_ = 0;
tomwalters@0: }
tomwalters@0:
tomwalters@0: bool ModuleSAI::InitializeInternal(const SignalBank &input) {
tomwalters@0: // The SAI output bank must be as long as the SAI's Maximum delay.
tomwalters@0: // One sample is added to the SAI buffer length to account for the
tomwalters@0: // zero-lag point
tomwalters@6: int sai_buffer_length = 1 + floor(input.sample_rate() * max_delay_ms_
tomwalters@6: / 1000.0f);
tomwalters@5: channel_count_ = input.channel_count();
tomwalters@0:
tomwalters@0: // Make an output SignalBank with the same number of channels and centre
tomwalters@0: // frequencies as the input, but with a different buffer length
tomwalters@0: if (!output_.Initialize(input.channel_count(),
tomwalters@0: sai_buffer_length,
tomwalters@5: input.sample_rate())) {
tomwalters@0: LOG_ERROR("Failed to create output buffer in SAI module");
tomwalters@0: return false;
tomwalters@0: }
tomwalters@0: for (int i = 0; i < input.channel_count(); ++i ) {
tomwalters@5: output_.set_centre_frequency(i, input.centre_frequency(i));
tomwalters@0: }
tomwalters@0:
tomwalters@0: // sai_temp_ will be initialized to zero
tomwalters@0: if (!sai_temp_.Initialize(output_)) {
tomwalters@0: LOG_ERROR("Failed to create temporary buffer in SAI module");
tomwalters@0: return false;
tomwalters@0: }
tomwalters@0:
tomwalters@5: frame_period_samples_ = floor(input.sample_rate() * frame_period_ms_
tomwalters@5: / 1000.0f);
tomwalters@5: min_strobe_delay_idx_ = floor(input.sample_rate() * min_delay_ms_
tomwalters@5: / 1000.0f);
tomwalters@5: max_strobe_delay_idx_ = floor(input.sample_rate() * max_delay_ms_
tomwalters@5: / 1000.0f);
tomwalters@0:
tomwalters@0: // Make sure we don't go past the output buffer's upper bound
tomwalters@5: if (max_strobe_delay_idx_ > output_.buffer_length()) {
tomwalters@0: max_strobe_delay_idx_ = output_.buffer_length();
tomwalters@5: }
tomwalters@0:
tomwalters@0: // Define decay factor from time since last sample (see ti2003)
tomwalters@0: sai_decay_factor_ = pow(0.5f, 1.0f / (buffer_memory_decay_
tomwalters@0: * input.sample_rate()));
tomwalters@0:
tomwalters@0: // Precompute strobe weights
tomwalters@0: strobe_weights_.resize(max_concurrent_strobes_);
tomwalters@0: for (int n = 0; n < max_concurrent_strobes_; ++n) {
tomwalters@5: strobe_weights_[n] = pow(1.0f / (n + 1), strobe_weight_alpha_);
tomwalters@0: }
tomwalters@0:
tomwalters@5: ResetInternal();
tomwalters@0:
tomwalters@0: return true;
tomwalters@0: }
tomwalters@0:
tomwalters@3: void ModuleSAI::ResetInternal() {
tomwalters@5: // Active Strobes
tomwalters@5: active_strobes_.clear();
tomwalters@5: active_strobes_.resize(channel_count_);
tomwalters@5: fire_counter_ = frame_period_samples_ - 1;
tomwalters@0: }
tomwalters@0:
tomwalters@0: void ModuleSAI::Process(const SignalBank &input) {
tomwalters@0:
tomwalters@0: // Reset the next strobe times
tomwalters@0: next_strobes_.clear();
tomwalters@0: next_strobes_.resize(output_.channel_count(), 0);
tomwalters@0:
tomwalters@0: // Offset the times on the strobes from the previous buffer
tomwalters@5: for (int ch = 0; ch < input.channel_count(); ++ch) {
tomwalters@5: active_strobes_[ch].ShiftStrobes(input.buffer_length());
tomwalters@0: }
tomwalters@0:
tomwalters@0: // Loop over samples to make the SAI
tomwalters@5: for (int i = 0; i < input.buffer_length(); ++i) {
tomwalters@0: float decay_factor = pow(sai_decay_factor_, fire_counter_);
tomwalters@0: // Loop over channels
tomwalters@5: for (int ch = 0; ch < input.channel_count(); ++ch) {
tomwalters@0: // Local convenience variables
tomwalters@5: StrobeList &active_strobes = active_strobes_[ch];
tomwalters@5: int next_strobe_index = next_strobes_[ch];
tomwalters@0:
tomwalters@5: // Update strobes
tomwalters@0: // If we are up to or beyond the next strobe...
tomwalters@5: if (next_strobe_index < input.strobe_count(ch)) {
tomwalters@5: if (i == input.strobe(ch, next_strobe_index)) {
tomwalters@5: // A new strobe has arrived.
tomwalters@5: // If there are too many strobes active, then get rid of the
tomwalters@5: // earliest one
tomwalters@5: if (active_strobes.strobe_count() >= max_concurrent_strobes_) {
tomwalters@5: active_strobes.DeleteFirstStrobe();
tomwalters@0: }
tomwalters@0:
tomwalters@5: // Add the active strobe to the list of current strobes and
tomwalters@5: // calculate the strobe weight
tomwalters@5: float weight = 1.0f;
tomwalters@5: if (active_strobes.strobe_count() > 0) {
tomwalters@5: int last_strobe_time = active_strobes.Strobe(
tomwalters@5: active_strobes.strobe_count() - 1).time;
tomwalters@5:
tomwalters@5: // If the strobe occured within 10 impulse-response
tomwalters@5: // cycles of the previous strobe, then lower its weight
tomwalters@5: weight = (i - last_strobe_time) / input.sample_rate()
tomwalters@5: * input.centre_frequency(ch) / 10.0f;
tomwalters@5: if (weight > 1.0f)
tomwalters@5: weight = 1.0f;
tomwalters@5: }
tomwalters@5: active_strobes.AddStrobe(i, weight);
tomwalters@5: next_strobe_index++;
tomwalters@5:
tomwalters@5:
tomwalters@5: // Update the strobe weights
tomwalters@0: float total_strobe_weight = 0.0f;
tomwalters@5: for (int si = 0; si < active_strobes.strobe_count(); ++si) {
tomwalters@5: total_strobe_weight += (active_strobes.Strobe(si).weight
tomwalters@5: * strobe_weights_[active_strobes.strobe_count() - si - 1]);
tomwalters@0: }
tomwalters@5: for (int si = 0; si < active_strobes.strobe_count(); ++si) {
tomwalters@5: active_strobes.SetWorkingWeight(si,
tomwalters@5: (active_strobes.Strobe(si).weight
tomwalters@5: * strobe_weights_[active_strobes.strobe_count() - si - 1])
tomwalters@5: / total_strobe_weight);
tomwalters@0: }
tomwalters@0: }
tomwalters@0: }
tomwalters@0:
tomwalters@5: // Remove inactive strobes
tomwalters@5: while (active_strobes.strobe_count() > 0) {
tomwalters@5: // Get the relative time of the first strobe, and see if it exceeds
tomwalters@5: // the maximum allowed time.
tomwalters@5: if ((i - active_strobes.Strobe(0).time) > max_strobe_delay_idx_)
tomwalters@5: active_strobes.DeleteFirstStrobe();
tomwalters@0: else
tomwalters@0: break;
tomwalters@0: }
tomwalters@0:
tomwalters@5: // Update the SAI buffer with the weighted effect of all the active
tomwalters@5: // strobes at the current sample
tomwalters@5: for (int si = 0; si < active_strobes.strobe_count(); ++si) {
tomwalters@5: // Add the effect of active strobe at correct place in the SAI buffer
tomwalters@5: // Calculate 'delay', the time from the strobe event to now
tomwalters@5: int delay = i - active_strobes.Strobe(si).time;
tomwalters@0:
tomwalters@0: // If the delay is greater than the (user-set)
tomwalters@0: // minimum strobe delay, the strobe can be used
tomwalters@5: if (delay >= min_strobe_delay_idx_ && delay < max_strobe_delay_idx_) {
tomwalters@0: // The value at be added to the SAI
tomwalters@5: float sig = input.sample(ch, i);
tomwalters@0:
tomwalters@0: // Weight the sample correctly
tomwalters@5: sig *= active_strobes.Strobe(si).working_weight;
tomwalters@0:
tomwalters@0: // Adjust the weight acording to the number of samples until the
tomwalters@0: // next output frame
tomwalters@5: sig *= decay_factor;
tomwalters@0:
tomwalters@0: // Update the temporary SAI buffer
tomwalters@5: output_.set_sample(ch, delay, output_.sample(ch, delay) + sig);
tomwalters@0: }
tomwalters@0: }
tomwalters@0:
tomwalters@5: next_strobes_[ch] = next_strobe_index;
tomwalters@0:
tomwalters@0: } // End loop over channels
tomwalters@0:
tomwalters@5: fire_counter_--;
tomwalters@0:
tomwalters@5: // Check to see if we need to output an SAI frame on this sample
tomwalters@5: if (fire_counter_ <= 0) {
tomwalters@0: // Decay the SAI by the correct amount and add the current output frame
tomwalters@5: float decay = pow(sai_decay_factor_, frame_period_samples_);
tomwalters@0:
tomwalters@5: for (int ch = 0; ch < input.channel_count(); ++ch) {
tomwalters@0: for (int i = 0; i < output_.buffer_length(); ++i) {
tomwalters@5: output_.set_sample(ch, i,
tomwalters@5: sai_temp_[ch][i] + output_[ch][i] * decay);
tomwalters@0: }
tomwalters@0: }
tomwalters@0:
tomwalters@0: // Zero the temporary signal
tomwalters@0: for (int ch = 0; ch < sai_temp_.channel_count(); ++ch) {
tomwalters@0: for (int i = 0; i < sai_temp_.buffer_length(); ++i) {
tomwalters@0: sai_temp_.set_sample(ch, i, 0.0f);
tomwalters@0: }
tomwalters@0: }
tomwalters@0:
tomwalters@5: fire_counter_ = frame_period_samples_ - 1;
tomwalters@0:
tomwalters@5: // Transfer the current time to the output buffer
tomwalters@5: output_.set_start_time(input.start_time() + i);
tomwalters@0: PushOutput();
tomwalters@0: }
tomwalters@0: } // End loop over samples
tomwalters@0: }
tomwalters@0:
tomwalters@0: ModuleSAI::~ModuleSAI() {
tomwalters@0: }
tomwalters@5: } // namespace aimc