]> git.sesse.net Git - nageru/commitdiff
Make the compression meter nonlinear.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Sat, 22 Oct 2016 20:27:28 +0000 (22:27 +0200)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Tue, 25 Oct 2016 16:48:36 +0000 (18:48 +0200)
This necessitated splitting it out into its own class, which also
took some complexity out of VUMeter.

Makefile
compression_reduction_meter.cpp [new file with mode: 0644]
compression_reduction_meter.h [new file with mode: 0644]
mainwindow.cpp
ui_audio_expanded_view.ui
vu_common.cpp
vu_common.h
vumeter.cpp
vumeter.h

index 6054259cfcbc855caf18cbdde3d5524fb8cbbea0..4114a141c40d5f4edd412e6368429a532920a5fd 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -14,8 +14,8 @@ endif
 LDLIBS=$(shell pkg-config --libs $(PKG_MODULES)) -pthread -lva -lva-drm -lva-x11 -lX11 -lavformat -lavcodec -lavutil -lswscale -lavresample -lzita-resampler -lasound -ldl
 
 # Qt objects
-OBJS=glwidget.o main.o mainwindow.o vumeter.o lrameter.o vu_common.o correlation_meter.o aboutdialog.o input_mapping_dialog.o midi_mapping_dialog.o nonlinear_fader.o piecewise_interpolator.o
-OBJS += glwidget.moc.o mainwindow.moc.o vumeter.moc.o lrameter.moc.o correlation_meter.moc.o aboutdialog.moc.o ellipsis_label.moc.o input_mapping_dialog.moc.o midi_mapping_dialog.moc.o nonlinear_fader.moc.o clickable_label.moc.o
+OBJS=glwidget.o main.o mainwindow.o vumeter.o lrameter.o compression_reduction_meter.o vu_common.o correlation_meter.o aboutdialog.o input_mapping_dialog.o midi_mapping_dialog.o nonlinear_fader.o piecewise_interpolator.o
+OBJS += glwidget.moc.o mainwindow.moc.o vumeter.moc.o lrameter.moc.o compression_reduction_meter.moc.o correlation_meter.moc.o aboutdialog.moc.o ellipsis_label.moc.o input_mapping_dialog.moc.o midi_mapping_dialog.moc.o nonlinear_fader.moc.o clickable_label.moc.o
 OBJS += midi_mapper.o midi_mapping.pb.o
 
 # Mixer objects
