annotate data/model/FFTModel.cpp @ 1784:4eac4bf35b45

More graceful handling of failure to construct FFT models in the case where the source model has already been deleted before this occurs
author Chris Cannam
date Tue, 17 Sep 2019 11:21:33 +0100
parents 6d6740b075c3
children dd51797e528e
rev   line source
Chris@152 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@152 2
Chris@152 3 /*
Chris@152 4 Sonic Visualiser
Chris@152 5 An audio file viewer and annotation editor.
Chris@152 6 Centre for Digital Music, Queen Mary, University of London.
Chris@152 7 This file copyright 2006 Chris Cannam.
Chris@152 8
Chris@152 9 This program is free software; you can redistribute it and/or
Chris@152 10 modify it under the terms of the GNU General Public License as
Chris@152 11 published by the Free Software Foundation; either version 2 of the
Chris@152 12 License, or (at your option) any later version. See the file
Chris@152 13 COPYING included with this distribution for more information.
Chris@152 14 */
Chris@152 15
Chris@152 16 #include "FFTModel.h"
Chris@152 17 #include "DenseTimeValueModel.h"
Chris@152 18
Chris@183 19 #include "base/Profiler.h"
Chris@275 20 #include "base/Pitch.h"
Chris@1256 21 #include "base/HitCount.h"
Chris@1428 22 #include "base/Debug.h"
Chris@1573 23 #include "base/MovingMedian.h"
Chris@183 24
Chris@402 25 #include <algorithm>
Chris@402 26
Chris@152 27 #include <cassert>
Chris@1090 28 #include <deque>
Chris@152 29
Chris@1090 30 using namespace std;
Chris@1090 31
Chris@1256 32 static HitCount inSmallCache("FFTModel: Small FFT cache");
Chris@1256 33 static HitCount inSourceCache("FFTModel: Source data cache");
Chris@1256 34
Chris@1744 35 FFTModel::FFTModel(ModelId modelId,
Chris@152 36 int channel,
Chris@152 37 WindowType windowType,
Chris@929 38 int windowSize,
Chris@929 39 int windowIncrement,
Chris@1090 40 int fftSize) :
Chris@1744 41 m_model(modelId),
Chris@1780 42 m_sampleRate(0),
Chris@1090 43 m_channel(channel),
Chris@1090 44 m_windowType(windowType),
Chris@1090 45 m_windowSize(windowSize),
Chris@1090 46 m_windowIncrement(windowIncrement),
Chris@1090 47 m_fftSize(fftSize),
Chris@1091 48 m_windower(windowType, windowSize),
Chris@1093 49 m_fft(fftSize),
Chris@1780 50 m_maximumFrequency(0.0),
Chris@1371 51 m_cacheWriteIndex(0),
Chris@1093 52 m_cacheSize(3)
Chris@152 53 {
Chris@1371 54 while (m_cached.size() < m_cacheSize) {
Chris@1371 55 m_cached.push_back({ -1, cvec(m_fftSize / 2 + 1) });
Chris@1371 56 }
Chris@1371 57
Chris@1091 58 if (m_windowSize > m_fftSize) {
Chris@1428 59 SVCERR << "ERROR: FFTModel::FFTModel: window size (" << m_windowSize
Chris@1680 60 << ") may not exceed FFT size (" << m_fftSize << ")" << endl;
Chris@1680 61 throw invalid_argument("FFTModel window size may not exceed FFT size");
Chris@1091 62 }
Chris@1133 63
Chris@1270 64 m_fft.initFloat();
Chris@1270 65
Chris@1744 66 auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
Chris@1744 67 if (model) {
Chris@1780 68 m_sampleRate = model->getSampleRate();
Chris@1780 69
Chris@1752 70 connect(model.get(), SIGNAL(modelChanged(ModelId)),
Chris@1752 71 this, SIGNAL(modelChanged(ModelId)));
Chris@1752 72 connect(model.get(), SIGNAL(modelChangedWithin(ModelId, sv_frame_t, sv_frame_t)),
Chris@1752 73 this, SIGNAL(modelChangedWithin(ModelId, sv_frame_t, sv_frame_t)));
Chris@1784 74 } else {
Chris@1784 75 m_error = QString("Model #%1 is not available").arg(m_model.untyped);
Chris@1744 76 }
Chris@152 77 }
Chris@152 78
Chris@152 79 FFTModel::~FFTModel()
Chris@152 80 {
Chris@152 81 }
Chris@152 82
Chris@1744 83 bool
Chris@1744 84 FFTModel::isOK() const
Chris@360 85 {
Chris@1744 86 auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
Chris@1784 87 if (!model) {
Chris@1784 88 m_error = QString("Model #%1 is not available").arg(m_model.untyped);
Chris@1784 89 return false;
Chris@1784 90 }
Chris@1784 91 if (!model->isOK()) {
Chris@1784 92 m_error = QString("Model #%1 is not OK").arg(m_model.untyped);
Chris@1784 93 return false;
Chris@1784 94 }
Chris@1784 95 return true;
Chris@1744 96 }
Chris@1744 97
Chris@1744 98 int
Chris@1744 99 FFTModel::getCompletion() const
Chris@1744 100 {
Chris@1744 101 int c = 100;
Chris@1744 102 auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
Chris@1744 103 if (model) {
Chris@1744 104 if (model->isReady(&c)) return 100;
Chris@360 105 }
Chris@1744 106 return c;
Chris@1744 107 }
Chris@1744 108
Chris@1744 109 sv_samplerate_t
Chris@1744 110 FFTModel::getSampleRate() const
Chris@1744 111 {
Chris@1780 112 return m_sampleRate;
Chris@1780 113 }
Chris@1780 114
Chris@1780 115 void
Chris@1780 116 FFTModel::setMaximumFrequency(double freq)
Chris@1780 117 {
Chris@1780 118 m_maximumFrequency = freq;
Chris@360 119 }
Chris@360 120
Chris@1091 121 int
Chris@1091 122 FFTModel::getWidth() const
Chris@1091 123 {
Chris@1744 124 auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
Chris@1744 125 if (!model) return 0;
Chris@1744 126 return int((model->getEndFrame() - model->getStartFrame())
Chris@1091 127 / m_windowIncrement) + 1;
Chris@1091 128 }
Chris@1091 129
Chris@1091 130 int
Chris@1091 131 FFTModel::getHeight() const
Chris@1091 132 {
Chris@1780 133 int height = m_fftSize / 2 + 1;
Chris@1780 134 if (m_maximumFrequency != 0.0) {
Chris@1780 135 int maxBin = int(ceil(m_maximumFrequency * m_fftSize) / m_sampleRate);
Chris@1780 136 if (maxBin >= 0 && maxBin < height) {
Chris@1780 137 return maxBin + 1;
Chris@1780 138 }
Chris@1780 139 }
Chris@1780 140 return height;
Chris@1091 141 }
Chris@1091 142
Chris@152 143 QString
Chris@929 144 FFTModel::getBinName(int n) const
Chris@152 145 {
Chris@1040 146 sv_samplerate_t sr = getSampleRate();
Chris@152 147 if (!sr) return "";
Chris@1780 148 QString name = tr("%1 Hz").arg((double(n) * sr) / m_fftSize);
Chris@152 149 return name;
Chris@152 150 }
Chris@152 151
Chris@1091 152 FFTModel::Column
Chris@1091 153 FFTModel::getColumn(int x) const
Chris@1091 154 {
Chris@1091 155 auto cplx = getFFTColumn(x);
Chris@1091 156 Column col;
Chris@1154 157 col.reserve(cplx.size());
Chris@1091 158 for (auto c: cplx) col.push_back(abs(c));
Chris@1319 159 return col;
Chris@1091 160 }
Chris@1091 161
Chris@1200 162 FFTModel::Column
Chris@1200 163 FFTModel::getPhases(int x) const
Chris@1200 164 {
Chris@1200 165 auto cplx = getFFTColumn(x);
Chris@1200 166 Column col;
Chris@1200 167 col.reserve(cplx.size());
Chris@1201 168 for (auto c: cplx) {
Chris@1201 169 col.push_back(arg(c));
Chris@1201 170 }
Chris@1319 171 return col;
Chris@1200 172 }
Chris@1200 173
Chris@1091 174 float
Chris@1091 175 FFTModel::getMagnitudeAt(int x, int y) const
Chris@1091 176 {
Chris@1569 177 if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) {
Chris@1569 178 return 0.f;
Chris@1569 179 }
Chris@1093 180 auto col = getFFTColumn(x);
Chris@1093 181 return abs(col[y]);
Chris@1091 182 }
Chris@1091 183
Chris@1091 184 float
Chris@1091 185 FFTModel::getMaximumMagnitudeAt(int x) const
Chris@1091 186 {
Chris@1091 187 Column col(getColumn(x));
Chris@1092 188 float max = 0.f;
Chris@1154 189 int n = int(col.size());
Chris@1154 190 for (int i = 0; i < n; ++i) {
Chris@1092 191 if (col[i] > max) max = col[i];
Chris@1092 192 }
Chris@1092 193 return max;
Chris@1091 194 }
Chris@1091 195
Chris@1091 196 float
Chris@1091 197 FFTModel::getPhaseAt(int x, int y) const
Chris@1091 198 {
Chris@1093 199 if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) return 0.f;
Chris@1091 200 return arg(getFFTColumn(x)[y]);
Chris@1091 201 }
Chris@1091 202
Chris@1091 203 void
Chris@1091 204 FFTModel::getValuesAt(int x, int y, float &re, float &im) const
Chris@1091 205 {
Chris@1780 206 if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) {
Chris@1780 207 re = 0.f;
Chris@1780 208 im = 0.f;
Chris@1780 209 return;
Chris@1780 210 }
Chris@1091 211 auto col = getFFTColumn(x);
Chris@1091 212 re = col[y].real();
Chris@1091 213 im = col[y].imag();
Chris@1091 214 }
Chris@1091 215
Chris@1091 216 bool
Chris@1091 217 FFTModel::getMagnitudesAt(int x, float *values, int minbin, int count) const
Chris@1091 218 {
Chris@1091 219 if (count == 0) count = getHeight();
Chris@1091 220 auto col = getFFTColumn(x);
Chris@1091 221 for (int i = 0; i < count; ++i) {
Chris@1091 222 values[i] = abs(col[minbin + i]);
Chris@1091 223 }
Chris@1091 224 return true;
Chris@1091 225 }
Chris@1091 226
Chris@1091 227 bool
Chris@1091 228 FFTModel::getPhasesAt(int x, float *values, int minbin, int count) const
Chris@1091 229 {
Chris@1091 230 if (count == 0) count = getHeight();
Chris@1091 231 auto col = getFFTColumn(x);
Chris@1091 232 for (int i = 0; i < count; ++i) {
Chris@1091 233 values[i] = arg(col[minbin + i]);
Chris@1091 234 }
Chris@1091 235 return true;
Chris@1091 236 }
Chris@1091 237
Chris@1091 238 bool
Chris@1091 239 FFTModel::getValuesAt(int x, float *reals, float *imags, int minbin, int count) const
Chris@1091 240 {
Chris@1091 241 if (count == 0) count = getHeight();
Chris@1091 242 auto col = getFFTColumn(x);
Chris@1091 243 for (int i = 0; i < count; ++i) {
Chris@1091 244 reals[i] = col[minbin + i].real();
Chris@1091 245 }
Chris@1091 246 for (int i = 0; i < count; ++i) {
Chris@1091 247 imags[i] = col[minbin + i].imag();
Chris@1091 248 }
Chris@1091 249 return true;
Chris@1091 250 }
Chris@1091 251
Chris@1326 252 FFTModel::fvec
Chris@1091 253 FFTModel::getSourceSamples(int column) const
Chris@1091 254 {
Chris@1094 255 // m_fftSize may be greater than m_windowSize, but not the reverse
Chris@1094 256
Chris@1094 257 // cerr << "getSourceSamples(" << column << ")" << endl;
Chris@1094 258
Chris@1091 259 auto range = getSourceSampleRange(column);
Chris@1094 260 auto data = getSourceData(range);
Chris@1094 261
Chris@1091 262 int off = (m_fftSize - m_windowSize) / 2;
Chris@1094 263
Chris@1094 264 if (off == 0) {
Chris@1094 265 return data;
Chris@1094 266 } else {
Chris@1094 267 vector<float> pad(off, 0.f);
Chris@1326 268 fvec padded;
Chris@1094 269 padded.reserve(m_fftSize);
Chris@1094 270 padded.insert(padded.end(), pad.begin(), pad.end());
Chris@1094 271 padded.insert(padded.end(), data.begin(), data.end());
Chris@1094 272 padded.insert(padded.end(), pad.begin(), pad.end());
Chris@1094 273 return padded;
Chris@1094 274 }
Chris@1094 275 }
Chris@1094 276
Chris@1326 277 FFTModel::fvec
Chris@1094 278 FFTModel::getSourceData(pair<sv_frame_t, sv_frame_t> range) const
Chris@1094 279 {
Chris@1094 280 // cerr << "getSourceData(" << range.first << "," << range.second
Chris@1094 281 // << "): saved range is (" << m_savedData.range.first
Chris@1094 282 // << "," << m_savedData.range.second << ")" << endl;
Chris@1094 283
Chris@1100 284 if (m_savedData.range == range) {
Chris@1256 285 inSourceCache.hit();
Chris@1100 286 return m_savedData.data;
Chris@1100 287 }
Chris@1094 288
Chris@1270 289 Profiler profiler("FFTModel::getSourceData (cache miss)");
Chris@1270 290
Chris@1094 291 if (range.first < m_savedData.range.second &&
Chris@1094 292 range.first >= m_savedData.range.first &&
Chris@1094 293 range.second > m_savedData.range.second) {
Chris@1094 294
Chris@1256 295 inSourceCache.partial();
Chris@1256 296
Chris@1100 297 sv_frame_t discard = range.first - m_savedData.range.first;
Chris@1100 298
Chris@1457 299 fvec data;
Chris@1457 300 data.reserve(range.second - range.first);
Chris@1094 301
Chris@1457 302 data.insert(data.end(),
Chris@1457 303 m_savedData.data.begin() + discard,
Chris@1457 304 m_savedData.data.end());
Chris@1100 305
Chris@1457 306 fvec rest = getSourceDataUncached
Chris@1457 307 ({ m_savedData.range.second, range.second });
Chris@1457 308
Chris@1457 309 data.insert(data.end(), rest.begin(), rest.end());
Chris@1094 310
Chris@1457 311 m_savedData = { range, data };
Chris@1457 312 return data;
Chris@1095 313
Chris@1095 314 } else {
Chris@1095 315
Chris@1256 316 inSourceCache.miss();
Chris@1256 317
Chris@1095 318 auto data = getSourceDataUncached(range);
Chris@1095 319 m_savedData = { range, data };
Chris@1095 320 return data;
Chris@1094 321 }
Chris@1095 322 }
Chris@1094 323
Chris@1326 324 FFTModel::fvec
Chris@1095 325 FFTModel::getSourceDataUncached(pair<sv_frame_t, sv_frame_t> range) const
Chris@1095 326 {
Chris@1457 327 Profiler profiler("FFTModel::getSourceDataUncached");
Chris@1688 328
Chris@1744 329 auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
Chris@1744 330 if (!model) return {};
Chris@1457 331
Chris@1091 332 decltype(range.first) pfx = 0;
Chris@1091 333 if (range.first < 0) {
Chris@1091 334 pfx = -range.first;
Chris@1091 335 range = { 0, range.second };
Chris@1091 336 }
Chris@1096 337
Chris@1744 338 auto data = model->getData(m_channel,
Chris@1744 339 range.first,
Chris@1744 340 range.second - range.first);
Chris@1773 341 /*
Chris@1281 342 if (data.empty()) {
Chris@1281 343 SVDEBUG << "NOTE: empty source data for range (" << range.first << ","
Chris@1281 344 << range.second << ") (model end frame "
Chris@1744 345 << model->getEndFrame() << ")" << endl;
Chris@1281 346 }
Chris@1773 347 */
Chris@1281 348
Chris@1096 349 // don't return a partial frame
Chris@1096 350 data.resize(range.second - range.first, 0.f);
Chris@1096 351
Chris@1096 352 if (pfx > 0) {
Chris@1096 353 vector<float> pad(pfx, 0.f);
Chris@1096 354 data.insert(data.begin(), pad.begin(), pad.end());
Chris@1096 355 }
Chris@1096 356
Chris@1091 357 if (m_channel == -1) {
Chris@1744 358 int channels = model->getChannelCount();
Chris@1429 359 if (channels > 1) {
Chris@1096 360 int n = int(data.size());
Chris@1096 361 float factor = 1.f / float(channels);
Chris@1100 362 // use mean instead of sum for fft model input
Chris@1429 363 for (int i = 0; i < n; ++i) {
Chris@1429 364 data[i] *= factor;
Chris@1429 365 }
Chris@1429 366 }
Chris@1091 367 }
Chris@1094 368
Chris@1094 369 return data;
Chris@1091 370 }
Chris@1091 371
Chris@1780 372 FFTModel::cvec
Chris@1093 373 FFTModel::getFFTColumn(int n) const
Chris@1091 374 {
Chris@1780 375 int h = getHeight();
Chris@1780 376 bool truncate = (h < m_fftSize / 2 + 1);
Chris@1780 377
Chris@1258 378 // The small cache (i.e. the m_cached deque) is for cases where
Chris@1258 379 // values are looked up individually, and for e.g. peak-frequency
Chris@1258 380 // spectrograms where values from two consecutive columns are
Chris@1257 381 // needed at once. This cache gets essentially no hits when
Chris@1258 382 // scrolling through a magnitude spectrogram, but 95%+ hits with a
Chris@1569 383 // peak-frequency spectrogram or spectrum.
Chris@1257 384 for (const auto &incache : m_cached) {
Chris@1093 385 if (incache.n == n) {
Chris@1256 386 inSmallCache.hit();
Chris@1780 387 if (!truncate) {
Chris@1780 388 return incache.col;
Chris@1780 389 } else {
Chris@1780 390 return cvec(incache.col.begin(), incache.col.begin() + h);
Chris@1780 391 }
Chris@1093 392 }
Chris@1093 393 }
Chris@1256 394 inSmallCache.miss();
Chris@1258 395
Chris@1258 396 Profiler profiler("FFTModel::getFFTColumn (cache miss)");
Chris@1093 397
Chris@1093 398 auto samples = getSourceSamples(n);
Chris@1567 399 m_windower.cut(samples.data() + (m_fftSize - m_windowSize) / 2);
Chris@1270 400 breakfastquay::v_fftshift(samples.data(), m_fftSize);
Chris@1270 401
Chris@1371 402 cvec &col = m_cached[m_cacheWriteIndex].col;
Chris@1270 403
Chris@1270 404 m_fft.forwardInterleaved(samples.data(),
Chris@1270 405 reinterpret_cast<float *>(col.data()));
Chris@1093 406
Chris@1371 407 m_cached[m_cacheWriteIndex].n = n;
Chris@1371 408
Chris@1371 409 m_cacheWriteIndex = (m_cacheWriteIndex + 1) % m_cacheSize;
Chris@1093 410
Chris@1780 411 if (!truncate) {
Chris@1780 412 return col;
Chris@1780 413 } else {
Chris@1780 414 return cvec(col.begin(), col.begin() + h);
Chris@1780 415 }
Chris@1091 416 }
Chris@1091 417
Chris@275 418 bool
Chris@1045 419 FFTModel::estimateStableFrequency(int x, int y, double &frequency)
Chris@275 420 {
Chris@275 421 if (!isOK()) return false;
Chris@275 422
Chris@1090 423 frequency = double(y * getSampleRate()) / m_fftSize;
Chris@275 424
Chris@275 425 if (x+1 >= getWidth()) return false;
Chris@275 426
Chris@275 427 // At frequency f, a phase shift of 2pi (one cycle) happens in 1/f sec.
Chris@275 428 // At hopsize h and sample rate sr, one hop happens in h/sr sec.
Chris@275 429 // At window size w, for bin b, f is b*sr/w.
Chris@275 430 // thus 2pi phase shift happens in w/(b*sr) sec.
Chris@275 431 // We need to know what phase shift we expect from h/sr sec.
Chris@275 432 // -> 2pi * ((h/sr) / (w/(b*sr)))
Chris@275 433 // = 2pi * ((h * b * sr) / (w * sr))
Chris@275 434 // = 2pi * (h * b) / w.
Chris@275 435
Chris@1038 436 double oldPhase = getPhaseAt(x, y);
Chris@1038 437 double newPhase = getPhaseAt(x+1, y);
Chris@275 438
Chris@929 439 int incr = getResolution();
Chris@275 440
Chris@1090 441 double expectedPhase = oldPhase + (2.0 * M_PI * y * incr) / m_fftSize;
Chris@275 442
Chris@1038 443 double phaseError = princarg(newPhase - expectedPhase);
Chris@275 444
Chris@275 445 // The new frequency estimate based on the phase error resulting
Chris@275 446 // from assuming the "native" frequency of this bin
Chris@275 447
Chris@275 448 frequency =
Chris@1090 449 (getSampleRate() * (expectedPhase + phaseError - oldPhase)) /
Chris@1045 450 (2.0 * M_PI * incr);
Chris@275 451
Chris@275 452 return true;
Chris@275 453 }
Chris@275 454
Chris@275 455 FFTModel::PeakLocationSet
Chris@1191 456 FFTModel::getPeaks(PeakPickType type, int x, int ymin, int ymax) const
Chris@275 457 {
Chris@551 458 Profiler profiler("FFTModel::getPeaks");
Chris@1575 459
Chris@275 460 FFTModel::PeakLocationSet peaks;
Chris@275 461 if (!isOK()) return peaks;
Chris@275 462
Chris@275 463 if (ymax == 0 || ymax > getHeight() - 1) {
Chris@275 464 ymax = getHeight() - 1;
Chris@275 465 }
Chris@275 466
Chris@275 467 if (type == AllPeaks) {
Chris@551 468 int minbin = ymin;
Chris@551 469 if (minbin > 0) minbin = minbin - 1;
Chris@551 470 int maxbin = ymax;
Chris@551 471 if (maxbin < getHeight() - 1) maxbin = maxbin + 1;
Chris@551 472 const int n = maxbin - minbin + 1;
Chris@1218 473 float *values = new float[n];
Chris@551 474 getMagnitudesAt(x, values, minbin, maxbin - minbin + 1);
Chris@929 475 for (int bin = ymin; bin <= ymax; ++bin) {
Chris@551 476 if (bin == minbin || bin == maxbin) continue;
Chris@551 477 if (values[bin - minbin] > values[bin - minbin - 1] &&
Chris@551 478 values[bin - minbin] > values[bin - minbin + 1]) {
Chris@275 479 peaks.insert(bin);
Chris@275 480 }
Chris@275 481 }
Chris@1218 482 delete[] values;
Chris@275 483 return peaks;
Chris@275 484 }
Chris@275 485
Chris@551 486 Column values = getColumn(x);
Chris@1154 487 int nv = int(values.size());
Chris@275 488
Chris@500 489 float mean = 0.f;
Chris@1154 490 for (int i = 0; i < nv; ++i) mean += values[i];
Chris@1154 491 if (nv > 0) mean = mean / float(values.size());
Chris@1038 492
Chris@275 493 // For peak picking we use a moving median window, picking the
Chris@275 494 // highest value within each continuous region of values that
Chris@275 495 // exceed the median. For pitch adaptivity, we adjust the window
Chris@275 496 // size to a roughly constant pitch range (about four tones).
Chris@275 497
Chris@1040 498 sv_samplerate_t sampleRate = getSampleRate();
Chris@275 499
Chris@1090 500 vector<int> inrange;
Chris@1576 501 double dist = 0.5;
Chris@500 502
Chris@929 503 int medianWinSize = getPeakPickWindowSize(type, sampleRate, ymin, dist);
Chris@929 504 int halfWin = medianWinSize/2;
Chris@275 505
Chris@1573 506 MovingMedian<float> window(medianWinSize);
Chris@1573 507
Chris@929 508 int binmin;
Chris@275 509 if (ymin > halfWin) binmin = ymin - halfWin;
Chris@275 510 else binmin = 0;
Chris@275 511
Chris@929 512 int binmax;
Chris@1154 513 if (ymax + halfWin < nv) binmax = ymax + halfWin;
Chris@1154 514 else binmax = nv - 1;
Chris@275 515
Chris@929 516 int prevcentre = 0;
Chris@500 517
Chris@929 518 for (int bin = binmin; bin <= binmax; ++bin) {
Chris@275 519
Chris@275 520 float value = values[bin];
Chris@275 521
Chris@280 522 // so-called median will actually be the dist*100'th percentile
Chris@280 523 medianWinSize = getPeakPickWindowSize(type, sampleRate, bin, dist);
Chris@275 524 halfWin = medianWinSize/2;
Chris@275 525
Chris@1573 526 int actualSize = std::min(medianWinSize, bin - binmin + 1);
Chris@1573 527 window.resize(actualSize);
Chris@1573 528 window.setPercentile(dist * 100.0);
Chris@1573 529 window.push(value);
Chris@275 530
Chris@275 531 if (type == MajorPitchAdaptivePeaks) {
Chris@1154 532 if (ymax + halfWin < nv) binmax = ymax + halfWin;
Chris@1154 533 else binmax = nv - 1;
Chris@275 534 }
Chris@275 535
Chris@1573 536 float median = window.get();
Chris@275 537
Chris@929 538 int centrebin = 0;
Chris@500 539 if (bin > actualSize/2) centrebin = bin - actualSize/2;
Chris@500 540
Chris@500 541 while (centrebin > prevcentre || bin == binmin) {
Chris@275 542
Chris@500 543 if (centrebin > prevcentre) ++prevcentre;
Chris@500 544
Chris@500 545 float centre = values[prevcentre];
Chris@500 546
Chris@500 547 if (centre > median) {
Chris@500 548 inrange.push_back(centrebin);
Chris@500 549 }
Chris@500 550
Chris@1154 551 if (centre <= median || centrebin+1 == nv) {
Chris@500 552 if (!inrange.empty()) {
Chris@929 553 int peakbin = 0;
Chris@500 554 float peakval = 0.f;
Chris@929 555 for (int i = 0; i < (int)inrange.size(); ++i) {
Chris@500 556 if (i == 0 || values[inrange[i]] > peakval) {
Chris@500 557 peakval = values[inrange[i]];
Chris@500 558 peakbin = inrange[i];
Chris@500 559 }
Chris@500 560 }
Chris@500 561 inrange.clear();
Chris@500 562 if (peakbin >= ymin && peakbin <= ymax) {
Chris@500 563 peaks.insert(peakbin);
Chris@275 564 }
Chris@275 565 }
Chris@275 566 }
Chris@500 567
Chris@500 568 if (bin == binmin) break;
Chris@275 569 }
Chris@275 570 }
Chris@275 571
Chris@275 572 return peaks;
Chris@275 573 }
Chris@275 574
Chris@929 575 int
Chris@1040 576 FFTModel::getPeakPickWindowSize(PeakPickType type, sv_samplerate_t sampleRate,
Chris@1576 577 int bin, double &dist) const
Chris@275 578 {
Chris@1576 579 dist = 0.5; // dist is percentile / 100.0
Chris@275 580 if (type == MajorPeaks) return 10;
Chris@275 581 if (bin == 0) return 3;
Chris@280 582
Chris@1091 583 double binfreq = (sampleRate * bin) / m_fftSize;
Chris@1038 584 double hifreq = Pitch::getFrequencyForPitch(73, 0, binfreq);
Chris@280 585
Chris@1091 586 int hibin = int(lrint((hifreq * m_fftSize) / sampleRate));
Chris@275 587 int medianWinSize = hibin - bin;
Chris@1576 588
Chris@1575 589 if (medianWinSize < 3) {
Chris@1575 590 medianWinSize = 3;
Chris@1575 591 }
Chris@1576 592
Chris@1576 593 // We want to avoid the median window size changing too often, as
Chris@1576 594 // it requires a reallocation. So snap to a nearby round number.
Chris@1576 595
Chris@1575 596 if (medianWinSize > 20) {
Chris@1575 597 medianWinSize = (1 + medianWinSize / 10) * 10;
Chris@1575 598 }
Chris@1576 599 if (medianWinSize > 200) {
Chris@1576 600 medianWinSize = (1 + medianWinSize / 100) * 100;
Chris@1576 601 }
Chris@1576 602 if (medianWinSize > 2000) {
Chris@1576 603 medianWinSize = (1 + medianWinSize / 1000) * 1000;
Chris@1576 604 }
Chris@1576 605 if (medianWinSize > 20000) {
Chris@1576 606 medianWinSize = 20000;
Chris@1575 607 }
Chris@280 608
Chris@1576 609 if (medianWinSize < 100) {
Chris@1576 610 dist = 1.0 - (4.0 / medianWinSize);
Chris@1576 611 } else {
Chris@1576 612 dist = 1.0 - (8.0 / medianWinSize);
Chris@1576 613 }
Chris@1576 614 if (dist < 0.5) dist = 0.5;
Chris@1575 615
Chris@275 616 return medianWinSize;
Chris@275 617 }
Chris@275 618
Chris@275 619 FFTModel::PeakSet
Chris@929 620 FFTModel::getPeakFrequencies(PeakPickType type, int x,
Chris@1191 621 int ymin, int ymax) const
Chris@275 622 {
Chris@551 623 Profiler profiler("FFTModel::getPeakFrequencies");
Chris@551 624
Chris@275 625 PeakSet peaks;
Chris@275 626 if (!isOK()) return peaks;
Chris@275 627 PeakLocationSet locations = getPeaks(type, x, ymin, ymax);
Chris@275 628
Chris@1040 629 sv_samplerate_t sampleRate = getSampleRate();
Chris@929 630 int incr = getResolution();
Chris@275 631
Chris@275 632 // This duplicates some of the work of estimateStableFrequency to
Chris@275 633 // allow us to retrieve the phases in two separate vertical
Chris@275 634 // columns, instead of jumping back and forth between columns x and
Chris@275 635 // x+1, which may be significantly slower if re-seeking is needed
Chris@275 636
Chris@1090 637 vector<float> phases;
Chris@275 638 for (PeakLocationSet::iterator i = locations.begin();
Chris@275 639 i != locations.end(); ++i) {
Chris@275 640 phases.push_back(getPhaseAt(x, *i));
Chris@275 641 }
Chris@275 642
Chris@929 643 int phaseIndex = 0;
Chris@275 644 for (PeakLocationSet::iterator i = locations.begin();
Chris@275 645 i != locations.end(); ++i) {
Chris@1038 646 double oldPhase = phases[phaseIndex];
Chris@1038 647 double newPhase = getPhaseAt(x+1, *i);
Chris@1090 648 double expectedPhase = oldPhase + (2.0 * M_PI * *i * incr) / m_fftSize;
Chris@1038 649 double phaseError = princarg(newPhase - expectedPhase);
Chris@1038 650 double frequency =
Chris@275 651 (sampleRate * (expectedPhase + phaseError - oldPhase))
Chris@275 652 / (2 * M_PI * incr);
Chris@1045 653 peaks[*i] = frequency;
Chris@275 654 ++phaseIndex;
Chris@275 655 }
Chris@275 656
Chris@275 657 return peaks;
Chris@275 658 }
Chris@275 659