annotate data/model/FFTModel.cpp @ 1833:21c792334c2e sensible-delimited-data-strings

Rewrite all the DelimitedDataString stuff so as to return vectors of individual cell strings rather than having the classes add the delimiters themselves. Rename accordingly to names based on StringExport. Take advantage of this in the CSV writer code so as to properly quote cells that contain delimiter characters.
author Chris Cannam
date Fri, 03 Apr 2020 17:11:05 +0100
parents dd51797e528e
children 1b688ab5f1b3
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@1780 109 void
Chris@1780 110 FFTModel::setMaximumFrequency(double freq)
Chris@1780 111 {
Chris@1780 112 m_maximumFrequency = freq;
Chris@360 113 }
Chris@360 114
Chris@1091 115 int
Chris@1091 116 FFTModel::getWidth() const
Chris@1091 117 {
Chris@1744 118 auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
Chris@1744 119 if (!model) return 0;
Chris@1744 120 return int((model->getEndFrame() - model->getStartFrame())
Chris@1091 121 / m_windowIncrement) + 1;
Chris@1091 122 }
Chris@1091 123
Chris@1091 124 int
Chris@1091 125 FFTModel::getHeight() const
Chris@1091 126 {
Chris@1780 127 int height = m_fftSize / 2 + 1;
Chris@1780 128 if (m_maximumFrequency != 0.0) {
Chris@1780 129 int maxBin = int(ceil(m_maximumFrequency * m_fftSize) / m_sampleRate);
Chris@1780 130 if (maxBin >= 0 && maxBin < height) {
Chris@1780 131 return maxBin + 1;
Chris@1780 132 }
Chris@1780 133 }
Chris@1780 134 return height;
Chris@1091 135 }
Chris@1091 136
Chris@152 137 QString
Chris@929 138 FFTModel::getBinName(int n) const
Chris@152 139 {
Chris@1790 140 return tr("%1 Hz").arg(getBinValue(n));
Chris@1790 141 }
Chris@1790 142
Chris@1790 143 float
Chris@1790 144 FFTModel::getBinValue(int n) const
Chris@1790 145 {
Chris@1790 146 return float((m_sampleRate * n) / m_fftSize);
Chris@152 147 }
Chris@152 148
Chris@1091 149 FFTModel::Column
Chris@1091 150 FFTModel::getColumn(int x) const
Chris@1091 151 {
Chris@1091 152 auto cplx = getFFTColumn(x);
Chris@1091 153 Column col;
Chris@1154 154 col.reserve(cplx.size());
Chris@1091 155 for (auto c: cplx) col.push_back(abs(c));
Chris@1319 156 return col;
Chris@1091 157 }
Chris@1091 158
Chris@1200 159 FFTModel::Column
Chris@1200 160 FFTModel::getPhases(int x) const
Chris@1200 161 {
Chris@1200 162 auto cplx = getFFTColumn(x);
Chris@1200 163 Column col;
Chris@1200 164 col.reserve(cplx.size());
Chris@1201 165 for (auto c: cplx) {
Chris@1201 166 col.push_back(arg(c));
Chris@1201 167 }
Chris@1319 168 return col;
Chris@1200 169 }
Chris@1200 170
Chris@1091 171 float
Chris@1091 172 FFTModel::getMagnitudeAt(int x, int y) const
Chris@1091 173 {
Chris@1569 174 if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) {
Chris@1569 175 return 0.f;
Chris@1569 176 }
Chris@1093 177 auto col = getFFTColumn(x);
Chris@1093 178 return abs(col[y]);
Chris@1091 179 }
Chris@1091 180
Chris@1091 181 float
Chris@1091 182 FFTModel::getMaximumMagnitudeAt(int x) const
Chris@1091 183 {
Chris@1091 184 Column col(getColumn(x));
Chris@1092 185 float max = 0.f;
Chris@1154 186 int n = int(col.size());
Chris@1154 187 for (int i = 0; i < n; ++i) {
Chris@1092 188 if (col[i] > max) max = col[i];
Chris@1092 189 }
Chris@1092 190 return max;
Chris@1091 191 }
Chris@1091 192
Chris@1091 193 float
Chris@1091 194 FFTModel::getPhaseAt(int x, int y) const
Chris@1091 195 {
Chris@1093 196 if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) return 0.f;
Chris@1091 197 return arg(getFFTColumn(x)[y]);
Chris@1091 198 }
Chris@1091 199
Chris@1091 200 void
Chris@1091 201 FFTModel::getValuesAt(int x, int y, float &re, float &im) const
Chris@1091 202 {
Chris@1780 203 if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) {
Chris@1780 204 re = 0.f;
Chris@1780 205 im = 0.f;
Chris@1780 206 return;
Chris@1780 207 }
Chris@1091 208 auto col = getFFTColumn(x);
Chris@1091 209 re = col[y].real();
Chris@1091 210 im = col[y].imag();
Chris@1091 211 }
Chris@1091 212
Chris@1091 213 bool
Chris@1091 214 FFTModel::getMagnitudesAt(int x, float *values, int minbin, int count) const
Chris@1091 215 {
Chris@1091 216 if (count == 0) count = getHeight();
Chris@1091 217 auto col = getFFTColumn(x);
Chris@1091 218 for (int i = 0; i < count; ++i) {
Chris@1091 219 values[i] = abs(col[minbin + i]);
Chris@1091 220 }
Chris@1091 221 return true;
Chris@1091 222 }
Chris@1091 223
Chris@1091 224 bool
Chris@1091 225 FFTModel::getPhasesAt(int x, float *values, int minbin, int count) const
Chris@1091 226 {
Chris@1091 227 if (count == 0) count = getHeight();
Chris@1091 228 auto col = getFFTColumn(x);
Chris@1091 229 for (int i = 0; i < count; ++i) {
Chris@1091 230 values[i] = arg(col[minbin + i]);
Chris@1091 231 }
Chris@1091 232 return true;
Chris@1091 233 }
Chris@1091 234
Chris@1091 235 bool
Chris@1091 236 FFTModel::getValuesAt(int x, float *reals, float *imags, int minbin, int count) const
Chris@1091 237 {
Chris@1091 238 if (count == 0) count = getHeight();
Chris@1091 239 auto col = getFFTColumn(x);
Chris@1091 240 for (int i = 0; i < count; ++i) {
Chris@1091 241 reals[i] = col[minbin + i].real();
Chris@1091 242 }
Chris@1091 243 for (int i = 0; i < count; ++i) {
Chris@1091 244 imags[i] = col[minbin + i].imag();
Chris@1091 245 }
Chris@1091 246 return true;
Chris@1091 247 }
Chris@1091 248
Chris@1326 249 FFTModel::fvec
Chris@1091 250 FFTModel::getSourceSamples(int column) const
Chris@1091 251 {
Chris@1094 252 // m_fftSize may be greater than m_windowSize, but not the reverse
Chris@1094 253
Chris@1094 254 // cerr << "getSourceSamples(" << column << ")" << endl;
Chris@1094 255
Chris@1091 256 auto range = getSourceSampleRange(column);
Chris@1094 257 auto data = getSourceData(range);
Chris@1094 258
Chris@1091 259 int off = (m_fftSize - m_windowSize) / 2;
Chris@1094 260
Chris@1094 261 if (off == 0) {
Chris@1094 262 return data;
Chris@1094 263 } else {
Chris@1094 264 vector<float> pad(off, 0.f);
Chris@1326 265 fvec padded;
Chris@1094 266 padded.reserve(m_fftSize);
Chris@1094 267 padded.insert(padded.end(), pad.begin(), pad.end());
Chris@1094 268 padded.insert(padded.end(), data.begin(), data.end());
Chris@1094 269 padded.insert(padded.end(), pad.begin(), pad.end());
Chris@1094 270 return padded;
Chris@1094 271 }
Chris@1094 272 }
Chris@1094 273
Chris@1326 274 FFTModel::fvec
Chris@1094 275 FFTModel::getSourceData(pair<sv_frame_t, sv_frame_t> range) const
Chris@1094 276 {
Chris@1094 277 // cerr << "getSourceData(" << range.first << "," << range.second
Chris@1094 278 // << "): saved range is (" << m_savedData.range.first
Chris@1094 279 // << "," << m_savedData.range.second << ")" << endl;
Chris@1094 280
Chris@1100 281 if (m_savedData.range == range) {
Chris@1256 282 inSourceCache.hit();
Chris@1100 283 return m_savedData.data;
Chris@1100 284 }
Chris@1094 285
Chris@1270 286 Profiler profiler("FFTModel::getSourceData (cache miss)");
Chris@1270 287
Chris@1094 288 if (range.first < m_savedData.range.second &&
Chris@1094 289 range.first >= m_savedData.range.first &&
Chris@1094 290 range.second > m_savedData.range.second) {
Chris@1094 291
Chris@1256 292 inSourceCache.partial();
Chris@1256 293
Chris@1100 294 sv_frame_t discard = range.first - m_savedData.range.first;
Chris@1100 295
Chris@1457 296 fvec data;
Chris@1457 297 data.reserve(range.second - range.first);
Chris@1094 298
Chris@1457 299 data.insert(data.end(),
Chris@1457 300 m_savedData.data.begin() + discard,
Chris@1457 301 m_savedData.data.end());
Chris@1100 302
Chris@1457 303 fvec rest = getSourceDataUncached
Chris@1457 304 ({ m_savedData.range.second, range.second });
Chris@1457 305
Chris@1457 306 data.insert(data.end(), rest.begin(), rest.end());
Chris@1094 307
Chris@1457 308 m_savedData = { range, data };
Chris@1457 309 return data;
Chris@1095 310
Chris@1095 311 } else {
Chris@1095 312
Chris@1256 313 inSourceCache.miss();
Chris@1256 314
Chris@1095 315 auto data = getSourceDataUncached(range);
Chris@1095 316 m_savedData = { range, data };
Chris@1095 317 return data;
Chris@1094 318 }
Chris@1095 319 }
Chris@1094 320
Chris@1326 321 FFTModel::fvec
Chris@1095 322 FFTModel::getSourceDataUncached(pair<sv_frame_t, sv_frame_t> range) const
Chris@1095 323 {
Chris@1457 324 Profiler profiler("FFTModel::getSourceDataUncached");
Chris@1688 325
Chris@1744 326 auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
Chris@1744 327 if (!model) return {};
Chris@1457 328
Chris@1091 329 decltype(range.first) pfx = 0;
Chris@1091 330 if (range.first < 0) {
Chris@1091 331 pfx = -range.first;
Chris@1091 332 range = { 0, range.second };
Chris@1091 333 }
Chris@1096 334
Chris@1744 335 auto data = model->getData(m_channel,
Chris@1744 336 range.first,
Chris@1744 337 range.second - range.first);
Chris@1773 338 /*
Chris@1281 339 if (data.empty()) {
Chris@1281 340 SVDEBUG << "NOTE: empty source data for range (" << range.first << ","
Chris@1281 341 << range.second << ") (model end frame "
Chris@1744 342 << model->getEndFrame() << ")" << endl;
Chris@1281 343 }
Chris@1773 344 */
Chris@1281 345
Chris@1096 346 // don't return a partial frame
Chris@1096 347 data.resize(range.second - range.first, 0.f);
Chris@1096 348
Chris@1096 349 if (pfx > 0) {
Chris@1096 350 vector<float> pad(pfx, 0.f);
Chris@1096 351 data.insert(data.begin(), pad.begin(), pad.end());
Chris@1096 352 }
Chris@1096 353
Chris@1091 354 if (m_channel == -1) {
Chris@1744 355 int channels = model->getChannelCount();
Chris@1429 356 if (channels > 1) {
Chris@1096 357 int n = int(data.size());
Chris@1096 358 float factor = 1.f / float(channels);
Chris@1100 359 // use mean instead of sum for fft model input
Chris@1429 360 for (int i = 0; i < n; ++i) {
Chris@1429 361 data[i] *= factor;
Chris@1429 362 }
Chris@1429 363 }
Chris@1091 364 }
Chris@1094 365
Chris@1094 366 return data;
Chris@1091 367 }
Chris@1091 368
Chris@1780 369 FFTModel::cvec
Chris@1093 370 FFTModel::getFFTColumn(int n) const
Chris@1091 371 {
Chris@1780 372 int h = getHeight();
Chris@1780 373 bool truncate = (h < m_fftSize / 2 + 1);
Chris@1780 374
Chris@1258 375 // The small cache (i.e. the m_cached deque) is for cases where
Chris@1258 376 // values are looked up individually, and for e.g. peak-frequency
Chris@1258 377 // spectrograms where values from two consecutive columns are
Chris@1257 378 // needed at once. This cache gets essentially no hits when
Chris@1258 379 // scrolling through a magnitude spectrogram, but 95%+ hits with a
Chris@1569 380 // peak-frequency spectrogram or spectrum.
Chris@1257 381 for (const auto &incache : m_cached) {
Chris@1093 382 if (incache.n == n) {
Chris@1256 383 inSmallCache.hit();
Chris@1780 384 if (!truncate) {
Chris@1780 385 return incache.col;
Chris@1780 386 } else {
Chris@1780 387 return cvec(incache.col.begin(), incache.col.begin() + h);
Chris@1780 388 }
Chris@1093 389 }
Chris@1093 390 }
Chris@1256 391 inSmallCache.miss();
Chris@1258 392
Chris@1258 393 Profiler profiler("FFTModel::getFFTColumn (cache miss)");
Chris@1093 394
Chris@1093 395 auto samples = getSourceSamples(n);
Chris@1567 396 m_windower.cut(samples.data() + (m_fftSize - m_windowSize) / 2);
Chris@1270 397 breakfastquay::v_fftshift(samples.data(), m_fftSize);
Chris@1270 398
Chris@1371 399 cvec &col = m_cached[m_cacheWriteIndex].col;
Chris@1270 400
Chris@1270 401 m_fft.forwardInterleaved(samples.data(),
Chris@1270 402 reinterpret_cast<float *>(col.data()));
Chris@1093 403
Chris@1371 404 m_cached[m_cacheWriteIndex].n = n;
Chris@1371 405
Chris@1371 406 m_cacheWriteIndex = (m_cacheWriteIndex + 1) % m_cacheSize;
Chris@1093 407
Chris@1780 408 if (!truncate) {
Chris@1780 409 return col;
Chris@1780 410 } else {
Chris@1780 411 return cvec(col.begin(), col.begin() + h);
Chris@1780 412 }
Chris@1091 413 }
Chris@1091 414
Chris@275 415 bool
Chris@1045 416 FFTModel::estimateStableFrequency(int x, int y, double &frequency)
Chris@275 417 {
Chris@275 418 if (!isOK()) return false;
Chris@275 419
Chris@1090 420 frequency = double(y * getSampleRate()) / m_fftSize;
Chris@275 421
Chris@275 422 if (x+1 >= getWidth()) return false;
Chris@275 423
Chris@275 424 // At frequency f, a phase shift of 2pi (one cycle) happens in 1/f sec.
Chris@275 425 // At hopsize h and sample rate sr, one hop happens in h/sr sec.
Chris@275 426 // At window size w, for bin b, f is b*sr/w.
Chris@275 427 // thus 2pi phase shift happens in w/(b*sr) sec.
Chris@275 428 // We need to know what phase shift we expect from h/sr sec.
Chris@275 429 // -> 2pi * ((h/sr) / (w/(b*sr)))
Chris@275 430 // = 2pi * ((h * b * sr) / (w * sr))
Chris@275 431 // = 2pi * (h * b) / w.
Chris@275 432
Chris@1038 433 double oldPhase = getPhaseAt(x, y);
Chris@1038 434 double newPhase = getPhaseAt(x+1, y);
Chris@275 435
Chris@929 436 int incr = getResolution();
Chris@275 437
Chris@1090 438 double expectedPhase = oldPhase + (2.0 * M_PI * y * incr) / m_fftSize;
Chris@275 439
Chris@1038 440 double phaseError = princarg(newPhase - expectedPhase);
Chris@275 441
Chris@275 442 // The new frequency estimate based on the phase error resulting
Chris@275 443 // from assuming the "native" frequency of this bin
Chris@275 444
Chris@275 445 frequency =
Chris@1090 446 (getSampleRate() * (expectedPhase + phaseError - oldPhase)) /
Chris@1045 447 (2.0 * M_PI * incr);
Chris@275 448
Chris@275 449 return true;
Chris@275 450 }
Chris@275 451
Chris@275 452 FFTModel::PeakLocationSet
Chris@1191 453 FFTModel::getPeaks(PeakPickType type, int x, int ymin, int ymax) const
Chris@275 454 {
Chris@551 455 Profiler profiler("FFTModel::getPeaks");
Chris@1575 456
Chris@275 457 FFTModel::PeakLocationSet peaks;
Chris@275 458 if (!isOK()) return peaks;
Chris@275 459
Chris@275 460 if (ymax == 0 || ymax > getHeight() - 1) {
Chris@275 461 ymax = getHeight() - 1;
Chris@275 462 }
Chris@275 463
Chris@275 464 if (type == AllPeaks) {
Chris@551 465 int minbin = ymin;
Chris@551 466 if (minbin > 0) minbin = minbin - 1;
Chris@551 467 int maxbin = ymax;
Chris@551 468 if (maxbin < getHeight() - 1) maxbin = maxbin + 1;
Chris@551 469 const int n = maxbin - minbin + 1;
Chris@1218 470 float *values = new float[n];
Chris@551 471 getMagnitudesAt(x, values, minbin, maxbin - minbin + 1);
Chris@929 472 for (int bin = ymin; bin <= ymax; ++bin) {
Chris@551 473 if (bin == minbin || bin == maxbin) continue;
Chris@551 474 if (values[bin - minbin] > values[bin - minbin - 1] &&
Chris@551 475 values[bin - minbin] > values[bin - minbin + 1]) {
Chris@275 476 peaks.insert(bin);
Chris@275 477 }
Chris@275 478 }
Chris@1218 479 delete[] values;
Chris@275 480 return peaks;
Chris@275 481 }
Chris@275 482
Chris@551 483 Column values = getColumn(x);
Chris@1154 484 int nv = int(values.size());
Chris@275 485
Chris@500 486 float mean = 0.f;
Chris@1154 487 for (int i = 0; i < nv; ++i) mean += values[i];
Chris@1154 488 if (nv > 0) mean = mean / float(values.size());
Chris@1038 489
Chris@275 490 // For peak picking we use a moving median window, picking the
Chris@275 491 // highest value within each continuous region of values that
Chris@275 492 // exceed the median. For pitch adaptivity, we adjust the window
Chris@275 493 // size to a roughly constant pitch range (about four tones).
Chris@275 494
Chris@1040 495 sv_samplerate_t sampleRate = getSampleRate();
Chris@275 496
Chris@1090 497 vector<int> inrange;
Chris@1576 498 double dist = 0.5;
Chris@500 499
Chris@929 500 int medianWinSize = getPeakPickWindowSize(type, sampleRate, ymin, dist);
Chris@929 501 int halfWin = medianWinSize/2;
Chris@275 502
Chris@1573 503 MovingMedian<float> window(medianWinSize);
Chris@1573 504
Chris@929 505 int binmin;
Chris@275 506 if (ymin > halfWin) binmin = ymin - halfWin;
Chris@275 507 else binmin = 0;
Chris@275 508
Chris@929 509 int binmax;
Chris@1154 510 if (ymax + halfWin < nv) binmax = ymax + halfWin;
Chris@1154 511 else binmax = nv - 1;
Chris@275 512
Chris@929 513 int prevcentre = 0;
Chris@500 514
Chris@929 515 for (int bin = binmin; bin <= binmax; ++bin) {
Chris@275 516
Chris@275 517 float value = values[bin];
Chris@275 518
Chris@280 519 // so-called median will actually be the dist*100'th percentile
Chris@280 520 medianWinSize = getPeakPickWindowSize(type, sampleRate, bin, dist);
Chris@275 521 halfWin = medianWinSize/2;
Chris@275 522
Chris@1573 523 int actualSize = std::min(medianWinSize, bin - binmin + 1);
Chris@1573 524 window.resize(actualSize);
Chris@1573 525 window.setPercentile(dist * 100.0);
Chris@1573 526 window.push(value);
Chris@275 527
Chris@275 528 if (type == MajorPitchAdaptivePeaks) {
Chris@1154 529 if (ymax + halfWin < nv) binmax = ymax + halfWin;
Chris@1154 530 else binmax = nv - 1;
Chris@275 531 }
Chris@275 532
Chris@1573 533 float median = window.get();
Chris@275 534
Chris@929 535 int centrebin = 0;
Chris@500 536 if (bin > actualSize/2) centrebin = bin - actualSize/2;
Chris@500 537
Chris@500 538 while (centrebin > prevcentre || bin == binmin) {
Chris@275 539
Chris@500 540 if (centrebin > prevcentre) ++prevcentre;
Chris@500 541
Chris@500 542 float centre = values[prevcentre];
Chris@500 543
Chris@500 544 if (centre > median) {
Chris@500 545 inrange.push_back(centrebin);
Chris@500 546 }
Chris@500 547
Chris@1154 548 if (centre <= median || centrebin+1 == nv) {
Chris@500 549 if (!inrange.empty()) {
Chris@929 550 int peakbin = 0;
Chris@500 551 float peakval = 0.f;
Chris@929 552 for (int i = 0; i < (int)inrange.size(); ++i) {
Chris@500 553 if (i == 0 || values[inrange[i]] > peakval) {
Chris@500 554 peakval = values[inrange[i]];
Chris@500 555 peakbin = inrange[i];
Chris@500 556 }
Chris@500 557 }
Chris@500 558 inrange.clear();
Chris@500 559 if (peakbin >= ymin && peakbin <= ymax) {
Chris@500 560 peaks.insert(peakbin);
Chris@275 561 }
Chris@275 562 }
Chris@275 563 }
Chris@500 564
Chris@500 565 if (bin == binmin) break;
Chris@275 566 }
Chris@275 567 }
Chris@275 568
Chris@275 569 return peaks;
Chris@275 570 }
Chris@275 571
Chris@929 572 int
Chris@1040 573 FFTModel::getPeakPickWindowSize(PeakPickType type, sv_samplerate_t sampleRate,
Chris@1576 574 int bin, double &dist) const
Chris@275 575 {
Chris@1576 576 dist = 0.5; // dist is percentile / 100.0
Chris@275 577 if (type == MajorPeaks) return 10;
Chris@275 578 if (bin == 0) return 3;
Chris@280 579
Chris@1091 580 double binfreq = (sampleRate * bin) / m_fftSize;
Chris@1038 581 double hifreq = Pitch::getFrequencyForPitch(73, 0, binfreq);
Chris@280 582
Chris@1091 583 int hibin = int(lrint((hifreq * m_fftSize) / sampleRate));
Chris@275 584 int medianWinSize = hibin - bin;
Chris@1576 585
Chris@1575 586 if (medianWinSize < 3) {
Chris@1575 587 medianWinSize = 3;
Chris@1575 588 }
Chris@1576 589
Chris@1576 590 // We want to avoid the median window size changing too often, as
Chris@1576 591 // it requires a reallocation. So snap to a nearby round number.
Chris@1576 592
Chris@1575 593 if (medianWinSize > 20) {
Chris@1575 594 medianWinSize = (1 + medianWinSize / 10) * 10;
Chris@1575 595 }
Chris@1576 596 if (medianWinSize > 200) {
Chris@1576 597 medianWinSize = (1 + medianWinSize / 100) * 100;
Chris@1576 598 }
Chris@1576 599 if (medianWinSize > 2000) {
Chris@1576 600 medianWinSize = (1 + medianWinSize / 1000) * 1000;
Chris@1576 601 }
Chris@1576 602 if (medianWinSize > 20000) {
Chris@1576 603 medianWinSize = 20000;
Chris@1575 604 }
Chris@280 605
Chris@1576 606 if (medianWinSize < 100) {
Chris@1576 607 dist = 1.0 - (4.0 / medianWinSize);
Chris@1576 608 } else {
Chris@1576 609 dist = 1.0 - (8.0 / medianWinSize);
Chris@1576 610 }
Chris@1576 611 if (dist < 0.5) dist = 0.5;
Chris@1575 612
Chris@275 613 return medianWinSize;
Chris@275 614 }
Chris@275 615
Chris@275 616 FFTModel::PeakSet
Chris@929 617 FFTModel::getPeakFrequencies(PeakPickType type, int x,
Chris@1191 618 int ymin, int ymax) const
Chris@275 619 {
Chris@551 620 Profiler profiler("FFTModel::getPeakFrequencies");
Chris@551 621
Chris@275 622 PeakSet peaks;
Chris@275 623 if (!isOK()) return peaks;
Chris@275 624 PeakLocationSet locations = getPeaks(type, x, ymin, ymax);
Chris@275 625
Chris@1040 626 sv_samplerate_t sampleRate = getSampleRate();
Chris@929 627 int incr = getResolution();
Chris@275 628
Chris@275 629 // This duplicates some of the work of estimateStableFrequency to
Chris@275 630 // allow us to retrieve the phases in two separate vertical
Chris@275 631 // columns, instead of jumping back and forth between columns x and
Chris@275 632 // x+1, which may be significantly slower if re-seeking is needed
Chris@275 633
Chris@1090 634 vector<float> phases;
Chris@275 635 for (PeakLocationSet::iterator i = locations.begin();
Chris@275 636 i != locations.end(); ++i) {
Chris@275 637 phases.push_back(getPhaseAt(x, *i));
Chris@275 638 }
Chris@275 639
Chris@929 640 int phaseIndex = 0;
Chris@275 641 for (PeakLocationSet::iterator i = locations.begin();
Chris@275 642 i != locations.end(); ++i) {
Chris@1038 643 double oldPhase = phases[phaseIndex];
Chris@1038 644 double newPhase = getPhaseAt(x+1, *i);
Chris@1090 645 double expectedPhase = oldPhase + (2.0 * M_PI * *i * incr) / m_fftSize;
Chris@1038 646 double phaseError = princarg(newPhase - expectedPhase);
Chris@1038 647 double frequency =
Chris@275 648 (sampleRate * (expectedPhase + phaseError - oldPhase))
Chris@275 649 / (2 * M_PI * incr);
Chris@1045 650 peaks[*i] = frequency;
Chris@275 651 ++phaseIndex;
Chris@275 652 }
Chris@275 653
Chris@275 654 return peaks;
Chris@275 655 }
Chris@275 656