annotate data/model/FFTModel.cpp @ 360:ac300d385ab2

* Various fixes to object lifetime management, particularly in the spectrum layer and for notification of main model deletion. The main purpose of this is to improve the behaviour of the spectrum, but I think it may also help with #1840922 Various crashes in Layer Summary window.
author Chris Cannam
date Wed, 23 Jan 2008 15:43:27 +0000
parents aa8dbac62024
children cc4eb32efc6c
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@297 18 #include "AggregateWaveModel.h"
Chris@152 19
Chris@183 20 #include "base/Profiler.h"
Chris@275 21 #include "base/Pitch.h"
Chris@183 22
Chris@152 23 #include <cassert>
Chris@152 24
Chris@152 25 FFTModel::FFTModel(const DenseTimeValueModel *model,
Chris@152 26 int channel,
Chris@152 27 WindowType windowType,
Chris@152 28 size_t windowSize,
Chris@152 29 size_t windowIncrement,
Chris@152 30 size_t fftSize,
Chris@152 31 bool polar,
Chris@334 32 StorageAdviser::Criteria criteria,
Chris@152 33 size_t fillFromColumn) :
Chris@152 34 //!!! ZoomConstraint!
Chris@152 35 m_server(0),
Chris@152 36 m_xshift(0),
Chris@152 37 m_yshift(0)
Chris@152 38 {
Chris@297 39 setSourceModel(const_cast<DenseTimeValueModel *>(model)); //!!! hmm.
Chris@297 40
Chris@297 41 m_server = getServer(model,
Chris@297 42 channel,
Chris@297 43 windowType,
Chris@297 44 windowSize,
Chris@297 45 windowIncrement,
Chris@297 46 fftSize,
Chris@297 47 polar,
Chris@334 48 criteria,
Chris@297 49 fillFromColumn);
Chris@152 50
Chris@200 51 if (!m_server) return; // caller should check isOK()
Chris@200 52
Chris@152 53 size_t xratio = windowIncrement / m_server->getWindowIncrement();
Chris@152 54 size_t yratio = m_server->getFFTSize() / fftSize;
Chris@152 55
Chris@152 56 while (xratio > 1) {
Chris@152 57 if (xratio & 0x1) {
Chris@152 58 std::cerr << "ERROR: FFTModel: Window increment ratio "
Chris@152 59 << windowIncrement << " / "
Chris@152 60 << m_server->getWindowIncrement()
Chris@152 61 << " must be a power of two" << std::endl;
Chris@152 62 assert(!(xratio & 0x1));
Chris@152 63 }
Chris@152 64 ++m_xshift;
Chris@152 65 xratio >>= 1;
Chris@152 66 }
Chris@152 67
Chris@152 68 while (yratio > 1) {
Chris@152 69 if (yratio & 0x1) {
Chris@152 70 std::cerr << "ERROR: FFTModel: FFT size ratio "
Chris@152 71 << m_server->getFFTSize() << " / " << fftSize
Chris@152 72 << " must be a power of two" << std::endl;
Chris@152 73 assert(!(yratio & 0x1));
Chris@152 74 }
Chris@152 75 ++m_yshift;
Chris@152 76 yratio >>= 1;
Chris@152 77 }
Chris@152 78 }
Chris@152 79
Chris@152 80 FFTModel::~FFTModel()
Chris@152 81 {
Chris@200 82 if (m_server) FFTDataServer::releaseInstance(m_server);
Chris@152 83 }
Chris@152 84
Chris@360 85 void
Chris@360 86 FFTModel::sourceModelAboutToBeDeleted()
Chris@360 87 {
Chris@360 88 if (m_sourceModel) {
Chris@360 89 FFTDataServer::modelAboutToBeDeleted(m_sourceModel);
Chris@360 90 }
Chris@360 91 }
Chris@360 92
Chris@297 93 FFTDataServer *
Chris@297 94 FFTModel::getServer(const DenseTimeValueModel *model,
Chris@297 95 int channel,
Chris@297 96 WindowType windowType,
Chris@297 97 size_t windowSize,
Chris@297 98 size_t windowIncrement,
Chris@297 99 size_t fftSize,
Chris@297 100 bool polar,
Chris@334 101 StorageAdviser::Criteria criteria,
Chris@297 102 size_t fillFromColumn)
Chris@297 103 {
Chris@297 104 // Obviously, an FFT model of channel C (where C != -1) of an
Chris@297 105 // aggregate model is the same as the FFT model of the appropriate
Chris@297 106 // channel of whichever model that aggregate channel is drawn
Chris@297 107 // from. We should use that model here, in case we already have
Chris@297 108 // the data for it or will be wanting the same data again later.
Chris@297 109
Chris@297 110 // If the channel is -1 (i.e. mixture of all channels), then we
Chris@297 111 // can't do this shortcut unless the aggregate model only has one
Chris@297 112 // channel or contains exactly all of the channels of a single
Chris@297 113 // other model. That isn't very likely -- if it were the case,
Chris@297 114 // why would we be using an aggregate model?
Chris@297 115
Chris@297 116 if (channel >= 0) {
Chris@297 117
Chris@297 118 const AggregateWaveModel *aggregate =
Chris@297 119 dynamic_cast<const AggregateWaveModel *>(model);
Chris@297 120
Chris@297 121 if (aggregate && channel < aggregate->getComponentCount()) {
Chris@297 122
Chris@297 123 AggregateWaveModel::ModelChannelSpec spec =
Chris@297 124 aggregate->getComponent(channel);
Chris@297 125
Chris@297 126 return getServer(spec.model,
Chris@297 127 spec.channel,
Chris@297 128 windowType,
Chris@297 129 windowSize,
Chris@297 130 windowIncrement,
Chris@297 131 fftSize,
Chris@297 132 polar,
Chris@334 133 criteria,
Chris@297 134 fillFromColumn);
Chris@297 135 }
Chris@297 136 }
Chris@297 137
Chris@297 138 // The normal case
Chris@297 139
Chris@297 140 return FFTDataServer::getFuzzyInstance(model,
Chris@297 141 channel,
Chris@297 142 windowType,
Chris@297 143 windowSize,
Chris@297 144 windowIncrement,
Chris@297 145 fftSize,
Chris@297 146 polar,
Chris@334 147 criteria,
Chris@297 148 fillFromColumn);
Chris@297 149 }
Chris@297 150
Chris@152 151 size_t
Chris@152 152 FFTModel::getSampleRate() const
Chris@152 153 {
Chris@152 154 return isOK() ? m_server->getModel()->getSampleRate() : 0;
Chris@152 155 }
Chris@152 156
Chris@152 157 void
Chris@182 158 FFTModel::getColumn(size_t x, Column &result) const
Chris@152 159 {
Chris@183 160 Profiler profiler("FFTModel::getColumn", false);
Chris@183 161
Chris@152 162 result.clear();
Chris@152 163 size_t height(getHeight());
Chris@152 164 for (size_t y = 0; y < height; ++y) {
Chris@152 165 result.push_back(const_cast<FFTModel *>(this)->getMagnitudeAt(x, y));
Chris@152 166 }
Chris@152 167 }
Chris@152 168
Chris@152 169 QString
Chris@152 170 FFTModel::getBinName(size_t n) const
Chris@152 171 {
Chris@152 172 size_t sr = getSampleRate();
Chris@152 173 if (!sr) return "";
Chris@204 174 QString name = tr("%1 Hz").arg((n * sr) / ((getHeight()-1) * 2));
Chris@152 175 return name;
Chris@152 176 }
Chris@152 177
Chris@275 178 bool
Chris@275 179 FFTModel::estimateStableFrequency(size_t x, size_t y, float &frequency)
Chris@275 180 {
Chris@275 181 if (!isOK()) return false;
Chris@275 182
Chris@275 183 size_t sampleRate = m_server->getModel()->getSampleRate();
Chris@275 184
Chris@275 185 size_t fftSize = m_server->getFFTSize() >> m_yshift;
Chris@275 186 frequency = (float(y) * sampleRate) / fftSize;
Chris@275 187
Chris@275 188 if (x+1 >= getWidth()) return false;
Chris@275 189
Chris@275 190 // At frequency f, a phase shift of 2pi (one cycle) happens in 1/f sec.
Chris@275 191 // At hopsize h and sample rate sr, one hop happens in h/sr sec.
Chris@275 192 // At window size w, for bin b, f is b*sr/w.
Chris@275 193 // thus 2pi phase shift happens in w/(b*sr) sec.
Chris@275 194 // We need to know what phase shift we expect from h/sr sec.
Chris@275 195 // -> 2pi * ((h/sr) / (w/(b*sr)))
Chris@275 196 // = 2pi * ((h * b * sr) / (w * sr))
Chris@275 197 // = 2pi * (h * b) / w.
Chris@275 198
Chris@275 199 float oldPhase = getPhaseAt(x, y);
Chris@275 200 float newPhase = getPhaseAt(x+1, y);
Chris@275 201
Chris@275 202 size_t incr = getResolution();
Chris@275 203
Chris@275 204 float expectedPhase = oldPhase + (2.0 * M_PI * y * incr) / fftSize;
Chris@275 205
Chris@275 206 float phaseError = princargf(newPhase - expectedPhase);
Chris@275 207
Chris@275 208 // bool stable = (fabsf(phaseError) < (1.1f * (m_windowIncrement * M_PI) / m_fftSize));
Chris@275 209
Chris@275 210 // The new frequency estimate based on the phase error resulting
Chris@275 211 // from assuming the "native" frequency of this bin
Chris@275 212
Chris@275 213 frequency =
Chris@275 214 (sampleRate * (expectedPhase + phaseError - oldPhase)) /
Chris@275 215 (2 * M_PI * incr);
Chris@275 216
Chris@275 217 return true;
Chris@275 218 }
Chris@275 219
Chris@275 220 FFTModel::PeakLocationSet
Chris@275 221 FFTModel::getPeaks(PeakPickType type, size_t x, size_t ymin, size_t ymax)
Chris@275 222 {
Chris@275 223 FFTModel::PeakLocationSet peaks;
Chris@275 224 if (!isOK()) return peaks;
Chris@275 225
Chris@275 226 if (ymax == 0 || ymax > getHeight() - 1) {
Chris@275 227 ymax = getHeight() - 1;
Chris@275 228 }
Chris@275 229
Chris@275 230 Column values;
Chris@275 231
Chris@275 232 if (type == AllPeaks) {
Chris@275 233 for (size_t y = ymin; y <= ymax; ++y) {
Chris@275 234 values.push_back(getMagnitudeAt(x, y));
Chris@275 235 }
Chris@275 236 size_t i = 0;
Chris@275 237 for (size_t bin = ymin; bin <= ymax; ++bin) {
Chris@275 238 if ((i == 0 || values[i] > values[i-1]) &&
Chris@275 239 (i == values.size()-1 || values[i] >= values[i+1])) {
Chris@275 240 peaks.insert(bin);
Chris@275 241 }
Chris@275 242 ++i;
Chris@275 243 }
Chris@275 244 return peaks;
Chris@275 245 }
Chris@275 246
Chris@275 247 getColumn(x, values);
Chris@275 248
Chris@275 249 // For peak picking we use a moving median window, picking the
Chris@275 250 // highest value within each continuous region of values that
Chris@275 251 // exceed the median. For pitch adaptivity, we adjust the window
Chris@275 252 // size to a roughly constant pitch range (about four tones).
Chris@275 253
Chris@275 254 size_t sampleRate = getSampleRate();
Chris@275 255
Chris@275 256 std::deque<float> window;
Chris@275 257 std::vector<size_t> inrange;
Chris@280 258 float dist = 0.5;
Chris@280 259 size_t medianWinSize = getPeakPickWindowSize(type, sampleRate, ymin, dist);
Chris@275 260 size_t halfWin = medianWinSize/2;
Chris@275 261
Chris@275 262 size_t binmin;
Chris@275 263 if (ymin > halfWin) binmin = ymin - halfWin;
Chris@275 264 else binmin = 0;
Chris@275 265
Chris@275 266 size_t binmax;
Chris@275 267 if (ymax + halfWin < values.size()) binmax = ymax + halfWin;
Chris@275 268 else binmax = values.size()-1;
Chris@275 269
Chris@275 270 for (size_t bin = binmin; bin <= binmax; ++bin) {
Chris@275 271
Chris@275 272 float value = values[bin];
Chris@275 273
Chris@275 274 window.push_back(value);
Chris@275 275
Chris@280 276 // so-called median will actually be the dist*100'th percentile
Chris@280 277 medianWinSize = getPeakPickWindowSize(type, sampleRate, bin, dist);
Chris@275 278 halfWin = medianWinSize/2;
Chris@275 279
Chris@275 280 while (window.size() > medianWinSize) window.pop_front();
Chris@275 281
Chris@275 282 if (type == MajorPitchAdaptivePeaks) {
Chris@275 283 if (ymax + halfWin < values.size()) binmax = ymax + halfWin;
Chris@275 284 else binmax = values.size()-1;
Chris@275 285 }
Chris@275 286
Chris@275 287 std::deque<float> sorted(window);
Chris@275 288 std::sort(sorted.begin(), sorted.end());
Chris@280 289 float median = sorted[int(sorted.size() * dist)];
Chris@275 290
Chris@275 291 if (value > median) {
Chris@275 292 inrange.push_back(bin);
Chris@275 293 }
Chris@275 294
Chris@275 295 if (value <= median || bin+1 == values.size()) {
Chris@275 296 size_t peakbin = 0;
Chris@275 297 float peakval = 0.f;
Chris@275 298 if (!inrange.empty()) {
Chris@275 299 for (size_t i = 0; i < inrange.size(); ++i) {
Chris@275 300 if (i == 0 || values[inrange[i]] > peakval) {
Chris@275 301 peakval = values[inrange[i]];
Chris@275 302 peakbin = inrange[i];
Chris@275 303 }
Chris@275 304 }
Chris@275 305 inrange.clear();
Chris@275 306 if (peakbin >= ymin && peakbin <= ymax) {
Chris@275 307 peaks.insert(peakbin);
Chris@275 308 }
Chris@275 309 }
Chris@275 310 }
Chris@275 311 }
Chris@275 312
Chris@275 313 return peaks;
Chris@275 314 }
Chris@275 315
Chris@275 316 size_t
Chris@280 317 FFTModel::getPeakPickWindowSize(PeakPickType type, size_t sampleRate,
Chris@280 318 size_t bin, float &percentile) const
Chris@275 319 {
Chris@280 320 percentile = 0.5;
Chris@275 321 if (type == MajorPeaks) return 10;
Chris@275 322 if (bin == 0) return 3;
Chris@280 323
Chris@275 324 size_t fftSize = m_server->getFFTSize() >> m_yshift;
Chris@275 325 float binfreq = (sampleRate * bin) / fftSize;
Chris@275 326 float hifreq = Pitch::getFrequencyForPitch(73, 0, binfreq);
Chris@280 327
Chris@275 328 int hibin = lrintf((hifreq * fftSize) / sampleRate);
Chris@275 329 int medianWinSize = hibin - bin;
Chris@275 330 if (medianWinSize < 3) medianWinSize = 3;
Chris@280 331
Chris@280 332 percentile = 0.5 + (binfreq / sampleRate);
Chris@280 333
Chris@275 334 return medianWinSize;
Chris@275 335 }
Chris@275 336
Chris@275 337 FFTModel::PeakSet
Chris@275 338 FFTModel::getPeakFrequencies(PeakPickType type, size_t x,
Chris@275 339 size_t ymin, size_t ymax)
Chris@275 340 {
Chris@275 341 PeakSet peaks;
Chris@275 342 if (!isOK()) return peaks;
Chris@275 343 PeakLocationSet locations = getPeaks(type, x, ymin, ymax);
Chris@275 344
Chris@275 345 size_t sampleRate = getSampleRate();
Chris@275 346 size_t fftSize = m_server->getFFTSize() >> m_yshift;
Chris@275 347 size_t incr = getResolution();
Chris@275 348
Chris@275 349 // This duplicates some of the work of estimateStableFrequency to
Chris@275 350 // allow us to retrieve the phases in two separate vertical
Chris@275 351 // columns, instead of jumping back and forth between columns x and
Chris@275 352 // x+1, which may be significantly slower if re-seeking is needed
Chris@275 353
Chris@275 354 std::vector<float> phases;
Chris@275 355 for (PeakLocationSet::iterator i = locations.begin();
Chris@275 356 i != locations.end(); ++i) {
Chris@275 357 phases.push_back(getPhaseAt(x, *i));
Chris@275 358 }
Chris@275 359
Chris@275 360 size_t phaseIndex = 0;
Chris@275 361 for (PeakLocationSet::iterator i = locations.begin();
Chris@275 362 i != locations.end(); ++i) {
Chris@275 363 float oldPhase = phases[phaseIndex];
Chris@275 364 float newPhase = getPhaseAt(x+1, *i);
Chris@275 365 float expectedPhase = oldPhase + (2.0 * M_PI * *i * incr) / fftSize;
Chris@275 366 float phaseError = princargf(newPhase - expectedPhase);
Chris@275 367 float frequency =
Chris@275 368 (sampleRate * (expectedPhase + phaseError - oldPhase))
Chris@275 369 / (2 * M_PI * incr);
Chris@275 370 // bool stable = (fabsf(phaseError) < (1.1f * (incr * M_PI) / fftSize));
Chris@275 371 // if (stable)
Chris@275 372 peaks[*i] = frequency;
Chris@275 373 ++phaseIndex;
Chris@275 374 }
Chris@275 375
Chris@275 376 return peaks;
Chris@275 377 }
Chris@275 378
Chris@152 379 Model *
Chris@152 380 FFTModel::clone() const
Chris@152 381 {
Chris@152 382 return new FFTModel(*this);
Chris@152 383 }
Chris@152 384
Chris@152 385 FFTModel::FFTModel(const FFTModel &model) :
Chris@152 386 DenseThreeDimensionalModel(),
Chris@152 387 m_server(model.m_server),
Chris@152 388 m_xshift(model.m_xshift),
Chris@152 389 m_yshift(model.m_yshift)
Chris@152 390 {
Chris@152 391 FFTDataServer::claimInstance(m_server);
Chris@152 392 }
Chris@152 393