diff widgets/LevelPanWidget.cpp @ 946:36cddc3de023 alignment_view

Merge from default branch
author Chris Cannam
date Mon, 20 Apr 2015 09:19:52 +0100
parents d6acb8e36605
children 125748a569fa
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/widgets/LevelPanWidget.cpp	Mon Apr 20 09:19:52 2015 +0100
@@ -0,0 +1,388 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "LevelPanWidget.h"
+
+#include <QPainter>
+#include <QMouseEvent>
+#include <QWheelEvent>
+
+#include "layer/ColourMapper.h"
+#include "base/AudioLevel.h"
+
+#include <iostream>
+#include <cmath>
+#include <cassert>
+
+using std::cerr;
+using std::endl;
+
+static const int maxLevel = 4; // min is 0, may be mute or not depending on m_includeMute
+static const int maxPan = 2; // range is -maxPan to maxPan
+
+LevelPanWidget::LevelPanWidget(QWidget *parent) :
+    QWidget(parent),
+    m_level(maxLevel),
+    m_pan(0),
+    m_editable(true),
+    m_includeMute(true)
+{
+}
+
+LevelPanWidget::~LevelPanWidget()
+{
+}
+
+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);
+}
+
+static int
+db_to_level(double db)
+{
+    // 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;
+}
+
+static double
+level_to_db(int level)
+{
+    // 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.;
+}
+
+void
+LevelPanWidget::setLevel(float flevel)
+{
+    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();
+    }
+}
+
+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)));
+    }
+}
+
+void
+LevelPanWidget::setPan(float pan)
+{
+    m_pan = int(round(pan * maxPan));
+    if (m_pan < -maxPan) m_pan = -maxPan;
+    if (m_pan > maxPan) m_pan = maxPan;
+    update();
+}
+
+bool
+LevelPanWidget::isEditable() const
+{
+    return m_editable;
+}
+
+bool
+LevelPanWidget::includesMute() const
+{
+    return m_includeMute;
+}
+
+void
+LevelPanWidget::setEditable(bool editable)
+{
+    m_editable = editable;
+    update();
+}
+
+void
+LevelPanWidget::setIncludeMute(bool include)
+{
+    m_includeMute = include;
+    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)
+{
+    mouseMoveEvent(e);
+}
+
+void
+LevelPanWidget::mouseMoveEvent(QMouseEvent *e)
+{
+    if (!m_editable) return;
+    
+    int level, pan;
+    toCell(rect(), e->pos(), level, pan);
+    if (level == m_level && pan == m_pan) {
+	return;
+    }
+    if (level != m_level) {
+	m_level = level;
+	emitLevelChanged();
+    }
+    if (pan != m_pan) {
+	m_pan = pan;
+	emitPanChanged();
+    }
+    update();
+}
+
+void
+LevelPanWidget::mouseReleaseEvent(QMouseEvent *e)
+{
+    mouseMoveEvent(e);
+}
+
+void
+LevelPanWidget::wheelEvent(QWheelEvent *e)
+{
+    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();
+	    }
+	}
+    } else {
+	if (e->delta() > 0) {
+	    if (m_level < maxLevel) {
+		++m_level;
+		emitLevelChanged();
+		update();
+	    }
+	} else {
+	    if (m_level > 0) {
+		--m_level;
+		emitLevelChanged();
+		update();
+	    }
+	}
+    }
+}
+
+void
+LevelPanWidget::toCell(QRectF rect, QPointF loc, int &level, int &pan) const
+{
+    double w = rect.width(), h = rect.height();
+
+    int npan = maxPan * 2 + 1;
+    int nlevel = maxLevel + 1;
+
+    double wcell = w / npan, hcell = h / nlevel;
+
+    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;
+}
+
+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;
+    return QSizeF(wcell, hcell);
+}
+
+QPointF
+LevelPanWidget::cellCentre(QRectF rect, int level, int pan) 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.);
+}
+
+QSizeF
+LevelPanWidget::cellLightSize(QRectF rect) const
+{
+    double extent = 3. / 4.;
+    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
+{
+    QSizeF cls = cellLightSize(rect);
+    QPointF cc = cellCentre(rect, level, pan);
+    return QRectF(cc.x() - cls.width() / 2., 
+		  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.));
+    return std::min(th, tw);
+}
+
+static QColor
+level_to_colour(int level)
+{
+    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);
+}
+
+void
+LevelPanWidget::renderTo(QPaintDevice *dev, QRectF rect, bool asIfEditable) const
+{
+    QPainter paint(dev);
+
+    paint.setRenderHint(QPainter::Antialiasing, true);
+
+    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);
+
+    for (int pan = -maxPan; pan <= maxPan; ++pan) {
+	paint.drawLine(cellCentre(rect, 0, pan), cellCentre(rect, maxLevel, pan));
+    }
+
+    if (isEnabled()) {
+	pen.setColor(Qt::black);
+    } else {
+	pen.setColor(Qt::darkGray);
+    }
+
+    if (!asIfEditable && m_includeMute && m_level == 0) {
+        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, 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);
+	}
+    }
+}
+
+void
+LevelPanWidget::paintEvent(QPaintEvent *)
+{
+    renderTo(this, rect(), m_editable);
+}
+
+
+