annotate src/Modules/SAI/ModuleSAI.cc @ 246:0a3342606855

- Allow processing without re-initialization.
author tom@acousticscale.org
date Tue, 26 Oct 2010 04:48:56 +0000
parents 9fcf55c040fe
children 2a69b38336c4
rev   line source
tomwalters@0 1 // Copyright 2006-2010, Thomas Walters
tomwalters@0 2 //
tomwalters@0 3 // AIM-C: A C++ implementation of the Auditory Image Model
tomwalters@0 4 // http://www.acousticscale.org/AIMC
tomwalters@0 5 //
tomwalters@45 6 // Licensed under the Apache License, Version 2.0 (the "License");
tomwalters@45 7 // you may not use this file except in compliance with the License.
tomwalters@45 8 // You may obtain a copy of the License at
tomwalters@0 9 //
tomwalters@45 10 // http://www.apache.org/licenses/LICENSE-2.0
tomwalters@0 11 //
tomwalters@45 12 // Unless required by applicable law or agreed to in writing, software
tomwalters@45 13 // distributed under the License is distributed on an "AS IS" BASIS,
tomwalters@45 14 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
tomwalters@45 15 // See the License for the specific language governing permissions and
tomwalters@45 16 // limitations under the License.
tomwalters@0 17
tomwalters@0 18 /*! \file
tomwalters@0 19 * \brief SAI module
tomwalters@0 20 */
tomwalters@0 21
tomwalters@0 22 /*
tomwalters@0 23 * \author Thomas Walters <tom@acousticscale.org>
tomwalters@0 24 * \date created 2007/08/29
tomwalters@23 25 * \version \$Id$
tomwalters@0 26 */
tomwalters@15 27 #include <cmath>
tomwalters@0 28
tomwalters@0 29 #include "Modules/SAI/ModuleSAI.h"
tomwalters@0 30
tomwalters@5 31 namespace aimc {
tomwalters@0 32 ModuleSAI::ModuleSAI(Parameters *parameters) : Module(parameters) {
tomwalters@5 33 module_identifier_ = "weighted_sai";
tomwalters@5 34 module_type_ = "sai";
tomwalters@0 35 module_description_ = "Stabilised auditory image";
tomwalters@23 36 module_version_ = "$Id$";
tomwalters@0 37
tomwalters@0 38 min_delay_ms_ = parameters_->DefaultFloat("sai.min_delay_ms", 0.0f);
tomwalters@0 39 max_delay_ms_ = parameters_->DefaultFloat("sai.max_delay_ms", 35.0f);
tomwalters@0 40 strobe_weight_alpha_ = parameters_->DefaultFloat("sai.strobe_weight_alpha",
tomwalters@0 41 0.5f);
tomwalters@0 42 buffer_memory_decay_ = parameters_->DefaultFloat("sai.buffer_memory_decay",
tomwalters@0 43 0.03f);
tomwalters@0 44 frame_period_ms_ = parameters_->DefaultFloat("sai.frame_period_ms", 20.0f);
tomwalters@0 45
tomwalters@5 46 max_concurrent_strobes_
tomwalters@5 47 = parameters_->DefaultInt("sai.max_concurrent_strobes", 50);
tomwalters@5 48
tomwalters@5 49 min_strobe_delay_idx_ = 0;
tomwalters@5 50 max_strobe_delay_idx_ = 0;
tomwalters@0 51 sai_decay_factor_ = 0.0f;
tomwalters@5 52 fire_counter_ = 0;
tomwalters@0 53 }
tomwalters@0 54
tomwalters@0 55 bool ModuleSAI::InitializeInternal(const SignalBank &input) {
tomwalters@0 56 // The SAI output bank must be as long as the SAI's Maximum delay.
tomwalters@0 57 // One sample is added to the SAI buffer length to account for the
tomwalters@0 58 // zero-lag point
tomwalters@6 59 int sai_buffer_length = 1 + floor(input.sample_rate() * max_delay_ms_
tomwalters@6 60 / 1000.0f);
tomwalters@5 61 channel_count_ = input.channel_count();
tomwalters@0 62
tomwalters@0 63 // Make an output SignalBank with the same number of channels and centre
tomwalters@0 64 // frequencies as the input, but with a different buffer length
tomwalters@0 65 if (!output_.Initialize(input.channel_count(),
tomwalters@0 66 sai_buffer_length,
tomwalters@5 67 input.sample_rate())) {
tomwalters@0 68 LOG_ERROR("Failed to create output buffer in SAI module");
tomwalters@0 69 return false;
tomwalters@0 70 }
tomwalters@8 71 for (int i = 0; i < input.channel_count(); ++i) {
tomwalters@5 72 output_.set_centre_frequency(i, input.centre_frequency(i));
tomwalters@0 73 }
tomwalters@0 74
tomwalters@0 75 // sai_temp_ will be initialized to zero
tomwalters@0 76 if (!sai_temp_.Initialize(output_)) {
tomwalters@0 77 LOG_ERROR("Failed to create temporary buffer in SAI module");
tomwalters@0 78 return false;
tomwalters@0 79 }
tomwalters@0 80
tomwalters@5 81 frame_period_samples_ = floor(input.sample_rate() * frame_period_ms_
tomwalters@5 82 / 1000.0f);
tomwalters@5 83 min_strobe_delay_idx_ = floor(input.sample_rate() * min_delay_ms_
tomwalters@5 84 / 1000.0f);
tomwalters@5 85 max_strobe_delay_idx_ = floor(input.sample_rate() * max_delay_ms_
tomwalters@5 86 / 1000.0f);
tomwalters@0 87
tomwalters@0 88 // Make sure we don't go past the output buffer's upper bound
tomwalters@5 89 if (max_strobe_delay_idx_ > output_.buffer_length()) {
tomwalters@0 90 max_strobe_delay_idx_ = output_.buffer_length();
tomwalters@5 91 }
tomwalters@0 92
tomwalters@0 93 // Define decay factor from time since last sample (see ti2003)
tomwalters@0 94 sai_decay_factor_ = pow(0.5f, 1.0f / (buffer_memory_decay_
tomwalters@0 95 * input.sample_rate()));
tomwalters@0 96
tomwalters@0 97 // Precompute strobe weights
tomwalters@0 98 strobe_weights_.resize(max_concurrent_strobes_);
tomwalters@0 99 for (int n = 0; n < max_concurrent_strobes_; ++n) {
tomwalters@5 100 strobe_weights_[n] = pow(1.0f / (n + 1), strobe_weight_alpha_);
tomwalters@0 101 }
tomwalters@0 102
tomwalters@5 103 ResetInternal();
tomwalters@0 104
tomwalters@0 105 return true;
tomwalters@0 106 }
tomwalters@0 107
tomwalters@3 108 void ModuleSAI::ResetInternal() {
tomwalters@5 109 // Active Strobes
tom@246 110 output_.Clear();
tom@246 111 sai_temp_.Clear();
tomwalters@5 112 active_strobes_.clear();
tomwalters@5 113 active_strobes_.resize(channel_count_);
tomwalters@5 114 fire_counter_ = frame_period_samples_ - 1;
tomwalters@0 115 }
tomwalters@0 116
tomwalters@0 117 void ModuleSAI::Process(const SignalBank &input) {
tomwalters@0 118 // Reset the next strobe times
tomwalters@0 119 next_strobes_.clear();
tomwalters@0 120 next_strobes_.resize(output_.channel_count(), 0);
tomwalters@0 121
tomwalters@0 122 // Offset the times on the strobes from the previous buffer
tomwalters@5 123 for (int ch = 0; ch < input.channel_count(); ++ch) {
tomwalters@5 124 active_strobes_[ch].ShiftStrobes(input.buffer_length());
tomwalters@0 125 }
tomwalters@0 126
tomwalters@0 127 // Loop over samples to make the SAI
tomwalters@5 128 for (int i = 0; i < input.buffer_length(); ++i) {
tomwalters@0 129 float decay_factor = pow(sai_decay_factor_, fire_counter_);
tomwalters@0 130 // Loop over channels
tomwalters@5 131 for (int ch = 0; ch < input.channel_count(); ++ch) {
tomwalters@0 132 // Local convenience variables
tomwalters@5 133 StrobeList &active_strobes = active_strobes_[ch];
tomwalters@5 134 int next_strobe_index = next_strobes_[ch];
tomwalters@0 135
tomwalters@5 136 // Update strobes
tomwalters@0 137 // If we are up to or beyond the next strobe...
tomwalters@5 138 if (next_strobe_index < input.strobe_count(ch)) {
tomwalters@5 139 if (i == input.strobe(ch, next_strobe_index)) {
tomwalters@5 140 // A new strobe has arrived.
tomwalters@5 141 // If there are too many strobes active, then get rid of the
tomwalters@5 142 // earliest one
tomwalters@5 143 if (active_strobes.strobe_count() >= max_concurrent_strobes_) {
tomwalters@5 144 active_strobes.DeleteFirstStrobe();
tomwalters@0 145 }
tomwalters@0 146
tomwalters@5 147 // Add the active strobe to the list of current strobes and
tomwalters@5 148 // calculate the strobe weight
tomwalters@5 149 float weight = 1.0f;
tomwalters@5 150 if (active_strobes.strobe_count() > 0) {
tomwalters@5 151 int last_strobe_time = active_strobes.Strobe(
tomwalters@5 152 active_strobes.strobe_count() - 1).time;
tomwalters@5 153
tomwalters@5 154 // If the strobe occured within 10 impulse-response
tomwalters@5 155 // cycles of the previous strobe, then lower its weight
tomwalters@5 156 weight = (i - last_strobe_time) / input.sample_rate()
tomwalters@5 157 * input.centre_frequency(ch) / 10.0f;
tomwalters@5 158 if (weight > 1.0f)
tomwalters@5 159 weight = 1.0f;
tomwalters@5 160 }
tomwalters@5 161 active_strobes.AddStrobe(i, weight);
tomwalters@5 162 next_strobe_index++;
tomwalters@5 163
tomwalters@5 164
tomwalters@5 165 // Update the strobe weights
tomwalters@0 166 float total_strobe_weight = 0.0f;
tomwalters@5 167 for (int si = 0; si < active_strobes.strobe_count(); ++si) {
tomwalters@5 168 total_strobe_weight += (active_strobes.Strobe(si).weight
tomwalters@5 169 * strobe_weights_[active_strobes.strobe_count() - si - 1]);
tomwalters@0 170 }
tomwalters@5 171 for (int si = 0; si < active_strobes.strobe_count(); ++si) {
tomwalters@5 172 active_strobes.SetWorkingWeight(si,
tomwalters@5 173 (active_strobes.Strobe(si).weight
tomwalters@5 174 * strobe_weights_[active_strobes.strobe_count() - si - 1])
tomwalters@5 175 / total_strobe_weight);
tomwalters@0 176 }
tomwalters@0 177 }
tomwalters@0 178 }
tomwalters@0 179
tomwalters@5 180 // Remove inactive strobes
tomwalters@5 181 while (active_strobes.strobe_count() > 0) {
tomwalters@5 182 // Get the relative time of the first strobe, and see if it exceeds
tomwalters@5 183 // the maximum allowed time.
tomwalters@5 184 if ((i - active_strobes.Strobe(0).time) > max_strobe_delay_idx_)
tomwalters@5 185 active_strobes.DeleteFirstStrobe();
tomwalters@0 186 else
tomwalters@0 187 break;
tomwalters@0 188 }
tomwalters@0 189
tomwalters@5 190 // Update the SAI buffer with the weighted effect of all the active
tomwalters@5 191 // strobes at the current sample
tomwalters@5 192 for (int si = 0; si < active_strobes.strobe_count(); ++si) {
tomwalters@5 193 // Add the effect of active strobe at correct place in the SAI buffer
tomwalters@5 194 // Calculate 'delay', the time from the strobe event to now
tomwalters@5 195 int delay = i - active_strobes.Strobe(si).time;
tomwalters@0 196
tomwalters@0 197 // If the delay is greater than the (user-set)
tomwalters@0 198 // minimum strobe delay, the strobe can be used
tomwalters@5 199 if (delay >= min_strobe_delay_idx_ && delay < max_strobe_delay_idx_) {
tomwalters@0 200 // The value at be added to the SAI
tomwalters@5 201 float sig = input.sample(ch, i);
tomwalters@0 202
tomwalters@0 203 // Weight the sample correctly
tomwalters@5 204 sig *= active_strobes.Strobe(si).working_weight;
tomwalters@0 205
tomwalters@0 206 // Adjust the weight acording to the number of samples until the
tomwalters@0 207 // next output frame
tomwalters@5 208 sig *= decay_factor;
tomwalters@0 209
tomwalters@0 210 // Update the temporary SAI buffer
tomwalters@5 211 output_.set_sample(ch, delay, output_.sample(ch, delay) + sig);
tomwalters@0 212 }
tomwalters@0 213 }
tomwalters@0 214
tomwalters@5 215 next_strobes_[ch] = next_strobe_index;
tomwalters@8 216 } // End loop over channels
tomwalters@0 217
tomwalters@5 218 fire_counter_--;
tomwalters@0 219
tomwalters@5 220 // Check to see if we need to output an SAI frame on this sample
tomwalters@5 221 if (fire_counter_ <= 0) {
tomwalters@0 222 // Decay the SAI by the correct amount and add the current output frame
tomwalters@5 223 float decay = pow(sai_decay_factor_, frame_period_samples_);
tomwalters@0 224
tomwalters@5 225 for (int ch = 0; ch < input.channel_count(); ++ch) {
tomwalters@0 226 for (int i = 0; i < output_.buffer_length(); ++i) {
tomwalters@5 227 output_.set_sample(ch, i,
tomwalters@5 228 sai_temp_[ch][i] + output_[ch][i] * decay);
tomwalters@0 229 }
tomwalters@0 230 }
tomwalters@0 231
tomwalters@0 232 // Zero the temporary signal
tomwalters@0 233 for (int ch = 0; ch < sai_temp_.channel_count(); ++ch) {
tomwalters@0 234 for (int i = 0; i < sai_temp_.buffer_length(); ++i) {
tomwalters@0 235 sai_temp_.set_sample(ch, i, 0.0f);
tomwalters@0 236 }
tomwalters@0 237 }
tomwalters@0 238
tomwalters@5 239 fire_counter_ = frame_period_samples_ - 1;
tomwalters@0 240
tomwalters@5 241 // Transfer the current time to the output buffer
tomwalters@5 242 output_.set_start_time(input.start_time() + i);
tomwalters@0 243 PushOutput();
tomwalters@0 244 }
tomwalters@8 245 } // End loop over samples
tomwalters@0 246 }
tomwalters@0 247
tomwalters@0 248 ModuleSAI::~ModuleSAI() {
tomwalters@0 249 }
tomwalters@5 250 } // namespace aimc