tomwalters@268: // Copyright 2006-2010, Thomas Walters tomwalters@268: // tomwalters@268: // AIM-C: A C++ implementation of the Auditory Image Model tomwalters@268: // http://www.acousticscale.org/AIMC tomwalters@268: // tomwalters@318: // Licensed under the Apache License, Version 2.0 (the "License"); tomwalters@318: // you may not use this file except in compliance with the License. tomwalters@318: // You may obtain a copy of the License at tomwalters@268: // tomwalters@318: // http://www.apache.org/licenses/LICENSE-2.0 tomwalters@268: // tomwalters@318: // Unless required by applicable law or agreed to in writing, software tomwalters@318: // distributed under the License is distributed on an "AS IS" BASIS, tomwalters@318: // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. tomwalters@318: // See the License for the specific language governing permissions and tomwalters@318: // limitations under the License. tomwalters@268: tomwalters@268: /*! \file tomwalters@268: * \brief SAI module tomwalters@268: */ tomwalters@268: tomwalters@268: /* tomwalters@268: * \author Thomas Walters tomwalters@268: * \date created 2007/08/29 tomwalters@296: * \version \$Id$ tomwalters@268: */ tomwalters@287: #include tomwalters@268: tomwalters@268: #include "Modules/SAI/ModuleSAI.h" tomwalters@268: tomwalters@277: namespace aimc { tomwalters@268: ModuleSAI::ModuleSAI(Parameters *parameters) : Module(parameters) { tomwalters@277: module_identifier_ = "weighted_sai"; tomwalters@277: module_type_ = "sai"; tomwalters@268: module_description_ = "Stabilised auditory image"; tomwalters@296: module_version_ = "$Id$"; tomwalters@268: tomwalters@268: min_delay_ms_ = parameters_->DefaultFloat("sai.min_delay_ms", 0.0f); tomwalters@268: max_delay_ms_ = parameters_->DefaultFloat("sai.max_delay_ms", 35.0f); tomwalters@268: strobe_weight_alpha_ = parameters_->DefaultFloat("sai.strobe_weight_alpha", tomwalters@268: 0.5f); tomwalters@268: buffer_memory_decay_ = parameters_->DefaultFloat("sai.buffer_memory_decay", tomwalters@268: 0.03f); tomwalters@268: frame_period_ms_ = parameters_->DefaultFloat("sai.frame_period_ms", 20.0f); tomwalters@268: tomwalters@277: max_concurrent_strobes_ tomwalters@277: = parameters_->DefaultInt("sai.max_concurrent_strobes", 50); tomwalters@277: tomwalters@277: min_strobe_delay_idx_ = 0; tomwalters@277: max_strobe_delay_idx_ = 0; tomwalters@268: sai_decay_factor_ = 0.0f; tomwalters@277: fire_counter_ = 0; tomwalters@268: } tomwalters@268: tomwalters@268: bool ModuleSAI::InitializeInternal(const SignalBank &input) { tomwalters@268: // The SAI output bank must be as long as the SAI's Maximum delay. tomwalters@268: // One sample is added to the SAI buffer length to account for the tomwalters@268: // zero-lag point tomwalters@278: int sai_buffer_length = 1 + floor(input.sample_rate() * max_delay_ms_ tomwalters@278: / 1000.0f); tomwalters@277: channel_count_ = input.channel_count(); tomwalters@268: tomwalters@268: // Make an output SignalBank with the same number of channels and centre tomwalters@268: // frequencies as the input, but with a different buffer length tomwalters@268: if (!output_.Initialize(input.channel_count(), tomwalters@268: sai_buffer_length, tomwalters@277: input.sample_rate())) { tomwalters@268: LOG_ERROR("Failed to create output buffer in SAI module"); tomwalters@268: return false; tomwalters@268: } tomwalters@280: for (int i = 0; i < input.channel_count(); ++i) { tomwalters@277: output_.set_centre_frequency(i, input.centre_frequency(i)); tomwalters@268: } tomwalters@268: tomwalters@268: // sai_temp_ will be initialized to zero tomwalters@268: if (!sai_temp_.Initialize(output_)) { tomwalters@268: LOG_ERROR("Failed to create temporary buffer in SAI module"); tomwalters@268: return false; tomwalters@268: } tomwalters@268: tomwalters@277: frame_period_samples_ = floor(input.sample_rate() * frame_period_ms_ tomwalters@277: / 1000.0f); tomwalters@277: min_strobe_delay_idx_ = floor(input.sample_rate() * min_delay_ms_ tomwalters@277: / 1000.0f); tomwalters@277: max_strobe_delay_idx_ = floor(input.sample_rate() * max_delay_ms_ tomwalters@277: / 1000.0f); tomwalters@268: tomwalters@268: // Make sure we don't go past the output buffer's upper bound tomwalters@277: if (max_strobe_delay_idx_ > output_.buffer_length()) { tomwalters@268: max_strobe_delay_idx_ = output_.buffer_length(); tomwalters@277: } tomwalters@268: tomwalters@268: // Define decay factor from time since last sample (see ti2003) tomwalters@268: sai_decay_factor_ = pow(0.5f, 1.0f / (buffer_memory_decay_ tomwalters@268: * input.sample_rate())); tomwalters@268: tomwalters@268: // Precompute strobe weights tomwalters@268: strobe_weights_.resize(max_concurrent_strobes_); tomwalters@268: for (int n = 0; n < max_concurrent_strobes_; ++n) { tomwalters@277: strobe_weights_[n] = pow(1.0f / (n + 1), strobe_weight_alpha_); tomwalters@268: } tomwalters@268: tomwalters@277: ResetInternal(); tomwalters@268: tomwalters@268: return true; tomwalters@268: } tomwalters@268: tomwalters@275: void ModuleSAI::ResetInternal() { tomwalters@277: // Active Strobes tom@420: output_.Clear(); tom@420: sai_temp_.Clear(); tomwalters@277: active_strobes_.clear(); tomwalters@277: active_strobes_.resize(channel_count_); tomwalters@277: fire_counter_ = frame_period_samples_ - 1; tomwalters@268: } tomwalters@268: tomwalters@268: void ModuleSAI::Process(const SignalBank &input) { tomwalters@268: // Reset the next strobe times tomwalters@268: next_strobes_.clear(); tomwalters@268: next_strobes_.resize(output_.channel_count(), 0); tomwalters@268: tomwalters@268: // Offset the times on the strobes from the previous buffer tomwalters@277: for (int ch = 0; ch < input.channel_count(); ++ch) { tomwalters@277: active_strobes_[ch].ShiftStrobes(input.buffer_length()); tomwalters@268: } tomwalters@268: tomwalters@268: // Loop over samples to make the SAI tomwalters@277: for (int i = 0; i < input.buffer_length(); ++i) { tomwalters@268: float decay_factor = pow(sai_decay_factor_, fire_counter_); tomwalters@268: // Loop over channels tomwalters@277: for (int ch = 0; ch < input.channel_count(); ++ch) { tomwalters@268: // Local convenience variables tomwalters@277: StrobeList &active_strobes = active_strobes_[ch]; tomwalters@277: int next_strobe_index = next_strobes_[ch]; tomwalters@268: tomwalters@277: // Update strobes tomwalters@268: // If we are up to or beyond the next strobe... tomwalters@277: if (next_strobe_index < input.strobe_count(ch)) { tomwalters@277: if (i == input.strobe(ch, next_strobe_index)) { tomwalters@277: // A new strobe has arrived. tomwalters@277: // If there are too many strobes active, then get rid of the tomwalters@277: // earliest one tomwalters@277: if (active_strobes.strobe_count() >= max_concurrent_strobes_) { tomwalters@277: active_strobes.DeleteFirstStrobe(); tomwalters@268: } tomwalters@268: tomwalters@277: // Add the active strobe to the list of current strobes and tomwalters@277: // calculate the strobe weight tomwalters@277: float weight = 1.0f; tomwalters@277: if (active_strobes.strobe_count() > 0) { tomwalters@277: int last_strobe_time = active_strobes.Strobe( tomwalters@277: active_strobes.strobe_count() - 1).time; tomwalters@277: tomwalters@277: // If the strobe occured within 10 impulse-response tomwalters@277: // cycles of the previous strobe, then lower its weight tomwalters@277: weight = (i - last_strobe_time) / input.sample_rate() tomwalters@277: * input.centre_frequency(ch) / 10.0f; tomwalters@277: if (weight > 1.0f) tomwalters@277: weight = 1.0f; tomwalters@277: } tomwalters@277: active_strobes.AddStrobe(i, weight); tomwalters@277: next_strobe_index++; tomwalters@277: tomwalters@277: tomwalters@277: // Update the strobe weights tomwalters@268: float total_strobe_weight = 0.0f; tomwalters@277: for (int si = 0; si < active_strobes.strobe_count(); ++si) { tomwalters@277: total_strobe_weight += (active_strobes.Strobe(si).weight tomwalters@277: * strobe_weights_[active_strobes.strobe_count() - si - 1]); tomwalters@268: } tomwalters@277: for (int si = 0; si < active_strobes.strobe_count(); ++si) { tomwalters@277: active_strobes.SetWorkingWeight(si, tomwalters@277: (active_strobes.Strobe(si).weight tomwalters@277: * strobe_weights_[active_strobes.strobe_count() - si - 1]) tomwalters@277: / total_strobe_weight); tomwalters@268: } tomwalters@268: } tomwalters@268: } tomwalters@268: tomwalters@277: // Remove inactive strobes tomwalters@277: while (active_strobes.strobe_count() > 0) { tomwalters@277: // Get the relative time of the first strobe, and see if it exceeds tomwalters@277: // the maximum allowed time. tomwalters@277: if ((i - active_strobes.Strobe(0).time) > max_strobe_delay_idx_) tomwalters@277: active_strobes.DeleteFirstStrobe(); tomwalters@268: else tomwalters@268: break; tomwalters@268: } tomwalters@268: tomwalters@277: // Update the SAI buffer with the weighted effect of all the active tomwalters@277: // strobes at the current sample tomwalters@277: for (int si = 0; si < active_strobes.strobe_count(); ++si) { tomwalters@277: // Add the effect of active strobe at correct place in the SAI buffer tomwalters@277: // Calculate 'delay', the time from the strobe event to now tomwalters@277: int delay = i - active_strobes.Strobe(si).time; tomwalters@268: tomwalters@268: // If the delay is greater than the (user-set) tomwalters@268: // minimum strobe delay, the strobe can be used tomwalters@277: if (delay >= min_strobe_delay_idx_ && delay < max_strobe_delay_idx_) { tomwalters@268: // The value at be added to the SAI tomwalters@277: float sig = input.sample(ch, i); tomwalters@268: tomwalters@268: // Weight the sample correctly tomwalters@277: sig *= active_strobes.Strobe(si).working_weight; tomwalters@268: tomwalters@268: // Adjust the weight acording to the number of samples until the tomwalters@268: // next output frame tomwalters@277: sig *= decay_factor; tomwalters@268: tomwalters@268: // Update the temporary SAI buffer tomwalters@596: sai_temp_.set_sample(ch, delay, sai_temp_.sample(ch, delay) + sig); tomwalters@268: } tomwalters@268: } tomwalters@268: tomwalters@277: next_strobes_[ch] = next_strobe_index; tomwalters@280: } // End loop over channels tomwalters@268: tomwalters@277: fire_counter_--; tomwalters@268: tomwalters@277: // Check to see if we need to output an SAI frame on this sample tomwalters@277: if (fire_counter_ <= 0) { tomwalters@268: // Decay the SAI by the correct amount and add the current output frame tomwalters@277: float decay = pow(sai_decay_factor_, frame_period_samples_); tomwalters@268: tomwalters@277: for (int ch = 0; ch < input.channel_count(); ++ch) { tomwalters@268: for (int i = 0; i < output_.buffer_length(); ++i) { tomwalters@277: output_.set_sample(ch, i, tomwalters@277: sai_temp_[ch][i] + output_[ch][i] * decay); tomwalters@268: } tomwalters@268: } tomwalters@268: tomwalters@268: // Zero the temporary signal tomwalters@268: for (int ch = 0; ch < sai_temp_.channel_count(); ++ch) { tomwalters@268: for (int i = 0; i < sai_temp_.buffer_length(); ++i) { tomwalters@268: sai_temp_.set_sample(ch, i, 0.0f); tomwalters@268: } tomwalters@268: } tomwalters@268: tomwalters@277: fire_counter_ = frame_period_samples_ - 1; tomwalters@268: tomwalters@277: // Transfer the current time to the output buffer tomwalters@277: output_.set_start_time(input.start_time() + i); tomwalters@268: PushOutput(); tomwalters@268: } tomwalters@280: } // End loop over samples tomwalters@268: } tomwalters@268: tomwalters@268: ModuleSAI::~ModuleSAI() { tomwalters@268: } tomwalters@277: } // namespace aimc