annotate layer/SpectrumLayer.cpp @ 188:dd573e090eed

* Add range input dialog * Make Panner support middle-click/ctrl-left-click to reset and emit doubleClicked when doubleClicked instead of resetting * Use range input dialog to enter new values for panner on double-click
author Chris Cannam
date Fri, 12 Jan 2007 21:52:56 +0000
parents 42118892f428
children 57c2350a8c40
rev   line source
Chris@133 1
Chris@133 2 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@133 3
Chris@133 4 /*
Chris@133 5 Sonic Visualiser
Chris@133 6 An audio file viewer and annotation editor.
Chris@133 7 Centre for Digital Music, Queen Mary, University of London.
Chris@182 8 This file copyright 2006 QMUL.
Chris@133 9
Chris@133 10 This program is free software; you can redistribute it and/or
Chris@133 11 modify it under the terms of the GNU General Public License as
Chris@133 12 published by the Free Software Foundation; either version 2 of the
Chris@133 13 License, or (at your option) any later version. See the file
Chris@133 14 COPYING included with this distribution for more information.
Chris@133 15 */
Chris@133 16
Chris@133 17 #include "SpectrumLayer.h"
Chris@133 18
Chris@133 19 #include "data/model/FFTModel.h"
Chris@133 20 #include "view/View.h"
Chris@153 21 #include "base/AudioLevel.h"
Chris@153 22 #include "base/Preferences.h"
Chris@167 23 #include "base/RangeMapper.h"
Chris@133 24
Chris@133 25 #include <QPainter>
Chris@133 26 #include <QPainterPath>
Chris@133 27
Chris@133 28 SpectrumLayer::SpectrumLayer() :
Chris@133 29 m_model(0),
Chris@153 30 m_channelMode(MixChannels),
Chris@153 31 m_channel(-1),
Chris@153 32 m_channelSet(false),
Chris@153 33 m_colour(Qt::darkBlue),
Chris@153 34 m_energyScale(dBScale),
Chris@153 35 m_normalize(false),
Chris@153 36 m_gain(1.0),
Chris@153 37 m_windowSize(1024),
Chris@153 38 m_windowType(HanningWindow),
Chris@153 39 m_windowHopLevel(2)
Chris@133 40 {
Chris@153 41 Preferences *prefs = Preferences::getInstance();
Chris@153 42 connect(prefs, SIGNAL(propertyChanged(PropertyContainer::PropertyName)),
Chris@153 43 this, SLOT(preferenceChanged(PropertyContainer::PropertyName)));
Chris@153 44 setWindowType(prefs->getWindowType());
Chris@133 45 }
Chris@133 46
Chris@133 47 SpectrumLayer::~SpectrumLayer()
Chris@133 48 {
Chris@153 49 for (size_t i = 0; i < m_fft.size(); ++i) delete m_fft[i];
Chris@133 50 }
Chris@133 51
Chris@133 52 void
Chris@133 53 SpectrumLayer::setModel(DenseTimeValueModel *model)
Chris@133 54 {
Chris@133 55 m_model = model;
Chris@153 56 setupFFTs();
Chris@153 57 }
Chris@153 58
Chris@153 59 void
Chris@153 60 SpectrumLayer::setupFFTs()
Chris@153 61 {
Chris@153 62 for (size_t i = 0; i < m_fft.size(); ++i) delete m_fft[i];
Chris@153 63 m_fft.clear();
Chris@153 64
Chris@153 65 int minChannel = m_channel, maxChannel = m_channel;
Chris@153 66
Chris@153 67 if (m_channel == -1 &&
Chris@153 68 m_channelMode != MixChannels) {
Chris@153 69 minChannel = 0;
Chris@153 70 maxChannel = 0;
Chris@153 71 if (m_model->getChannelCount() > 1) {
Chris@153 72 maxChannel = m_model->getChannelCount() - 1;
Chris@153 73 }
Chris@153 74 }
Chris@153 75
Chris@153 76 for (int c = minChannel; c <= maxChannel; ++c) {
Chris@153 77
Chris@153 78 m_fft.push_back(new FFTModel(m_model,
Chris@153 79 c,
Chris@153 80 HanningWindow,
Chris@153 81 m_windowSize,
Chris@153 82 getWindowIncrement(),
Chris@153 83 m_windowSize,
Chris@153 84 true));
Chris@153 85
Chris@153 86 if (m_channelSet) m_fft[m_fft.size()-1]->resume();
Chris@153 87 }
Chris@153 88 }
Chris@153 89
Chris@153 90 void
Chris@153 91 SpectrumLayer::setChannel(int channel)
Chris@153 92 {
Chris@153 93 m_channelSet = true;
Chris@153 94
Chris@153 95 if (m_channel == channel) {
Chris@153 96 for (size_t i = 0; i < m_fft.size(); ++i) {
Chris@153 97 m_fft[i]->resume();
Chris@153 98 }
Chris@153 99 return;
Chris@153 100 }
Chris@153 101
Chris@153 102 m_channel = channel;
Chris@153 103
Chris@153 104 if (!m_fft.empty()) setupFFTs();
Chris@153 105
Chris@153 106 emit layerParametersChanged();
Chris@133 107 }
Chris@133 108
Chris@133 109 void
Chris@133 110 SpectrumLayer::paint(View *v, QPainter &paint, QRect rect) const
Chris@133 111 {
Chris@153 112 if (m_fft.empty()) return;
Chris@153 113 if (!m_channelSet) {
Chris@153 114 for (size_t i = 0; i < m_fft.size(); ++i) {
Chris@153 115 m_fft[i]->resume();
Chris@153 116 }
Chris@153 117 }
Chris@133 118
Chris@153 119 FFTModel *fft = m_fft[0]; //!!! for now
Chris@153 120
Chris@153 121 int windowIncrement = getWindowIncrement();
Chris@133 122
Chris@133 123 size_t f = v->getCentreFrame();
Chris@133 124
Chris@133 125 int w = (v->width() * 2) / 3;
Chris@133 126 int xorigin = (v->width() / 2) - (w / 2);
Chris@133 127
Chris@133 128 int h = (v->height() * 2) / 3;
Chris@133 129 int yorigin = (v->height() / 2) + (h / 2);
Chris@133 130
Chris@133 131 size_t column = f / windowIncrement;
Chris@133 132
Chris@133 133 paint.save();
Chris@133 134 paint.setPen(m_colour);
Chris@133 135 paint.setRenderHint(QPainter::Antialiasing, false);
Chris@133 136
Chris@133 137 QPainterPath path;
Chris@133 138 float thresh = -80.f;
Chris@133 139
Chris@153 140 for (size_t bin = 0; bin < fft->getHeight(); ++bin) {
Chris@133 141
Chris@153 142 float x = xorigin + (float(w) * bin) / fft->getHeight();
Chris@153 143 float mag;
Chris@153 144 if (m_normalize) {
Chris@153 145 mag = fft->getNormalizedMagnitudeAt(column, bin);
Chris@153 146 } else {
Chris@153 147 mag = fft->getMagnitudeAt(column, bin);
Chris@153 148 }
Chris@153 149 mag *= m_gain;
Chris@153 150 float y = 0.f;
Chris@153 151
Chris@153 152 switch (m_energyScale) {
Chris@153 153
Chris@153 154 case dBScale:
Chris@153 155 {
Chris@153 156 float db = thresh;
Chris@153 157 if (mag > 0.f) db = 10.f * log10f(mag);
Chris@153 158 if (db < thresh) db = thresh;
Chris@153 159 float val = (db - thresh) / -thresh;
Chris@153 160 y = yorigin - (float(h) * val);
Chris@153 161 break;
Chris@153 162 }
Chris@153 163
Chris@153 164 case MeterScale:
Chris@153 165 y = yorigin - AudioLevel::multiplier_to_preview(mag, h);
Chris@153 166 break;
Chris@153 167
Chris@153 168 default:
Chris@153 169 y = yorigin - (float(h) * mag);
Chris@153 170 break;
Chris@153 171 }
Chris@133 172
Chris@133 173 if (bin == 0) {
Chris@133 174 path.moveTo(x, y);
Chris@133 175 } else {
Chris@133 176 path.lineTo(x, y);
Chris@133 177 }
Chris@133 178 }
Chris@133 179
Chris@133 180 paint.drawPath(path);
Chris@133 181 paint.restore();
Chris@133 182
Chris@133 183 }
Chris@133 184
Chris@153 185 Layer::PropertyList
Chris@153 186 SpectrumLayer::getProperties() const
Chris@153 187 {
Chris@153 188 PropertyList list;
Chris@153 189 list.push_back("Colour");
Chris@153 190 list.push_back("Scale");
Chris@153 191 list.push_back("Normalize");
Chris@153 192 list.push_back("Gain");
Chris@153 193 list.push_back("Window Size");
Chris@153 194 list.push_back("Window Increment");
Chris@153 195
Chris@153 196 if (m_model && m_model->getChannelCount() > 1 && m_channel == -1) {
Chris@153 197 list.push_back("Channels");
Chris@153 198 }
Chris@153 199
Chris@153 200 return list;
Chris@153 201 }
Chris@153 202
Chris@153 203 QString
Chris@153 204 SpectrumLayer::getPropertyLabel(const PropertyName &name) const
Chris@153 205 {
Chris@153 206 if (name == "Colour") return tr("Colour");
Chris@153 207 if (name == "Energy Scale") return tr("Scale");
Chris@153 208 if (name == "Channels") return tr("Channels");
Chris@153 209 if (name == "Window Size") return tr("Window Size");
Chris@153 210 if (name == "Window Increment") return tr("Window Overlap");
Chris@153 211 if (name == "Normalize") return tr("Normalize");
Chris@153 212 if (name == "Gain") return tr("Gain");
Chris@153 213 return "";
Chris@153 214 }
Chris@153 215
Chris@153 216 Layer::PropertyType
Chris@153 217 SpectrumLayer::getPropertyType(const PropertyName &name) const
Chris@153 218 {
Chris@153 219 if (name == "Gain") return RangeProperty;
Chris@153 220 if (name == "Normalize") return ToggleProperty;
Chris@153 221 return ValueProperty;
Chris@153 222 }
Chris@153 223
Chris@153 224 QString
Chris@153 225 SpectrumLayer::getPropertyGroupName(const PropertyName &name) const
Chris@153 226 {
Chris@153 227 if (name == "Window Size" ||
Chris@153 228 name == "Window Increment") return tr("Window");
Chris@153 229 if (name == "Scale" ||
Chris@153 230 name == "Normalize" ||
Chris@153 231 name == "Gain") return tr("Energy Scale");
Chris@153 232 return QString();
Chris@153 233 }
Chris@153 234
Chris@153 235 int
Chris@153 236 SpectrumLayer::getPropertyRangeAndValue(const PropertyName &name,
Chris@153 237 int *min, int *max) const
Chris@153 238 {
Chris@153 239 int deft = 0;
Chris@153 240
Chris@153 241 int garbage0, garbage1;
Chris@153 242 if (!min) min = &garbage0;
Chris@153 243 if (!max) max = &garbage1;
Chris@153 244
Chris@153 245 if (name == "Gain") {
Chris@153 246
Chris@153 247 *min = -50;
Chris@153 248 *max = 50;
Chris@153 249
Chris@153 250 deft = lrint(log10(m_gain) * 20.0);
Chris@153 251 if (deft < *min) deft = *min;
Chris@153 252 if (deft > *max) deft = *max;
Chris@153 253
Chris@153 254 } else if (name == "Normalize") {
Chris@153 255
Chris@153 256 deft = (m_normalize ? 1 : 0);
Chris@153 257
Chris@153 258 } else if (name == "Colour") {
Chris@153 259
Chris@153 260 *min = 0;
Chris@153 261 *max = 5;
Chris@153 262
Chris@153 263 if (m_colour == Qt::black) deft = 0;
Chris@153 264 else if (m_colour == Qt::darkRed) deft = 1;
Chris@153 265 else if (m_colour == Qt::darkBlue) deft = 2;
Chris@153 266 else if (m_colour == Qt::darkGreen) deft = 3;
Chris@153 267 else if (m_colour == QColor(200, 50, 255)) deft = 4;
Chris@153 268 else if (m_colour == QColor(255, 150, 50)) deft = 5;
Chris@153 269
Chris@153 270 } else if (name == "Channels") {
Chris@153 271
Chris@153 272 *min = 0;
Chris@153 273 *max = 2;
Chris@153 274 if (m_channelMode == MixChannels) deft = 1;
Chris@153 275 else if (m_channelMode == OverlayChannels) deft = 2;
Chris@153 276 else deft = 0;
Chris@153 277
Chris@153 278 } else if (name == "Scale") {
Chris@153 279
Chris@153 280 *min = 0;
Chris@153 281 *max = 2;
Chris@153 282
Chris@153 283 deft = (int)m_energyScale;
Chris@153 284
Chris@153 285 } else if (name == "Window Size") {
Chris@153 286
Chris@153 287 *min = 0;
Chris@153 288 *max = 10;
Chris@153 289
Chris@153 290 deft = 0;
Chris@153 291 int ws = m_windowSize;
Chris@153 292 while (ws > 32) { ws >>= 1; deft ++; }
Chris@153 293
Chris@153 294 } else if (name == "Window Increment") {
Chris@153 295
Chris@153 296 *min = 0;
Chris@153 297 *max = 5;
Chris@153 298
Chris@153 299 deft = m_windowHopLevel;
Chris@153 300
Chris@153 301 } else {
Chris@153 302 deft = Layer::getPropertyRangeAndValue(name, min, max);
Chris@153 303 }
Chris@153 304
Chris@153 305 return deft;
Chris@153 306 }
Chris@153 307
Chris@153 308 QString
Chris@153 309 SpectrumLayer::getPropertyValueLabel(const PropertyName &name,
Chris@153 310 int value) const
Chris@153 311 {
Chris@153 312 if (name == "Colour") {
Chris@153 313 switch (value) {
Chris@153 314 default:
Chris@153 315 case 0: return tr("Black");
Chris@153 316 case 1: return tr("Red");
Chris@153 317 case 2: return tr("Blue");
Chris@153 318 case 3: return tr("Green");
Chris@153 319 case 4: return tr("Purple");
Chris@153 320 case 5: return tr("Orange");
Chris@153 321 }
Chris@153 322 }
Chris@153 323 if (name == "Scale") {
Chris@153 324 switch (value) {
Chris@153 325 default:
Chris@153 326 case 0: return tr("Linear");
Chris@153 327 case 1: return tr("Meter");
Chris@153 328 case 2: return tr("dB");
Chris@153 329 }
Chris@153 330 }
Chris@153 331 if (name == "Channels") {
Chris@153 332 switch (value) {
Chris@153 333 default:
Chris@153 334 case 0: return tr("Separate");
Chris@153 335 case 1: return tr("Mean");
Chris@153 336 case 2: return tr("Overlay");
Chris@153 337 }
Chris@153 338 }
Chris@153 339 if (name == "Window Size") {
Chris@153 340 return QString("%1").arg(32 << value);
Chris@153 341 }
Chris@153 342 if (name == "Window Increment") {
Chris@153 343 switch (value) {
Chris@153 344 default:
Chris@153 345 case 0: return tr("None");
Chris@153 346 case 1: return tr("25 %");
Chris@153 347 case 2: return tr("50 %");
Chris@153 348 case 3: return tr("75 %");
Chris@153 349 case 4: return tr("87.5 %");
Chris@153 350 case 5: return tr("93.75 %");
Chris@153 351 }
Chris@153 352 }
Chris@153 353 return tr("<unknown>");
Chris@153 354 }
Chris@153 355
Chris@167 356 RangeMapper *
Chris@167 357 SpectrumLayer::getNewPropertyRangeMapper(const PropertyName &name) const
Chris@167 358 {
Chris@167 359 if (name == "Gain") {
Chris@167 360 return new LinearRangeMapper(-50, 50, -25, 25, tr("dB"));
Chris@167 361 }
Chris@167 362 return 0;
Chris@167 363 }
Chris@167 364
Chris@133 365 void
Chris@153 366 SpectrumLayer::setProperty(const PropertyName &name, int value)
Chris@133 367 {
Chris@153 368 if (name == "Gain") {
Chris@153 369 setGain(pow(10, float(value)/20.0));
Chris@153 370 } else if (name == "Colour") {
Chris@153 371 switch (value) {
Chris@153 372 default:
Chris@153 373 case 0: setBaseColour(Qt::black); break;
Chris@153 374 case 1: setBaseColour(Qt::darkRed); break;
Chris@153 375 case 2: setBaseColour(Qt::darkBlue); break;
Chris@153 376 case 3: setBaseColour(Qt::darkGreen); break;
Chris@153 377 case 4: setBaseColour(QColor(200, 50, 255)); break;
Chris@153 378 case 5: setBaseColour(QColor(255, 150, 50)); break;
Chris@153 379 }
Chris@153 380 } else if (name == "Channels") {
Chris@153 381 if (value == 1) setChannelMode(MixChannels);
Chris@153 382 else if (value == 2) setChannelMode(OverlayChannels);
Chris@153 383 else setChannelMode(SeparateChannels);
Chris@153 384 } else if (name == "Scale") {
Chris@153 385 switch (value) {
Chris@153 386 default:
Chris@153 387 case 0: setEnergyScale(LinearScale); break;
Chris@153 388 case 1: setEnergyScale(MeterScale); break;
Chris@153 389 case 2: setEnergyScale(dBScale); break;
Chris@153 390 }
Chris@153 391 } else if (name == "Window Size") {
Chris@153 392 setWindowSize(32 << value);
Chris@153 393 } else if (name == "Window Increment") {
Chris@153 394 setWindowHopLevel(value);
Chris@153 395 } else if (name == "Normalize") {
Chris@153 396 setNormalize(value ? true : false);
Chris@153 397 }
Chris@153 398 }
Chris@153 399
Chris@153 400 void
Chris@153 401 SpectrumLayer::setBaseColour(QColor colour)
Chris@153 402 {
Chris@153 403 if (m_colour == colour) return;
Chris@153 404 m_colour = colour;
Chris@153 405 emit layerParametersChanged();
Chris@153 406 }
Chris@153 407
Chris@153 408 void
Chris@153 409 SpectrumLayer::setChannelMode(ChannelMode channelMode)
Chris@153 410 {
Chris@153 411 if (m_channelMode == channelMode) return;
Chris@153 412 m_channelMode = channelMode;
Chris@153 413 emit layerParametersChanged();
Chris@153 414 }
Chris@153 415
Chris@153 416 void
Chris@153 417 SpectrumLayer::setEnergyScale(EnergyScale scale)
Chris@153 418 {
Chris@153 419 if (m_energyScale == scale) return;
Chris@153 420 m_energyScale = scale;
Chris@153 421 emit layerParametersChanged();
Chris@153 422 }
Chris@153 423
Chris@153 424 void
Chris@153 425 SpectrumLayer::setWindowSize(size_t ws)
Chris@153 426 {
Chris@153 427 if (m_windowSize == ws) return;
Chris@153 428 m_windowSize = ws;
Chris@153 429 setupFFTs();
Chris@153 430 emit layerParametersChanged();
Chris@153 431 }
Chris@153 432
Chris@153 433 void
Chris@153 434 SpectrumLayer::setWindowHopLevel(size_t v)
Chris@153 435 {
Chris@153 436 if (m_windowHopLevel == v) return;
Chris@153 437 m_windowHopLevel = v;
Chris@153 438 setupFFTs();
Chris@153 439 emit layerParametersChanged();
Chris@153 440 }
Chris@153 441
Chris@153 442 void
Chris@153 443 SpectrumLayer::setWindowType(WindowType w)
Chris@153 444 {
Chris@153 445 if (m_windowType == w) return;
Chris@153 446 m_windowType = w;
Chris@153 447 setupFFTs();
Chris@153 448 emit layerParametersChanged();
Chris@153 449 }
Chris@153 450
Chris@153 451 void
Chris@153 452 SpectrumLayer::setNormalize(bool n)
Chris@153 453 {
Chris@153 454 if (m_normalize == n) return;
Chris@153 455 m_normalize = n;
Chris@153 456 emit layerParametersChanged();
Chris@153 457 }
Chris@153 458
Chris@153 459 void
Chris@153 460 SpectrumLayer::setGain(float gain)
Chris@153 461 {
Chris@153 462 if (m_gain == gain) return;
Chris@153 463 m_gain = gain;
Chris@153 464 emit layerParametersChanged();
Chris@153 465 }
Chris@153 466
Chris@153 467 void
Chris@153 468 SpectrumLayer::preferenceChanged(PropertyContainer::PropertyName name)
Chris@153 469 {
Chris@153 470 if (name == "Window Type") {
Chris@153 471 setWindowType(Preferences::getInstance()->getWindowType());
Chris@153 472 return;
Chris@153 473 }
Chris@153 474 }
Chris@153 475
Chris@153 476 QString
Chris@153 477 SpectrumLayer::toXmlString(QString indent, QString extraAttributes) const
Chris@153 478 {
Chris@153 479 QString s;
Chris@153 480
Chris@153 481 s += QString("colour=\"%1\" "
Chris@153 482 "channelMode=\"%2\" "
Chris@153 483 "channel=\"%3\" "
Chris@153 484 "energyScale=\"%4\" "
Chris@153 485 "windowSize=\"%5\" "
Chris@153 486 "windowHopLevel=\"%6\" "
Chris@153 487 "gain=\"%7\" "
Chris@153 488 "normalize=\"%8\"")
Chris@153 489 .arg(encodeColour(m_colour))
Chris@153 490 .arg(m_channelMode)
Chris@153 491 .arg(m_channel)
Chris@153 492 .arg(m_energyScale)
Chris@153 493 .arg(m_windowSize)
Chris@153 494 .arg(m_windowHopLevel)
Chris@153 495 .arg(m_gain)
Chris@153 496 .arg(m_normalize ? "true" : "false");
Chris@153 497
Chris@153 498 return Layer::toXmlString(indent, extraAttributes + " " + s);
Chris@153 499 }
Chris@153 500
Chris@153 501 void
Chris@153 502 SpectrumLayer::setProperties(const QXmlAttributes &attributes)
Chris@153 503 {
Chris@153 504 bool ok = false;
Chris@153 505
Chris@153 506 QString colourSpec = attributes.value("colour");
Chris@153 507 if (colourSpec != "") {
Chris@153 508 QColor colour(colourSpec);
Chris@153 509 if (colour.isValid()) {
Chris@153 510 setBaseColour(QColor(colourSpec));
Chris@153 511 }
Chris@153 512 }
Chris@153 513
Chris@153 514 ChannelMode channelMode = (ChannelMode)
Chris@153 515 attributes.value("channelMode").toInt(&ok);
Chris@153 516 if (ok) setChannelMode(channelMode);
Chris@153 517
Chris@153 518 int channel = attributes.value("channel").toInt(&ok);
Chris@153 519 if (ok) setChannel(channel);
Chris@153 520
Chris@153 521 EnergyScale scale = (EnergyScale)
Chris@153 522 attributes.value("energyScale").toInt(&ok);
Chris@153 523 if (ok) setEnergyScale(scale);
Chris@153 524
Chris@153 525 size_t windowSize = attributes.value("windowSize").toUInt(&ok);
Chris@153 526 if (ok) setWindowSize(windowSize);
Chris@153 527
Chris@153 528 size_t windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok);
Chris@153 529 if (ok) setWindowHopLevel(windowHopLevel);
Chris@153 530
Chris@153 531 float gain = attributes.value("gain").toFloat(&ok);
Chris@153 532 if (ok) setGain(gain);
Chris@153 533
Chris@153 534 bool normalize = (attributes.value("normalize").trimmed() == "true");
Chris@153 535 setNormalize(normalize);
Chris@133 536 }
Chris@133 537
Chris@133 538 bool
Chris@133 539 SpectrumLayer::getValueExtents(float &min, float &max, bool &logarithmic,
Chris@133 540 QString &units) const
Chris@133 541 {
Chris@133 542 return false;
Chris@133 543 }
Chris@133 544