]> git.sesse.net Git - nageru/commitdiff
Add the beginnings of a loudness range meter.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Sat, 31 Oct 2015 01:41:10 +0000 (02:41 +0100)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Sat, 31 Oct 2015 01:41:10 +0000 (02:41 +0100)
Makefile
lrameter.cpp [new file with mode: 0644]
lrameter.h [new file with mode: 0644]
mainwindow.cpp
mixer.cpp
mixer.h
ui_mainwindow.ui
vumeter.cpp

index b4a8a19ed771d212b85ed726b6892a62f97d4452..d8dd820ea978d5ce16e586716e46e42b0b775234 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -4,8 +4,8 @@ CXXFLAGS := -O2 -march=native -g -std=gnu++11 -Wall -Wno-deprecated-declarations
 LDFLAGS=$(shell pkg-config --libs $(PKG_MODULES)) -lEGL -lGL -pthread -lva -lva-drm -lva-x11 -lX11 -lavformat -lavcodec -lavutil -lzita-resampler -lebur128
 
 # Qt objects
-OBJS=glwidget.o main.o mainwindow.o vumeter.o
-OBJS += glwidget.moc.o mainwindow.moc.o vumeter.moc.o
+OBJS=glwidget.o main.o mainwindow.o vumeter.o lrameter.o
+OBJS += glwidget.moc.o mainwindow.moc.o vumeter.moc.o lrameter.moc.o
 
 # Mixer objects
 OBJS += h264encode.o mixer.o bmusb/bmusb.o pbo_frame_allocator.o context.o ref_counted_frame.o theme.o resampler.o httpd.o
