comparison src/Modules/SAI/ModuleSAI.cc @ 0:582cbe817f2c

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