annotate widgets/LevelPanWidget.cpp @ 1127:9fb8dfd7ce4c spectrogram-minor-refactor

Fix threshold in spectrogram -- it wasn't working in the last release. There is a new protocol for this. Formerly the threshold parameter had a range from -50dB to 0 with the default at -50, and -50 treated internally as "no threshold". However, there was a hardcoded, hidden internal threshold for spectrogram colour mapping at -80dB with anything below this being rounded to zero. Now the threshold parameter has range -81 to -1 with the default at -80, -81 is treated internally as "no threshold", and there is no hidden internal threshold. So the default behaviour is the same as before, an effective -80dB threshold, but it is now possible to change this in both directions. Sessions reloaded from prior versions may look slightly different because, if the session says there should be no threshold, there will now actually be no threshold instead of having the hidden internal one. Still need to do something in the UI to make it apparent that the -81dB setting removes the threshold entirely. This is at least no worse than the previous, also obscured, magic -50dB setting.
author Chris Cannam
date Mon, 01 Aug 2016 16:21:01 +0100
parents d6acb8e36605
children 125748a569fa
rev   line source
Chris@923 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@923 2
Chris@923 3 /*
Chris@923 4 Sonic Visualiser
Chris@923 5 An audio file viewer and annotation editor.
Chris@923 6 Centre for Digital Music, Queen Mary, University of London.
Chris@923 7
Chris@923 8 This program is free software; you can redistribute it and/or
Chris@923 9 modify it under the terms of the GNU General Public License as
Chris@923 10 published by the Free Software Foundation; either version 2 of the
Chris@923 11 License, or (at your option) any later version. See the file
Chris@923 12 COPYING included with this distribution for more information.
Chris@923 13 */
Chris@923 14
Chris@923 15 #include "LevelPanWidget.h"
Chris@923 16
Chris@923 17 #include <QPainter>
Chris@923 18 #include <QMouseEvent>
Chris@923 19 #include <QWheelEvent>
Chris@923 20
Chris@923 21 #include "layer/ColourMapper.h"
Chris@925 22 #include "base/AudioLevel.h"
Chris@923 23
Chris@923 24 #include <iostream>
Chris@926 25 #include <cmath>
Chris@940 26 #include <cassert>
Chris@923 27
Chris@923 28 using std::cerr;
Chris@923 29 using std::endl;
Chris@923 30
Chris@940 31 static const int maxLevel = 4; // min is 0, may be mute or not depending on m_includeMute
Chris@923 32 static const int maxPan = 2; // range is -maxPan to maxPan
Chris@923 33
Chris@923 34 LevelPanWidget::LevelPanWidget(QWidget *parent) :
Chris@923 35 QWidget(parent),
Chris@923 36 m_level(maxLevel),
Chris@923 37 m_pan(0),
Chris@940 38 m_editable(true),
Chris@940 39 m_includeMute(true)
Chris@923 40 {
Chris@923 41 }
Chris@923 42
Chris@923 43 LevelPanWidget::~LevelPanWidget()
Chris@923 44 {
Chris@923 45 }
Chris@923 46
Chris@929 47 QSize
Chris@929 48 LevelPanWidget::sizeHint() const
Chris@929 49 {
Chris@929 50 static double ratio = 0.0;
Chris@929 51 if (ratio == 0.0) {
Chris@929 52 double baseEm;
Chris@929 53 #ifdef Q_OS_MAC
Chris@929 54 baseEm = 17.0;
Chris@929 55 #else
Chris@929 56 baseEm = 15.0;
Chris@929 57 #endif
Chris@929 58 double em = QFontMetrics(QFont()).height();
Chris@929 59 ratio = em / baseEm;
Chris@929 60 }
Chris@929 61
Chris@929 62 int pixels = 40;
Chris@929 63 int scaled = int(pixels * ratio + 0.5);
Chris@929 64 if (pixels != 0 && scaled == 0) scaled = 1;
Chris@929 65 return QSize(scaled, scaled);
Chris@929 66 }
Chris@929 67
Chris@940 68 static int
Chris@940 69 db_to_level(double db)
Chris@940 70 {
Chris@940 71 // Only if !m_includeMute, otherwise AudioLevel is used.
Chris@940 72 // Levels are: +6 0 -6 -12 -20
Chris@940 73 assert(maxLevel == 4);
Chris@940 74 if (db > 3.) return 4;
Chris@940 75 else if (db > -3.) return 3;
Chris@940 76 else if (db > -9.) return 2;
Chris@940 77 else if (db > -16.) return 1;
Chris@940 78 else return 0;
Chris@940 79 }
Chris@940 80
Chris@940 81 static double
Chris@940 82 level_to_db(int level)
Chris@940 83 {
Chris@940 84 // Only if !m_includeMute, otherwise AudioLevel is used.
Chris@940 85 // Levels are: +6 0 -6 -12 -20
Chris@940 86 assert(maxLevel == 4);
Chris@940 87 if (level >= 4) return 6.;
Chris@940 88 else if (level == 3) return 0.;
Chris@940 89 else if (level == 2) return -6.;
Chris@940 90 else if (level == 1) return -12.;
Chris@940 91 else return -20.;
Chris@940 92 }
Chris@940 93
Chris@923 94 void
Chris@925 95 LevelPanWidget::setLevel(float flevel)
Chris@923 96 {
Chris@940 97 int level;
Chris@940 98 if (m_includeMute) {
Chris@940 99 level = AudioLevel::multiplier_to_fader
Chris@940 100 (flevel, maxLevel, AudioLevel::ShortFader);
Chris@940 101 } else {
Chris@940 102 level = db_to_level(AudioLevel::multiplier_to_dB(flevel));
Chris@940 103 }
Chris@925 104 if (level < 0) level = 0;
Chris@925 105 if (level > maxLevel) level = maxLevel;
Chris@925 106 if (level != m_level) {
Chris@925 107 m_level = level;
Chris@925 108 float convertsTo = getLevel();
Chris@925 109 if (fabsf(convertsTo - flevel) > 1e-5) {
Chris@925 110 emitLevelChanged();
Chris@925 111 }
Chris@925 112 update();
Chris@925 113 }
Chris@923 114 }
Chris@923 115
Chris@940 116 float
Chris@940 117 LevelPanWidget::getLevel() const
Chris@940 118 {
Chris@940 119 if (m_includeMute) {
Chris@940 120 return float(AudioLevel::fader_to_multiplier
Chris@940 121 (m_level, maxLevel, AudioLevel::ShortFader));
Chris@940 122 } else {
Chris@940 123 return float(AudioLevel::dB_to_multiplier(level_to_db(m_level)));
Chris@940 124 }
Chris@940 125 }
Chris@940 126
Chris@923 127 void
Chris@923 128 LevelPanWidget::setPan(float pan)
Chris@923 129 {
Chris@923 130 m_pan = int(round(pan * maxPan));
Chris@923 131 if (m_pan < -maxPan) m_pan = -maxPan;
Chris@923 132 if (m_pan > maxPan) m_pan = maxPan;
Chris@923 133 update();
Chris@923 134 }
Chris@923 135
Chris@940 136 bool
Chris@940 137 LevelPanWidget::isEditable() const
Chris@940 138 {
Chris@940 139 return m_editable;
Chris@940 140 }
Chris@940 141
Chris@940 142 bool
Chris@940 143 LevelPanWidget::includesMute() const
Chris@940 144 {
Chris@940 145 return m_includeMute;
Chris@940 146 }
Chris@940 147
Chris@923 148 void
Chris@923 149 LevelPanWidget::setEditable(bool editable)
Chris@923 150 {
Chris@923 151 m_editable = editable;
Chris@923 152 update();
Chris@923 153 }
Chris@923 154
Chris@940 155 void
Chris@940 156 LevelPanWidget::setIncludeMute(bool include)
Chris@923 157 {
Chris@940 158 m_includeMute = include;
Chris@940 159 emitLevelChanged();
Chris@940 160 update();
Chris@923 161 }
Chris@923 162
Chris@923 163 float
Chris@923 164 LevelPanWidget::getPan() const
Chris@923 165 {
Chris@923 166 return float(m_pan) / float(maxPan);
Chris@923 167 }
Chris@923 168
Chris@923 169 void
Chris@923 170 LevelPanWidget::emitLevelChanged()
Chris@923 171 {
Chris@923 172 cerr << "emitting levelChanged(" << getLevel() << ")" << endl;
Chris@923 173 emit levelChanged(getLevel());
Chris@923 174 }
Chris@923 175
Chris@923 176 void
Chris@923 177 LevelPanWidget::emitPanChanged()
Chris@923 178 {
Chris@923 179 cerr << "emitting panChanged(" << getPan() << ")" << endl;
Chris@923 180 emit panChanged(getPan());
Chris@923 181 }
Chris@923 182
Chris@923 183 void
Chris@923 184 LevelPanWidget::mousePressEvent(QMouseEvent *e)
Chris@923 185 {
Chris@923 186 mouseMoveEvent(e);
Chris@923 187 }
Chris@923 188
Chris@923 189 void
Chris@923 190 LevelPanWidget::mouseMoveEvent(QMouseEvent *e)
Chris@923 191 {
Chris@923 192 if (!m_editable) return;
Chris@923 193
Chris@923 194 int level, pan;
Chris@929 195 toCell(rect(), e->pos(), level, pan);
Chris@923 196 if (level == m_level && pan == m_pan) {
Chris@923 197 return;
Chris@923 198 }
Chris@923 199 if (level != m_level) {
Chris@923 200 m_level = level;
Chris@923 201 emitLevelChanged();
Chris@923 202 }
Chris@923 203 if (pan != m_pan) {
Chris@923 204 m_pan = pan;
Chris@923 205 emitPanChanged();
Chris@923 206 }
Chris@923 207 update();
Chris@923 208 }
Chris@923 209
Chris@923 210 void
Chris@923 211 LevelPanWidget::mouseReleaseEvent(QMouseEvent *e)
Chris@923 212 {
Chris@923 213 mouseMoveEvent(e);
Chris@923 214 }
Chris@923 215
Chris@923 216 void
Chris@923 217 LevelPanWidget::wheelEvent(QWheelEvent *e)
Chris@923 218 {
Chris@923 219 if (e->modifiers() & Qt::ControlModifier) {
Chris@923 220 if (e->delta() > 0) {
Chris@923 221 if (m_pan < maxPan) {
Chris@923 222 ++m_pan;
Chris@923 223 emitPanChanged();
Chris@923 224 update();
Chris@923 225 }
Chris@923 226 } else {
Chris@923 227 if (m_pan > -maxPan) {
Chris@923 228 --m_pan;
Chris@923 229 emitPanChanged();
Chris@923 230 update();
Chris@923 231 }
Chris@923 232 }
Chris@923 233 } else {
Chris@923 234 if (e->delta() > 0) {
Chris@923 235 if (m_level < maxLevel) {
Chris@923 236 ++m_level;
Chris@923 237 emitLevelChanged();
Chris@923 238 update();
Chris@923 239 }
Chris@923 240 } else {
Chris@923 241 if (m_level > 0) {
Chris@923 242 --m_level;
Chris@923 243 emitLevelChanged();
Chris@923 244 update();
Chris@923 245 }
Chris@923 246 }
Chris@923 247 }
Chris@923 248 }
Chris@923 249
Chris@923 250 void
Chris@929 251 LevelPanWidget::toCell(QRectF rect, QPointF loc, int &level, int &pan) const
Chris@923 252 {
Chris@929 253 double w = rect.width(), h = rect.height();
Chris@929 254
Chris@923 255 int npan = maxPan * 2 + 1;
Chris@924 256 int nlevel = maxLevel + 1;
Chris@929 257
Chris@924 258 double wcell = w / npan, hcell = h / nlevel;
Chris@929 259
Chris@932 260 level = int((h - (loc.y() - rect.y())) / hcell);
Chris@924 261 if (level < 0) level = 0;
Chris@923 262 if (level > maxLevel) level = maxLevel;
Chris@929 263
Chris@932 264 pan = int((loc.x() - rect.x()) / wcell) - maxPan;
Chris@923 265 if (pan < -maxPan) pan = -maxPan;
Chris@923 266 if (pan > maxPan) pan = maxPan;
Chris@923 267 }
Chris@923 268
Chris@923 269 QSizeF
Chris@929 270 LevelPanWidget::cellSize(QRectF rect) const
Chris@923 271 {
Chris@929 272 double w = rect.width(), h = rect.height();
Chris@923 273 int npan = maxPan * 2 + 1;
Chris@924 274 int nlevel = maxLevel + 1;
Chris@924 275 double wcell = w / npan, hcell = h / nlevel;
Chris@923 276 return QSizeF(wcell, hcell);
Chris@923 277 }
Chris@923 278
Chris@923 279 QPointF
Chris@929 280 LevelPanWidget::cellCentre(QRectF rect, int level, int pan) const
Chris@923 281 {
Chris@929 282 QSizeF cs = cellSize(rect);
Chris@932 283 return QPointF(rect.x() + cs.width() * (pan + maxPan) + cs.width() / 2.,
Chris@932 284 rect.y() + rect.height() - cs.height() * (level + 1) + cs.height() / 2.);
Chris@923 285 }
Chris@923 286
Chris@923 287 QSizeF
Chris@929 288 LevelPanWidget::cellLightSize(QRectF rect) const
Chris@923 289 {
Chris@923 290 double extent = 3. / 4.;
Chris@929 291 QSizeF cs = cellSize(rect);
Chris@923 292 double m = std::min(cs.width(), cs.height());
Chris@923 293 return QSizeF(m * extent, m * extent);
Chris@923 294 }
Chris@923 295
Chris@923 296 QRectF
Chris@929 297 LevelPanWidget::cellLightRect(QRectF rect, int level, int pan) const
Chris@923 298 {
Chris@929 299 QSizeF cls = cellLightSize(rect);
Chris@929 300 QPointF cc = cellCentre(rect, level, pan);
Chris@923 301 return QRectF(cc.x() - cls.width() / 2.,
Chris@923 302 cc.y() - cls.height() / 2.,
Chris@923 303 cls.width(),
Chris@923 304 cls.height());
Chris@923 305 }
Chris@923 306
Chris@923 307 double
Chris@929 308 LevelPanWidget::thinLineWidth(QRectF rect) const
Chris@923 309 {
Chris@929 310 double tw = ceil(rect.width() / (maxPan * 2. * 10.));
Chris@929 311 double th = ceil(rect.height() / (maxLevel * 10.));
Chris@923 312 return std::min(th, tw);
Chris@923 313 }
Chris@923 314
Chris@941 315 static QColor
Chris@941 316 level_to_colour(int level)
Chris@941 317 {
Chris@941 318 assert(maxLevel == 4);
Chris@941 319 if (level == 0) return Qt::black;
Chris@941 320 else if (level == 1) return QColor(80, 0, 0);
Chris@941 321 else if (level == 2) return QColor(160, 0, 0);
Chris@941 322 else if (level == 3) return QColor(255, 0, 0);
Chris@941 323 else return QColor(255, 255, 0);
Chris@941 324 }
Chris@941 325
Chris@923 326 void
Chris@929 327 LevelPanWidget::renderTo(QPaintDevice *dev, QRectF rect, bool asIfEditable) const
Chris@923 328 {
Chris@929 329 QPainter paint(dev);
Chris@923 330
Chris@923 331 paint.setRenderHint(QPainter::Antialiasing, true);
Chris@923 332
Chris@923 333 QPen pen;
Chris@923 334
Chris@929 335 double thin = thinLineWidth(rect);
Chris@938 336
Chris@923 337 pen.setColor(QColor(127, 127, 127, 127));
Chris@929 338 pen.setWidthF(cellLightSize(rect).width() + thin);
Chris@923 339 pen.setCapStyle(Qt::RoundCap);
Chris@923 340 paint.setPen(pen);
Chris@923 341
Chris@923 342 for (int pan = -maxPan; pan <= maxPan; ++pan) {
Chris@929 343 paint.drawLine(cellCentre(rect, 0, pan), cellCentre(rect, maxLevel, pan));
Chris@924 344 }
Chris@924 345
Chris@924 346 if (isEnabled()) {
Chris@924 347 pen.setColor(Qt::black);
Chris@924 348 } else {
Chris@924 349 pen.setColor(Qt::darkGray);
Chris@923 350 }
Chris@929 351
Chris@940 352 if (!asIfEditable && m_includeMute && m_level == 0) {
Chris@929 353 pen.setWidthF(thin * 2);
Chris@929 354 pen.setCapStyle(Qt::RoundCap);
Chris@929 355 paint.setPen(pen);
Chris@930 356 paint.drawLine(cellCentre(rect, 0, -maxPan),
Chris@930 357 cellCentre(rect, maxLevel, maxPan));
Chris@930 358 paint.drawLine(cellCentre(rect, maxLevel, -maxPan),
Chris@930 359 cellCentre(rect, 0, maxPan));
Chris@929 360 return;
Chris@929 361 }
Chris@923 362
Chris@923 363 pen.setWidthF(thin);
Chris@923 364 pen.setCapStyle(Qt::FlatCap);
Chris@923 365 paint.setPen(pen);
Chris@923 366
Chris@924 367 for (int level = 0; level <= m_level; ++level) {
Chris@924 368 if (isEnabled()) {
Chris@941 369 paint.setBrush(level_to_colour(level));
Chris@924 370 }
Chris@929 371 QRectF clr = cellLightRect(rect, level, m_pan);
Chris@940 372 if (m_includeMute && m_level == 0) {
Chris@924 373 paint.drawLine(clr.topLeft(), clr.bottomRight());
Chris@924 374 paint.drawLine(clr.bottomLeft(), clr.topRight());
Chris@924 375 } else {
Chris@924 376 paint.drawEllipse(clr);
Chris@924 377 }
Chris@923 378 }
Chris@923 379 }
Chris@923 380
Chris@929 381 void
Chris@929 382 LevelPanWidget::paintEvent(QPaintEvent *)
Chris@929 383 {
Chris@929 384 renderTo(this, rect(), m_editable);
Chris@929 385 }
Chris@923 386
Chris@929 387
Chris@929 388