annotate data/model/FFTModel.cpp @ 294:2c1e57ad86e7

* Show colour swatch next to layer name in pane (if available) * Fix for incorrect layer name prefix handling (was making some layers appear to have the same model name in cases where the model names differed by the final character only)
author Chris Cannam
date Wed, 05 Sep 2007 15:17:15 +0000
parents daf89d31f45c
children c022976d18e8
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@183 21
Chris@152 22 #include <cassert>
Chris@152 23
Chris@152 24 FFTModel::FFTModel(const DenseTimeValueModel *model,
Chris@152 25 int channel,
Chris@152 26 WindowType windowType,
Chris@152 27 size_t windowSize,
Chris@152 28 size_t windowIncrement,
Chris@152 29 size_t fftSize,
Chris@152 30 bool polar,
Chris@152 31 size_t fillFromColumn) :
Chris@152 32 //!!! ZoomConstraint!
Chris@152 33 m_server(0),
Chris@152 34 m_xshift(0),
Chris@152 35 m_yshift(0)
Chris@152 36 {
Chris@152 37 m_server = FFTDataServer::getFuzzyInstance(model,
Chris@152 38 channel,
Chris@152 39 windowType,
Chris@152 40 windowSize,
Chris@152 41 windowIncrement,
Chris@152 42 fftSize,
Chris@152 43 polar,
Chris@152 44 fillFromColumn);
Chris@152 45
Chris@200 46 if (!m_server) return; // caller should check isOK()
Chris@200 47
Chris@152 48 size_t xratio = windowIncrement / m_server->getWindowIncrement();
Chris@152 49 size_t yratio = m_server->getFFTSize() / fftSize;
Chris@152 50
Chris@152 51 while (xratio > 1) {
Chris@152 52 if (xratio & 0x1) {
Chris@152 53 std::cerr << "ERROR: FFTModel: Window increment ratio "
Chris@152 54 << windowIncrement << " / "
Chris@152 55 << m_server->getWindowIncrement()
Chris@152 56 << " must be a power of two" << std::endl;
Chris@152 57 assert(!(xratio & 0x1));
Chris@152 58 }
Chris@152 59 ++m_xshift;
Chris@152 60 xratio >>= 1;
Chris@152 61 }
Chris@152 62
Chris@152 63 while (yratio > 1) {
Chris@152 64 if (yratio & 0x1) {
Chris@152 65 std::cerr << "ERROR: FFTModel: FFT size ratio "
Chris@152 66 << m_server->getFFTSize() << " / " << fftSize
Chris@152 67 << " must be a power of two" << std::endl;
Chris@152 68 assert(!(yratio & 0x1));
Chris@152 69 }
Chris@152 70 ++m_yshift;
Chris@152 71 yratio >>= 1;
Chris@152 72 }
Chris@152 73 }
Chris@152 74
Chris@152 75 FFTModel::~FFTModel()
Chris@152 76 {
Chris@200 77 if (m_server) FFTDataServer::releaseInstance(m_server);
Chris@152 78 }
Chris@152 79
Chris@152 80 size_t
Chris@152 81 FFTModel::getSampleRate() const
Chris@152 82 {
Chris@152 83 return isOK() ? m_server->getModel()->getSampleRate() : 0;
Chris@152 84 }
Chris@152 85
Chris@152 86 void
Chris@182 87 FFTModel::getColumn(size_t x, Column &result) const
Chris@152 88 {
Chris@183 89 Profiler profiler("FFTModel::getColumn", false);
Chris@183 90
Chris@152 91 result.clear();
Chris@152 92 size_t height(getHeight());
Chris@152 93 for (size_t y = 0; y < height; ++y) {
Chris@152 94 result.push_back(const_cast<FFTModel *>(this)->getMagnitudeAt(x, y));
Chris@152 95 }
Chris@152 96 }
Chris@152 97
Chris@152 98 QString
Chris@152 99 FFTModel::getBinName(size_t n) const
Chris@152 100 {
Chris@152 101 size_t sr = getSampleRate();
Chris@152 102 if (!sr) return "";
Chris@204 103 QString name = tr("%1 Hz").arg((n * sr) / ((getHeight()-1) * 2));
Chris@152 104 return name;
Chris@152 105 }
Chris@152 106
Chris@275 107 bool
Chris@275 108 FFTModel::estimateStableFrequency(size_t x, size_t y, float &frequency)
Chris@275 109 {
Chris@275 110 if (!isOK()) return false;
Chris@275 111
Chris@275 112 size_t sampleRate = m_server->getModel()->getSampleRate();
Chris@275 113
Chris@275 114 size_t fftSize = m_server->getFFTSize() >> m_yshift;
Chris@275 115 frequency = (float(y) * sampleRate) / fftSize;
Chris@275 116
Chris@275 117 if (x+1 >= getWidth()) return false;
Chris@275 118
Chris@275 119 // At frequency f, a phase shift of 2pi (one cycle) happens in 1/f sec.
Chris@275 120 // At hopsize h and sample rate sr, one hop happens in h/sr sec.
Chris@275 121 // At window size w, for bin b, f is b*sr/w.
Chris@275 122 // thus 2pi phase shift happens in w/(b*sr) sec.
Chris@275 123 // We need to know what phase shift we expect from h/sr sec.
Chris@275 124 // -> 2pi * ((h/sr) / (w/(b*sr)))
Chris@275 125 // = 2pi * ((h * b * sr) / (w * sr))
Chris@275 126 // = 2pi * (h * b) / w.
Chris@275 127
Chris@275 128 float oldPhase = getPhaseAt(x, y);
Chris@275 129 float newPhase = getPhaseAt(x+1, y);
Chris@275 130
Chris@275 131 size_t incr = getResolution();
Chris@275 132
Chris@275 133 float expectedPhase = oldPhase + (2.0 * M_PI * y * incr) / fftSize;
Chris@275 134
Chris@275 135 float phaseError = princargf(newPhase - expectedPhase);
Chris@275 136
Chris@275 137 // bool stable = (fabsf(phaseError) < (1.1f * (m_windowIncrement * M_PI) / m_fftSize));
Chris@275 138
Chris@275 139 // The new frequency estimate based on the phase error resulting
Chris@275 140 // from assuming the "native" frequency of this bin
Chris@275 141
Chris@275 142 frequency =
Chris@275 143 (sampleRate * (expectedPhase + phaseError - oldPhase)) /
Chris@275 144 (2 * M_PI * incr);
Chris@275 145
Chris@275 146 return true;
Chris@275 147 }
Chris@275 148
Chris@275 149 FFTModel::PeakLocationSet
Chris@275 150 FFTModel::getPeaks(PeakPickType type, size_t x, size_t ymin, size_t ymax)
Chris@275 151 {
Chris@275 152 FFTModel::PeakLocationSet peaks;
Chris@275 153 if (!isOK()) return peaks;
Chris@275 154
Chris@275 155 if (ymax == 0 || ymax > getHeight() - 1) {
Chris@275 156 ymax = getHeight() - 1;
Chris@275 157 }
Chris@275 158
Chris@275 159 Column values;
Chris@275 160
Chris@275 161 if (type == AllPeaks) {
Chris@275 162 for (size_t y = ymin; y <= ymax; ++y) {
Chris@275 163 values.push_back(getMagnitudeAt(x, y));
Chris@275 164 }
Chris@275 165 size_t i = 0;
Chris@275 166 for (size_t bin = ymin; bin <= ymax; ++bin) {
Chris@275 167 if ((i == 0 || values[i] > values[i-1]) &&
Chris@275 168 (i == values.size()-1 || values[i] >= values[i+1])) {
Chris@275 169 peaks.insert(bin);
Chris@275 170 }
Chris@275 171 ++i;
Chris@275 172 }
Chris@275 173 return peaks;
Chris@275 174 }
Chris@275 175
Chris@275 176 getColumn(x, values);
Chris@275 177
Chris@275 178 // For peak picking we use a moving median window, picking the
Chris@275 179 // highest value within each continuous region of values that
Chris@275 180 // exceed the median. For pitch adaptivity, we adjust the window
Chris@275 181 // size to a roughly constant pitch range (about four tones).
Chris@275 182
Chris@275 183 size_t sampleRate = getSampleRate();
Chris@275 184
Chris@275 185 std::deque<float> window;
Chris@275 186 std::vector<size_t> inrange;
Chris@280 187 float dist = 0.5;
Chris@280 188 size_t medianWinSize = getPeakPickWindowSize(type, sampleRate, ymin, dist);
Chris@275 189 size_t halfWin = medianWinSize/2;
Chris@275 190
Chris@275 191 size_t binmin;
Chris@275 192 if (ymin > halfWin) binmin = ymin - halfWin;
Chris@275 193 else binmin = 0;
Chris@275 194
Chris@275 195 size_t binmax;
Chris@275 196 if (ymax + halfWin < values.size()) binmax = ymax + halfWin;
Chris@275 197 else binmax = values.size()-1;
Chris@275 198
Chris@275 199 for (size_t bin = binmin; bin <= binmax; ++bin) {
Chris@275 200
Chris@275 201 float value = values[bin];
Chris@275 202
Chris@275 203 window.push_back(value);
Chris@275 204
Chris@280 205 // so-called median will actually be the dist*100'th percentile
Chris@280 206 medianWinSize = getPeakPickWindowSize(type, sampleRate, bin, dist);
Chris@275 207 halfWin = medianWinSize/2;
Chris@275 208
Chris@275 209 while (window.size() > medianWinSize) window.pop_front();
Chris@275 210
Chris@275 211 if (type == MajorPitchAdaptivePeaks) {
Chris@275 212 if (ymax + halfWin < values.size()) binmax = ymax + halfWin;
Chris@275 213 else binmax = values.size()-1;
Chris@275 214 }
Chris@275 215
Chris@275 216 std::deque<float> sorted(window);
Chris@275 217 std::sort(sorted.begin(), sorted.end());
Chris@280 218 float median = sorted[int(sorted.size() * dist)];
Chris@275 219
Chris@275 220 if (value > median) {
Chris@275 221 inrange.push_back(bin);
Chris@275 222 }
Chris@275 223
Chris@275 224 if (value <= median || bin+1 == values.size()) {
Chris@275 225 size_t peakbin = 0;
Chris@275 226 float peakval = 0.f;
Chris@275 227 if (!inrange.empty()) {
Chris@275 228 for (size_t i = 0; i < inrange.size(); ++i) {
Chris@275 229 if (i == 0 || values[inrange[i]] > peakval) {
Chris@275 230 peakval = values[inrange[i]];
Chris@275 231 peakbin = inrange[i];
Chris@275 232 }
Chris@275 233 }
Chris@275 234 inrange.clear();
Chris@275 235 if (peakbin >= ymin && peakbin <= ymax) {
Chris@275 236 peaks.insert(peakbin);
Chris@275 237 }
Chris@275 238 }
Chris@275 239 }
Chris@275 240 }
Chris@275 241
Chris@275 242 return peaks;
Chris@275 243 }
Chris@275 244
Chris@275 245 size_t
Chris@280 246 FFTModel::getPeakPickWindowSize(PeakPickType type, size_t sampleRate,
Chris@280 247 size_t bin, float &percentile) const
Chris@275 248 {
Chris@280 249 percentile = 0.5;
Chris@275 250 if (type == MajorPeaks) return 10;
Chris@275 251 if (bin == 0) return 3;
Chris@280 252
Chris@275 253 size_t fftSize = m_server->getFFTSize() >> m_yshift;
Chris@275 254 float binfreq = (sampleRate * bin) / fftSize;
Chris@275 255 float hifreq = Pitch::getFrequencyForPitch(73, 0, binfreq);
Chris@280 256
Chris@275 257 int hibin = lrintf((hifreq * fftSize) / sampleRate);
Chris@275 258 int medianWinSize = hibin - bin;
Chris@275 259 if (medianWinSize < 3) medianWinSize = 3;
Chris@280 260
Chris@280 261 percentile = 0.5 + (binfreq / sampleRate);
Chris@280 262
Chris@275 263 return medianWinSize;
Chris@275 264 }
Chris@275 265
Chris@275 266 FFTModel::PeakSet
Chris@275 267 FFTModel::getPeakFrequencies(PeakPickType type, size_t x,
Chris@275 268 size_t ymin, size_t ymax)
Chris@275 269 {
Chris@275 270 PeakSet peaks;
Chris@275 271 if (!isOK()) return peaks;
Chris@275 272 PeakLocationSet locations = getPeaks(type, x, ymin, ymax);
Chris@275 273
Chris@275 274 size_t sampleRate = getSampleRate();
Chris@275 275 size_t fftSize = m_server->getFFTSize() >> m_yshift;
Chris@275 276 size_t incr = getResolution();
Chris@275 277
Chris@275 278 // This duplicates some of the work of estimateStableFrequency to
Chris@275 279 // allow us to retrieve the phases in two separate vertical
Chris@275 280 // columns, instead of jumping back and forth between columns x and
Chris@275 281 // x+1, which may be significantly slower if re-seeking is needed
Chris@275 282
Chris@275 283 std::vector<float> phases;
Chris@275 284 for (PeakLocationSet::iterator i = locations.begin();
Chris@275 285 i != locations.end(); ++i) {
Chris@275 286 phases.push_back(getPhaseAt(x, *i));
Chris@275 287 }
Chris@275 288
Chris@275 289 size_t phaseIndex = 0;
Chris@275 290 for (PeakLocationSet::iterator i = locations.begin();
Chris@275 291 i != locations.end(); ++i) {
Chris@275 292 float oldPhase = phases[phaseIndex];
Chris@275 293 float newPhase = getPhaseAt(x+1, *i);
Chris@275 294 float expectedPhase = oldPhase + (2.0 * M_PI * *i * incr) / fftSize;
Chris@275 295 float phaseError = princargf(newPhase - expectedPhase);
Chris@275 296 float frequency =
Chris@275 297 (sampleRate * (expectedPhase + phaseError - oldPhase))
Chris@275 298 / (2 * M_PI * incr);
Chris@275 299 // bool stable = (fabsf(phaseError) < (1.1f * (incr * M_PI) / fftSize));
Chris@275 300 // if (stable)
Chris@275 301 peaks[*i] = frequency;
Chris@275 302 ++phaseIndex;
Chris@275 303 }
Chris@275 304
Chris@275 305 return peaks;
Chris@275 306 }
Chris@275 307
Chris@152 308 Model *
Chris@152 309 FFTModel::clone() const
Chris@152 310 {
Chris@152 311 return new FFTModel(*this);
Chris@152 312 }
Chris@152 313
Chris@152 314 FFTModel::FFTModel(const FFTModel &model) :
Chris@152 315 DenseThreeDimensionalModel(),
Chris@152 316 m_server(model.m_server),
Chris@152 317 m_xshift(model.m_xshift),
Chris@152 318 m_yshift(model.m_yshift)
Chris@152 319 {
Chris@152 320 FFTDataServer::claimInstance(m_server);
Chris@152 321 }
Chris@152 322