annotate layer/SpectrumLayer.cpp @ 312:6de6f78b13a1

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