Chris@923: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@923: Chris@923: /* Chris@923: Sonic Visualiser Chris@923: An audio file viewer and annotation editor. Chris@923: Centre for Digital Music, Queen Mary, University of London. Chris@923: Chris@923: This program is free software; you can redistribute it and/or Chris@923: modify it under the terms of the GNU General Public License as Chris@923: published by the Free Software Foundation; either version 2 of the Chris@923: License, or (at your option) any later version. See the file Chris@923: COPYING included with this distribution for more information. Chris@923: */ Chris@923: Chris@923: #include "LevelPanWidget.h" Chris@923: Chris@923: #include Chris@923: #include Chris@923: #include Chris@923: Chris@923: #include "layer/ColourMapper.h" Chris@925: #include "base/AudioLevel.h" Chris@923: Chris@1176: #include "WidgetScale.h" Chris@1176: Chris@923: #include Chris@926: #include Chris@940: #include Chris@923: Chris@923: using std::cerr; Chris@923: using std::endl; Chris@923: Chris@940: static const int maxLevel = 4; // min is 0, may be mute or not depending on m_includeMute Chris@923: static const int maxPan = 2; // range is -maxPan to maxPan Chris@923: Chris@923: LevelPanWidget::LevelPanWidget(QWidget *parent) : Chris@923: QWidget(parent), Chris@923: m_level(maxLevel), Chris@923: m_pan(0), Chris@1177: m_monitorLeft(-1), Chris@1177: m_monitorRight(-1), Chris@940: m_editable(true), Chris@1249: m_editing(false), Chris@940: m_includeMute(true) Chris@923: { Chris@1191: setToolTip(tr("Drag vertically to adjust level, horizontally to adjust pan")); Chris@1201: setLevel(1.0); Chris@1201: setPan(0.0); Chris@923: } Chris@923: Chris@923: LevelPanWidget::~LevelPanWidget() Chris@923: { Chris@923: } Chris@923: Chris@1249: void Chris@1249: LevelPanWidget::setToDefault() Chris@1249: { Chris@1249: setLevel(1.0); Chris@1249: setPan(0.0); Chris@1250: emitLevelChanged(); Chris@1250: emitPanChanged(); Chris@1249: } Chris@1249: Chris@929: QSize Chris@929: LevelPanWidget::sizeHint() const Chris@929: { Chris@1176: return WidgetScale::scaleQSize(QSize(40, 40)); Chris@929: } Chris@929: Chris@940: static int Chris@940: db_to_level(double db) Chris@940: { Chris@940: // Only if !m_includeMute, otherwise AudioLevel is used. Chris@940: // Levels are: +6 0 -6 -12 -20 Chris@940: assert(maxLevel == 4); Chris@940: if (db > 3.) return 4; Chris@940: else if (db > -3.) return 3; Chris@940: else if (db > -9.) return 2; Chris@940: else if (db > -16.) return 1; Chris@940: else return 0; Chris@940: } Chris@940: Chris@940: static double Chris@940: level_to_db(int level) Chris@940: { Chris@940: // Only if !m_includeMute, otherwise AudioLevel is used. Chris@940: // Levels are: +6 0 -6 -12 -20 Chris@940: assert(maxLevel == 4); Chris@940: if (level >= 4) return 6.; Chris@940: else if (level == 3) return 0.; Chris@940: else if (level == 2) return -6.; Chris@940: else if (level == 1) return -12.; Chris@940: else return -20.; Chris@940: } Chris@940: Chris@1177: int Chris@1177: LevelPanWidget::audioLevelToLevel(float audioLevel, bool withMute) Chris@1177: { Chris@1177: int level; Chris@1177: if (withMute) { Chris@1177: level = AudioLevel::multiplier_to_fader Chris@1177: (audioLevel, maxLevel, AudioLevel::ShortFader); Chris@1177: } else { Chris@1177: level = db_to_level(AudioLevel::multiplier_to_dB(audioLevel)); Chris@1177: } Chris@1177: if (level < 0) level = 0; Chris@1177: if (level > maxLevel) level = maxLevel; Chris@1177: return level; Chris@1177: } Chris@1177: Chris@1177: float Chris@1177: LevelPanWidget::levelToAudioLevel(int level, bool withMute) Chris@1177: { Chris@1177: if (withMute) { Chris@1177: return float(AudioLevel::fader_to_multiplier Chris@1177: (level, maxLevel, AudioLevel::ShortFader)); Chris@1177: } else { Chris@1177: return float(AudioLevel::dB_to_multiplier(level_to_db(level))); Chris@1177: } Chris@1177: } Chris@1177: Chris@923: void Chris@925: LevelPanWidget::setLevel(float flevel) Chris@923: { Chris@1177: int level = audioLevelToLevel(flevel, m_includeMute); Chris@925: if (level != m_level) { Chris@1266: m_level = level; Chris@1266: float convertsTo = getLevel(); Chris@1266: if (fabsf(convertsTo - flevel) > 1e-5) { Chris@1266: emitLevelChanged(); Chris@1266: } Chris@1266: update(); Chris@925: } Chris@923: } Chris@923: Chris@940: float Chris@940: LevelPanWidget::getLevel() const Chris@940: { Chris@1201: float flevel = levelToAudioLevel(m_level, m_includeMute); Chris@1201: return flevel; Chris@1177: } Chris@1177: Chris@1177: int Chris@1177: LevelPanWidget::audioPanToPan(float audioPan) Chris@1177: { Chris@1177: int pan = int(round(audioPan * maxPan)); Chris@1177: if (pan < -maxPan) pan = -maxPan; Chris@1177: if (pan > maxPan) pan = maxPan; Chris@1177: return pan; Chris@1177: } Chris@1177: Chris@1177: float Chris@1177: LevelPanWidget::panToAudioPan(int pan) Chris@1177: { Chris@1177: return float(pan) / float(maxPan); Chris@1177: } Chris@1177: Chris@1177: void Chris@1177: LevelPanWidget::setPan(float fpan) Chris@1177: { Chris@1177: int pan = audioPanToPan(fpan); Chris@1177: if (pan != m_pan) { Chris@1177: m_pan = pan; Chris@1177: update(); Chris@940: } Chris@940: } Chris@940: Chris@1177: float Chris@1177: LevelPanWidget::getPan() const Chris@1177: { Chris@1177: return panToAudioPan(m_pan); Chris@1177: } Chris@1177: Chris@923: void Chris@1177: LevelPanWidget::setMonitoringLevels(float left, float right) Chris@923: { Chris@1177: m_monitorLeft = left; Chris@1177: m_monitorRight = right; Chris@923: update(); Chris@923: } Chris@923: Chris@940: bool Chris@940: LevelPanWidget::isEditable() const Chris@940: { Chris@940: return m_editable; Chris@940: } Chris@940: Chris@940: bool Chris@940: LevelPanWidget::includesMute() const Chris@940: { Chris@940: return m_includeMute; Chris@940: } Chris@940: Chris@923: void Chris@923: LevelPanWidget::setEditable(bool editable) Chris@923: { Chris@923: m_editable = editable; Chris@923: update(); Chris@923: } Chris@923: Chris@940: void Chris@940: LevelPanWidget::setIncludeMute(bool include) Chris@923: { Chris@940: m_includeMute = include; Chris@940: emitLevelChanged(); Chris@940: update(); Chris@923: } Chris@923: Chris@923: void Chris@923: LevelPanWidget::emitLevelChanged() Chris@923: { Chris@923: emit levelChanged(getLevel()); Chris@923: } Chris@923: Chris@923: void Chris@923: LevelPanWidget::emitPanChanged() Chris@923: { Chris@923: emit panChanged(getPan()); Chris@923: } Chris@923: Chris@923: void Chris@923: LevelPanWidget::mousePressEvent(QMouseEvent *e) Chris@923: { Chris@1249: if (e->button() == Qt::MidButton || Chris@1249: ((e->button() == Qt::LeftButton) && Chris@1249: (e->modifiers() & Qt::ControlModifier))) { Chris@1249: setToDefault(); Chris@1249: } else if (e->button() == Qt::LeftButton) { Chris@1249: m_editing = true; Chris@1249: mouseMoveEvent(e); Chris@1249: } Chris@1249: } Chris@1249: Chris@1249: void Chris@1249: LevelPanWidget::mouseReleaseEvent(QMouseEvent *e) Chris@1249: { Chris@923: mouseMoveEvent(e); Chris@1249: m_editing = false; Chris@923: } Chris@923: Chris@923: void Chris@923: LevelPanWidget::mouseMoveEvent(QMouseEvent *e) Chris@923: { Chris@923: if (!m_editable) return; Chris@1249: if (!m_editing) return; Chris@923: Chris@923: int level, pan; Chris@929: toCell(rect(), e->pos(), level, pan); Chris@923: if (level == m_level && pan == m_pan) { Chris@1266: return; Chris@923: } Chris@923: if (level != m_level) { Chris@1266: m_level = level; Chris@1266: emitLevelChanged(); Chris@923: } Chris@923: if (pan != m_pan) { Chris@1266: m_pan = pan; Chris@1266: emitPanChanged(); Chris@923: } Chris@923: update(); Chris@923: } Chris@923: Chris@923: void Chris@923: LevelPanWidget::wheelEvent(QWheelEvent *e) Chris@923: { Chris@923: if (e->modifiers() & Qt::ControlModifier) { Chris@1266: if (e->delta() > 0) { Chris@1266: if (m_pan < maxPan) { Chris@1266: ++m_pan; Chris@1266: emitPanChanged(); Chris@1266: update(); Chris@1266: } Chris@1266: } else { Chris@1266: if (m_pan > -maxPan) { Chris@1266: --m_pan; Chris@1266: emitPanChanged(); Chris@1266: update(); Chris@1266: } Chris@1266: } Chris@923: } else { Chris@1266: if (e->delta() > 0) { Chris@1266: if (m_level < maxLevel) { Chris@1266: ++m_level; Chris@1266: emitLevelChanged(); Chris@1266: update(); Chris@1266: } Chris@1266: } else { Chris@1266: if (m_level > 0) { Chris@1266: --m_level; Chris@1266: emitLevelChanged(); Chris@1266: update(); Chris@1266: } Chris@1266: } Chris@923: } Chris@923: } Chris@923: Chris@923: void Chris@929: LevelPanWidget::toCell(QRectF rect, QPointF loc, int &level, int &pan) const Chris@923: { Chris@929: double w = rect.width(), h = rect.height(); Chris@929: Chris@923: int npan = maxPan * 2 + 1; Chris@924: int nlevel = maxLevel + 1; Chris@929: Chris@924: double wcell = w / npan, hcell = h / nlevel; Chris@929: Chris@932: level = int((h - (loc.y() - rect.y())) / hcell); Chris@924: if (level < 0) level = 0; Chris@923: if (level > maxLevel) level = maxLevel; Chris@929: Chris@932: pan = int((loc.x() - rect.x()) / wcell) - maxPan; Chris@923: if (pan < -maxPan) pan = -maxPan; Chris@923: if (pan > maxPan) pan = maxPan; Chris@923: } Chris@923: Chris@923: QSizeF Chris@929: LevelPanWidget::cellSize(QRectF rect) const Chris@923: { Chris@929: double w = rect.width(), h = rect.height(); Chris@923: int npan = maxPan * 2 + 1; Chris@924: int nlevel = maxLevel + 1; Chris@924: double wcell = w / npan, hcell = h / nlevel; Chris@923: return QSizeF(wcell, hcell); Chris@923: } Chris@923: Chris@923: QPointF Chris@929: LevelPanWidget::cellCentre(QRectF rect, int level, int pan) const Chris@923: { Chris@929: QSizeF cs = cellSize(rect); Chris@932: return QPointF(rect.x() + cs.width() * (pan + maxPan) + cs.width() / 2., Chris@1266: rect.y() + rect.height() - cs.height() * (level + 1) + cs.height() / 2.); Chris@923: } Chris@923: Chris@923: QSizeF Chris@929: LevelPanWidget::cellLightSize(QRectF rect) const Chris@923: { Chris@923: double extent = 3. / 4.; Chris@929: QSizeF cs = cellSize(rect); Chris@923: double m = std::min(cs.width(), cs.height()); Chris@923: return QSizeF(m * extent, m * extent); Chris@923: } Chris@923: Chris@923: QRectF Chris@929: LevelPanWidget::cellLightRect(QRectF rect, int level, int pan) const Chris@923: { Chris@929: QSizeF cls = cellLightSize(rect); Chris@929: QPointF cc = cellCentre(rect, level, pan); Chris@923: return QRectF(cc.x() - cls.width() / 2., Chris@1266: cc.y() - cls.height() / 2., Chris@1266: cls.width(), Chris@1266: cls.height()); Chris@923: } Chris@923: Chris@923: double Chris@929: LevelPanWidget::thinLineWidth(QRectF rect) const Chris@923: { Chris@929: double tw = ceil(rect.width() / (maxPan * 2. * 10.)); Chris@929: double th = ceil(rect.height() / (maxLevel * 10.)); Chris@923: return std::min(th, tw); Chris@923: } Chris@923: Chris@941: static QColor Chris@941: level_to_colour(int level) Chris@941: { Chris@941: assert(maxLevel == 4); Chris@941: if (level == 0) return Qt::black; Chris@941: else if (level == 1) return QColor(80, 0, 0); Chris@941: else if (level == 2) return QColor(160, 0, 0); Chris@941: else if (level == 3) return QColor(255, 0, 0); Chris@941: else return QColor(255, 255, 0); Chris@941: } Chris@941: Chris@923: void Chris@929: LevelPanWidget::renderTo(QPaintDevice *dev, QRectF rect, bool asIfEditable) const Chris@923: { Chris@929: QPainter paint(dev); Chris@923: Chris@923: paint.setRenderHint(QPainter::Antialiasing, true); Chris@923: Chris@923: QPen pen; Chris@923: Chris@929: double thin = thinLineWidth(rect); Chris@938: Chris@923: pen.setColor(QColor(127, 127, 127, 127)); Chris@929: pen.setWidthF(cellLightSize(rect).width() + thin); Chris@923: pen.setCapStyle(Qt::RoundCap); Chris@923: paint.setPen(pen); Chris@1177: paint.setBrush(Qt::NoBrush); Chris@923: Chris@923: for (int pan = -maxPan; pan <= maxPan; ++pan) { Chris@1266: paint.drawLine(cellCentre(rect, 0, pan), cellCentre(rect, maxLevel, pan)); Chris@924: } Chris@924: Chris@1177: if (m_monitorLeft > 0.f || m_monitorRight > 0.f) { Chris@1177: paint.setPen(Qt::NoPen); Chris@1177: for (int pan = -maxPan; pan <= maxPan; ++pan) { Chris@1177: float audioPan = panToAudioPan(pan); Chris@1177: float audioLevel; Chris@1177: if (audioPan < 0.f) { Chris@1177: audioLevel = m_monitorLeft + m_monitorRight * (1.f + audioPan); Chris@1177: } else { Chris@1177: audioLevel = m_monitorRight + m_monitorLeft * (1.f - audioPan); Chris@1177: } Chris@1177: int levelHere = audioLevelToLevel(audioLevel, false); Chris@1177: for (int level = 0; level <= levelHere; ++level) { Chris@1177: paint.setBrush(level_to_colour(level)); Chris@1177: QRectF clr = cellLightRect(rect, level, pan); Chris@1177: paint.drawEllipse(clr); Chris@1177: } Chris@1177: } Chris@1177: paint.setPen(pen); Chris@1177: paint.setBrush(Qt::NoBrush); Chris@1177: } Chris@1177: Chris@924: if (isEnabled()) { Chris@1266: pen.setColor(Qt::black); Chris@924: } else { Chris@1266: pen.setColor(Qt::darkGray); Chris@923: } Chris@929: Chris@940: if (!asIfEditable && m_includeMute && m_level == 0) { Chris@929: pen.setWidthF(thin * 2); Chris@929: pen.setCapStyle(Qt::RoundCap); Chris@929: paint.setPen(pen); Chris@930: paint.drawLine(cellCentre(rect, 0, -maxPan), Chris@930: cellCentre(rect, maxLevel, maxPan)); Chris@930: paint.drawLine(cellCentre(rect, maxLevel, -maxPan), Chris@930: cellCentre(rect, 0, maxPan)); Chris@929: return; Chris@929: } Chris@923: Chris@923: pen.setWidthF(thin); Chris@923: pen.setCapStyle(Qt::FlatCap); Chris@923: paint.setPen(pen); Chris@923: Chris@924: for (int level = 0; level <= m_level; ++level) { Chris@1266: if (isEnabled()) { Chris@1266: paint.setBrush(level_to_colour(level)); Chris@1266: } Chris@1266: QRectF clr = cellLightRect(rect, level, m_pan); Chris@1266: if (m_includeMute && m_level == 0) { Chris@1266: paint.drawLine(clr.topLeft(), clr.bottomRight()); Chris@1266: paint.drawLine(clr.bottomLeft(), clr.topRight()); Chris@1266: } else { Chris@1266: paint.drawEllipse(clr); Chris@1266: } Chris@923: } Chris@923: } Chris@923: Chris@929: void Chris@929: LevelPanWidget::paintEvent(QPaintEvent *) Chris@929: { Chris@929: renderTo(this, rect(), m_editable); Chris@929: } Chris@923: Chris@1180: void Chris@1180: LevelPanWidget::enterEvent(QEvent *e) Chris@1180: { Chris@1180: QWidget::enterEvent(e); Chris@1180: emit mouseEntered(); Chris@1180: } Chris@929: Chris@1180: void Chris@1180: LevelPanWidget::leaveEvent(QEvent *e) Chris@1180: { Chris@1180: QWidget::enterEvent(e); Chris@1180: emit mouseLeft(); Chris@1180: } Chris@929: