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@0
|
6 // This program is free software: you can redistribute it and/or modify
|
tomwalters@0
|
7 // it under the terms of the GNU General Public License as published by
|
tomwalters@0
|
8 // the Free Software Foundation, either version 3 of the License, or
|
tomwalters@0
|
9 // (at your option) any later version.
|
tomwalters@0
|
10 //
|
tomwalters@0
|
11 // This program is distributed in the hope that it will be useful,
|
tomwalters@0
|
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
|
tomwalters@0
|
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
tomwalters@0
|
14 // GNU General Public License for more details.
|
tomwalters@0
|
15 //
|
tomwalters@0
|
16 // You should have received a copy of the GNU General Public License
|
tomwalters@0
|
17 // along with this program. If not, see <http://www.gnu.org/licenses/>.
|
tomwalters@0
|
18
|
tomwalters@0
|
19 /*! \file
|
tomwalters@0
|
20 * \brief SAI module
|
tomwalters@0
|
21 */
|
tomwalters@0
|
22
|
tomwalters@0
|
23 /*
|
tomwalters@0
|
24 * \author Thomas Walters <tom@acousticscale.org>
|
tomwalters@0
|
25 * \date created 2007/08/29
|
tomwalters@0
|
26 * \version \$Id: ModuleSAI.cc 4 2010-02-03 18:44:58Z tcw $
|
tomwalters@0
|
27 */
|
tomwalters@0
|
28 #include <math.h>
|
tomwalters@0
|
29
|
tomwalters@0
|
30 #include "Modules/SAI/ModuleSAI.h"
|
tomwalters@0
|
31
|
tomwalters@5
|
32 namespace aimc {
|
tomwalters@0
|
33 ModuleSAI::ModuleSAI(Parameters *parameters) : Module(parameters) {
|
tomwalters@5
|
34 module_identifier_ = "weighted_sai";
|
tomwalters@5
|
35 module_type_ = "sai";
|
tomwalters@0
|
36 module_description_ = "Stabilised auditory image";
|
tomwalters@5
|
37 module_version_ = "$Id: ModuleSAI.cc 4 2010-02-03 18:44:58Z tcw $";
|
tomwalters@0
|
38
|
tomwalters@0
|
39 min_delay_ms_ = parameters_->DefaultFloat("sai.min_delay_ms", 0.0f);
|
tomwalters@0
|
40 max_delay_ms_ = parameters_->DefaultFloat("sai.max_delay_ms", 35.0f);
|
tomwalters@0
|
41 strobe_weight_alpha_ = parameters_->DefaultFloat("sai.strobe_weight_alpha",
|
tomwalters@0
|
42 0.5f);
|
tomwalters@0
|
43 buffer_memory_decay_ = parameters_->DefaultFloat("sai.buffer_memory_decay",
|
tomwalters@0
|
44 0.03f);
|
tomwalters@0
|
45 frame_period_ms_ = parameters_->DefaultFloat("sai.frame_period_ms", 20.0f);
|
tomwalters@0
|
46
|
tomwalters@5
|
47 max_concurrent_strobes_
|
tomwalters@5
|
48 = parameters_->DefaultInt("sai.max_concurrent_strobes", 50);
|
tomwalters@5
|
49
|
tomwalters@5
|
50 min_strobe_delay_idx_ = 0;
|
tomwalters@5
|
51 max_strobe_delay_idx_ = 0;
|
tomwalters@0
|
52 sai_decay_factor_ = 0.0f;
|
tomwalters@5
|
53 fire_counter_ = 0;
|
tomwalters@0
|
54 }
|
tomwalters@0
|
55
|
tomwalters@0
|
56 bool ModuleSAI::InitializeInternal(const SignalBank &input) {
|
tomwalters@0
|
57 // The SAI output bank must be as long as the SAI's Maximum delay.
|
tomwalters@0
|
58 // One sample is added to the SAI buffer length to account for the
|
tomwalters@0
|
59 // zero-lag point
|
tomwalters@6
|
60 int sai_buffer_length = 1 + floor(input.sample_rate() * max_delay_ms_
|
tomwalters@6
|
61 / 1000.0f);
|
tomwalters@5
|
62 channel_count_ = input.channel_count();
|
tomwalters@0
|
63
|
tomwalters@0
|
64 // Make an output SignalBank with the same number of channels and centre
|
tomwalters@0
|
65 // frequencies as the input, but with a different buffer length
|
tomwalters@0
|
66 if (!output_.Initialize(input.channel_count(),
|
tomwalters@0
|
67 sai_buffer_length,
|
tomwalters@5
|
68 input.sample_rate())) {
|
tomwalters@0
|
69 LOG_ERROR("Failed to create output buffer in SAI module");
|
tomwalters@0
|
70 return false;
|
tomwalters@0
|
71 }
|
tomwalters@0
|
72 for (int i = 0; i < input.channel_count(); ++i ) {
|
tomwalters@5
|
73 output_.set_centre_frequency(i, input.centre_frequency(i));
|
tomwalters@0
|
74 }
|
tomwalters@0
|
75
|
tomwalters@0
|
76 // sai_temp_ will be initialized to zero
|
tomwalters@0
|
77 if (!sai_temp_.Initialize(output_)) {
|
tomwalters@0
|
78 LOG_ERROR("Failed to create temporary buffer in SAI module");
|
tomwalters@0
|
79 return false;
|
tomwalters@0
|
80 }
|
tomwalters@0
|
81
|
tomwalters@5
|
82 frame_period_samples_ = floor(input.sample_rate() * frame_period_ms_
|
tomwalters@5
|
83 / 1000.0f);
|
tomwalters@5
|
84 min_strobe_delay_idx_ = floor(input.sample_rate() * min_delay_ms_
|
tomwalters@5
|
85 / 1000.0f);
|
tomwalters@5
|
86 max_strobe_delay_idx_ = floor(input.sample_rate() * max_delay_ms_
|
tomwalters@5
|
87 / 1000.0f);
|
tomwalters@0
|
88
|
tomwalters@0
|
89 // Make sure we don't go past the output buffer's upper bound
|
tomwalters@5
|
90 if (max_strobe_delay_idx_ > output_.buffer_length()) {
|
tomwalters@0
|
91 max_strobe_delay_idx_ = output_.buffer_length();
|
tomwalters@5
|
92 }
|
tomwalters@0
|
93
|
tomwalters@0
|
94 // Define decay factor from time since last sample (see ti2003)
|
tomwalters@0
|
95 sai_decay_factor_ = pow(0.5f, 1.0f / (buffer_memory_decay_
|
tomwalters@0
|
96 * input.sample_rate()));
|
tomwalters@0
|
97
|
tomwalters@0
|
98 // Precompute strobe weights
|
tomwalters@0
|
99 strobe_weights_.resize(max_concurrent_strobes_);
|
tomwalters@0
|
100 for (int n = 0; n < max_concurrent_strobes_; ++n) {
|
tomwalters@5
|
101 strobe_weights_[n] = pow(1.0f / (n + 1), strobe_weight_alpha_);
|
tomwalters@0
|
102 }
|
tomwalters@0
|
103
|
tomwalters@5
|
104 ResetInternal();
|
tomwalters@0
|
105
|
tomwalters@0
|
106 return true;
|
tomwalters@0
|
107 }
|
tomwalters@0
|
108
|
tomwalters@3
|
109 void ModuleSAI::ResetInternal() {
|
tomwalters@5
|
110 // Active Strobes
|
tomwalters@5
|
111 active_strobes_.clear();
|
tomwalters@5
|
112 active_strobes_.resize(channel_count_);
|
tomwalters@5
|
113 fire_counter_ = frame_period_samples_ - 1;
|
tomwalters@0
|
114 }
|
tomwalters@0
|
115
|
tomwalters@0
|
116 void ModuleSAI::Process(const SignalBank &input) {
|
tomwalters@0
|
117
|
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@0
|
216
|
tomwalters@0
|
217 } // End loop over channels
|
tomwalters@0
|
218
|
tomwalters@5
|
219 fire_counter_--;
|
tomwalters@0
|
220
|
tomwalters@5
|
221 // Check to see if we need to output an SAI frame on this sample
|
tomwalters@5
|
222 if (fire_counter_ <= 0) {
|
tomwalters@0
|
223 // Decay the SAI by the correct amount and add the current output frame
|
tomwalters@5
|
224 float decay = pow(sai_decay_factor_, frame_period_samples_);
|
tomwalters@0
|
225
|
tomwalters@5
|
226 for (int ch = 0; ch < input.channel_count(); ++ch) {
|
tomwalters@0
|
227 for (int i = 0; i < output_.buffer_length(); ++i) {
|
tomwalters@5
|
228 output_.set_sample(ch, i,
|
tomwalters@5
|
229 sai_temp_[ch][i] + output_[ch][i] * decay);
|
tomwalters@0
|
230 }
|
tomwalters@0
|
231 }
|
tomwalters@0
|
232
|
tomwalters@0
|
233 // Zero the temporary signal
|
tomwalters@0
|
234 for (int ch = 0; ch < sai_temp_.channel_count(); ++ch) {
|
tomwalters@0
|
235 for (int i = 0; i < sai_temp_.buffer_length(); ++i) {
|
tomwalters@0
|
236 sai_temp_.set_sample(ch, i, 0.0f);
|
tomwalters@0
|
237 }
|
tomwalters@0
|
238 }
|
tomwalters@0
|
239
|
tomwalters@5
|
240 fire_counter_ = frame_period_samples_ - 1;
|
tomwalters@0
|
241
|
tomwalters@5
|
242 // Transfer the current time to the output buffer
|
tomwalters@5
|
243 output_.set_start_time(input.start_time() + i);
|
tomwalters@0
|
244 PushOutput();
|
tomwalters@0
|
245 }
|
tomwalters@0
|
246 } // End loop over samples
|
tomwalters@0
|
247 }
|
tomwalters@0
|
248
|
tomwalters@0
|
249 ModuleSAI::~ModuleSAI() {
|
tomwalters@0
|
250 }
|
tomwalters@5
|
251 } // namespace aimc
|