Chris@0
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@7
|
2 /*
|
Chris@7
|
3 Permission is hereby granted, free of charge, to any person
|
Chris@7
|
4 obtaining a copy of this software and associated documentation
|
Chris@7
|
5 files (the "Software"), to deal in the Software without
|
Chris@7
|
6 restriction, including without limitation the rights to use, copy,
|
Chris@7
|
7 modify, merge, publish, distribute, sublicense, and/or sell copies
|
Chris@7
|
8 of the Software, and to permit persons to whom the Software is
|
Chris@7
|
9 furnished to do so, subject to the following conditions:
|
Chris@7
|
10
|
Chris@7
|
11 The above copyright notice and this permission notice shall be
|
Chris@7
|
12 included in all copies or substantial portions of the Software.
|
Chris@7
|
13
|
Chris@7
|
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
Chris@7
|
15 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
Chris@7
|
16 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
Chris@7
|
17 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
|
Chris@7
|
18 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
Chris@7
|
19 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
Chris@7
|
20 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
Chris@7
|
21 */
|
Chris@0
|
22
|
Chris@0
|
23 #include "SimpleCepstrum.h"
|
Chris@0
|
24
|
Chris@0
|
25 #include <vector>
|
Chris@0
|
26 #include <algorithm>
|
Chris@0
|
27
|
Chris@0
|
28 #include <cstdio>
|
Chris@0
|
29 #include <cmath>
|
Chris@4
|
30 #include <complex>
|
Chris@0
|
31
|
Chris@0
|
32 using std::string;
|
Chris@0
|
33
|
Chris@0
|
34 SimpleCepstrum::SimpleCepstrum(float inputSampleRate) :
|
Chris@0
|
35 Plugin(inputSampleRate),
|
Chris@0
|
36 m_channels(0),
|
Chris@0
|
37 m_stepSize(256),
|
Chris@0
|
38 m_blockSize(1024),
|
Chris@0
|
39 m_fmin(50),
|
Chris@0
|
40 m_fmax(1000),
|
Chris@10
|
41 m_histlen(1),
|
Chris@10
|
42 m_vflen(1),
|
Chris@3
|
43 m_clamp(false),
|
Chris@5
|
44 m_method(InverseSymmetric),
|
Chris@5
|
45 m_binFrom(0),
|
Chris@5
|
46 m_binTo(0),
|
Chris@5
|
47 m_bins(0),
|
Chris@5
|
48 m_history(0)
|
Chris@0
|
49 {
|
Chris@0
|
50 }
|
Chris@0
|
51
|
Chris@0
|
52 SimpleCepstrum::~SimpleCepstrum()
|
Chris@0
|
53 {
|
Chris@5
|
54 if (m_history) {
|
Chris@5
|
55 for (int i = 0; i < m_histlen; ++i) {
|
Chris@5
|
56 delete[] m_history[i];
|
Chris@5
|
57 }
|
Chris@5
|
58 delete[] m_history;
|
Chris@5
|
59 }
|
Chris@0
|
60 }
|
Chris@0
|
61
|
Chris@0
|
62 string
|
Chris@0
|
63 SimpleCepstrum::getIdentifier() const
|
Chris@0
|
64 {
|
Chris@0
|
65 return "simple-cepstrum";
|
Chris@0
|
66 }
|
Chris@0
|
67
|
Chris@0
|
68 string
|
Chris@0
|
69 SimpleCepstrum::getName() const
|
Chris@0
|
70 {
|
Chris@0
|
71 return "Simple Cepstrum";
|
Chris@0
|
72 }
|
Chris@0
|
73
|
Chris@0
|
74 string
|
Chris@0
|
75 SimpleCepstrum::getDescription() const
|
Chris@0
|
76 {
|
Chris@2
|
77 return "Return simple cepstral data from DFT bins. This plugin is intended for casual inspection of cepstral data. It returns a lot of different sorts of data and is quite slow; it's not a good way to extract a single feature rapidly.";
|
Chris@0
|
78 }
|
Chris@0
|
79
|
Chris@0
|
80 string
|
Chris@0
|
81 SimpleCepstrum::getMaker() const
|
Chris@0
|
82 {
|
Chris@7
|
83 return "Chris Cannam";
|
Chris@0
|
84 }
|
Chris@0
|
85
|
Chris@0
|
86 int
|
Chris@0
|
87 SimpleCepstrum::getPluginVersion() const
|
Chris@0
|
88 {
|
Chris@0
|
89 // Increment this each time you release a version that behaves
|
Chris@0
|
90 // differently from the previous one
|
Chris@0
|
91 return 1;
|
Chris@0
|
92 }
|
Chris@0
|
93
|
Chris@0
|
94 string
|
Chris@0
|
95 SimpleCepstrum::getCopyright() const
|
Chris@0
|
96 {
|
Chris@7
|
97 return "Freely redistributable (BSD license)";
|
Chris@0
|
98 }
|
Chris@0
|
99
|
Chris@0
|
100 SimpleCepstrum::InputDomain
|
Chris@0
|
101 SimpleCepstrum::getInputDomain() const
|
Chris@0
|
102 {
|
Chris@0
|
103 return FrequencyDomain;
|
Chris@0
|
104 }
|
Chris@0
|
105
|
Chris@0
|
106 size_t
|
Chris@0
|
107 SimpleCepstrum::getPreferredBlockSize() const
|
Chris@0
|
108 {
|
Chris@0
|
109 return 1024;
|
Chris@0
|
110 }
|
Chris@0
|
111
|
Chris@0
|
112 size_t
|
Chris@0
|
113 SimpleCepstrum::getPreferredStepSize() const
|
Chris@0
|
114 {
|
Chris@0
|
115 return 256;
|
Chris@0
|
116 }
|
Chris@0
|
117
|
Chris@0
|
118 size_t
|
Chris@0
|
119 SimpleCepstrum::getMinChannelCount() const
|
Chris@0
|
120 {
|
Chris@0
|
121 return 1;
|
Chris@0
|
122 }
|
Chris@0
|
123
|
Chris@0
|
124 size_t
|
Chris@0
|
125 SimpleCepstrum::getMaxChannelCount() const
|
Chris@0
|
126 {
|
Chris@0
|
127 return 1;
|
Chris@0
|
128 }
|
Chris@0
|
129
|
Chris@0
|
130 SimpleCepstrum::ParameterList
|
Chris@0
|
131 SimpleCepstrum::getParameterDescriptors() const
|
Chris@0
|
132 {
|
Chris@0
|
133 ParameterList list;
|
Chris@0
|
134
|
Chris@0
|
135 ParameterDescriptor d;
|
Chris@0
|
136
|
Chris@0
|
137 d.identifier = "fmin";
|
Chris@0
|
138 d.name = "Minimum frequency";
|
Chris@0
|
139 d.description = "";
|
Chris@0
|
140 d.unit = "Hz";
|
Chris@0
|
141 d.minValue = m_inputSampleRate / m_blockSize;
|
Chris@0
|
142 d.maxValue = m_inputSampleRate / 2;
|
Chris@0
|
143 d.defaultValue = 50;
|
Chris@0
|
144 d.isQuantized = false;
|
Chris@0
|
145 list.push_back(d);
|
Chris@0
|
146
|
Chris@0
|
147 d.identifier = "fmax";
|
Chris@0
|
148 d.name = "Maximum frequency";
|
Chris@0
|
149 d.description = "";
|
Chris@0
|
150 d.unit = "Hz";
|
Chris@0
|
151 d.minValue = m_inputSampleRate / m_blockSize;
|
Chris@0
|
152 d.maxValue = m_inputSampleRate / 2;
|
Chris@0
|
153 d.defaultValue = 1000;
|
Chris@0
|
154 d.isQuantized = false;
|
Chris@0
|
155 list.push_back(d);
|
Chris@0
|
156
|
Chris@5
|
157 d.identifier = "histlen";
|
Chris@5
|
158 d.name = "Mean filter history length";
|
Chris@5
|
159 d.description = "";
|
Chris@5
|
160 d.unit = "";
|
Chris@5
|
161 d.minValue = 1;
|
Chris@5
|
162 d.maxValue = 10;
|
Chris@10
|
163 d.defaultValue = 1;
|
Chris@5
|
164 d.isQuantized = true;
|
Chris@5
|
165 d.quantizeStep = 1;
|
Chris@5
|
166 list.push_back(d);
|
Chris@5
|
167
|
Chris@10
|
168 d.identifier = "vflen";
|
Chris@10
|
169 d.name = "Vertical filter length";
|
Chris@10
|
170 d.description = "";
|
Chris@10
|
171 d.unit = "";
|
Chris@10
|
172 d.minValue = 1;
|
Chris@10
|
173 d.maxValue = 11;
|
Chris@10
|
174 d.defaultValue = 1;
|
Chris@10
|
175 d.isQuantized = true;
|
Chris@10
|
176 d.quantizeStep = 2;
|
Chris@10
|
177 list.push_back(d);
|
Chris@10
|
178
|
Chris@3
|
179 d.identifier = "method";
|
Chris@3
|
180 d.name = "Cepstrum transform method";
|
Chris@3
|
181 d.unit = "";
|
Chris@3
|
182 d.minValue = 0;
|
Chris@5
|
183 d.maxValue = 4;
|
Chris@3
|
184 d.defaultValue = 0;
|
Chris@3
|
185 d.isQuantized = true;
|
Chris@3
|
186 d.quantizeStep = 1;
|
Chris@3
|
187 d.valueNames.push_back("Inverse symmetric");
|
Chris@3
|
188 d.valueNames.push_back("Inverse asymmetric");
|
Chris@4
|
189 d.valueNames.push_back("Inverse complex");
|
Chris@3
|
190 d.valueNames.push_back("Forward magnitude");
|
Chris@3
|
191 d.valueNames.push_back("Forward difference");
|
Chris@3
|
192 list.push_back(d);
|
Chris@3
|
193
|
Chris@10
|
194 d.identifier = "clamp";
|
Chris@10
|
195 d.name = "Clamp negative values in cepstrum at zero";
|
Chris@10
|
196 d.unit = "";
|
Chris@10
|
197 d.minValue = 0;
|
Chris@10
|
198 d.maxValue = 1;
|
Chris@10
|
199 d.defaultValue = 0;
|
Chris@10
|
200 d.isQuantized = true;
|
Chris@10
|
201 d.quantizeStep = 1;
|
Chris@10
|
202 d.valueNames.clear();
|
Chris@10
|
203 list.push_back(d);
|
Chris@10
|
204
|
Chris@0
|
205 return list;
|
Chris@0
|
206 }
|
Chris@0
|
207
|
Chris@0
|
208 float
|
Chris@0
|
209 SimpleCepstrum::getParameter(string identifier) const
|
Chris@0
|
210 {
|
Chris@0
|
211 if (identifier == "fmin") return m_fmin;
|
Chris@0
|
212 else if (identifier == "fmax") return m_fmax;
|
Chris@5
|
213 else if (identifier == "histlen") return m_histlen;
|
Chris@10
|
214 else if (identifier == "vflen") return m_vflen;
|
Chris@10
|
215 else if (identifier == "clamp") return (m_clamp ? 1 : 0);
|
Chris@3
|
216 else if (identifier == "method") return (int)m_method;
|
Chris@0
|
217 else return 0.f;
|
Chris@0
|
218 }
|
Chris@0
|
219
|
Chris@0
|
220 void
|
Chris@0
|
221 SimpleCepstrum::setParameter(string identifier, float value)
|
Chris@0
|
222 {
|
Chris@0
|
223 if (identifier == "fmin") m_fmin = value;
|
Chris@0
|
224 else if (identifier == "fmax") m_fmax = value;
|
Chris@5
|
225 else if (identifier == "histlen") m_histlen = value;
|
Chris@10
|
226 else if (identifier == "vflen") m_vflen = value;
|
Chris@10
|
227 else if (identifier == "clamp") m_clamp = (value > 0.5);
|
Chris@3
|
228 else if (identifier == "method") m_method = Method(int(value + 0.5));
|
Chris@0
|
229 }
|
Chris@0
|
230
|
Chris@0
|
231 SimpleCepstrum::ProgramList
|
Chris@0
|
232 SimpleCepstrum::getPrograms() const
|
Chris@0
|
233 {
|
Chris@0
|
234 ProgramList list;
|
Chris@0
|
235 return list;
|
Chris@0
|
236 }
|
Chris@0
|
237
|
Chris@0
|
238 string
|
Chris@0
|
239 SimpleCepstrum::getCurrentProgram() const
|
Chris@0
|
240 {
|
Chris@0
|
241 return ""; // no programs
|
Chris@0
|
242 }
|
Chris@0
|
243
|
Chris@0
|
244 void
|
Chris@0
|
245 SimpleCepstrum::selectProgram(string name)
|
Chris@0
|
246 {
|
Chris@0
|
247 }
|
Chris@0
|
248
|
Chris@0
|
249 SimpleCepstrum::OutputList
|
Chris@0
|
250 SimpleCepstrum::getOutputDescriptors() const
|
Chris@0
|
251 {
|
Chris@0
|
252 OutputList outputs;
|
Chris@0
|
253
|
Chris@0
|
254 int n = 0;
|
Chris@0
|
255
|
Chris@0
|
256 OutputDescriptor d;
|
Chris@2
|
257
|
Chris@7
|
258 d.identifier = "raw_cepstral_peak";
|
Chris@7
|
259 d.name = "Frequency corresponding to raw cepstral peak";
|
Chris@24
|
260 d.description = "Return the frequency whose period corresponds to the quefrency with the maximum bin value within the specified range of the cepstrum";
|
Chris@6
|
261 d.unit = "Hz";
|
Chris@0
|
262 d.hasFixedBinCount = true;
|
Chris@0
|
263 d.binCount = 1;
|
Chris@0
|
264 d.hasKnownExtents = true;
|
Chris@0
|
265 d.minValue = m_fmin;
|
Chris@0
|
266 d.maxValue = m_fmax;
|
Chris@0
|
267 d.isQuantized = false;
|
Chris@0
|
268 d.sampleType = OutputDescriptor::OneSamplePerStep;
|
Chris@0
|
269 d.hasDuration = false;
|
Chris@5
|
270 m_pkOutput = n++;
|
Chris@0
|
271 outputs.push_back(d);
|
Chris@0
|
272
|
Chris@24
|
273 d.identifier = "interpolated_peak";
|
Chris@24
|
274 d.name = "Interpolated peak frequency";
|
Chris@24
|
275 d.description = "Return the frequency whose period corresponds to the quefrency with the maximum bin value within the specified range of the cepstrum, using cubic interpolation to estimate the peak quefrency to finer than single bin resolution";
|
Chris@24
|
276 m_ipkOutput = n++;
|
Chris@24
|
277 outputs.push_back(d);
|
Chris@24
|
278
|
Chris@0
|
279 d.identifier = "variance";
|
Chris@0
|
280 d.name = "Variance of cepstral bins in range";
|
Chris@0
|
281 d.unit = "";
|
Chris@2
|
282 d.description = "Return the variance of bin values within the specified range of the cepstrum";
|
Chris@6
|
283 d.hasKnownExtents = false;
|
Chris@0
|
284 m_varOutput = n++;
|
Chris@0
|
285 outputs.push_back(d);
|
Chris@0
|
286
|
Chris@0
|
287 d.identifier = "peak";
|
Chris@8
|
288 d.name = "Value at peak";
|
Chris@0
|
289 d.unit = "";
|
Chris@2
|
290 d.description = "Return the value found in the maximum-valued bin within the specified range of the cepstrum";
|
Chris@0
|
291 m_pvOutput = n++;
|
Chris@0
|
292 outputs.push_back(d);
|
Chris@0
|
293
|
Chris@5
|
294 d.identifier = "peak_to_rms";
|
Chris@5
|
295 d.name = "Peak-to-RMS distance";
|
Chris@5
|
296 d.unit = "";
|
Chris@5
|
297 d.description = "Return the difference between maximum and root mean square bin values within the specified range of the cepstrum";
|
Chris@5
|
298 m_p2rOutput = n++;
|
Chris@5
|
299 outputs.push_back(d);
|
Chris@5
|
300
|
Chris@6
|
301 d.identifier = "peak_proportion";
|
Chris@7
|
302 d.name = "Energy around peak";
|
Chris@6
|
303 d.unit = "";
|
Chris@7
|
304 d.description = "Return the proportion of total energy that is found in the bins around the peak bin (as far as the nearest local minima), within the specified range of the cepstrum";
|
Chris@6
|
305 m_ppOutput = n++;
|
Chris@6
|
306 outputs.push_back(d);
|
Chris@6
|
307
|
Chris@20
|
308 d.identifier = "peak_to_second_peak";
|
Chris@20
|
309 d.name = "Peak to second-peak ratio";
|
Chris@20
|
310 d.unit = "";
|
Chris@20
|
311 d.description = "Return the ratio of the value found in the peak bin within the specified range of the cepstrum, to the value found in the next highest peak";
|
Chris@20
|
312 m_pkoOutput = n++;
|
Chris@20
|
313 outputs.push_back(d);
|
Chris@20
|
314
|
Chris@6
|
315 d.identifier = "total";
|
Chris@6
|
316 d.name = "Total energy";
|
Chris@6
|
317 d.unit = "";
|
Chris@7
|
318 d.description = "Return the total energy found in all bins within the specified range of the cepstrum";
|
Chris@6
|
319 m_totOutput = n++;
|
Chris@6
|
320 outputs.push_back(d);
|
Chris@6
|
321
|
Chris@0
|
322 d.identifier = "cepstrum";
|
Chris@0
|
323 d.name = "Cepstrum";
|
Chris@0
|
324 d.unit = "";
|
Chris@2
|
325 d.description = "The unprocessed cepstrum bins within the specified range";
|
Chris@0
|
326
|
Chris@0
|
327 int from = int(m_inputSampleRate / m_fmax);
|
Chris@0
|
328 int to = int(m_inputSampleRate / m_fmin);
|
Chris@0
|
329 if (to >= (int)m_blockSize / 2) {
|
Chris@0
|
330 to = m_blockSize / 2 - 1;
|
Chris@0
|
331 }
|
Chris@0
|
332 d.binCount = to - from + 1;
|
Chris@0
|
333 for (int i = from; i <= to; ++i) {
|
Chris@0
|
334 float freq = m_inputSampleRate / i;
|
Chris@5
|
335 char buffer[20];
|
Chris@2
|
336 sprintf(buffer, "%.2f Hz", freq);
|
Chris@0
|
337 d.binNames.push_back(buffer);
|
Chris@0
|
338 }
|
Chris@0
|
339
|
Chris@0
|
340 d.hasKnownExtents = false;
|
Chris@0
|
341 m_cepOutput = n++;
|
Chris@0
|
342 outputs.push_back(d);
|
Chris@0
|
343
|
Chris@0
|
344 d.identifier = "am";
|
Chris@5
|
345 d.name = "Cepstrum bins relative to RMS";
|
Chris@5
|
346 d.description = "The cepstrum bins within the specified range, expressed as a value relative to the root mean square bin value in the range, with values below the RMS clamped to zero";
|
Chris@0
|
347 m_amOutput = n++;
|
Chris@0
|
348 outputs.push_back(d);
|
Chris@0
|
349
|
Chris@2
|
350 d.identifier = "env";
|
Chris@2
|
351 d.name = "Spectral envelope";
|
Chris@2
|
352 d.description = "Envelope calculated from the cepstral values below the specified minimum";
|
Chris@2
|
353 d.binCount = m_blockSize/2 + 1;
|
Chris@2
|
354 d.binNames.clear();
|
Chris@7
|
355 for (int i = 0; i < (int)d.binCount; ++i) {
|
Chris@2
|
356 float freq = (m_inputSampleRate / m_blockSize) * i;
|
Chris@5
|
357 char buffer[20];
|
Chris@2
|
358 sprintf(buffer, "%.2f Hz", freq);
|
Chris@2
|
359 d.binNames.push_back(buffer);
|
Chris@2
|
360 }
|
Chris@2
|
361 m_envOutput = n++;
|
Chris@2
|
362 outputs.push_back(d);
|
Chris@2
|
363
|
Chris@2
|
364 d.identifier = "es";
|
Chris@2
|
365 d.name = "Spectrum without envelope";
|
Chris@2
|
366 d.description = "Magnitude of spectrum values divided by calculated envelope values, to deconvolve the envelope";
|
Chris@2
|
367 m_esOutput = n++;
|
Chris@2
|
368 outputs.push_back(d);
|
Chris@2
|
369
|
Chris@0
|
370 return outputs;
|
Chris@0
|
371 }
|
Chris@0
|
372
|
Chris@0
|
373 bool
|
Chris@0
|
374 SimpleCepstrum::initialise(size_t channels, size_t stepSize, size_t blockSize)
|
Chris@0
|
375 {
|
Chris@0
|
376 if (channels < getMinChannelCount() ||
|
Chris@0
|
377 channels > getMaxChannelCount()) return false;
|
Chris@0
|
378
|
Chris@0
|
379 // std::cerr << "SimpleCepstrum::initialise: channels = " << channels
|
Chris@0
|
380 // << ", stepSize = " << stepSize << ", blockSize = " << blockSize
|
Chris@0
|
381 // << std::endl;
|
Chris@0
|
382
|
Chris@0
|
383 m_channels = channels;
|
Chris@0
|
384 m_stepSize = stepSize;
|
Chris@0
|
385 m_blockSize = blockSize;
|
Chris@0
|
386
|
Chris@5
|
387 m_binFrom = int(m_inputSampleRate / m_fmax);
|
Chris@5
|
388 m_binTo = int(m_inputSampleRate / m_fmin);
|
Chris@5
|
389
|
Chris@7
|
390 if (m_binTo >= (int)m_blockSize / 2) {
|
Chris@5
|
391 m_binTo = m_blockSize / 2 - 1;
|
Chris@5
|
392 }
|
Chris@5
|
393
|
Chris@5
|
394 m_bins = (m_binTo - m_binFrom) + 1;
|
Chris@5
|
395
|
Chris@5
|
396 m_history = new double *[m_histlen];
|
Chris@5
|
397 for (int i = 0; i < m_histlen; ++i) {
|
Chris@5
|
398 m_history[i] = new double[m_bins];
|
Chris@5
|
399 }
|
Chris@5
|
400
|
Chris@5
|
401 reset();
|
Chris@5
|
402
|
Chris@0
|
403 return true;
|
Chris@0
|
404 }
|
Chris@0
|
405
|
Chris@0
|
406 void
|
Chris@0
|
407 SimpleCepstrum::reset()
|
Chris@0
|
408 {
|
Chris@5
|
409 for (int i = 0; i < m_histlen; ++i) {
|
Chris@5
|
410 for (int j = 0; j < m_bins; ++j) {
|
Chris@5
|
411 m_history[i][j] = 0.0;
|
Chris@5
|
412 }
|
Chris@5
|
413 }
|
Chris@5
|
414 }
|
Chris@5
|
415
|
Chris@5
|
416 void
|
Chris@5
|
417 SimpleCepstrum::filter(const double *cep, double *result)
|
Chris@5
|
418 {
|
Chris@5
|
419 int hix = m_histlen - 1; // current history index
|
Chris@5
|
420
|
Chris@5
|
421 // roll back the history
|
Chris@5
|
422 if (m_histlen > 1) {
|
Chris@5
|
423 double *oldest = m_history[0];
|
Chris@5
|
424 for (int i = 1; i < m_histlen; ++i) {
|
Chris@5
|
425 m_history[i-1] = m_history[i];
|
Chris@5
|
426 }
|
Chris@5
|
427 // and stick this back in the newest spot, to recycle
|
Chris@5
|
428 m_history[hix] = oldest;
|
Chris@5
|
429 }
|
Chris@5
|
430
|
Chris@5
|
431 for (int i = 0; i < m_bins; ++i) {
|
Chris@10
|
432 double v = 0;
|
Chris@10
|
433 int n = 0;
|
Chris@10
|
434 // average according to the vertical filter length
|
Chris@10
|
435 for (int j = -m_vflen/2; j <= m_vflen/2; ++j) {
|
Chris@10
|
436 int ix = i + m_binFrom + j;
|
Chris@10
|
437 if (ix >= 0 && ix < m_blockSize) {
|
Chris@10
|
438 v += cep[ix];
|
Chris@10
|
439 ++n;
|
Chris@10
|
440 }
|
Chris@10
|
441 }
|
Chris@10
|
442 m_history[hix][i] = v / n;
|
Chris@5
|
443 }
|
Chris@5
|
444
|
Chris@5
|
445 for (int i = 0; i < m_bins; ++i) {
|
Chris@5
|
446 double mean = 0.0;
|
Chris@5
|
447 for (int j = 0; j < m_histlen; ++j) {
|
Chris@5
|
448 mean += m_history[j][i];
|
Chris@5
|
449 }
|
Chris@5
|
450 mean /= m_histlen;
|
Chris@5
|
451 result[i] = mean;
|
Chris@5
|
452 }
|
Chris@5
|
453 }
|
Chris@24
|
454
|
Chris@24
|
455 double
|
Chris@24
|
456 SimpleCepstrum::cubicInterpolate(const double y[4], double x)
|
Chris@24
|
457 {
|
Chris@24
|
458 double a0 = y[3] - y[2] - y[0] + y[1];
|
Chris@24
|
459 double a1 = y[0] - y[1] - a0;
|
Chris@24
|
460 double a2 = y[2] - y[0];
|
Chris@24
|
461 double a3 = y[1];
|
Chris@24
|
462 return
|
Chris@24
|
463 a0 * x * x * x +
|
Chris@24
|
464 a1 * x * x +
|
Chris@24
|
465 a2 * x +
|
Chris@24
|
466 a3;
|
Chris@24
|
467 }
|
Chris@24
|
468
|
Chris@24
|
469 double
|
Chris@24
|
470 SimpleCepstrum::findInterpolatedPeak(const double *in, int maxbin)
|
Chris@24
|
471 {
|
Chris@24
|
472 if (maxbin < 2 || maxbin > m_bins - 3) {
|
Chris@24
|
473 return maxbin;
|
Chris@24
|
474 }
|
Chris@24
|
475
|
Chris@24
|
476 double maxval = 0.0;
|
Chris@24
|
477 double maxidx = maxbin;
|
Chris@24
|
478
|
Chris@24
|
479 const int divisions = 10;
|
Chris@24
|
480 double y[4];
|
Chris@24
|
481
|
Chris@24
|
482 y[0] = in[maxbin-1];
|
Chris@24
|
483 y[1] = in[maxbin];
|
Chris@24
|
484 y[2] = in[maxbin+1];
|
Chris@24
|
485 y[3] = in[maxbin+2];
|
Chris@24
|
486 for (int i = 0; i < divisions; ++i) {
|
Chris@24
|
487 double probe = double(i) / double(divisions);
|
Chris@24
|
488 double value = cubicInterpolate(y, probe);
|
Chris@24
|
489 if (value > maxval) {
|
Chris@24
|
490 maxval = value;
|
Chris@24
|
491 maxidx = maxbin + probe;
|
Chris@24
|
492 }
|
Chris@24
|
493 }
|
Chris@24
|
494
|
Chris@24
|
495 y[3] = y[2];
|
Chris@24
|
496 y[2] = y[1];
|
Chris@24
|
497 y[1] = y[0];
|
Chris@24
|
498 y[0] = in[maxbin-2];
|
Chris@24
|
499 for (int i = 0; i < divisions; ++i) {
|
Chris@24
|
500 double probe = double(i) / double(divisions);
|
Chris@24
|
501 double value = cubicInterpolate(y, probe);
|
Chris@24
|
502 if (value > maxval) {
|
Chris@24
|
503 maxval = value;
|
Chris@24
|
504 maxidx = maxbin - 1 + probe;
|
Chris@24
|
505 }
|
Chris@24
|
506 }
|
Chris@24
|
507
|
Chris@24
|
508 /*
|
Chris@24
|
509 std::cerr << "centre = " << maxbin << ": ["
|
Chris@24
|
510 << in[maxbin-2] << ","
|
Chris@24
|
511 << in[maxbin-1] << ","
|
Chris@24
|
512 << in[maxbin] << ","
|
Chris@24
|
513 << in[maxbin+1] << ","
|
Chris@24
|
514 << in[maxbin+2] << "] -> " << maxidx << std::endl;
|
Chris@24
|
515 */
|
Chris@24
|
516
|
Chris@24
|
517 return maxidx;
|
Chris@24
|
518 }
|
Chris@5
|
519
|
Chris@5
|
520 void
|
Chris@5
|
521 SimpleCepstrum::addStatisticalOutputs(FeatureSet &fs, const double *data)
|
Chris@5
|
522 {
|
Chris@5
|
523 int n = m_bins;
|
Chris@5
|
524
|
Chris@6
|
525 double maxval = 0.0;
|
Chris@5
|
526 int maxbin = 0;
|
Chris@5
|
527
|
Chris@5
|
528 for (int i = 0; i < n; ++i) {
|
Chris@5
|
529 if (data[i] > maxval) {
|
Chris@5
|
530 maxval = data[i];
|
Chris@6
|
531 maxbin = i;
|
Chris@5
|
532 }
|
Chris@5
|
533 }
|
Chris@5
|
534
|
Chris@20
|
535 double nextPeakVal = 0.0;
|
Chris@20
|
536
|
Chris@20
|
537 for (int i = 1; i+1 < n; ++i) {
|
Chris@20
|
538 if (data[i] > data[i-1] &&
|
Chris@20
|
539 data[i] > data[i+1] &&
|
Chris@20
|
540 i != maxbin &&
|
Chris@20
|
541 data[i] > nextPeakVal) {
|
Chris@20
|
542 nextPeakVal = data[i];
|
Chris@20
|
543 }
|
Chris@20
|
544 }
|
Chris@20
|
545
|
Chris@5
|
546 Feature rf;
|
Chris@24
|
547 Feature irf;
|
Chris@6
|
548 if (maxval > 0.0) {
|
Chris@6
|
549 rf.values.push_back(m_inputSampleRate / (maxbin + m_binFrom));
|
Chris@24
|
550 double cimax = findInterpolatedPeak(data, maxbin);
|
Chris@24
|
551 irf.values.push_back(m_inputSampleRate / (cimax + m_binFrom));
|
Chris@5
|
552 } else {
|
Chris@5
|
553 rf.values.push_back(0);
|
Chris@24
|
554 irf.values.push_back(0);
|
Chris@5
|
555 }
|
Chris@5
|
556 fs[m_pkOutput].push_back(rf);
|
Chris@24
|
557 fs[m_ipkOutput].push_back(irf);
|
Chris@5
|
558
|
Chris@6
|
559 double total = 0;
|
Chris@5
|
560 for (int i = 0; i < n; ++i) {
|
Chris@6
|
561 total += data[i];
|
Chris@5
|
562 }
|
Chris@5
|
563
|
Chris@6
|
564 Feature tot;
|
Chris@6
|
565 tot.values.push_back(total);
|
Chris@6
|
566 fs[m_totOutput].push_back(tot);
|
Chris@6
|
567
|
Chris@6
|
568 double mean = total / n;
|
Chris@6
|
569
|
Chris@6
|
570 double totsqr = 0;
|
Chris@8
|
571 double abstot = 0;
|
Chris@5
|
572 for (int i = 0; i < n; ++i) {
|
Chris@6
|
573 totsqr += data[i] * data[i];
|
Chris@8
|
574 abstot += fabs(data[i]);
|
Chris@5
|
575 }
|
Chris@6
|
576 double rms = sqrt(totsqr / n);
|
Chris@5
|
577
|
Chris@5
|
578 double variance = 0;
|
Chris@5
|
579 for (int i = 0; i < n; ++i) {
|
Chris@5
|
580 double dev = fabs(data[i] - mean);
|
Chris@5
|
581 variance += dev * dev;
|
Chris@5
|
582 }
|
Chris@5
|
583 variance /= n;
|
Chris@5
|
584
|
Chris@6
|
585 double aroundPeak = 0.0;
|
Chris@6
|
586 double peakProportion = 0.0;
|
Chris@6
|
587 if (maxval > 0.0) {
|
Chris@7
|
588 aroundPeak += fabs(maxval);
|
Chris@6
|
589 int i = maxbin - 1;
|
Chris@6
|
590 while (i > 0 && data[i] <= data[i+1]) {
|
Chris@7
|
591 aroundPeak += fabs(data[i]);
|
Chris@6
|
592 --i;
|
Chris@6
|
593 }
|
Chris@6
|
594 i = maxbin + 1;
|
Chris@6
|
595 while (i < n && data[i] <= data[i-1]) {
|
Chris@7
|
596 aroundPeak += fabs(data[i]);
|
Chris@6
|
597 ++i;
|
Chris@6
|
598 }
|
Chris@6
|
599 }
|
Chris@8
|
600 peakProportion = aroundPeak / abstot;
|
Chris@6
|
601 Feature pp;
|
Chris@6
|
602 pp.values.push_back(peakProportion);
|
Chris@6
|
603 fs[m_ppOutput].push_back(pp);
|
Chris@6
|
604
|
Chris@5
|
605 Feature vf;
|
Chris@5
|
606 vf.values.push_back(variance);
|
Chris@5
|
607 fs[m_varOutput].push_back(vf);
|
Chris@5
|
608
|
Chris@5
|
609 Feature pr;
|
Chris@5
|
610 pr.values.push_back(maxval - rms);
|
Chris@5
|
611 fs[m_p2rOutput].push_back(pr);
|
Chris@5
|
612
|
Chris@5
|
613 Feature pv;
|
Chris@5
|
614 pv.values.push_back(maxval);
|
Chris@5
|
615 fs[m_pvOutput].push_back(pv);
|
Chris@5
|
616
|
Chris@20
|
617 Feature pko;
|
Chris@20
|
618 if (nextPeakVal != 0.0) {
|
Chris@20
|
619 pko.values.push_back(maxval / nextPeakVal);
|
Chris@20
|
620 } else {
|
Chris@20
|
621 pko.values.push_back(0.0);
|
Chris@20
|
622 }
|
Chris@20
|
623 fs[m_pkoOutput].push_back(pko);
|
Chris@20
|
624
|
Chris@5
|
625 Feature am;
|
Chris@5
|
626 for (int i = 0; i < n; ++i) {
|
Chris@5
|
627 if (data[i] < rms) am.values.push_back(0);
|
Chris@5
|
628 else am.values.push_back(data[i] - rms);
|
Chris@5
|
629 }
|
Chris@5
|
630 fs[m_amOutput].push_back(am);
|
Chris@5
|
631 }
|
Chris@5
|
632
|
Chris@5
|
633 void
|
Chris@5
|
634 SimpleCepstrum::addEnvelopeOutputs(FeatureSet &fs, const float *const *inputBuffers, const double *cep)
|
Chris@5
|
635 {
|
Chris@5
|
636 // Wipe the higher cepstral bins in order to calculate the
|
Chris@5
|
637 // envelope. This calculation uses the raw cepstrum, not the
|
Chris@5
|
638 // filtered values (because only values "in frequency range" are
|
Chris@5
|
639 // filtered).
|
Chris@5
|
640 int bs = m_blockSize;
|
Chris@5
|
641 int hs = m_blockSize/2 + 1;
|
Chris@5
|
642
|
Chris@5
|
643 double *ecep = new double[bs];
|
Chris@5
|
644 for (int i = 0; i < m_binFrom; ++i) {
|
Chris@5
|
645 ecep[i] = cep[i] / bs;
|
Chris@5
|
646 }
|
Chris@5
|
647 for (int i = m_binFrom; i < bs; ++i) {
|
Chris@5
|
648 ecep[i] = 0;
|
Chris@5
|
649 }
|
Chris@5
|
650 ecep[0] /= 2;
|
Chris@5
|
651 ecep[m_binFrom-1] /= 2;
|
Chris@5
|
652
|
Chris@5
|
653 double *env = new double[bs];
|
Chris@5
|
654 double *io = new double[bs];
|
Chris@7
|
655
|
Chris@7
|
656 //!!! This is only right if the previous transform was an inverse one!
|
Chris@5
|
657 fft(bs, false, ecep, 0, env, io);
|
Chris@5
|
658
|
Chris@5
|
659 for (int i = 0; i < hs; ++i) {
|
Chris@5
|
660 env[i] = exp(env[i]);
|
Chris@5
|
661 }
|
Chris@5
|
662 Feature envf;
|
Chris@5
|
663 for (int i = 0; i < hs; ++i) {
|
Chris@5
|
664 envf.values.push_back(env[i]);
|
Chris@5
|
665 }
|
Chris@5
|
666 fs[m_envOutput].push_back(envf);
|
Chris@5
|
667
|
Chris@5
|
668 Feature es;
|
Chris@5
|
669 for (int i = 0; i < hs; ++i) {
|
Chris@5
|
670 double re = inputBuffers[0][i*2 ] / env[i];
|
Chris@5
|
671 double im = inputBuffers[0][i*2+1] / env[i];
|
Chris@5
|
672 double mag = sqrt(re*re + im*im);
|
Chris@5
|
673 es.values.push_back(mag);
|
Chris@5
|
674 }
|
Chris@5
|
675 fs[m_esOutput].push_back(es);
|
Chris@5
|
676
|
Chris@5
|
677 delete[] env;
|
Chris@5
|
678 delete[] ecep;
|
Chris@5
|
679 delete[] io;
|
Chris@0
|
680 }
|
Chris@0
|
681
|
Chris@0
|
682 SimpleCepstrum::FeatureSet
|
Chris@0
|
683 SimpleCepstrum::process(const float *const *inputBuffers, Vamp::RealTime timestamp)
|
Chris@0
|
684 {
|
Chris@1
|
685 FeatureSet fs;
|
Chris@1
|
686
|
Chris@0
|
687 int bs = m_blockSize;
|
Chris@0
|
688 int hs = m_blockSize/2 + 1;
|
Chris@0
|
689
|
Chris@5
|
690 double *rawcep = new double[bs];
|
Chris@3
|
691 double *io = new double[bs];
|
Chris@3
|
692
|
Chris@4
|
693 if (m_method != InverseComplex) {
|
Chris@3
|
694
|
Chris@4
|
695 double *logmag = new double[bs];
|
Chris@4
|
696
|
Chris@4
|
697 for (int i = 0; i < hs; ++i) {
|
Chris@3
|
698
|
Chris@4
|
699 double power =
|
Chris@4
|
700 inputBuffers[0][i*2 ] * inputBuffers[0][i*2 ] +
|
Chris@4
|
701 inputBuffers[0][i*2+1] * inputBuffers[0][i*2+1];
|
Chris@5
|
702 double mag = sqrt(power);
|
Chris@3
|
703
|
Chris@5
|
704 double lm = log(mag + 0.00000001);
|
Chris@4
|
705
|
Chris@4
|
706 switch (m_method) {
|
Chris@4
|
707 case InverseSymmetric:
|
Chris@4
|
708 logmag[i] = lm;
|
Chris@4
|
709 if (i > 0) logmag[bs - i] = lm;
|
Chris@4
|
710 break;
|
Chris@4
|
711 case InverseAsymmetric:
|
Chris@4
|
712 logmag[i] = lm;
|
Chris@4
|
713 if (i > 0) logmag[bs - i] = 0;
|
Chris@4
|
714 break;
|
Chris@4
|
715 default:
|
Chris@4
|
716 logmag[bs/2 + i - 1] = lm;
|
Chris@4
|
717 if (i < hs-1) {
|
Chris@4
|
718 logmag[bs/2 - i - 1] = lm;
|
Chris@4
|
719 }
|
Chris@4
|
720 break;
|
Chris@3
|
721 }
|
Chris@3
|
722 }
|
Chris@4
|
723
|
Chris@4
|
724 if (m_method == InverseSymmetric ||
|
Chris@4
|
725 m_method == InverseAsymmetric) {
|
Chris@4
|
726
|
Chris@5
|
727 fft(bs, true, logmag, 0, rawcep, io);
|
Chris@4
|
728
|
Chris@4
|
729 } else {
|
Chris@4
|
730
|
Chris@5
|
731 fft(bs, false, logmag, 0, rawcep, io);
|
Chris@4
|
732
|
Chris@4
|
733 if (m_method == ForwardDifference) {
|
Chris@4
|
734 for (int i = 0; i < hs; ++i) {
|
Chris@5
|
735 rawcep[i] = fabs(io[i]) - fabs(rawcep[i]);
|
Chris@4
|
736 }
|
Chris@4
|
737 } else {
|
Chris@4
|
738 for (int i = 0; i < hs; ++i) {
|
Chris@5
|
739 rawcep[i] = sqrt(rawcep[i]*rawcep[i] + io[i]*io[i]);
|
Chris@4
|
740 }
|
Chris@4
|
741 }
|
Chris@4
|
742 }
|
Chris@4
|
743
|
Chris@4
|
744 delete[] logmag;
|
Chris@4
|
745
|
Chris@4
|
746 } else { // InverseComplex
|
Chris@4
|
747
|
Chris@4
|
748 double *ri = new double[bs];
|
Chris@4
|
749 double *ii = new double[bs];
|
Chris@4
|
750
|
Chris@4
|
751 for (int i = 0; i < hs; ++i) {
|
Chris@4
|
752 double re = inputBuffers[0][i*2];
|
Chris@4
|
753 double im = inputBuffers[0][i*2+1];
|
Chris@4
|
754 std::complex<double> c(re, im);
|
Chris@4
|
755 std::complex<double> clog = std::log(c);
|
Chris@4
|
756 ri[i] = clog.real();
|
Chris@4
|
757 ii[i] = clog.imag();
|
Chris@4
|
758 if (i > 0) {
|
Chris@4
|
759 ri[bs - i] = ri[i];
|
Chris@4
|
760 ii[bs - i] = -ii[i];
|
Chris@4
|
761 }
|
Chris@4
|
762 }
|
Chris@4
|
763
|
Chris@5
|
764 fft(bs, true, ri, ii, rawcep, io);
|
Chris@4
|
765
|
Chris@4
|
766 delete[] ri;
|
Chris@4
|
767 delete[] ii;
|
Chris@3
|
768 }
|
Chris@0
|
769
|
Chris@0
|
770 if (m_clamp) {
|
Chris@0
|
771 for (int i = 0; i < bs; ++i) {
|
Chris@5
|
772 if (rawcep[i] < 0) rawcep[i] = 0;
|
Chris@0
|
773 }
|
Chris@0
|
774 }
|
Chris@0
|
775
|
Chris@5
|
776 delete[] io;
|
Chris@0
|
777
|
Chris@5
|
778 double *latest = new double[m_bins];
|
Chris@5
|
779 filter(rawcep, latest);
|
Chris@5
|
780
|
Chris@5
|
781 int n = m_bins;
|
Chris@0
|
782
|
Chris@0
|
783 Feature cf;
|
Chris@5
|
784 for (int i = 0; i < n; ++i) {
|
Chris@5
|
785 cf.values.push_back(latest[i]);
|
Chris@0
|
786 }
|
Chris@0
|
787 fs[m_cepOutput].push_back(cf);
|
Chris@0
|
788
|
Chris@5
|
789 addStatisticalOutputs(fs, latest);
|
Chris@0
|
790
|
Chris@5
|
791 addEnvelopeOutputs(fs, inputBuffers, rawcep);
|
Chris@0
|
792
|
Chris@5
|
793 delete[] latest;
|
Chris@7
|
794 delete[] rawcep;
|
Chris@0
|
795
|
Chris@0
|
796 return fs;
|
Chris@0
|
797 }
|
Chris@0
|
798
|
Chris@0
|
799 SimpleCepstrum::FeatureSet
|
Chris@0
|
800 SimpleCepstrum::getRemainingFeatures()
|
Chris@0
|
801 {
|
Chris@0
|
802 FeatureSet fs;
|
Chris@0
|
803 return fs;
|
Chris@0
|
804 }
|
Chris@0
|
805
|
Chris@0
|
806 void
|
Chris@0
|
807 SimpleCepstrum::fft(unsigned int n, bool inverse,
|
Chris@0
|
808 double *ri, double *ii, double *ro, double *io)
|
Chris@0
|
809 {
|
Chris@0
|
810 if (!ri || !ro || !io) return;
|
Chris@0
|
811
|
Chris@0
|
812 unsigned int bits;
|
Chris@0
|
813 unsigned int i, j, k, m;
|
Chris@0
|
814 unsigned int blockSize, blockEnd;
|
Chris@0
|
815
|
Chris@0
|
816 double tr, ti;
|
Chris@0
|
817
|
Chris@0
|
818 if (n < 2) return;
|
Chris@0
|
819 if (n & (n-1)) return;
|
Chris@0
|
820
|
Chris@0
|
821 double angle = 2.0 * M_PI;
|
Chris@0
|
822 if (inverse) angle = -angle;
|
Chris@0
|
823
|
Chris@0
|
824 for (i = 0; ; ++i) {
|
Chris@0
|
825 if (n & (1 << i)) {
|
Chris@0
|
826 bits = i;
|
Chris@0
|
827 break;
|
Chris@0
|
828 }
|
Chris@0
|
829 }
|
Chris@0
|
830
|
Chris@0
|
831 static unsigned int tableSize = 0;
|
Chris@0
|
832 static int *table = 0;
|
Chris@0
|
833
|
Chris@0
|
834 if (tableSize != n) {
|
Chris@0
|
835
|
Chris@0
|
836 delete[] table;
|
Chris@0
|
837
|
Chris@0
|
838 table = new int[n];
|
Chris@0
|
839
|
Chris@0
|
840 for (i = 0; i < n; ++i) {
|
Chris@0
|
841
|
Chris@0
|
842 m = i;
|
Chris@0
|
843
|
Chris@0
|
844 for (j = k = 0; j < bits; ++j) {
|
Chris@0
|
845 k = (k << 1) | (m & 1);
|
Chris@0
|
846 m >>= 1;
|
Chris@0
|
847 }
|
Chris@0
|
848
|
Chris@0
|
849 table[i] = k;
|
Chris@0
|
850 }
|
Chris@0
|
851
|
Chris@0
|
852 tableSize = n;
|
Chris@0
|
853 }
|
Chris@0
|
854
|
Chris@0
|
855 if (ii) {
|
Chris@0
|
856 for (i = 0; i < n; ++i) {
|
Chris@0
|
857 ro[table[i]] = ri[i];
|
Chris@0
|
858 io[table[i]] = ii[i];
|
Chris@0
|
859 }
|
Chris@0
|
860 } else {
|
Chris@0
|
861 for (i = 0; i < n; ++i) {
|
Chris@0
|
862 ro[table[i]] = ri[i];
|
Chris@0
|
863 io[table[i]] = 0.0;
|
Chris@0
|
864 }
|
Chris@0
|
865 }
|
Chris@0
|
866
|
Chris@0
|
867 blockEnd = 1;
|
Chris@0
|
868
|
Chris@0
|
869 for (blockSize = 2; blockSize <= n; blockSize <<= 1) {
|
Chris@0
|
870
|
Chris@0
|
871 double delta = angle / (double)blockSize;
|
Chris@0
|
872 double sm2 = -sin(-2 * delta);
|
Chris@0
|
873 double sm1 = -sin(-delta);
|
Chris@0
|
874 double cm2 = cos(-2 * delta);
|
Chris@0
|
875 double cm1 = cos(-delta);
|
Chris@0
|
876 double w = 2 * cm1;
|
Chris@0
|
877 double ar[3], ai[3];
|
Chris@0
|
878
|
Chris@0
|
879 for (i = 0; i < n; i += blockSize) {
|
Chris@0
|
880
|
Chris@0
|
881 ar[2] = cm2;
|
Chris@0
|
882 ar[1] = cm1;
|
Chris@0
|
883
|
Chris@0
|
884 ai[2] = sm2;
|
Chris@0
|
885 ai[1] = sm1;
|
Chris@0
|
886
|
Chris@0
|
887 for (j = i, m = 0; m < blockEnd; j++, m++) {
|
Chris@0
|
888
|
Chris@0
|
889 ar[0] = w * ar[1] - ar[2];
|
Chris@0
|
890 ar[2] = ar[1];
|
Chris@0
|
891 ar[1] = ar[0];
|
Chris@0
|
892
|
Chris@0
|
893 ai[0] = w * ai[1] - ai[2];
|
Chris@0
|
894 ai[2] = ai[1];
|
Chris@0
|
895 ai[1] = ai[0];
|
Chris@0
|
896
|
Chris@0
|
897 k = j + blockEnd;
|
Chris@0
|
898 tr = ar[0] * ro[k] - ai[0] * io[k];
|
Chris@0
|
899 ti = ar[0] * io[k] + ai[0] * ro[k];
|
Chris@0
|
900
|
Chris@0
|
901 ro[k] = ro[j] - tr;
|
Chris@0
|
902 io[k] = io[j] - ti;
|
Chris@0
|
903
|
Chris@0
|
904 ro[j] += tr;
|
Chris@0
|
905 io[j] += ti;
|
Chris@0
|
906 }
|
Chris@0
|
907 }
|
Chris@0
|
908
|
Chris@0
|
909 blockEnd = blockSize;
|
Chris@0
|
910 }
|
Chris@0
|
911 }
|
Chris@0
|
912
|
Chris@0
|
913
|