annotate layer/SpectrumLayer.cpp @ 162:f32212631b9c

* Handle generator transforms (plugins whose channel count isn't dependent on number of audio inputs, as they have none) * Be less keen to suspend writing FFT data in spectrogram repaint -- only do it if we find we actually need to query the FFT data (i.e. we aren't repainting an area that hasn't been generated at all yet)
author Chris Cannam
date Tue, 10 Oct 2006 19:04:57 +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