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