Mercurial > hg > aimc
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 } |