diff --git a/compression_reduction_meter.cpp b/compression_reduction_meter.cpp
new file mode 100644 (file)
index 0000000..a59a71e
--- /dev/null
@@ -0,0 +1,100 @@
+#include "compression_reduction_meter.h"
+
+#include <QPainter>
+#include <QRect>
+#include "piecewise_interpolator.h"
+#include "vu_common.h"
+
+class QPaintEvent;
+class QResizeEvent;
+
+using namespace std;
+
+namespace {
+
+vector<PiecewiseInterpolator::ControlPoint> control_points = {
+       { 60.0f, 6.0f },
+       { 30.0f, 5.0f },
+       { 18.0f, 4.0f },
+       { 12.0f, 3.0f },
+       { 6.0f, 2.0f },
+       { 3.0f, 1.0f },
+       { 0.0f, 0.0f }
+};
+PiecewiseInterpolator interpolator(control_points);
+
+}  // namespace
+
+CompressionReductionMeter::CompressionReductionMeter(QWidget *parent)
+       : QWidget(parent)
+{
+}
+
+void CompressionReductionMeter::resizeEvent(QResizeEvent *event)
+{
+       recalculate_pixmaps();
+}
+
+void CompressionReductionMeter::paintEvent(QPaintEvent *event)
+{
+       QPainter painter(this);
+
+       float level_db;
+       {
+               unique_lock<mutex> lock(level_mutex);
+               level_db = this->level_db;
+       }
+
+       int on_pos = lrint(db_to_pos(level_db));
+
+       QRect on_rect(0, 0, width(), on_pos);
+       QRect off_rect(0, on_pos, width(), height());
+
+       painter.drawPixmap(on_rect, on_pixmap, on_rect);
+       painter.drawPixmap(off_rect, off_pixmap, off_rect);
+}
+
+void CompressionReductionMeter::recalculate_pixmaps()
+{
+       constexpr int y_offset = text_box_height / 2;
+       constexpr int text_margin = 5;
+       float margin = 0.5 * (width() - meter_width);
+
+       on_pixmap = QPixmap(width(), height());
+       QPainter on_painter(&on_pixmap);
+       on_painter.fillRect(0, 0, width(), height(), parentWidget()->palette().window());
+       draw_vu_meter(on_painter, width(), meter_height(), margin, 2.0, true, min_level, max_level, /*flip=*/true, y_offset);
+       draw_scale(&on_painter, 0.5 * width() + 0.5 * meter_width + text_margin);
+
+       off_pixmap = QPixmap(width(), height());
+       QPainter off_painter(&off_pixmap);
+       off_painter.fillRect(0, 0, width(), height(), parentWidget()->palette().window());
+       draw_vu_meter(off_painter, width(), meter_height(), margin, 2.0, false, min_level, max_level, /*flip=*/true, y_offset);
+       draw_scale(&off_painter, 0.5 * width() + 0.5 * meter_width + text_margin);
+}
+
+void CompressionReductionMeter::draw_scale(QPainter *painter, int x_pos)
+{
+       QFont font;
+       font.setPointSize(8);
+       painter->setPen(Qt::black);
+       painter->setFont(font);
+       for (size_t i = 0; i < control_points.size(); ++i) {
+               char buf[256];
+               snprintf(buf, 256, "%.0f", control_points[i].db_value);
+               double y = db_to_pos(control_points[i].db_value);
+               painter->drawText(QRect(x_pos, y - text_box_height / 2, text_box_width, text_box_height),
+                       Qt::AlignCenter | Qt::AlignVCenter, buf);
+       }
+}
+
+double CompressionReductionMeter::db_to_pos(double level_db) const
+{
+       float value = interpolator.db_to_fraction(level_db);
+       return height() - lufs_to_pos(value, meter_height(), min_level, max_level) - text_box_height / 2;
+}
+
+int CompressionReductionMeter::meter_height() const
+{
+       return height() - text_box_height;
+}
diff --git a/compression_reduction_meter.h b/compression_reduction_meter.h
new file mode 100644 (file)
index 0000000..5890c13
--- /dev/null
@@ -0,0 +1,54 @@
+#ifndef COMPRESSION_REDUCTION_METER_H
+#define COMPRESSION_REDUCTION_METER_H
+
+// A meter that goes downwards instead of upwards, and has a non-linear scale.
+
+#include <math.h>
+#include <QPixmap>
+#include <QString>
+#include <QWidget>
+#include <mutex>
+
+#include "piecewise_interpolator.h"
+
+class QObject;
+class QPaintEvent;
+class QResizeEvent;
+
+class CompressionReductionMeter : public QWidget
+{
+       Q_OBJECT
+
+public:
+       CompressionReductionMeter(QWidget *parent);
+
+       void set_reduction_db(float level_db) {
+               std::unique_lock<std::mutex> lock(level_mutex);
+               this->level_db = level_db;
+               QMetaObject::invokeMethod(this, "update", Qt::AutoConnection);
+       }
+
+private:
+       void resizeEvent(QResizeEvent *event) override;
+       void paintEvent(QPaintEvent *event) override;
+       void recalculate_pixmaps();
+       void draw_scale(QPainter *painter, int x_pos);
+       double db_to_pos(double db) const;
+       int meter_height() const;
+
+       std::mutex level_mutex;
+       float level_db = 0.0f;
+
+       static constexpr float min_level = 0.0f;  // Must match control_points (in the .cpp file).
+       static constexpr float max_level = 6.0f;  // Same.
+       static constexpr int meter_width = 20;
+
+       // Size of the text box. The meter will be shrunk to make room for the text box
+       // (half the height) on both sides.
+       static constexpr int text_box_width = 15;
+       static constexpr int text_box_height = 10;
+
+       QPixmap on_pixmap, off_pixmap;
+};
+
+#endif
index 0eda02d3a5c82bcdc8d8763971ccf225faa393aa..36ea55ee3f8883fbe4e97fa484570bc26e70e8ee 100644 (file)
@@ -529,13 +529,6 @@ void MainWindow::setup_audio_expanded_view()
                                global_audio_mixer->reset_peak(bus_index);
                                midi_mapper.refresh_lights();
                        });
