From 0066ae51753e9ba7dbe54ab9c6f24fd861db41a2 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Sat, 22 Oct 2016 22:27:28 +0200 Subject: [PATCH] Make the compression meter nonlinear. This necessitated splitting it out into its own class, which also took some complexity out of VUMeter. --- Makefile | 4 +- compression_reduction_meter.cpp | 100 ++++++++++++++++++++++++++++++++ compression_reduction_meter.h | 54 +++++++++++++++++ mainwindow.cpp | 9 +-- ui_audio_expanded_view.ui | 13 ++++- vu_common.cpp | 6 +- vu_common.h | 2 +- vumeter.cpp | 29 +++++---- vumeter.h | 7 --- 9 files changed, 185 insertions(+), 39 deletions(-) create mode 100644 compression_reduction_meter.cpp create mode 100644 compression_reduction_meter.h diff --git a/Makefile b/Makefile index 6054259..4114a14 100644 --- 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 index 0000000..a59a71e --- /dev/null +++ b/compression_reduction_meter.cpp @@ -0,0 +1,100 @@ +#include "compression_reduction_meter.h" + +#include +#include +#include "piecewise_interpolator.h" +#include "vu_common.h" + +class QPaintEvent; +class QResizeEvent; + +using namespace std; + +namespace { + +vector 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 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 index 0000000..5890c13 --- /dev/null +++ b/compression_reduction_meter.h @@ -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 +#include +#include +#include +#include + +#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 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 diff --git a/mainwindow.cpp b/mainwindow.cpp index 0eda02d..36ea55e 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -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, vectorpeak_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); diff --git a/ui_audio_expanded_view.ui b/ui_audio_expanded_view.ui index 46142e3..3c560d4 100644 --- a/ui_audio_expanded_view.ui +++ b/ui_audio_expanded_view.ui @@ -323,6 +323,9 @@ + + 0 + @@ -336,10 +339,10 @@ - + - 20 + 16777215 16777215 @@ -506,6 +509,12 @@ QLabel
ellipsis_label.h
+ + CompressionReductionMeter + QWidget +
compression_reduction_meter.h
+ 1 +
diff --git a/vu_common.cpp b/vu_common.cpp index b40502c..171f50d 100644 --- a/vu_common.cpp +++ b/vu_common.cpp @@ -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)); } } diff --git a/vu_common.h b/vu_common.h index c9a62a8..602de8b 100644 --- a/vu_common.h +++ b/vu_common.h @@ -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) diff --git a/vumeter.cpp b/vumeter.cpp index 65f081c..b697a83 100644 --- a/vumeter.cpp +++ b/vumeter.cpp @@ -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); } diff --git a/vumeter.h b/vumeter.h index 4ac2c0f..7a94200 100644 --- 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; }; -- 2.39.2