annotate widgets/LevelPanWidget.cpp @ 1066:c98a1fe7c7f5 spectrogram-minor-refactor

Comments
author Chris Cannam
date Thu, 23 Jun 2016 10:51:45 +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