annotate layer/SpectrumLayer.cpp @ 157:e68cc314deb7

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