/*
- Copyright (C) 2013-2016 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
*/
+
#include "audio_dialog.h"
#include "audio_plot.h"
+#include "check_box.h"
+#include "static_text.h"
#include "wx_util.h"
-#include "lib/audio_analysis.h"
-#include "lib/film.h"
#include "lib/analyse_audio_job.h"
+#include "lib/audio_analysis.h"
#include "lib/audio_content.h"
+#include "lib/film.h"
#include "lib/job_manager.h"
+#include "lib/maths_util.h"
#include <libxml++/libxml++.h>
#include <boost/filesystem.hpp>
-#include <boost/foreach.hpp>
#include <iostream>
+
using std::cout;
using std::list;
-using boost::shared_ptr;
+using std::make_shared;
+using std::pair;
+using std::shared_ptr;
+using std::vector;
+using std::weak_ptr;
using boost::bind;
using boost::optional;
using boost::const_pointer_cast;
-using boost::dynamic_pointer_cast;
+using std::dynamic_pointer_cast;
+using namespace dcpomatic;
+#if BOOST_VERSION >= 106100
+using namespace boost::placeholders;
+#endif
+
-/** @param content Content to analyse, or 0 to analyse all of the film's audio */
-AudioDialog::AudioDialog (wxWindow* parent, shared_ptr<Film> film, shared_ptr<Content> content)
- : wxDialog (parent, wxID_ANY, _("Audio"), wxDefaultPosition, wxSize (640, 512), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE)
+/** @param parent Parent window.
+ * @param film Film we are using.
+ * @param content Content to analyse, or 0 to analyse all of the film's audio.
+ */
+AudioDialog::AudioDialog (wxWindow* parent, shared_ptr<Film> film, FilmViewer& viewer, shared_ptr<Content> content)
+ : wxDialog (
+ parent,
+ wxID_ANY,
+ _("Audio"),
+ wxDefaultPosition,
+ wxSize (640, 512),
+#ifdef DCPOMATIC_OSX
+ /* I can't get wxFRAME_FLOAT_ON_PARENT to work on OS X, and although wxSTAY_ON_TOP keeps
+ the window above all others (and not just our own) it's better than nothing for now.
+ */
+ wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE | wxSTAY_ON_TOP
+#else
+ wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE | wxFRAME_FLOAT_ON_PARENT
+#endif
+ )
, _film (film)
, _content (content)
, _channels (film->audio_channels ())
- , _plot (0)
+ , _plot (nullptr)
{
wxFont subheading_font (*wxNORMAL_FONT);
subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
- wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
- wxBoxSizer* lr_sizer = new wxBoxSizer (wxHORIZONTAL);
+ auto overall_sizer = new wxBoxSizer (wxVERTICAL);
+ auto lr_sizer = new wxBoxSizer (wxHORIZONTAL);
- wxBoxSizer* left = new wxBoxSizer (wxVERTICAL);
+ auto left = new wxBoxSizer (wxVERTICAL);
- _plot = new AudioPlot (this);
+ _cursor = new StaticText (this, wxT("Cursor: none"));
+ left->Add (_cursor, 0, wxTOP, DCPOMATIC_SIZER_Y_GAP);
+ _plot = new AudioPlot (this, viewer);
left->Add (_plot, 1, wxTOP | wxEXPAND, 12);
- _sample_peak = new wxStaticText (this, wxID_ANY, wxT (""));
+ _sample_peak = new StaticText (this, wxT (""));
left->Add (_sample_peak, 0, wxTOP, DCPOMATIC_SIZER_Y_GAP);
- _true_peak = new wxStaticText (this, wxID_ANY, wxT (""));
+ _true_peak = new StaticText (this, wxT (""));
left->Add (_true_peak, 0, wxTOP, DCPOMATIC_SIZER_Y_GAP);
- _integrated_loudness = new wxStaticText (this, wxID_ANY, wxT (""));
+ _integrated_loudness = new StaticText (this, wxT (""));
left->Add (_integrated_loudness, 0, wxTOP, DCPOMATIC_SIZER_Y_GAP);
- _loudness_range = new wxStaticText (this, wxID_ANY, wxT (""));
+ _loudness_range = new StaticText (this, wxT (""));
left->Add (_loudness_range, 0, wxTOP, DCPOMATIC_SIZER_Y_GAP);
+ _leqm = new StaticText (this, wxT(""));
+ left->Add (_leqm, 0, wxTOP, DCPOMATIC_SIZER_Y_GAP);
- lr_sizer->Add (left, 1, wxALL, 12);
+ lr_sizer->Add (left, 1, wxALL | wxEXPAND, 12);
- wxBoxSizer* right = new wxBoxSizer (wxVERTICAL);
+ auto right = new wxBoxSizer (wxVERTICAL);
{
- wxStaticText* m = new wxStaticText (this, wxID_ANY, _("Channels"));
+ auto m = new StaticText (this, _("Channels"));
m->SetFont (subheading_font);
- right->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM, 16);
+ right->Add (m, 1, wxTOP | wxBOTTOM, 16);
}
- for (int i = 0; i < _channels; ++i) {
- _channel_checkbox[i] = new wxCheckBox (this, wxID_ANY, std_to_wx (audio_channel_name (i)));
+ for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
+ _channel_checkbox[i] = new CheckBox (this, std_to_wx(audio_channel_name(i)));
+ _channel_checkbox[i]->SetForegroundColour(wxColour(_plot->colour(i)));
right->Add (_channel_checkbox[i], 0, wxEXPAND | wxALL, 3);
- _channel_checkbox[i]->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AudioDialog::channel_clicked, this, _1));
+ _channel_checkbox[i]->Bind (wxEVT_CHECKBOX, boost::bind (&AudioDialog::channel_clicked, this, _1));
}
+ show_or_hide_channel_checkboxes ();
+
{
- wxStaticText* m = new wxStaticText (this, wxID_ANY, _("Type"));
+ auto m = new StaticText (this, _("Type"));
m->SetFont (subheading_font);
- right->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP, 16);
+ right->Add (m, 1, wxTOP, 16);
}
wxString const types[] = {
};
for (int i = 0; i < AudioPoint::COUNT; ++i) {
- _type_checkbox[i] = new wxCheckBox (this, wxID_ANY, types[i]);
+ _type_checkbox[i] = new CheckBox (this, types[i]);
right->Add (_type_checkbox[i], 0, wxEXPAND | wxALL, 3);
- _type_checkbox[i]->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AudioDialog::type_clicked, this, _1));
+ _type_checkbox[i]->Bind (wxEVT_CHECKBOX, boost::bind (&AudioDialog::type_clicked, this, _1));
}
{
- wxStaticText* m = new wxStaticText (this, wxID_ANY, _("Smoothing"));
+ auto m = new StaticText (this, _("Smoothing"));
m->SetFont (subheading_font);
- right->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP, 16);
+ right->Add (m, 1, wxTOP, 16);
}
_smoothing = new wxSlider (this, wxID_ANY, AudioPlot::max_smoothing / 2, 1, AudioPlot::max_smoothing);
lr_sizer->Add (right, 0, wxALL, 12);
- overall_sizer->Add (lr_sizer);
+ overall_sizer->Add (lr_sizer, 0, wxEXPAND);
#ifdef DCPOMATIC_LINUX
- wxSizer* buttons = CreateSeparatedButtonSizer (wxCLOSE);
+ auto buttons = CreateSeparatedButtonSizer (wxCLOSE);
if (buttons) {
overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
}
overall_sizer->Layout ();
overall_sizer->SetSizeHints (this);
- _film_connection = film->ContentChanged.connect (boost::bind (&AudioDialog::content_changed, this, _2));
- SetTitle (_("DCP-o-matic audio"));
+ _film_connection = film->Change.connect (boost::bind(&AudioDialog::film_change, this, _1, _2));
+ _film_content_connection = film->ContentChange.connect(boost::bind(&AudioDialog::content_change, this, _1, _3));
+ DCPOMATIC_ASSERT (film->directory());
+ if (content) {
+ SetTitle(wxString::Format(_("DCP-o-matic audio - %s"), std_to_wx(content->path(0).string())));
+ } else {
+ SetTitle(wxString::Format(_("DCP-o-matic audio - %s"), std_to_wx(film->directory().get().string())));
+ }
if (content) {
- _playlist.reset (new Playlist ());
- const_pointer_cast<Playlist> (_playlist)->add (content);
+ _playlist = make_shared<Playlist>();
+ const_pointer_cast<Playlist>(_playlist)->add(film, content);
} else {
_playlist = film->playlist ();
}
+
+ _plot->Cursor.connect (bind (&AudioDialog::set_cursor, this, _1, _2));
}
+
+void
+AudioDialog::show_or_hide_channel_checkboxes ()
+{
+ for (int i = 0; i < _channels; ++i) {
+ _channel_checkbox[i]->Show ();
+ }
+
+ for (int i = _channels; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
+ _channel_checkbox[i]->Hide ();
+ }
+}
+
+
void
AudioDialog::try_to_load_analysis ()
{
return;
}
- shared_ptr<const Film> film = _film.lock ();
+ auto film = _film.lock ();
DCPOMATIC_ASSERT (film);
- boost::filesystem::path const path = film->audio_analysis_path (_playlist);
+ auto check = _content.lock();
+
+ auto const path = film->audio_analysis_path (_playlist);
if (!boost::filesystem::exists (path)) {
_plot->set_analysis (shared_ptr<AudioAnalysis> ());
_analysis.reset ();
- JobManager::instance()->analyse_audio (film, _playlist, _analysis_finished_connection, bind (&AudioDialog::analysis_finished, this));
+
+ for (auto i: JobManager::instance()->get()) {
+ if (dynamic_pointer_cast<AnalyseAudioJob>(i)) {
+ i->cancel ();
+ }
+ }
+
+ JobManager::instance()->analyse_audio (
+ film, _playlist, !static_cast<bool>(check), _analysis_finished_connection, bind (&AudioDialog::analysis_finished, this)
+ );
return;
}
try {
_analysis.reset (new AudioAnalysis (path));
+ } catch (OldFormatError& e) {
+ /* An old analysis file: recreate it */
+ JobManager::instance()->analyse_audio (
+ film, _playlist, !static_cast<bool>(check), _analysis_finished_connection, bind (&AudioDialog::analysis_finished, this)
+ );
+ return;
} catch (xmlpp::exception& e) {
- /* Probably an old-style analysis file: recreate it */
- JobManager::instance()->analyse_audio (film, _playlist, _analysis_finished_connection, bind (&AudioDialog::analysis_finished, this));
+ /* Probably a (very) old-style analysis file: recreate it */
+ JobManager::instance()->analyse_audio (
+ film, _playlist, !static_cast<bool>(check), _analysis_finished_connection, bind (&AudioDialog::analysis_finished, this)
+ );
return;
}
_plot->set_analysis (_analysis);
_plot->set_gain_correction (_analysis->gain_correction (_playlist));
setup_statistics ();
+ show_or_hide_channel_checkboxes ();
/* Set up some defaults if no check boxes are checked */
/* Nothing checked; check mapped ones */
list<int> mapped;
- shared_ptr<Content> content = _content.lock ();
+ auto content = _content.lock ();
if (content) {
mapped = content->audio->mapping().mapped_output_channels ();
mapped = film->mapped_audio_channels ();
}
- BOOST_FOREACH (int i, mapped) {
+ for (auto i: mapped) {
if (_channel_checkbox[i]) {
_channel_checkbox[i]->SetValue (true);
_plot->set_channel_visible (i, true);
void
AudioDialog::analysis_finished ()
{
- shared_ptr<const Film> film = _film.lock ();
- DCPOMATIC_ASSERT (film);
+ auto film = _film.lock ();
+ if (!film) {
+ /* This should not happen, but if it does we should just give up quietly */
+ return;
+ }
if (!boost::filesystem::exists (film->audio_analysis_path (_playlist))) {
/* We analysed and still nothing showed up, so maybe it was cancelled or it failed.
}
void
-AudioDialog::content_changed (int p)
+AudioDialog::film_change (ChangeType type, Film::Property p)
{
+ if (type != ChangeType::DONE) {
+ return;
+ }
+
+ if (p == Film::Property::AUDIO_CHANNELS) {
+ auto film = _film.lock ();
+ if (film) {
+ _channels = film->audio_channels ();
+ try_to_load_analysis ();
+ }
+ }
+}
+
+void
+AudioDialog::content_change (ChangeType type, int p)
+{
+ if (type != ChangeType::DONE) {
+ return;
+ }
+
if (p == AudioContentProperty::STREAMS) {
try_to_load_analysis ();
} else if (p == AudioContentProperty::GAIN) {
return;
}
- shared_ptr<Film> film = _film.lock ();
+ auto film = _film.lock ();
if (!film) {
return;
}
- if (static_cast<bool>(_analysis->sample_peak ())) {
-
- float const peak_dB = 20 * log10 (_analysis->sample_peak().get()) + _analysis->gain_correction (_playlist);
-
- _sample_peak->SetLabel (
- wxString::Format (
- _("Sample peak is %.2fdB at %s"),
- peak_dB,
- time_to_timecode (_analysis->sample_peak_time().get(), film->video_frame_rate ()).data ()
- )
- );
-
- if (peak_dB > -3) {
- _sample_peak->SetForegroundColour (wxColour (255, 0, 0));
- } else {
- _sample_peak->SetForegroundColour (wxColour (0, 0, 0));
- }
+ auto const peak = _analysis->overall_sample_peak ();
+ float const peak_dB = linear_to_db(peak.first.peak) + _analysis->gain_correction(_playlist);
+ _sample_peak->SetLabel (
+ wxString::Format (
+ _("Sample peak is %.2fdB at %s on %s"),
+ peak_dB,
+ time_to_timecode (peak.first.time, film->video_frame_rate ()).data (),
+ std_to_wx (short_audio_channel_name (peak.second)).data ()
+ )
+ );
+
+ if (peak_dB > -3) {
+ _sample_peak->SetForegroundColour (wxColour (255, 0, 0));
+ } else {
+ _sample_peak->SetForegroundColour (wxColour (0, 0, 0));
}
- if (static_cast<bool>(_analysis->true_peak ())) {
- float const peak_dB = 20 * log10 (_analysis->true_peak().get()) + _analysis->gain_correction (_playlist);
+ if (_analysis->overall_true_peak()) {
+ float const peak = _analysis->overall_true_peak().get();
+ float const peak_dB = linear_to_db(peak) + _analysis->gain_correction(_playlist);
_true_peak->SetLabel (wxString::Format (_("True peak is %.2fdB"), peak_dB));
/* XXX: check whether it's ok to add dB gain to these quantities */
- if (static_cast<bool>(_analysis->integrated_loudness ())) {
+ if (static_cast<bool>(_analysis->integrated_loudness())) {
_integrated_loudness->SetLabel (
wxString::Format (
_("Integrated loudness %.2f LUFS"),
);
}
- if (static_cast<bool>(_analysis->loudness_range ())) {
+ if (static_cast<bool>(_analysis->loudness_range())) {
_loudness_range->SetLabel (
wxString::Format (
_("Loudness range %.2f LU"),
)
);
}
+
+ if (static_cast<bool>(_analysis->leqm())) {
+ _leqm->SetLabel(
+ wxString::Format(
+ _("LEQ(m) %.2fdB"), _analysis->leqm().get() + _analysis->gain_correction(_playlist)
+ )
+ );
+ }
}
bool
try_to_load_analysis ();
return r;
}
+
+void
+AudioDialog::set_cursor (optional<DCPTime> time, optional<float> db)
+{
+ if (!time || !db) {
+ _cursor->SetLabel (_("Cursor: none"));
+ return;
+ }
+
+ auto film = _film.lock();
+ DCPOMATIC_ASSERT (film);
+ _cursor->SetLabel (wxString::Format (_("Cursor: %.1fdB at %s"), *db, time->timecode(film->video_frame_rate())));
+}