summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2025-04-17 02:09:09 +0200
committerCarl Hetherington <cth@carlh.net>2025-04-28 02:31:15 +0200
commit011ebbfad404991fb0619939fd0fff262588cb02 (patch)
tree49c47086494ba1fb21a432ab9ab2e70e3f8885ab
parentbd08f1845d8ed5e171f4725a3c9d0a23a9c19f77 (diff)
Add non-OpenGL "simple" audio meters.
-rw-r--r--src/lib/audio_level_calculator.cc98
-rw-r--r--src/lib/audio_level_calculator.h68
-rw-r--r--src/lib/audio_levels.h45
-rw-r--r--src/lib/butler.cc29
-rw-r--r--src/lib/butler.h9
-rw-r--r--src/lib/wscript1
-rw-r--r--src/tools/dcpomatic_player.cc8
-rw-r--r--src/wx/film_viewer.cc28
-rw-r--r--src/wx/film_viewer.h4
-rw-r--r--src/wx/meter_util.cc88
-rw-r--r--src/wx/meter_util.h41
-rw-r--r--src/wx/meters_dialog.cc44
-rw-r--r--src/wx/meters_dialog.h40
-rw-r--r--src/wx/simple_meters.cc136
-rw-r--r--src/wx/simple_meters.h65
-rw-r--r--src/wx/simple_video_view.cc3
-rw-r--r--src/wx/simple_video_view.h7
-rw-r--r--src/wx/text_size_cache.cc47
-rw-r--r--src/wx/text_size_cache.h40
-rw-r--r--src/wx/wscript4
-rw-r--r--test/audio_level_calculator_test.cc46
-rw-r--r--test/wscript1
22 files changed, 850 insertions, 2 deletions
diff --git a/src/lib/audio_level_calculator.cc b/src/lib/audio_level_calculator.cc
new file mode 100644
index 000000000..f347ffba6
--- /dev/null
+++ b/src/lib/audio_level_calculator.cc
@@ -0,0 +1,98 @@
+/*
+ Copyright (C) 2025 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "audio_buffers.h"
+#include "audio_level_calculator.h"
+
+
+using std::make_pair;
+using std::shared_ptr;
+using boost::optional;
+
+
+AudioLevelCalculator::AudioLevelCalculator(int calculation_frame_rate, int falloff)
+ : _calculation_frame_rate(calculation_frame_rate)
+ , _falloff_linear(pow(10, -falloff / (calculation_frame_rate * 20.0f)))
+{
+
+}
+
+
+void
+AudioLevelCalculator::put(shared_ptr<const AudioBuffers> audio, dcpomatic::DCPTime time, int frame_rate)
+{
+ int const frames_per_measurement = frame_rate / _calculation_frame_rate;
+
+ boost::mutex::scoped_lock lm(_current_mutex);
+
+ auto const channels = audio->channels();
+
+ if (static_cast<int>(_current_peaks.size()) != channels) {
+ _current_peaks.resize(channels);
+ }
+
+ auto const data = audio->data();
+ auto const frames = audio->frames();
+ for (auto frame = 0; frame < frames; ++frame) {
+ for (auto channel = 0; channel < channels; ++channel) {
+ _current_peaks[channel] = std::max(std::abs(data[channel][frame]), _current_peaks[channel]);
+ }
+
+ ++_current_frames;
+ if (_current_frames == frames_per_measurement) {
+ {
+ boost::mutex::scoped_lock lm(_store_mutex);
+ _levels.emplace_back(time + dcpomatic::DCPTime::from_frames(frame + 1, frame_rate), _current_peaks);
+ }
+ for (auto channel = 0; channel < channels; ++channel) {
+ _current_peaks[channel] *= _falloff_linear;
+ }
+ _current_frames = 0;
+ }
+ }
+
+}
+
+
+void
+AudioLevelCalculator::clear()
+{
+ boost::mutex::scoped_lock slm(_store_mutex);
+ _levels.clear();
+
+ boost::mutex::scoped_lock clm(_current_mutex);
+ std::fill(_current_peaks.begin(), _current_peaks.end(), 0.0f);
+ _current_frames = 0;
+}
+
+
+boost::optional<AudioLevels>
+AudioLevelCalculator::get()
+{
+ boost::mutex::scoped_lock lm(_store_mutex);
+ if (_levels.empty()) {
+ return {};
+ }
+ auto const m = _levels.front();
+ _levels.pop_front();
+ return m;
+}
+
diff --git a/src/lib/audio_level_calculator.h b/src/lib/audio_level_calculator.h
new file mode 100644
index 000000000..c7488e13f
--- /dev/null
+++ b/src/lib/audio_level_calculator.h
@@ -0,0 +1,68 @@
+/*
+ Copyright (C) 2025 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#ifndef DCPOMATIC_LEVEL_CALCULATOR_H
+#define DCPOMATIC_LEVEL_CALCULATOR_H
+
+
+#include "audio_levels.h"
+#include "dcpomatic_time.h"
+#include <boost/thread/mutex.hpp>
+#include <memory>
+
+
+class AudioBuffers;
+
+
+class AudioLevelCalculator
+{
+public:
+ /** @param calculation_frame_rate number of peak calculations to make per second
+ * @param falloff meter falloff in db/sec
+ */
+ AudioLevelCalculator(int calculation_frame_rate = 30, int falloff = 15);
+
+ AudioLevelCalculator(AudioLevelCalculator const&) = delete;
+ AudioLevelCalculator& operator=(AudioLevelCalculator const&) = delete;
+
+ void put(std::shared_ptr<const AudioBuffers> audio, dcpomatic::DCPTime time, int frame_rate);
+ void clear();
+ boost::optional<AudioLevels> get();
+
+ dcpomatic::DCPTime time_between_measurements() const {
+ return dcpomatic::DCPTime::from_seconds(1) / _calculation_frame_rate;
+ };
+
+private:
+ int const _calculation_frame_rate;
+ float const _falloff_linear;
+
+ boost::mutex _current_mutex;
+ std::vector<float> _current_peaks;
+ int _current_frames = 0;
+
+ mutable boost::mutex _store_mutex;
+ std::list<AudioLevels> _levels;
+};
+
+
+#endif
+
diff --git a/src/lib/audio_levels.h b/src/lib/audio_levels.h
new file mode 100644
index 000000000..4ab02d19d
--- /dev/null
+++ b/src/lib/audio_levels.h
@@ -0,0 +1,45 @@
+/*
+ Copyright (C) 2025 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#ifndef DCPOMATIC_AUDIO_LEVELS_H
+#define DCPOMATIC_AUDIO_LEVELS_H
+
+
+#include "dcpomatic_time.h"
+#include <vector>
+
+
+struct AudioLevels
+{
+ AudioLevels(dcpomatic::DCPTime t, std::vector<float> p)
+ : time(t)
+ , peaks(std::move(p))
+ {
+
+ }
+
+ dcpomatic::DCPTime time;
+ std::vector<float> peaks;
+};
+
+
+#endif
+
diff --git a/src/lib/butler.cc b/src/lib/butler.cc
index 14f342045..61772b115 100644
--- a/src/lib/butler.cc
+++ b/src/lib/butler.cc
@@ -19,6 +19,7 @@
*/
+#include "audio_level_calculator.h"
#include "butler.h"
#include "compose.hpp"
#include "cross.h"
@@ -33,10 +34,12 @@
using std::cout;
using std::function;
using std::make_pair;
+using std::make_shared;
using std::pair;
using std::shared_ptr;
using std::string;
using std::weak_ptr;
+using std::vector;
using boost::bind;
using boost::optional;
using namespace dcpomatic;
@@ -88,6 +91,7 @@ Butler::Butler(
, _alignment(alignment)
, _fast(fast)
, _prepare_only_proxy(prepare_only_proxy)
+ , _enable_audio_level_calculation(false)
{
_player_video_connection = _player.Video.connect(bind(&Butler::video, this, _1, _2));
_player_audio_connection = _player.Audio.connect(bind(&Butler::audio, this, _1, _2, _3));
@@ -312,6 +316,7 @@ Butler::seek_unlocked(DCPTime position, bool accurate)
_video.clear();
_audio.clear();
+ _audio_level_calculator.clear();
_closed_caption.clear();
_summon.notify_all();
@@ -371,6 +376,9 @@ Butler::audio(shared_ptr<AudioBuffers> audio, DCPTime time, int frame_rate)
}
_audio.put(remap(audio, _audio_channels, _audio_mapping), time, frame_rate);
+ if (_enable_audio_level_calculation) {
+ _audio_level_calculator.put(audio, time, frame_rate);
+ }
}
@@ -481,3 +489,24 @@ Butler::Error::summary() const
return "";
}
+
+void
+Butler::enable_audio_level_calculation(bool enable)
+{
+ _enable_audio_level_calculation = enable;
+}
+
+
+dcpomatic::DCPTime
+Butler::time_between_audio_level_measurements() const
+{
+ return _audio_level_calculator.time_between_measurements();
+}
+
+
+boost::optional<AudioLevels>
+Butler::get_audio_levels()
+{
+ return _audio_level_calculator.get();
+}
+
diff --git a/src/lib/butler.h b/src/lib/butler.h
index ad2552769..eb07a8a3e 100644
--- a/src/lib/butler.h
+++ b/src/lib/butler.h
@@ -23,6 +23,8 @@
#define DCPOMATIC_BUTLER_H
+#include "audio_level_calculator.h"
+#include "audio_levels.h"
#include "audio_mapping.h"
#include "audio_ring_buffers.h"
#include "change_signaller.h"
@@ -92,8 +94,12 @@ public:
std::pair<std::shared_ptr<PlayerVideo>, dcpomatic::DCPTime> get_video(Behaviour behaviour, Error* e = nullptr);
boost::optional<dcpomatic::DCPTime> get_audio(Behaviour behaviour, float* out, Frame frames);
+ boost::optional<AudioLevels> get_audio_levels();
boost::optional<TextRingBuffers::Data> get_closed_caption();
+ void enable_audio_level_calculation(bool enable);
+ dcpomatic::DCPTime time_between_audio_level_measurements() const;
+
std::pair<size_t, std::string> memory_used() const;
private:
@@ -112,6 +118,7 @@ private:
VideoRingBuffers _video;
AudioRingBuffers _audio;
+ AudioLevelCalculator _audio_level_calculator;
TextRingBuffers _closed_caption;
boost::thread_group _prepare_pool;
@@ -154,6 +161,8 @@ private:
*/
boost::optional<dcpomatic::DCPTime> _awaiting;
+ boost::atomic<bool> _enable_audio_level_calculation;
+
boost::signals2::scoped_connection _player_video_connection;
boost::signals2::scoped_connection _player_audio_connection;
boost::signals2::scoped_connection _player_text_connection;
diff --git a/src/lib/wscript b/src/lib/wscript
index dafd655fe..a20a2de10 100644
--- a/src/lib/wscript
+++ b/src/lib/wscript
@@ -38,6 +38,7 @@ sources = """
audio_delay.cc
audio_filter.cc
audio_filter_graph.cc
+ audio_level_calculator.cc
audio_mapping.cc
audio_merger.cc
audio_point.cc
diff --git a/src/tools/dcpomatic_player.cc b/src/tools/dcpomatic_player.cc
index c5242c310..3137acaa3 100644
--- a/src/tools/dcpomatic_player.cc
+++ b/src/tools/dcpomatic_player.cc
@@ -133,6 +133,7 @@ enum {
ID_view_full_screen = DCPOMATIC_MAIN_MENU + 200,
ID_view_dual_screen,
ID_view_closed_captions,
+ ID_view_meters,
ID_view_eye,
ID_view_eye_left,
ID_view_eye_right,
@@ -241,6 +242,7 @@ public:
Bind (wxEVT_MENU, boost::bind (&DOMFrame::view_full_screen, this), ID_view_full_screen);
Bind (wxEVT_MENU, boost::bind (&DOMFrame::view_dual_screen, this), ID_view_dual_screen);
Bind (wxEVT_MENU, boost::bind (&DOMFrame::view_closed_captions, this), ID_view_closed_captions);
+ Bind (wxEVT_MENU, boost::bind (&DOMFrame::view_meters, this), ID_view_meters);
Bind (wxEVT_MENU, boost::bind (&DOMFrame::view_cpl, this, _1), ID_view_cpl, ID_view_cpl + MAX_CPLS);
Bind (wxEVT_MENU, boost::bind (&DOMFrame::view_eye_changed, this, _1), ID_view_eye_left);
Bind (wxEVT_MENU, boost::bind (&DOMFrame::view_eye_changed, this, _1), ID_view_eye_right);
@@ -644,6 +646,7 @@ private:
setup_menu ();
view->AppendSeparator();
view->Append(ID_view_closed_captions, _("Closed captions..."));
+ view->Append(ID_view_meters, _("Audio meters...\tCtrl-L"));
_view_eye_menu = new wxMenu;
_view_eye_left = _view_eye_menu->AppendRadioItem(ID_view_eye_left, _("Left"));
_view_eye_menu->AppendRadioItem(ID_view_eye_right, _("Right"));
@@ -989,6 +992,11 @@ private:
_viewer.show_closed_captions();
}
+ void view_meters ()
+ {
+ _viewer.show_meters();
+ }
+
void tools_verify ()
{
DCPOMATIC_ASSERT(!_film->content().empty());
diff --git a/src/wx/film_viewer.cc b/src/wx/film_viewer.cc
index b77ff987c..a52f4286e 100644
--- a/src/wx/film_viewer.cc
+++ b/src/wx/film_viewer.cc
@@ -28,6 +28,7 @@
#include "closed_captions_dialog.h"
#include "film_viewer.h"
#include "gl_video_view.h"
+#include "meters_dialog.h"
#include "nag_dialog.h"
#include "playhead_to_frame_dialog.h"
#include "playhead_to_timecode_dialog.h"
@@ -89,6 +90,7 @@ rtaudio_callback(void* out, void *, unsigned int frames, double, RtAudioStreamSt
FilmViewer::FilmViewer(wxWindow* p)
: _closed_captions_dialog(new ClosedCaptionsDialog(p, this))
+ , _meters_dialog(new MetersDialog(p))
{
#if wxCHECK_VERSION(3, 1, 0)
switch (Config::instance()->video_view_type()) {
@@ -96,13 +98,21 @@ FilmViewer::FilmViewer(wxWindow* p)
_video_view = std::make_shared<GLVideoView>(this, p);
break;
case Config::VIDEO_VIEW_SIMPLE:
- _video_view = std::make_shared<SimpleVideoView>(this, p);
+ {
+ auto simple = std::make_shared<SimpleVideoView>(this, p);
+ _meters_dialog->set_meters(simple->get_meters());
+ _video_view = simple;
+ }
break;
}
#else
- _video_view = std::make_shared<SimpleVideoView>(this, p);
+ auto simple = std::make_shared<SimpleVideoView>(this, p);
+ _meters_dialog->set_meters(simple->get_meters());
+ _video_view = simple;
#endif
+ _meters_dialog->Bind(wxEVT_SHOW, boost::bind(&FilmViewer::meters_shown, this, _1));
+
_video_view->Sized.connect(boost::bind(&FilmViewer::video_view_sized, this));
_video_view->TooManyDropped.connect(boost::bind(boost::ref(TooManyDropped)));
@@ -119,6 +129,13 @@ FilmViewer::~FilmViewer()
}
+void
+FilmViewer::meters_shown(wxShowEvent& ev)
+{
+ _butler->enable_audio_level_calculation(ev.IsShown());
+}
+
+
/** Ask for ::idle_handler() to be called next time we are idle */
void
FilmViewer::request_idle_display_next_frame()
@@ -808,6 +825,13 @@ FilmViewer::show_closed_captions()
void
+FilmViewer::show_meters()
+{
+ _meters_dialog->Show();
+}
+
+
+void
FilmViewer::seek_by(DCPTime by, bool accurate)
{
seek(_video_view->position() + by, accurate);
diff --git a/src/wx/film_viewer.h b/src/wx/film_viewer.h
index 2a6239d7d..25c99b378 100644
--- a/src/wx/film_viewer.h
+++ b/src/wx/film_viewer.h
@@ -48,6 +48,7 @@ class FFmpegPlayer;
class Image;
class Player;
class PlayerVideo;
+class MetersDialog;
class RGBPlusAlphaImage;
class wxToggleButton;
@@ -71,6 +72,7 @@ public:
}
void show_closed_captions();
+ void show_meters();
void set_film(std::shared_ptr<Film>);
std::shared_ptr<Film> film() const {
@@ -184,6 +186,7 @@ private:
void film_length_change();
void ui_finished();
void start_audio_stream_if_open();
+ void meters_shown(wxShowEvent& ev);
dcpomatic::DCPTime uncorrected_time() const;
@@ -212,6 +215,7 @@ private:
Optimisation _optimisation = Optimisation::NONE;
ClosedCaptionsDialog* _closed_captions_dialog = nullptr;
+ MetersDialog* _meters_dialog = nullptr;
bool _outline_content = false;
boost::optional<dcpomatic::Rect<double>> _outline_subtitles;
diff --git a/src/wx/meter_util.cc b/src/wx/meter_util.cc
new file mode 100644
index 000000000..4f69219ae
--- /dev/null
+++ b/src/wx/meter_util.cc
@@ -0,0 +1,88 @@
+/*
+ Copyright (C) 2025 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "meter_util.h"
+#include <wx/wx.h>
+#include <algorithm>
+#include <cmath>
+
+
+using std::pair;
+using std::vector;
+
+
+auto constexpr min_db = -70.0f;
+
+static auto const _scales = std::vector<std::pair<int, wxString>>{
+ { 0, wxT("0dB") },
+ { -10, wxT("-10dB") },
+ { -20, wxT("-20dB") },
+ { -30, wxT("-30dB") },
+ { -40, wxT("-40dB") },
+ { -50, wxT("-50dB") },
+ { -60, wxT("-60dB") },
+ { -70, wxT("-70dB") },
+};
+
+
+
+int
+dcpomatic::meter::db_to_y(int meter_height, float db)
+{
+ return meter_height - (db - min_db) * meter_height / -min_db;
+}
+
+
+float
+dcpomatic::meter::linear_level_to_db(float level)
+{
+ return std::max(min_db, 20 * log10f(level));
+
+}
+
+
+vector<pair<int, wxString>> const&
+dcpomatic::meter::scales()
+{
+ return _scales;
+}
+
+
+int
+dcpomatic::meter::border()
+{
+ return 16;
+}
+
+
+int
+dcpomatic::meter::scale_label_width()
+{
+ return 28;
+}
+
+
+int
+dcpomatic::meter::channel_label_height()
+{
+ return 24;
+}
+
diff --git a/src/wx/meter_util.h b/src/wx/meter_util.h
new file mode 100644
index 000000000..cdf347e26
--- /dev/null
+++ b/src/wx/meter_util.h
@@ -0,0 +1,41 @@
+/*
+ Copyright (C) 2025 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include <wx/wx.h>
+#include <utility>
+#include <vector>
+
+
+namespace dcpomatic {
+namespace meter {
+
+int border();
+int scale_label_width();
+int channel_label_height();
+
+int db_to_y(int meter_height, float db);
+float linear_level_to_db(float level);
+std::vector<std::pair<int, wxString>> const& scales();
+
+
+}
+}
+
diff --git a/src/wx/meters_dialog.cc b/src/wx/meters_dialog.cc
new file mode 100644
index 000000000..e95fb65f2
--- /dev/null
+++ b/src/wx/meters_dialog.cc
@@ -0,0 +1,44 @@
+/*
+ Copyright (C) 2019-2021 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "meters_dialog.h"
+#include <wx/wx.h>
+
+
+MetersDialog::MetersDialog(wxWindow* parent)
+ : wxDialog(parent, wxID_ANY, _("Meters"))
+{
+
+}
+
+void
+MetersDialog::set_meters(wxWindow* meters)
+{
+ meters->Reparent(this);
+
+ auto sizer = new wxBoxSizer(wxVERTICAL);
+ sizer->Add(meters, 1, wxEXPAND);
+ SetSizer(sizer);
+
+ SetSize({640, 480});
+ sizer->Layout();
+}
+
diff --git a/src/wx/meters_dialog.h b/src/wx/meters_dialog.h
new file mode 100644
index 000000000..84e2f0504
--- /dev/null
+++ b/src/wx/meters_dialog.h
@@ -0,0 +1,40 @@
+/*
+ Copyright (C) 2019-2021 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "simple_meters.h"
+#include "lib/dcpomatic_time.h"
+#include <wx/wx.h>
+#include <memory>
+
+
+class Butler;
+class LevelCalculator;
+class wxWindow;
+
+
+class MetersDialog : public wxDialog
+{
+public:
+ MetersDialog(wxWindow* parent);
+
+ void set_meters(wxWindow* meters);
+};
+
diff --git a/src/wx/simple_meters.cc b/src/wx/simple_meters.cc
new file mode 100644
index 000000000..3c0b99969
--- /dev/null
+++ b/src/wx/simple_meters.cc
@@ -0,0 +1,136 @@
+/*
+ Copyright (C) 2025 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "meter_util.h"
+#include "simple_meters.h"
+#include "wx_util.h"
+#include "lib/butler.h"
+#include "lib/constants.h"
+#include "lib/util.h"
+#include <dcp/types.h>
+LIBDCP_DISABLE_WARNINGS
+#include <wx/graphics.h>
+LIBDCP_ENABLE_WARNINGS
+#include <wx/wx.h>
+#include <algorithm>
+
+
+using std::shared_ptr;
+#if BOOST_VERSION >= 106100
+using namespace boost::placeholders;
+#endif
+
+
+SimpleMeters::SimpleMeters(wxWindow* parent)
+ : _panel(new wxPanel(parent, wxID_ANY))
+{
+ _panel->Bind(wxEVT_PAINT, boost::bind(&SimpleMeters::paint, this));
+}
+
+
+wxWindow*
+SimpleMeters::get() const
+{
+ return _panel;
+}
+
+
+void
+SimpleMeters::paint()
+{
+ using namespace dcpomatic::meter;
+
+ wxPaintDC dc(_panel);
+
+ dc.SetBackground(*wxBLACK_BRUSH);
+ dc.Clear();
+
+ auto const scale = 1 / dpi_scale_factor(_panel);
+ auto const panel_size = dcp::Size(_panel->GetSize().GetWidth() / scale, _panel->GetSize().GetHeight() / scale);
+ auto const meter_height = panel_size.height - channel_label_height() - 2 * border();
+ dc.SetLogicalScale(scale, scale);
+
+ auto gc = wxGraphicsContext::Create(dc);
+ if (!gc) {
+ return;
+ }
+
+ gc->SetAntialiasMode(wxANTIALIAS_DEFAULT);
+ gc->SetFont(gc->CreateFont(*wxSMALL_FONT, *wxWHITE));
+
+ /* Scale lines and labels (0dB, -10dB etc.) */
+ dc.SetPen(*wxGREY_PEN);
+ for (auto const& scale: dcpomatic::meter::scales()) {
+ auto const y = static_cast<int>(std::round(border() + db_to_y(meter_height, scale.first)));
+ dc.DrawLine({ border() + scale_label_width(), y }, { panel_size.width - border(), y });
+ auto const size = _sizes.get(gc, scale.second);
+ gc->DrawText(scale.second, border() + scale_label_width() - size.width - 4, y - size.height / 2);
+ }
+
+ if (!_levels) {
+ delete gc;
+ return;
+ }
+
+ auto const channel_width = (panel_size.width - 2 * border() - scale_label_width()) / _levels->peaks.size();
+
+ /* Meter bars */
+ int x = border() + scale_label_width();
+ dc.SetBrush(*wxGREEN_BRUSH);
+ dc.SetPen(*wxBLACK_PEN);
+ for (auto peak: _levels->peaks) {
+ auto const y = border() + db_to_y(meter_height, linear_level_to_db(peak));
+ dc.DrawRectangle(x, y, channel_width, meter_height - y + border());
+ x += channel_width;
+ }
+
+ /* Channel name labels */
+ for (auto i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
+ auto name = std_to_wx(short_audio_channel_name(i));
+ gc->DrawText(name, border() + scale_label_width() + i * channel_width + (channel_width - _sizes.get(gc, name).width) / 2, panel_size.height - channel_label_height());
+ }
+
+ delete gc;
+}
+
+
+void
+SimpleMeters::update(shared_ptr<Butler> butler, dcpomatic::DCPTime time)
+{
+ while (!_levels || (_levels->time + butler->time_between_audio_level_measurements()) < time) {
+ _levels = butler->get_audio_levels();
+ if (!_levels) {
+ return;
+ }
+ }
+
+ _panel->Refresh();
+ _panel->Update();
+}
+
+
+void
+SimpleMeters::clear()
+{
+ _levels = {};
+ _panel->Refresh();
+ _panel->Update();
+}
diff --git a/src/wx/simple_meters.h b/src/wx/simple_meters.h
new file mode 100644
index 000000000..d05129e6c
--- /dev/null
+++ b/src/wx/simple_meters.h
@@ -0,0 +1,65 @@
+/*
+ Copyright (C) 2025 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#ifndef DCPOMATIC_SIMPLE_METERS_H
+#define DCPOMATIC_SIMPLE_METERS_H
+
+
+#include "lib/audio_levels.h"
+#include "lib/dcpomatic_time.h"
+#include "text_size_cache.h"
+#include <memory>
+#include <vector>
+
+
+class Butler;
+class LevelCalculator;
+class wxPanel;
+class wxShowEvent;
+class wxWindow;
+
+
+class SimpleMeters
+{
+public:
+ SimpleMeters(wxWindow* parent);
+
+ SimpleMeters(SimpleMeters const&) = delete;
+ SimpleMeters& operator=(SimpleMeters const&) = delete;
+
+ wxWindow* get() const;
+
+ void update(std::shared_ptr<Butler> butler, dcpomatic::DCPTime time);
+ void clear();
+
+private:
+ void paint();
+ void shown(wxShowEvent& ev);
+
+ wxPanel* _panel;
+ boost::optional<AudioLevels> _levels;
+
+ TextSizeCache _sizes;
+};
+
+
+#endif
+
diff --git a/src/wx/simple_video_view.cc b/src/wx/simple_video_view.cc
index 665cee117..dcc15c80a 100644
--- a/src/wx/simple_video_view.cc
+++ b/src/wx/simple_video_view.cc
@@ -50,6 +50,7 @@ SimpleVideoView::SimpleVideoView (FilmViewer* viewer, wxWindow* parent)
: VideoView (viewer)
, _rec2020_filter("convert", "convert", "", "colorspace=all=bt709:iall=bt2020")
, _rec2020_filter_graph({ _rec2020_filter }, dcp::Fraction(24, 1))
+ , _meters(parent)
{
_panel = new wxPanel (parent);
@@ -265,4 +266,6 @@ SimpleVideoView::update ()
_inter_size = player_video().first->inter_size ();
refresh_panel ();
+
+ _meters.update(_viewer->butler(), _viewer->time());
}
diff --git a/src/wx/simple_video_view.h b/src/wx/simple_video_view.h
index e19068979..2c6ce7965 100644
--- a/src/wx/simple_video_view.h
+++ b/src/wx/simple_video_view.h
@@ -19,6 +19,7 @@
*/
+#include "simple_meters.h"
#include "video_view.h"
#include "lib/filter.h"
#include "lib/position.h"
@@ -43,6 +44,10 @@ public:
return _panel;
}
+ wxWindow* get_meters() {
+ return _meters.get();
+ }
+
void update () override;
void start () override;
NextFrameResult display_next_frame (bool non_blocking) override;
@@ -60,4 +65,6 @@ private:
Filter _rec2020_filter;
VideoFilterGraphSet _rec2020_filter_graph;
+
+ SimpleMeters _meters;
};
diff --git a/src/wx/text_size_cache.cc b/src/wx/text_size_cache.cc
new file mode 100644
index 000000000..03b7e62ff
--- /dev/null
+++ b/src/wx/text_size_cache.cc
@@ -0,0 +1,47 @@
+/*
+ Copyright (C) 2025 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "text_size_cache.h"
+LIBDCP_DISABLE_WARNINGS
+#include <wx/graphics.h>
+LIBDCP_ENABLE_WARNINGS
+#include <wx/wx.h>
+
+
+dcp::Size
+TextSizeCache::get(wxGraphicsContext* gc, wxString const& text)
+{
+ auto iter = _known.find(text);
+ if (iter != _known.end()) {
+ return iter->second;
+ }
+
+ wxDouble width;
+ wxDouble height;
+ wxDouble descent;
+ wxDouble leading;
+ gc->GetTextExtent(text, &width, &height, &descent, &leading);
+ auto size = dcp::Size{static_cast<int>(std::round(width)), static_cast<int>(std::round(height))};
+ _known[text] = size;
+
+ return size;
+}
+
diff --git a/src/wx/text_size_cache.h b/src/wx/text_size_cache.h
new file mode 100644
index 000000000..58fb9c82a
--- /dev/null
+++ b/src/wx/text_size_cache.h
@@ -0,0 +1,40 @@
+/*
+ Copyright (C) 2025 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include <dcp/types.h>
+#include <wx/wx.h>
+#include <unordered_map>
+
+
+class wxGraphicsContext;
+class wxString;
+
+
+class TextSizeCache
+{
+public:
+ TextSizeCache() = default;
+
+ dcp::Size get(wxGraphicsContext* gc, wxString const& text);
+
+private:
+ std::unordered_map<wxString, dcp::Size> _known;
+};
diff --git a/src/wx/wscript b/src/wx/wscript
index 6bb10511b..e1a2bf084 100644
--- a/src/wx/wscript
+++ b/src/wx/wscript
@@ -125,6 +125,8 @@ sources = """
markers_panel.cc
message_dialog.cc
metadata_dialog.cc
+ meter_util.cc
+ meters_dialog.cc
move_to_dialog.cc
nag_dialog.cc
name_format_editor.cc
@@ -160,6 +162,7 @@ sources = """
server_dialog.cc
servers_list_dialog.cc
short_kdm_output_panel.cc
+ simple_meters.cc
simple_video_view.cc
smpte_metadata_dialog.cc
sound_preferences_page.cc
@@ -174,6 +177,7 @@ sources = """
tall_kdm_output_panel.cc
templates_dialog.cc
text_panel.cc
+ text_size_cache.cc
text_view.cc
time_picker.cc
timer_display.cc
diff --git a/test/audio_level_calculator_test.cc b/test/audio_level_calculator_test.cc
new file mode 100644
index 000000000..c9b6d8245
--- /dev/null
+++ b/test/audio_level_calculator_test.cc
@@ -0,0 +1,46 @@
+/*
+ Copyright (C) 2025 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "lib/audio_buffers.h"
+#include "lib/audio_level_calculator.h"
+#include <boost/test/unit_test.hpp>
+
+
+using std::make_shared;
+
+
+BOOST_AUTO_TEST_CASE(level_calculator_falloff_correct)
+{
+ auto pulse = make_shared<AudioBuffers>(6, 96000);
+ pulse->make_silent();
+
+ pulse->data()[0][0] = 1;
+
+ AudioLevelCalculator calc(30, 15);
+ calc.put(pulse, {}, 48000);
+
+ for (auto i = 0; i < 30; ++i) {
+ calc.get();
+ }
+
+ BOOST_CHECK_CLOSE(calc.get()->peaks[0], pow(10, -15.0 / 20), 0.0001);
+}
+
diff --git a/test/wscript b/test/wscript
index b568872a3..afcc47ed7 100644
--- a/test/wscript
+++ b/test/wscript
@@ -54,6 +54,7 @@ def build(bld):
audio_content_test.cc
audio_delay_test.cc
audio_filter_test.cc
+ audio_level_calculator_test.cc
audio_mapping_test.cc
audio_merger_test.cc
audio_processor_test.cc