annotate SimpleCepstrum.cpp @ 24:0a3c1ecff644

Add rather simplistic cubic interpolation for peak values in a cepstrum output (not yet for pitch tracker). Only gains us 1dp
author Chris Cannam
date Thu, 05 Jul 2012 20:50:56 +0100
parents 7786d595d2f2
children 44bb93cae288
rev   line source
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