]> git.sesse.net Git - nageru/commitdiff
Replace the R128 meters for each channel with a digital peak meter.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Sun, 28 Aug 2016 15:49:23 +0000 (17:49 +0200)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Wed, 19 Oct 2016 22:55:44 +0000 (00:55 +0200)
R128 is a superior way of measuring loudness, but for each bus, we care more
about getting the recording levels right (ie., that we don't peak) than loudness.
Since we do our mixing in float, unlike a typical digital mixer, it's not as
critical (at least assuming that the limiter is on and will save us later in
the mastering chain), but it's probably better to be consistent with convention
here.

Thus, use a simple digital meter for each bus, even though we of course still
use R128 for the mastering section.

audio_mixer.cpp
audio_mixer.h
lrameter.cpp
mainwindow.cpp
ui_audio_expanded_view.ui
ui_audio_miniview.ui
vu_common.cpp
vu_common.h
vumeter.cpp
vumeter.h

index 13eed33048fe5fcf659bf1ed762bb522ef4adfcc..6b7ffeb0d6d28fb1ffdbfa8308ab341970893b20 100644 (file)
@@ -462,10 +462,6 @@ vector<float> AudioMixer::get_output(double pts, unsigned num_samples, Resamplin
                        }
                }
 
                        }
                }
 
-               // TODO: We should measure post-fader.
-               deinterleave_samples(samples_bus, &left, &right);
-               measure_bus_levels(bus_index, left, right);
-
                float volume = from_db(fader_volume_db[bus_index]);
                if (bus_index == 0) {
                        for (unsigned i = 0; i < num_samples * 2; ++i) {
                float volume = from_db(fader_volume_db[bus_index]);
                if (bus_index == 0) {
                        for (unsigned i = 0; i < num_samples * 2; ++i) {
@@ -476,6 +472,9 @@ vector<float> AudioMixer::get_output(double pts, unsigned num_samples, Resamplin
                                samples_out[i] += samples_bus[i] * volume;
                        }
                }
                                samples_out[i] += samples_bus[i] * volume;
                        }
                }
+
+               deinterleave_samples(samples_bus, &left, &right);
+               measure_bus_levels(bus_index, left, right, volume);
        }
 
        {
        }
 
        {
@@ -542,12 +541,36 @@ vector<float> AudioMixer::get_output(double pts, unsigned num_samples, Resamplin
        return samples_out;
 }
 
        return samples_out;
 }
 
-void AudioMixer::measure_bus_levels(unsigned bus_index, const vector<float> &left, const vector<float> &right)
+void AudioMixer::measure_bus_levels(unsigned bus_index, const vector<float> &left, const vector<float> &right, float volume)
 {
 {
-       const float *ptrs[] = { left.data(), right.data() };
-       {
-               lock_guard<mutex> lock(audio_measure_mutex);
-               bus_r128[bus_index]->process(left.size(), const_cast<float **>(ptrs));
+       assert(left.size() == right.size());
+       const float peak_levels[2] = {
+               find_peak(left.data(), left.size()) * volume,
+               find_peak(right.data(), right.size()) * volume
+       };
+       for (unsigned channel = 0; channel < 2; ++channel) {
+               // Compute the current value, including hold and falloff.
+               // The constants are borrowed from zita-mu1 by Fons Adriaensen.
+               static constexpr float hold_sec = 0.5f;
+               static constexpr float falloff_db_sec = 15.0f;  // dB/sec falloff after hold.
+               float current_peak;
+               PeakHistory &history = peak_history[bus_index][channel];
+               if (history.age_seconds < hold_sec) {
+                       current_peak = history.last_peak;
+               } else {
+                       current_peak = history.last_peak * from_db(-falloff_db_sec * (history.age_seconds - hold_sec));
+               }
+
+               // See if we have a new peak to replace the old (possibly falling) one.
+               if (peak_levels[channel] > current_peak) {
+                       history.last_peak = peak_levels[channel];
+                       history.age_seconds = 0.0f;  // Not 100% correct, but more than good enough given our frame sizes.
+                       current_peak = peak_levels[channel];
+               } else {
+                       history.age_seconds += float(left.size()) / OUTPUT_FREQUENCY;
+               }
+               history.current_level = peak_levels[channel];
+               history.current_peak = current_peak;
        }
 }
 
        }
 }
 
