2 Copyright (C) 2013-2015 Carl Hetherington <cth@carlh.net>
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 #include "audio_dialog.h"
21 #include "audio_plot.h"
23 #include "lib/audio_analysis.h"
25 #include "lib/analyse_audio_job.h"
26 #include "lib/audio_content.h"
27 #include "lib/job_manager.h"
28 #include <boost/filesystem.hpp>
31 using boost::shared_ptr;
33 using boost::optional;
34 using boost::const_pointer_cast;
35 using boost::dynamic_pointer_cast;
37 /** @param content Content to analyse, or 0 to analyse all of the film's audio */
38 AudioDialog::AudioDialog (wxWindow* parent, shared_ptr<Film> film, shared_ptr<AudioContent> content)
39 : wxDialog (parent, wxID_ANY, _("Audio"), wxDefaultPosition, wxSize (640, 512), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE)
43 wxFont subheading_font (*wxNORMAL_FONT);
44 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
46 wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
47 wxBoxSizer* lr_sizer = new wxBoxSizer (wxHORIZONTAL);
49 wxBoxSizer* left = new wxBoxSizer (wxVERTICAL);
51 _plot = new AudioPlot (this);
52 left->Add (_plot, 1, wxALL | wxEXPAND, 12);
53 _peak_time = new wxStaticText (this, wxID_ANY, wxT (""));
54 left->Add (_peak_time, 0, wxALL, 12);
56 lr_sizer->Add (left, 1, wxALL, 12);
58 wxBoxSizer* right = new wxBoxSizer (wxVERTICAL);
61 wxStaticText* m = new wxStaticText (this, wxID_ANY, _("Channels"));
62 m->SetFont (subheading_font);
63 right->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM, 16);
66 for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
67 _channel_checkbox[i] = new wxCheckBox (this, wxID_ANY, std_to_wx (audio_channel_name (i)));
68 right->Add (_channel_checkbox[i], 0, wxEXPAND | wxALL, 3);
69 _channel_checkbox[i]->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AudioDialog::channel_clicked, this, _1));
73 wxStaticText* m = new wxStaticText (this, wxID_ANY, _("Type"));
74 m->SetFont (subheading_font);
75 right->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP, 16);
78 wxString const types[] = {
83 for (int i = 0; i < AudioPoint::COUNT; ++i) {
84 _type_checkbox[i] = new wxCheckBox (this, wxID_ANY, types[i]);
85 right->Add (_type_checkbox[i], 0, wxEXPAND | wxALL, 3);
86 _type_checkbox[i]->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AudioDialog::type_clicked, this, _1));
90 wxStaticText* m = new wxStaticText (this, wxID_ANY, _("Smoothing"));
91 m->SetFont (subheading_font);
92 right->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP, 16);
95 _smoothing = new wxSlider (this, wxID_ANY, AudioPlot::max_smoothing / 2, 1, AudioPlot::max_smoothing);
96 _smoothing->Bind (wxEVT_SCROLL_THUMBTRACK, boost::bind (&AudioDialog::smoothing_changed, this));
97 right->Add (_smoothing, 0, wxEXPAND);
99 lr_sizer->Add (right, 0, wxALL, 12);
101 overall_sizer->Add (lr_sizer);
103 #ifdef DCPOMATIC_LINUX
104 wxSizer* buttons = CreateSeparatedButtonSizer (wxCLOSE);
106 overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
110 SetSizer (overall_sizer);
111 overall_sizer->Layout ();
112 overall_sizer->SetSizeHints (this);
114 _film_connection = film->ContentChanged.connect (boost::bind (&AudioDialog::content_changed, this, _2));
115 SetTitle (_("DCP-o-matic audio"));
118 _playlist.reset (new Playlist ());
119 const_pointer_cast<Playlist> (_playlist)->add (content);
121 _playlist = film->playlist ();
126 AudioDialog::try_to_load_analysis ()
132 shared_ptr<const Film> film = _film.lock ();
133 DCPOMATIC_ASSERT (film);
135 boost::filesystem::path path = film->audio_analysis_path (_playlist);
137 if (!boost::filesystem::exists (path)) {
138 _plot->set_analysis (shared_ptr<AudioAnalysis> ());
140 shared_ptr<AnalyseAudioJob> job (new AnalyseAudioJob (film, _playlist));
141 _analysis_finished_connection = job->Finished.connect (bind (&AudioDialog::analysis_finished, this));
142 JobManager::instance()->add (job);
147 _analysis.reset (new AudioAnalysis (path));
148 } catch (xmlpp::exception& e) {
149 /* Probably an old-style analysis file: recreate it */
150 shared_ptr<AnalyseAudioJob> job (new AnalyseAudioJob (film, _playlist));
151 _analysis_finished_connection = job->Finished.connect (bind (&AudioDialog::analysis_finished, this));
152 JobManager::instance()->add (job);
156 _plot->set_analysis (_analysis);
157 _plot->set_gain_correction (gain_correction ());
160 /* Set up some defaults if no check boxes are checked */
163 while (i < MAX_DCP_AUDIO_CHANNELS && (!_channel_checkbox[i] || !_channel_checkbox[i]->GetValue ())) {
167 if (i == MAX_DCP_AUDIO_CHANNELS && _channel_checkbox[0]) {
168 _channel_checkbox[0]->SetValue (true);
169 _plot->set_channel_visible (0, true);
173 while (i < AudioPoint::COUNT && !_type_checkbox[i]->GetValue ()) {
177 if (i == AudioPoint::COUNT) {
178 for (int i = 0; i < AudioPoint::COUNT; ++i) {
179 _type_checkbox[i]->SetValue (true);
180 _plot->set_type_visible (i, true);
188 AudioDialog::analysis_finished ()
190 shared_ptr<const Film> film = _film.lock ();
191 DCPOMATIC_ASSERT (film);
193 if (!boost::filesystem::exists (film->audio_analysis_path (_playlist))) {
194 /* We analysed and still nothing showed up, so maybe it was cancelled or it failed.
197 _plot->set_message (_("Could not analyse audio."));
201 try_to_load_analysis ();
205 AudioDialog::channel_clicked (wxCommandEvent& ev)
208 while (c < MAX_DCP_AUDIO_CHANNELS && ev.GetEventObject() != _channel_checkbox[c]) {
212 DCPOMATIC_ASSERT (c < MAX_DCP_AUDIO_CHANNELS);
214 _plot->set_channel_visible (c, _channel_checkbox[c]->GetValue ());
218 AudioDialog::content_changed (int p)
220 if (p == AudioContentProperty::AUDIO_STREAMS) {
221 try_to_load_analysis ();
222 } else if (p == AudioContentProperty::AUDIO_GAIN) {
223 if (_playlist->content().size() == 1) {
224 /* We can use a short-cut to render the effect of this
225 change, rather than recalculating everything.
227 _plot->set_gain_correction (gain_correction ());
230 try_to_load_analysis ();
236 AudioDialog::type_clicked (wxCommandEvent& ev)
239 while (t < AudioPoint::COUNT && ev.GetEventObject() != _type_checkbox[t]) {
243 DCPOMATIC_ASSERT (t < AudioPoint::COUNT);
245 _plot->set_type_visible (t, _type_checkbox[t]->GetValue ());
249 AudioDialog::smoothing_changed ()
251 _plot->set_smoothing (_smoothing->GetValue ());
255 AudioDialog::setup_peak_time ()
257 if (!_analysis || !_analysis->peak ()) {
261 shared_ptr<Film> film = _film.lock ();
266 float const peak_dB = 20 * log10 (_analysis->peak().get()) + gain_correction ();
268 _peak_time->SetLabel (
270 _("Peak is %.2fdB at %s"),
272 time_to_timecode (_analysis->peak_time().get(), film->video_frame_rate ()).data ()
277 _peak_time->SetForegroundColour (wxColour (255, 0, 0));
279 _peak_time->SetForegroundColour (wxColour (0, 0, 0));
284 AudioDialog::Show (bool show)
286 bool const r = wxDialog::Show (show);
287 try_to_load_analysis ();
291 /** @return gain correction in dB required to be added to raw gain values to render
292 * the dialog correctly.
295 AudioDialog::gain_correction ()
297 if (_playlist->content().size() == 1 && _analysis->analysis_gain ()) {
298 /* In this case we know that the analysis was of a single piece of content and
299 we know that content's gain when the analysis was run. Hence we can work out
300 what correction is now needed to make it look `right'.
302 shared_ptr<const AudioContent> ac = dynamic_pointer_cast<const AudioContent> (_playlist->content().front ());
303 DCPOMATIC_ASSERT (ac);
304 return ac->audio_gain() - _analysis->analysis_gain().get ();