Mercurial > hg > svgui
diff widgets/LevelPanWidget.cpp @ 1324:13d9b422f7fe zoom
Merge from default branch
author | Chris Cannam |
---|---|
date | Mon, 17 Sep 2018 13:51:31 +0100 |
parents | 5af5c611f4cb |
children |
line wrap: on
line diff
--- a/widgets/LevelPanWidget.cpp Mon Dec 12 15:18:52 2016 +0000 +++ b/widgets/LevelPanWidget.cpp Mon Sep 17 13:51:31 2018 +0100 @@ -21,6 +21,8 @@ #include "layer/ColourMapper.h" #include "base/AudioLevel.h" +#include "WidgetScale.h" + #include <iostream> #include <cmath> #include <cassert> @@ -28,108 +30,165 @@ using std::cerr; using std::endl; -static const int maxLevel = 4; // min is 0, may be mute or not depending on m_includeMute +/** + * Gain and pan scales: + * + * Gain: we have 5 circles vertically in the display, each of which + * has half-circle and full-circle versions, and we also have "no + * circles", so there are in total 11 distinct levels, which we refer + * to as "notches" and number 0-10. (We use "notch" because "level" is + * used by the external API to refer to audio gain.) + * + * i.e. the levels are represented by these (schematic, rotated to + * horizontal) displays: + * + * 0 X + * 1 [ + * 2 [] + * 3 [][ + * ... + * 9 [][][][][ + * 10 [][][][][] + * + * If we have mute enabled, then we map the range 0-10 to gain using + * AudioLevel::fader_to_* with the ShortFader type, which treats fader + * 0 as muted. If mute is disabled, then we map the range 1-10. + * + * We can also disable half-circles, which leaves the range unchanged + * but limits the notches to even values. + * + * Pan: we have 5 columns with no finer resolution, so we only have 2 + * possible pan values on each side of centre. + */ + static const int maxPan = 2; // range is -maxPan to maxPan LevelPanWidget::LevelPanWidget(QWidget *parent) : QWidget(parent), - m_level(maxLevel), + m_minNotch(0), + m_maxNotch(10), + m_notch(m_maxNotch), m_pan(0), + m_monitorLeft(-1), + m_monitorRight(-1), m_editable(true), - m_includeMute(true) + m_editing(false), + m_includeMute(true), + m_includeHalfSteps(true) { + setToolTip(tr("Drag vertically to adjust level, horizontally to adjust pan")); + setLevel(1.0); + setPan(0.0); } LevelPanWidget::~LevelPanWidget() { } +void +LevelPanWidget::setToDefault() +{ + setLevel(1.0); + setPan(0.0); + emitLevelChanged(); + emitPanChanged(); +} + QSize LevelPanWidget::sizeHint() const { - static double ratio = 0.0; - if (ratio == 0.0) { - double baseEm; -#ifdef Q_OS_MAC - baseEm = 17.0; -#else - baseEm = 15.0; -#endif - double em = QFontMetrics(QFont()).height(); - ratio = em / baseEm; - } - - int pixels = 40; - int scaled = int(pixels * ratio + 0.5); - if (pixels != 0 && scaled == 0) scaled = 1; - return QSize(scaled, scaled); + return WidgetScale::scaleQSize(QSize(40, 40)); } -static int -db_to_level(double db) +int +LevelPanWidget::clampNotch(int notch) const { - // Only if !m_includeMute, otherwise AudioLevel is used. - // Levels are: +6 0 -6 -12 -20 - assert(maxLevel == 4); - if (db > 3.) return 4; - else if (db > -3.) return 3; - else if (db > -9.) return 2; - else if (db > -16.) return 1; - else return 0; + if (notch < m_minNotch) notch = m_minNotch; + if (notch > m_maxNotch) notch = m_maxNotch; + if (!m_includeHalfSteps) { + notch = (notch / 2) * 2; + } + return notch; } -static double -level_to_db(int level) +int +LevelPanWidget::clampPan(int pan) const { - // Only if !m_includeMute, otherwise AudioLevel is used. - // Levels are: +6 0 -6 -12 -20 - assert(maxLevel == 4); - if (level >= 4) return 6.; - else if (level == 3) return 0.; - else if (level == 2) return -6.; - else if (level == 1) return -12.; - else return -20.; + if (pan < -maxPan) pan = -maxPan; + if (pan > maxPan) pan = maxPan; + return pan; +} + +int +LevelPanWidget::audioLevelToNotch(float audioLevel) const +{ + int notch = AudioLevel::multiplier_to_fader + (audioLevel, m_maxNotch, AudioLevel::ShortFader); + return clampNotch(notch); +} + +float +LevelPanWidget::notchToAudioLevel(int notch) const +{ + return float(AudioLevel::fader_to_multiplier + (notch, m_maxNotch, AudioLevel::ShortFader)); } void -LevelPanWidget::setLevel(float flevel) +LevelPanWidget::setLevel(float level) { - int level; - if (m_includeMute) { - level = AudioLevel::multiplier_to_fader - (flevel, maxLevel, AudioLevel::ShortFader); - } else { - level = db_to_level(AudioLevel::multiplier_to_dB(flevel)); - } - if (level < 0) level = 0; - if (level > maxLevel) level = maxLevel; - if (level != m_level) { - m_level = level; - float convertsTo = getLevel(); - if (fabsf(convertsTo - flevel) > 1e-5) { - emitLevelChanged(); - } - update(); + int notch = audioLevelToNotch(level); + if (notch != m_notch) { + m_notch = notch; + float convertsTo = getLevel(); + if (fabsf(convertsTo - level) > 1e-5) { + emitLevelChanged(); + } + update(); } } float LevelPanWidget::getLevel() const { - if (m_includeMute) { - return float(AudioLevel::fader_to_multiplier - (m_level, maxLevel, AudioLevel::ShortFader)); - } else { - return float(AudioLevel::dB_to_multiplier(level_to_db(m_level))); + return notchToAudioLevel(m_notch); +} + +int +LevelPanWidget::audioPanToPan(float audioPan) const +{ + int pan = int(round(audioPan * maxPan)); + pan = clampPan(pan); + return pan; +} + +float +LevelPanWidget::panToAudioPan(int pan) const +{ + return float(pan) / float(maxPan); +} + +void +LevelPanWidget::setPan(float fpan) +{ + int pan = audioPanToPan(fpan); + if (pan != m_pan) { + m_pan = pan; + update(); } } +float +LevelPanWidget::getPan() const +{ + return panToAudioPan(m_pan); +} + void -LevelPanWidget::setPan(float pan) +LevelPanWidget::setMonitoringLevels(float left, float right) { - m_pan = int(round(pan * maxPan)); - if (m_pan < -maxPan) m_pan = -maxPan; - if (m_pan > maxPan) m_pan = maxPan; + m_monitorLeft = left; + m_monitorRight = right; update(); } @@ -156,171 +215,190 @@ LevelPanWidget::setIncludeMute(bool include) { m_includeMute = include; + if (m_includeMute) { + m_minNotch = 0; + } else { + m_minNotch = 1; + } emitLevelChanged(); update(); } -float -LevelPanWidget::getPan() const -{ - return float(m_pan) / float(maxPan); -} - void LevelPanWidget::emitLevelChanged() { - cerr << "emitting levelChanged(" << getLevel() << ")" << endl; emit levelChanged(getLevel()); } void LevelPanWidget::emitPanChanged() { - cerr << "emitting panChanged(" << getPan() << ")" << endl; emit panChanged(getPan()); } void LevelPanWidget::mousePressEvent(QMouseEvent *e) { + if (e->button() == Qt::MidButton || + ((e->button() == Qt::LeftButton) && + (e->modifiers() & Qt::ControlModifier))) { + setToDefault(); + } else if (e->button() == Qt::LeftButton) { + m_editing = true; + mouseMoveEvent(e); + } +} + +void +LevelPanWidget::mouseReleaseEvent(QMouseEvent *e) +{ mouseMoveEvent(e); + m_editing = false; } void LevelPanWidget::mouseMoveEvent(QMouseEvent *e) { if (!m_editable) return; + if (!m_editing) return; - int level, pan; - toCell(rect(), e->pos(), level, pan); - if (level == m_level && pan == m_pan) { - return; + int notch = coordsToNotch(rect(), e->pos()); + int pan = coordsToPan(rect(), e->pos()); + + if (notch == m_notch && pan == m_pan) { + return; } - if (level != m_level) { - m_level = level; - emitLevelChanged(); + if (notch != m_notch) { + m_notch = notch; + emitLevelChanged(); } if (pan != m_pan) { - m_pan = pan; - emitPanChanged(); + m_pan = pan; + emitPanChanged(); } update(); } void -LevelPanWidget::mouseReleaseEvent(QMouseEvent *e) -{ - mouseMoveEvent(e); -} - -void LevelPanWidget::wheelEvent(QWheelEvent *e) { + int delta = m_wheelCounter.count(e); + + if (delta == 0) { + return; + } + if (e->modifiers() & Qt::ControlModifier) { - if (e->delta() > 0) { - if (m_pan < maxPan) { - ++m_pan; - emitPanChanged(); - update(); - } - } else { - if (m_pan > -maxPan) { - --m_pan; - emitPanChanged(); - update(); - } - } + m_pan = clampPan(m_pan + delta); + emitPanChanged(); + update(); } else { - if (e->delta() > 0) { - if (m_level < maxLevel) { - ++m_level; - emitLevelChanged(); - update(); - } - } else { - if (m_level > 0) { - --m_level; - emitLevelChanged(); - update(); - } - } + m_notch = clampNotch(m_notch + delta); + emitLevelChanged(); + update(); } } -void -LevelPanWidget::toCell(QRectF rect, QPointF loc, int &level, int &pan) const +int +LevelPanWidget::coordsToNotch(QRectF rect, QPointF loc) const { - double w = rect.width(), h = rect.height(); + double h = rect.height(); + + int nnotch = m_maxNotch + 1; + double cell = h / nnotch; + + int notch = int((h - (loc.y() - rect.y())) / cell); + notch = clampNotch(notch); + + return notch; +} + +int +LevelPanWidget::coordsToPan(QRectF rect, QPointF loc) const +{ + double w = rect.width(); int npan = maxPan * 2 + 1; - int nlevel = maxLevel + 1; + double cell = w / npan; - double wcell = w / npan, hcell = h / nlevel; + int pan = int((loc.x() - rect.x()) / cell) - maxPan; + pan = clampPan(pan); - level = int((h - (loc.y() - rect.y())) / hcell); - if (level < 0) level = 0; - if (level > maxLevel) level = maxLevel; - - pan = int((loc.x() - rect.x()) / wcell) - maxPan; - if (pan < -maxPan) pan = -maxPan; - if (pan > maxPan) pan = maxPan; + return pan; } QSizeF LevelPanWidget::cellSize(QRectF rect) const { double w = rect.width(), h = rect.height(); - int npan = maxPan * 2 + 1; - int nlevel = maxLevel + 1; - double wcell = w / npan, hcell = h / nlevel; + int ncol = maxPan * 2 + 1; + int nrow = m_maxNotch/2; + double wcell = w / ncol, hcell = h / nrow; return QSizeF(wcell, hcell); } QPointF -LevelPanWidget::cellCentre(QRectF rect, int level, int pan) const +LevelPanWidget::cellCentre(QRectF rect, int row, int col) const { QSizeF cs = cellSize(rect); - return QPointF(rect.x() + cs.width() * (pan + maxPan) + cs.width() / 2., - rect.y() + rect.height() - cs.height() * (level + 1) + cs.height() / 2.); + return QPointF(rect.x() + + cs.width() * (col + maxPan) + cs.width() / 2., + rect.y() + rect.height() - + cs.height() * (row + 1) + cs.height() / 2.); } QSizeF LevelPanWidget::cellLightSize(QRectF rect) const { - double extent = 3. / 4.; + double extent = 0.7; QSizeF cs = cellSize(rect); double m = std::min(cs.width(), cs.height()); return QSizeF(m * extent, m * extent); } QRectF -LevelPanWidget::cellLightRect(QRectF rect, int level, int pan) const +LevelPanWidget::cellLightRect(QRectF rect, int row, int col) const { QSizeF cls = cellLightSize(rect); - QPointF cc = cellCentre(rect, level, pan); + QPointF cc = cellCentre(rect, row, col); return QRectF(cc.x() - cls.width() / 2., - cc.y() - cls.height() / 2., - cls.width(), - cls.height()); + cc.y() - cls.height() / 2., + cls.width(), + cls.height()); } double LevelPanWidget::thinLineWidth(QRectF rect) const { double tw = ceil(rect.width() / (maxPan * 2. * 10.)); - double th = ceil(rect.height() / (maxLevel * 10.)); + double th = ceil(rect.height() / (m_maxNotch/2 * 10.)); return std::min(th, tw); } -static QColor -level_to_colour(int level) +double +LevelPanWidget::cornerRadius(QRectF rect) const { - assert(maxLevel == 4); - if (level == 0) return Qt::black; - else if (level == 1) return QColor(80, 0, 0); - else if (level == 2) return QColor(160, 0, 0); - else if (level == 3) return QColor(255, 0, 0); - else return QColor(255, 255, 0); + QSizeF cs = cellSize(rect); + double m = std::min(cs.width(), cs.height()); + return m / 5; +} + +QRectF +LevelPanWidget::cellOutlineRect(QRectF rect, int row, int col) const +{ + QRectF clr = cellLightRect(rect, row, col); + double adj = thinLineWidth(rect)/2 + 0.5; + return clr.adjusted(-adj, -adj, adj, adj); +} + +QColor +LevelPanWidget::cellToColour(int cell) const +{ + if (cell < 1) return Qt::black; + if (cell < 2) return QColor(80, 0, 0); + if (cell < 3) return QColor(160, 0, 0); + if (cell < 4) return QColor(255, 0, 0); + return QColor(255, 255, 0); } void @@ -330,51 +408,166 @@ paint.setRenderHint(QPainter::Antialiasing, true); + double thin = thinLineWidth(rect); + double radius = cornerRadius(rect); + + QColor columnBackground = QColor(180, 180, 180); + + bool monitoring = (m_monitorLeft > 0.f || m_monitorRight > 0.f); + QPen pen; - - double thin = thinLineWidth(rect); - - pen.setColor(QColor(127, 127, 127, 127)); - pen.setWidthF(cellLightSize(rect).width() + thin); - pen.setCapStyle(Qt::RoundCap); - paint.setPen(pen); + if (isEnabled()) { + pen.setColor(Qt::black); + } else { + pen.setColor(Qt::darkGray); + } + pen.setWidthF(thin); + pen.setCapStyle(Qt::FlatCap); + pen.setJoinStyle(Qt::MiterJoin); for (int pan = -maxPan; pan <= maxPan; ++pan) { - paint.drawLine(cellCentre(rect, 0, pan), cellCentre(rect, maxLevel, pan)); + + paint.setPen(Qt::NoPen); + paint.setBrush(columnBackground); + + QRectF top = cellOutlineRect(rect, m_maxNotch/2 - 1, pan); + QRectF bottom = cellOutlineRect(rect, 0, pan); + paint.drawRoundedRect(QRectF(top.x(), + top.y(), + top.width(), + bottom.y() + bottom.height() - top.y()), + radius, radius); + + if (!asIfEditable && m_includeMute && m_notch == 0) { + // We will instead be drawing a single big X for mute, + // after this loop + continue; + } + + if (!monitoring && m_pan != pan) { + continue; + } + + int monitorNotch = 0; + if (monitoring) { + float rprop = float(pan - (-maxPan)) / float(maxPan * 2); + float lprop = float(maxPan - pan) / float(maxPan * 2); + float monitorLevel = + lprop * m_monitorLeft * m_monitorLeft + + rprop * m_monitorRight * m_monitorRight; + monitorNotch = audioLevelToNotch(monitorLevel); + } + + int firstCell = 0; + int lastCell = m_maxNotch / 2 - 1; + + for (int cell = firstCell; cell <= lastCell; ++cell) { + + QRectF clr = cellLightRect(rect, cell, pan); + + if (m_includeMute && m_pan == pan && m_notch == 0) { + // X for mute in the bottom cell + paint.setPen(pen); + paint.drawLine(clr.topLeft(), clr.bottomRight()); + paint.drawLine(clr.bottomLeft(), clr.topRight()); + break; + } + + const int none = 0, half = 1, full = 2; + + int fill = none; + + int outline = none; + if (m_pan == pan && m_notch > cell * 2 + 1) { + outline = full; + } else if (m_pan == pan && m_notch == cell * 2 + 1) { + outline = half; + } + + if (monitoring) { + if (monitorNotch > cell * 2 + 1) { + fill = full; + } else if (monitorNotch == cell * 2 + 1) { + fill = half; + } + } else { + if (isEnabled()) { + fill = outline; + } + } + + // If one of {fill, outline} is "full" and the other is + // "half", then we draw the "half" one first (because we + // need to erase half of it) + + if (fill == half || outline == half) { + if (fill == half) { + paint.setBrush(cellToColour(cell)); + } else { + paint.setBrush(Qt::NoBrush); + } + if (outline == half) { + paint.setPen(pen); + } else { + paint.setPen(Qt::NoPen); + } + + paint.drawRoundedRect(clr, radius, radius); + + paint.setBrush(columnBackground); + + if (cell == lastCell) { + QPen bgpen(pen); + bgpen.setColor(columnBackground); + paint.setPen(bgpen); + paint.drawRoundedRect(QRectF(clr.x(), + clr.y(), + clr.width(), + clr.height()/4), + radius, radius); + paint.drawRect(QRectF(clr.x(), + clr.y() + clr.height()/4, + clr.width(), + clr.height()/4)); + } else { + paint.setPen(Qt::NoPen); + QRectF cor = cellOutlineRect(rect, cell, pan); + paint.drawRect(QRectF(cor.x(), + cor.y() - 0.5, + cor.width(), + cor.height()/2)); + } + } + + if (outline == full || fill == full) { + + if (fill == full) { + paint.setBrush(cellToColour(cell)); + } else { + paint.setBrush(Qt::NoBrush); + } + if (outline == full) { + paint.setPen(pen); + } else { + paint.setPen(Qt::NoPen); + } + + paint.drawRoundedRect(clr, radius, radius); + } + } } - if (isEnabled()) { - pen.setColor(Qt::black); - } else { - pen.setColor(Qt::darkGray); - } - - if (!asIfEditable && m_includeMute && m_level == 0) { + if (!asIfEditable && m_includeMute && m_notch == 0) { + // The X for mute takes up the whole display when we're not + // being rendered in editable style + pen.setColor(Qt::black); pen.setWidthF(thin * 2); pen.setCapStyle(Qt::RoundCap); paint.setPen(pen); paint.drawLine(cellCentre(rect, 0, -maxPan), - cellCentre(rect, maxLevel, maxPan)); - paint.drawLine(cellCentre(rect, maxLevel, -maxPan), + cellCentre(rect, m_maxNotch/2 - 1, maxPan)); + paint.drawLine(cellCentre(rect, m_maxNotch/2 - 1, -maxPan), cellCentre(rect, 0, maxPan)); - return; - } - - pen.setWidthF(thin); - pen.setCapStyle(Qt::FlatCap); - paint.setPen(pen); - - for (int level = 0; level <= m_level; ++level) { - if (isEnabled()) { - paint.setBrush(level_to_colour(level)); - } - QRectF clr = cellLightRect(rect, level, m_pan); - if (m_includeMute && m_level == 0) { - paint.drawLine(clr.topLeft(), clr.bottomRight()); - paint.drawLine(clr.bottomLeft(), clr.topRight()); - } else { - paint.drawEllipse(clr); - } } } @@ -384,5 +577,17 @@ renderTo(this, rect(), m_editable); } +void +LevelPanWidget::enterEvent(QEvent *e) +{ + QWidget::enterEvent(e); + emit mouseEntered(); +} +void +LevelPanWidget::leaveEvent(QEvent *e) +{ + QWidget::enterEvent(e); + emit mouseLeft(); +}