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@0: ModuleSAI::ModuleSAI(Parameters *parameters) : Module(parameters) { tomwalters@0: module_description_ = "Stabilised auditory image"; tomwalters@0: module_name_ = "sai2007"; tomwalters@0: module_type_ = "sai"; tomwalters@0: module_id_ = "$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@0: min_strobe_delay_index_ = 0; tomwalters@0: max_strobe_delay_index_ = 0; tomwalters@0: sai_decay_factor_ = 0.0f; tomwalters@0: max_concurrent_strobes_ = 0; tomwalters@0: output_frame_period_ms_ = 0.0f; tomwalters@0: last_output_frame_time_ms_ = 0.0f; 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@0: int sai_buffer_length = 1 + Round(input.sample_rate() * max_delay_ms_); 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@0: 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@0: output_.set_centre_frequency(i, input.get_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@0: last_output_time_ms_ = 0.0f; tomwalters@0: tomwalters@0: frame_period_samples_ = Round(input.sample_rate() tomwalters@0: * frame_period_ms_ / 1000.0f); tomwalters@0: tomwalters@0: min_strobe_delay_idx_ = Round(input.sample_rate() * min_delay_ms_ / 1000.0f); tomwalters@0: max_strobe_delay_idx_ = Round(input.sample_rate() * max_delay_ms_ / 1000.0f); tomwalters@0: tomwalters@0: // Make sure we don't go past the output buffer's upper bound tomwalters@0: if (max_strobe_delay_idx_ > output_.buffer_length())) tomwalters@0: max_strobe_delay_idx_ = output_.buffer_length(); 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: // Maximum strobes that can be active at the same time within maxdelay. tomwalters@0: //! \todo Choose this value in a more principled way tomwalters@0: max_concurrent_strobes_ = Round(1000.0f * max_delay_ * 5); 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@0: strobe_weights_[n] = pow(1.0f / (n + 1)), strobe_weight_alpha_); tomwalters@0: } tomwalters@0: tomwalters@0: // Active Strobes tomwalters@0: active_strobes_.Resize(input.channel_count()); tomwalters@0: for (int i = 0; i < input.channel_count(); ++i) { tomwalters@0: active_strobes_[i].Create(max_concurrent_strobes_); tomwalters@0: } tomwalters@0: next_strobes_.resize(input.channel_count(), 0); tomwalters@0: tomwalters@0: return true; tomwalters@0: } tomwalters@0: tomwalters@0: void ModuleSAI::Reset() { tomwalters@0: } tomwalters@0: tomwalters@0: void ModuleSAI::Process(const SignalBank &input) { tomwalters@0: int s; tomwalters@0: int c; tomwalters@0: int output_buffer_length = output_.buffer_length(); 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@0: for (c = 0; c < input.channel_count(), ++c) { tomwalters@0: active_strobes_[c].shiftStrobes(input.buffer_length()); tomwalters@0: } tomwalters@0: tomwalters@0: // Make sure only start time is transferred to the output tomwalters@0: output_.set_start_time(input.start_time()); tomwalters@0: tomwalters@0: // Loop over samples to make the SAI tomwalters@0: for (s = 0; s < input_buffer_length; ++s) { tomwalters@0: float decay_factor = pow(sai_decay_factor_, fire_counter_); tomwalters@0: // Loop over channels tomwalters@0: for (c = 0; c < input.channel_count(); ++c) { tomwalters@0: // Local convenience variables tomwalters@0: StrobeList &active_strobes = active_strobes_[c]; tomwalters@0: float centre_frequency = input.get_centre_frequency(c); tomwalters@0: int next_strobe = next_strobes_[c]; tomwalters@0: tomwalters@0: // 1. Update strobes tomwalters@0: // If we are up to or beyond the next strobe... tomwalters@0: if (next_strobe < input.strobe_count(c)) { tomwalters@0: if (s == pSigIn->getStrobe(iNextStrobe)) { tomwalters@0: //A new strobe has arrived tomwalters@0: // if there aren't already too many strobes active... tomwalters@0: if ((active_strobes.getStrobeCount() + 1) < max_concurrent_strobes_) { tomwalters@0: // ...add the active strobe to the list of current strobes tomwalters@0: // calculate the strobe weight tomwalters@0: float weight = 1.0f; tomwalters@0: if (active_strobes.getStrobeCount() > 0) { tomwalters@0: int last_strobe = active_strobes.getTime( tomwalters@0: active_strobes.getStrobeCount()); tomwalters@0: tomwalters@0: // If the strobe occured within 10 impulse-response tomwalters@0: // cycles of the previous strobe, then lower its weight tomwalters@0: weight = (s - iLastStrobe) / input.sample_rate() tomwalters@0: * centre_frequency / 10.0f; tomwalters@0: if (weight > 1.0f) tomwalters@0: weight = 1.0f; tomwalters@0: } tomwalters@0: pActiveStrobes->addStrobe(iCurrentSample, weight); tomwalters@0: iNextStrobe++; tomwalters@0: } else { tomwalters@0: // We have a problem tomwalters@0: aimASSERT(0); tomwalters@0: } tomwalters@0: tomwalters@0: // 2. Having updated the strobes, we now need to update the tomwalters@0: // strobe weights tomwalters@0: float total_strobe_weight = 0.0f; tomwalters@0: for (int si = 1; si <= pActiveStrobes->getStrobeCount(); ++si) { tomwalters@0: total_strobe_weight += (pActiveStrobes->getWeight(si) tomwalters@0: * m_pStrobeWeights[pActiveStrobes->getStrobeCount() - si]); tomwalters@0: } tomwalters@0: for (int si = 1; si <= pActiveStrobes->getStrobeCount(); ++si) { tomwalters@0: active_strobes.setWorkingWeight(si,(active_strobes.getWeight(si) tomwalters@0: * strobe_weights_[active_strobes.getStrobeCount() - si]) tomwalters@0: / total_strobe_weight); tomwalters@0: } tomwalters@0: } tomwalters@0: } tomwalters@0: tomwalters@0: // remove inactive strobes... tomwalters@0: while (pActiveStrobes->getStrobeCount() > 0) { tomwalters@0: // Get the time of the first strobe (ordering of strobes is tomwalters@0: // from one, not zero) tomwalters@0: int iStrobeTime = pActiveStrobes->getTime(1); tomwalters@0: int iDelay = iCurrentSample - iStrobeTime; tomwalters@0: // ... do we now need to remove this strobe? tomwalters@0: if (iDelay > m_maxStrobeDelayIdx) tomwalters@0: pActiveStrobes->deleteFirstStrobe(); tomwalters@0: else tomwalters@0: break; tomwalters@0: // Since the strobes are ordered, we don't need to go tomwalters@0: // beyond the first still-active strobe tomwalters@0: } tomwalters@0: tomwalters@0: // 3. Loop over active strobes tomwalters@0: for (int si = 1; si <= pActiveStrobes->getStrobeCount(); si++) { tomwalters@0: // 3.1 Add effect of active strobe at correct place in the SAI buffer tomwalters@0: // Calculate the time from the strobe event to 'now': iDelay tomwalters@0: int iStrobeTime = pActiveStrobes->getTime(si); tomwalters@0: int iDelay = iCurrentSample - iStrobeTime; tomwalters@0: tomwalters@0: // If the delay is greater than the (user-set) tomwalters@0: // minimum strobe delay, the strobe can be used tomwalters@0: if (iDelay >= m_minStrobeDelayIdx && iDelay < m_maxStrobeDelayIdx) { tomwalters@0: // The value at be added to the SAI tomwalters@0: float sig = pSigIn->getSample(iCurrentSample, audCh); tomwalters@0: tomwalters@0: // Weight the sample correctly tomwalters@0: sig *= pActiveStrobes->getWorkingWeight(si); tomwalters@0: tomwalters@0: // Adjust the weight acording to the number of samples until the tomwalters@0: // next output frame tomwalters@0: sig *= fDecayFactor; tomwalters@0: tomwalters@0: // Update the temporary SAI buffer tomwalters@0: pSigOut->setSample(iDelay, audCh, tomwalters@0: pSigOut->getSample(iDelay, audCh)+sig); tomwalters@0: } tomwalters@0: } tomwalters@0: tomwalters@0: m_pNextStrobes[bankCh]=iNextStrobe; tomwalters@0: tomwalters@0: } // End loop over channels tomwalters@0: tomwalters@0: tomwalters@0: //Check to see if we need to output an SAI frame this sample tomwalters@0: if (m_iFireCounter-- == 0) { tomwalters@0: // Decay the SAI by the correct amount and add the current output frame tomwalters@0: float decay = pow(sai_decay_factor_, fire_period_samples_); tomwalters@0: tomwalters@0: for (c = 0; c < input.channel_count(); ++c) { tomwalters@0: for (int i = 0; i < output_.buffer_length(); ++i) { tomwalters@0: output_.set_sample(c, i, sai_temp_[c][i] + output_[c][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@0: m_iFireCounter=m_iFirePeriodSamples-1; tomwalters@0: tomwalters@0: // Make sure the start time is transferred to the output tomwalters@0: m_pOutputData->setStartTime(m_pInputData->getSignal(0)->getStartTime()+(SIGNAL_SAMPLE)((float)iCurrentSample*1000.0f/(float)m_pInputData->getSamplerate())); tomwalters@0: PushOutput(); tomwalters@0: } tomwalters@0: } // End loop over samples tomwalters@0: } tomwalters@0: tomwalters@0: ModuleSAI::~ModuleSAI() { tomwalters@0: }