annotate widgets/LevelPanWidget.cpp @ 1280:34394e8c2942 horizontal-scale

Merge
author Chris Cannam
date Wed, 02 May 2018 14:27:17 +0100
parents a34a2a25907c
children e8368466fa34
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@1176 24 #include "WidgetScale.h"
Chris@1176 25
Chris@923 26 #include <iostream>
Chris@926 27 #include <cmath>
Chris@940 28 #include <cassert>
Chris@923 29
Chris@923 30 using std::cerr;
Chris@923 31 using std::endl;
Chris@923 32
Chris@940 33 static const int maxLevel = 4; // min is 0, may be mute or not depending on m_includeMute
Chris@923 34 static const int maxPan = 2; // range is -maxPan to maxPan
Chris@923 35
Chris@923 36 LevelPanWidget::LevelPanWidget(QWidget *parent) :
Chris@923 37 QWidget(parent),
Chris@923 38 m_level(maxLevel),
Chris@923 39 m_pan(0),
Chris@1177 40 m_monitorLeft(-1),
Chris@1177 41 m_monitorRight(-1),
Chris@940 42 m_editable(true),
Chris@1249 43 m_editing(false),
Chris@940 44 m_includeMute(true)
Chris@923 45 {
Chris@1191 46 setToolTip(tr("Drag vertically to adjust level, horizontally to adjust pan"));
Chris@1201 47 setLevel(1.0);
Chris@1201 48 setPan(0.0);
Chris@923 49 }
Chris@923 50
Chris@923 51 LevelPanWidget::~LevelPanWidget()
Chris@923 52 {
Chris@923 53 }
Chris@923 54
Chris@1249 55 void
Chris@1249 56 LevelPanWidget::setToDefault()
Chris@1249 57 {
Chris@1249 58 setLevel(1.0);
Chris@1249 59 setPan(0.0);
Chris@1250 60 emitLevelChanged();
Chris@1250 61 emitPanChanged();
Chris@1249 62 }
Chris@1249 63
Chris@929 64 QSize
Chris@929 65 LevelPanWidget::sizeHint() const
Chris@929 66 {
Chris@1176 67 return WidgetScale::scaleQSize(QSize(40, 40));
Chris@929 68 }
Chris@929 69
Chris@940 70 static int
Chris@940 71 db_to_level(double db)
Chris@940 72 {
Chris@940 73 // Only if !m_includeMute, otherwise AudioLevel is used.
Chris@940 74 // Levels are: +6 0 -6 -12 -20
Chris@940 75 assert(maxLevel == 4);
Chris@940 76 if (db > 3.) return 4;
Chris@940 77 else if (db > -3.) return 3;
Chris@940 78 else if (db > -9.) return 2;
Chris@940 79 else if (db > -16.) return 1;
Chris@940 80 else return 0;
Chris@940 81 }
Chris@940 82
Chris@940 83 static double
Chris@940 84 level_to_db(int level)
Chris@940 85 {
Chris@940 86 // Only if !m_includeMute, otherwise AudioLevel is used.
Chris@940 87 // Levels are: +6 0 -6 -12 -20
Chris@940 88 assert(maxLevel == 4);
Chris@940 89 if (level >= 4) return 6.;
Chris@940 90 else if (level == 3) return 0.;
Chris@940 91 else if (level == 2) return -6.;
Chris@940 92 else if (level == 1) return -12.;
Chris@940 93 else return -20.;
Chris@940 94 }
Chris@940 95
Chris@1177 96 int
Chris@1177 97 LevelPanWidget::audioLevelToLevel(float audioLevel, bool withMute)
Chris@1177 98 {
Chris@1177 99 int level;
Chris@1177 100 if (withMute) {
Chris@1177 101 level = AudioLevel::multiplier_to_fader
Chris@1177 102 (audioLevel, maxLevel, AudioLevel::ShortFader);
Chris@1177 103 } else {
Chris@1177 104 level = db_to_level(AudioLevel::multiplier_to_dB(audioLevel));
Chris@1177 105 }
Chris@1177 106 if (level < 0) level = 0;
Chris@1177 107 if (level > maxLevel) level = maxLevel;
Chris@1177 108 return level;
Chris@1177 109 }
Chris@1177 110
Chris@1177 111 float
Chris@1177 112 LevelPanWidget::levelToAudioLevel(int level, bool withMute)
Chris@1177 113 {
Chris@1177 114 if (withMute) {
Chris@1177 115 return float(AudioLevel::fader_to_multiplier
Chris@1177 116 (level, maxLevel, AudioLevel::ShortFader));
Chris@1177 117 } else {
Chris@1177 118 return float(AudioLevel::dB_to_multiplier(level_to_db(level)));
Chris@1177 119 }
Chris@1177 120 }
Chris@1177 121
Chris@923 122 void
Chris@925 123 LevelPanWidget::setLevel(float flevel)
Chris@923 124 {
Chris@1177 125 int level = audioLevelToLevel(flevel, m_includeMute);
Chris@925 126 if (level != m_level) {
Chris@1266 127 m_level = level;
Chris@1266 128 float convertsTo = getLevel();
Chris@1266 129 if (fabsf(convertsTo - flevel) > 1e-5) {
Chris@1266 130 emitLevelChanged();
Chris@1266 131 }
Chris@1266 132 update();
Chris@925 133 }
Chris@923 134 }
Chris@923 135
Chris@940 136 float
Chris@940 137 LevelPanWidget::getLevel() const
Chris@940 138 {
Chris@1201 139 float flevel = levelToAudioLevel(m_level, m_includeMute);
Chris@1201 140 return flevel;
Chris@1177 141 }
Chris@1177 142
Chris@1177 143 int
Chris@1177 144 LevelPanWidget::audioPanToPan(float audioPan)
Chris@1177 145 {
Chris@1177 146 int pan = int(round(audioPan * maxPan));
Chris@1177 147 if (pan < -maxPan) pan = -maxPan;
Chris@1177 148 if (pan > maxPan) pan = maxPan;
Chris@1177 149 return pan;
Chris@1177 150 }
Chris@1177 151
Chris@1177 152 float
Chris@1177 153 LevelPanWidget::panToAudioPan(int pan)
Chris@1177 154 {
Chris@1177 155 return float(pan) / float(maxPan);
Chris@1177 156 }
Chris@1177 157
Chris@1177 158 void
Chris@1177 159 LevelPanWidget::setPan(float fpan)
Chris@1177 160 {
Chris@1177 161 int pan = audioPanToPan(fpan);
Chris@1177 162 if (pan != m_pan) {
Chris@1177 163 m_pan = pan;
Chris@1177 164 update();
Chris@940 165 }
Chris@940 166 }
Chris@940 167
Chris@1177 168 float
Chris@1177 169 LevelPanWidget::getPan() const
Chris@1177 170 {
Chris@1177 171 return panToAudioPan(m_pan);
Chris@1177 172 }
Chris@1177 173
Chris@923 174 void
Chris@1177 175 LevelPanWidget::setMonitoringLevels(float left, float right)
Chris@923 176 {
Chris@1177 177 m_monitorLeft = left;
Chris@1177 178 m_monitorRight = right;
Chris@923 179 update();
Chris@923 180 }
Chris@923 181
Chris@940 182 bool
Chris@940 183 LevelPanWidget::isEditable() const
Chris@940 184 {
Chris@940 185 return m_editable;
Chris@940 186 }
Chris@940 187
Chris@940 188 bool
Chris@940 189 LevelPanWidget::includesMute() const
Chris@940 190 {
Chris@940 191 return m_includeMute;
Chris@940 192 }
Chris@940 193
Chris@923 194 void
Chris@923 195 LevelPanWidget::setEditable(bool editable)
Chris@923 196 {
Chris@923 197 m_editable = editable;
Chris@923 198 update();
Chris@923 199 }
Chris@923 200
Chris@940 201 void
Chris@940 202 LevelPanWidget::setIncludeMute(bool include)
Chris@923 203 {
Chris@940 204 m_includeMute = include;
Chris@940 205 emitLevelChanged();
Chris@940 206 update();
Chris@923 207 }
Chris@923 208
Chris@923 209 void
Chris@923 210 LevelPanWidget::emitLevelChanged()
Chris@923 211 {
Chris@923 212 emit levelChanged(getLevel());
Chris@923 213 }
Chris@923 214
Chris@923 215 void
Chris@923 216 LevelPanWidget::emitPanChanged()
Chris@923 217 {
Chris@923 218 emit panChanged(getPan());
Chris@923 219 }
Chris@923 220
Chris@923 221 void
Chris@923 222 LevelPanWidget::mousePressEvent(QMouseEvent *e)
Chris@923 223 {
Chris@1249 224 if (e->button() == Qt::MidButton ||
Chris@1249 225 ((e->button() == Qt::LeftButton) &&
Chris@1249 226 (e->modifiers() & Qt::ControlModifier))) {
Chris@1249 227 setToDefault();
Chris@1249 228 } else if (e->button() == Qt::LeftButton) {
Chris@1249 229 m_editing = true;
Chris@1249 230 mouseMoveEvent(e);
Chris@1249 231 }
Chris@1249 232 }
Chris@1249 233
Chris@1249 234 void
Chris@1249 235 LevelPanWidget::mouseReleaseEvent(QMouseEvent *e)
Chris@1249 236 {
Chris@923 237 mouseMoveEvent(e);
Chris@1249 238 m_editing = false;
Chris@923 239 }
Chris@923 240
Chris@923 241 void
Chris@923 242 LevelPanWidget::mouseMoveEvent(QMouseEvent *e)
Chris@923 243 {
Chris@923 244 if (!m_editable) return;
Chris@1249 245 if (!m_editing) return;
Chris@923 246
Chris@923 247 int level, pan;
Chris@929 248 toCell(rect(), e->pos(), level, pan);
Chris@923 249 if (level == m_level && pan == m_pan) {
Chris@1266 250 return;
Chris@923 251 }
Chris@923 252 if (level != m_level) {
Chris@1266 253 m_level = level;
Chris@1266 254 emitLevelChanged();
Chris@923 255 }
Chris@923 256 if (pan != m_pan) {
Chris@1266 257 m_pan = pan;
Chris@1266 258 emitPanChanged();
Chris@923 259 }
Chris@923 260 update();
Chris@923 261 }
Chris@923 262
Chris@923 263 void
Chris@923 264 LevelPanWidget::wheelEvent(QWheelEvent *e)
Chris@923 265 {
Chris@923 266 if (e->modifiers() & Qt::ControlModifier) {
Chris@1266 267 if (e->delta() > 0) {
Chris@1266 268 if (m_pan < maxPan) {
Chris@1266 269 ++m_pan;
Chris@1266 270 emitPanChanged();
Chris@1266 271 update();
Chris@1266 272 }
Chris@1266 273 } else {
Chris@1266 274 if (m_pan > -maxPan) {
Chris@1266 275 --m_pan;
Chris@1266 276 emitPanChanged();
Chris@1266 277 update();
Chris@1266 278 }
Chris@1266 279 }
Chris@923 280 } else {
Chris@1266 281 if (e->delta() > 0) {
Chris@1266 282 if (m_level < maxLevel) {
Chris@1266 283 ++m_level;
Chris@1266 284 emitLevelChanged();
Chris@1266 285 update();
Chris@1266 286 }
Chris@1266 287 } else {
Chris@1266 288 if (m_level > 0) {
Chris@1266 289 --m_level;
Chris@1266 290 emitLevelChanged();
Chris@1266 291 update();
Chris@1266 292 }
Chris@1266 293 }
Chris@923 294 }
Chris@923 295 }
Chris@923 296
Chris@923 297 void
Chris@929 298 LevelPanWidget::toCell(QRectF rect, QPointF loc, int &level, int &pan) const
Chris@923 299 {
Chris@929 300 double w = rect.width(), h = rect.height();
Chris@929 301
Chris@923 302 int npan = maxPan * 2 + 1;
Chris@924 303 int nlevel = maxLevel + 1;
Chris@929 304
Chris@924 305 double wcell = w / npan, hcell = h / nlevel;
Chris@929 306
Chris@932 307 level = int((h - (loc.y() - rect.y())) / hcell);
Chris@924 308 if (level < 0) level = 0;
Chris@923 309 if (level > maxLevel) level = maxLevel;
Chris@929 310
Chris@932 311 pan = int((loc.x() - rect.x()) / wcell) - maxPan;
Chris@923 312 if (pan < -maxPan) pan = -maxPan;
Chris@923 313 if (pan > maxPan) pan = maxPan;
Chris@923 314 }
Chris@923 315
Chris@923 316 QSizeF
Chris@929 317 LevelPanWidget::cellSize(QRectF rect) const
Chris@923 318 {
Chris@929 319 double w = rect.width(), h = rect.height();
Chris@923 320 int npan = maxPan * 2 + 1;
Chris@924 321 int nlevel = maxLevel + 1;
Chris@924 322 double wcell = w / npan, hcell = h / nlevel;
Chris@923 323 return QSizeF(wcell, hcell);
Chris@923 324 }
Chris@923 325
Chris@923 326 QPointF
Chris@929 327 LevelPanWidget::cellCentre(QRectF rect, int level, int pan) const
Chris@923 328 {
Chris@929 329 QSizeF cs = cellSize(rect);
Chris@932 330 return QPointF(rect.x() + cs.width() * (pan + maxPan) + cs.width() / 2.,
Chris@1266 331 rect.y() + rect.height() - cs.height() * (level + 1) + cs.height() / 2.);
Chris@923 332 }
Chris@923 333
Chris@923 334 QSizeF
Chris@929 335 LevelPanWidget::cellLightSize(QRectF rect) const
Chris@923 336 {
Chris@923 337 double extent = 3. / 4.;
Chris@929 338 QSizeF cs = cellSize(rect);
Chris@923 339 double m = std::min(cs.width(), cs.height());
Chris@923 340 return QSizeF(m * extent, m * extent);
Chris@923 341 }
Chris@923 342
Chris@923 343 QRectF
Chris@929 344 LevelPanWidget::cellLightRect(QRectF rect, int level, int pan) const
Chris@923 345 {
Chris@929 346 QSizeF cls = cellLightSize(rect);
Chris@929 347 QPointF cc = cellCentre(rect, level, pan);
Chris@923 348 return QRectF(cc.x() - cls.width() / 2.,
Chris@1266 349 cc.y() - cls.height() / 2.,
Chris@1266 350 cls.width(),
Chris@1266 351 cls.height());
Chris@923 352 }
Chris@923 353
Chris@923 354 double
Chris@929 355 LevelPanWidget::thinLineWidth(QRectF rect) const
Chris@923 356 {
Chris@929 357 double tw = ceil(rect.width() / (maxPan * 2. * 10.));
Chris@929 358 double th = ceil(rect.height() / (maxLevel * 10.));
Chris@923 359 return std::min(th, tw);
Chris@923 360 }
Chris@923 361
Chris@941 362 static QColor
Chris@941 363 level_to_colour(int level)
Chris@941 364 {
Chris@941 365 assert(maxLevel == 4);
Chris@941 366 if (level == 0) return Qt::black;
Chris@941 367 else if (level == 1) return QColor(80, 0, 0);
Chris@941 368 else if (level == 2) return QColor(160, 0, 0);
Chris@941 369 else if (level == 3) return QColor(255, 0, 0);
Chris@941 370 else return QColor(255, 255, 0);
Chris@941 371 }
Chris@941 372
Chris@923 373 void
Chris@929 374 LevelPanWidget::renderTo(QPaintDevice *dev, QRectF rect, bool asIfEditable) const
Chris@923 375 {
Chris@929 376 QPainter paint(dev);
Chris@923 377
Chris@923 378 paint.setRenderHint(QPainter::Antialiasing, true);
Chris@923 379
Chris@923 380 QPen pen;
Chris@923 381
Chris@929 382 double thin = thinLineWidth(rect);
Chris@938 383
Chris@923 384 pen.setColor(QColor(127, 127, 127, 127));
Chris@929 385 pen.setWidthF(cellLightSize(rect).width() + thin);
Chris@923 386 pen.setCapStyle(Qt::RoundCap);
Chris@923 387 paint.setPen(pen);
Chris@1177 388 paint.setBrush(Qt::NoBrush);
Chris@923 389
Chris@923 390 for (int pan = -maxPan; pan <= maxPan; ++pan) {
Chris@1266 391 paint.drawLine(cellCentre(rect, 0, pan), cellCentre(rect, maxLevel, pan));
Chris@924 392 }
Chris@924 393
Chris@1177 394 if (m_monitorLeft > 0.f || m_monitorRight > 0.f) {
Chris@1177 395 paint.setPen(Qt::NoPen);
Chris@1177 396 for (int pan = -maxPan; pan <= maxPan; ++pan) {
Chris@1177 397 float audioPan = panToAudioPan(pan);
Chris@1177 398 float audioLevel;
Chris@1177 399 if (audioPan < 0.f) {
Chris@1177 400 audioLevel = m_monitorLeft + m_monitorRight * (1.f + audioPan);
Chris@1177 401 } else {
Chris@1177 402 audioLevel = m_monitorRight + m_monitorLeft * (1.f - audioPan);
Chris@1177 403 }
Chris@1177 404 int levelHere = audioLevelToLevel(audioLevel, false);
Chris@1177 405 for (int level = 0; level <= levelHere; ++level) {
Chris@1177 406 paint.setBrush(level_to_colour(level));
Chris@1177 407 QRectF clr = cellLightRect(rect, level, pan);
Chris@1177 408 paint.drawEllipse(clr);
Chris@1177 409 }
Chris@1177 410 }
Chris@1177 411 paint.setPen(pen);
Chris@1177 412 paint.setBrush(Qt::NoBrush);
Chris@1177 413 }
Chris@1177 414
Chris@924 415 if (isEnabled()) {
Chris@1266 416 pen.setColor(Qt::black);
Chris@924 417 } else {
Chris@1266 418 pen.setColor(Qt::darkGray);
Chris@923 419 }
Chris@929 420
Chris@940 421 if (!asIfEditable && m_includeMute && m_level == 0) {
Chris@929 422 pen.setWidthF(thin * 2);
Chris@929 423 pen.setCapStyle(Qt::RoundCap);
Chris@929 424 paint.setPen(pen);
Chris@930 425 paint.drawLine(cellCentre(rect, 0, -maxPan),
Chris@930 426 cellCentre(rect, maxLevel, maxPan));
Chris@930 427 paint.drawLine(cellCentre(rect, maxLevel, -maxPan),
Chris@930 428 cellCentre(rect, 0, maxPan));
Chris@929 429 return;
Chris@929 430 }
Chris@923 431
Chris@923 432 pen.setWidthF(thin);
Chris@923 433 pen.setCapStyle(Qt::FlatCap);
Chris@923 434 paint.setPen(pen);
Chris@923 435
Chris@924 436 for (int level = 0; level <= m_level; ++level) {
Chris@1266 437 if (isEnabled()) {
Chris@1266 438 paint.setBrush(level_to_colour(level));
Chris@1266 439 }
Chris@1266 440 QRectF clr = cellLightRect(rect, level, m_pan);
Chris@1266 441 if (m_includeMute && m_level == 0) {
Chris@1266 442 paint.drawLine(clr.topLeft(), clr.bottomRight());
Chris@1266 443 paint.drawLine(clr.bottomLeft(), clr.topRight());
Chris@1266 444 } else {
Chris@1266 445 paint.drawEllipse(clr);
Chris@1266 446 }
Chris@923 447 }
Chris@923 448 }
Chris@923 449
Chris@929 450 void
Chris@929 451 LevelPanWidget::paintEvent(QPaintEvent *)
Chris@929 452 {
Chris@929 453 renderTo(this, rect(), m_editable);
Chris@929 454 }
Chris@923 455
Chris@1180 456 void
Chris@1180 457 LevelPanWidget::enterEvent(QEvent *e)
Chris@1180 458 {
Chris@1180 459 QWidget::enterEvent(e);
Chris@1180 460 emit mouseEntered();
Chris@1180 461 }
Chris@929 462
Chris@1180 463 void
Chris@1180 464 LevelPanWidget::leaveEvent(QEvent *e)
Chris@1180 465 {
Chris@1180 466 QWidget::enterEvent(e);
Chris@1180 467 emit mouseLeft();
Chris@1180 468 }
Chris@929 469