diff --git a/lrameter.cpp b/lrameter.cpp
new file mode 100644 (file)
index 0000000..79a702d
--- /dev/null
@@ -0,0 +1,87 @@
+#include <QPainter>
+
+#include "lrameter.h"
+
+using namespace std;
+
+namespace {
+
+int lufs_to_pos(float level_lu, int height)
+{
+       const float min_level = 9.0f;    // y=0 is top of screen, so “min” is the loudest level.
+       const float max_level = -18.0f;
+       int y = lrintf(height * (level_lu - min_level) / (max_level - min_level));
+       y = std::max(y, 0);
+       y = std::min(y, height - 1);
+       return y;
+}
+
+}  // namespace
+
+LRAMeter::LRAMeter(QWidget *parent)
+       : QWidget(parent)
+{
+}
+
+void LRAMeter::paintEvent(QPaintEvent *event)
+{
+       QPainter painter(this);
+       const int margin = 5;
+
+       painter.fillRect(0, 0, width(), height(), parentWidget()->palette().window());
+       painter.fillRect(margin, 0, width() - 2 * margin, height(), Qt::black);
+
+       // TODO: QLinearGradient is not gamma-correct; we might want to correct for that.
+       QLinearGradient on(0, 0, 0, height());
+        on.setColorAt(0.0f, QColor(255, 0, 0));
+        on.setColorAt(0.5f, QColor(255, 255, 0));
+        on.setColorAt(1.0f, QColor(0, 255, 0));
+       QColor off(80, 80, 80);
+
+       float level_lufs;
+       {
+               unique_lock<mutex> lock(level_mutex);
+               level_lufs = this->level_lufs;
+       }
+
+       float level_lu = level_lufs + 23.0f;
+       int y = lufs_to_pos(level_lu, height());
+
+       // Draw bars colored up until the level, then gray from there.
+       for (int level = -18; level < 9; ++level) {
+               int min_y = lufs_to_pos(level + 1.0f, height()) + 1;
+               int max_y = lufs_to_pos(level, height()) - 1;
+
+               if (y > max_y) {
+                       painter.fillRect(margin, min_y, width() - 2 * margin, max_y - min_y, off);
+               } else if (y < min_y) {
+                       painter.fillRect(margin, min_y, width() - 2 * margin, max_y - min_y, on);
+               } else {
+                       painter.fillRect(margin, min_y, width() - 2 * margin, y - min_y, off);
+                       painter.fillRect(margin, y, width() - 2 * margin, max_y - y, on);
+               }
+       }
+
+       // Draw the target area (+/-1 LU is allowed EBU range).
+       int min_y = lufs_to_pos(1.0f, height());
+       int max_y = lufs_to_pos(-1.0f, height());
+
+       // FIXME: This outlining isn't so pretty.
+       {
+               QPen pen(Qt::black);
+               pen.setWidth(5);
+               painter.setPen(pen);
+               painter.drawRect(2, min_y, width() - 5, max_y - min_y);
+       }
+       {
+               QPen pen;
+               if (level_lu >= -1.0f && level_lu <= 1.0f) {
+                       pen.setColor(Qt::green);
+               } else {
+                       pen.setColor(Qt::red);
+               }
+               pen.setWidth(3);
+               painter.setPen(pen);
+               painter.drawRect(2, min_y, width() - 5, max_y - min_y);
+       }
+}
diff --git a/lrameter.h b/lrameter.h
new file mode 100644 (file)
index 0000000..ec84b83
--- /dev/null
@@ -0,0 +1,36 @@
+// TODO: This isn't really an LRA meter right now (it ignores the range).
+
+#ifndef LRAMETER_H
+#define LRAMETER_H
+
+#include <QWidget>
+#include <QLabel>
+#include <QPaintEvent>
+
+#include <mutex>
+
+class LRAMeter : public QWidget
+{
+       Q_OBJECT
+
+public:
+       LRAMeter(QWidget *parent);
+
+       void set_levels(float level_lufs, float range_low_lufs, float range_high_lufs) {
+               std::unique_lock<std::mutex> lock(level_mutex);
+               this->level_lufs = level_lufs;
+               this->range_low_lufs = range_low_lufs;
+               this->range_high_lufs = range_high_lufs;
+               update();
+       }
+
+private:
+       void paintEvent(QPaintEvent *event) override;
+
+       std::mutex level_mutex;
+       float level_lufs = -HUGE_VAL;
+       float range_low_lufs = -HUGE_VAL;
+       float range_high_lufs = -HUGE_VAL;
+};
+
+#endif
index 2888c83916de398ccd4ed7792fb9f96a10b0c606..ecdb131028f6a1ef691d190d4edc9f0ef2a1ddea 100644 (file)
@@ -94,8 +94,9 @@ void MainWindow::resizeEvent(QResizeEvent* event)
 
 void MainWindow::mixer_created(Mixer *mixer)
 {
-       mixer->set_audio_level_callback([this](float level_lufs, float peak_db){
+       mixer->set_audio_level_callback([this](float level_lufs, float peak_db, float global_level_lufs, float range_low_lufs, float range_high_lufs){
                ui->vu_meter->set_level(level_lufs);
+               ui->lra_meter->set_levels(global_level_lufs, range_low_lufs, range_high_lufs);
 
                char buf[256];
                snprintf(buf, sizeof(buf), "%.1f", peak_db);
index 45dee4404155ebfd1d94b294e5474f4ac69d8d9e..308f22c99937a1b830f1b74e75902fa579c20962 100644 (file)
--- a/mixer.cpp
+++ b/mixer.cpp
@@ -368,11 +368,20 @@ void Mixer::thread_func()
                }
 
                if (audio_level_callback != nullptr) {
-                       double loudness_s, peak_level_l, peak_level_r;
+                       double loudness_s, loudness_i, peak_level_l, peak_level_r;
+                       double lra;
                        ebur128_loudness_shortterm(r128_state, &loudness_s);
+                       ebur128_loudness_global(r128_state, &loudness_i);
+                       ebur128_loudness_range(r128_state, &lra);
                        ebur128_sample_peak(r128_state, 0, &peak_level_l);
                        ebur128_sample_peak(r128_state, 1, &peak_level_r);
-                       audio_level_callback(loudness_s, 20.0 * log10(max(peak_level_l, peak_level_r)));
+
+                       // FIXME: This is wrong. We need proper support from libebur128 for this.
+                       double loudness_range_low = loudness_i - 0.5 * lra;
+                       double loudness_range_high = loudness_i + 0.5 * lra;
+
+                       audio_level_callback(loudness_s, 20.0 * log10(max(peak_level_l, peak_level_r)),
+                                            loudness_i, loudness_range_low, loudness_range_high);
                }
 
                // If the first card is reporting a corrupted or otherwise dropped frame,
diff --git a/mixer.h b/mixer.h
index 7a0bfa3053fa6dc3bc4690ee0907fa533d0fbd9d..498a2d13afd1a8e02340a455949daeb6c7cb8f9d 100644 (file)
--- a/mixer.h
+++ b/mixer.h
@@ -81,7 +81,7 @@ public:
                output_channel[output].set_frame_ready_callback(callback);
        }
 
-       typedef std::function<void(float, float)> audio_level_callback_t;
+       typedef std::function<void(float, float, float, float, float)> audio_level_callback_t;
        void set_audio_level_callback(audio_level_callback_t callback)
        {
                audio_level_callback = callback;
index 9518f67a9bda3f52e5b71afcea91457114aff315..63418676d33732e68a86e92ca1764b4d24366824 100644 (file)
@@ -27,7 +27,7 @@
     <item row="0" column="0">
      <layout class="QVBoxLayout" name="vertical_layout" stretch="0,0,0">
       <item>
-       <layout class="QHBoxLayout" name="me_displays" stretch="0,0,0,0">
+       <layout class="QHBoxLayout" name="me_displays" stretch="0,0,0,0,0">
         <item>
          <layout class="QVBoxLayout" name="verticalLayout_3">
           <property name="leftMargin">
          </layout>
         </item>
         <item>
-         <layout class="QVBoxLayout" name="verticalLayout_4" stretch="1,0">
+         <layout class="QVBoxLayout" name="vu_meter_vertical_layout" stretch="1,0">
           <property name="leftMargin">
            <number>0</number>
           </property>
+          <property name="bottomMargin">
+           <number>4</number>
+          </property>
           <item>
            <layout class="QHBoxLayout" name="horizontalLayout">
             <property name="bottomMargin">
           </item>
          </layout>
         </item>
+        <item>
+         <layout class="QVBoxLayout" name="lra_vertical_layout" stretch="1,0">
+          <property name="spacing">
+           <number>3</number>
+          </property>
+          <property name="leftMargin">
+           <number>0</number>
+          </property>
+          <item>
+           <layout class="QHBoxLayout" name="horizontalLayout_2">
+            <item>
+             <widget class="LRAMeter" name="lra_meter" native="true">
+              <property name="sizePolicy">
+               <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+                <horstretch>0</horstretch>
+                <verstretch>0</verstretch>
+               </sizepolicy>
+              </property>
+              <property name="minimumSize">
+               <size>
+                <width>24</width>
+                <height>0</height>
+               </size>
+              </property>
+              <property name="palette">
+               <palette>
+                <active>
+                 <colorrole role="Base">
+                  <brush brushstyle="SolidPattern">
+                   <color alpha="255">
+                    <red>255</red>
+                    <green>255</green>
+                    <blue>255</blue>
+                   </color>
+                  </brush>
+                 </colorrole>
+                 <colorrole role="Window">
+                  <brush brushstyle="SolidPattern">
+                   <color alpha="255">
+                    <red>0</red>
+                    <green>239</green>
+                    <blue>219</blue>
+                   </color>
+                  </brush>
+                 </colorrole>
+                </active>
+                <inactive>
+                 <colorrole role="Base">
+                  <brush brushstyle="SolidPattern">
+                   <color alpha="255">
+                    <red>255</red>
+                    <green>255</green>
+                    <blue>255</blue>
+                   </color>
+                  </brush>
+                 </colorrole>
+                 <colorrole role="Window">
+                  <brush brushstyle="SolidPattern">
+                   <color alpha="255">
+                    <red>0</red>
+                    <green>239</green>
+                    <blue>219</blue>
+                   </color>
+                  </brush>
+                 </colorrole>
+                </inactive>
+                <disabled>
+                 <colorrole role="Base">
+                  <brush brushstyle="SolidPattern">
+                   <color alpha="255">
+                    <red>0</red>
+                    <green>239</green>
+                    <blue>219</blue>
+                   </color>
+                  </brush>
+                 </colorrole>
+                 <colorrole role="Window">
+                  <brush brushstyle="SolidPattern">
+                   <color alpha="255">
+                    <red>0</red>
+                    <green>239</green>
+                    <blue>219</blue>
+                   </color>
+                  </brush>
+                 </colorrole>
+                </disabled>
+               </palette>
+              </property>
+              <property name="autoFillBackground">
+               <bool>true</bool>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
+          <item>
+           <widget class="QPushButton" name="pushButton">
+            <property name="maximumSize">
+             <size>
+              <width>30</width>
+              <height>20</height>
+             </size>
+            </property>
+            <property name="text">
+             <string>RST</string>
+            </property>
+            <property name="checked">
+             <bool>false</bool>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </item>
        </layout>
       </item>
       <item>
    <header>vumeter.h</header>
    <container>1</container>
   </customwidget>
+  <customwidget>
+   <class>LRAMeter</class>
+   <extends>QWidget</extends>
+   <header>lrameter.h</header>
+   <container>1</container>
+  </customwidget>
  </customwidgets>
  <resources/>
  <connections/>
index 5b1c85251e20206918232d2c0b1a888a8f53c677..7254698830c4815a00942302b7612ccf85f0701e 100644 (file)
@@ -38,7 +38,7 @@ void VUMeter::paintEvent(QPaintEvent *event)
                if (level == -1 || level == 0) {
                        painter.fillRect(1, min_y, width() - 2, max_y - min_y, Qt::green);
                } else {
-                       painter.fillRect(1, min_y, width() - 2, max_y - min_y, Qt::darkGray);
+                       painter.fillRect(1, min_y, width() - 2, max_y - min_y, QColor(80, 80, 80));
                }
        }