@@ -611,8 +634,11 @@ void AudioMixer::send_audio_level_callback()
        bus_levels.resize(input_mapping.buses.size());
        {
                lock_guard<mutex> lock(compressor_mutex);
        bus_levels.resize(input_mapping.buses.size());
        {
                lock_guard<mutex> lock(compressor_mutex);
-               for (unsigned bus_index = 0; bus_index < bus_r128.size(); ++bus_index) {
-                       bus_levels[bus_index].loudness_lufs = bus_r128[bus_index]->loudness_S();
+               for (unsigned bus_index = 0; bus_index < bus_levels.size(); ++bus_index) {
+                       bus_levels[bus_index].current_level_dbfs[0] = to_db(peak_history[bus_index][0].current_level);
+                       bus_levels[bus_index].current_level_dbfs[1] = to_db(peak_history[bus_index][1].current_level);
+                       bus_levels[bus_index].peak_level_dbfs[0] = to_db(peak_history[bus_index][0].current_peak);
+                       bus_levels[bus_index].peak_level_dbfs[1] = to_db(peak_history[bus_index][1].current_peak);
                        bus_levels[bus_index].gain_staging_db = gain_staging_db[bus_index];
                        if (compressor_enabled[bus_index]) {
                                bus_levels[bus_index].compressor_attenuation_db = -to_db(compressor[bus_index]->get_attenuation());
                        bus_levels[bus_index].gain_staging_db = gain_staging_db[bus_index];
                        if (compressor_enabled[bus_index]) {
                                bus_levels[bus_index].compressor_attenuation_db = -to_db(compressor[bus_index]->get_attenuation());
@@ -693,17 +719,6 @@ void AudioMixer::set_input_mapping(const InputMapping &new_input_mapping)
                }
        }
 
                }
        }
 
-       {
-               lock_guard<mutex> lock(audio_measure_mutex);
-               bus_r128.resize(new_input_mapping.buses.size());
-               for (unsigned bus_index = 0; bus_index < bus_r128.size(); ++bus_index) {
-                       if (bus_r128[bus_index] == nullptr) {
-                               bus_r128[bus_index].reset(new Ebu_r128_proc);
-                       }
-                       bus_r128[bus_index]->init(2, OUTPUT_FREQUENCY);
-               }
-       }
-
        input_mapping = new_input_mapping;
 }
 
        input_mapping = new_input_mapping;
 }
 
index 5a3f2af1d6532ac09ded74c31937756415d91c5c..6e7719f23313238b5bebaab70fea90e47af1c8b0 100644 (file)
@@ -202,7 +202,8 @@ public:
        }
 
        struct BusLevel {
        }
 
        struct BusLevel {
-               float loudness_lufs;
+               float current_level_dbfs[2];  // Digital peak of last frame, left and right.
+               float peak_level_dbfs[2];  // Digital peak with hold, left and right.
                float gain_staging_db;
                float compressor_attenuation_db;  // A positive number; 0.0 for no attenuation.
        };
                float gain_staging_db;
                float compressor_attenuation_db;  // A positive number; 0.0 for no attenuation.
        };
@@ -236,7 +237,7 @@ private:
        void reset_alsa_mutex_held(DeviceSpec device_spec);
        std::map<DeviceSpec, DeviceInfo> get_devices_mutex_held() const;
        void update_meters(const std::vector<float> &samples);
        void reset_alsa_mutex_held(DeviceSpec device_spec);
        std::map<DeviceSpec, DeviceInfo> get_devices_mutex_held() const;
        void update_meters(const std::vector<float> &samples);
-       void measure_bus_levels(unsigned bus_index, const std::vector<float> &left, const std::vector<float> &right);
+       void measure_bus_levels(unsigned bus_index, const std::vector<float> &left, const std::vector<float> &right, float volume);
        void send_audio_level_callback();
 
        unsigned num_cards;
        void send_audio_level_callback();
 
        unsigned num_cards;
