annotate data/model/FFTModel.cpp @ 1455:ec9e65fcf749

The use of the begin/end pairs here just seems to cause too many rows to be deleted (from the visual representation, not the underlying model). Things apparently work better if we just modify the underlying model and let the change signals percolate back up again. To that end, update the change handlers so as to cover their proper ranges with dataChanged signals.
author Chris Cannam
date Mon, 23 Apr 2018 16:03:35 +0100
parents 48e9f538e6e9
children 0925b37a3ed1
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@183 23
Chris@402 24 #include <algorithm>
Chris@402 25
Chris@152 26 #include <cassert>
Chris@1090 27 #include <deque>
Chris@152 28
Chris@1090 29 using namespace std;
Chris@1090 30
Chris@1256 31 static HitCount inSmallCache("FFTModel: Small FFT cache");
Chris@1256 32 static HitCount inSourceCache("FFTModel: Source data cache");
Chris@1256 33
Chris@152 34 FFTModel::FFTModel(const DenseTimeValueModel *model,
Chris@152 35 int channel,
Chris@152 36 WindowType windowType,
Chris@929 37 int windowSize,
Chris@929 38 int windowIncrement,
Chris@1090 39 int fftSize) :
Chris@1090 40 m_model(model),
Chris@1090 41 m_channel(channel),
Chris@1090 42 m_windowType(windowType),
Chris@1090 43 m_windowSize(windowSize),
Chris@1090 44 m_windowIncrement(windowIncrement),
Chris@1090 45 m_fftSize(fftSize),
Chris@1091 46 m_windower(windowType, windowSize),
Chris@1093 47 m_fft(fftSize),
Chris@1371 48 m_cacheWriteIndex(0),
Chris@1093 49 m_cacheSize(3)
Chris@152 50 {
Chris@1371 51 while (m_cached.size() < m_cacheSize) {
Chris@1371 52 m_cached.push_back({ -1, cvec(m_fftSize / 2 + 1) });
Chris@1371 53 }
Chris@1371 54
Chris@1091 55 if (m_windowSize > m_fftSize) {
Chris@1428 56 SVCERR << "ERROR: FFTModel::FFTModel: window size (" << m_windowSize
Chris@1428 57 << ") must be at least FFT size (" << m_fftSize << ")" << endl;
Chris@1091 58 throw invalid_argument("FFTModel window size must be at least FFT size");
Chris@1091 59 }
Chris@1133 60
Chris@1270 61 m_fft.initFloat();
Chris@1270 62
Chris@1133 63 connect(model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged()));
Chris@1133 64 connect(model, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
Chris@1133 65 this, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)));
Chris@152 66 }
Chris@152 67
Chris@152 68 FFTModel::~FFTModel()
Chris@152 69 {
Chris@152 70 }
Chris@152 71
Chris@360 72 void
Chris@360 73 FFTModel::sourceModelAboutToBeDeleted()
Chris@360 74 {
Chris@1090 75 if (m_model) {
Chris@1428 76 SVDEBUG << "FFTModel[" << this << "]::sourceModelAboutToBeDeleted(" << m_model << ")" << endl;
Chris@1090 77 m_model = 0;
Chris@360 78 }
Chris@360 79 }
Chris@360 80
Chris@1091 81 int
Chris@1091 82 FFTModel::getWidth() const
Chris@1091 83 {
Chris@1091 84 if (!m_model) return 0;
Chris@1091 85 return int((m_model->getEndFrame() - m_model->getStartFrame())
Chris@1091 86 / m_windowIncrement) + 1;
Chris@1091 87 }
Chris@1091 88
Chris@1091 89 int
Chris@1091 90 FFTModel::getHeight() const
Chris@1091 91 {
Chris@1091 92 return m_fftSize / 2 + 1;
Chris@1091 93 }
Chris@1091 94
Chris@152 95 QString
Chris@929 96 FFTModel::getBinName(int n) const
Chris@152 97 {
Chris@1040 98 sv_samplerate_t sr = getSampleRate();
Chris@152 99 if (!sr) return "";
Chris@204 100 QString name = tr("%1 Hz").arg((n * sr) / ((getHeight()-1) * 2));
Chris@152 101 return name;
Chris@152 102 }
Chris@152 103
Chris@1091 104 FFTModel::Column
Chris@1091 105 FFTModel::getColumn(int x) const
Chris@1091 106 {
Chris@1091 107 auto cplx = getFFTColumn(x);
Chris@1091 108 Column col;
Chris@1154 109 col.reserve(cplx.size());
Chris@1091 110 for (auto c: cplx) col.push_back(abs(c));
Chris@1319 111 return col;
Chris@1091 112 }
Chris@1091 113
Chris@1200 114 FFTModel::Column
Chris@1200 115 FFTModel::getPhases(int x) const
Chris@1200 116 {
Chris@1200 117 auto cplx = getFFTColumn(x);
Chris@1200 118 Column col;
Chris@1200 119 col.reserve(cplx.size());
Chris@1201 120 for (auto c: cplx) {
Chris@1201 121 col.push_back(arg(c));
Chris@1201 122 }
Chris@1319 123 return col;
Chris@1200 124 }
Chris@1200 125
Chris@1091 126 float
Chris@1091 127 FFTModel::getMagnitudeAt(int x, int y) const
Chris@1091 128 {
Chris@1093 129 if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) return 0.f;
Chris@1093 130 auto col = getFFTColumn(x);
Chris@1093 131 return abs(col[y]);
Chris@1091 132 }
Chris@1091 133
Chris@1091 134 float
Chris@1091 135 FFTModel::getMaximumMagnitudeAt(int x) const
Chris@1091 136 {
Chris@1091 137 Column col(getColumn(x));
Chris@1092 138 float max = 0.f;
Chris@1154 139 int n = int(col.size());
Chris@1154 140 for (int i = 0; i < n; ++i) {
Chris@1092 141 if (col[i] > max) max = col[i];
Chris@1092 142 }
Chris@1092 143 return max;
Chris@1091 144 }
Chris@1091 145
Chris@1091 146 float
Chris@1091 147 FFTModel::getPhaseAt(int x, int y) const
Chris@1091 148 {
Chris@1093 149 if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) return 0.f;
Chris@1091 150 return arg(getFFTColumn(x)[y]);
Chris@1091 151 }
Chris@1091 152
Chris@1091 153 void
Chris@1091 154 FFTModel::getValuesAt(int x, int y, float &re, float &im) const
Chris@1091 155 {
Chris@1091 156 auto col = getFFTColumn(x);
Chris@1091 157 re = col[y].real();
Chris@1091 158 im = col[y].imag();
Chris@1091 159 }
Chris@1091 160
Chris@1091 161 bool
Chris@1091 162 FFTModel::getMagnitudesAt(int x, float *values, int minbin, int count) const
Chris@1091 163 {
Chris@1091 164 if (count == 0) count = getHeight();
Chris@1091 165 auto col = getFFTColumn(x);
Chris@1091 166 for (int i = 0; i < count; ++i) {
Chris@1091 167 values[i] = abs(col[minbin + i]);
Chris@1091 168 }
Chris@1091 169 return true;
Chris@1091 170 }
Chris@1091 171
Chris@1091 172 bool
Chris@1091 173 FFTModel::getPhasesAt(int x, float *values, int minbin, int count) const
Chris@1091 174 {
Chris@1091 175 if (count == 0) count = getHeight();
Chris@1091 176 auto col = getFFTColumn(x);
Chris@1091 177 for (int i = 0; i < count; ++i) {
Chris@1091 178 values[i] = arg(col[minbin + i]);
Chris@1091 179 }
Chris@1091 180 return true;
Chris@1091 181 }
Chris@1091 182
Chris@1091 183 bool
Chris@1091 184 FFTModel::getValuesAt(int x, float *reals, float *imags, int minbin, int count) const
Chris@1091 185 {
Chris@1091 186 if (count == 0) count = getHeight();
Chris@1091 187 auto col = getFFTColumn(x);
Chris@1091 188 for (int i = 0; i < count; ++i) {
Chris@1091 189 reals[i] = col[minbin + i].real();
Chris@1091 190 }
Chris@1091 191 for (int i = 0; i < count; ++i) {
Chris@1091 192 imags[i] = col[minbin + i].imag();
Chris@1091 193 }
Chris@1091 194 return true;
Chris@1091 195 }
Chris@1091 196
Chris@1326 197 FFTModel::fvec
Chris@1091 198 FFTModel::getSourceSamples(int column) const
Chris@1091 199 {
Chris@1094 200 // m_fftSize may be greater than m_windowSize, but not the reverse
Chris@1094 201
Chris@1094 202 // cerr << "getSourceSamples(" << column << ")" << endl;
Chris@1094 203
Chris@1091 204 auto range = getSourceSampleRange(column);
Chris@1094 205 auto data = getSourceData(range);
Chris@1094 206
Chris@1091 207 int off = (m_fftSize - m_windowSize) / 2;
Chris@1094 208
Chris@1094 209 if (off == 0) {
Chris@1094 210 return data;
Chris@1094 211 } else {
Chris@1094 212 vector<float> pad(off, 0.f);
Chris@1326 213 fvec padded;
Chris@1094 214 padded.reserve(m_fftSize);
Chris@1094 215 padded.insert(padded.end(), pad.begin(), pad.end());
Chris@1094 216 padded.insert(padded.end(), data.begin(), data.end());
Chris@1094 217 padded.insert(padded.end(), pad.begin(), pad.end());
Chris@1094 218 return padded;
Chris@1094 219 }
Chris@1094 220 }
Chris@1094 221
Chris@1326 222 FFTModel::fvec
Chris@1094 223 FFTModel::getSourceData(pair<sv_frame_t, sv_frame_t> range) const
Chris@1094 224 {
Chris@1094 225 // cerr << "getSourceData(" << range.first << "," << range.second
Chris@1094 226 // << "): saved range is (" << m_savedData.range.first
Chris@1094 227 // << "," << m_savedData.range.second << ")" << endl;
Chris@1094 228
Chris@1100 229 if (m_savedData.range == range) {
Chris@1256 230 inSourceCache.hit();
Chris@1100 231 return m_savedData.data;
Chris@1100 232 }
Chris@1094 233
Chris@1270 234 Profiler profiler("FFTModel::getSourceData (cache miss)");
Chris@1270 235
Chris@1094 236 if (range.first < m_savedData.range.second &&
Chris@1094 237 range.first >= m_savedData.range.first &&
Chris@1094 238 range.second > m_savedData.range.second) {
Chris@1094 239
Chris@1256 240 inSourceCache.partial();
Chris@1256 241
Chris@1100 242 sv_frame_t discard = range.first - m_savedData.range.first;
Chris@1100 243
Chris@1326 244 fvec acc(m_savedData.data.begin() + discard, m_savedData.data.end());
Chris@1094 245
Chris@1326 246 fvec rest = getSourceDataUncached({ m_savedData.range.second, range.second });
Chris@1100 247
Chris@1100 248 acc.insert(acc.end(), rest.begin(), rest.end());
Chris@1094 249
Chris@1095 250 m_savedData = { range, acc };
Chris@1095 251 return acc;
Chris@1095 252
Chris@1095 253 } else {
Chris@1095 254
Chris@1256 255 inSourceCache.miss();
Chris@1256 256
Chris@1095 257 auto data = getSourceDataUncached(range);
Chris@1095 258 m_savedData = { range, data };
Chris@1095 259 return data;
Chris@1094 260 }
Chris@1095 261 }
Chris@1094 262
Chris@1326 263 FFTModel::fvec
Chris@1095 264 FFTModel::getSourceDataUncached(pair<sv_frame_t, sv_frame_t> range) const
Chris@1095 265 {
Chris@1091 266 decltype(range.first) pfx = 0;
Chris@1091 267 if (range.first < 0) {
Chris@1091 268 pfx = -range.first;
Chris@1091 269 range = { 0, range.second };
Chris@1091 270 }
Chris@1096 271
Chris@1096 272 auto data = m_model->getData(m_channel,
Chris@1096 273 range.first,
Chris@1096 274 range.second - range.first);
Chris@1096 275
Chris@1281 276 if (data.empty()) {
Chris@1281 277 SVDEBUG << "NOTE: empty source data for range (" << range.first << ","
Chris@1281 278 << range.second << ") (model end frame "
Chris@1281 279 << m_model->getEndFrame() << ")" << endl;
Chris@1281 280 }
Chris@1281 281
Chris@1096 282 // don't return a partial frame
Chris@1096 283 data.resize(range.second - range.first, 0.f);
Chris@1096 284
Chris@1096 285 if (pfx > 0) {
Chris@1096 286 vector<float> pad(pfx, 0.f);
Chris@1096 287 data.insert(data.begin(), pad.begin(), pad.end());
Chris@1096 288 }
Chris@1096 289
Chris@1091 290 if (m_channel == -1) {
Chris@1429 291 int channels = m_model->getChannelCount();
Chris@1429 292 if (channels > 1) {
Chris@1096 293 int n = int(data.size());
Chris@1096 294 float factor = 1.f / float(channels);
Chris@1100 295 // use mean instead of sum for fft model input
Chris@1429 296 for (int i = 0; i < n; ++i) {
Chris@1429 297 data[i] *= factor;
Chris@1429 298 }
Chris@1429 299 }
Chris@1091 300 }
Chris@1094 301
Chris@1094 302 return data;
Chris@1091 303 }
Chris@1091 304
Chris@1371 305 const FFTModel::cvec &
Chris@1093 306 FFTModel::getFFTColumn(int n) const
Chris@1091 307 {
Chris@1258 308 // The small cache (i.e. the m_cached deque) is for cases where
Chris@1258 309 // values are looked up individually, and for e.g. peak-frequency
Chris@1258 310 // spectrograms where values from two consecutive columns are
Chris@1257 311 // needed at once. This cache gets essentially no hits when
Chris@1258 312 // scrolling through a magnitude spectrogram, but 95%+ hits with a
Chris@1258 313 // peak-frequency spectrogram.
Chris@1257 314 for (const auto &incache : m_cached) {
Chris@1093 315 if (incache.n == n) {
Chris@1256 316 inSmallCache.hit();
Chris@1093 317 return incache.col;
Chris@1093 318 }
Chris@1093 319 }
Chris@1256 320 inSmallCache.miss();
Chris@1258 321
Chris@1258 322 Profiler profiler("FFTModel::getFFTColumn (cache miss)");
Chris@1093 323
Chris@1093 324 auto samples = getSourceSamples(n);
Chris@1100 325 m_windower.cut(samples.data());
Chris@1270 326 breakfastquay::v_fftshift(samples.data(), m_fftSize);
Chris@1270 327
Chris@1371 328 cvec &col = m_cached[m_cacheWriteIndex].col;
Chris@1270 329
Chris@1270 330 m_fft.forwardInterleaved(samples.data(),
Chris@1270 331 reinterpret_cast<float *>(col.data()));
Chris@1093 332
Chris@1371 333 m_cached[m_cacheWriteIndex].n = n;
Chris@1371 334
Chris@1371 335 m_cacheWriteIndex = (m_cacheWriteIndex + 1) % m_cacheSize;
Chris@1093 336
Chris@1319 337 return col;
Chris@1091 338 }
Chris@1091 339
Chris@275 340 bool
Chris@1045 341 FFTModel::estimateStableFrequency(int x, int y, double &frequency)
Chris@275 342 {
Chris@275 343 if (!isOK()) return false;
Chris@275 344
Chris@1090 345 frequency = double(y * getSampleRate()) / m_fftSize;
Chris@275 346
Chris@275 347 if (x+1 >= getWidth()) return false;
Chris@275 348
Chris@275 349 // At frequency f, a phase shift of 2pi (one cycle) happens in 1/f sec.
Chris@275 350 // At hopsize h and sample rate sr, one hop happens in h/sr sec.
Chris@275 351 // At window size w, for bin b, f is b*sr/w.
Chris@275 352 // thus 2pi phase shift happens in w/(b*sr) sec.
Chris@275 353 // We need to know what phase shift we expect from h/sr sec.
Chris@275 354 // -> 2pi * ((h/sr) / (w/(b*sr)))
Chris@275 355 // = 2pi * ((h * b * sr) / (w * sr))
Chris@275 356 // = 2pi * (h * b) / w.
Chris@275 357
Chris@1038 358 double oldPhase = getPhaseAt(x, y);
Chris@1038 359 double newPhase = getPhaseAt(x+1, y);
Chris@275 360
Chris@929 361 int incr = getResolution();
Chris@275 362
Chris@1090 363 double expectedPhase = oldPhase + (2.0 * M_PI * y * incr) / m_fftSize;
Chris@275 364
Chris@1038 365 double phaseError = princarg(newPhase - expectedPhase);
Chris@275 366
Chris@275 367 // The new frequency estimate based on the phase error resulting
Chris@275 368 // from assuming the "native" frequency of this bin
Chris@275 369
Chris@275 370 frequency =
Chris@1090 371 (getSampleRate() * (expectedPhase + phaseError - oldPhase)) /
Chris@1045 372 (2.0 * M_PI * incr);
Chris@275 373
Chris@275 374 return true;
Chris@275 375 }
Chris@275 376
Chris@275 377 FFTModel::PeakLocationSet
Chris@1191 378 FFTModel::getPeaks(PeakPickType type, int x, int ymin, int ymax) const
Chris@275 379 {
Chris@551 380 Profiler profiler("FFTModel::getPeaks");
Chris@551 381
Chris@275 382 FFTModel::PeakLocationSet peaks;
Chris@275 383 if (!isOK()) return peaks;
Chris@275 384
Chris@275 385 if (ymax == 0 || ymax > getHeight() - 1) {
Chris@275 386 ymax = getHeight() - 1;
Chris@275 387 }
Chris@275 388
Chris@275 389 if (type == AllPeaks) {
Chris@551 390 int minbin = ymin;
Chris@551 391 if (minbin > 0) minbin = minbin - 1;
Chris@551 392 int maxbin = ymax;
Chris@551 393 if (maxbin < getHeight() - 1) maxbin = maxbin + 1;
Chris@551 394 const int n = maxbin - minbin + 1;
Chris@1218 395 float *values = new float[n];
Chris@551 396 getMagnitudesAt(x, values, minbin, maxbin - minbin + 1);
Chris@929 397 for (int bin = ymin; bin <= ymax; ++bin) {
Chris@551 398 if (bin == minbin || bin == maxbin) continue;
Chris@551 399 if (values[bin - minbin] > values[bin - minbin - 1] &&
Chris@551 400 values[bin - minbin] > values[bin - minbin + 1]) {
Chris@275 401 peaks.insert(bin);
Chris@275 402 }
Chris@275 403 }
Chris@1218 404 delete[] values;
Chris@275 405 return peaks;
Chris@275 406 }
Chris@275 407
Chris@551 408 Column values = getColumn(x);
Chris@1154 409 int nv = int(values.size());
Chris@275 410
Chris@500 411 float mean = 0.f;
Chris@1154 412 for (int i = 0; i < nv; ++i) mean += values[i];
Chris@1154 413 if (nv > 0) mean = mean / float(values.size());
Chris@1038 414
Chris@275 415 // For peak picking we use a moving median window, picking the
Chris@275 416 // highest value within each continuous region of values that
Chris@275 417 // exceed the median. For pitch adaptivity, we adjust the window
Chris@275 418 // size to a roughly constant pitch range (about four tones).
Chris@275 419
Chris@1040 420 sv_samplerate_t sampleRate = getSampleRate();
Chris@275 421
Chris@1090 422 deque<float> window;
Chris@1090 423 vector<int> inrange;
Chris@280 424 float dist = 0.5;
Chris@500 425
Chris@929 426 int medianWinSize = getPeakPickWindowSize(type, sampleRate, ymin, dist);
Chris@929 427 int halfWin = medianWinSize/2;
Chris@275 428
Chris@929 429 int binmin;
Chris@275 430 if (ymin > halfWin) binmin = ymin - halfWin;
Chris@275 431 else binmin = 0;
Chris@275 432
Chris@929 433 int binmax;
Chris@1154 434 if (ymax + halfWin < nv) binmax = ymax + halfWin;
Chris@1154 435 else binmax = nv - 1;
Chris@275 436
Chris@929 437 int prevcentre = 0;
Chris@500 438
Chris@929 439 for (int bin = binmin; bin <= binmax; ++bin) {
Chris@275 440
Chris@275 441 float value = values[bin];
Chris@275 442
Chris@275 443 window.push_back(value);
Chris@275 444
Chris@280 445 // so-called median will actually be the dist*100'th percentile
Chris@280 446 medianWinSize = getPeakPickWindowSize(type, sampleRate, bin, dist);
Chris@275 447 halfWin = medianWinSize/2;
Chris@275 448
Chris@929 449 while ((int)window.size() > medianWinSize) {
Chris@500 450 window.pop_front();
Chris@500 451 }
Chris@500 452
Chris@1038 453 int actualSize = int(window.size());
Chris@275 454
Chris@275 455 if (type == MajorPitchAdaptivePeaks) {
Chris@1154 456 if (ymax + halfWin < nv) binmax = ymax + halfWin;
Chris@1154 457 else binmax = nv - 1;
Chris@275 458 }
Chris@275 459
Chris@1090 460 deque<float> sorted(window);
Chris@1090 461 sort(sorted.begin(), sorted.end());
Chris@1038 462 float median = sorted[int(float(sorted.size()) * dist)];
Chris@275 463
Chris@929 464 int centrebin = 0;
Chris@500 465 if (bin > actualSize/2) centrebin = bin - actualSize/2;
Chris@500 466
Chris@500 467 while (centrebin > prevcentre || bin == binmin) {
Chris@275 468
Chris@500 469 if (centrebin > prevcentre) ++prevcentre;
Chris@500 470
Chris@500 471 float centre = values[prevcentre];
Chris@500 472
Chris@500 473 if (centre > median) {
Chris@500 474 inrange.push_back(centrebin);
Chris@500 475 }
Chris@500 476
Chris@1154 477 if (centre <= median || centrebin+1 == nv) {
Chris@500 478 if (!inrange.empty()) {
Chris@929 479 int peakbin = 0;
Chris@500 480 float peakval = 0.f;
Chris@929 481 for (int i = 0; i < (int)inrange.size(); ++i) {
Chris@500 482 if (i == 0 || values[inrange[i]] > peakval) {
Chris@500 483 peakval = values[inrange[i]];
Chris@500 484 peakbin = inrange[i];
Chris@500 485 }
Chris@500 486 }
Chris@500 487 inrange.clear();
Chris@500 488 if (peakbin >= ymin && peakbin <= ymax) {
Chris@500 489 peaks.insert(peakbin);
Chris@275 490 }
Chris@275 491 }
Chris@275 492 }
Chris@500 493
Chris@500 494 if (bin == binmin) break;
Chris@275 495 }
Chris@275 496 }
Chris@275 497
Chris@275 498 return peaks;
Chris@275 499 }
Chris@275 500
Chris@929 501 int
Chris@1040 502 FFTModel::getPeakPickWindowSize(PeakPickType type, sv_samplerate_t sampleRate,
Chris@929 503 int bin, float &percentile) const
Chris@275 504 {
Chris@280 505 percentile = 0.5;
Chris@275 506 if (type == MajorPeaks) return 10;
Chris@275 507 if (bin == 0) return 3;
Chris@280 508
Chris@1091 509 double binfreq = (sampleRate * bin) / m_fftSize;
Chris@1038 510 double hifreq = Pitch::getFrequencyForPitch(73, 0, binfreq);
Chris@280 511
Chris@1091 512 int hibin = int(lrint((hifreq * m_fftSize) / sampleRate));
Chris@275 513 int medianWinSize = hibin - bin;
Chris@275 514 if (medianWinSize < 3) medianWinSize = 3;
Chris@280 515
Chris@1091 516 percentile = 0.5f + float(binfreq / sampleRate);
Chris@280 517
Chris@275 518 return medianWinSize;
Chris@275 519 }
Chris@275 520
Chris@275 521 FFTModel::PeakSet
Chris@929 522 FFTModel::getPeakFrequencies(PeakPickType type, int x,
Chris@1191 523 int ymin, int ymax) const
Chris@275 524 {
Chris@551 525 Profiler profiler("FFTModel::getPeakFrequencies");
Chris@551 526
Chris@275 527 PeakSet peaks;
Chris@275 528 if (!isOK()) return peaks;
Chris@275 529 PeakLocationSet locations = getPeaks(type, x, ymin, ymax);
Chris@275 530
Chris@1040 531 sv_samplerate_t sampleRate = getSampleRate();
Chris@929 532 int incr = getResolution();
Chris@275 533
Chris@275 534 // This duplicates some of the work of estimateStableFrequency to
Chris@275 535 // allow us to retrieve the phases in two separate vertical
Chris@275 536 // columns, instead of jumping back and forth between columns x and
Chris@275 537 // x+1, which may be significantly slower if re-seeking is needed
Chris@275 538
Chris@1090 539 vector<float> phases;
Chris@275 540 for (PeakLocationSet::iterator i = locations.begin();
Chris@275 541 i != locations.end(); ++i) {
Chris@275 542 phases.push_back(getPhaseAt(x, *i));
Chris@275 543 }
Chris@275 544
Chris@929 545 int phaseIndex = 0;
Chris@275 546 for (PeakLocationSet::iterator i = locations.begin();
Chris@275 547 i != locations.end(); ++i) {
Chris@1038 548 double oldPhase = phases[phaseIndex];
Chris@1038 549 double newPhase = getPhaseAt(x+1, *i);
Chris@1090 550 double expectedPhase = oldPhase + (2.0 * M_PI * *i * incr) / m_fftSize;
Chris@1038 551 double phaseError = princarg(newPhase - expectedPhase);
Chris@1038 552 double frequency =
Chris@275 553 (sampleRate * (expectedPhase + phaseError - oldPhase))
Chris@275 554 / (2 * M_PI * incr);
Chris@1045 555 peaks[*i] = frequency;
Chris@275 556 ++phaseIndex;
Chris@275 557 }
Chris@275 558
Chris@275 559 return peaks;
Chris@275 560 }
Chris@275 561