annotate layer/SpectrogramLayer.cpp @ 1135:628cd329c241 spectrogram-minor-refactor

Use a count of bins rather than min and max bins (because the name maxbin tells us nothing about whether the range is inclusive or not)
author Chris Cannam
date Wed, 03 Aug 2016 14:20:27 +0100
parents 199049012fc6
children 9ff838a64461
rev   line source
Chris@58 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@0 2
Chris@0 3 /*
Chris@59 4 Sonic Visualiser
Chris@59 5 An audio file viewer and annotation editor.
Chris@59 6 Centre for Digital Music, Queen Mary, University of London.
Chris@484 7 This file copyright 2006-2009 Chris Cannam and QMUL.
Chris@0 8
Chris@59 9 This program is free software; you can redistribute it and/or
Chris@59 10 modify it under the terms of the GNU General Public License as
Chris@59 11 published by the Free Software Foundation; either version 2 of the
Chris@59 12 License, or (at your option) any later version. See the file
Chris@59 13 COPYING included with this distribution for more information.
Chris@0 14 */
Chris@0 15
Chris@0 16 #include "SpectrogramLayer.h"
Chris@0 17
Chris@128 18 #include "view/View.h"
Chris@0 19 #include "base/Profiler.h"
Chris@0 20 #include "base/AudioLevel.h"
Chris@0 21 #include "base/Window.h"
Chris@24 22 #include "base/Pitch.h"
Chris@118 23 #include "base/Preferences.h"
Chris@167 24 #include "base/RangeMapper.h"
Chris@253 25 #include "base/LogRange.h"
Chris@1063 26 #include "base/ColumnOp.h"
Chris@376 27 #include "widgets/CommandHistory.h"
Chris@1078 28 #include "data/model/Dense3DModelPeakCache.h"
Chris@1078 29
Chris@376 30 #include "ColourMapper.h"
Chris@283 31 #include "ImageRegionFinder.h"
Chris@690 32 #include "PianoScale.h"
Chris@1078 33 #include "PaintAssistant.h"
Chris@1089 34 #include "Colour3DPlotRenderer.h"
Chris@0 35
Chris@0 36 #include <QPainter>
Chris@0 37 #include <QImage>
Chris@0 38 #include <QPixmap>
Chris@0 39 #include <QRect>
Chris@92 40 #include <QApplication>
Chris@178 41 #include <QMessageBox>
Chris@283 42 #include <QMouseEvent>
Chris@316 43 #include <QTextStream>
Chris@1017 44 #include <QSettings>
Chris@0 45
Chris@0 46 #include <iostream>
Chris@0 47
Chris@0 48 #include <cassert>
Chris@0 49 #include <cmath>
Chris@0 50
Chris@545 51 #ifndef __GNUC__
Chris@545 52 #include <alloca.h>
Chris@545 53 #endif
Chris@545 54
Chris@1090 55 #define DEBUG_SPECTROGRAM 1
Chris@1044 56 #define DEBUG_SPECTROGRAM_REPAINT 1
Chris@1025 57
Chris@1025 58 using namespace std;
Chris@907 59
Chris@44 60 SpectrogramLayer::SpectrogramLayer(Configuration config) :
Chris@0 61 m_model(0),
Chris@0 62 m_channel(0),
Chris@0 63 m_windowSize(1024),
Chris@0 64 m_windowType(HanningWindow),
Chris@97 65 m_windowHopLevel(2),
Chris@0 66 m_gain(1.0),
Chris@215 67 m_initialGain(1.0),
Chris@1128 68 m_threshold(1.0e-8f),
Chris@1128 69 m_initialThreshold(1.0e-8f),
Chris@9 70 m_colourRotation(0),
Chris@215 71 m_initialRotation(0),
Chris@119 72 m_minFrequency(10),
Chris@0 73 m_maxFrequency(8000),
Chris@135 74 m_initialMaxFrequency(8000),
Chris@1105 75 m_colourScale(ColourScaleType::Log),
Chris@197 76 m_colourMap(0),
Chris@1103 77 m_binScale(BinScale::Linear),
Chris@1103 78 m_binDisplay(BinDisplay::AllBins),
Chris@1104 79 m_normalization(ColumnNormalization::None),
Chris@1104 80 m_normalizeVisibleArea(false),
Chris@133 81 m_lastEmittedZoomStep(-1),
Chris@390 82 m_synchronous(false),
Chris@608 83 m_haveDetailedScale(false),
Chris@193 84 m_exiting(false),
Chris@1088 85 m_fftModel(0),
Chris@1088 86 m_peakCache(0),
Chris@1088 87 m_peakCacheDivisor(8)
Chris@0 88 {
Chris@1017 89 QString colourConfigName = "spectrogram-colour";
Chris@1017 90 int colourConfigDefault = int(ColourMapper::Green);
Chris@1017 91
Chris@215 92 if (config == FullRangeDb) {
Chris@215 93 m_initialMaxFrequency = 0;
Chris@215 94 setMaxFrequency(0);
Chris@215 95 } else if (config == MelodicRange) {
Chris@0 96 setWindowSize(8192);
Chris@97 97 setWindowHopLevel(4);
Chris@215 98 m_initialMaxFrequency = 1500;
Chris@215 99 setMaxFrequency(1500);
Chris@215 100 setMinFrequency(40);
Chris@1105 101 setColourScale(ColourScaleType::Linear);
Chris@215 102 setColourMap(ColourMapper::Sunset);
Chris@1103 103 setBinScale(BinScale::Log);
Chris@1017 104 colourConfigName = "spectrogram-melodic-colour";
Chris@1017 105 colourConfigDefault = int(ColourMapper::Sunset);
Chris@224 106 // setGain(20);
Chris@37 107 } else if (config == MelodicPeaks) {
Chris@37 108 setWindowSize(4096);
Chris@97 109 setWindowHopLevel(5);
Chris@135 110 m_initialMaxFrequency = 2000;
Chris@40 111 setMaxFrequency(2000);
Chris@37 112 setMinFrequency(40);
Chris@1103 113 setBinScale(BinScale::Log);
Chris@1105 114 setColourScale(ColourScaleType::Linear);
Chris@1103 115 setBinDisplay(BinDisplay::PeakFrequencies);
Chris@1104 116 setNormalization(ColumnNormalization::Max1);
Chris@1017 117 colourConfigName = "spectrogram-melodic-colour";
Chris@1017 118 colourConfigDefault = int(ColourMapper::Sunset);
Chris@0 119 }
Chris@110 120
Chris@1017 121 QSettings settings;
Chris@1017 122 settings.beginGroup("Preferences");
Chris@1017 123 setColourMap(settings.value(colourConfigName, colourConfigDefault).toInt());
Chris@1017 124 settings.endGroup();
Chris@1017 125
Chris@122 126 Preferences *prefs = Preferences::getInstance();
Chris@122 127 connect(prefs, SIGNAL(propertyChanged(PropertyContainer::PropertyName)),
Chris@122 128 this, SLOT(preferenceChanged(PropertyContainer::PropertyName)));
Chris@122 129 setWindowType(prefs->getWindowType());
Chris@0 130 }
Chris@0 131
Chris@0 132 SpectrogramLayer::~SpectrogramLayer()
Chris@0 133 {
Chris@1106 134 invalidateRenderers();
Chris@1088 135 invalidateFFTModel();
Chris@0 136 }
Chris@0 137
Chris@1105 138 ColourScaleType
Chris@1104 139 SpectrogramLayer::convertToColourScale(int value)
Chris@1104 140 {
Chris@1104 141 switch (value) {
Chris@1105 142 case 0: return ColourScaleType::Linear;
Chris@1105 143 case 1: return ColourScaleType::Meter;
Chris@1105 144 case 2: return ColourScaleType::Log; //!!! db^2
Chris@1105 145 case 3: return ColourScaleType::Log;
Chris@1105 146 case 4: return ColourScaleType::Phase;
Chris@1105 147 default: return ColourScaleType::Linear;
Chris@1104 148 }
Chris@1104 149 }
Chris@1104 150
Chris@1104 151 int
Chris@1105 152 SpectrogramLayer::convertFromColourScale(ColourScaleType scale)
Chris@1104 153 {
Chris@1104 154 switch (scale) {
Chris@1105 155 case ColourScaleType::Linear: return 0;
Chris@1105 156 case ColourScaleType::Meter: return 1;
Chris@1105 157 case ColourScaleType::Log: return 3; //!!! + db^2
Chris@1105 158 case ColourScaleType::Phase: return 4;
Chris@1105 159
Chris@1105 160 case ColourScaleType::PlusMinusOne:
Chris@1105 161 case ColourScaleType::Absolute:
Chris@1104 162 default: return 0;
Chris@1104 163 }
Chris@1104 164 }
Chris@1104 165
Chris@1104 166 std::pair<ColumnNormalization, bool>
Chris@1104 167 SpectrogramLayer::convertToColumnNorm(int value)
Chris@1104 168 {
Chris@1104 169 switch (value) {
Chris@1104 170 default:
Chris@1104 171 case 0: return { ColumnNormalization::None, false };
Chris@1104 172 case 1: return { ColumnNormalization::Max1, false };
Chris@1104 173 case 2: return { ColumnNormalization::None, true }; // visible area
Chris@1104 174 case 3: return { ColumnNormalization::Hybrid, false };
Chris@1104 175 }
Chris@1104 176 }
Chris@1104 177
Chris@1104 178 int
Chris@1104 179 SpectrogramLayer::convertFromColumnNorm(ColumnNormalization norm, bool visible)
Chris@1104 180 {
Chris@1104 181 if (visible) return 2;
Chris@1104 182 switch (norm) {
Chris@1104 183 case ColumnNormalization::None: return 0;
Chris@1104 184 case ColumnNormalization::Max1: return 1;
Chris@1104 185 case ColumnNormalization::Hybrid: return 3;
Chris@1104 186
Chris@1104 187 case ColumnNormalization::Sum1:
Chris@1104 188 default: return 0;
Chris@1104 189 }
Chris@1104 190 }
Chris@1104 191
Chris@0 192 void
Chris@0 193 SpectrogramLayer::setModel(const DenseTimeValueModel *model)
Chris@0 194 {
Chris@682 195 // cerr << "SpectrogramLayer(" << this << "): setModel(" << model << ")" << endl;
Chris@34 196
Chris@110 197 if (model == m_model) return;
Chris@110 198
Chris@0 199 m_model = model;
Chris@1088 200 invalidateFFTModel();
Chris@0 201
Chris@0 202 if (!m_model || !m_model->isOK()) return;
Chris@0 203
Chris@320 204 connectSignals(m_model);
Chris@0 205
Chris@0 206 connect(m_model, SIGNAL(modelChanged()), this, SLOT(cacheInvalid()));
Chris@906 207 connect(m_model, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
Chris@906 208 this, SLOT(cacheInvalid(sv_frame_t, sv_frame_t)));
Chris@0 209
Chris@0 210 emit modelReplaced();
Chris@110 211 }
Chris@115 212
Chris@0 213 Layer::PropertyList
Chris@0 214 SpectrogramLayer::getProperties() const
Chris@0 215 {
Chris@0 216 PropertyList list;
Chris@87 217 list.push_back("Colour");
Chris@87 218 list.push_back("Colour Scale");
Chris@87 219 list.push_back("Window Size");
Chris@97 220 list.push_back("Window Increment");
Chris@862 221 list.push_back("Normalization");
Chris@87 222 list.push_back("Bin Display");
Chris@87 223 list.push_back("Threshold");
Chris@87 224 list.push_back("Gain");
Chris@87 225 list.push_back("Colour Rotation");
Chris@153 226 // list.push_back("Min Frequency");
Chris@153 227 // list.push_back("Max Frequency");
Chris@87 228 list.push_back("Frequency Scale");
Chris@0 229 return list;
Chris@0 230 }
Chris@0 231
Chris@87 232 QString
Chris@87 233 SpectrogramLayer::getPropertyLabel(const PropertyName &name) const
Chris@87 234 {
Chris@87 235 if (name == "Colour") return tr("Colour");
Chris@87 236 if (name == "Colour Scale") return tr("Colour Scale");
Chris@87 237 if (name == "Window Size") return tr("Window Size");
Chris@112 238 if (name == "Window Increment") return tr("Window Overlap");
Chris@862 239 if (name == "Normalization") return tr("Normalization");
Chris@87 240 if (name == "Bin Display") return tr("Bin Display");
Chris@87 241 if (name == "Threshold") return tr("Threshold");
Chris@87 242 if (name == "Gain") return tr("Gain");
Chris@87 243 if (name == "Colour Rotation") return tr("Colour Rotation");
Chris@87 244 if (name == "Min Frequency") return tr("Min Frequency");
Chris@87 245 if (name == "Max Frequency") return tr("Max Frequency");
Chris@87 246 if (name == "Frequency Scale") return tr("Frequency Scale");
Chris@87 247 return "";
Chris@87 248 }
Chris@87 249
Chris@335 250 QString
Chris@862 251 SpectrogramLayer::getPropertyIconName(const PropertyName &) const
Chris@335 252 {
Chris@335 253 return "";
Chris@335 254 }
Chris@335 255
Chris@0 256 Layer::PropertyType
Chris@0 257 SpectrogramLayer::getPropertyType(const PropertyName &name) const
Chris@0 258 {
Chris@87 259 if (name == "Gain") return RangeProperty;
Chris@87 260 if (name == "Colour Rotation") return RangeProperty;
Chris@87 261 if (name == "Threshold") return RangeProperty;
Chris@0 262 return ValueProperty;
Chris@0 263 }
Chris@0 264
Chris@0 265 QString
Chris@0 266 SpectrogramLayer::getPropertyGroupName(const PropertyName &name) const
Chris@0 267 {
Chris@153 268 if (name == "Bin Display" ||
Chris@153 269 name == "Frequency Scale") return tr("Bins");
Chris@87 270 if (name == "Window Size" ||
Chris@1086 271 name == "Window Increment") return tr("Window");
Chris@87 272 if (name == "Colour" ||
Chris@87 273 name == "Threshold" ||
Chris@87 274 name == "Colour Rotation") return tr("Colour");
Chris@862 275 if (name == "Normalization" ||
Chris@153 276 name == "Gain" ||
Chris@87 277 name == "Colour Scale") return tr("Scale");
Chris@0 278 return QString();
Chris@0 279 }
Chris@0 280
Chris@0 281 int
Chris@0 282 SpectrogramLayer::getPropertyRangeAndValue(const PropertyName &name,
Chris@216 283 int *min, int *max, int *deflt) const
Chris@0 284 {
Chris@216 285 int val = 0;
Chris@216 286
Chris@216 287 int garbage0, garbage1, garbage2;
Chris@55 288 if (!min) min = &garbage0;
Chris@55 289 if (!max) max = &garbage1;
Chris@216 290 if (!deflt) deflt = &garbage2;
Chris@10 291
Chris@87 292 if (name == "Gain") {
Chris@0 293
Chris@0 294 *min = -50;
Chris@0 295 *max = 50;
Chris@0 296
Chris@906 297 *deflt = int(lrint(log10(m_initialGain) * 20.0));
Chris@216 298 if (*deflt < *min) *deflt = *min;
Chris@216 299 if (*deflt > *max) *deflt = *max;
Chris@216 300
Chris@906 301 val = int(lrint(log10(m_gain) * 20.0));
Chris@216 302 if (val < *min) val = *min;
Chris@216 303 if (val > *max) val = *max;
Chris@0 304
Chris@87 305 } else if (name == "Threshold") {
Chris@37 306
Chris@1127 307 *min = -81;
Chris@1127 308 *max = -1;
Chris@37 309
Chris@906 310 *deflt = int(lrint(AudioLevel::multiplier_to_dB(m_initialThreshold)));
Chris@216 311 if (*deflt < *min) *deflt = *min;
Chris@216 312 if (*deflt > *max) *deflt = *max;
Chris@216 313
Chris@906 314 val = int(lrint(AudioLevel::multiplier_to_dB(m_threshold)));
Chris@216 315 if (val < *min) val = *min;
Chris@216 316 if (val > *max) val = *max;
Chris@37 317
Chris@87 318 } else if (name == "Colour Rotation") {
Chris@9 319
Chris@9 320 *min = 0;
Chris@9 321 *max = 256;
Chris@216 322 *deflt = m_initialRotation;
Chris@216 323
Chris@216 324 val = m_colourRotation;
Chris@9 325
Chris@87 326 } else if (name == "Colour Scale") {
Chris@0 327
Chris@1099 328 // linear, meter, db^2, db, phase
Chris@0 329 *min = 0;
Chris@176 330 *max = 4;
Chris@1092 331 *deflt = 2;
Chris@216 332
Chris@1104 333 val = convertFromColourScale(m_colourScale);
Chris@0 334
Chris@87 335 } else if (name == "Colour") {
Chris@0 336
Chris@0 337 *min = 0;
Chris@196 338 *max = ColourMapper::getColourMapCount() - 1;
Chris@216 339 *deflt = 0;
Chris@216 340
Chris@216 341 val = m_colourMap;
Chris@0 342
Chris@87 343 } else if (name == "Window Size") {
Chris@0 344
Chris@0 345 *min = 0;
Chris@0 346 *max = 10;
Chris@216 347 *deflt = 5;
Chris@0 348
Chris@216 349 val = 0;
Chris@0 350 int ws = m_windowSize;
Chris@216 351 while (ws > 32) { ws >>= 1; val ++; }
Chris@0 352
Chris@97 353 } else if (name == "Window Increment") {
Chris@0 354
Chris@0 355 *min = 0;
Chris@97 356 *max = 5;
Chris@216 357 *deflt = 2;
Chris@216 358
Chris@216 359 val = m_windowHopLevel;
Chris@0 360
Chris@87 361 } else if (name == "Min Frequency") {
Chris@37 362
Chris@37 363 *min = 0;
Chris@37 364 *max = 9;
Chris@216 365 *deflt = 1;
Chris@37 366
Chris@37 367 switch (m_minFrequency) {
Chris@216 368 case 0: default: val = 0; break;
Chris@216 369 case 10: val = 1; break;
Chris@216 370 case 20: val = 2; break;
Chris@216 371 case 40: val = 3; break;
Chris@216 372 case 100: val = 4; break;
Chris@216 373 case 250: val = 5; break;
Chris@216 374 case 500: val = 6; break;
Chris@216 375 case 1000: val = 7; break;
Chris@216 376 case 4000: val = 8; break;
Chris@216 377 case 10000: val = 9; break;
Chris@37 378 }
Chris@37 379
Chris@87 380 } else if (name == "Max Frequency") {
Chris@0 381
Chris@0 382 *min = 0;
Chris@0 383 *max = 9;
Chris@216 384 *deflt = 6;
Chris@0 385
Chris@0 386 switch (m_maxFrequency) {
Chris@216 387 case 500: val = 0; break;
Chris@216 388 case 1000: val = 1; break;
Chris@216 389 case 1500: val = 2; break;
Chris@216 390 case 2000: val = 3; break;
Chris@216 391 case 4000: val = 4; break;
Chris@216 392 case 6000: val = 5; break;
Chris@216 393 case 8000: val = 6; break;
Chris@216 394 case 12000: val = 7; break;
Chris@216 395 case 16000: val = 8; break;
Chris@216 396 default: val = 9; break;
Chris@0 397 }
Chris@0 398
Chris@87 399 } else if (name == "Frequency Scale") {
Chris@0 400
Chris@0 401 *min = 0;
Chris@0 402 *max = 1;
Chris@1103 403 *deflt = int(BinScale::Linear);
Chris@1093 404 val = (int)m_binScale;
Chris@0 405
Chris@87 406 } else if (name == "Bin Display") {
Chris@35 407
Chris@35 408 *min = 0;
Chris@35 409 *max = 2;
Chris@1103 410 *deflt = int(BinDisplay::AllBins);
Chris@216 411 val = (int)m_binDisplay;
Chris@35 412
Chris@862 413 } else if (name == "Normalization") {
Chris@36 414
Chris@862 415 *min = 0;
Chris@862 416 *max = 3;
Chris@1104 417 *deflt = 0;
Chris@1104 418
Chris@1104 419 val = convertFromColumnNorm(m_normalization, m_normalizeVisibleArea);
Chris@120 420
Chris@0 421 } else {
Chris@216 422 val = Layer::getPropertyRangeAndValue(name, min, max, deflt);
Chris@0 423 }
Chris@0 424
Chris@216 425 return val;
Chris@0 426 }
Chris@0 427
Chris@0 428 QString
Chris@0 429 SpectrogramLayer::getPropertyValueLabel(const PropertyName &name,
Chris@9 430 int value) const
Chris@0 431 {
Chris@87 432 if (name == "Colour") {
Chris@196 433 return ColourMapper::getColourMapName(value);
Chris@0 434 }
Chris@87 435 if (name == "Colour Scale") {
Chris@0 436 switch (value) {
Chris@0 437 default:
Chris@37 438 case 0: return tr("Linear");
Chris@37 439 case 1: return tr("Meter");
Chris@215 440 case 2: return tr("dBV^2");
Chris@215 441 case 3: return tr("dBV");
Chris@119 442 case 4: return tr("Phase");
Chris@0 443 }
Chris@0 444 }
Chris@862 445 if (name == "Normalization") {
Chris@862 446 return ""; // icon only
Chris@862 447 }
Chris@87 448 if (name == "Window Size") {
Chris@0 449 return QString("%1").arg(32 << value);
Chris@0 450 }
Chris@97 451 if (name == "Window Increment") {
Chris@0 452 switch (value) {
Chris@0 453 default:
Chris@112 454 case 0: return tr("None");
Chris@112 455 case 1: return tr("25 %");
Chris@112 456 case 2: return tr("50 %");
Chris@112 457 case 3: return tr("75 %");
Chris@112 458 case 4: return tr("87.5 %");
Chris@112 459 case 5: return tr("93.75 %");
Chris@0 460 }
Chris@0 461 }
Chris@87 462 if (name == "Min Frequency") {
Chris@37 463 switch (value) {
Chris@37 464 default:
Chris@38 465 case 0: return tr("No min");
Chris@37 466 case 1: return tr("10 Hz");
Chris@37 467 case 2: return tr("20 Hz");
Chris@37 468 case 3: return tr("40 Hz");
Chris@37 469 case 4: return tr("100 Hz");
Chris@37 470 case 5: return tr("250 Hz");
Chris@37 471 case 6: return tr("500 Hz");
Chris@37 472 case 7: return tr("1 KHz");
Chris@37 473 case 8: return tr("4 KHz");
Chris@37 474 case 9: return tr("10 KHz");
Chris@37 475 }
Chris@37 476 }
Chris@87 477 if (name == "Max Frequency") {
Chris@0 478 switch (value) {
Chris@0 479 default:
Chris@0 480 case 0: return tr("500 Hz");
Chris@0 481 case 1: return tr("1 KHz");
Chris@0 482 case 2: return tr("1.5 KHz");
Chris@0 483 case 3: return tr("2 KHz");
Chris@0 484 case 4: return tr("4 KHz");
Chris@0 485 case 5: return tr("6 KHz");
Chris@0 486 case 6: return tr("8 KHz");
Chris@0 487 case 7: return tr("12 KHz");
Chris@0 488 case 8: return tr("16 KHz");
Chris@38 489 case 9: return tr("No max");
Chris@0 490 }
Chris@0 491 }
Chris@87 492 if (name == "Frequency Scale") {
Chris@0 493 switch (value) {
Chris@0 494 default:
Chris@0 495 case 0: return tr("Linear");
Chris@0 496 case 1: return tr("Log");
Chris@0 497 }
Chris@0 498 }
Chris@87 499 if (name == "Bin Display") {
Chris@35 500 switch (value) {
Chris@35 501 default:
Chris@37 502 case 0: return tr("All Bins");
Chris@37 503 case 1: return tr("Peak Bins");
Chris@37 504 case 2: return tr("Frequencies");
Chris@35 505 }
Chris@35 506 }
Chris@0 507 return tr("<unknown>");
Chris@0 508 }
Chris@0 509
Chris@862 510 QString
Chris@862 511 SpectrogramLayer::getPropertyValueIconName(const PropertyName &name,
Chris@862 512 int value) const
Chris@862 513 {
Chris@862 514 if (name == "Normalization") {
Chris@862 515 switch(value) {
Chris@862 516 default:
Chris@862 517 case 0: return "normalise-none";
Chris@862 518 case 1: return "normalise-columns";
Chris@862 519 case 2: return "normalise";
Chris@862 520 case 3: return "normalise-hybrid";
Chris@862 521 }
Chris@862 522 }
Chris@862 523 return "";
Chris@862 524 }
Chris@862 525
Chris@167 526 RangeMapper *
Chris@167 527 SpectrogramLayer::getNewPropertyRangeMapper(const PropertyName &name) const
Chris@167 528 {
Chris@167 529 if (name == "Gain") {
Chris@167 530 return new LinearRangeMapper(-50, 50, -25, 25, tr("dB"));
Chris@167 531 }
Chris@167 532 if (name == "Threshold") {
Chris@1127 533 return new LinearRangeMapper(-81, -1, -81, -1, tr("dB"));
Chris@167 534 }
Chris@167 535 return 0;
Chris@167 536 }
Chris@167 537
Chris@0 538 void
Chris@0 539 SpectrogramLayer::setProperty(const PropertyName &name, int value)
Chris@0 540 {
Chris@87 541 if (name == "Gain") {
Chris@906 542 setGain(float(pow(10, float(value)/20.0)));
Chris@87 543 } else if (name == "Threshold") {
Chris@1127 544 if (value == -81) setThreshold(0.0);
Chris@906 545 else setThreshold(float(AudioLevel::dB_to_multiplier(value)));
Chris@87 546 } else if (name == "Colour Rotation") {
Chris@9 547 setColourRotation(value);
Chris@87 548 } else if (name == "Colour") {
Chris@197 549 setColourMap(value);
Chris@87 550 } else if (name == "Window Size") {
Chris@0 551 setWindowSize(32 << value);
Chris@97 552 } else if (name == "Window Increment") {
Chris@97 553 setWindowHopLevel(value);
Chris@87 554 } else if (name == "Min Frequency") {
Chris@37 555 switch (value) {
Chris@37 556 default:
Chris@37 557 case 0: setMinFrequency(0); break;
Chris@37 558 case 1: setMinFrequency(10); break;
Chris@37 559 case 2: setMinFrequency(20); break;
Chris@37 560 case 3: setMinFrequency(40); break;
Chris@37 561 case 4: setMinFrequency(100); break;
Chris@37 562 case 5: setMinFrequency(250); break;
Chris@37 563 case 6: setMinFrequency(500); break;
Chris@37 564 case 7: setMinFrequency(1000); break;
Chris@37 565 case 8: setMinFrequency(4000); break;
Chris@37 566 case 9: setMinFrequency(10000); break;
Chris@37 567 }
Chris@133 568 int vs = getCurrentVerticalZoomStep();
Chris@133 569 if (vs != m_lastEmittedZoomStep) {
Chris@133 570 emit verticalZoomChanged();
Chris@133 571 m_lastEmittedZoomStep = vs;
Chris@133 572 }
Chris@87 573 } else if (name == "Max Frequency") {
Chris@0 574 switch (value) {
Chris@0 575 case 0: setMaxFrequency(500); break;
Chris@0 576 case 1: setMaxFrequency(1000); break;
Chris@0 577 case 2: setMaxFrequency(1500); break;
Chris@0 578 case 3: setMaxFrequency(2000); break;
Chris@0 579 case 4: setMaxFrequency(4000); break;
Chris@0 580 case 5: setMaxFrequency(6000); break;
Chris@0 581 case 6: setMaxFrequency(8000); break;
Chris@0 582 case 7: setMaxFrequency(12000); break;
Chris@0 583 case 8: setMaxFrequency(16000); break;
Chris@0 584 default:
Chris@0 585 case 9: setMaxFrequency(0); break;
Chris@0 586 }
Chris@133 587 int vs = getCurrentVerticalZoomStep();
Chris@133 588 if (vs != m_lastEmittedZoomStep) {
Chris@133 589 emit verticalZoomChanged();
Chris@133 590 m_lastEmittedZoomStep = vs;
Chris@133 591 }
Chris@87 592 } else if (name == "Colour Scale") {
Chris@0 593 switch (value) {
Chris@0 594 default:
Chris@1105 595 case 0: setColourScale(ColourScaleType::Linear); break;
Chris@1105 596 case 1: setColourScale(ColourScaleType::Meter); break;
Chris@1105 597 case 2: setColourScale(ColourScaleType::Log); break; //!!! dB^2
Chris@1105 598 case 3: setColourScale(ColourScaleType::Log); break;
Chris@1105 599 case 4: setColourScale(ColourScaleType::Phase); break;
Chris@0 600 }
Chris@87 601 } else if (name == "Frequency Scale") {
Chris@0 602 switch (value) {
Chris@0 603 default:
Chris@1103 604 case 0: setBinScale(BinScale::Linear); break;
Chris@1103 605 case 1: setBinScale(BinScale::Log); break;
Chris@0 606 }
Chris@87 607 } else if (name == "Bin Display") {
Chris@35 608 switch (value) {
Chris@35 609 default:
Chris@1103 610 case 0: setBinDisplay(BinDisplay::AllBins); break;
Chris@1103 611 case 1: setBinDisplay(BinDisplay::PeakBins); break;
Chris@1103 612 case 2: setBinDisplay(BinDisplay::PeakFrequencies); break;
Chris@35 613 }
Chris@862 614 } else if (name == "Normalization") {
Chris@1104 615 auto n = convertToColumnNorm(value);
Chris@1104 616 setNormalization(n.first);
Chris@1104 617 setNormalizeVisibleArea(n.second);
Chris@0 618 }
Chris@0 619 }
Chris@0 620
Chris@0 621 void
Chris@1106 622 SpectrogramLayer::invalidateRenderers()
Chris@95 623 {
Chris@1044 624 #ifdef DEBUG_SPECTROGRAM
Chris@1106 625 cerr << "SpectrogramLayer::invalidateRenderers called" << endl;
Chris@1044 626 #endif
Chris@1106 627
Chris@1089 628 for (ViewRendererMap::iterator i = m_renderers.begin();
Chris@1089 629 i != m_renderers.end(); ++i) {
Chris@1089 630 delete i->second;
Chris@1089 631 }
Chris@1089 632 m_renderers.clear();
Chris@95 633 }
Chris@95 634
Chris@95 635 void
Chris@122 636 SpectrogramLayer::preferenceChanged(PropertyContainer::PropertyName name)
Chris@122 637 {
Chris@587 638 SVDEBUG << "SpectrogramLayer::preferenceChanged(" << name << ")" << endl;
Chris@122 639
Chris@122 640 if (name == "Window Type") {
Chris@122 641 setWindowType(Preferences::getInstance()->getWindowType());
Chris@122 642 return;
Chris@122 643 }
Chris@490 644 if (name == "Spectrogram Y Smoothing") {
Chris@1086 645 setWindowSize(m_windowSize);
Chris@1106 646 invalidateRenderers();
Chris@490 647 invalidateMagnitudes();
Chris@490 648 emit layerParametersChanged();
Chris@490 649 }
Chris@490 650 if (name == "Spectrogram X Smoothing") {
Chris@1106 651 invalidateRenderers();
Chris@122 652 invalidateMagnitudes();
Chris@122 653 emit layerParametersChanged();
Chris@122 654 }
Chris@122 655 if (name == "Tuning Frequency") {
Chris@122 656 emit layerParametersChanged();
Chris@122 657 }
Chris@122 658 }
Chris@122 659
Chris@122 660 void
Chris@0 661 SpectrogramLayer::setChannel(int ch)
Chris@0 662 {
Chris@0 663 if (m_channel == ch) return;
Chris@0 664
Chris@1106 665 invalidateRenderers();
Chris@0 666 m_channel = ch;
Chris@1088 667 invalidateFFTModel();
Chris@9 668
Chris@0 669 emit layerParametersChanged();
Chris@0 670 }
Chris@0 671
Chris@0 672 int
Chris@0 673 SpectrogramLayer::getChannel() const
Chris@0 674 {
Chris@0 675 return m_channel;
Chris@0 676 }
Chris@0 677
Chris@1086 678 int
Chris@1086 679 SpectrogramLayer::getFFTOversampling() const
Chris@1086 680 {
Chris@1103 681 if (m_binDisplay != BinDisplay::AllBins) {
Chris@1086 682 return 1;
Chris@1086 683 }
Chris@1086 684
Chris@1086 685 Preferences::SpectrogramSmoothing smoothing =
Chris@1086 686 Preferences::getInstance()->getSpectrogramSmoothing();
Chris@1086 687
Chris@1086 688 if (smoothing == Preferences::NoSpectrogramSmoothing ||
Chris@1086 689 smoothing == Preferences::SpectrogramInterpolated) {
Chris@1086 690 return 1;
Chris@1086 691 }
Chris@1086 692
Chris@1086 693 return 4;
Chris@1086 694 }
Chris@1086 695
Chris@1087 696 int
Chris@1087 697 SpectrogramLayer::getFFTSize() const
Chris@1087 698 {
Chris@1087 699 return m_windowSize * getFFTOversampling();
Chris@1087 700 }
Chris@1087 701
Chris@0 702 void
Chris@805 703 SpectrogramLayer::setWindowSize(int ws)
Chris@0 704 {
Chris@1087 705 if (m_windowSize == ws) return;
Chris@0 706
Chris@1106 707 invalidateRenderers();
Chris@0 708
Chris@0 709 m_windowSize = ws;
Chris@0 710
Chris@1088 711 invalidateFFTModel();
Chris@9 712
Chris@9 713 emit layerParametersChanged();
Chris@0 714 }
Chris@0 715
Chris@805 716 int
Chris@0 717 SpectrogramLayer::getWindowSize() const
Chris@0 718 {
Chris@0 719 return m_windowSize;
Chris@0 720 }
Chris@0 721
Chris@0 722 void
Chris@805 723 SpectrogramLayer::setWindowHopLevel(int v)
Chris@0 724 {
Chris@97 725 if (m_windowHopLevel == v) return;
Chris@0 726
Chris@1106 727 invalidateRenderers();
Chris@0 728
Chris@97 729 m_windowHopLevel = v;
Chris@0 730
Chris@1088 731 invalidateFFTModel();
Chris@9 732
Chris@9 733 emit layerParametersChanged();
Chris@9 734
Chris@110 735 // fillCache();
Chris@0 736 }
Chris@0 737
Chris@805 738 int
Chris@97 739 SpectrogramLayer::getWindowHopLevel() const
Chris@0 740 {
Chris@97 741 return m_windowHopLevel;
Chris@0 742 }
Chris@0 743
Chris@0 744 void
Chris@0 745 SpectrogramLayer::setWindowType(WindowType w)
Chris@0 746 {
Chris@0 747 if (m_windowType == w) return;
Chris@0 748
Chris@1106 749 invalidateRenderers();
Chris@0 750
Chris@0 751 m_windowType = w;
Chris@110 752
Chris@1088 753 invalidateFFTModel();
Chris@9 754
Chris@9 755 emit layerParametersChanged();
Chris@0 756 }
Chris@0 757
Chris@0 758 WindowType
Chris@0 759 SpectrogramLayer::getWindowType() const
Chris@0 760 {
Chris@0 761 return m_windowType;
Chris@0 762 }
Chris@0 763
Chris@0 764 void
Chris@0 765 SpectrogramLayer::setGain(float gain)
Chris@0 766 {
Chris@587 767 // SVDEBUG << "SpectrogramLayer::setGain(" << gain << ") (my gain is now "
Chris@585 768 // << m_gain << ")" << endl;
Chris@55 769
Chris@40 770 if (m_gain == gain) return;
Chris@0 771
Chris@1106 772 invalidateRenderers();
Chris@0 773
Chris@0 774 m_gain = gain;
Chris@0 775
Chris@9 776 emit layerParametersChanged();
Chris@0 777 }
Chris@0 778
Chris@0 779 float
Chris@0 780 SpectrogramLayer::getGain() const
Chris@0 781 {
Chris@0 782 return m_gain;
Chris@0 783 }
Chris@0 784
Chris@0 785 void
Chris@37 786 SpectrogramLayer::setThreshold(float threshold)
Chris@37 787 {
Chris@40 788 if (m_threshold == threshold) return;
Chris@37 789
Chris@1106 790 invalidateRenderers();
Chris@37 791
Chris@37 792 m_threshold = threshold;
Chris@37 793
Chris@37 794 emit layerParametersChanged();
Chris@37 795 }
Chris@37 796
Chris@37 797 float
Chris@37 798 SpectrogramLayer::getThreshold() const
Chris@37 799 {
Chris@37 800 return m_threshold;
Chris@37 801 }
Chris@37 802
Chris@37 803 void
Chris@805 804 SpectrogramLayer::setMinFrequency(int mf)
Chris@37 805 {
Chris@37 806 if (m_minFrequency == mf) return;
Chris@37 807
Chris@587 808 // SVDEBUG << "SpectrogramLayer::setMinFrequency: " << mf << endl;
Chris@187 809
Chris@1106 810 invalidateRenderers();
Chris@119 811 invalidateMagnitudes();
Chris@37 812
Chris@37 813 m_minFrequency = mf;
Chris@37 814
Chris@37 815 emit layerParametersChanged();
Chris@37 816 }
Chris@37 817
Chris@805 818 int
Chris@37 819 SpectrogramLayer::getMinFrequency() const
Chris@37 820 {
Chris@37 821 return m_minFrequency;
Chris@37 822 }
Chris@37 823
Chris@37 824 void
Chris@805 825 SpectrogramLayer::setMaxFrequency(int mf)
Chris@0 826 {
Chris@0 827 if (m_maxFrequency == mf) return;
Chris@0 828
Chris@587 829 // SVDEBUG << "SpectrogramLayer::setMaxFrequency: " << mf << endl;
Chris@187 830
Chris@1106 831 invalidateRenderers();
Chris@119 832 invalidateMagnitudes();
Chris@0 833
Chris@0 834 m_maxFrequency = mf;
Chris@0 835
Chris@9 836 emit layerParametersChanged();
Chris@0 837 }
Chris@0 838
Chris@805 839 int
Chris@0 840 SpectrogramLayer::getMaxFrequency() const
Chris@0 841 {
Chris@0 842 return m_maxFrequency;
Chris@0 843 }
Chris@0 844
Chris@0 845 void
Chris@9 846 SpectrogramLayer::setColourRotation(int r)
Chris@9 847 {
Chris@9 848 if (r < 0) r = 0;
Chris@9 849 if (r > 256) r = 256;
Chris@9 850 int distance = r - m_colourRotation;
Chris@9 851
Chris@9 852 if (distance != 0) {
Chris@1106 853 //!!! rotatePalette(-distance);
Chris@9 854 m_colourRotation = r;
Chris@9 855 }
Chris@9 856
Chris@1112 857 invalidateRenderers(); //!!! in theory we shouldn't have to do this... but...
Chris@1112 858
Chris@9 859 emit layerParametersChanged();
Chris@9 860 }
Chris@9 861
Chris@9 862 void
Chris@1105 863 SpectrogramLayer::setColourScale(ColourScaleType colourScale)
Chris@0 864 {
Chris@0 865 if (m_colourScale == colourScale) return;
Chris@0 866
Chris@1106 867 invalidateRenderers();
Chris@0 868
Chris@0 869 m_colourScale = colourScale;
Chris@0 870
Chris@9 871 emit layerParametersChanged();
Chris@0 872 }
Chris@0 873
Chris@1105 874 ColourScaleType
Chris@0 875 SpectrogramLayer::getColourScale() const
Chris@0 876 {
Chris@0 877 return m_colourScale;
Chris@0 878 }
Chris@0 879
Chris@0 880 void
Chris@197 881 SpectrogramLayer::setColourMap(int map)
Chris@0 882 {
Chris@197 883 if (m_colourMap == map) return;
Chris@0 884
Chris@1106 885 invalidateRenderers();
Chris@0 886
Chris@197 887 m_colourMap = map;
Chris@9 888
Chris@0 889 emit layerParametersChanged();
Chris@0 890 }
Chris@0 891
Chris@196 892 int
Chris@197 893 SpectrogramLayer::getColourMap() const
Chris@0 894 {
Chris@197 895 return m_colourMap;
Chris@0 896 }
Chris@0 897
Chris@0 898 void
Chris@1103 899 SpectrogramLayer::setBinScale(BinScale binScale)
Chris@0 900 {
Chris@1093 901 if (m_binScale == binScale) return;
Chris@0 902
Chris@1106 903 invalidateRenderers();
Chris@1093 904 m_binScale = binScale;
Chris@9 905
Chris@9 906 emit layerParametersChanged();
Chris@0 907 }
Chris@0 908
Chris@1103 909 BinScale
Chris@1093 910 SpectrogramLayer::getBinScale() const
Chris@0 911 {
Chris@1093 912 return m_binScale;
Chris@0 913 }
Chris@0 914
Chris@0 915 void
Chris@1103 916 SpectrogramLayer::setBinDisplay(BinDisplay binDisplay)
Chris@35 917 {
Chris@37 918 if (m_binDisplay == binDisplay) return;
Chris@35 919
Chris@1106 920 invalidateRenderers();
Chris@37 921 m_binDisplay = binDisplay;
Chris@35 922
Chris@35 923 emit layerParametersChanged();
Chris@35 924 }
Chris@35 925
Chris@1103 926 BinDisplay
Chris@37 927 SpectrogramLayer::getBinDisplay() const
Chris@35 928 {
Chris@37 929 return m_binDisplay;
Chris@35 930 }
Chris@35 931
Chris@35 932 void
Chris@1104 933 SpectrogramLayer::setNormalization(ColumnNormalization n)
Chris@36 934 {
Chris@862 935 if (m_normalization == n) return;
Chris@36 936
Chris@1106 937 invalidateRenderers();
Chris@119 938 invalidateMagnitudes();
Chris@862 939 m_normalization = n;
Chris@36 940
Chris@36 941 emit layerParametersChanged();
Chris@36 942 }
Chris@36 943
Chris@1104 944 ColumnNormalization
Chris@862 945 SpectrogramLayer::getNormalization() const
Chris@36 946 {
Chris@862 947 return m_normalization;
Chris@36 948 }
Chris@36 949
Chris@36 950 void
Chris@1104 951 SpectrogramLayer::setNormalizeVisibleArea(bool n)
Chris@1104 952 {
Chris@1104 953 if (m_normalizeVisibleArea == n) return;
Chris@1104 954
Chris@1106 955 invalidateRenderers();
Chris@1104 956 invalidateMagnitudes();
Chris@1104 957 m_normalizeVisibleArea = n;
Chris@1104 958
Chris@1104 959 emit layerParametersChanged();
Chris@1104 960 }
Chris@1104 961
Chris@1104 962 bool
Chris@1104 963 SpectrogramLayer::getNormalizeVisibleArea() const
Chris@1104 964 {
Chris@1104 965 return m_normalizeVisibleArea;
Chris@1104 966 }
Chris@1104 967
Chris@1104 968 void
Chris@918 969 SpectrogramLayer::setLayerDormant(const LayerGeometryProvider *v, bool dormant)
Chris@29 970 {
Chris@33 971 if (dormant) {
Chris@33 972
Chris@331 973 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@985 974 cerr << "SpectrogramLayer::setLayerDormant(" << dormant << ")"
Chris@585 975 << endl;
Chris@331 976 #endif
Chris@331 977
Chris@131 978 if (isLayerDormant(v)) {
Chris@131 979 return;
Chris@131 980 }
Chris@131 981
Chris@131 982 Layer::setLayerDormant(v, true);
Chris@33 983
Chris@1106 984 invalidateRenderers();
Chris@1030 985
Chris@1088 986 //!!! in theory we should call invalidateFFTModel() if and
Chris@1088 987 //!!! only if there are no remaining views in which we are not
Chris@1088 988 //!!! dormant
Chris@33 989
Chris@33 990 } else {
Chris@33 991
Chris@131 992 Layer::setLayerDormant(v, false);
Chris@33 993 }
Chris@29 994 }
Chris@29 995
Chris@29 996 void
Chris@0 997 SpectrogramLayer::cacheInvalid()
Chris@0 998 {
Chris@391 999 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@985 1000 cerr << "SpectrogramLayer::cacheInvalid()" << endl;
Chris@391 1001 #endif
Chris@391 1002
Chris@1106 1003 invalidateRenderers();
Chris@119 1004 invalidateMagnitudes();
Chris@0 1005 }
Chris@0 1006
Chris@0 1007 void
Chris@1037 1008 SpectrogramLayer::cacheInvalid(
Chris@1037 1009 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1037 1010 sv_frame_t from, sv_frame_t to
Chris@1037 1011 #else
Chris@1037 1012 sv_frame_t , sv_frame_t
Chris@1037 1013 #endif
Chris@1037 1014 )
Chris@0 1015 {
Chris@391 1016 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@985 1017 cerr << "SpectrogramLayer::cacheInvalid(" << from << ", " << to << ")" << endl;
Chris@391 1018 #endif
Chris@391 1019
Chris@1117 1020 //!!! now in common with Colour3DPlotLayer:
Chris@1030 1021 // We used to call invalidateMagnitudes(from, to) to invalidate
Chris@1030 1022 // only those caches whose views contained some of the (from, to)
Chris@1030 1023 // range. That's the right thing to do; it has been lost in
Chris@1030 1024 // pulling out the image cache code, but it might not matter very
Chris@1030 1025 // much, since the underlying models for spectrogram layers don't
Chris@1030 1026 // change very often. Let's see.
Chris@1106 1027 invalidateRenderers();
Chris@391 1028 invalidateMagnitudes();
Chris@0 1029 }
Chris@0 1030
Chris@224 1031 bool
Chris@224 1032 SpectrogramLayer::hasLightBackground() const
Chris@224 1033 {
Chris@287 1034 return ColourMapper(m_colourMap, 1.f, 255.f).hasLightBackground();
Chris@224 1035 }
Chris@224 1036
Chris@905 1037 double
Chris@40 1038 SpectrogramLayer::getEffectiveMinFrequency() const
Chris@40 1039 {
Chris@907 1040 sv_samplerate_t sr = m_model->getSampleRate();
Chris@1087 1041 double minf = double(sr) / getFFTSize();
Chris@40 1042
Chris@40 1043 if (m_minFrequency > 0.0) {
Chris@1087 1044 int minbin = int((double(m_minFrequency) * getFFTSize()) / sr + 0.01);
Chris@40 1045 if (minbin < 1) minbin = 1;
Chris@1087 1046 minf = minbin * sr / getFFTSize();
Chris@40 1047 }
Chris@40 1048
Chris@40 1049 return minf;
Chris@40 1050 }
Chris@40 1051
Chris@905 1052 double
Chris@40 1053 SpectrogramLayer::getEffectiveMaxFrequency() const
Chris@40 1054 {
Chris@907 1055 sv_samplerate_t sr = m_model->getSampleRate();
Chris@905 1056 double maxf = double(sr) / 2;
Chris@40 1057
Chris@40 1058 if (m_maxFrequency > 0.0) {
Chris@1087 1059 int maxbin = int((double(m_maxFrequency) * getFFTSize()) / sr + 0.1);
Chris@1087 1060 if (maxbin > getFFTSize() / 2) maxbin = getFFTSize() / 2;
Chris@1087 1061 maxf = maxbin * sr / getFFTSize();
Chris@40 1062 }
Chris@40 1063
Chris@40 1064 return maxf;
Chris@40 1065 }
Chris@40 1066
Chris@0 1067 bool
Chris@918 1068 SpectrogramLayer::getYBinRange(LayerGeometryProvider *v, int y, double &q0, double &q1) const
Chris@0 1069 {
Chris@382 1070 Profiler profiler("SpectrogramLayer::getYBinRange");
Chris@918 1071 int h = v->getPaintHeight();
Chris@0 1072 if (y < 0 || y >= h) return false;
Chris@1117 1073 q0 = getBinForY(v, y);
Chris@1117 1074 q1 = getBinForY(v, y-1);
Chris@1117 1075 return true;
Chris@1117 1076 }
Chris@1117 1077
Chris@1117 1078 double
Chris@1117 1079 SpectrogramLayer::getYForBin(const LayerGeometryProvider *v, double bin) const
Chris@1117 1080 {
Chris@1117 1081 double minf = getEffectiveMinFrequency();
Chris@1117 1082 double maxf = getEffectiveMaxFrequency();
Chris@1117 1083 bool logarithmic = (m_binScale == BinScale::Log);
Chris@1117 1084 sv_samplerate_t sr = m_model->getSampleRate();
Chris@1117 1085
Chris@1117 1086 double freq = (bin * sr) / getFFTSize();
Chris@1117 1087
Chris@1117 1088 double y = v->getYForFrequency(freq, minf, maxf, logarithmic);
Chris@1117 1089
Chris@1117 1090 return y;
Chris@1117 1091 }
Chris@1117 1092
Chris@1117 1093 double
Chris@1117 1094 SpectrogramLayer::getBinForY(const LayerGeometryProvider *v, double y) const
Chris@1117 1095 {
Chris@907 1096 sv_samplerate_t sr = m_model->getSampleRate();
Chris@905 1097 double minf = getEffectiveMinFrequency();
Chris@905 1098 double maxf = getEffectiveMaxFrequency();
Chris@0 1099
Chris@1103 1100 bool logarithmic = (m_binScale == BinScale::Log);
Chris@38 1101
Chris@1117 1102 double freq = v->getFrequencyForY(y, minf, maxf, logarithmic);
Chris@1117 1103
Chris@1117 1104 // Now map on to ("proportion of") actual bins
Chris@1117 1105 double bin = (freq * getFFTSize()) / sr;
Chris@1117 1106
Chris@1117 1107 return bin;
Chris@1085 1108 }
Chris@1085 1109
Chris@0 1110 bool
Chris@918 1111 SpectrogramLayer::getXBinRange(LayerGeometryProvider *v, int x, double &s0, double &s1) const
Chris@0 1112 {
Chris@907 1113 sv_frame_t modelStart = m_model->getStartFrame();
Chris@907 1114 sv_frame_t modelEnd = m_model->getEndFrame();
Chris@0 1115
Chris@0 1116 // Each pixel column covers an exact range of sample frames:
Chris@907 1117 sv_frame_t f0 = v->getFrameForX(x) - modelStart;
Chris@907 1118 sv_frame_t f1 = v->getFrameForX(x + 1) - modelStart - 1;
Chris@20 1119
Chris@41 1120 if (f1 < int(modelStart) || f0 > int(modelEnd)) {
Chris@41 1121 return false;
Chris@41 1122 }
Chris@20 1123
Chris@0 1124 // And that range may be drawn from a possibly non-integral
Chris@0 1125 // range of spectrogram windows:
Chris@0 1126
Chris@805 1127 int windowIncrement = getWindowIncrement();
Chris@905 1128 s0 = double(f0) / windowIncrement;
Chris@905 1129 s1 = double(f1) / windowIncrement;
Chris@0 1130
Chris@0 1131 return true;
Chris@0 1132 }
Chris@0 1133
Chris@0 1134 bool
Chris@918 1135 SpectrogramLayer::getXBinSourceRange(LayerGeometryProvider *v, int x, RealTime &min, RealTime &max) const
Chris@0 1136 {
Chris@905 1137 double s0 = 0, s1 = 0;
Chris@44 1138 if (!getXBinRange(v, x, s0, s1)) return false;
Chris@0 1139
Chris@0 1140 int s0i = int(s0 + 0.001);
Chris@0 1141 int s1i = int(s1);
Chris@0 1142
Chris@0 1143 int windowIncrement = getWindowIncrement();
Chris@0 1144 int w0 = s0i * windowIncrement - (m_windowSize - windowIncrement)/2;
Chris@0 1145 int w1 = s1i * windowIncrement + windowIncrement +
Chris@0 1146 (m_windowSize - windowIncrement)/2 - 1;
Chris@0 1147
Chris@0 1148 min = RealTime::frame2RealTime(w0, m_model->getSampleRate());
Chris@0 1149 max = RealTime::frame2RealTime(w1, m_model->getSampleRate());
Chris@0 1150 return true;
Chris@0 1151 }
Chris@0 1152
Chris@0 1153 bool
Chris@918 1154 SpectrogramLayer::getYBinSourceRange(LayerGeometryProvider *v, int y, double &freqMin, double &freqMax)
Chris@0 1155 const
Chris@0 1156 {
Chris@905 1157 double q0 = 0, q1 = 0;
Chris@44 1158 if (!getYBinRange(v, y, q0, q1)) return false;
Chris@0 1159
Chris@0 1160 int q0i = int(q0 + 0.001);
Chris@0 1161 int q1i = int(q1);
Chris@0 1162
Chris@907 1163 sv_samplerate_t sr = m_model->getSampleRate();
Chris@0 1164
Chris@0 1165 for (int q = q0i; q <= q1i; ++q) {
Chris@1087 1166 if (q == q0i) freqMin = (sr * q) / getFFTSize();
Chris@1087 1167 if (q == q1i) freqMax = (sr * (q+1)) / getFFTSize();
Chris@0 1168 }
Chris@0 1169 return true;
Chris@0 1170 }
Chris@35 1171
Chris@35 1172 bool
Chris@918 1173 SpectrogramLayer::getAdjustedYBinSourceRange(LayerGeometryProvider *v, int x, int y,
Chris@905 1174 double &freqMin, double &freqMax,
Chris@905 1175 double &adjFreqMin, double &adjFreqMax)
Chris@35 1176 const
Chris@35 1177 {
Chris@277 1178 if (!m_model || !m_model->isOK() || !m_model->isReady()) {
Chris@277 1179 return false;
Chris@277 1180 }
Chris@277 1181
Chris@1088 1182 FFTModel *fft = getFFTModel();
Chris@114 1183 if (!fft) return false;
Chris@110 1184
Chris@905 1185 double s0 = 0, s1 = 0;
Chris@44 1186 if (!getXBinRange(v, x, s0, s1)) return false;
Chris@35 1187
Chris@905 1188 double q0 = 0, q1 = 0;
Chris@44 1189 if (!getYBinRange(v, y, q0, q1)) return false;
Chris@35 1190
Chris@35 1191 int s0i = int(s0 + 0.001);
Chris@35 1192 int s1i = int(s1);
Chris@35 1193
Chris@35 1194 int q0i = int(q0 + 0.001);
Chris@35 1195 int q1i = int(q1);
Chris@35 1196
Chris@907 1197 sv_samplerate_t sr = m_model->getSampleRate();
Chris@35 1198
Chris@35 1199 bool haveAdj = false;
Chris@35 1200
Chris@1103 1201 bool peaksOnly = (m_binDisplay == BinDisplay::PeakBins ||
Chris@1103 1202 m_binDisplay == BinDisplay::PeakFrequencies);
Chris@37 1203
Chris@35 1204 for (int q = q0i; q <= q1i; ++q) {
Chris@35 1205
Chris@35 1206 for (int s = s0i; s <= s1i; ++s) {
Chris@35 1207
Chris@905 1208 double binfreq = (double(sr) * q) / m_windowSize;
Chris@35 1209 if (q == q0i) freqMin = binfreq;
Chris@35 1210 if (q == q1i) freqMax = binfreq;
Chris@37 1211
Chris@114 1212 if (peaksOnly && !fft->isLocalPeak(s, q)) continue;
Chris@38 1213
Chris@1086 1214 if (!fft->isOverThreshold
Chris@1087 1215 (s, q, float(m_threshold * double(getFFTSize())/2.0))) {
Chris@1086 1216 continue;
Chris@1086 1217 }
Chris@907 1218
Chris@907 1219 double freq = binfreq;
Chris@40 1220
Chris@114 1221 if (s < int(fft->getWidth()) - 1) {
Chris@38 1222
Chris@277 1223 fft->estimateStableFrequency(s, q, freq);
Chris@35 1224
Chris@38 1225 if (!haveAdj || freq < adjFreqMin) adjFreqMin = freq;
Chris@38 1226 if (!haveAdj || freq > adjFreqMax) adjFreqMax = freq;
Chris@35 1227
Chris@35 1228 haveAdj = true;
Chris@35 1229 }
Chris@35 1230 }
Chris@35 1231 }
Chris@35 1232
Chris@35 1233 if (!haveAdj) {
Chris@40 1234 adjFreqMin = adjFreqMax = 0.0;
Chris@35 1235 }
Chris@35 1236
Chris@35 1237 return haveAdj;
Chris@35 1238 }
Chris@0 1239
Chris@0 1240 bool
Chris@918 1241 SpectrogramLayer::getXYBinSourceRange(LayerGeometryProvider *v, int x, int y,
Chris@905 1242 double &min, double &max,
Chris@905 1243 double &phaseMin, double &phaseMax) const
Chris@0 1244 {
Chris@277 1245 if (!m_model || !m_model->isOK() || !m_model->isReady()) {
Chris@277 1246 return false;
Chris@277 1247 }
Chris@277 1248
Chris@905 1249 double q0 = 0, q1 = 0;
Chris@44 1250 if (!getYBinRange(v, y, q0, q1)) return false;
Chris@0 1251
Chris@905 1252 double s0 = 0, s1 = 0;
Chris@44 1253 if (!getXBinRange(v, x, s0, s1)) return false;
Chris@0 1254
Chris@0 1255 int q0i = int(q0 + 0.001);
Chris@0 1256 int q1i = int(q1);
Chris@0 1257
Chris@0 1258 int s0i = int(s0 + 0.001);
Chris@0 1259 int s1i = int(s1);
Chris@0 1260
Chris@37 1261 bool rv = false;
Chris@37 1262
Chris@1088 1263 FFTModel *fft = getFFTModel();
Chris@0 1264
Chris@114 1265 if (fft) {
Chris@114 1266
Chris@114 1267 int cw = fft->getWidth();
Chris@114 1268 int ch = fft->getHeight();
Chris@0 1269
Chris@110 1270 min = 0.0;
Chris@110 1271 max = 0.0;
Chris@110 1272 phaseMin = 0.0;
Chris@110 1273 phaseMax = 0.0;
Chris@110 1274 bool have = false;
Chris@0 1275
Chris@110 1276 for (int q = q0i; q <= q1i; ++q) {
Chris@110 1277 for (int s = s0i; s <= s1i; ++s) {
Chris@110 1278 if (s >= 0 && q >= 0 && s < cw && q < ch) {
Chris@117 1279
Chris@905 1280 double value;
Chris@38 1281
Chris@114 1282 value = fft->getPhaseAt(s, q);
Chris@110 1283 if (!have || value < phaseMin) { phaseMin = value; }
Chris@110 1284 if (!have || value > phaseMax) { phaseMax = value; }
Chris@91 1285
Chris@1087 1286 value = fft->getMagnitudeAt(s, q) / (getFFTSize()/2.0);
Chris@110 1287 if (!have || value < min) { min = value; }
Chris@110 1288 if (!have || value > max) { max = value; }
Chris@110 1289
Chris@110 1290 have = true;
Chris@110 1291 }
Chris@110 1292 }
Chris@110 1293 }
Chris@110 1294
Chris@110 1295 if (have) {
Chris@110 1296 rv = true;
Chris@110 1297 }
Chris@0 1298 }
Chris@0 1299
Chris@37 1300 return rv;
Chris@0 1301 }
Chris@114 1302
Chris@130 1303 FFTModel *
Chris@1088 1304 SpectrogramLayer::getFFTModel() const
Chris@114 1305 {
Chris@114 1306 if (!m_model) return 0;
Chris@114 1307
Chris@1087 1308 int fftSize = getFFTSize();
Chris@114 1309
Chris@1088 1310 //!!! it is now surely slower to do this on every getFFTModel()
Chris@1088 1311 //!!! request than it would be to recreate the model immediately
Chris@1088 1312 //!!! when something changes instead of just invalidating it
Chris@920 1313
Chris@1088 1314 if (m_fftModel &&
Chris@1088 1315 m_fftModel->getHeight() == fftSize / 2 + 1 &&
Chris@1088 1316 m_fftModel->getWindowIncrement() == getWindowIncrement()) {
Chris@1088 1317 return m_fftModel;
Chris@114 1318 }
Chris@1088 1319
Chris@1088 1320 delete m_peakCache;
Chris@1088 1321 m_peakCache = 0;
Chris@1088 1322
Chris@1088 1323 delete m_fftModel;
Chris@1088 1324 m_fftModel = new FFTModel(m_model,
Chris@1088 1325 m_channel,
Chris@1088 1326 m_windowType,
Chris@1088 1327 m_windowSize,
Chris@1088 1328 getWindowIncrement(),
Chris@1088 1329 fftSize);
Chris@1088 1330
Chris@1088 1331 if (!m_fftModel->isOK()) {
Chris@1088 1332 QMessageBox::critical
Chris@1088 1333 (0, tr("FFT cache failed"),
Chris@1088 1334 tr("Failed to create the FFT model for this spectrogram.\n"
Chris@1088 1335 "There may be insufficient memory or disc space to continue."));
Chris@1088 1336 delete m_fftModel;
Chris@1088 1337 m_fftModel = 0;
Chris@1088 1338 return 0;
Chris@114 1339 }
Chris@114 1340
Chris@1088 1341 ((SpectrogramLayer *)this)->sliceableModelReplaced(0, m_fftModel);
Chris@1088 1342
Chris@1088 1343 return m_fftModel;
Chris@114 1344 }
Chris@114 1345
Chris@484 1346 Dense3DModelPeakCache *
Chris@1088 1347 SpectrogramLayer::getPeakCache() const
Chris@484 1348 {
Chris@1088 1349 //!!! see comment in getFFTModel
Chris@1088 1350
Chris@1088 1351 if (!m_peakCache) {
Chris@1088 1352 FFTModel *f = getFFTModel();
Chris@484 1353 if (!f) return 0;
Chris@1088 1354 m_peakCache = new Dense3DModelPeakCache(f, m_peakCacheDivisor);
Chris@484 1355 }
Chris@1088 1356 return m_peakCache;
Chris@484 1357 }
Chris@484 1358
Chris@193 1359 const Model *
Chris@193 1360 SpectrogramLayer::getSliceableModel() const
Chris@193 1361 {
Chris@1088 1362 return m_fftModel;
Chris@193 1363 }
Chris@193 1364
Chris@114 1365 void
Chris@1088 1366 SpectrogramLayer::invalidateFFTModel()
Chris@114 1367 {
Chris@1044 1368 #ifdef DEBUG_SPECTROGRAM
Chris@1088 1369 cerr << "SpectrogramLayer::invalidateFFTModel called" << endl;
Chris@1044 1370 #endif
Chris@1088 1371
Chris@1088 1372 emit sliceableModelReplaced(m_fftModel, 0);
Chris@1088 1373
Chris@1088 1374 delete m_fftModel;
Chris@1088 1375 delete m_peakCache;
Chris@1088 1376
Chris@1088 1377 m_fftModel = 0;
Chris@1088 1378 m_peakCache = 0;
Chris@114 1379 }
Chris@114 1380
Chris@0 1381 void
Chris@119 1382 SpectrogramLayer::invalidateMagnitudes()
Chris@119 1383 {
Chris@1044 1384 #ifdef DEBUG_SPECTROGRAM
Chris@1044 1385 cerr << "SpectrogramLayer::invalidateMagnitudes called" << endl;
Chris@1044 1386 #endif
Chris@119 1387 m_viewMags.clear();
Chris@119 1388 }
Chris@1134 1389
Chris@119 1390 void
Chris@389 1391 SpectrogramLayer::setSynchronousPainting(bool synchronous)
Chris@389 1392 {
Chris@389 1393 m_synchronous = synchronous;
Chris@389 1394 }
Chris@389 1395
Chris@1089 1396 Colour3DPlotRenderer *
Chris@1089 1397 SpectrogramLayer::getRenderer(LayerGeometryProvider *v) const
Chris@1089 1398 {
Chris@1089 1399 if (m_renderers.find(v->getId()) == m_renderers.end()) {
Chris@1089 1400
Chris@1089 1401 Colour3DPlotRenderer::Sources sources;
Chris@1089 1402 sources.verticalBinLayer = this;
Chris@1090 1403 sources.fft = getFFTModel();
Chris@1090 1404 sources.source = sources.fft;
Chris@1090 1405 sources.peaks = getPeakCache();
Chris@1089 1406
Chris@1092 1407 ColourScale::Parameters cparams;
Chris@1092 1408 cparams.colourMap = m_colourMap;
Chris@1092 1409 cparams.scale = m_colourScale;
Chris@1129 1410
Chris@1129 1411 if (m_colourScale != ColourScaleType::Phase) {
Chris@1129 1412 cparams.gain = m_gain;
Chris@1129 1413 cparams.threshold = m_threshold;
Chris@1129 1414 }
Chris@1093 1415
Chris@1125 1416 if (m_colourScale == ColourScaleType::Linear &&
Chris@1104 1417 m_normalization == ColumnNormalization::None) {
Chris@1125 1418 //!!! This should not be necessary -- what is the actual range
Chris@1125 1419 cparams.maxValue = 0.1;
Chris@1129 1420 if (cparams.maxValue <= m_threshold) {
Chris@1129 1421 cparams.maxValue = m_threshold + 0.1;
Chris@1129 1422 }
Chris@1093 1423 }
Chris@1129 1424
Chris@1089 1425 Colour3DPlotRenderer::Parameters params;
Chris@1092 1426 params.colourScale = ColourScale(cparams);
Chris@1089 1427 params.normalization = m_normalization;
Chris@1093 1428 params.binDisplay = m_binDisplay;
Chris@1093 1429 params.binScale = m_binScale;
Chris@1125 1430 params.alwaysOpaque = false; //!!! should be true though
Chris@1093 1431 params.invertVertical = false;
Chris@1125 1432 params.scaleFactor = 1.0;
Chris@1112 1433 params.colourRotation = m_colourRotation;
Chris@1093 1434
Chris@1129 1435 if (m_colourScale != ColourScaleType::Phase) {
Chris@1125 1436 params.scaleFactor *= 2.f / float(getFFTSize());
Chris@1125 1437 }
Chris@1125 1438
Chris@1093 1439 Preferences::SpectrogramSmoothing smoothing =
Chris@1093 1440 Preferences::getInstance()->getSpectrogramSmoothing();
Chris@1093 1441 params.interpolate =
Chris@1093 1442 (smoothing == Preferences::SpectrogramInterpolated ||
Chris@1093 1443 smoothing == Preferences::SpectrogramZeroPaddedAndInterpolated);
Chris@1089 1444
Chris@1089 1445 m_renderers[v->getId()] = new Colour3DPlotRenderer(sources, params);
Chris@1089 1446 }
Chris@1089 1447
Chris@1089 1448 return m_renderers[v->getId()];
Chris@1089 1449 }
Chris@1089 1450
Chris@1089 1451 void
Chris@1106 1452 SpectrogramLayer::paintWithRenderer(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
Chris@1106 1453 {
Chris@1121 1454 Colour3DPlotRenderer *renderer = getRenderer(v);
Chris@1121 1455
Chris@1121 1456 Colour3DPlotRenderer::RenderResult result;
Chris@1122 1457 MagnitudeRange magRange;
Chris@1122 1458 int viewId = v->getId();
Chris@1122 1459
Chris@1122 1460 if (!renderer->geometryChanged(v)) {
Chris@1122 1461 magRange = m_viewMags[viewId];
Chris@1122 1462 }
Chris@1106 1463
Chris@1106 1464 if (m_synchronous) {
Chris@1121 1465
Chris@1121 1466 result = renderer->render(v, paint, rect);
Chris@1121 1467
Chris@1121 1468 } else {
Chris@1121 1469
Chris@1121 1470 result = renderer->renderTimeConstrained(v, paint, rect);
Chris@1121 1471
Chris@1122 1472 cerr << "rect width from this paint: " << result.rendered.width()
Chris@1122 1473 << ", mag range in this paint: " << result.range.getMin() << " -> "
Chris@1121 1474 << result.range.getMax() << endl;
Chris@1121 1475
Chris@1121 1476 QRect uncached = renderer->getLargestUncachedRect(v);
Chris@1121 1477 if (uncached.width() > 0) {
Chris@1121 1478 v->updatePaintRect(uncached);
Chris@1121 1479 }
Chris@1106 1480 }
Chris@1106 1481
Chris@1122 1482 magRange.sample(result.range);
Chris@1122 1483
Chris@1122 1484 if (magRange.isSet()) {
Chris@1123 1485 if (!(m_viewMags[viewId] == magRange)) {
Chris@1122 1486 m_viewMags[viewId] = magRange;
Chris@1123 1487 //!!! now need to do the normalise-visible thing
Chris@1122 1488 }
Chris@1122 1489 }
Chris@1106 1490
Chris@1121 1491 cerr << "mag range in this view: "
Chris@1122 1492 << m_viewMags[viewId].getMin()
Chris@1121 1493 << " -> "
Chris@1122 1494 << m_viewMags[viewId].getMax()
Chris@1121 1495 << endl;
Chris@1106 1496 }
Chris@1106 1497
Chris@1106 1498 void
Chris@916 1499 SpectrogramLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
Chris@0 1500 {
Chris@334 1501 Profiler profiler("SpectrogramLayer::paint", false);
Chris@334 1502
Chris@0 1503 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1026 1504 cerr << "SpectrogramLayer::paint() entering: m_model is " << m_model << ", zoom level is " << v->getZoomLevel() << endl;
Chris@95 1505
Chris@1026 1506 cerr << "SpectrogramLayer::paint(): rect is " << rect.x() << "," << rect.y() << " " << rect.width() << "x" << rect.height() << endl;
Chris@0 1507 #endif
Chris@95 1508
Chris@0 1509 if (!m_model || !m_model->isOK() || !m_model->isReady()) {
Chris@0 1510 return;
Chris@0 1511 }
Chris@0 1512
Chris@47 1513 if (isLayerDormant(v)) {
Chris@587 1514 SVDEBUG << "SpectrogramLayer::paint(): Layer is dormant, making it undormant again" << endl;
Chris@29 1515 }
Chris@29 1516
Chris@1106 1517 paintWithRenderer(v, paint, rect);
Chris@480 1518 }
Chris@477 1519
Chris@121 1520 void
Chris@918 1521 SpectrogramLayer::illuminateLocalFeatures(LayerGeometryProvider *v, QPainter &paint) const
Chris@121 1522 {
Chris@382 1523 Profiler profiler("SpectrogramLayer::illuminateLocalFeatures");
Chris@382 1524
Chris@121 1525 QPoint localPos;
Chris@121 1526 if (!v->shouldIlluminateLocalFeatures(this, localPos) || !m_model) {
Chris@121 1527 return;
Chris@121 1528 }
Chris@121 1529
Chris@1134 1530 cerr << "SpectrogramLayer: illuminateLocalFeatures("
Chris@1134 1531 << localPos.x() << "," << localPos.y() << ")" << endl;
Chris@121 1532
Chris@905 1533 double s0, s1;
Chris@905 1534 double f0, f1;
Chris@121 1535
Chris@121 1536 if (getXBinRange(v, localPos.x(), s0, s1) &&
Chris@121 1537 getYBinSourceRange(v, localPos.y(), f0, f1)) {
Chris@121 1538
Chris@121 1539 int s0i = int(s0 + 0.001);
Chris@121 1540 int s1i = int(s1);
Chris@121 1541
Chris@121 1542 int x0 = v->getXForFrame(s0i * getWindowIncrement());
Chris@121 1543 int x1 = v->getXForFrame((s1i + 1) * getWindowIncrement());
Chris@121 1544
Chris@248 1545 int y1 = int(getYForFrequency(v, f1));
Chris@248 1546 int y0 = int(getYForFrequency(v, f0));
Chris@121 1547
Chris@1134 1548 cerr << "SpectrogramLayer: illuminate "
Chris@1134 1549 << x0 << "," << y1 << " -> " << x1 << "," << y0 << endl;
Chris@121 1550
Chris@287 1551 paint.setPen(v->getForeground());
Chris@133 1552
Chris@133 1553 //!!! should we be using paintCrosshairs for this?
Chris@133 1554
Chris@121 1555 paint.drawRect(x0, y1, x1 - x0 + 1, y0 - y1 + 1);
Chris@121 1556 }
Chris@121 1557 }
Chris@121 1558
Chris@905 1559 double
Chris@918 1560 SpectrogramLayer::getYForFrequency(const LayerGeometryProvider *v, double frequency) const
Chris@42 1561 {
Chris@44 1562 return v->getYForFrequency(frequency,
Chris@44 1563 getEffectiveMinFrequency(),
Chris@44 1564 getEffectiveMaxFrequency(),
Chris@1103 1565 m_binScale == BinScale::Log);
Chris@42 1566 }
Chris@42 1567
Chris@905 1568 double
Chris@918 1569 SpectrogramLayer::getFrequencyForY(const LayerGeometryProvider *v, int y) const
Chris@42 1570 {
Chris@44 1571 return v->getFrequencyForY(y,
Chris@44 1572 getEffectiveMinFrequency(),
Chris@44 1573 getEffectiveMaxFrequency(),
Chris@1103 1574 m_binScale == BinScale::Log);
Chris@42 1575 }
Chris@42 1576
Chris@0 1577 int
Chris@1090 1578 SpectrogramLayer::getCompletion(LayerGeometryProvider *) const
Chris@0 1579 {
Chris@1088 1580 if (!m_fftModel) return 100;
Chris@1088 1581 int completion = m_fftModel->getCompletion();
Chris@224 1582 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@985 1583 cerr << "SpectrogramLayer::getCompletion: completion = " << completion << endl;
Chris@224 1584 #endif
Chris@0 1585 return completion;
Chris@0 1586 }
Chris@0 1587
Chris@583 1588 QString
Chris@1090 1589 SpectrogramLayer::getError(LayerGeometryProvider *) const
Chris@583 1590 {
Chris@1088 1591 if (!m_fftModel) return "";
Chris@1088 1592 return m_fftModel->getError();
Chris@583 1593 }
Chris@583 1594
Chris@28 1595 bool
Chris@905 1596 SpectrogramLayer::getValueExtents(double &min, double &max,
Chris@101 1597 bool &logarithmic, QString &unit) const
Chris@79 1598 {
Chris@133 1599 if (!m_model) return false;
Chris@133 1600
Chris@907 1601 sv_samplerate_t sr = m_model->getSampleRate();
Chris@1087 1602 min = double(sr) / getFFTSize();
Chris@905 1603 max = double(sr) / 2;
Chris@133 1604
Chris@1103 1605 logarithmic = (m_binScale == BinScale::Log);
Chris@79 1606 unit = "Hz";
Chris@79 1607 return true;
Chris@79 1608 }
Chris@79 1609
Chris@79 1610 bool
Chris@905 1611 SpectrogramLayer::getDisplayExtents(double &min, double &max) const
Chris@101 1612 {
Chris@101 1613 min = getEffectiveMinFrequency();
Chris@101 1614 max = getEffectiveMaxFrequency();
Chris@253 1615
Chris@587 1616 // SVDEBUG << "SpectrogramLayer::getDisplayExtents: " << min << "->" << max << endl;
Chris@101 1617 return true;
Chris@101 1618 }
Chris@101 1619
Chris@101 1620 bool
Chris@905 1621 SpectrogramLayer::setDisplayExtents(double min, double max)
Chris@120 1622 {
Chris@120 1623 if (!m_model) return false;
Chris@187 1624
Chris@587 1625 // SVDEBUG << "SpectrogramLayer::setDisplayExtents: " << min << "->" << max << endl;
Chris@187 1626
Chris@120 1627 if (min < 0) min = 0;
Chris@907 1628 if (max > m_model->getSampleRate()/2.0) max = m_model->getSampleRate()/2.0;
Chris@120 1629
Chris@907 1630 int minf = int(lrint(min));
Chris@907 1631 int maxf = int(lrint(max));
Chris@120 1632
Chris@120 1633 if (m_minFrequency == minf && m_maxFrequency == maxf) return true;
Chris@120 1634
Chris@1106 1635 invalidateRenderers();
Chris@120 1636 invalidateMagnitudes();
Chris@120 1637
Chris@120 1638 m_minFrequency = minf;
Chris@120 1639 m_maxFrequency = maxf;
Chris@120 1640
Chris@120 1641 emit layerParametersChanged();
Chris@120 1642
Chris@133 1643 int vs = getCurrentVerticalZoomStep();
Chris@133 1644 if (vs != m_lastEmittedZoomStep) {
Chris@133 1645 emit verticalZoomChanged();
Chris@133 1646 m_lastEmittedZoomStep = vs;
Chris@133 1647 }
Chris@133 1648
Chris@120 1649 return true;
Chris@120 1650 }
Chris@120 1651
Chris@120 1652 bool
Chris@918 1653 SpectrogramLayer::getYScaleValue(const LayerGeometryProvider *v, int y,
Chris@905 1654 double &value, QString &unit) const
Chris@261 1655 {
Chris@261 1656 value = getFrequencyForY(v, y);
Chris@261 1657 unit = "Hz";
Chris@261 1658 return true;
Chris@261 1659 }
Chris@261 1660
Chris@261 1661 bool
Chris@918 1662 SpectrogramLayer::snapToFeatureFrame(LayerGeometryProvider *,
Chris@907 1663 sv_frame_t &frame,
Chris@805 1664 int &resolution,
Chris@28 1665 SnapType snap) const
Chris@13 1666 {
Chris@13 1667 resolution = getWindowIncrement();
Chris@907 1668 sv_frame_t left = (frame / resolution) * resolution;
Chris@907 1669 sv_frame_t right = left + resolution;
Chris@28 1670
Chris@28 1671 switch (snap) {
Chris@28 1672 case SnapLeft: frame = left; break;
Chris@28 1673 case SnapRight: frame = right; break;
Chris@28 1674 case SnapNearest:
Chris@28 1675 case SnapNeighbouring:
Chris@28 1676 if (frame - left > right - frame) frame = right;
Chris@28 1677 else frame = left;
Chris@28 1678 break;
Chris@28 1679 }
Chris@28 1680
Chris@28 1681 return true;
Chris@28 1682 }
Chris@13 1683
Chris@283 1684 void
Chris@1106 1685 SpectrogramLayer::measureDoubleClick(LayerGeometryProvider *, QMouseEvent *)
Chris@283 1686 {
Chris@1106 1687 /*!!! replace this
Chris@920 1688 const View *view = v->getView();
Chris@1106 1689
Chris@1030 1690 ScrollableImageCache &cache = getImageCacheReference(view);
Chris@1030 1691
Chris@1030 1692 cerr << "cache width: " << cache.getSize().width() << ", height: "
Chris@1030 1693 << cache.getSize().height() << endl;
Chris@1030 1694
Chris@1030 1695 QImage image = cache.getImage();
Chris@283 1696
Chris@283 1697 ImageRegionFinder finder;
Chris@283 1698 QRect rect = finder.findRegionExtents(&image, e->pos());
Chris@283 1699 if (rect.isValid()) {
Chris@283 1700 MeasureRect mr;
Chris@283 1701 setMeasureRectFromPixrect(v, mr, rect);
Chris@283 1702 CommandHistory::getInstance()->addCommand
Chris@283 1703 (new AddMeasurementRectCommand(this, mr));
Chris@283 1704 }
Chris@1106 1705 */
Chris@283 1706 }
Chris@283 1707
Chris@77 1708 bool
Chris@918 1709 SpectrogramLayer::getCrosshairExtents(LayerGeometryProvider *v, QPainter &paint,
Chris@77 1710 QPoint cursorPos,
Chris@1025 1711 vector<QRect> &extents) const
Chris@77 1712 {
Chris@918 1713 QRect vertical(cursorPos.x() - 12, 0, 12, v->getPaintHeight());
Chris@77 1714 extents.push_back(vertical);
Chris@77 1715
Chris@77 1716 QRect horizontal(0, cursorPos.y(), cursorPos.x(), 1);
Chris@77 1717 extents.push_back(horizontal);
Chris@77 1718
Chris@608 1719 int sw = getVerticalScaleWidth(v, m_haveDetailedScale, paint);
Chris@264 1720
Chris@280 1721 QRect freq(sw, cursorPos.y() - paint.fontMetrics().ascent() - 2,
Chris@280 1722 paint.fontMetrics().width("123456 Hz") + 2,
Chris@280 1723 paint.fontMetrics().height());
Chris@280 1724 extents.push_back(freq);
Chris@264 1725
Chris@279 1726 QRect pitch(sw, cursorPos.y() + 2,
Chris@279 1727 paint.fontMetrics().width("C#10+50c") + 2,
Chris@279 1728 paint.fontMetrics().height());
Chris@279 1729 extents.push_back(pitch);
Chris@279 1730
Chris@280 1731 QRect rt(cursorPos.x(),
Chris@918 1732 v->getPaintHeight() - paint.fontMetrics().height() - 2,
Chris@280 1733 paint.fontMetrics().width("1234.567 s"),
Chris@280 1734 paint.fontMetrics().height());
Chris@280 1735 extents.push_back(rt);
Chris@280 1736
Chris@280 1737 int w(paint.fontMetrics().width("1234567890") + 2);
Chris@280 1738 QRect frame(cursorPos.x() - w - 2,
Chris@918 1739 v->getPaintHeight() - paint.fontMetrics().height() - 2,
Chris@280 1740 w,
Chris@280 1741 paint.fontMetrics().height());
Chris@280 1742 extents.push_back(frame);
Chris@280 1743
Chris@77 1744 return true;
Chris@77 1745 }
Chris@77 1746
Chris@77 1747 void
Chris@918 1748 SpectrogramLayer::paintCrosshairs(LayerGeometryProvider *v, QPainter &paint,
Chris@77 1749 QPoint cursorPos) const
Chris@77 1750 {
Chris@77 1751 paint.save();
Chris@283 1752
Chris@608 1753 int sw = getVerticalScaleWidth(v, m_haveDetailedScale, paint);
Chris@283 1754
Chris@282 1755 QFont fn = paint.font();
Chris@282 1756 if (fn.pointSize() > 8) {
Chris@282 1757 fn.setPointSize(fn.pointSize() - 1);
Chris@282 1758 paint.setFont(fn);
Chris@282 1759 }
Chris@77 1760 paint.setPen(m_crosshairColour);
Chris@77 1761
Chris@77 1762 paint.drawLine(0, cursorPos.y(), cursorPos.x() - 1, cursorPos.y());
Chris@918 1763 paint.drawLine(cursorPos.x(), 0, cursorPos.x(), v->getPaintHeight());
Chris@77 1764
Chris@905 1765 double fundamental = getFrequencyForY(v, cursorPos.y());
Chris@77 1766
Chris@1078 1767 PaintAssistant::drawVisibleText(v, paint,
Chris@278 1768 sw + 2,
Chris@278 1769 cursorPos.y() - 2,
Chris@278 1770 QString("%1 Hz").arg(fundamental),
Chris@1078 1771 PaintAssistant::OutlinedText);
Chris@278 1772
Chris@279 1773 if (Pitch::isFrequencyInMidiRange(fundamental)) {
Chris@279 1774 QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental);
Chris@1078 1775 PaintAssistant::drawVisibleText(v, paint,
Chris@279 1776 sw + 2,
Chris@279 1777 cursorPos.y() + paint.fontMetrics().ascent() + 2,
Chris@279 1778 pitchLabel,
Chris@1078 1779 PaintAssistant::OutlinedText);
Chris@279 1780 }
Chris@279 1781
Chris@907 1782 sv_frame_t frame = v->getFrameForX(cursorPos.x());
Chris@279 1783 RealTime rt = RealTime::frame2RealTime(frame, m_model->getSampleRate());
Chris@280 1784 QString rtLabel = QString("%1 s").arg(rt.toText(true).c_str());
Chris@280 1785 QString frameLabel = QString("%1").arg(frame);
Chris@1078 1786 PaintAssistant::drawVisibleText(v, paint,
Chris@280 1787 cursorPos.x() - paint.fontMetrics().width(frameLabel) - 2,
Chris@918 1788 v->getPaintHeight() - 2,
Chris@280 1789 frameLabel,
Chris@1078 1790 PaintAssistant::OutlinedText);
Chris@1078 1791 PaintAssistant::drawVisibleText(v, paint,
Chris@280 1792 cursorPos.x() + 2,
Chris@918 1793 v->getPaintHeight() - 2,
Chris@280 1794 rtLabel,
Chris@1078 1795 PaintAssistant::OutlinedText);
Chris@264 1796
Chris@77 1797 int harmonic = 2;
Chris@77 1798
Chris@77 1799 while (harmonic < 100) {
Chris@77 1800
Chris@907 1801 int hy = int(lrint(getYForFrequency(v, fundamental * harmonic)));
Chris@918 1802 if (hy < 0 || hy > v->getPaintHeight()) break;
Chris@77 1803
Chris@77 1804 int len = 7;
Chris@77 1805
Chris@77 1806 if (harmonic % 2 == 0) {
Chris@77 1807 if (harmonic % 4 == 0) {
Chris@77 1808 len = 12;
Chris@77 1809 } else {
Chris@77 1810 len = 10;
Chris@77 1811 }
Chris@77 1812 }
Chris@77 1813
Chris@77 1814 paint.drawLine(cursorPos.x() - len,
Chris@907 1815 hy,
Chris@77 1816 cursorPos.x(),
Chris@907 1817 hy);
Chris@77 1818
Chris@77 1819 ++harmonic;
Chris@77 1820 }
Chris@77 1821
Chris@77 1822 paint.restore();
Chris@77 1823 }
Chris@77 1824
Chris@25 1825 QString
Chris@918 1826 SpectrogramLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
Chris@25 1827 {
Chris@25 1828 int x = pos.x();
Chris@25 1829 int y = pos.y();
Chris@0 1830
Chris@25 1831 if (!m_model || !m_model->isOK()) return "";
Chris@0 1832
Chris@905 1833 double magMin = 0, magMax = 0;
Chris@905 1834 double phaseMin = 0, phaseMax = 0;
Chris@905 1835 double freqMin = 0, freqMax = 0;
Chris@905 1836 double adjFreqMin = 0, adjFreqMax = 0;
Chris@25 1837 QString pitchMin, pitchMax;
Chris@0 1838 RealTime rtMin, rtMax;
Chris@0 1839
Chris@38 1840 bool haveValues = false;
Chris@0 1841
Chris@44 1842 if (!getXBinSourceRange(v, x, rtMin, rtMax)) {
Chris@38 1843 return "";
Chris@38 1844 }
Chris@44 1845 if (getXYBinSourceRange(v, x, y, magMin, magMax, phaseMin, phaseMax)) {
Chris@38 1846 haveValues = true;
Chris@38 1847 }
Chris@0 1848
Chris@35 1849 QString adjFreqText = "", adjPitchText = "";
Chris@35 1850
Chris@1103 1851 if (m_binDisplay == BinDisplay::PeakFrequencies) {
Chris@35 1852
Chris@44 1853 if (!getAdjustedYBinSourceRange(v, x, y, freqMin, freqMax,
Chris@38 1854 adjFreqMin, adjFreqMax)) {
Chris@38 1855 return "";
Chris@38 1856 }
Chris@35 1857
Chris@35 1858 if (adjFreqMin != adjFreqMax) {
Chris@65 1859 adjFreqText = tr("Peak Frequency:\t%1 - %2 Hz\n")
Chris@35 1860 .arg(adjFreqMin).arg(adjFreqMax);
Chris@35 1861 } else {
Chris@65 1862 adjFreqText = tr("Peak Frequency:\t%1 Hz\n")
Chris@35 1863 .arg(adjFreqMin);
Chris@38 1864 }
Chris@38 1865
Chris@38 1866 QString pmin = Pitch::getPitchLabelForFrequency(adjFreqMin);
Chris@38 1867 QString pmax = Pitch::getPitchLabelForFrequency(adjFreqMax);
Chris@38 1868
Chris@38 1869 if (pmin != pmax) {
Chris@65 1870 adjPitchText = tr("Peak Pitch:\t%3 - %4\n").arg(pmin).arg(pmax);
Chris@38 1871 } else {
Chris@65 1872 adjPitchText = tr("Peak Pitch:\t%2\n").arg(pmin);
Chris@35 1873 }
Chris@35 1874
Chris@35 1875 } else {
Chris@35 1876
Chris@44 1877 if (!getYBinSourceRange(v, y, freqMin, freqMax)) return "";
Chris@35 1878 }
Chris@35 1879
Chris@25 1880 QString text;
Chris@25 1881
Chris@25 1882 if (rtMin != rtMax) {
Chris@25 1883 text += tr("Time:\t%1 - %2\n")
Chris@25 1884 .arg(rtMin.toText(true).c_str())
Chris@25 1885 .arg(rtMax.toText(true).c_str());
Chris@25 1886 } else {
Chris@25 1887 text += tr("Time:\t%1\n")
Chris@25 1888 .arg(rtMin.toText(true).c_str());
Chris@0 1889 }
Chris@0 1890
Chris@25 1891 if (freqMin != freqMax) {
Chris@65 1892 text += tr("%1Bin Frequency:\t%2 - %3 Hz\n%4Bin Pitch:\t%5 - %6\n")
Chris@65 1893 .arg(adjFreqText)
Chris@25 1894 .arg(freqMin)
Chris@25 1895 .arg(freqMax)
Chris@65 1896 .arg(adjPitchText)
Chris@65 1897 .arg(Pitch::getPitchLabelForFrequency(freqMin))
Chris@65 1898 .arg(Pitch::getPitchLabelForFrequency(freqMax));
Chris@65 1899 } else {
Chris@65 1900 text += tr("%1Bin Frequency:\t%2 Hz\n%3Bin Pitch:\t%4\n")
Chris@35 1901 .arg(adjFreqText)
Chris@25 1902 .arg(freqMin)
Chris@65 1903 .arg(adjPitchText)
Chris@65 1904 .arg(Pitch::getPitchLabelForFrequency(freqMin));
Chris@25 1905 }
Chris@25 1906
Chris@38 1907 if (haveValues) {
Chris@905 1908 double dbMin = AudioLevel::multiplier_to_dB(magMin);
Chris@905 1909 double dbMax = AudioLevel::multiplier_to_dB(magMax);
Chris@43 1910 QString dbMinString;
Chris@43 1911 QString dbMaxString;
Chris@43 1912 if (dbMin == AudioLevel::DB_FLOOR) {
Chris@43 1913 dbMinString = tr("-Inf");
Chris@43 1914 } else {
Chris@907 1915 dbMinString = QString("%1").arg(lrint(dbMin));
Chris@43 1916 }
Chris@43 1917 if (dbMax == AudioLevel::DB_FLOOR) {
Chris@43 1918 dbMaxString = tr("-Inf");
Chris@43 1919 } else {
Chris@907 1920 dbMaxString = QString("%1").arg(lrint(dbMax));
Chris@43 1921 }
Chris@907 1922 if (lrint(dbMin) != lrint(dbMax)) {
Chris@199 1923 text += tr("dB:\t%1 - %2").arg(dbMinString).arg(dbMaxString);
Chris@25 1924 } else {
Chris@199 1925 text += tr("dB:\t%1").arg(dbMinString);
Chris@25 1926 }
Chris@38 1927 if (phaseMin != phaseMax) {
Chris@38 1928 text += tr("\nPhase:\t%1 - %2").arg(phaseMin).arg(phaseMax);
Chris@38 1929 } else {
Chris@38 1930 text += tr("\nPhase:\t%1").arg(phaseMin);
Chris@38 1931 }
Chris@25 1932 }
Chris@25 1933
Chris@25 1934 return text;
Chris@0 1935 }
Chris@25 1936
Chris@0 1937 int
Chris@40 1938 SpectrogramLayer::getColourScaleWidth(QPainter &paint) const
Chris@40 1939 {
Chris@40 1940 int cw;
Chris@40 1941
Chris@119 1942 cw = paint.fontMetrics().width("-80dB");
Chris@119 1943
Chris@40 1944 return cw;
Chris@40 1945 }
Chris@40 1946
Chris@40 1947 int
Chris@918 1948 SpectrogramLayer::getVerticalScaleWidth(LayerGeometryProvider *, bool detailed, QPainter &paint) const
Chris@0 1949 {
Chris@0 1950 if (!m_model || !m_model->isOK()) return 0;
Chris@0 1951
Chris@607 1952 int cw = 0;
Chris@607 1953 if (detailed) cw = getColourScaleWidth(paint);
Chris@40 1954
Chris@0 1955 int tw = paint.fontMetrics().width(QString("%1")
Chris@0 1956 .arg(m_maxFrequency > 0 ?
Chris@0 1957 m_maxFrequency - 1 :
Chris@0 1958 m_model->getSampleRate() / 2));
Chris@0 1959
Chris@234 1960 int fw = paint.fontMetrics().width(tr("43Hz"));
Chris@0 1961 if (tw < fw) tw = fw;
Chris@40 1962
Chris@1103 1963 int tickw = (m_binScale == BinScale::Log ? 10 : 4);
Chris@0 1964
Chris@40 1965 return cw + tickw + tw + 13;
Chris@0 1966 }
Chris@0 1967
Chris@0 1968 void
Chris@918 1969 SpectrogramLayer::paintVerticalScale(LayerGeometryProvider *v, bool detailed, QPainter &paint, QRect rect) const
Chris@0 1970 {
Chris@0 1971 if (!m_model || !m_model->isOK()) {
Chris@0 1972 return;
Chris@0 1973 }
Chris@0 1974
Chris@382 1975 Profiler profiler("SpectrogramLayer::paintVerticalScale");
Chris@122 1976
Chris@120 1977 //!!! cache this?
Chris@120 1978
Chris@0 1979 int h = rect.height(), w = rect.width();
Chris@0 1980
Chris@1103 1981 int tickw = (m_binScale == BinScale::Log ? 10 : 4);
Chris@1103 1982 int pkw = (m_binScale == BinScale::Log ? 10 : 0);
Chris@40 1983
Chris@1087 1984 int bins = getFFTSize() / 2;
Chris@907 1985 sv_samplerate_t sr = m_model->getSampleRate();
Chris@0 1986
Chris@0 1987 if (m_maxFrequency > 0) {
Chris@1087 1988 bins = int((double(m_maxFrequency) * getFFTSize()) / sr + 0.1);
Chris@1087 1989 if (bins > getFFTSize() / 2) bins = getFFTSize() / 2;
Chris@0 1990 }
Chris@0 1991
Chris@607 1992 int cw = 0;
Chris@607 1993
Chris@607 1994 if (detailed) cw = getColourScaleWidth(paint);
Chris@119 1995 int cbw = paint.fontMetrics().width("dB");
Chris@40 1996
Chris@0 1997 int py = -1;
Chris@0 1998 int textHeight = paint.fontMetrics().height();
Chris@0 1999 int toff = -textHeight + paint.fontMetrics().ascent() + 2;
Chris@0 2000
Chris@607 2001 if (detailed && (h > textHeight * 3 + 10)) {
Chris@119 2002
Chris@119 2003 int topLines = 2;
Chris@1105 2004 if (m_colourScale == ColourScaleType::Phase) topLines = 1;
Chris@119 2005
Chris@119 2006 int ch = h - textHeight * (topLines + 1) - 8;
Chris@119 2007 // paint.drawRect(4, textHeight + 4, cw - 1, ch + 1);
Chris@119 2008 paint.drawRect(4 + cw - cbw, textHeight * topLines + 4, cbw - 1, ch + 1);
Chris@40 2009
Chris@40 2010 QString top, bottom;
Chris@1030 2011 double min = m_viewMags[v->getId()].getMin();
Chris@1030 2012 double max = m_viewMags[v->getId()].getMax();
Chris@905 2013
Chris@1127 2014 if (min < m_threshold) min = m_threshold;
Chris@1131 2015 if (max <= min) max = min + 0.1;
Chris@1127 2016
Chris@905 2017 double dBmin = AudioLevel::multiplier_to_dB(min);
Chris@905 2018 double dBmax = AudioLevel::multiplier_to_dB(max);
Chris@119 2019
Chris@1044 2020 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1044 2021 cerr << "paintVerticalScale: for view id " << v->getId()
Chris@1044 2022 << ": min = " << min << ", max = " << max
Chris@1044 2023 << ", dBmin = " << dBmin << ", dBmax = " << dBmax << endl;
Chris@1044 2024 #endif
Chris@1044 2025
Chris@120 2026 if (dBmax < -60.f) dBmax = -60.f;
Chris@907 2027 else top = QString("%1").arg(lrint(dBmax));
Chris@120 2028
Chris@120 2029 if (dBmin < dBmax - 60.f) dBmin = dBmax - 60.f;
Chris@907 2030 bottom = QString("%1").arg(lrint(dBmin));
Chris@119 2031
Chris@1125 2032 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1125 2033 cerr << "adjusted dB range to min = " << dBmin << ", max = " << dBmax
Chris@1125 2034 << endl;
Chris@1125 2035 #endif
Chris@1125 2036
Chris@119 2037 //!!! & phase etc
Chris@119 2038
Chris@1105 2039 if (m_colourScale != ColourScaleType::Phase) {
Chris@119 2040 paint.drawText((cw + 6 - paint.fontMetrics().width("dBFS")) / 2,
Chris@119 2041 2 + textHeight + toff, "dBFS");
Chris@119 2042 }
Chris@119 2043
Chris@119 2044 // paint.drawText((cw + 6 - paint.fontMetrics().width(top)) / 2,
Chris@119 2045 paint.drawText(3 + cw - cbw - paint.fontMetrics().width(top),
Chris@119 2046 2 + textHeight * topLines + toff + textHeight/2, top);
Chris@119 2047
Chris@119 2048 paint.drawText(3 + cw - cbw - paint.fontMetrics().width(bottom),
Chris@119 2049 h + toff - 3 - textHeight/2, bottom);
Chris@40 2050
Chris@40 2051 paint.save();
Chris@40 2052 paint.setBrush(Qt::NoBrush);
Chris@119 2053
Chris@119 2054 int lasty = 0;
Chris@119 2055 int lastdb = 0;
Chris@119 2056
Chris@40 2057 for (int i = 0; i < ch; ++i) {
Chris@119 2058
Chris@905 2059 double dBval = dBmin + (((dBmax - dBmin) * i) / (ch - 1));
Chris@119 2060 int idb = int(dBval);
Chris@119 2061
Chris@1125 2062 double value = AudioLevel::dB_to_multiplier(dBval);
Chris@1125 2063 paint.setPen(getRenderer(v)->getColour(value));
Chris@119 2064
Chris@119 2065 int y = textHeight * topLines + 4 + ch - i;
Chris@119 2066
Chris@119 2067 paint.drawLine(5 + cw - cbw, y, cw + 2, y);
Chris@119 2068
Chris@119 2069 if (i == 0) {
Chris@119 2070 lasty = y;
Chris@119 2071 lastdb = idb;
Chris@119 2072 } else if (i < ch - paint.fontMetrics().ascent() &&
Chris@120 2073 idb != lastdb &&
Chris@119 2074 ((abs(y - lasty) > textHeight &&
Chris@119 2075 idb % 10 == 0) ||
Chris@119 2076 (abs(y - lasty) > paint.fontMetrics().ascent() &&
Chris@119 2077 idb % 5 == 0))) {
Chris@287 2078 paint.setPen(v->getBackground());
Chris@119 2079 QString text = QString("%1").arg(idb);
Chris@119 2080 paint.drawText(3 + cw - cbw - paint.fontMetrics().width(text),
Chris@119 2081 y + toff + textHeight/2, text);
Chris@287 2082 paint.setPen(v->getForeground());
Chris@119 2083 paint.drawLine(5 + cw - cbw, y, 8 + cw - cbw, y);
Chris@119 2084 lasty = y;
Chris@119 2085 lastdb = idb;
Chris@119 2086 }
Chris@40 2087 }
Chris@40 2088 paint.restore();
Chris@40 2089 }
Chris@40 2090
Chris@40 2091 paint.drawLine(cw + 7, 0, cw + 7, h);
Chris@40 2092
Chris@0 2093 int bin = -1;
Chris@0 2094
Chris@918 2095 for (int y = 0; y < v->getPaintHeight(); ++y) {
Chris@0 2096
Chris@905 2097 double q0, q1;
Chris@918 2098 if (!getYBinRange(v, v->getPaintHeight() - y, q0, q1)) continue;
Chris@0 2099
Chris@0 2100 int vy;
Chris@0 2101
Chris@0 2102 if (int(q0) > bin) {
Chris@0 2103 vy = y;
Chris@0 2104 bin = int(q0);
Chris@0 2105 } else {
Chris@0 2106 continue;
Chris@0 2107 }
Chris@0 2108
Chris@1087 2109 int freq = int((sr * bin) / getFFTSize());
Chris@0 2110
Chris@0 2111 if (py >= 0 && (vy - py) < textHeight - 1) {
Chris@1103 2112 if (m_binScale == BinScale::Linear) {
Chris@40 2113 paint.drawLine(w - tickw, h - vy, w, h - vy);
Chris@40 2114 }
Chris@0 2115 continue;
Chris@0 2116 }
Chris@0 2117
Chris@0 2118 QString text = QString("%1").arg(freq);
Chris@234 2119 if (bin == 1) text = tr("%1Hz").arg(freq); // bin 0 is DC
Chris@40 2120 paint.drawLine(cw + 7, h - vy, w - pkw - 1, h - vy);
Chris@0 2121
Chris@0 2122 if (h - vy - textHeight >= -2) {
Chris@1025 2123 int tx = w - 3 - paint.fontMetrics().width(text) - max(tickw, pkw);
Chris@0 2124 paint.drawText(tx, h - vy + toff, text);
Chris@0 2125 }
Chris@0 2126
Chris@0 2127 py = vy;
Chris@0 2128 }
Chris@40 2129
Chris@1103 2130 if (m_binScale == BinScale::Log) {
Chris@40 2131
Chris@277 2132 // piano keyboard
Chris@277 2133
Chris@690 2134 PianoScale().paintPianoVertical
Chris@690 2135 (v, paint, QRect(w - pkw - 1, 0, pkw, h),
Chris@690 2136 getEffectiveMinFrequency(), getEffectiveMaxFrequency());
Chris@40 2137 }
Chris@608 2138
Chris@608 2139 m_haveDetailedScale = detailed;
Chris@0 2140 }
Chris@0 2141
Chris@187 2142 class SpectrogramRangeMapper : public RangeMapper
Chris@187 2143 {
Chris@187 2144 public:
Chris@901 2145 SpectrogramRangeMapper(sv_samplerate_t sr, int /* fftsize */) :
Chris@901 2146 m_dist(sr / 2),
Chris@901 2147 m_s2(sqrt(sqrt(2))) { }
Chris@187 2148 ~SpectrogramRangeMapper() { }
Chris@187 2149
Chris@901 2150 virtual int getPositionForValue(double value) const {
Chris@901 2151
Chris@901 2152 double dist = m_dist;
Chris@187 2153
Chris@187 2154 int n = 0;
Chris@187 2155
Chris@901 2156 while (dist > (value + 0.00001) && dist > 0.1) {
Chris@187 2157 dist /= m_s2;
Chris@187 2158 ++n;
Chris@187 2159 }
Chris@187 2160
Chris@187 2161 return n;
Chris@187 2162 }
Chris@724 2163
Chris@901 2164 virtual int getPositionForValueUnclamped(double value) const {
Chris@724 2165 // We don't really support this
Chris@724 2166 return getPositionForValue(value);
Chris@724 2167 }
Chris@187 2168
Chris@901 2169 virtual double getValueForPosition(int position) const {
Chris@187 2170
Chris@187 2171 // Vertical zoom step 0 shows the entire range from DC ->
Chris@187 2172 // Nyquist frequency. Step 1 shows 2^(1/4) of the range of
Chris@187 2173 // step 0, and so on until the visible range is smaller than
Chris@187 2174 // the frequency step between bins at the current fft size.
Chris@187 2175
Chris@901 2176 double dist = m_dist;
Chris@187 2177
Chris@187 2178 int n = 0;
Chris@187 2179 while (n < position) {
Chris@187 2180 dist /= m_s2;
Chris@187 2181 ++n;
Chris@187 2182 }
Chris@187 2183
Chris@187 2184 return dist;
Chris@187 2185 }
Chris@187 2186
Chris@901 2187 virtual double getValueForPositionUnclamped(int position) const {
Chris@724 2188 // We don't really support this
Chris@724 2189 return getValueForPosition(position);
Chris@724 2190 }
Chris@724 2191
Chris@187 2192 virtual QString getUnit() const { return "Hz"; }
Chris@187 2193
Chris@187 2194 protected:
Chris@901 2195 double m_dist;
Chris@901 2196 double m_s2;
Chris@187 2197 };
Chris@187 2198
Chris@133 2199 int
Chris@133 2200 SpectrogramLayer::getVerticalZoomSteps(int &defaultStep) const
Chris@133 2201 {
Chris@135 2202 if (!m_model) return 0;
Chris@187 2203
Chris@907 2204 sv_samplerate_t sr = m_model->getSampleRate();
Chris@187 2205
Chris@1087 2206 SpectrogramRangeMapper mapper(sr, getFFTSize());
Chris@1087 2207
Chris@1087 2208 // int maxStep = mapper.getPositionForValue((double(sr) / getFFTSize()) + 0.001);
Chris@187 2209 int maxStep = mapper.getPositionForValue(0);
Chris@905 2210 int minStep = mapper.getPositionForValue(double(sr) / 2);
Chris@250 2211
Chris@805 2212 int initialMax = m_initialMaxFrequency;
Chris@907 2213 if (initialMax == 0) initialMax = int(sr / 2);
Chris@250 2214
Chris@250 2215 defaultStep = mapper.getPositionForValue(initialMax) - minStep;
Chris@250 2216
Chris@587 2217 // SVDEBUG << "SpectrogramLayer::getVerticalZoomSteps: " << maxStep - minStep << " (" << maxStep <<"-" << minStep << "), default is " << defaultStep << " (from initial max freq " << initialMax << ")" << endl;
Chris@187 2218
Chris@187 2219 return maxStep - minStep;
Chris@133 2220 }
Chris@133 2221
Chris@133 2222 int
Chris@133 2223 SpectrogramLayer::getCurrentVerticalZoomStep() const
Chris@133 2224 {
Chris@133 2225 if (!m_model) return 0;
Chris@133 2226
Chris@905 2227 double dmin, dmax;
Chris@133 2228 getDisplayExtents(dmin, dmax);
Chris@133 2229
Chris@1087 2230 SpectrogramRangeMapper mapper(m_model->getSampleRate(), getFFTSize());
Chris@187 2231 int n = mapper.getPositionForValue(dmax - dmin);
Chris@587 2232 // SVDEBUG << "SpectrogramLayer::getCurrentVerticalZoomStep: " << n << endl;
Chris@133 2233 return n;
Chris@133 2234 }
Chris@133 2235
Chris@133 2236 void
Chris@133 2237 SpectrogramLayer::setVerticalZoomStep(int step)
Chris@133 2238 {
Chris@187 2239 if (!m_model) return;
Chris@187 2240
Chris@905 2241 double dmin = m_minFrequency, dmax = m_maxFrequency;
Chris@253 2242 // getDisplayExtents(dmin, dmax);
Chris@253 2243
Chris@682 2244 // cerr << "current range " << dmin << " -> " << dmax << ", range " << dmax-dmin << ", mid " << (dmax + dmin)/2 << endl;
Chris@133 2245
Chris@907 2246 sv_samplerate_t sr = m_model->getSampleRate();
Chris@1087 2247 SpectrogramRangeMapper mapper(sr, getFFTSize());
Chris@905 2248 double newdist = mapper.getValueForPosition(step);
Chris@905 2249
Chris@905 2250 double newmin, newmax;
Chris@253 2251
Chris@1103 2252 if (m_binScale == BinScale::Log) {
Chris@253 2253
Chris@253 2254 // need to pick newmin and newmax such that
Chris@253 2255 //
Chris@253 2256 // (log(newmin) + log(newmax)) / 2 == logmid
Chris@253 2257 // and
Chris@253 2258 // newmax - newmin = newdist
Chris@253 2259 //
Chris@253 2260 // so log(newmax - newdist) + log(newmax) == 2logmid
Chris@253 2261 // log(newmax(newmax - newdist)) == 2logmid
Chris@253 2262 // newmax.newmax - newmax.newdist == exp(2logmid)
Chris@253 2263 // newmax^2 + (-newdist)newmax + -exp(2logmid) == 0
Chris@253 2264 // quadratic with a = 1, b = -newdist, c = -exp(2logmid), all known
Chris@253 2265 //
Chris@253 2266 // positive root
Chris@253 2267 // newmax = (newdist + sqrt(newdist^2 + 4exp(2logmid))) / 2
Chris@253 2268 //
Chris@253 2269 // but logmid = (log(dmin) + log(dmax)) / 2
Chris@253 2270 // so exp(2logmid) = exp(log(dmin) + log(dmax))
Chris@253 2271 // = exp(log(dmin.dmax))
Chris@253 2272 // = dmin.dmax
Chris@253 2273 // so newmax = (newdist + sqrtf(newdist^2 + 4dmin.dmax)) / 2
Chris@253 2274
Chris@907 2275 newmax = (newdist + sqrt(newdist*newdist + 4*dmin*dmax)) / 2;
Chris@253 2276 newmin = newmax - newdist;
Chris@253 2277
Chris@682 2278 // cerr << "newmin = " << newmin << ", newmax = " << newmax << endl;
Chris@253 2279
Chris@253 2280 } else {
Chris@905 2281 double dmid = (dmax + dmin) / 2;
Chris@253 2282 newmin = dmid - newdist / 2;
Chris@253 2283 newmax = dmid + newdist / 2;
Chris@253 2284 }
Chris@187 2285
Chris@905 2286 double mmin, mmax;
Chris@187 2287 mmin = 0;
Chris@905 2288 mmax = double(sr) / 2;
Chris@133 2289
Chris@187 2290 if (newmin < mmin) {
Chris@187 2291 newmax += (mmin - newmin);
Chris@187 2292 newmin = mmin;
Chris@187 2293 }
Chris@187 2294 if (newmax > mmax) {
Chris@187 2295 newmax = mmax;
Chris@187 2296 }
Chris@133 2297
Chris@587 2298 // SVDEBUG << "SpectrogramLayer::setVerticalZoomStep: " << step << ": " << newmin << " -> " << newmax << " (range " << newdist << ")" << endl;
Chris@253 2299
Chris@907 2300 setMinFrequency(int(lrint(newmin)));
Chris@907 2301 setMaxFrequency(int(lrint(newmax)));
Chris@187 2302 }
Chris@187 2303
Chris@187 2304 RangeMapper *
Chris@187 2305 SpectrogramLayer::getNewVerticalZoomRangeMapper() const
Chris@187 2306 {
Chris@187 2307 if (!m_model) return 0;
Chris@1087 2308 return new SpectrogramRangeMapper(m_model->getSampleRate(), getFFTSize());
Chris@133 2309 }
Chris@133 2310
Chris@273 2311 void
Chris@918 2312 SpectrogramLayer::updateMeasureRectYCoords(LayerGeometryProvider *v, const MeasureRect &r) const
Chris@273 2313 {
Chris@273 2314 int y0 = 0;
Chris@907 2315 if (r.startY > 0.0) y0 = int(getYForFrequency(v, r.startY));
Chris@273 2316
Chris@273 2317 int y1 = y0;
Chris@907 2318 if (r.endY > 0.0) y1 = int(getYForFrequency(v, r.endY));
Chris@273 2319
Chris@587 2320 // SVDEBUG << "SpectrogramLayer::updateMeasureRectYCoords: start " << r.startY << " -> " << y0 << ", end " << r.endY << " -> " << y1 << endl;
Chris@273 2321
Chris@273 2322 r.pixrect = QRect(r.pixrect.x(), y0, r.pixrect.width(), y1 - y0);
Chris@273 2323 }
Chris@273 2324
Chris@273 2325 void
Chris@918 2326 SpectrogramLayer::setMeasureRectYCoord(LayerGeometryProvider *v, MeasureRect &r, bool start, int y) const
Chris@273 2327 {
Chris@273 2328 if (start) {
Chris@273 2329 r.startY = getFrequencyForY(v, y);
Chris@273 2330 r.endY = r.startY;
Chris@273 2331 } else {
Chris@273 2332 r.endY = getFrequencyForY(v, y);
Chris@273 2333 }
Chris@587 2334 // SVDEBUG << "SpectrogramLayer::setMeasureRectYCoord: start " << r.startY << " <- " << y << ", end " << r.endY << " <- " << y << endl;
Chris@273 2335
Chris@273 2336 }
Chris@273 2337
Chris@316 2338 void
Chris@316 2339 SpectrogramLayer::toXml(QTextStream &stream,
Chris@316 2340 QString indent, QString extraAttributes) const
Chris@6 2341 {
Chris@6 2342 QString s;
Chris@6 2343
Chris@6 2344 s += QString("channel=\"%1\" "
Chris@6 2345 "windowSize=\"%2\" "
Chris@153 2346 "windowHopLevel=\"%3\" "
Chris@153 2347 "gain=\"%4\" "
Chris@153 2348 "threshold=\"%5\" ")
Chris@6 2349 .arg(m_channel)
Chris@6 2350 .arg(m_windowSize)
Chris@97 2351 .arg(m_windowHopLevel)
Chris@37 2352 .arg(m_gain)
Chris@37 2353 .arg(m_threshold);
Chris@37 2354
Chris@37 2355 s += QString("minFrequency=\"%1\" "
Chris@37 2356 "maxFrequency=\"%2\" "
Chris@37 2357 "colourScale=\"%3\" "
Chris@37 2358 "colourScheme=\"%4\" "
Chris@37 2359 "colourRotation=\"%5\" "
Chris@37 2360 "frequencyScale=\"%6\" "
Chris@761 2361 "binDisplay=\"%7\" ")
Chris@37 2362 .arg(m_minFrequency)
Chris@6 2363 .arg(m_maxFrequency)
Chris@1104 2364 .arg(convertFromColourScale(m_colourScale))
Chris@197 2365 .arg(m_colourMap)
Chris@37 2366 .arg(m_colourRotation)
Chris@1103 2367 .arg(int(m_binScale))
Chris@1103 2368 .arg(int(m_binDisplay));
Chris@761 2369
Chris@1009 2370 // New-style normalization attributes, allowing for more types of
Chris@1009 2371 // normalization in future: write out the column normalization
Chris@1009 2372 // type separately, and then whether we are normalizing visible
Chris@1009 2373 // area as well afterwards
Chris@1009 2374
Chris@1009 2375 s += QString("columnNormalization=\"%1\" ")
Chris@1104 2376 .arg(m_normalization == ColumnNormalization::Max1 ? "peak" :
Chris@1104 2377 m_normalization == ColumnNormalization::Hybrid ? "hybrid" : "none");
Chris@1009 2378
Chris@1009 2379 // Old-style normalization attribute. We *don't* write out
Chris@1009 2380 // normalizeHybrid here because the only release that would accept
Chris@1009 2381 // it (Tony v1.0) has a totally different scale factor for
Chris@1009 2382 // it. We'll just have to accept that session files from Tony
Chris@1009 2383 // v2.0+ will look odd in Tony v1.0
Chris@1009 2384
Chris@1009 2385 s += QString("normalizeColumns=\"%1\" ")
Chris@1104 2386 .arg(m_normalization == ColumnNormalization::Max1 ? "true" : "false");
Chris@1009 2387
Chris@1009 2388 // And this applies to both old- and new-style attributes
Chris@1009 2389
Chris@1009 2390 s += QString("normalizeVisibleArea=\"%1\" ")
Chris@1104 2391 .arg(m_normalizeVisibleArea ? "true" : "false");
Chris@1009 2392
Chris@316 2393 Layer::toXml(stream, indent, extraAttributes + " " + s);
Chris@6 2394 }
Chris@6 2395
Chris@11 2396 void
Chris@11 2397 SpectrogramLayer::setProperties(const QXmlAttributes &attributes)
Chris@11 2398 {
Chris@11 2399 bool ok = false;
Chris@11 2400
Chris@11 2401 int channel = attributes.value("channel").toInt(&ok);
Chris@11 2402 if (ok) setChannel(channel);
Chris@11 2403
Chris@805 2404 int windowSize = attributes.value("windowSize").toUInt(&ok);
Chris@11 2405 if (ok) setWindowSize(windowSize);
Chris@11 2406
Chris@805 2407 int windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok);
Chris@97 2408 if (ok) setWindowHopLevel(windowHopLevel);
Chris@97 2409 else {
Chris@805 2410 int windowOverlap = attributes.value("windowOverlap").toUInt(&ok);
Chris@97 2411 // a percentage value
Chris@97 2412 if (ok) {
Chris@97 2413 if (windowOverlap == 0) setWindowHopLevel(0);
Chris@97 2414 else if (windowOverlap == 25) setWindowHopLevel(1);
Chris@97 2415 else if (windowOverlap == 50) setWindowHopLevel(2);
Chris@97 2416 else if (windowOverlap == 75) setWindowHopLevel(3);
Chris@97 2417 else if (windowOverlap == 90) setWindowHopLevel(4);
Chris@97 2418 }
Chris@97 2419 }
Chris@11 2420
Chris@11 2421 float gain = attributes.value("gain").toFloat(&ok);
Chris@11 2422 if (ok) setGain(gain);
Chris@11 2423
Chris@37 2424 float threshold = attributes.value("threshold").toFloat(&ok);
Chris@37 2425 if (ok) setThreshold(threshold);
Chris@37 2426
Chris@805 2427 int minFrequency = attributes.value("minFrequency").toUInt(&ok);
Chris@187 2428 if (ok) {
Chris@587 2429 SVDEBUG << "SpectrogramLayer::setProperties: setting min freq to " << minFrequency << endl;
Chris@187 2430 setMinFrequency(minFrequency);
Chris@187 2431 }
Chris@37 2432
Chris@805 2433 int maxFrequency = attributes.value("maxFrequency").toUInt(&ok);
Chris@187 2434 if (ok) {
Chris@587 2435 SVDEBUG << "SpectrogramLayer::setProperties: setting max freq to " << maxFrequency << endl;
Chris@187 2436 setMaxFrequency(maxFrequency);
Chris@187 2437 }
Chris@11 2438
Chris@1105 2439 ColourScaleType colourScale = convertToColourScale
Chris@1092 2440 (attributes.value("colourScale").toInt(&ok));
Chris@11 2441 if (ok) setColourScale(colourScale);
Chris@11 2442
Chris@197 2443 int colourMap = attributes.value("colourScheme").toInt(&ok);
Chris@197 2444 if (ok) setColourMap(colourMap);
Chris@11 2445
Chris@37 2446 int colourRotation = attributes.value("colourRotation").toInt(&ok);
Chris@37 2447 if (ok) setColourRotation(colourRotation);
Chris@37 2448
Chris@1103 2449 BinScale binScale = (BinScale)
Chris@11 2450 attributes.value("frequencyScale").toInt(&ok);
Chris@1093 2451 if (ok) setBinScale(binScale);
Chris@1093 2452
Chris@1103 2453 BinDisplay binDisplay = (BinDisplay)
Chris@37 2454 attributes.value("binDisplay").toInt(&ok);
Chris@37 2455 if (ok) setBinDisplay(binDisplay);
Chris@36 2456
Chris@1009 2457 bool haveNewStyleNormalization = false;
Chris@1009 2458
Chris@1009 2459 QString columnNormalization = attributes.value("columnNormalization");
Chris@1009 2460
Chris@1009 2461 if (columnNormalization != "") {
Chris@1009 2462
Chris@1009 2463 haveNewStyleNormalization = true;
Chris@1009 2464
Chris@1009 2465 if (columnNormalization == "peak") {
Chris@1104 2466 setNormalization(ColumnNormalization::Max1);
Chris@1009 2467 } else if (columnNormalization == "hybrid") {
Chris@1104 2468 setNormalization(ColumnNormalization::Hybrid);
Chris@1009 2469 } else if (columnNormalization == "none") {
Chris@1104 2470 setNormalization(ColumnNormalization::None);
Chris@1009 2471 } else {
Chris@1009 2472 cerr << "NOTE: Unknown or unsupported columnNormalization attribute \""
Chris@1009 2473 << columnNormalization << "\"" << endl;
Chris@1009 2474 }
Chris@1009 2475 }
Chris@1009 2476
Chris@1009 2477 if (!haveNewStyleNormalization) {
Chris@1009 2478
Chris@1009 2479 bool normalizeColumns =
Chris@1009 2480 (attributes.value("normalizeColumns").trimmed() == "true");
Chris@1009 2481 if (normalizeColumns) {
Chris@1104 2482 setNormalization(ColumnNormalization::Max1);
Chris@1009 2483 }
Chris@1009 2484
Chris@1009 2485 bool normalizeHybrid =
Chris@1009 2486 (attributes.value("normalizeHybrid").trimmed() == "true");
Chris@1009 2487 if (normalizeHybrid) {
Chris@1104 2488 setNormalization(ColumnNormalization::Hybrid);
Chris@1009 2489 }
Chris@862 2490 }
Chris@153 2491
Chris@153 2492 bool normalizeVisibleArea =
Chris@1099 2493 (attributes.value("normalizeVisibleArea").trimmed() == "true");
Chris@1104 2494 setNormalizeVisibleArea(normalizeVisibleArea);
Chris@1104 2495
Chris@1104 2496 if (!haveNewStyleNormalization && m_normalization == ColumnNormalization::Hybrid) {
Chris@1009 2497 // Tony v1.0 is (and hopefully will remain!) the only released
Chris@1009 2498 // SV-a-like to use old-style attributes when saving sessions
Chris@1009 2499 // that ask for hybrid normalization. It saves them with the
Chris@1009 2500 // wrong gain factor, so hack in a fix for that here -- this
Chris@1009 2501 // gives us backward but not forward compatibility.
Chris@1087 2502 setGain(m_gain / float(getFFTSize() / 2));
Chris@862 2503 }
Chris@11 2504 }
Chris@11 2505