@@ -269,6 +270,14 @@ private:
        std::atomic<float> compressor_threshold_dbfs[MAX_BUSES];
        std::atomic<bool> compressor_enabled[MAX_BUSES];
 
        std::atomic<float> compressor_threshold_dbfs[MAX_BUSES];
        std::atomic<bool> compressor_enabled[MAX_BUSES];
 
+       struct PeakHistory {
+               float current_level = 0.0f;  // Peak of the last frame (not in dB).
+               float current_peak = 0.0f;  // Current peak of the peak meter (not in dB).
+               float last_peak = 0.0f;
+               float age_seconds = 0.0f;   // Time since "last_peak" was set.
+       };
+       PeakHistory peak_history[MAX_BUSES][2];  // Separate for each channel.
+
        double final_makeup_gain = 1.0;  // Under compressor_mutex. Read/write by the user. Note: Not in dB, we want the numeric precision so that we can change it slowly.
        bool final_makeup_gain_auto = true;  // Under compressor_mutex.
 
        double final_makeup_gain = 1.0;  // Under compressor_mutex. Read/write by the user. Note: Not in dB, we want the numeric precision so that we can change it slowly.
        bool final_makeup_gain_auto = true;  // Under compressor_mutex.
 
@@ -281,12 +290,6 @@ private:
        CorrelationMeasurer correlation;  // Under audio_measure_mutex.
        Resampler peak_resampler;  // Under audio_measure_mutex.
        std::atomic<float> peak{0.0f};
        CorrelationMeasurer correlation;  // Under audio_measure_mutex.
        Resampler peak_resampler;  // Under audio_measure_mutex.
        std::atomic<float> peak{0.0f};
-
-       // Under audio_measure_mutex. Note that Ebu_r128_proc has a broken
-       // copy constructor (it uses the default, but holds arrays),
-       // so we can't just use raw Ebu_r128_proc elements, but need to use
-       // unique_ptrs.
-       std::vector<std::unique_ptr<Ebu_r128_proc>> bus_r128;
 };
 
 #endif  // !defined(_AUDIO_MIXER_H)
 };
 
 #endif  // !defined(_AUDIO_MIXER_H)
index d7736e3a27cbd68c77397a4beeb3fd70925789ac..8121ee4b1dfdd3b3fced4565e23b78bce34c11d5 100644 (file)
@@ -99,10 +99,10 @@ void LRAMeter::recalculate_pixmaps()
        on_pixmap = QPixmap(width(), height());
        QPainter on_painter(&on_pixmap);
        on_painter.fillRect(0, 0, width(), height(), parentWidget()->palette().window());
        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(), height(), margin, true, min_level, max_level, /*flip=*/false);
+       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);
        off_painter.fillRect(0, 0, width(), height(), parentWidget()->palette().window());
 
        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(), height(), margin, false, min_level, max_level, /*flip=*/false);
+       draw_vu_meter(off_painter, width(), height(), margin, 2.0, false, min_level, max_level, /*flip=*/false);
 }
 }
index 09dcaba1cc5e53e1f26ac49fa3d03ea3e14fa7ad..923c4f9f199c45eabfbf05a50c70772e6013db15 100644 (file)
@@ -318,6 +318,13 @@ void MainWindow::setup_audio_miniview()
                ui_audio_miniview->bus_desc_label->setFullText(
                        QString::fromStdString(mapping.buses[bus_index].name));
                audio_miniviews[bus_index] = ui_audio_miniview;
                ui_audio_miniview->bus_desc_label->setFullText(
                        QString::fromStdString(mapping.buses[bus_index].name));
                audio_miniviews[bus_index] = ui_audio_miniview;
+
+               // Set up the peak meter.
+               VUMeter *peak_meter = ui_audio_miniview->peak_meter;
+               peak_meter->set_min_level(-30.0f);
+               peak_meter->set_max_level(0.0f);
+               peak_meter->set_ref_level(0.0f);
+
                // TODO: Set the fader position.
                ui->faders->addWidget(channel);
 
                // TODO: Set the fader position.
                ui->faders->addWidget(channel);
 
