annotate src/Modules/SAI/ModuleSAI.cc @ 121:3cdaa81c3aca

- Massive refactoring to make module tree stuff work. In theory we now support configuration files again. The graphics stuff is untested as yet.
author tomwalters
date Mon, 18 Oct 2010 04:42:28 +0000
parents c5f5e9569863
children 0c492eada814
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
tomwalters@5 110 active_strobes_.clear();
tomwalters@5 111 active_strobes_.resize(channel_count_);
tomwalters@5 112 fire_counter_ = frame_period_samples_ - 1;
tomwalters@0 113 }
tomwalters@0 114
tomwalters@0 115 void ModuleSAI::Process(const SignalBank &input) {
tomwalters@0 116 // Reset the next strobe times
tomwalters@0 117 next_strobes_.clear();
tomwalters@0 118 next_strobes_.resize(output_.channel_count(), 0);
tomwalters@0 119
tomwalters@0 120 // Offset the times on the strobes from the previous buffer
tomwalters@5 121 for (int ch = 0; ch < input.channel_count(); ++ch) {
tomwalters@5 122 active_strobes_[ch].ShiftStrobes(input.buffer_length());
tomwalters@0 123 }
tomwalters@0 124
tomwalters@0 125 // Loop over samples to make the SAI
tomwalters@5 126 for (int i = 0; i < input.buffer_length(); ++i) {
tomwalters@0 127 float decay_factor = pow(sai_decay_factor_, fire_counter_);
tomwalters@0 128 // Loop over channels
tomwalters@5 129 for (int ch = 0; ch < input.channel_count(); ++ch) {
tomwalters@0 130 // Local convenience variables
tomwalters@5 131 StrobeList &active_strobes = active_strobes_[ch];
tomwalters@5 132 int next_strobe_index = next_strobes_[ch];
tomwalters@0 133
tomwalters@5 134 // Update strobes
tomwalters@0 135 // If we are up to or beyond the next strobe...
tomwalters@5 136 if (next_strobe_index < input.strobe_count(ch)) {
tomwalters@5 137 if (i == input.strobe(ch, next_strobe_index)) {
tomwalters@5 138 // A new strobe has arrived.
tomwalters@5 139 // If there are too many strobes active, then get rid of the
tomwalters@5 140 // earliest one
tomwalters@5 141 if (active_strobes.strobe_count() >= max_concurrent_strobes_) {
tomwalters@5 142 active_strobes.DeleteFirstStrobe();
tomwalters@0 143 }
tomwalters@0 144
tomwalters@5 145 // Add the active strobe to the list of current strobes and
tomwalters@5 146 // calculate the strobe weight
tomwalters@5 147 float weight = 1.0f;
tomwalters@5 148 if (active_strobes.strobe_count() > 0) {
tomwalters@5 149 int last_strobe_time = active_strobes.Strobe(
tomwalters@5 150 active_strobes.strobe_count() - 1).time;
tomwalters@5 151
tomwalters@5 152 // If the strobe occured within 10 impulse-response
tomwalters@5 153 // cycles of the previous strobe, then lower its weight
tomwalters@5 154 weight = (i - last_strobe_time) / input.sample_rate()
tomwalters@5 155 * input.centre_frequency(ch) / 10.0f;
tomwalters@5 156 if (weight > 1.0f)
tomwalters@5 157 weight = 1.0f;
tomwalters@5 158 }
tomwalters@5 159 active_strobes.AddStrobe(i, weight);
tomwalters@5 160 next_strobe_index++;
tomwalters@5 161
tomwalters@5 162
tomwalters@5 163 // Update the strobe weights
tomwalters@0 164 float total_strobe_weight = 0.0f;
tomwalters@5 165 for (int si = 0; si < active_strobes.strobe_count(); ++si) {
tomwalters@5 166 total_strobe_weight += (active_strobes.Strobe(si).weight
tomwalters@5 167 * strobe_weights_[active_strobes.strobe_count() - si - 1]);
tomwalters@0 168 }
tomwalters@5 169 for (int si = 0; si < active_strobes.strobe_count(); ++si) {
tomwalters@5 170 active_strobes.SetWorkingWeight(si,
tomwalters@5 171 (active_strobes.Strobe(si).weight
tomwalters@5 172 * strobe_weights_[active_strobes.strobe_count() - si - 1])
tomwalters@5 173 / total_strobe_weight);
tomwalters@0 174 }
tomwalters@0 175 }
tomwalters@0 176 }
tomwalters@0 177
tomwalters@5 178 // Remove inactive strobes
tomwalters@5 179 while (active_strobes.strobe_count() > 0) {
tomwalters@5 180 // Get the relative time of the first strobe, and see if it exceeds
tomwalters@5 181 // the maximum allowed time.
tomwalters@5 182 if ((i - active_strobes.Strobe(0).time) > max_strobe_delay_idx_)
tomwalters@5 183 active_strobes.DeleteFirstStrobe();
tomwalters@0 184 else
tomwalters@0 185 break;
tomwalters@0 186 }
tomwalters@0 187
tomwalters@5 188 // Update the SAI buffer with the weighted effect of all the active
tomwalters@5 189 // strobes at the current sample
tomwalters@5 190 for (int si = 0; si < active_strobes.strobe_count(); ++si) {
tomwalters@5 191 // Add the effect of active strobe at correct place in the SAI buffer
tomwalters@5 192 // Calculate 'delay', the time from the strobe event to now
tomwalters@5 193 int delay = i - active_strobes.Strobe(si).time;
tomwalters@0 194
tomwalters@0 195 // If the delay is greater than the (user-set)
tomwalters@0 196 // minimum strobe delay, the strobe can be used
tomwalters@5 197 if (delay >= min_strobe_delay_idx_ && delay < max_strobe_delay_idx_) {
tomwalters@0 198 // The value at be added to the SAI
tomwalters@5 199 float sig = input.sample(ch, i);
tomwalters@0 200
tomwalters@0 201 // Weight the sample correctly
tomwalters@5 202 sig *= active_strobes.Strobe(si).working_weight;
tomwalters@0 203
tomwalters@0 204 // Adjust the weight acording to the number of samples until the
tomwalters@0 205 // next output frame
tomwalters@5 206 sig *= decay_factor;
tomwalters@0 207
tomwalters@0 208 // Update the temporary SAI buffer
tomwalters@5 209 output_.set_sample(ch, delay, output_.sample(ch, delay) + sig);
tomwalters@0 210 }
tomwalters@0 211 }
tomwalters@0 212
tomwalters@5 213 next_strobes_[ch] = next_strobe_index;
tomwalters@8 214 } // End loop over channels
tomwalters@0 215
tomwalters@5 216 fire_counter_--;
tomwalters@0 217
tomwalters@5 218 // Check to see if we need to output an SAI frame on this sample
tomwalters@5 219 if (fire_counter_ <= 0) {
tomwalters@0 220 // Decay the SAI by the correct amount and add the current output frame
tomwalters@5 221 float decay = pow(sai_decay_factor_, frame_period_samples_);
tomwalters@0 222
tomwalters@5 223 for (int ch = 0; ch < input.channel_count(); ++ch) {
tomwalters@0 224 for (int i = 0; i < output_.buffer_length(); ++i) {
tomwalters@5 225 output_.set_sample(ch, i,
tomwalters@5 226 sai_temp_[ch][i] + output_[ch][i] * decay);
tomwalters@0 227 }
tomwalters@0 228 }
tomwalters@0 229
tomwalters@0 230 // Zero the temporary signal
tomwalters@0 231 for (int ch = 0; ch < sai_temp_.channel_count(); ++ch) {
tomwalters@0 232 for (int i = 0; i < sai_temp_.buffer_length(); ++i) {
tomwalters@0 233 sai_temp_.set_sample(ch, i, 0.0f);
tomwalters@0 234 }
tomwalters@0 235 }
tomwalters@0 236
tomwalters@5 237 fire_counter_ = frame_period_samples_ - 1;
tomwalters@0 238
tomwalters@5 239 // Transfer the current time to the output buffer
tomwalters@5 240 output_.set_start_time(input.start_time() + i);
tomwalters@0 241 PushOutput();
tomwalters@0 242 }
tomwalters@8 243 } // End loop over samples
tomwalters@0 244 }
tomwalters@0 245
tomwalters@0 246 ModuleSAI::~ModuleSAI() {
tomwalters@0 247 }
tomwalters@5 248 } // namespace aimc