-
-               // Set up the compression attenuation meter.
-               VUMeter *reduction_meter = ui_audio_expanded_view->reduction_meter;
-               reduction_meter->set_min_level(0.0f);
-               reduction_meter->set_max_level(10.0f);
-               reduction_meter->set_ref_level(0.0f);
-               reduction_meter->set_flip(true);
        }
 
        update_cutoff_labels(global_audio_mixer->get_locut_cutoff());
@@ -817,7 +810,7 @@ void MainWindow::audio_level_callback(float level_lufs, float peak_db, vector<Au
                                        level.current_level_dbfs[0], level.current_level_dbfs[1]);
                                view->peak_meter->set_peak(
                                        level.peak_level_dbfs[0], level.peak_level_dbfs[1]);
-                               view->reduction_meter->set_level(level.compressor_attenuation_db);
+                               view->reduction_meter->set_reduction_db(level.compressor_attenuation_db);
                                view->gainstaging_knob->blockSignals(true);
                                view->gainstaging_knob->setValue(lrintf(level.gain_staging_db * 10.0f));
                                view->gainstaging_knob->blockSignals(false);
index 46142e3552dcabfa4795d22fbaf19e163f4e82d8..3c560d4ed6b497f9225ca3031e0e1dc87239629a 100644 (file)
          </item>
          <item>
           <layout class="QVBoxLayout" name="reduction_layout" stretch="0,1">
+           <property name="spacing">
+            <number>0</number>
+           </property>
            <item>
             <widget class="QLabel" name="reduction_header">
              <property name="text">
            <item>
             <layout class="QHBoxLayout" name="reduction_meter_centerer">
              <item>
-              <widget class="VUMeter" name="reduction_meter" native="true">
+              <widget class="CompressionReductionMeter" name="reduction_meter" native="true">
                <property name="maximumSize">
                 <size>
-                 <width>20</width>
+                 <width>16777215</width>
                  <height>16777215</height>
                 </size>
                </property>
    <extends>QLabel</extends>
    <header>ellipsis_label.h</header>
   </customwidget>
+  <customwidget>
+   <class>CompressionReductionMeter</class>
+   <extends>QWidget</extends>
+   <header>compression_reduction_meter.h</header>
+   <container>1</container>
+  </customwidget>
  </customwidgets>
  <resources/>
  <connections/>
index b40502cad91b918276983a3c35a7b2422641de40..171f50d26a244059abcb1cab0cbd056565f6dbcc 100644 (file)
@@ -30,9 +30,9 @@ double lufs_to_pos(float level_lu, int height, float min_level, float max_level)
        return y;
 }
 
