Mercurial > hg > aimc
diff src/Modules/SAI/ModuleSAI.cc @ 0:582cbe817f2c
- Initial add of support code and modules. Not everything is working yet.
author | tomwalters |
---|---|
date | Fri, 12 Feb 2010 12:31:23 +0000 |
parents | |
children | decdac21cfc2 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Modules/SAI/ModuleSAI.cc Fri Feb 12 12:31:23 2010 +0000 @@ -0,0 +1,261 @@ +// Copyright 2006-2010, Thomas Walters +// +// AIM-C: A C++ implementation of the Auditory Image Model +// http://www.acousticscale.org/AIMC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +/*! \file + * \brief SAI module + */ + +/* + * \author Thomas Walters <tom@acousticscale.org> + * \date created 2007/08/29 + * \version \$Id: ModuleSAI.cc 4 2010-02-03 18:44:58Z tcw $ + */ +#include <math.h> + +#include "Modules/SAI/ModuleSAI.h" + +ModuleSAI::ModuleSAI(Parameters *parameters) : Module(parameters) { + module_description_ = "Stabilised auditory image"; + module_name_ = "sai2007"; + module_type_ = "sai"; + module_id_ = "$Id: ModuleSAI.cc 4 2010-02-03 18:44:58Z tcw $"; + + min_delay_ms_ = parameters_->DefaultFloat("sai.min_delay_ms", 0.0f); + max_delay_ms_ = parameters_->DefaultFloat("sai.max_delay_ms", 35.0f); + strobe_weight_alpha_ = parameters_->DefaultFloat("sai.strobe_weight_alpha", + 0.5f); + buffer_memory_decay_ = parameters_->DefaultFloat("sai.buffer_memory_decay", + 0.03f); + frame_period_ms_ = parameters_->DefaultFloat("sai.frame_period_ms", 20.0f); + + min_strobe_delay_index_ = 0; + max_strobe_delay_index_ = 0; + sai_decay_factor_ = 0.0f; + max_concurrent_strobes_ = 0; + output_frame_period_ms_ = 0.0f; + last_output_frame_time_ms_ = 0.0f; +} + +bool ModuleSAI::InitializeInternal(const SignalBank &input) { + // The SAI output bank must be as long as the SAI's Maximum delay. + // One sample is added to the SAI buffer length to account for the + // zero-lag point + int sai_buffer_length = 1 + Round(input.sample_rate() * max_delay_ms_); + + // Make an output SignalBank with the same number of channels and centre + // frequencies as the input, but with a different buffer length + if (!output_.Initialize(input.channel_count(), + sai_buffer_length, + input.sample_rate());) { + LOG_ERROR("Failed to create output buffer in SAI module"); + return false; + } + for (int i = 0; i < input.channel_count(); ++i ) { + output_.set_centre_frequency(i, input.get_centre_frequency(i)); + } + + // sai_temp_ will be initialized to zero + if (!sai_temp_.Initialize(output_)) { + LOG_ERROR("Failed to create temporary buffer in SAI module"); + return false; + } + + last_output_time_ms_ = 0.0f; + + frame_period_samples_ = Round(input.sample_rate() + * frame_period_ms_ / 1000.0f); + + min_strobe_delay_idx_ = Round(input.sample_rate() * min_delay_ms_ / 1000.0f); + max_strobe_delay_idx_ = Round(input.sample_rate() * max_delay_ms_ / 1000.0f); + + // Make sure we don't go past the output buffer's upper bound + if (max_strobe_delay_idx_ > output_.buffer_length())) + max_strobe_delay_idx_ = output_.buffer_length(); + + // Define decay factor from time since last sample (see ti2003) + sai_decay_factor_ = pow(0.5f, 1.0f / (buffer_memory_decay_ + * input.sample_rate())); + + // Maximum strobes that can be active at the same time within maxdelay. + //! \todo Choose this value in a more principled way + max_concurrent_strobes_ = Round(1000.0f * max_delay_ * 5); + + // Precompute strobe weights + strobe_weights_.resize(max_concurrent_strobes_); + for (int n = 0; n < max_concurrent_strobes_; ++n) { + strobe_weights_[n] = pow(1.0f / (n + 1)), strobe_weight_alpha_); + } + + // Active Strobes + active_strobes_.Resize(input.channel_count()); + for (int i = 0; i < input.channel_count(); ++i) { + active_strobes_[i].Create(max_concurrent_strobes_); + } + next_strobes_.resize(input.channel_count(), 0); + + return true; +} + +void ModuleSAI::Reset() { +} + +void ModuleSAI::Process(const SignalBank &input) { + int s; + int c; + int output_buffer_length = output_.buffer_length(); + + // Reset the next strobe times + next_strobes_.clear(); + next_strobes_.resize(output_.channel_count(), 0); + + // Offset the times on the strobes from the previous buffer + for (c = 0; c < input.channel_count(), ++c) { + active_strobes_[c].shiftStrobes(input.buffer_length()); + } + + // Make sure only start time is transferred to the output + output_.set_start_time(input.start_time()); + + // Loop over samples to make the SAI + for (s = 0; s < input_buffer_length; ++s) { + float decay_factor = pow(sai_decay_factor_, fire_counter_); + // Loop over channels + for (c = 0; c < input.channel_count(); ++c) { + // Local convenience variables + StrobeList &active_strobes = active_strobes_[c]; + float centre_frequency = input.get_centre_frequency(c); + int next_strobe = next_strobes_[c]; + + // 1. Update strobes + // If we are up to or beyond the next strobe... + if (next_strobe < input.strobe_count(c)) { + if (s == pSigIn->getStrobe(iNextStrobe)) { + //A new strobe has arrived + // if there aren't already too many strobes active... + if ((active_strobes.getStrobeCount() + 1) < max_concurrent_strobes_) { + // ...add the active strobe to the list of current strobes + // calculate the strobe weight + float weight = 1.0f; + if (active_strobes.getStrobeCount() > 0) { + int last_strobe = active_strobes.getTime( + active_strobes.getStrobeCount()); + + // If the strobe occured within 10 impulse-response + // cycles of the previous strobe, then lower its weight + weight = (s - iLastStrobe) / input.sample_rate() + * centre_frequency / 10.0f; + if (weight > 1.0f) + weight = 1.0f; + } + pActiveStrobes->addStrobe(iCurrentSample, weight); + iNextStrobe++; + } else { + // We have a problem + aimASSERT(0); + } + + // 2. Having updated the strobes, we now need to update the + // strobe weights + float total_strobe_weight = 0.0f; + for (int si = 1; si <= pActiveStrobes->getStrobeCount(); ++si) { + total_strobe_weight += (pActiveStrobes->getWeight(si) + * m_pStrobeWeights[pActiveStrobes->getStrobeCount() - si]); + } + for (int si = 1; si <= pActiveStrobes->getStrobeCount(); ++si) { + active_strobes.setWorkingWeight(si,(active_strobes.getWeight(si) + * strobe_weights_[active_strobes.getStrobeCount() - si]) + / total_strobe_weight); + } + } + } + + // remove inactive strobes... + while (pActiveStrobes->getStrobeCount() > 0) { + // Get the time of the first strobe (ordering of strobes is + // from one, not zero) + int iStrobeTime = pActiveStrobes->getTime(1); + int iDelay = iCurrentSample - iStrobeTime; + // ... do we now need to remove this strobe? + if (iDelay > m_maxStrobeDelayIdx) + pActiveStrobes->deleteFirstStrobe(); + else + break; + // Since the strobes are ordered, we don't need to go + // beyond the first still-active strobe + } + + // 3. Loop over active strobes + for (int si = 1; si <= pActiveStrobes->getStrobeCount(); si++) { + // 3.1 Add effect of active strobe at correct place in the SAI buffer + // Calculate the time from the strobe event to 'now': iDelay + int iStrobeTime = pActiveStrobes->getTime(si); + int iDelay = iCurrentSample - iStrobeTime; + + // If the delay is greater than the (user-set) + // minimum strobe delay, the strobe can be used + if (iDelay >= m_minStrobeDelayIdx && iDelay < m_maxStrobeDelayIdx) { + // The value at be added to the SAI + float sig = pSigIn->getSample(iCurrentSample, audCh); + + // Weight the sample correctly + sig *= pActiveStrobes->getWorkingWeight(si); + + // Adjust the weight acording to the number of samples until the + // next output frame + sig *= fDecayFactor; + + // Update the temporary SAI buffer + pSigOut->setSample(iDelay, audCh, + pSigOut->getSample(iDelay, audCh)+sig); + } + } + + m_pNextStrobes[bankCh]=iNextStrobe; + + } // End loop over channels + + + //Check to see if we need to output an SAI frame this sample + if (m_iFireCounter-- == 0) { + // Decay the SAI by the correct amount and add the current output frame + float decay = pow(sai_decay_factor_, fire_period_samples_); + + for (c = 0; c < input.channel_count(); ++c) { + for (int i = 0; i < output_.buffer_length(); ++i) { + output_.set_sample(c, i, sai_temp_[c][i] + output_[c][i] * decay); + } + } + + // Zero the temporary signal + for (int ch = 0; ch < sai_temp_.channel_count(); ++ch) { + for (int i = 0; i < sai_temp_.buffer_length(); ++i) { + sai_temp_.set_sample(ch, i, 0.0f); + } + } + + m_iFireCounter=m_iFirePeriodSamples-1; + + // Make sure the start time is transferred to the output + m_pOutputData->setStartTime(m_pInputData->getSignal(0)->getStartTime()+(SIGNAL_SAMPLE)((float)iCurrentSample*1000.0f/(float)m_pInputData->getSamplerate())); + PushOutput(); + } + } // End loop over samples +} + +ModuleSAI::~ModuleSAI() { +}