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