annotate trunk/src/Modules/SAI/ModuleSAI.cc @ 272:2147f317aedc

Created wiki page through web user interface.
author tomwalters
date Sun, 14 Feb 2010 22:42:58 +0000
parents e14c70d1b171
children ce2bab04f155
rev   line source
tomwalters@268 1 // Copyright 2006-2010, Thomas Walters
tomwalters@268 2 //
tomwalters@268 3 // AIM-C: A C++ implementation of the Auditory Image Model
tomwalters@268 4 // http://www.acousticscale.org/AIMC
tomwalters@268 5 //
tomwalters@268 6 // This program is free software: you can redistribute it and/or modify
tomwalters@268 7 // it under the terms of the GNU General Public License as published by
tomwalters@268 8 // the Free Software Foundation, either version 3 of the License, or
tomwalters@268 9 // (at your option) any later version.
tomwalters@268 10 //
tomwalters@268 11 // This program is distributed in the hope that it will be useful,
tomwalters@268 12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
tomwalters@268 13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
tomwalters@268 14 // GNU General Public License for more details.
tomwalters@268 15 //
tomwalters@268 16 // You should have received a copy of the GNU General Public License
tomwalters@268 17 // along with this program. If not, see <http://www.gnu.org/licenses/>.
tomwalters@268 18
tomwalters@268 19 /*! \file
tomwalters@268 20 * \brief SAI module
tomwalters@268 21 */
tomwalters@268 22
tomwalters@268 23 /*
tomwalters@268 24 * \author Thomas Walters <tom@acousticscale.org>
tomwalters@268 25 * \date created 2007/08/29
tomwalters@268 26 * \version \$Id: ModuleSAI.cc 4 2010-02-03 18:44:58Z tcw $
tomwalters@268 27 */
tomwalters@268 28 #include <math.h>
tomwalters@268 29
tomwalters@268 30 #include "Modules/SAI/ModuleSAI.h"
tomwalters@268 31
tomwalters@268 32 ModuleSAI::ModuleSAI(Parameters *parameters) : Module(parameters) {
tomwalters@268 33 module_description_ = "Stabilised auditory image";
tomwalters@268 34 module_name_ = "sai2007";
tomwalters@268 35 module_type_ = "sai";
tomwalters@268 36 module_id_ = "$Id: ModuleSAI.cc 4 2010-02-03 18:44:58Z tcw $";
tomwalters@268 37
tomwalters@268 38 min_delay_ms_ = parameters_->DefaultFloat("sai.min_delay_ms", 0.0f);
tomwalters@268 39 max_delay_ms_ = parameters_->DefaultFloat("sai.max_delay_ms", 35.0f);
tomwalters@268 40 strobe_weight_alpha_ = parameters_->DefaultFloat("sai.strobe_weight_alpha",
tomwalters@268 41 0.5f);
tomwalters@268 42 buffer_memory_decay_ = parameters_->DefaultFloat("sai.buffer_memory_decay",
tomwalters@268 43 0.03f);
tomwalters@268 44 frame_period_ms_ = parameters_->DefaultFloat("sai.frame_period_ms", 20.0f);
tomwalters@268 45
tomwalters@268 46 min_strobe_delay_index_ = 0;
tomwalters@268 47 max_strobe_delay_index_ = 0;
tomwalters@268 48 sai_decay_factor_ = 0.0f;
tomwalters@268 49 max_concurrent_strobes_ = 0;
tomwalters@268 50 output_frame_period_ms_ = 0.0f;
tomwalters@268 51 last_output_frame_time_ms_ = 0.0f;
tomwalters@268 52 }
tomwalters@268 53
tomwalters@268 54 bool ModuleSAI::InitializeInternal(const SignalBank &input) {
tomwalters@268 55 // The SAI output bank must be as long as the SAI's Maximum delay.
tomwalters@268 56 // One sample is added to the SAI buffer length to account for the
tomwalters@268 57 // zero-lag point
tomwalters@268 58 int sai_buffer_length = 1 + Round(input.sample_rate() * max_delay_ms_);
tomwalters@268 59
tomwalters@268 60 // Make an output SignalBank with the same number of channels and centre
tomwalters@268 61 // frequencies as the input, but with a different buffer length
tomwalters@268 62 if (!output_.Initialize(input.channel_count(),
tomwalters@268 63 sai_buffer_length,
tomwalters@268 64 input.sample_rate());) {
tomwalters@268 65 LOG_ERROR("Failed to create output buffer in SAI module");
tomwalters@268 66 return false;
tomwalters@268 67 }
tomwalters@268 68 for (int i = 0; i < input.channel_count(); ++i ) {
tomwalters@268 69 output_.set_centre_frequency(i, input.get_centre_frequency(i));
tomwalters@268 70 }
tomwalters@268 71
tomwalters@268 72 // sai_temp_ will be initialized to zero
tomwalters@268 73 if (!sai_temp_.Initialize(output_)) {
tomwalters@268 74 LOG_ERROR("Failed to create temporary buffer in SAI module");
tomwalters@268 75 return false;
tomwalters@268 76 }
tomwalters@268 77
tomwalters@268 78 last_output_time_ms_ = 0.0f;
tomwalters@268 79
tomwalters@268 80 frame_period_samples_ = Round(input.sample_rate()
tomwalters@268 81 * frame_period_ms_ / 1000.0f);
tomwalters@268 82
tomwalters@268 83 min_strobe_delay_idx_ = Round(input.sample_rate() * min_delay_ms_ / 1000.0f);
tomwalters@268 84 max_strobe_delay_idx_ = Round(input.sample_rate() * max_delay_ms_ / 1000.0f);
tomwalters@268 85
tomwalters@268 86 // Make sure we don't go past the output buffer's upper bound
tomwalters@268 87 if (max_strobe_delay_idx_ > output_.buffer_length()))
tomwalters@268 88 max_strobe_delay_idx_ = output_.buffer_length();
tomwalters@268 89
tomwalters@268 90 // Define decay factor from time since last sample (see ti2003)
tomwalters@268 91 sai_decay_factor_ = pow(0.5f, 1.0f / (buffer_memory_decay_
tomwalters@268 92 * input.sample_rate()));
tomwalters@268 93
tomwalters@268 94 // Maximum strobes that can be active at the same time within maxdelay.
tomwalters@268 95 //! \todo Choose this value in a more principled way
tomwalters@268 96 max_concurrent_strobes_ = Round(1000.0f * max_delay_ * 5);
tomwalters@268 97
tomwalters@268 98 // Precompute strobe weights
tomwalters@268 99 strobe_weights_.resize(max_concurrent_strobes_);
tomwalters@268 100 for (int n = 0; n < max_concurrent_strobes_; ++n) {
tomwalters@268 101 strobe_weights_[n] = pow(1.0f / (n + 1)), strobe_weight_alpha_);
tomwalters@268 102 }
tomwalters@268 103
tomwalters@268 104 // Active Strobes
tomwalters@268 105 active_strobes_.Resize(input.channel_count());
tomwalters@268 106 for (int i = 0; i < input.channel_count(); ++i) {
tomwalters@268 107 active_strobes_[i].Create(max_concurrent_strobes_);
tomwalters@268 108 }
tomwalters@268 109 next_strobes_.resize(input.channel_count(), 0);
tomwalters@268 110
tomwalters@268 111 return true;
tomwalters@268 112 }
tomwalters@268 113
tomwalters@268 114 void ModuleSAI::Reset() {
tomwalters@268 115 }
tomwalters@268 116
tomwalters@268 117 void ModuleSAI::Process(const SignalBank &input) {
tomwalters@268 118 int s;
tomwalters@268 119 int c;
tomwalters@268 120 int output_buffer_length = output_.buffer_length();
tomwalters@268 121
tomwalters@268 122 // Reset the next strobe times
tomwalters@268 123 next_strobes_.clear();
tomwalters@268 124 next_strobes_.resize(output_.channel_count(), 0);
tomwalters@268 125
tomwalters@268 126 // Offset the times on the strobes from the previous buffer
tomwalters@268 127 for (c = 0; c < input.channel_count(), ++c) {
tomwalters@268 128 active_strobes_[c].shiftStrobes(input.buffer_length());
tomwalters@268 129 }
tomwalters@268 130
tomwalters@268 131 // Make sure only start time is transferred to the output
tomwalters@268 132 output_.set_start_time(input.start_time());
tomwalters@268 133
tomwalters@268 134 // Loop over samples to make the SAI
tomwalters@268 135 for (s = 0; s < input_buffer_length; ++s) {
tomwalters@268 136 float decay_factor = pow(sai_decay_factor_, fire_counter_);
tomwalters@268 137 // Loop over channels
tomwalters@268 138 for (c = 0; c < input.channel_count(); ++c) {
tomwalters@268 139 // Local convenience variables
tomwalters@268 140 StrobeList &active_strobes = active_strobes_[c];
tomwalters@268 141 float centre_frequency = input.get_centre_frequency(c);
tomwalters@268 142 int next_strobe = next_strobes_[c];
tomwalters@268 143
tomwalters@268 144 // 1. Update strobes
tomwalters@268 145 // If we are up to or beyond the next strobe...
tomwalters@268 146 if (next_strobe < input.strobe_count(c)) {
tomwalters@268 147 if (s == pSigIn->getStrobe(iNextStrobe)) {
tomwalters@268 148 //A new strobe has arrived
tomwalters@268 149 // if there aren't already too many strobes active...
tomwalters@268 150 if ((active_strobes.getStrobeCount() + 1) < max_concurrent_strobes_) {
tomwalters@268 151 // ...add the active strobe to the list of current strobes
tomwalters@268 152 // calculate the strobe weight
tomwalters@268 153 float weight = 1.0f;
tomwalters@268 154 if (active_strobes.getStrobeCount() > 0) {
tomwalters@268 155 int last_strobe = active_strobes.getTime(
tomwalters@268 156 active_strobes.getStrobeCount());
tomwalters@268 157
tomwalters@268 158 // If the strobe occured within 10 impulse-response
tomwalters@268 159 // cycles of the previous strobe, then lower its weight
tomwalters@268 160 weight = (s - iLastStrobe) / input.sample_rate()
tomwalters@268 161 * centre_frequency / 10.0f;
tomwalters@268 162 if (weight > 1.0f)
tomwalters@268 163 weight = 1.0f;
tomwalters@268 164 }
tomwalters@268 165 pActiveStrobes->addStrobe(iCurrentSample, weight);
tomwalters@268 166 iNextStrobe++;
tomwalters@268 167 } else {
tomwalters@268 168 // We have a problem
tomwalters@268 169 aimASSERT(0);
tomwalters@268 170 }
tomwalters@268 171
tomwalters@268 172 // 2. Having updated the strobes, we now need to update the
tomwalters@268 173 // strobe weights
tomwalters@268 174 float total_strobe_weight = 0.0f;
tomwalters@268 175 for (int si = 1; si <= pActiveStrobes->getStrobeCount(); ++si) {
tomwalters@268 176 total_strobe_weight += (pActiveStrobes->getWeight(si)
tomwalters@268 177 * m_pStrobeWeights[pActiveStrobes->getStrobeCount() - si]);
tomwalters@268 178 }
tomwalters@268 179 for (int si = 1; si <= pActiveStrobes->getStrobeCount(); ++si) {
tomwalters@268 180 active_strobes.setWorkingWeight(si,(active_strobes.getWeight(si)
tomwalters@268 181 * strobe_weights_[active_strobes.getStrobeCount() - si])
tomwalters@268 182 / total_strobe_weight);
tomwalters@268 183 }
tomwalters@268 184 }
tomwalters@268 185 }
tomwalters@268 186
tomwalters@268 187 // remove inactive strobes...
tomwalters@268 188 while (pActiveStrobes->getStrobeCount() > 0) {
tomwalters@268 189 // Get the time of the first strobe (ordering of strobes is
tomwalters@268 190 // from one, not zero)
tomwalters@268 191 int iStrobeTime = pActiveStrobes->getTime(1);
tomwalters@268 192 int iDelay = iCurrentSample - iStrobeTime;
tomwalters@268 193 // ... do we now need to remove this strobe?
tomwalters@268 194 if (iDelay > m_maxStrobeDelayIdx)
tomwalters@268 195 pActiveStrobes->deleteFirstStrobe();
tomwalters@268 196 else
tomwalters@268 197 break;
tomwalters@268 198 // Since the strobes are ordered, we don't need to go
tomwalters@268 199 // beyond the first still-active strobe
tomwalters@268 200 }
tomwalters@268 201
tomwalters@268 202 // 3. Loop over active strobes
tomwalters@268 203 for (int si = 1; si <= pActiveStrobes->getStrobeCount(); si++) {
tomwalters@268 204 // 3.1 Add effect of active strobe at correct place in the SAI buffer
tomwalters@268 205 // Calculate the time from the strobe event to 'now': iDelay
tomwalters@268 206 int iStrobeTime = pActiveStrobes->getTime(si);
tomwalters@268 207 int iDelay = iCurrentSample - iStrobeTime;
tomwalters@268 208
tomwalters@268 209 // If the delay is greater than the (user-set)
tomwalters@268 210 // minimum strobe delay, the strobe can be used
tomwalters@268 211 if (iDelay >= m_minStrobeDelayIdx && iDelay < m_maxStrobeDelayIdx) {
tomwalters@268 212 // The value at be added to the SAI
tomwalters@268 213 float sig = pSigIn->getSample(iCurrentSample, audCh);
tomwalters@268 214
tomwalters@268 215 // Weight the sample correctly
tomwalters@268 216 sig *= pActiveStrobes->getWorkingWeight(si);
tomwalters@268 217
tomwalters@268 218 // Adjust the weight acording to the number of samples until the
tomwalters@268 219 // next output frame
tomwalters@268 220 sig *= fDecayFactor;
tomwalters@268 221
tomwalters@268 222 // Update the temporary SAI buffer
tomwalters@268 223 pSigOut->setSample(iDelay, audCh,
tomwalters@268 224 pSigOut->getSample(iDelay, audCh)+sig);
tomwalters@268 225 }
tomwalters@268 226 }
tomwalters@268 227
tomwalters@268 228 m_pNextStrobes[bankCh]=iNextStrobe;
tomwalters@268 229
tomwalters@268 230 } // End loop over channels
tomwalters@268 231
tomwalters@268 232
tomwalters@268 233 //Check to see if we need to output an SAI frame this sample
tomwalters@268 234 if (m_iFireCounter-- == 0) {
tomwalters@268 235 // Decay the SAI by the correct amount and add the current output frame
tomwalters@268 236 float decay = pow(sai_decay_factor_, fire_period_samples_);
tomwalters@268 237
tomwalters@268 238 for (c = 0; c < input.channel_count(); ++c) {
tomwalters@268 239 for (int i = 0; i < output_.buffer_length(); ++i) {
tomwalters@268 240 output_.set_sample(c, i, sai_temp_[c][i] + output_[c][i] * decay);
tomwalters@268 241 }
tomwalters@268 242 }
tomwalters@268 243
tomwalters@268 244 // Zero the temporary signal
tomwalters@268 245 for (int ch = 0; ch < sai_temp_.channel_count(); ++ch) {
tomwalters@268 246 for (int i = 0; i < sai_temp_.buffer_length(); ++i) {
tomwalters@268 247 sai_temp_.set_sample(ch, i, 0.0f);
tomwalters@268 248 }
tomwalters@268 249 }
tomwalters@268 250
tomwalters@268 251 m_iFireCounter=m_iFirePeriodSamples-1;
tomwalters@268 252
tomwalters@268 253 // Make sure the start time is transferred to the output
tomwalters@268 254 m_pOutputData->setStartTime(m_pInputData->getSignal(0)->getStartTime()+(SIGNAL_SAMPLE)((float)iCurrentSample*1000.0f/(float)m_pInputData->getSamplerate()));
tomwalters@268 255 PushOutput();
tomwalters@268 256 }
tomwalters@268 257 } // End loop over samples
tomwalters@268 258 }
tomwalters@268 259
tomwalters@268 260 ModuleSAI::~ModuleSAI() {
tomwalters@268 261 }