-void draw_vu_meter(QPainter &painter, int width, int height, int horizontal_margin, double segment_margin, bool is_on, float min_level, float max_level, bool flip)
+void draw_vu_meter(QPainter &painter, int width, int height, int horizontal_margin, double segment_margin, bool is_on, float min_level, float max_level, bool flip, int y_offset)
 {
-       painter.fillRect(horizontal_margin, 0, width - 2 * horizontal_margin, height, Qt::black);
+       painter.fillRect(horizontal_margin, y_offset, width - 2 * horizontal_margin, height, Qt::black);
 
        for (int y = 0; y < height; ++y) {
                // Find coverage of “on” rectangles in this pixel row.
@@ -68,6 +68,6 @@ void draw_vu_meter(QPainter &painter, int width, int height, int horizontal_marg
                int g = lrintf(255 * pow(on_g * coverage, 1.0 / 2.2));
                int b = lrintf(255 * pow(on_b * coverage, 1.0 / 2.2));
                int draw_y = flip ? (height - y - 1) : y;
-               painter.fillRect(horizontal_margin, draw_y, width - 2 * horizontal_margin, 1, QColor(r, g, b));
+               painter.fillRect(horizontal_margin, draw_y + y_offset, width - 2 * horizontal_margin, 1, QColor(r, g, b));
        }
 }
index c9a62a876c60d65f76cf8dba2f8f88da8e972467..602de8b8c59452a3cc743e0b41798d9e03e5090c 100644 (file)
@@ -5,6 +5,6 @@ class QPainter;
 
 double lufs_to_pos(float level_lu, int height, float min_level, float max_level);
 
-void draw_vu_meter(QPainter &painter, int width, int height, int horizontal_margin, double segment_margin, bool is_on, float min_level, float max_level, bool flip);
+void draw_vu_meter(QPainter &painter, int width, int height, int horizontal_margin, double segment_margin, bool is_on, float min_level, float max_level, bool flip, int y_offset = 0);
 
 #endif // !defined(_VU_COMMON_H)
index 65f081c65d7d8230554e84e0fd22151b8f8793ff..b697a834da3876533c06db888c16a42e8f209067 100644 (file)
@@ -40,19 +40,11 @@ void VUMeter::paintEvent(QPaintEvent *event)
                float level_lu = level_lufs[channel] - ref_level_lufs;
                int on_pos = lrint(lufs_to_pos(level_lu, height()));
 
-               if (flip) {
-                       QRect on_rect(left, 0, right - left, height() - on_pos);
-                       QRect off_rect(left, height() - on_pos, right - left, height());
-
-                       painter.drawPixmap(on_rect, on_pixmap, on_rect);
-                       painter.drawPixmap(off_rect, off_pixmap, off_rect);
-               } else {
-                       QRect off_rect(left, 0, right - left, on_pos);
-                       QRect on_rect(left, on_pos, right - left, height() - on_pos);
-
-                       painter.drawPixmap(off_rect, off_pixmap, off_rect);
-                       painter.drawPixmap(on_rect, on_pixmap, on_rect);
-               }
+               QRect off_rect(left, 0, right - left, on_pos);
+               QRect on_rect(left, on_pos, right - left, height() - on_pos);
+
+               painter.drawPixmap(off_rect, off_pixmap, off_rect);
+               painter.drawPixmap(on_rect, on_pixmap, on_rect);
 
                float peak_lu = peak_lufs[channel] - ref_level_lufs;
                if (peak_lu >= min_level && peak_lu <= max_level) {
@@ -67,13 +59,18 @@ void VUMeter::recalculate_pixmaps()
 {
        full_on_pixmap = QPixmap(width(), height());
        QPainter full_on_painter(&full_on_pixmap);
-       draw_vu_meter(full_on_painter, width(), height(), 0, 0.0, true, min_level, max_level, flip);
+       full_on_painter.fillRect(0, 0, width(), height(), parentWidget()->palette().window());
+       draw_vu_meter(full_on_painter, width(), height(), 0, 0.0, true, min_level, max_level, /*flip=*/false);
+
+       float margin = 0.5 * (width() - 20);
 
        on_pixmap = QPixmap(width(), height());
        QPainter on_painter(&on_pixmap);
-       draw_vu_meter(on_painter, width(), height(), 0, 2.0, true, min_level, max_level, flip);
+       on_painter.fillRect(0, 0, width(), height(), parentWidget()->palette().window());
+       draw_vu_meter(on_painter, width(), height(), margin, 2.0, true, min_level, max_level, /*flip=*/false);
 
        off_pixmap = QPixmap(width(), height());
        QPainter off_painter(&off_pixmap);
-       draw_vu_meter(off_painter, width(), height(), 0, 2.0, false, min_level, max_level, flip);
+       off_painter.fillRect(0, 0, width(), height(), parentWidget()->palette().window());
+       draw_vu_meter(off_painter, width(), height(), margin, 2.0, false, min_level, max_level, /*flip=*/false);
 }
index 4ac2c0fe2bfa13f8e20ff8ffa4c679ea11426bb1..7a9420058358f30047f8f2d3645eaa972ec51895 100644 (file)
--- a/vumeter.h
+++ b/vumeter.h
@@ -64,12 +64,6 @@ public:
                this->ref_level_lufs = ref_level_lufs;
        }
 
-       void set_flip(bool flip)
-       {
-               this->flip = flip;
-               recalculate_pixmaps();
-       }
-
 private:
        void resizeEvent(QResizeEvent *event) override;
        void paintEvent(QPaintEvent *event) override;
@@ -79,7 +73,6 @@ private:
        float level_lufs[2] { -HUGE_VALF, -HUGE_VALF };
        float peak_lufs[2] { -HUGE_VALF, -HUGE_VALF };
        float min_level = -18.0f, max_level = 9.0f, ref_level_lufs = -23.0f;
-       bool flip = false;
 
        QPixmap full_on_pixmap, on_pixmap, off_pixmap;
 };