annotate layer/SpectrumLayer.cpp @ 333:e74b56f07c73

* Some work on correct alignment when moving panes during playback * Overhaul alignment for playback frame values (view manager now always refers to reference-timeline values, only the play source deals in playback model timeline values) * When making a selection, ensure the selection regions shown in other panes (and used for playback constraints if appropriate) are aligned correctly. This may be the coolest feature ever implemented in any program ever.
author Chris Cannam
date Thu, 22 Nov 2007 14:17:19 +0000
parents 29fcf125f98b
children 2f83b6e3b8ca 64e84e5efb76
rev   line source
Chris@133 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@133 2
Chris@133 3 /*
Chris@133 4 Sonic Visualiser
Chris@133 5 An audio file viewer and annotation editor.
Chris@133 6 Centre for Digital Music, Queen Mary, University of London.
Chris@195 7 This file copyright 2006-2007 QMUL.
Chris@133 8
Chris@133 9 This program is free software; you can redistribute it and/or
Chris@133 10 modify it under the terms of the GNU General Public License as
Chris@133 11 published by the Free Software Foundation; either version 2 of the
Chris@133 12 License, or (at your option) any later version. See the file
Chris@133 13 COPYING included with this distribution for more information.
Chris@133 14 */
Chris@133 15
Chris@133 16 #include "SpectrumLayer.h"
Chris@133 17
Chris@133 18 #include "data/model/FFTModel.h"
Chris@133 19 #include "view/View.h"
Chris@153 20 #include "base/AudioLevel.h"
Chris@153 21 #include "base/Preferences.h"
Chris@167 22 #include "base/RangeMapper.h"
Chris@277 23 #include "base/Pitch.h"
Chris@285 24 #include "base/ColourMapper.h"
Chris@254 25
Chris@254 26 #include <QPainter>
Chris@316 27 #include <QTextStream>
Chris@316 28
Chris@133 29
Chris@133 30 SpectrumLayer::SpectrumLayer() :
Chris@193 31 m_originModel(0),
Chris@153 32 m_channel(-1),
Chris@153 33 m_channelSet(false),
Chris@290 34 m_windowSize(4096),
Chris@153 35 m_windowType(HanningWindow),
Chris@290 36 m_windowHopLevel(3),
Chris@284 37 m_showPeaks(false),
Chris@275 38 m_newFFTNeeded(true)
Chris@133 39 {
Chris@153 40 Preferences *prefs = Preferences::getInstance();
Chris@153 41 connect(prefs, SIGNAL(propertyChanged(PropertyContainer::PropertyName)),
Chris@153 42 this, SLOT(preferenceChanged(PropertyContainer::PropertyName)));
Chris@153 43 setWindowType(prefs->getWindowType());
Chris@195 44
Chris@195 45 setBinScale(LogBins);
Chris@133 46 }
Chris@133 47
Chris@133 48 SpectrumLayer::~SpectrumLayer()
Chris@133 49 {
Chris@193 50 //!!! delete parent's model
Chris@193 51 // for (size_t i = 0; i < m_fft.size(); ++i) delete m_fft[i];
Chris@133 52 }
Chris@133 53
Chris@133 54 void
Chris@133 55 SpectrumLayer::setModel(DenseTimeValueModel *model)
Chris@133 56 {
Chris@193 57 if (m_originModel == model) return;
Chris@193 58 m_originModel = model;
Chris@277 59
Chris@277 60 if (m_sliceableModel) {
Chris@277 61 const Model *oldModel = m_sliceableModel;
Chris@277 62 setSliceableModel(0);
Chris@277 63 // surprised I'm allowed to delete a const pointer -- may be a
Chris@277 64 // source of future compiler rejection?
Chris@277 65 delete oldModel;
Chris@277 66 }
Chris@275 67 //!!! setupFFT();
Chris@153 68 }
Chris@153 69
Chris@153 70 void
Chris@193 71 SpectrumLayer::setupFFT()
Chris@153 72 {
Chris@193 73 FFTModel *oldFFT = dynamic_cast<FFTModel *>
Chris@193 74 (const_cast<DenseThreeDimensionalModel *>(m_sliceableModel));
Chris@153 75
Chris@193 76 if (oldFFT) {
Chris@193 77 setSliceableModel(0);
Chris@193 78 delete oldFFT;
Chris@153 79 }
Chris@153 80
Chris@193 81 FFTModel *newFFT = new FFTModel(m_originModel,
Chris@193 82 m_channel,
Chris@193 83 m_windowType,
Chris@193 84 m_windowSize,
Chris@193 85 getWindowIncrement(),
Chris@193 86 m_windowSize,
Chris@327 87 false,
Chris@327 88 StorageAdviser::Criteria
Chris@327 89 (StorageAdviser::SpeedCritical |
Chris@327 90 StorageAdviser::FrequentLookupLikely));
Chris@153 91
Chris@193 92 setSliceableModel(newFFT);
Chris@193 93
Chris@254 94 m_biasCurve.clear();
Chris@254 95 for (size_t i = 0; i < m_windowSize; ++i) {
Chris@254 96 m_biasCurve.push_back(1.f / (float(m_windowSize)/2.f));
Chris@254 97 }
Chris@254 98
Chris@193 99 newFFT->resume();
Chris@153 100 }
Chris@153 101
Chris@153 102 void
Chris@153 103 SpectrumLayer::setChannel(int channel)
Chris@153 104 {
Chris@153 105 m_channelSet = true;
Chris@153 106
Chris@193 107 FFTModel *fft = dynamic_cast<FFTModel *>
Chris@193 108 (const_cast<DenseThreeDimensionalModel *>(m_sliceableModel));
Chris@193 109
Chris@153 110 if (m_channel == channel) {
Chris@193 111 if (fft) fft->resume();
Chris@153 112 return;
Chris@153 113 }
Chris@153 114
Chris@153 115 m_channel = channel;
Chris@153 116
Chris@153 117 emit layerParametersChanged();
Chris@133 118 }
Chris@133 119
Chris@153 120 Layer::PropertyList
Chris@153 121 SpectrumLayer::getProperties() const
Chris@153 122 {
Chris@193 123 PropertyList list = SliceLayer::getProperties();
Chris@153 124 list.push_back("Window Size");
Chris@153 125 list.push_back("Window Increment");
Chris@284 126 list.push_back("Show Peak Frequencies");
Chris@153 127 return list;
Chris@153 128 }
Chris@153 129
Chris@153 130 QString
Chris@153 131 SpectrumLayer::getPropertyLabel(const PropertyName &name) const
Chris@153 132 {
Chris@153 133 if (name == "Window Size") return tr("Window Size");
Chris@153 134 if (name == "Window Increment") return tr("Window Overlap");
Chris@284 135 if (name == "Show Peak Frequencies") return tr("Show Peak Frequencies");
Chris@193 136 return SliceLayer::getPropertyLabel(name);
Chris@153 137 }
Chris@153 138
Chris@153 139 Layer::PropertyType
Chris@153 140 SpectrumLayer::getPropertyType(const PropertyName &name) const
Chris@153 141 {
Chris@193 142 if (name == "Window Size") return ValueProperty;
Chris@193 143 if (name == "Window Increment") return ValueProperty;
Chris@284 144 if (name == "Show Peak Frequencies") return ToggleProperty;
Chris@193 145 return SliceLayer::getPropertyType(name);
Chris@153 146 }
Chris@153 147
Chris@153 148 QString
Chris@153 149 SpectrumLayer::getPropertyGroupName(const PropertyName &name) const
Chris@153 150 {
Chris@153 151 if (name == "Window Size" ||
Chris@153 152 name == "Window Increment") return tr("Window");
Chris@284 153 if (name == "Show Peak Frequencies") return tr("Plot Type");
Chris@193 154 return SliceLayer::getPropertyGroupName(name);
Chris@153 155 }
Chris@153 156
Chris@153 157 int
Chris@153 158 SpectrumLayer::getPropertyRangeAndValue(const PropertyName &name,
Chris@216 159 int *min, int *max, int *deflt) const
Chris@153 160 {
Chris@216 161 int val = 0;
Chris@153 162
Chris@216 163 int garbage0, garbage1, garbage2;
Chris@153 164 if (!min) min = &garbage0;
Chris@153 165 if (!max) max = &garbage1;
Chris@216 166 if (!deflt) deflt = &garbage2;
Chris@153 167
Chris@193 168 if (name == "Window Size") {
Chris@153 169
Chris@153 170 *min = 0;
Chris@254 171 *max = 15;
Chris@216 172 *deflt = 5;
Chris@153 173
Chris@216 174 val = 0;
Chris@153 175 int ws = m_windowSize;
Chris@216 176 while (ws > 32) { ws >>= 1; val ++; }
Chris@153 177
Chris@153 178 } else if (name == "Window Increment") {
Chris@153 179
Chris@153 180 *min = 0;
Chris@153 181 *max = 5;
Chris@216 182 *deflt = 2;
Chris@153 183
Chris@216 184 val = m_windowHopLevel;
Chris@153 185
Chris@284 186 } else if (name == "Show Peak Frequencies") {
Chris@284 187
Chris@284 188 return m_showPeaks ? 1 : 0;
Chris@284 189
Chris@153 190 } else {
Chris@193 191
Chris@216 192 val = SliceLayer::getPropertyRangeAndValue(name, min, max, deflt);
Chris@153 193 }
Chris@153 194
Chris@216 195 return val;
Chris@153 196 }
Chris@153 197
Chris@153 198 QString
Chris@153 199 SpectrumLayer::getPropertyValueLabel(const PropertyName &name,
Chris@153 200 int value) const
Chris@153 201 {
Chris@153 202 if (name == "Window Size") {
Chris@153 203 return QString("%1").arg(32 << value);
Chris@153 204 }
Chris@153 205 if (name == "Window Increment") {
Chris@153 206 switch (value) {
Chris@153 207 default:
Chris@153 208 case 0: return tr("None");
Chris@153 209 case 1: return tr("25 %");
Chris@153 210 case 2: return tr("50 %");
Chris@153 211 case 3: return tr("75 %");
Chris@153 212 case 4: return tr("87.5 %");
Chris@153 213 case 5: return tr("93.75 %");
Chris@153 214 }
Chris@153 215 }
Chris@193 216 return SliceLayer::getPropertyValueLabel(name, value);
Chris@153 217 }
Chris@153 218
Chris@167 219 RangeMapper *
Chris@167 220 SpectrumLayer::getNewPropertyRangeMapper(const PropertyName &name) const
Chris@167 221 {
Chris@193 222 return SliceLayer::getNewPropertyRangeMapper(name);
Chris@167 223 }
Chris@167 224
Chris@133 225 void
Chris@153 226 SpectrumLayer::setProperty(const PropertyName &name, int value)
Chris@133 227 {
Chris@193 228 if (name == "Window Size") {
Chris@153 229 setWindowSize(32 << value);
Chris@153 230 } else if (name == "Window Increment") {
Chris@153 231 setWindowHopLevel(value);
Chris@284 232 } else if (name == "Show Peak Frequencies") {
Chris@284 233 setShowPeaks(value ? true : false);
Chris@193 234 } else {
Chris@193 235 SliceLayer::setProperty(name, value);
Chris@153 236 }
Chris@153 237 }
Chris@153 238
Chris@153 239 void
Chris@153 240 SpectrumLayer::setWindowSize(size_t ws)
Chris@153 241 {
Chris@153 242 if (m_windowSize == ws) return;
Chris@153 243 m_windowSize = ws;
Chris@275 244 m_newFFTNeeded = true;
Chris@153 245 emit layerParametersChanged();
Chris@153 246 }
Chris@153 247
Chris@153 248 void
Chris@153 249 SpectrumLayer::setWindowHopLevel(size_t v)
Chris@153 250 {
Chris@153 251 if (m_windowHopLevel == v) return;
Chris@153 252 m_windowHopLevel = v;
Chris@275 253 m_newFFTNeeded = true;
Chris@153 254 emit layerParametersChanged();
Chris@153 255 }
Chris@153 256
Chris@153 257 void
Chris@153 258 SpectrumLayer::setWindowType(WindowType w)
Chris@153 259 {
Chris@153 260 if (m_windowType == w) return;
Chris@153 261 m_windowType = w;
Chris@275 262 m_newFFTNeeded = true;
Chris@153 263 emit layerParametersChanged();
Chris@153 264 }
Chris@153 265
Chris@153 266 void
Chris@284 267 SpectrumLayer::setShowPeaks(bool show)
Chris@284 268 {
Chris@284 269 if (m_showPeaks == show) return;
Chris@284 270 m_showPeaks = show;
Chris@284 271 emit layerParametersChanged();
Chris@284 272 }
Chris@284 273
Chris@284 274 void
Chris@153 275 SpectrumLayer::preferenceChanged(PropertyContainer::PropertyName name)
Chris@153 276 {
Chris@153 277 if (name == "Window Type") {
Chris@153 278 setWindowType(Preferences::getInstance()->getWindowType());
Chris@153 279 return;
Chris@153 280 }
Chris@153 281 }
Chris@153 282
Chris@133 283 bool
Chris@248 284 SpectrumLayer::getValueExtents(float &, float &, bool &, QString &) const
Chris@133 285 {
Chris@133 286 return false;
Chris@133 287 }
Chris@133 288
Chris@254 289 float
Chris@265 290 SpectrumLayer::getXForBin(int bin, int totalBins, float w) const
Chris@265 291 {
Chris@265 292 if (!m_sliceableModel) return SliceLayer::getXForBin(bin, totalBins, w);
Chris@265 293
Chris@265 294 float sampleRate = m_sliceableModel->getSampleRate();
Chris@265 295 float binfreq = (sampleRate * bin) / (totalBins * 2);
Chris@265 296
Chris@265 297 return getXForFrequency(binfreq, w);
Chris@265 298 }
Chris@265 299
Chris@265 300 int
Chris@265 301 SpectrumLayer::getBinForX(float x, int totalBins, float w) const
Chris@265 302 {
Chris@265 303 if (!m_sliceableModel) return SliceLayer::getBinForX(x, totalBins, w);
Chris@265 304
Chris@265 305 float sampleRate = m_sliceableModel->getSampleRate();
Chris@265 306 float binfreq = getFrequencyForX(x, w);
Chris@265 307
Chris@265 308 return int((binfreq * totalBins * 2) / sampleRate);
Chris@265 309 }
Chris@265 310
Chris@265 311 float
Chris@254 312 SpectrumLayer::getFrequencyForX(float x, float w) const
Chris@254 313 {
Chris@254 314 float freq = 0;
Chris@280 315 if (!m_sliceableModel) return 0;
Chris@254 316
Chris@254 317 int sampleRate = m_sliceableModel->getSampleRate();
Chris@254 318
Chris@254 319 float maxfreq = float(sampleRate) / 2;
Chris@254 320
Chris@254 321 switch (m_binScale) {
Chris@254 322
Chris@254 323 case LinearBins:
Chris@254 324 freq = ((x * maxfreq) / w);
Chris@254 325 break;
Chris@254 326
Chris@254 327 case LogBins:
Chris@254 328 freq = powf(10.f, (x * log10f(maxfreq)) / w);
Chris@254 329 break;
Chris@254 330
Chris@254 331 case InvertedLogBins:
Chris@254 332 freq = maxfreq - powf(10.f, ((w - x) * log10f(maxfreq)) / w);
Chris@254 333 break;
Chris@254 334 }
Chris@254 335
Chris@254 336 return freq;
Chris@254 337 }
Chris@254 338
Chris@254 339 float
Chris@254 340 SpectrumLayer::getXForFrequency(float freq, float w) const
Chris@254 341 {
Chris@254 342 float x = 0;
Chris@280 343 if (!m_sliceableModel) return x;
Chris@254 344
Chris@254 345 int sampleRate = m_sliceableModel->getSampleRate();
Chris@254 346
Chris@254 347 float maxfreq = float(sampleRate) / 2;
Chris@254 348
Chris@254 349 switch (m_binScale) {
Chris@254 350
Chris@254 351 case LinearBins:
Chris@254 352 x = (freq * w) / maxfreq;
Chris@254 353 break;
Chris@254 354
Chris@254 355 case LogBins:
Chris@254 356 x = (log10f(freq) * w) / log10f(maxfreq);
Chris@254 357 break;
Chris@254 358
Chris@254 359 case InvertedLogBins:
Chris@274 360 if (maxfreq == freq) x = w;
Chris@274 361 else x = w - (log10f(maxfreq - freq) * w) / log10f(maxfreq);
Chris@254 362 break;
Chris@254 363 }
Chris@254 364
Chris@254 365 return x;
Chris@254 366 }
Chris@254 367
Chris@260 368 bool
Chris@267 369 SpectrumLayer::getXScaleValue(const View *v, int x,
Chris@260 370 float &value, QString &unit) const
Chris@260 371 {
Chris@267 372 if (m_xorigins.find(v) == m_xorigins.end()) return false;
Chris@267 373 int xorigin = m_xorigins.find(v)->second;
Chris@267 374 value = getFrequencyForX(x - xorigin, v->width() - xorigin - 1);
Chris@260 375 unit = "Hz";
Chris@260 376 return true;
Chris@260 377 }
Chris@260 378
Chris@264 379 bool
Chris@274 380 SpectrumLayer::getYScaleValue(const View *v, int y,
Chris@274 381 float &value, QString &unit) const
Chris@274 382 {
Chris@274 383 value = getValueForY(y, v);
Chris@274 384
Chris@274 385 if (m_energyScale == dBScale || m_energyScale == MeterScale) {
Chris@274 386
Chris@274 387 if (value > 0.f) {
Chris@274 388 value = 10.f * log10f(value);
Chris@284 389 if (value < m_threshold) value = m_threshold;
Chris@284 390 } else value = m_threshold;
Chris@274 391
Chris@274 392 unit = "dBV";
Chris@274 393
Chris@274 394 } else {
Chris@274 395 unit = "V";
Chris@274 396 }
Chris@274 397
Chris@274 398 return true;
Chris@274 399 }
Chris@274 400
Chris@274 401 bool
Chris@274 402 SpectrumLayer::getYScaleDifference(const View *v, int y0, int y1,
Chris@274 403 float &diff, QString &unit) const
Chris@274 404 {
Chris@274 405 bool rv = SliceLayer::getYScaleDifference(v, y0, y1, diff, unit);
Chris@274 406 if (rv && (unit == "dBV")) unit = "dB";
Chris@274 407 return rv;
Chris@274 408 }
Chris@274 409
Chris@274 410
Chris@274 411 bool
Chris@264 412 SpectrumLayer::getCrosshairExtents(View *v, QPainter &paint,
Chris@264 413 QPoint cursorPos,
Chris@264 414 std::vector<QRect> &extents) const
Chris@264 415 {
Chris@264 416 QRect vertical(cursorPos.x(), cursorPos.y(), 1, v->height() - cursorPos.y());
Chris@264 417 extents.push_back(vertical);
Chris@264 418
Chris@264 419 QRect horizontal(0, cursorPos.y(), v->width(), 12);
Chris@264 420 extents.push_back(horizontal);
Chris@264 421
Chris@280 422 int hoffset = 2;
Chris@280 423 if (m_binScale == LogBins) hoffset = 13;
Chris@278 424
Chris@280 425 int sw = getVerticalScaleWidth(v, paint);
Chris@280 426
Chris@280 427 QRect value(sw, cursorPos.y() - paint.fontMetrics().ascent() - 2,
Chris@280 428 paint.fontMetrics().width("0.0000001 V") + 2,
Chris@264 429 paint.fontMetrics().height());
Chris@280 430 extents.push_back(value);
Chris@280 431
Chris@280 432 QRect log(sw, cursorPos.y() + 2,
Chris@280 433 paint.fontMetrics().width("-80.000 dBV") + 2,
Chris@280 434 paint.fontMetrics().height());
Chris@280 435 extents.push_back(log);
Chris@280 436
Chris@280 437 QRect freq(cursorPos.x(),
Chris@280 438 v->height() - paint.fontMetrics().height() - hoffset,
Chris@280 439 paint.fontMetrics().width("123456 Hz") + 2,
Chris@280 440 paint.fontMetrics().height());
Chris@280 441 extents.push_back(freq);
Chris@264 442
Chris@278 443 int w(paint.fontMetrics().width("C#10+50c") + 2);
Chris@278 444 QRect pitch(cursorPos.x() - w,
Chris@278 445 v->height() - paint.fontMetrics().height() - hoffset,
Chris@278 446 w,
Chris@278 447 paint.fontMetrics().height());
Chris@278 448 extents.push_back(pitch);
Chris@278 449
Chris@264 450 return true;
Chris@264 451 }
Chris@264 452
Chris@254 453 void
Chris@254 454 SpectrumLayer::paintCrosshairs(View *v, QPainter &paint,
Chris@254 455 QPoint cursorPos) const
Chris@254 456 {
Chris@280 457 if (!m_sliceableModel) return;
Chris@280 458
Chris@254 459 paint.save();
Chris@282 460 QFont fn = paint.font();
Chris@282 461 if (fn.pointSize() > 8) {
Chris@282 462 fn.setPointSize(fn.pointSize() - 1);
Chris@282 463 paint.setFont(fn);
Chris@282 464 }
Chris@254 465
Chris@254 466 ColourMapper mapper(m_colourMap, 0, 1);
Chris@254 467 paint.setPen(mapper.getContrastingColour());
Chris@254 468
Chris@254 469 int xorigin = m_xorigins[v];
Chris@254 470 int w = v->width() - xorigin - 1;
Chris@254 471
Chris@254 472 paint.drawLine(xorigin, cursorPos.y(), v->width(), cursorPos.y());
Chris@254 473 paint.drawLine(cursorPos.x(), cursorPos.y(), cursorPos.x(), v->height());
Chris@254 474
Chris@254 475 float fundamental = getFrequencyForX(cursorPos.x() - xorigin, w);
Chris@254 476
Chris@280 477 int hoffset = 2;
Chris@280 478 if (m_binScale == LogBins) hoffset = 13;
Chris@278 479
Chris@278 480 v->drawVisibleText(paint,
Chris@278 481 cursorPos.x() + 2,
Chris@278 482 v->height() - 2 - hoffset,
Chris@278 483 QString("%1 Hz").arg(fundamental),
Chris@278 484 View::OutlinedText);
Chris@278 485
Chris@278 486 if (Pitch::isFrequencyInMidiRange(fundamental)) {
Chris@278 487 QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental);
Chris@278 488 v->drawVisibleText(paint,
Chris@278 489 cursorPos.x() - paint.fontMetrics().width(pitchLabel) - 2,
Chris@278 490 v->height() - 2 - hoffset,
Chris@278 491 pitchLabel,
Chris@278 492 View::OutlinedText);
Chris@278 493 }
Chris@264 494
Chris@280 495 float value = getValueForY(cursorPos.y(), v);
Chris@284 496 float thresh = m_threshold;
Chris@280 497 float db = thresh;
Chris@280 498 if (value > 0.f) db = 10.f * log10f(value);
Chris@280 499 if (db < thresh) db = thresh;
Chris@280 500
Chris@280 501 v->drawVisibleText(paint,
Chris@280 502 xorigin + 2,
Chris@280 503 cursorPos.y() - 2,
Chris@280 504 QString("%1 V").arg(value),
Chris@280 505 View::OutlinedText);
Chris@280 506
Chris@280 507 v->drawVisibleText(paint,
Chris@280 508 xorigin + 2,
Chris@280 509 cursorPos.y() + 2 + paint.fontMetrics().ascent(),
Chris@280 510 QString("%1 dBV").arg(db),
Chris@280 511 View::OutlinedText);
Chris@280 512
Chris@254 513 int harmonic = 2;
Chris@254 514
Chris@254 515 while (harmonic < 100) {
Chris@254 516
Chris@254 517 float hx = lrintf(getXForFrequency(fundamental * harmonic, w));
Chris@254 518 hx += xorigin;
Chris@254 519
Chris@254 520 if (hx < xorigin || hx > v->width()) break;
Chris@254 521
Chris@254 522 int len = 7;
Chris@254 523
Chris@254 524 if (harmonic % 2 == 0) {
Chris@254 525 if (harmonic % 4 == 0) {
Chris@254 526 len = 12;
Chris@254 527 } else {
Chris@254 528 len = 10;
Chris@254 529 }
Chris@254 530 }
Chris@254 531
Chris@254 532 paint.drawLine(int(hx),
Chris@254 533 cursorPos.y(),
Chris@254 534 int(hx),
Chris@254 535 cursorPos.y() + len);
Chris@254 536
Chris@254 537 ++harmonic;
Chris@254 538 }
Chris@254 539
Chris@254 540 paint.restore();
Chris@254 541 }
Chris@254 542
Chris@199 543 QString
Chris@199 544 SpectrumLayer::getFeatureDescription(View *v, QPoint &p) const
Chris@199 545 {
Chris@199 546 if (!m_sliceableModel) return "";
Chris@199 547
Chris@199 548 int minbin = 0, maxbin = 0, range = 0;
Chris@199 549 QString genericDesc = SliceLayer::getFeatureDescription
Chris@199 550 (v, p, false, minbin, maxbin, range);
Chris@199 551
Chris@199 552 if (genericDesc == "") return "";
Chris@199 553
Chris@199 554 float minvalue = 0.f;
Chris@248 555 if (minbin < int(m_values.size())) minvalue = m_values[minbin];
Chris@199 556
Chris@199 557 float maxvalue = minvalue;
Chris@248 558 if (maxbin < int(m_values.size())) maxvalue = m_values[maxbin];
Chris@199 559
Chris@199 560 if (minvalue > maxvalue) std::swap(minvalue, maxvalue);
Chris@199 561
Chris@199 562 QString binstr;
Chris@199 563 QString hzstr;
Chris@199 564 int minfreq = lrintf((minbin * m_sliceableModel->getSampleRate()) /
Chris@199 565 m_windowSize);
Chris@199 566 int maxfreq = lrintf((std::max(maxbin, minbin+1)
Chris@199 567 * m_sliceableModel->getSampleRate()) /
Chris@199 568 m_windowSize);
Chris@199 569
Chris@199 570 if (maxbin != minbin) {
Chris@199 571 binstr = tr("%1 - %2").arg(minbin+1).arg(maxbin+1);
Chris@199 572 } else {
Chris@199 573 binstr = QString("%1").arg(minbin+1);
Chris@199 574 }
Chris@199 575 if (minfreq != maxfreq) {
Chris@199 576 hzstr = tr("%1 - %2 Hz").arg(minfreq).arg(maxfreq);
Chris@199 577 } else {
Chris@199 578 hzstr = tr("%1 Hz").arg(minfreq);
Chris@199 579 }
Chris@199 580
Chris@199 581 QString valuestr;
Chris@199 582 if (maxvalue != minvalue) {
Chris@199 583 valuestr = tr("%1 - %2").arg(minvalue).arg(maxvalue);
Chris@199 584 } else {
Chris@199 585 valuestr = QString("%1").arg(minvalue);
Chris@199 586 }
Chris@199 587
Chris@199 588 QString dbstr;
Chris@199 589 float mindb = AudioLevel::multiplier_to_dB(minvalue);
Chris@199 590 float maxdb = AudioLevel::multiplier_to_dB(maxvalue);
Chris@199 591 QString mindbstr;
Chris@199 592 QString maxdbstr;
Chris@199 593 if (mindb == AudioLevel::DB_FLOOR) {
Chris@199 594 mindbstr = tr("-Inf");
Chris@199 595 } else {
Chris@199 596 mindbstr = QString("%1").arg(lrintf(mindb));
Chris@199 597 }
Chris@199 598 if (maxdb == AudioLevel::DB_FLOOR) {
Chris@199 599 maxdbstr = tr("-Inf");
Chris@199 600 } else {
Chris@199 601 maxdbstr = QString("%1").arg(lrintf(maxdb));
Chris@199 602 }
Chris@199 603 if (lrintf(mindb) != lrintf(maxdb)) {
Chris@199 604 dbstr = tr("%1 - %2").arg(mindbstr).arg(maxdbstr);
Chris@199 605 } else {
Chris@199 606 dbstr = tr("%1").arg(mindbstr);
Chris@199 607 }
Chris@199 608
Chris@199 609 QString description;
Chris@199 610
Chris@248 611 if (range > int(m_sliceableModel->getResolution())) {
Chris@199 612 description = tr("%1\nBin:\t%2 (%3)\n%4 value:\t%5\ndB:\t%6")
Chris@199 613 .arg(genericDesc)
Chris@199 614 .arg(binstr)
Chris@199 615 .arg(hzstr)
Chris@199 616 .arg(m_samplingMode == NearestSample ? tr("First") :
Chris@199 617 m_samplingMode == SampleMean ? tr("Mean") : tr("Peak"))
Chris@199 618 .arg(valuestr)
Chris@199 619 .arg(dbstr);
Chris@199 620 } else {
Chris@199 621 description = tr("%1\nBin:\t%2 (%3)\nValue:\t%4\ndB:\t%5")
Chris@199 622 .arg(genericDesc)
Chris@199 623 .arg(binstr)
Chris@199 624 .arg(hzstr)
Chris@199 625 .arg(valuestr)
Chris@199 626 .arg(dbstr);
Chris@199 627 }
Chris@199 628
Chris@199 629 return description;
Chris@199 630 }
Chris@199 631
Chris@254 632 void
Chris@275 633 SpectrumLayer::paint(View *v, QPainter &paint, QRect rect) const
Chris@275 634 {
Chris@275 635 if (!m_originModel || !m_originModel->isOK() ||
Chris@275 636 !m_originModel->isReady()) return;
Chris@275 637
Chris@275 638 if (m_newFFTNeeded) {
Chris@275 639 const_cast<SpectrumLayer *>(this)->setupFFT(); //ugh
Chris@275 640 m_newFFTNeeded = false;
Chris@275 641 }
Chris@277 642
Chris@277 643 FFTModel *fft = dynamic_cast<FFTModel *>
Chris@277 644 (const_cast<DenseThreeDimensionalModel *>(m_sliceableModel));
Chris@277 645
Chris@280 646 float thresh = (powf(10, -6) / m_gain) * (m_windowSize / 2.f); // -60dB adj
Chris@277 647
Chris@277 648 int xorigin = getVerticalScaleWidth(v, paint) + 1;
Chris@277 649 int w = v->width() - xorigin - 1;
Chris@277 650
Chris@278 651 int pkh = 0;
Chris@287 652 //!!! if (m_binScale == LogBins) {
Chris@287 653 pkh = 10;
Chris@287 654 //!!! }
Chris@278 655
Chris@284 656 if (fft && m_showPeaks) {
Chris@277 657
Chris@277 658 // draw peak lines
Chris@277 659
Chris@277 660 size_t col = v->getCentreFrame() / fft->getResolution();
Chris@277 661
Chris@277 662 paint.save();
Chris@277 663 paint.setRenderHint(QPainter::Antialiasing, false);
Chris@277 664 paint.setPen(QColor(160, 160, 160)); //!!!
Chris@277 665
Chris@290 666 int peakminbin = 0;
Chris@290 667 int peakmaxbin = fft->getHeight() - 1;
Chris@290 668 float peakmaxfreq = Pitch::getFrequencyForPitch(128);
Chris@290 669 peakmaxbin = ((peakmaxfreq * fft->getHeight() * 2) / fft->getSampleRate());
Chris@290 670
Chris@280 671 FFTModel::PeakSet peaks = fft->getPeakFrequencies
Chris@290 672 (FFTModel::MajorPitchAdaptivePeaks, col, peakminbin, peakmaxbin);
Chris@280 673
Chris@280 674 ColourMapper mapper(ColourMapper::BlackOnWhite, 0, 1);
Chris@277 675
Chris@277 676 BiasCurve curve;
Chris@277 677 getBiasCurve(curve);
Chris@277 678 size_t cs = curve.size();
Chris@280 679
Chris@280 680 std::vector<float> values;
Chris@277 681
Chris@277 682 for (size_t bin = 0; bin < fft->getHeight(); ++bin) {
Chris@280 683 float value = m_sliceableModel->getValueAt(col, bin);
Chris@280 684 if (bin < cs) value *= curve[bin];
Chris@280 685 values.push_back(value);
Chris@280 686 }
Chris@280 687
Chris@280 688 for (FFTModel::PeakSet::iterator i = peaks.begin();
Chris@280 689 i != peaks.end(); ++i) {
Chris@280 690
Chris@280 691 size_t bin = i->first;
Chris@277 692
Chris@280 693 // std::cerr << "bin = " << bin << ", thresh = " << thresh << ", value = " << fft->getMagnitudeAt(col, bin) << std::endl;
Chris@280 694
Chris@277 695 if (!fft->isOverThreshold(col, bin, thresh)) continue;
Chris@277 696
Chris@280 697 float freq = i->second;
Chris@280 698
Chris@277 699 int x = lrintf(getXForFrequency(freq, w));
Chris@277 700
Chris@277 701 float norm = 0.f;
Chris@280 702 float y = getYForValue(values[bin], v, norm); // don't need y, need norm
Chris@277 703
Chris@277 704 paint.setPen(mapper.map(norm));
Chris@278 705 paint.drawLine(xorigin + x, 0, xorigin + x, v->height() - pkh - 1);
Chris@277 706 }
Chris@277 707
Chris@277 708 paint.restore();
Chris@277 709 }
Chris@275 710
Chris@275 711 SliceLayer::paint(v, paint, rect);
Chris@277 712
Chris@278 713 //!!! All of this stuff relating to depicting frequencies
Chris@278 714 //(keyboard, crosshairs etc) should be applicable to any slice
Chris@278 715 //layer whose model has a vertical scale unit of Hz. However, the
Chris@278 716 //dense 3d model at the moment doesn't record its vertical scale
Chris@278 717 //unit -- we need to fix that and hoist this code as appropriate.
Chris@278 718 //Same really goes for any code in SpectrogramLayer that could be
Chris@278 719 //relevant to Colour3DPlotLayer with unit Hz, but that's a bigger
Chris@278 720 //proposition.
Chris@278 721
Chris@287 722 // if (m_binScale == LogBins) {
Chris@277 723
Chris@287 724 // int pkh = 10;
Chris@277 725 int h = v->height();
Chris@277 726
Chris@277 727 // piano keyboard
Chris@277 728 //!!! should be in a new paintHorizontalScale()?
Chris@278 729 // nice to have a piano keyboard class, of course
Chris@277 730
Chris@278 731 paint.drawLine(xorigin, h - pkh - 1, w + xorigin, h - pkh - 1);
Chris@277 732
Chris@277 733 int px = xorigin, ppx = xorigin;
Chris@278 734 paint.setBrush(paint.pen().color());
Chris@277 735
Chris@277 736 for (int i = 0; i < 128; ++i) {
Chris@277 737
Chris@277 738 float f = Pitch::getFrequencyForPitch(i);
Chris@277 739 int x = lrintf(getXForFrequency(f, w));
Chris@278 740
Chris@278 741 x += xorigin;
Chris@277 742
Chris@278 743 if (i == 0) {
Chris@278 744 px = ppx = x;
Chris@278 745 }
Chris@278 746 if (i == 1) {
Chris@278 747 ppx = px - (x - px);
Chris@278 748 }
Chris@278 749
Chris@278 750 if (x < xorigin) {
Chris@278 751 ppx = px;
Chris@278 752 px = x;
Chris@277 753 continue;
Chris@277 754 }
Chris@278 755
Chris@278 756 if (x > w) {
Chris@278 757 break;
Chris@278 758 }
Chris@277 759
Chris@277 760 int n = (i % 12);
Chris@277 761
Chris@277 762 if (n == 1) {
Chris@277 763 // C# -- fill the C from here
Chris@284 764 QColor col = Qt::gray;
Chris@284 765 if (i == 61) { // filling middle C
Chris@284 766 col = Qt::blue;
Chris@284 767 col = col.light(150);
Chris@284 768 }
Chris@277 769 if (x - ppx > 2) {
Chris@278 770 paint.fillRect((px + ppx) / 2 + 1,
Chris@277 771 h - pkh,
Chris@278 772 x - (px + ppx) / 2 - 1,
Chris@277 773 pkh,
Chris@284 774 col);
Chris@277 775 }
Chris@277 776 }
Chris@277 777
Chris@277 778 if (n == 1 || n == 3 || n == 6 || n == 8 || n == 10) {
Chris@277 779 // black notes
Chris@277 780 paint.drawLine(x, h - pkh, x, h);
Chris@278 781 int rw = lrintf(float(x - px) / 4) * 2;
Chris@277 782 if (rw < 2) rw = 2;
Chris@278 783 paint.drawRect(x - rw/2, h - pkh, rw, pkh/2);
Chris@277 784 } else if (n == 0 || n == 5) {
Chris@277 785 // C, F
Chris@277 786 if (px < w) {
Chris@277 787 paint.drawLine((x + px) / 2, h - pkh, (x + px) / 2, h);
Chris@277 788 }
Chris@277 789 }
Chris@277 790
Chris@277 791 ppx = px;
Chris@277 792 px = x;
Chris@277 793 }
Chris@287 794 // }
Chris@275 795 }
Chris@275 796
Chris@275 797 void
Chris@254 798 SpectrumLayer::getBiasCurve(BiasCurve &curve) const
Chris@254 799 {
Chris@254 800 curve = m_biasCurve;
Chris@254 801 }
Chris@199 802
Chris@316 803 void
Chris@316 804 SpectrumLayer::toXml(QTextStream &stream,
Chris@316 805 QString indent, QString extraAttributes) const
Chris@220 806 {
Chris@316 807 QString s = QString("windowSize=\"%1\" "
Chris@316 808 "windowHopLevel=\"%2\"")
Chris@220 809 .arg(m_windowSize)
Chris@220 810 .arg(m_windowHopLevel);
Chris@220 811
Chris@316 812 SliceLayer::toXml(stream, indent, extraAttributes + " " + s);
Chris@220 813 }
Chris@220 814
Chris@220 815 void
Chris@220 816 SpectrumLayer::setProperties(const QXmlAttributes &attributes)
Chris@220 817 {
Chris@220 818 SliceLayer::setProperties(attributes);
Chris@220 819
Chris@220 820 bool ok = false;
Chris@220 821
Chris@220 822 size_t windowSize = attributes.value("windowSize").toUInt(&ok);
Chris@220 823 if (ok) setWindowSize(windowSize);
Chris@220 824
Chris@220 825 size_t windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok);
Chris@220 826 if (ok) setWindowHopLevel(windowHopLevel);
Chris@220 827 }
Chris@220 828
Chris@220 829