From 4bf5a8794cf1ed6411e085b35c11777c3508f82e Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Wed, 1 Jul 2015 23:37:55 +0100 Subject: [PATCH] Restore short-cutting of analysis gain updates. If we have an analysis of one piece of content and the gain changes we don't re-run the analysis, instead applying a suitable `correction' in the UI. --- src/lib/analyse_audio_job.cc | 10 +++++++++ src/lib/audio_analysis.cc | 5 +++++ src/lib/audio_analysis.h | 13 ++++++++++++ src/lib/film.cc | 10 ++++++++- src/wx/audio_dialog.cc | 38 ++++++++++++++++++++++++++++++--- src/wx/audio_dialog.h | 1 + src/wx/audio_plot.cc | 41 ++++++++++++++++++++++++++---------- src/wx/audio_plot.h | 6 ++++-- 8 files changed, 107 insertions(+), 17 deletions(-) diff --git a/src/lib/analyse_audio_job.cc b/src/lib/analyse_audio_job.cc index 1cec15c2a..2f48d12a9 100644 --- a/src/lib/analyse_audio_job.cc +++ b/src/lib/analyse_audio_job.cc @@ -89,6 +89,16 @@ AnalyseAudioJob::run () } _analysis->set_peak (_overall_peak, DCPTime::from_frames (_overall_peak_frame, _film->audio_frame_rate ())); + + if (_playlist->content().size() == 1) { + /* If there was only one piece of content in this analysis we may later need to know what its + gain was when we analysed it. + */ + shared_ptr ac = dynamic_pointer_cast (_playlist->content().front ()); + DCPOMATIC_ASSERT (ac); + _analysis->set_analysis_gain (ac->audio_gain ()); + } + _analysis->write (_film->audio_analysis_path (_playlist)); set_progress (1); diff --git a/src/lib/audio_analysis.cc b/src/lib/audio_analysis.cc index 7e1dc6e78..127def807 100644 --- a/src/lib/audio_analysis.cc +++ b/src/lib/audio_analysis.cc @@ -102,6 +102,7 @@ AudioAnalysis::AudioAnalysis (boost::filesystem::path filename) _peak = f.number_child ("Peak"); _peak_time = DCPTime (f.number_child ("PeakTime")); + _analysis_gain = f.optional_number_child ("AnalysisGain"); } void @@ -149,5 +150,9 @@ AudioAnalysis::write (boost::filesystem::path filename) root->add_child("PeakTime")->add_child_text (raw_convert (_peak_time.get().get ())); } + if (_analysis_gain) { + root->add_child("AnalysisGain")->add_child_text (raw_convert (_analysis_gain.get ())); + } + doc->write_to_file_formatted (filename.string ()); } diff --git a/src/lib/audio_analysis.h b/src/lib/audio_analysis.h index 2411b4316..478b0e532 100644 --- a/src/lib/audio_analysis.h +++ b/src/lib/audio_analysis.h @@ -75,12 +75,25 @@ public: return _peak_time; } + boost::optional analysis_gain () const { + return _analysis_gain; + } + + void set_analysis_gain (double gain) { + _analysis_gain = gain; + } + void write (boost::filesystem::path); private: std::vector > _data; boost::optional _peak; boost::optional _peak_time; + /** If this analysis was run on a single piece of + * content we store its gain in dB when the analysis + * happened. + */ + boost::optional _analysis_gain; }; #endif diff --git a/src/lib/film.cc b/src/lib/film.cc index 5310ef71f..e19b309f7 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -241,7 +241,15 @@ Film::audio_analysis_path (shared_ptr playlist) const digester.add (ac->digest ()); digester.add (ac->audio_mapping().digest ()); - digester.add (ac->audio_gain ()); + if (playlist->content().size() != 1) { + /* Analyses should be considered equal regardless of gain + if they were made from just one piece of content. This + is because we can fake any gain change in a single-content + analysis at the plotting stage rather than having to + recompute it. + */ + digester.add (ac->audio_gain ()); + } } if (audio_processor ()) { diff --git a/src/wx/audio_dialog.cc b/src/wx/audio_dialog.cc index cd59693ea..17d50ef69 100644 --- a/src/wx/audio_dialog.cc +++ b/src/wx/audio_dialog.cc @@ -27,10 +27,12 @@ #include "lib/job_manager.h" #include +using std::cout; using boost::shared_ptr; using boost::bind; using boost::optional; using boost::const_pointer_cast; +using boost::dynamic_pointer_cast; /** @param content Content to analyse, or 0 to analyse all of the film's audio */ AudioDialog::AudioDialog (wxWindow* parent, shared_ptr film, shared_ptr content) @@ -109,7 +111,7 @@ AudioDialog::AudioDialog (wxWindow* parent, shared_ptr film, shared_ptrLayout (); overall_sizer->SetSizeHints (this); - _film_connection = film->ContentChanged.connect (boost::bind (&AudioDialog::try_to_load_analysis, this)); + _film_connection = film->ContentChanged.connect (boost::bind (&AudioDialog::content_changed, this, _2)); SetTitle (_("DCP-o-matic audio")); if (content) { @@ -152,6 +154,7 @@ AudioDialog::try_to_load_analysis () } _plot->set_analysis (_analysis); + _plot->set_gain_correction (gain_correction ()); setup_peak_time (); /* Set up some defaults if no check boxes are checked */ @@ -214,8 +217,18 @@ AudioDialog::channel_clicked (wxCommandEvent& ev) void AudioDialog::content_changed (int p) { - if (p == AudioContentProperty::AUDIO_GAIN || p == AudioContentProperty::AUDIO_STREAMS) { + if (p == AudioContentProperty::AUDIO_STREAMS) { try_to_load_analysis (); + } else if (p == AudioContentProperty::AUDIO_GAIN) { + if (_playlist->content().size() == 1) { + /* We can use a short-cut to render the effect of this + change, rather than recalculating everything. + */ + _plot->set_gain_correction (gain_correction ()); + setup_peak_time (); + } else { + try_to_load_analysis (); + } } } @@ -250,7 +263,7 @@ AudioDialog::setup_peak_time () return; } - float peak_dB = 20 * log10 (_analysis->peak().get()); + float const peak_dB = 20 * log10 (_analysis->peak().get()) + gain_correction (); _peak_time->SetLabel ( wxString::Format ( @@ -274,3 +287,22 @@ AudioDialog::Show (bool show) try_to_load_analysis (); return r; } + +/** @return gain correction in dB required to be added to raw gain values to render + * the dialog correctly. + */ +float +AudioDialog::gain_correction () +{ + if (_playlist->content().size() == 1 && _analysis->analysis_gain ()) { + /* In this case we know that the analysis was of a single piece of content and + we know that content's gain when the analysis was run. Hence we can work out + what correction is now needed to make it look `right'. + */ + shared_ptr ac = dynamic_pointer_cast (_playlist->content().front ()); + DCPOMATIC_ASSERT (ac); + return ac->audio_gain() - _analysis->analysis_gain().get (); + } + + return 0.0f; +} diff --git a/src/wx/audio_dialog.h b/src/wx/audio_dialog.h index c99261150..945ff013b 100644 --- a/src/wx/audio_dialog.h +++ b/src/wx/audio_dialog.h @@ -42,6 +42,7 @@ private: void try_to_load_analysis (); void analysis_finished (); void setup_peak_time (); + float gain_correction (); boost::shared_ptr _analysis; boost::weak_ptr _film; diff --git a/src/wx/audio_plot.cc b/src/wx/audio_plot.cc index 1c5c1f94b..605738b4f 100644 --- a/src/wx/audio_plot.cc +++ b/src/wx/audio_plot.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2013 Carl Hetherington + Copyright (C) 2013-2015 Carl Hetherington This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,13 +17,13 @@ */ -#include -#include -#include #include "audio_plot.h" #include "lib/audio_decoder.h" #include "lib/audio_analysis.h" #include "wx/wx_util.h" +#include +#include +#include using std::cout; using std::vector; @@ -39,6 +39,7 @@ int const AudioPlot::max_smoothing = 128; AudioPlot::AudioPlot (wxWindow* parent) : wxPanel (parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE) , _smoothing (max_smoothing / 2) + , _gain_correction (0) { #ifndef __WXOSX__ SetDoubleBuffered (true); @@ -214,12 +215,12 @@ AudioPlot::plot_peak (wxGraphicsPath& path, int channel, Metrics const & metrics return; } - path.MoveToPoint (metrics.db_label_width, y_for_linear (_analysis->get_point(channel, 0)[AudioPoint::PEAK], metrics)); + path.MoveToPoint (metrics.db_label_width, y_for_linear (get_point(channel, 0)[AudioPoint::PEAK], metrics)); float peak = 0; int const N = _analysis->points(channel); for (int i = 0; i < N; ++i) { - float const p = _analysis->get_point(channel, i)[AudioPoint::PEAK]; + float const p = get_point(channel, i)[AudioPoint::PEAK]; peak -= 0.01f * (1 - log10 (_smoothing) / log10 (max_smoothing)); if (p > peak) { peak = p; @@ -238,14 +239,14 @@ AudioPlot::plot_rms (wxGraphicsPath& path, int channel, Metrics const & metrics) return; } - path.MoveToPoint (metrics.db_label_width, y_for_linear (_analysis->get_point(channel, 0)[AudioPoint::RMS], metrics)); + path.MoveToPoint (metrics.db_label_width, y_for_linear (get_point(channel, 0)[AudioPoint::RMS], metrics)); list smoothing; int const N = _analysis->points(channel); - float const first = _analysis->get_point(channel, 0)[AudioPoint::RMS]; - float const last = _analysis->get_point(channel, N - 1)[AudioPoint::RMS]; + float const first = get_point(channel, 0)[AudioPoint::RMS]; + float const last = get_point(channel, N - 1)[AudioPoint::RMS]; int const before = _smoothing / 2; int const after = _smoothing - before; @@ -256,7 +257,7 @@ AudioPlot::plot_rms (wxGraphicsPath& path, int channel, Metrics const & metrics) } for (int i = 0; i < after; ++i) { if (i < N) { - smoothing.push_back (_analysis->get_point(channel, i)[AudioPoint::RMS]); + smoothing.push_back (get_point(channel, i)[AudioPoint::RMS]); } else { smoothing.push_back (last); } @@ -267,7 +268,7 @@ AudioPlot::plot_rms (wxGraphicsPath& path, int channel, Metrics const & metrics) int const next_for_window = i + after; if (next_for_window < N) { - smoothing.push_back (_analysis->get_point(channel, i)[AudioPoint::RMS]); + smoothing.push_back (get_point(channel, i)[AudioPoint::RMS]); } else { smoothing.push_back (last); } @@ -293,3 +294,21 @@ AudioPlot::set_smoothing (int s) _smoothing = s; Refresh (); } + +void +AudioPlot::set_gain_correction (double gain) +{ + _gain_correction = gain; + Refresh (); +} + +AudioPoint +AudioPlot::get_point (int channel, int point) const +{ + AudioPoint p = _analysis->get_point (channel, point); + for (int i = 0; i < AudioPoint::COUNT; ++i) { + p[i] *= pow (10, _gain_correction / 20); + } + + return p; +} diff --git a/src/wx/audio_plot.h b/src/wx/audio_plot.h index 846ac22dc..2027b6528 100644 --- a/src/wx/audio_plot.h +++ b/src/wx/audio_plot.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 Carl Hetherington + Copyright (C) 2012-2015 Carl Hetherington This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -35,6 +35,7 @@ public: void set_type_visible (int t, bool v); void set_smoothing (int); void set_message (wxString); + void set_gain_correction (double gain); static const int max_smoothing; @@ -43,14 +44,15 @@ private: void plot_peak (wxGraphicsPath &, int, Metrics const &) const; void plot_rms (wxGraphicsPath &, int, Metrics const &) const; float y_for_linear (float, Metrics const &) const; + AudioPoint get_point (int channel, int point) const; boost::shared_ptr _analysis; bool _channel_visible[MAX_DCP_AUDIO_CHANNELS]; bool _type_visible[AudioPoint::COUNT]; int _smoothing; std::vector _colours; - wxString _message; + float _gain_correction; static const int _minimum; }; -- 2.30.2