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@7
|
260 d.description = "Return the frequency whose period corresponds to the quefrency with the maximum 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@0
|
273 d.identifier = "variance";
|
Chris@0
|
274 d.name = "Variance of cepstral bins in range";
|
Chris@0
|
275 d.unit = "";
|
Chris@2
|
276 d.description = "Return the variance of bin values within the specified range of the cepstrum";
|
Chris@6
|
277 d.hasKnownExtents = false;
|
Chris@0
|
278 m_varOutput = n++;
|
Chris@0
|
279 outputs.push_back(d);
|
Chris@0
|
280
|
Chris@0
|
281 d.identifier = "peak";
|
Chris@8
|
282 d.name = "Value at peak";
|
Chris@0
|
283 d.unit = "";
|
Chris@2
|
284 d.description = "Return the value found in the maximum-valued bin within the specified range of the cepstrum";
|
Chris@0
|
285 m_pvOutput = n++;
|
Chris@0
|
286 outputs.push_back(d);
|
Chris@0
|
287
|
Chris@5
|
288 d.identifier = "peak_to_rms";
|
Chris@5
|
289 d.name = "Peak-to-RMS distance";
|
Chris@5
|
290 d.unit = "";
|
Chris@5
|
291 d.description = "Return the difference between maximum and root mean square bin values within the specified range of the cepstrum";
|
Chris@5
|
292 m_p2rOutput = n++;
|
Chris@5
|
293 outputs.push_back(d);
|
Chris@5
|
294
|
Chris@6
|
295 d.identifier = "peak_proportion";
|
Chris@7
|
296 d.name = "Energy around peak";
|
Chris@6
|
297 d.unit = "";
|
Chris@7
|
298 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
|
299 m_ppOutput = n++;
|
Chris@6
|
300 outputs.push_back(d);
|
Chris@6
|
301
|
Chris@6
|
302 d.identifier = "total";
|
Chris@6
|
303 d.name = "Total energy";
|
Chris@6
|
304 d.unit = "";
|
Chris@7
|
305 d.description = "Return the total energy found in all bins within the specified range of the cepstrum";
|
Chris@6
|
306 m_totOutput = n++;
|
Chris@6
|
307 outputs.push_back(d);
|
Chris@6
|
308
|
Chris@0
|
309 d.identifier = "cepstrum";
|
Chris@0
|
310 d.name = "Cepstrum";
|
Chris@0
|
311 d.unit = "";
|
Chris@2
|
312 d.description = "The unprocessed cepstrum bins within the specified range";
|
Chris@0
|
313
|
Chris@0
|
314 int from = int(m_inputSampleRate / m_fmax);
|
Chris@0
|
315 int to = int(m_inputSampleRate / m_fmin);
|
Chris@0
|
316 if (to >= (int)m_blockSize / 2) {
|
Chris@0
|
317 to = m_blockSize / 2 - 1;
|
Chris@0
|
318 }
|
Chris@0
|
319 d.binCount = to - from + 1;
|
Chris@0
|
320 for (int i = from; i <= to; ++i) {
|
Chris@0
|
321 float freq = m_inputSampleRate / i;
|
Chris@5
|
322 char buffer[20];
|
Chris@2
|
323 sprintf(buffer, "%.2f Hz", freq);
|
Chris@0
|
324 d.binNames.push_back(buffer);
|
Chris@0
|
325 }
|
Chris@0
|
326
|
Chris@0
|
327 d.hasKnownExtents = false;
|
Chris@0
|
328 m_cepOutput = n++;
|
Chris@0
|
329 outputs.push_back(d);
|
Chris@0
|
330
|
Chris@0
|
331 d.identifier = "am";
|
Chris@5
|
332 d.name = "Cepstrum bins relative to RMS";
|
Chris@5
|
333 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
|
334 m_amOutput = n++;
|
Chris@0
|
335 outputs.push_back(d);
|
Chris@0
|
336
|
Chris@2
|
337 d.identifier = "env";
|
Chris@2
|
338 d.name = "Spectral envelope";
|
Chris@2
|
339 d.description = "Envelope calculated from the cepstral values below the specified minimum";
|
Chris@2
|
340 d.binCount = m_blockSize/2 + 1;
|
Chris@2
|
341 d.binNames.clear();
|
Chris@7
|
342 for (int i = 0; i < (int)d.binCount; ++i) {
|
Chris@2
|
343 float freq = (m_inputSampleRate / m_blockSize) * i;
|
Chris@5
|
344 char buffer[20];
|
Chris@2
|
345 sprintf(buffer, "%.2f Hz", freq);
|
Chris@2
|
346 d.binNames.push_back(buffer);
|
Chris@2
|
347 }
|
Chris@2
|
348 m_envOutput = n++;
|
Chris@2
|
349 outputs.push_back(d);
|
Chris@2
|
350
|
Chris@2
|
351 d.identifier = "es";
|
Chris@2
|
352 d.name = "Spectrum without envelope";
|
Chris@2
|
353 d.description = "Magnitude of spectrum values divided by calculated envelope values, to deconvolve the envelope";
|
Chris@2
|
354 m_esOutput = n++;
|
Chris@2
|
355 outputs.push_back(d);
|
Chris@2
|
356
|
Chris@0
|
357 return outputs;
|
Chris@0
|
358 }
|
Chris@0
|
359
|
Chris@0
|
360 bool
|
Chris@0
|
361 SimpleCepstrum::initialise(size_t channels, size_t stepSize, size_t blockSize)
|
Chris@0
|
362 {
|
Chris@0
|
363 if (channels < getMinChannelCount() ||
|
Chris@0
|
364 channels > getMaxChannelCount()) return false;
|
Chris@0
|
365
|
Chris@0
|
366 // std::cerr << "SimpleCepstrum::initialise: channels = " << channels
|
Chris@0
|
367 // << ", stepSize = " << stepSize << ", blockSize = " << blockSize
|
Chris@0
|
368 // << std::endl;
|
Chris@0
|
369
|
Chris@0
|
370 m_channels = channels;
|
Chris@0
|
371 m_stepSize = stepSize;
|
Chris@0
|
372 m_blockSize = blockSize;
|
Chris@0
|
373
|
Chris@5
|
374 m_binFrom = int(m_inputSampleRate / m_fmax);
|
Chris@5
|
375 m_binTo = int(m_inputSampleRate / m_fmin);
|
Chris@5
|
376
|
Chris@7
|
377 if (m_binTo >= (int)m_blockSize / 2) {
|
Chris@5
|
378 m_binTo = m_blockSize / 2 - 1;
|
Chris@5
|
379 }
|
Chris@5
|
380
|
Chris@5
|
381 m_bins = (m_binTo - m_binFrom) + 1;
|
Chris@5
|
382
|
Chris@5
|
383 m_history = new double *[m_histlen];
|
Chris@5
|
384 for (int i = 0; i < m_histlen; ++i) {
|
Chris@5
|
385 m_history[i] = new double[m_bins];
|
Chris@5
|
386 }
|
Chris@5
|
387
|
Chris@5
|
388 reset();
|
Chris@5
|
389
|
Chris@0
|
390 return true;
|
Chris@0
|
391 }
|
Chris@0
|
392
|
Chris@0
|
393 void
|
Chris@0
|
394 SimpleCepstrum::reset()
|
Chris@0
|
395 {
|
Chris@5
|
396 for (int i = 0; i < m_histlen; ++i) {
|
Chris@5
|
397 for (int j = 0; j < m_bins; ++j) {
|
Chris@5
|
398 m_history[i][j] = 0.0;
|
Chris@5
|
399 }
|
Chris@5
|
400 }
|
Chris@5
|
401 }
|
Chris@5
|
402
|
Chris@5
|
403 void
|
Chris@5
|
404 SimpleCepstrum::filter(const double *cep, double *result)
|
Chris@5
|
405 {
|
Chris@5
|
406 int hix = m_histlen - 1; // current history index
|
Chris@5
|
407
|
Chris@5
|
408 // roll back the history
|
Chris@5
|
409 if (m_histlen > 1) {
|
Chris@5
|
410 double *oldest = m_history[0];
|
Chris@5
|
411 for (int i = 1; i < m_histlen; ++i) {
|
Chris@5
|
412 m_history[i-1] = m_history[i];
|
Chris@5
|
413 }
|
Chris@5
|
414 // and stick this back in the newest spot, to recycle
|
Chris@5
|
415 m_history[hix] = oldest;
|
Chris@5
|
416 }
|
Chris@5
|
417
|
Chris@5
|
418 for (int i = 0; i < m_bins; ++i) {
|
Chris@10
|
419 double v = 0;
|
Chris@10
|
420 int n = 0;
|
Chris@10
|
421 // average according to the vertical filter length
|
Chris@10
|
422 for (int j = -m_vflen/2; j <= m_vflen/2; ++j) {
|
Chris@10
|
423 int ix = i + m_binFrom + j;
|
Chris@10
|
424 if (ix >= 0 && ix < m_blockSize) {
|
Chris@10
|
425 v += cep[ix];
|
Chris@10
|
426 ++n;
|
Chris@10
|
427 }
|
Chris@10
|
428 }
|
Chris@10
|
429 m_history[hix][i] = v / n;
|
Chris@5
|
430 }
|
Chris@5
|
431
|
Chris@5
|
432 for (int i = 0; i < m_bins; ++i) {
|
Chris@5
|
433 double mean = 0.0;
|
Chris@5
|
434 for (int j = 0; j < m_histlen; ++j) {
|
Chris@5
|
435 mean += m_history[j][i];
|
Chris@5
|
436 }
|
Chris@5
|
437 mean /= m_histlen;
|
Chris@5
|
438 result[i] = mean;
|
Chris@5
|
439 }
|
Chris@5
|
440 }
|
Chris@5
|
441
|
Chris@5
|
442 void
|
Chris@5
|
443 SimpleCepstrum::addStatisticalOutputs(FeatureSet &fs, const double *data)
|
Chris@5
|
444 {
|
Chris@5
|
445 int n = m_bins;
|
Chris@5
|
446
|
Chris@6
|
447 double maxval = 0.0;
|
Chris@5
|
448 int maxbin = 0;
|
Chris@5
|
449
|
Chris@5
|
450 for (int i = 0; i < n; ++i) {
|
Chris@5
|
451 if (data[i] > maxval) {
|
Chris@5
|
452 maxval = data[i];
|
Chris@6
|
453 maxbin = i;
|
Chris@5
|
454 }
|
Chris@5
|
455 }
|
Chris@5
|
456
|
Chris@5
|
457 Feature rf;
|
Chris@6
|
458 if (maxval > 0.0) {
|
Chris@6
|
459 rf.values.push_back(m_inputSampleRate / (maxbin + m_binFrom));
|
Chris@5
|
460 } else {
|
Chris@5
|
461 rf.values.push_back(0);
|
Chris@5
|
462 }
|
Chris@5
|
463 fs[m_pkOutput].push_back(rf);
|
Chris@5
|
464
|
Chris@6
|
465 double total = 0;
|
Chris@5
|
466 for (int i = 0; i < n; ++i) {
|
Chris@6
|
467 total += data[i];
|
Chris@5
|
468 }
|
Chris@5
|
469
|
Chris@6
|
470 Feature tot;
|
Chris@6
|
471 tot.values.push_back(total);
|
Chris@6
|
472 fs[m_totOutput].push_back(tot);
|
Chris@6
|
473
|
Chris@6
|
474 double mean = total / n;
|
Chris@6
|
475
|
Chris@6
|
476 double totsqr = 0;
|
Chris@8
|
477 double abstot = 0;
|
Chris@5
|
478 for (int i = 0; i < n; ++i) {
|
Chris@6
|
479 totsqr += data[i] * data[i];
|
Chris@8
|
480 abstot += fabs(data[i]);
|
Chris@5
|
481 }
|
Chris@6
|
482 double rms = sqrt(totsqr / n);
|
Chris@5
|
483
|
Chris@5
|
484 double variance = 0;
|
Chris@5
|
485 for (int i = 0; i < n; ++i) {
|
Chris@5
|
486 double dev = fabs(data[i] - mean);
|
Chris@5
|
487 variance += dev * dev;
|
Chris@5
|
488 }
|
Chris@5
|
489 variance /= n;
|
Chris@5
|
490
|
Chris@6
|
491 double aroundPeak = 0.0;
|
Chris@6
|
492 double peakProportion = 0.0;
|
Chris@6
|
493 if (maxval > 0.0) {
|
Chris@7
|
494 aroundPeak += fabs(maxval);
|
Chris@6
|
495 int i = maxbin - 1;
|
Chris@6
|
496 while (i > 0 && data[i] <= data[i+1]) {
|
Chris@7
|
497 aroundPeak += fabs(data[i]);
|
Chris@6
|
498 --i;
|
Chris@6
|
499 }
|
Chris@6
|
500 i = maxbin + 1;
|
Chris@6
|
501 while (i < n && data[i] <= data[i-1]) {
|
Chris@7
|
502 aroundPeak += fabs(data[i]);
|
Chris@6
|
503 ++i;
|
Chris@6
|
504 }
|
Chris@6
|
505 }
|
Chris@8
|
506 peakProportion = aroundPeak / abstot;
|
Chris@6
|
507 Feature pp;
|
Chris@6
|
508 pp.values.push_back(peakProportion);
|
Chris@6
|
509 fs[m_ppOutput].push_back(pp);
|
Chris@6
|
510
|
Chris@5
|
511 Feature vf;
|
Chris@5
|
512 vf.values.push_back(variance);
|
Chris@5
|
513 fs[m_varOutput].push_back(vf);
|
Chris@5
|
514
|
Chris@5
|
515 Feature pr;
|
Chris@5
|
516 pr.values.push_back(maxval - rms);
|
Chris@5
|
517 fs[m_p2rOutput].push_back(pr);
|
Chris@5
|
518
|
Chris@5
|
519 Feature pv;
|
Chris@5
|
520 pv.values.push_back(maxval);
|
Chris@5
|
521 fs[m_pvOutput].push_back(pv);
|
Chris@5
|
522
|
Chris@5
|
523 Feature am;
|
Chris@5
|
524 for (int i = 0; i < n; ++i) {
|
Chris@5
|
525 if (data[i] < rms) am.values.push_back(0);
|
Chris@5
|
526 else am.values.push_back(data[i] - rms);
|
Chris@5
|
527 }
|
Chris@5
|
528 fs[m_amOutput].push_back(am);
|
Chris@5
|
529 }
|
Chris@5
|
530
|
Chris@5
|
531 void
|
Chris@5
|
532 SimpleCepstrum::addEnvelopeOutputs(FeatureSet &fs, const float *const *inputBuffers, const double *cep)
|
Chris@5
|
533 {
|
Chris@5
|
534 // Wipe the higher cepstral bins in order to calculate the
|
Chris@5
|
535 // envelope. This calculation uses the raw cepstrum, not the
|
Chris@5
|
536 // filtered values (because only values "in frequency range" are
|
Chris@5
|
537 // filtered).
|
Chris@5
|
538 int bs = m_blockSize;
|
Chris@5
|
539 int hs = m_blockSize/2 + 1;
|
Chris@5
|
540
|
Chris@5
|
541 double *ecep = new double[bs];
|
Chris@5
|
542 for (int i = 0; i < m_binFrom; ++i) {
|
Chris@5
|
543 ecep[i] = cep[i] / bs;
|
Chris@5
|
544 }
|
Chris@5
|
545 for (int i = m_binFrom; i < bs; ++i) {
|
Chris@5
|
546 ecep[i] = 0;
|
Chris@5
|
547 }
|
Chris@5
|
548 ecep[0] /= 2;
|
Chris@5
|
549 ecep[m_binFrom-1] /= 2;
|
Chris@5
|
550
|
Chris@5
|
551 double *env = new double[bs];
|
Chris@5
|
552 double *io = new double[bs];
|
Chris@7
|
553
|
Chris@7
|
554 //!!! This is only right if the previous transform was an inverse one!
|
Chris@5
|
555 fft(bs, false, ecep, 0, env, io);
|
Chris@5
|
556
|
Chris@5
|
557 for (int i = 0; i < hs; ++i) {
|
Chris@5
|
558 env[i] = exp(env[i]);
|
Chris@5
|
559 }
|
Chris@5
|
560 Feature envf;
|
Chris@5
|
561 for (int i = 0; i < hs; ++i) {
|
Chris@5
|
562 envf.values.push_back(env[i]);
|
Chris@5
|
563 }
|
Chris@5
|
564 fs[m_envOutput].push_back(envf);
|
Chris@5
|
565
|
Chris@5
|
566 Feature es;
|
Chris@5
|
567 for (int i = 0; i < hs; ++i) {
|
Chris@5
|
568 double re = inputBuffers[0][i*2 ] / env[i];
|
Chris@5
|
569 double im = inputBuffers[0][i*2+1] / env[i];
|
Chris@5
|
570 double mag = sqrt(re*re + im*im);
|
Chris@5
|
571 es.values.push_back(mag);
|
Chris@5
|
572 }
|
Chris@5
|
573 fs[m_esOutput].push_back(es);
|
Chris@5
|
574
|
Chris@5
|
575 delete[] env;
|
Chris@5
|
576 delete[] ecep;
|
Chris@5
|
577 delete[] io;
|
Chris@0
|
578 }
|
Chris@0
|
579
|
Chris@0
|
580 SimpleCepstrum::FeatureSet
|
Chris@0
|
581 SimpleCepstrum::process(const float *const *inputBuffers, Vamp::RealTime timestamp)
|
Chris@0
|
582 {
|
Chris@1
|
583 FeatureSet fs;
|
Chris@1
|
584
|
Chris@0
|
585 int bs = m_blockSize;
|
Chris@0
|
586 int hs = m_blockSize/2 + 1;
|
Chris@0
|
587
|
Chris@5
|
588 double *rawcep = new double[bs];
|
Chris@3
|
589 double *io = new double[bs];
|
Chris@3
|
590
|
Chris@4
|
591 if (m_method != InverseComplex) {
|
Chris@3
|
592
|
Chris@4
|
593 double *logmag = new double[bs];
|
Chris@4
|
594
|
Chris@4
|
595 for (int i = 0; i < hs; ++i) {
|
Chris@3
|
596
|
Chris@4
|
597 double power =
|
Chris@4
|
598 inputBuffers[0][i*2 ] * inputBuffers[0][i*2 ] +
|
Chris@4
|
599 inputBuffers[0][i*2+1] * inputBuffers[0][i*2+1];
|
Chris@5
|
600 double mag = sqrt(power);
|
Chris@3
|
601
|
Chris@5
|
602 double lm = log(mag + 0.00000001);
|
Chris@4
|
603
|
Chris@4
|
604 switch (m_method) {
|
Chris@4
|
605 case InverseSymmetric:
|
Chris@4
|
606 logmag[i] = lm;
|
Chris@4
|
607 if (i > 0) logmag[bs - i] = lm;
|
Chris@4
|
608 break;
|
Chris@4
|
609 case InverseAsymmetric:
|
Chris@4
|
610 logmag[i] = lm;
|
Chris@4
|
611 if (i > 0) logmag[bs - i] = 0;
|
Chris@4
|
612 break;
|
Chris@4
|
613 default:
|
Chris@4
|
614 logmag[bs/2 + i - 1] = lm;
|
Chris@4
|
615 if (i < hs-1) {
|
Chris@4
|
616 logmag[bs/2 - i - 1] = lm;
|
Chris@4
|
617 }
|
Chris@4
|
618 break;
|
Chris@3
|
619 }
|
Chris@3
|
620 }
|
Chris@4
|
621
|
Chris@4
|
622 if (m_method == InverseSymmetric ||
|
Chris@4
|
623 m_method == InverseAsymmetric) {
|
Chris@4
|
624
|
Chris@5
|
625 fft(bs, true, logmag, 0, rawcep, io);
|
Chris@4
|
626
|
Chris@4
|
627 } else {
|
Chris@4
|
628
|
Chris@5
|
629 fft(bs, false, logmag, 0, rawcep, io);
|
Chris@4
|
630
|
Chris@4
|
631 if (m_method == ForwardDifference) {
|
Chris@4
|
632 for (int i = 0; i < hs; ++i) {
|
Chris@5
|
633 rawcep[i] = fabs(io[i]) - fabs(rawcep[i]);
|
Chris@4
|
634 }
|
Chris@4
|
635 } else {
|
Chris@4
|
636 for (int i = 0; i < hs; ++i) {
|
Chris@5
|
637 rawcep[i] = sqrt(rawcep[i]*rawcep[i] + io[i]*io[i]);
|
Chris@4
|
638 }
|
Chris@4
|
639 }
|
Chris@4
|
640 }
|
Chris@4
|
641
|
Chris@4
|
642 delete[] logmag;
|
Chris@4
|
643
|
Chris@4
|
644 } else { // InverseComplex
|
Chris@4
|
645
|
Chris@4
|
646 double *ri = new double[bs];
|
Chris@4
|
647 double *ii = new double[bs];
|
Chris@4
|
648
|
Chris@4
|
649 for (int i = 0; i < hs; ++i) {
|
Chris@4
|
650 double re = inputBuffers[0][i*2];
|
Chris@4
|
651 double im = inputBuffers[0][i*2+1];
|
Chris@4
|
652 std::complex<double> c(re, im);
|
Chris@4
|
653 std::complex<double> clog = std::log(c);
|
Chris@4
|
654 ri[i] = clog.real();
|
Chris@4
|
655 ii[i] = clog.imag();
|
Chris@4
|
656 if (i > 0) {
|
Chris@4
|
657 ri[bs - i] = ri[i];
|
Chris@4
|
658 ii[bs - i] = -ii[i];
|
Chris@4
|
659 }
|
Chris@4
|
660 }
|
Chris@4
|
661
|
Chris@5
|
662 fft(bs, true, ri, ii, rawcep, io);
|
Chris@4
|
663
|
Chris@4
|
664 delete[] ri;
|
Chris@4
|
665 delete[] ii;
|
Chris@3
|
666 }
|
Chris@0
|
667
|
Chris@0
|
668 if (m_clamp) {
|
Chris@0
|
669 for (int i = 0; i < bs; ++i) {
|
Chris@5
|
670 if (rawcep[i] < 0) rawcep[i] = 0;
|
Chris@0
|
671 }
|
Chris@0
|
672 }
|
Chris@0
|
673
|
Chris@5
|
674 delete[] io;
|
Chris@0
|
675
|
Chris@5
|
676 double *latest = new double[m_bins];
|
Chris@5
|
677 filter(rawcep, latest);
|
Chris@5
|
678
|
Chris@5
|
679 int n = m_bins;
|
Chris@0
|
680
|
Chris@0
|
681 Feature cf;
|
Chris@5
|
682 for (int i = 0; i < n; ++i) {
|
Chris@5
|
683 cf.values.push_back(latest[i]);
|
Chris@0
|
684 }
|
Chris@0
|
685 fs[m_cepOutput].push_back(cf);
|
Chris@0
|
686
|
Chris@5
|
687 addStatisticalOutputs(fs, latest);
|
Chris@0
|
688
|
Chris@5
|
689 addEnvelopeOutputs(fs, inputBuffers, rawcep);
|
Chris@0
|
690
|
Chris@5
|
691 delete[] latest;
|
Chris@7
|
692 delete[] rawcep;
|
Chris@0
|
693
|
Chris@0
|
694 return fs;
|
Chris@0
|
695 }
|
Chris@0
|
696
|
Chris@0
|
697 SimpleCepstrum::FeatureSet
|
Chris@0
|
698 SimpleCepstrum::getRemainingFeatures()
|
Chris@0
|
699 {
|
Chris@0
|
700 FeatureSet fs;
|
Chris@0
|
701 return fs;
|
Chris@0
|
702 }
|
Chris@0
|
703
|
Chris@0
|
704 void
|
Chris@0
|
705 SimpleCepstrum::fft(unsigned int n, bool inverse,
|
Chris@0
|
706 double *ri, double *ii, double *ro, double *io)
|
Chris@0
|
707 {
|
Chris@0
|
708 if (!ri || !ro || !io) return;
|
Chris@0
|
709
|
Chris@0
|
710 unsigned int bits;
|
Chris@0
|
711 unsigned int i, j, k, m;
|
Chris@0
|
712 unsigned int blockSize, blockEnd;
|
Chris@0
|
713
|
Chris@0
|
714 double tr, ti;
|
Chris@0
|
715
|
Chris@0
|
716 if (n < 2) return;
|
Chris@0
|
717 if (n & (n-1)) return;
|
Chris@0
|
718
|
Chris@0
|
719 double angle = 2.0 * M_PI;
|
Chris@0
|
720 if (inverse) angle = -angle;
|
Chris@0
|
721
|
Chris@0
|
722 for (i = 0; ; ++i) {
|
Chris@0
|
723 if (n & (1 << i)) {
|
Chris@0
|
724 bits = i;
|
Chris@0
|
725 break;
|
Chris@0
|
726 }
|
Chris@0
|
727 }
|
Chris@0
|
728
|
Chris@0
|
729 static unsigned int tableSize = 0;
|
Chris@0
|
730 static int *table = 0;
|
Chris@0
|
731
|
Chris@0
|
732 if (tableSize != n) {
|
Chris@0
|
733
|
Chris@0
|
734 delete[] table;
|
Chris@0
|
735
|
Chris@0
|
736 table = new int[n];
|
Chris@0
|
737
|
Chris@0
|
738 for (i = 0; i < n; ++i) {
|
Chris@0
|
739
|
Chris@0
|
740 m = i;
|
Chris@0
|
741
|
Chris@0
|
742 for (j = k = 0; j < bits; ++j) {
|
Chris@0
|
743 k = (k << 1) | (m & 1);
|
Chris@0
|
744 m >>= 1;
|
Chris@0
|
745 }
|
Chris@0
|
746
|
Chris@0
|
747 table[i] = k;
|
Chris@0
|
748 }
|
Chris@0
|
749
|
Chris@0
|
750 tableSize = n;
|
Chris@0
|
751 }
|
Chris@0
|
752
|
Chris@0
|
753 if (ii) {
|
Chris@0
|
754 for (i = 0; i < n; ++i) {
|
Chris@0
|
755 ro[table[i]] = ri[i];
|
Chris@0
|
756 io[table[i]] = ii[i];
|
Chris@0
|
757 }
|
Chris@0
|
758 } else {
|
Chris@0
|
759 for (i = 0; i < n; ++i) {
|
Chris@0
|
760 ro[table[i]] = ri[i];
|
Chris@0
|
761 io[table[i]] = 0.0;
|
Chris@0
|
762 }
|
Chris@0
|
763 }
|
Chris@0
|
764
|
Chris@0
|
765 blockEnd = 1;
|
Chris@0
|
766
|
Chris@0
|
767 for (blockSize = 2; blockSize <= n; blockSize <<= 1) {
|
Chris@0
|
768
|
Chris@0
|
769 double delta = angle / (double)blockSize;
|
Chris@0
|
770 double sm2 = -sin(-2 * delta);
|
Chris@0
|
771 double sm1 = -sin(-delta);
|
Chris@0
|
772 double cm2 = cos(-2 * delta);
|
Chris@0
|
773 double cm1 = cos(-delta);
|
Chris@0
|
774 double w = 2 * cm1;
|
Chris@0
|
775 double ar[3], ai[3];
|
Chris@0
|
776
|
Chris@0
|
777 for (i = 0; i < n; i += blockSize) {
|
Chris@0
|
778
|
Chris@0
|
779 ar[2] = cm2;
|
Chris@0
|
780 ar[1] = cm1;
|
Chris@0
|
781
|
Chris@0
|
782 ai[2] = sm2;
|
Chris@0
|
783 ai[1] = sm1;
|
Chris@0
|
784
|
Chris@0
|
785 for (j = i, m = 0; m < blockEnd; j++, m++) {
|
Chris@0
|
786
|
Chris@0
|
787 ar[0] = w * ar[1] - ar[2];
|
Chris@0
|
788 ar[2] = ar[1];
|
Chris@0
|
789 ar[1] = ar[0];
|
Chris@0
|
790
|
Chris@0
|
791 ai[0] = w * ai[1] - ai[2];
|
Chris@0
|
792 ai[2] = ai[1];
|
Chris@0
|
793 ai[1] = ai[0];
|
Chris@0
|
794
|
Chris@0
|
795 k = j + blockEnd;
|
Chris@0
|
796 tr = ar[0] * ro[k] - ai[0] * io[k];
|
Chris@0
|
797 ti = ar[0] * io[k] + ai[0] * ro[k];
|
Chris@0
|
798
|
Chris@0
|
799 ro[k] = ro[j] - tr;
|
Chris@0
|
800 io[k] = io[j] - ti;
|
Chris@0
|
801
|
Chris@0
|
802 ro[j] += tr;
|
Chris@0
|
803 io[j] += ti;
|
Chris@0
|
804 }
|
Chris@0
|
805 }
|
Chris@0
|
806
|
Chris@0
|
807 blockEnd = blockSize;
|
Chris@0
|
808 }
|
Chris@0
|
809 }
|
Chris@0
|
810
|
Chris@0
|
811
|