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@0
|
32 ModuleSAI::ModuleSAI(Parameters *parameters) : Module(parameters) {
|
tomwalters@0
|
33 module_description_ = "Stabilised auditory image";
|
tomwalters@0
|
34 module_name_ = "sai2007";
|
tomwalters@0
|
35 module_type_ = "sai";
|
tomwalters@0
|
36 module_id_ = "$Id: ModuleSAI.cc 4 2010-02-03 18:44:58Z tcw $";
|
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@0
|
46 min_strobe_delay_index_ = 0;
|
tomwalters@0
|
47 max_strobe_delay_index_ = 0;
|
tomwalters@0
|
48 sai_decay_factor_ = 0.0f;
|
tomwalters@0
|
49 max_concurrent_strobes_ = 0;
|
tomwalters@0
|
50 output_frame_period_ms_ = 0.0f;
|
tomwalters@0
|
51 last_output_frame_time_ms_ = 0.0f;
|
tomwalters@0
|
52 }
|
tomwalters@0
|
53
|
tomwalters@0
|
54 bool ModuleSAI::InitializeInternal(const SignalBank &input) {
|
tomwalters@0
|
55 // The SAI output bank must be as long as the SAI's Maximum delay.
|
tomwalters@0
|
56 // One sample is added to the SAI buffer length to account for the
|
tomwalters@0
|
57 // zero-lag point
|
tomwalters@0
|
58 int sai_buffer_length = 1 + Round(input.sample_rate() * max_delay_ms_);
|
tomwalters@0
|
59
|
tomwalters@0
|
60 // Make an output SignalBank with the same number of channels and centre
|
tomwalters@0
|
61 // frequencies as the input, but with a different buffer length
|
tomwalters@0
|
62 if (!output_.Initialize(input.channel_count(),
|
tomwalters@0
|
63 sai_buffer_length,
|
tomwalters@0
|
64 input.sample_rate());) {
|
tomwalters@0
|
65 LOG_ERROR("Failed to create output buffer in SAI module");
|
tomwalters@0
|
66 return false;
|
tomwalters@0
|
67 }
|
tomwalters@0
|
68 for (int i = 0; i < input.channel_count(); ++i ) {
|
tomwalters@0
|
69 output_.set_centre_frequency(i, input.get_centre_frequency(i));
|
tomwalters@0
|
70 }
|
tomwalters@0
|
71
|
tomwalters@0
|
72 // sai_temp_ will be initialized to zero
|
tomwalters@0
|
73 if (!sai_temp_.Initialize(output_)) {
|
tomwalters@0
|
74 LOG_ERROR("Failed to create temporary buffer in SAI module");
|
tomwalters@0
|
75 return false;
|
tomwalters@0
|
76 }
|
tomwalters@0
|
77
|
tomwalters@0
|
78 last_output_time_ms_ = 0.0f;
|
tomwalters@0
|
79
|
tomwalters@0
|
80 frame_period_samples_ = Round(input.sample_rate()
|
tomwalters@0
|
81 * frame_period_ms_ / 1000.0f);
|
tomwalters@0
|
82
|
tomwalters@0
|
83 min_strobe_delay_idx_ = Round(input.sample_rate() * min_delay_ms_ / 1000.0f);
|
tomwalters@0
|
84 max_strobe_delay_idx_ = Round(input.sample_rate() * max_delay_ms_ / 1000.0f);
|
tomwalters@0
|
85
|
tomwalters@0
|
86 // Make sure we don't go past the output buffer's upper bound
|
tomwalters@0
|
87 if (max_strobe_delay_idx_ > output_.buffer_length()))
|
tomwalters@0
|
88 max_strobe_delay_idx_ = output_.buffer_length();
|
tomwalters@0
|
89
|
tomwalters@0
|
90 // Define decay factor from time since last sample (see ti2003)
|
tomwalters@0
|
91 sai_decay_factor_ = pow(0.5f, 1.0f / (buffer_memory_decay_
|
tomwalters@0
|
92 * input.sample_rate()));
|
tomwalters@0
|
93
|
tomwalters@0
|
94 // Maximum strobes that can be active at the same time within maxdelay.
|
tomwalters@0
|
95 //! \todo Choose this value in a more principled way
|
tomwalters@0
|
96 max_concurrent_strobes_ = Round(1000.0f * max_delay_ * 5);
|
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@0
|
101 strobe_weights_[n] = pow(1.0f / (n + 1)), strobe_weight_alpha_);
|
tomwalters@0
|
102 }
|
tomwalters@0
|
103
|
tomwalters@0
|
104 // Active Strobes
|
tomwalters@0
|
105 active_strobes_.Resize(input.channel_count());
|
tomwalters@0
|
106 for (int i = 0; i < input.channel_count(); ++i) {
|
tomwalters@0
|
107 active_strobes_[i].Create(max_concurrent_strobes_);
|
tomwalters@0
|
108 }
|
tomwalters@0
|
109 next_strobes_.resize(input.channel_count(), 0);
|
tomwalters@0
|
110
|
tomwalters@0
|
111 return true;
|
tomwalters@0
|
112 }
|
tomwalters@0
|
113
|
tomwalters@0
|
114 void ModuleSAI::Reset() {
|
tomwalters@0
|
115 }
|
tomwalters@0
|
116
|
tomwalters@0
|
117 void ModuleSAI::Process(const SignalBank &input) {
|
tomwalters@0
|
118 int s;
|
tomwalters@0
|
119 int c;
|
tomwalters@0
|
120 int output_buffer_length = output_.buffer_length();
|
tomwalters@0
|
121
|
tomwalters@0
|
122 // Reset the next strobe times
|
tomwalters@0
|
123 next_strobes_.clear();
|
tomwalters@0
|
124 next_strobes_.resize(output_.channel_count(), 0);
|
tomwalters@0
|
125
|
tomwalters@0
|
126 // Offset the times on the strobes from the previous buffer
|
tomwalters@0
|
127 for (c = 0; c < input.channel_count(), ++c) {
|
tomwalters@0
|
128 active_strobes_[c].shiftStrobes(input.buffer_length());
|
tomwalters@0
|
129 }
|
tomwalters@0
|
130
|
tomwalters@0
|
131 // Make sure only start time is transferred to the output
|
tomwalters@0
|
132 output_.set_start_time(input.start_time());
|
tomwalters@0
|
133
|
tomwalters@0
|
134 // Loop over samples to make the SAI
|
tomwalters@0
|
135 for (s = 0; s < input_buffer_length; ++s) {
|
tomwalters@0
|
136 float decay_factor = pow(sai_decay_factor_, fire_counter_);
|
tomwalters@0
|
137 // Loop over channels
|
tomwalters@0
|
138 for (c = 0; c < input.channel_count(); ++c) {
|
tomwalters@0
|
139 // Local convenience variables
|
tomwalters@0
|
140 StrobeList &active_strobes = active_strobes_[c];
|
tomwalters@0
|
141 float centre_frequency = input.get_centre_frequency(c);
|
tomwalters@0
|
142 int next_strobe = next_strobes_[c];
|
tomwalters@0
|
143
|
tomwalters@0
|
144 // 1. Update strobes
|
tomwalters@0
|
145 // If we are up to or beyond the next strobe...
|
tomwalters@0
|
146 if (next_strobe < input.strobe_count(c)) {
|
tomwalters@0
|
147 if (s == pSigIn->getStrobe(iNextStrobe)) {
|
tomwalters@0
|
148 //A new strobe has arrived
|
tomwalters@0
|
149 // if there aren't already too many strobes active...
|
tomwalters@0
|
150 if ((active_strobes.getStrobeCount() + 1) < max_concurrent_strobes_) {
|
tomwalters@0
|
151 // ...add the active strobe to the list of current strobes
|
tomwalters@0
|
152 // calculate the strobe weight
|
tomwalters@0
|
153 float weight = 1.0f;
|
tomwalters@0
|
154 if (active_strobes.getStrobeCount() > 0) {
|
tomwalters@0
|
155 int last_strobe = active_strobes.getTime(
|
tomwalters@0
|
156 active_strobes.getStrobeCount());
|
tomwalters@0
|
157
|
tomwalters@0
|
158 // If the strobe occured within 10 impulse-response
|
tomwalters@0
|
159 // cycles of the previous strobe, then lower its weight
|
tomwalters@0
|
160 weight = (s - iLastStrobe) / input.sample_rate()
|
tomwalters@0
|
161 * centre_frequency / 10.0f;
|
tomwalters@0
|
162 if (weight > 1.0f)
|
tomwalters@0
|
163 weight = 1.0f;
|
tomwalters@0
|
164 }
|
tomwalters@0
|
165 pActiveStrobes->addStrobe(iCurrentSample, weight);
|
tomwalters@0
|
166 iNextStrobe++;
|
tomwalters@0
|
167 } else {
|
tomwalters@0
|
168 // We have a problem
|
tomwalters@0
|
169 aimASSERT(0);
|
tomwalters@0
|
170 }
|
tomwalters@0
|
171
|
tomwalters@0
|
172 // 2. Having updated the strobes, we now need to update the
|
tomwalters@0
|
173 // strobe weights
|
tomwalters@0
|
174 float total_strobe_weight = 0.0f;
|
tomwalters@0
|
175 for (int si = 1; si <= pActiveStrobes->getStrobeCount(); ++si) {
|
tomwalters@0
|
176 total_strobe_weight += (pActiveStrobes->getWeight(si)
|
tomwalters@0
|
177 * m_pStrobeWeights[pActiveStrobes->getStrobeCount() - si]);
|
tomwalters@0
|
178 }
|
tomwalters@0
|
179 for (int si = 1; si <= pActiveStrobes->getStrobeCount(); ++si) {
|
tomwalters@0
|
180 active_strobes.setWorkingWeight(si,(active_strobes.getWeight(si)
|
tomwalters@0
|
181 * strobe_weights_[active_strobes.getStrobeCount() - si])
|
tomwalters@0
|
182 / total_strobe_weight);
|
tomwalters@0
|
183 }
|
tomwalters@0
|
184 }
|
tomwalters@0
|
185 }
|
tomwalters@0
|
186
|
tomwalters@0
|
187 // remove inactive strobes...
|
tomwalters@0
|
188 while (pActiveStrobes->getStrobeCount() > 0) {
|
tomwalters@0
|
189 // Get the time of the first strobe (ordering of strobes is
|
tomwalters@0
|
190 // from one, not zero)
|
tomwalters@0
|
191 int iStrobeTime = pActiveStrobes->getTime(1);
|
tomwalters@0
|
192 int iDelay = iCurrentSample - iStrobeTime;
|
tomwalters@0
|
193 // ... do we now need to remove this strobe?
|
tomwalters@0
|
194 if (iDelay > m_maxStrobeDelayIdx)
|
tomwalters@0
|
195 pActiveStrobes->deleteFirstStrobe();
|
tomwalters@0
|
196 else
|
tomwalters@0
|
197 break;
|
tomwalters@0
|
198 // Since the strobes are ordered, we don't need to go
|
tomwalters@0
|
199 // beyond the first still-active strobe
|
tomwalters@0
|
200 }
|
tomwalters@0
|
201
|
tomwalters@0
|
202 // 3. Loop over active strobes
|
tomwalters@0
|
203 for (int si = 1; si <= pActiveStrobes->getStrobeCount(); si++) {
|
tomwalters@0
|
204 // 3.1 Add effect of active strobe at correct place in the SAI buffer
|
tomwalters@0
|
205 // Calculate the time from the strobe event to 'now': iDelay
|
tomwalters@0
|
206 int iStrobeTime = pActiveStrobes->getTime(si);
|
tomwalters@0
|
207 int iDelay = iCurrentSample - iStrobeTime;
|
tomwalters@0
|
208
|
tomwalters@0
|
209 // If the delay is greater than the (user-set)
|
tomwalters@0
|
210 // minimum strobe delay, the strobe can be used
|
tomwalters@0
|
211 if (iDelay >= m_minStrobeDelayIdx && iDelay < m_maxStrobeDelayIdx) {
|
tomwalters@0
|
212 // The value at be added to the SAI
|
tomwalters@0
|
213 float sig = pSigIn->getSample(iCurrentSample, audCh);
|
tomwalters@0
|
214
|
tomwalters@0
|
215 // Weight the sample correctly
|
tomwalters@0
|
216 sig *= pActiveStrobes->getWorkingWeight(si);
|
tomwalters@0
|
217
|
tomwalters@0
|
218 // Adjust the weight acording to the number of samples until the
|
tomwalters@0
|
219 // next output frame
|
tomwalters@0
|
220 sig *= fDecayFactor;
|
tomwalters@0
|
221
|
tomwalters@0
|
222 // Update the temporary SAI buffer
|
tomwalters@0
|
223 pSigOut->setSample(iDelay, audCh,
|
tomwalters@0
|
224 pSigOut->getSample(iDelay, audCh)+sig);
|
tomwalters@0
|
225 }
|
tomwalters@0
|
226 }
|
tomwalters@0
|
227
|
tomwalters@0
|
228 m_pNextStrobes[bankCh]=iNextStrobe;
|
tomwalters@0
|
229
|
tomwalters@0
|
230 } // End loop over channels
|
tomwalters@0
|
231
|
tomwalters@0
|
232
|
tomwalters@0
|
233 //Check to see if we need to output an SAI frame this sample
|
tomwalters@0
|
234 if (m_iFireCounter-- == 0) {
|
tomwalters@0
|
235 // Decay the SAI by the correct amount and add the current output frame
|
tomwalters@0
|
236 float decay = pow(sai_decay_factor_, fire_period_samples_);
|
tomwalters@0
|
237
|
tomwalters@0
|
238 for (c = 0; c < input.channel_count(); ++c) {
|
tomwalters@0
|
239 for (int i = 0; i < output_.buffer_length(); ++i) {
|
tomwalters@0
|
240 output_.set_sample(c, i, sai_temp_[c][i] + output_[c][i] * decay);
|
tomwalters@0
|
241 }
|
tomwalters@0
|
242 }
|
tomwalters@0
|
243
|
tomwalters@0
|
244 // Zero the temporary signal
|
tomwalters@0
|
245 for (int ch = 0; ch < sai_temp_.channel_count(); ++ch) {
|
tomwalters@0
|
246 for (int i = 0; i < sai_temp_.buffer_length(); ++i) {
|
tomwalters@0
|
247 sai_temp_.set_sample(ch, i, 0.0f);
|
tomwalters@0
|
248 }
|
tomwalters@0
|
249 }
|
tomwalters@0
|
250
|
tomwalters@0
|
251 m_iFireCounter=m_iFirePeriodSamples-1;
|
tomwalters@0
|
252
|
tomwalters@0
|
253 // Make sure the start time is transferred to the output
|
tomwalters@0
|
254 m_pOutputData->setStartTime(m_pInputData->getSignal(0)->getStartTime()+(SIGNAL_SAMPLE)((float)iCurrentSample*1000.0f/(float)m_pInputData->getSamplerate()));
|
tomwalters@0
|
255 PushOutput();
|
tomwalters@0
|
256 }
|
tomwalters@0
|
257 } // End loop over samples
|
tomwalters@0
|
258 }
|
tomwalters@0
|
259
|
tomwalters@0
|
260 ModuleSAI::~ModuleSAI() {
|
tomwalters@0
|
261 }
|