@@ -369,6 +376,12 @@ void MainWindow::setup_audio_expanded_view()
 
                slave_fader(audio_miniviews[bus_index]->fader, ui_audio_expanded_view->fader);
 
 
                slave_fader(audio_miniviews[bus_index]->fader, ui_audio_expanded_view->fader);
 
+               // Set up the peak meter.
+               VUMeter *peak_meter = ui_audio_expanded_view->peak_meter;
+               peak_meter->set_min_level(-30.0f);
+               peak_meter->set_max_level(0.0f);
+               peak_meter->set_ref_level(0.0f);
+
                // Set up the compression attenuation meter.
                VUMeter *reduction_meter = ui_audio_expanded_view->reduction_meter;
                reduction_meter->set_min_level(0.0f);
                // Set up the compression attenuation meter.
                VUMeter *reduction_meter = ui_audio_expanded_view->reduction_meter;
                reduction_meter->set_min_level(0.0f);
@@ -544,7 +557,12 @@ void MainWindow::audio_level_callback(float level_lufs, float peak_db, vector<Au
        steady_clock::time_point now = steady_clock::now();
 
        // The meters are somewhat inefficient to update. Only update them
        steady_clock::time_point now = steady_clock::now();
 
        // The meters are somewhat inefficient to update. Only update them
-       // every 100 ms or so (we get updates every 5–20 ms).
+       // every 100 ms or so (we get updates every 5–20 ms). Note that this
+       // means that the digital peak meters are ever so slightly too low
+       // (each update won't be a faithful representation of the highest peak
+       // since the previous update, since there are frames we won't draw),
+       // but the _peak_ of the peak meters will be correct (it's tracked in
+       // AudioMixer, not here), and that's much more important.
        double last_update_age = duration<double>(now - last_audio_level_callback).count();
        if (last_update_age < 0.100) {
                return;
        double last_update_age = duration<double>(now - last_audio_level_callback).count();
        if (last_update_age < 0.100) {
                return;
@@ -556,10 +574,17 @@ void MainWindow::audio_level_callback(float level_lufs, float peak_db, vector<Au
                for (unsigned bus_index = 0; bus_index < bus_levels.size(); ++bus_index) {
                        if (bus_index < audio_miniviews.size()) {
                                const AudioMixer::BusLevel &level = bus_levels[bus_index];
                for (unsigned bus_index = 0; bus_index < bus_levels.size(); ++bus_index) {
                        if (bus_index < audio_miniviews.size()) {
                                const AudioMixer::BusLevel &level = bus_levels[bus_index];
-                               audio_miniviews[bus_index]->vu_meter_meter->set_level(level.loudness_lufs);
+                               Ui::AudioMiniView *miniview = audio_miniviews[bus_index];
+                               miniview->peak_meter->set_level(
+                                       level.current_level_dbfs[0], level.current_level_dbfs[1]);
+                               miniview->peak_meter->set_peak(
+                                       level.peak_level_dbfs[0], level.peak_level_dbfs[1]);
 
                                Ui::AudioExpandedView *view = audio_expanded_views[bus_index];
 
                                Ui::AudioExpandedView *view = audio_expanded_views[bus_index];
-                               view->vu_meter_meter->set_level(level.loudness_lufs);
+                               view->peak_meter->set_level(
+                                       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->gainstaging_knob->blockSignals(true);
                                view->gainstaging_knob->setValue(lrintf(level.gain_staging_db * 10.0f));
                                view->reduction_meter->set_level(level.compressor_attenuation_db);
                                view->gainstaging_knob->blockSignals(true);
                                view->gainstaging_knob->setValue(lrintf(level.gain_staging_db * 10.0f));
index 7d9492e605d6730f5504d320bae796565f83be6e..d48721face3bcc5e10c3b97cb3e8c0b3ce9f86a9 100644 (file)
        <item>
         <layout class="QHBoxLayout" name="vu_centerer">
          <item>
        <item>
         <layout class="QHBoxLayout" name="vu_centerer">
          <item>
-          <widget class="VUMeter" name="vu_meter_meter" native="true">
+          <widget class="VUMeter" name="peak_meter" native="true">
            <property name="maximumSize">
             <size>
              <width>20</width>
            <property name="maximumSize">
             <size>
              <width>20</width>
index a261e889214b2fccbaa6d9f324060833fc06459c..96c0bd8ebd4469291f91bc159d09847ddcdfc8f1 100644 (file)
                  <number>0</number>
                 </property>
                 <item>
                  <number>0</number>
                 </property>
                 <item>
-                 <widget class="VUMeter" name="vu_meter_meter" native="true">
+                 <widget class="VUMeter" name="peak_meter" native="true">
                   <property name="sizePolicy">
                    <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
                     <horstretch>0</horstretch>
                   <property name="sizePolicy">
                    <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
                     <horstretch>0</horstretch>
index 87254dae16c764a9381ecadf168dee586df6733a..d4abab20326be1da7f3abf2615b0689336f723c0 100644 (file)
@@ -32,16 +32,16 @@ double lufs_to_pos(float level_lu, int height, float min_level, float max_level)
        return y;
 }
 
        return y;
 }
 
-void draw_vu_meter(QPainter &painter, int width, int height, int 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)
 {
 {
-       painter.fillRect(margin, 0, width - 2 * margin, height, Qt::black);
+       painter.fillRect(horizontal_margin, 0, width - 2 * horizontal_margin, height, Qt::black);
 
        for (int y = 0; y < height; ++y) {
                // Find coverage of “on” rectangles in this pixel row.
                double coverage = 0.0;
                for (int level = floor(min_level); level <= ceil(max_level); ++level) {
 
        for (int y = 0; y < height; ++y) {
                // Find coverage of “on” rectangles in this pixel row.
                double coverage = 0.0;
                for (int level = floor(min_level); level <= ceil(max_level); ++level) {
-                       double min_y = lufs_to_pos(level + 1.0, height, min_level, max_level) + 1.0;
-                       double max_y = lufs_to_pos(level, height, min_level, max_level) - 1.0;
+                       double min_y = lufs_to_pos(level + 1.0, height, min_level, max_level) + segment_margin * 0.5;
+                       double max_y = lufs_to_pos(level, height, min_level, max_level) - segment_margin * 0.5;
                        min_y = std::max<double>(min_y, y);
                        min_y = std::min<double>(min_y, y + 1);
                        max_y = std::max<double>(max_y, y);
                        min_y = std::max<double>(min_y, y);
                        min_y = std::min<double>(min_y, y + 1);
                        max_y = std::max<double>(max_y, y);
@@ -70,6 +70,6 @@ void draw_vu_meter(QPainter &painter, int width, int height, int margin, bool is
                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;
                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(margin, draw_y, width - 2 * margin, 1, QColor(r, g, b));
+               painter.fillRect(horizontal_margin, draw_y, width - 2 * horizontal_margin, 1, QColor(r, g, b));
        }
 }
        }
 }
index c5b1ac0d37cc51f0d48a34c8cd0f869c38d60ae0..9c7098e619edc4aa9582735cec3fc186363648c1 100644 (file)
@@ -5,6 +5,6 @@
 
 double lufs_to_pos(float level_lu, int height, float min_level, float max_level);
 
 
 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 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);
 
 #endif // !defined(_VU_COMMON_H)
 
 #endif // !defined(_VU_COMMON_H)
index 50f0f143f7e575d03f8e0810130cad28da010424..2bf36390206a59a18622d59f205c19769fb81a28 100644 (file)
@@ -19,11 +19,13 @@ void VUMeter::paintEvent(QPaintEvent *event)
 {
        QPainter painter(this);
 
 {
        QPainter painter(this);
 
-       float level_lufs[2];
+       float level_lufs[2], peak_lufs[2];
        {
                unique_lock<mutex> lock(level_mutex);
                level_lufs[0] = this->level_lufs[0];
                level_lufs[1] = this->level_lufs[1];
        {
                unique_lock<mutex> lock(level_mutex);
                level_lufs[0] = this->level_lufs[0];
                level_lufs[1] = this->level_lufs[1];
+               peak_lufs[0] = this->peak_lufs[0];
+               peak_lufs[1] = this->peak_lufs[1];
        }
 
        int mid = width() / 2;
        }
 
        int mid = width() / 2;
@@ -35,28 +37,39 @@ void VUMeter::paintEvent(QPaintEvent *event)
                int on_pos = lrint(lufs_to_pos(level_lu, height()));
 
                if (flip) {
                int on_pos = lrint(lufs_to_pos(level_lu, height()));
 
                if (flip) {
-                       QRect on_rect(left, 0, right, height() - on_pos);
-                       QRect off_rect(left, height() - on_pos, right, height());
+                       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 {
 
                        painter.drawPixmap(on_rect, on_pixmap, on_rect);
                        painter.drawPixmap(off_rect, off_pixmap, off_rect);
                } else {
-                       QRect off_rect(left, 0, right, on_pos);
-                       QRect on_rect(left, on_pos, right, height() - on_pos);
+                       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);
                }
 
                        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) {
+                       int peak_pos = lrint(lufs_to_pos(peak_lu, height()));
+                       QRect peak_rect(left, peak_pos - 1, right, 2);
+                       painter.drawPixmap(peak_rect, full_on_pixmap, peak_rect);
+               }
        }
 }
 
 void VUMeter::recalculate_pixmaps()
 {
        }
 }
 
 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);
+
        on_pixmap = QPixmap(width(), height());
        QPainter on_painter(&on_pixmap);
        on_pixmap = QPixmap(width(), height());
        QPainter on_painter(&on_pixmap);
-       draw_vu_meter(on_painter, width(), height(), 0, true, min_level, max_level, flip);
+       draw_vu_meter(on_painter, width(), height(), 0, 2.0, true, min_level, max_level, flip);
 
        off_pixmap = QPixmap(width(), height());
        QPainter off_painter(&off_pixmap);
 
        off_pixmap = QPixmap(width(), height());
        QPainter off_painter(&off_pixmap);
-       draw_vu_meter(off_painter, width(), height(), 0, false, min_level, max_level, flip);
+       draw_vu_meter(off_painter, width(), height(), 0, 2.0, false, min_level, max_level, flip);
 }
 }
index 1fecf7e9cfcd34a0af9e05bad31bccf2df59a30b..9b17f43d2c7c2c81fd9d9c4487303045499b5189 100644 (file)
--- a/vumeter.h
+++ b/vumeter.h
@@ -27,6 +27,17 @@ public:
                QMetaObject::invokeMethod(this, "update", Qt::AutoConnection);
        }
 
                QMetaObject::invokeMethod(this, "update", Qt::AutoConnection);
        }
 
+       void set_peak(float peak_lufs) {
+               set_peak(peak_lufs, peak_lufs);
+       }
+
+       void set_peak(float peak_lufs_left, float peak_lufs_right) {
+               std::unique_lock<std::mutex> lock(level_mutex);
+               this->peak_lufs[0] = peak_lufs_left;
+               this->peak_lufs[1] = peak_lufs_right;
+               QMetaObject::invokeMethod(this, "update", Qt::AutoConnection);
+       }
+
        double lufs_to_pos(float level_lu, int height)
        {
                return ::lufs_to_pos(level_lu, height, min_level, max_level);
        double lufs_to_pos(float level_lu, int height)
        {
                return ::lufs_to_pos(level_lu, height, min_level, max_level);
@@ -62,10 +73,11 @@ private:
 
        std::mutex level_mutex;
        float level_lufs[2] { -HUGE_VALF, -HUGE_VALF };
 
        std::mutex level_mutex;
        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;
 
        float min_level = -18.0f, max_level = 9.0f, ref_level_lufs = -23.0f;
        bool flip = false;
 
-       QPixmap on_pixmap, off_pixmap;
+       QPixmap full_on_pixmap, on_pixmap, off_pixmap;
 };
 
 #endif
 };
 
 #endif