No-op; fix GPL address and use the explicit-program-name version.
[dcpomatic.git] / src / wx / audio_dialog.cc
1 /*
2     Copyright (C) 2013-2016 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     DCP-o-matic is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21 #include "audio_dialog.h"
22 #include "audio_plot.h"
23 #include "wx_util.h"
24 #include "lib/audio_analysis.h"
25 #include "lib/film.h"
26 #include "lib/analyse_audio_job.h"
27 #include "lib/audio_content.h"
28 #include "lib/job_manager.h"
29 #include <libxml++/libxml++.h>
30 #include <boost/filesystem.hpp>
31 #include <boost/foreach.hpp>
32 #include <iostream>
33
34 using std::cout;
35 using std::list;
36 using boost::shared_ptr;
37 using boost::bind;
38 using boost::optional;
39 using boost::const_pointer_cast;
40 using boost::dynamic_pointer_cast;
41
42 /** @param content Content to analyse, or 0 to analyse all of the film's audio */
43 AudioDialog::AudioDialog (wxWindow* parent, shared_ptr<Film> film, shared_ptr<Content> content)
44         : wxDialog (parent, wxID_ANY, _("Audio"), wxDefaultPosition, wxSize (640, 512), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE)
45         , _film (film)
46         , _content (content)
47         , _channels (film->audio_channels ())
48         , _plot (0)
49 {
50         wxFont subheading_font (*wxNORMAL_FONT);
51         subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
52
53         wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
54         wxBoxSizer* lr_sizer = new wxBoxSizer (wxHORIZONTAL);
55
56         wxBoxSizer* left = new wxBoxSizer (wxVERTICAL);
57
58         _plot = new AudioPlot (this);
59         left->Add (_plot, 1, wxTOP | wxEXPAND, 12);
60         _sample_peak = new wxStaticText (this, wxID_ANY, wxT (""));
61         left->Add (_sample_peak, 0, wxTOP, DCPOMATIC_SIZER_Y_GAP);
62         _true_peak = new wxStaticText (this, wxID_ANY, wxT (""));
63         left->Add (_true_peak, 0, wxTOP, DCPOMATIC_SIZER_Y_GAP);
64         _integrated_loudness = new wxStaticText (this, wxID_ANY, wxT (""));
65         left->Add (_integrated_loudness, 0, wxTOP, DCPOMATIC_SIZER_Y_GAP);
66         _loudness_range = new wxStaticText (this, wxID_ANY, wxT (""));
67         left->Add (_loudness_range, 0, wxTOP, DCPOMATIC_SIZER_Y_GAP);
68
69         lr_sizer->Add (left, 1, wxALL, 12);
70
71         wxBoxSizer* right = new wxBoxSizer (wxVERTICAL);
72
73         {
74                 wxStaticText* m = new wxStaticText (this, wxID_ANY, _("Channels"));
75                 m->SetFont (subheading_font);
76                 right->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM, 16);
77         }
78
79         for (int i = 0; i < _channels; ++i) {
80                 _channel_checkbox[i] = new wxCheckBox (this, wxID_ANY, std_to_wx (audio_channel_name (i)));
81                 right->Add (_channel_checkbox[i], 0, wxEXPAND | wxALL, 3);
82                 _channel_checkbox[i]->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AudioDialog::channel_clicked, this, _1));
83         }
84
85         {
86                 wxStaticText* m = new wxStaticText (this, wxID_ANY, _("Type"));
87                 m->SetFont (subheading_font);
88                 right->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP, 16);
89         }
90
91         wxString const types[] = {
92                 _("Peak"),
93                 _("RMS")
94         };
95
96         for (int i = 0; i < AudioPoint::COUNT; ++i) {
97                 _type_checkbox[i] = new wxCheckBox (this, wxID_ANY, types[i]);
98                 right->Add (_type_checkbox[i], 0, wxEXPAND | wxALL, 3);
99                 _type_checkbox[i]->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AudioDialog::type_clicked, this, _1));
100         }
101
102         {
103                 wxStaticText* m = new wxStaticText (this, wxID_ANY, _("Smoothing"));
104                 m->SetFont (subheading_font);
105                 right->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP, 16);
106         }
107
108         _smoothing = new wxSlider (this, wxID_ANY, AudioPlot::max_smoothing / 2, 1, AudioPlot::max_smoothing);
109         _smoothing->Bind (wxEVT_SCROLL_THUMBTRACK, boost::bind (&AudioDialog::smoothing_changed, this));
110         right->Add (_smoothing, 0, wxEXPAND);
111
112         lr_sizer->Add (right, 0, wxALL, 12);
113
114         overall_sizer->Add (lr_sizer);
115
116 #ifdef DCPOMATIC_LINUX
117         wxSizer* buttons = CreateSeparatedButtonSizer (wxCLOSE);
118         if (buttons) {
119                 overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
120         }
121 #endif
122
123         SetSizer (overall_sizer);
124         overall_sizer->Layout ();
125         overall_sizer->SetSizeHints (this);
126
127         _film_connection = film->ContentChanged.connect (boost::bind (&AudioDialog::content_changed, this, _2));
128         SetTitle (_("DCP-o-matic audio"));
129
130         if (content) {
131                 _playlist.reset (new Playlist ());
132                 const_pointer_cast<Playlist> (_playlist)->add (content);
133         } else {
134                 _playlist = film->playlist ();
135         }
136 }
137
138 void
139 AudioDialog::try_to_load_analysis ()
140 {
141         if (!IsShown ()) {
142                 return;
143         }
144
145         shared_ptr<const Film> film = _film.lock ();
146         DCPOMATIC_ASSERT (film);
147
148         boost::filesystem::path const path = film->audio_analysis_path (_playlist);
149         if (!boost::filesystem::exists (path)) {
150                 _plot->set_analysis (shared_ptr<AudioAnalysis> ());
151                 _analysis.reset ();
152                 JobManager::instance()->analyse_audio (film, _playlist, _analysis_finished_connection, bind (&AudioDialog::analysis_finished, this));
153                 return;
154         }
155
156         try {
157                 _analysis.reset (new AudioAnalysis (path));
158         } catch (xmlpp::exception& e) {
159                 /* Probably an old-style analysis file: recreate it */
160                 JobManager::instance()->analyse_audio (film, _playlist, _analysis_finished_connection, bind (&AudioDialog::analysis_finished, this));
161                 return;
162         }
163
164         _plot->set_analysis (_analysis);
165         _plot->set_gain_correction (_analysis->gain_correction (_playlist));
166         setup_statistics ();
167
168         /* Set up some defaults if no check boxes are checked */
169
170         int i = 0;
171         while (i < _channels && (!_channel_checkbox[i] || !_channel_checkbox[i]->GetValue ())) {
172                 ++i;
173         }
174
175         if (i == _channels) {
176                 /* Nothing checked; check mapped ones */
177
178                 list<int> mapped;
179                 shared_ptr<Content> content = _content.lock ();
180
181                 if (content) {
182                         mapped = content->audio->mapping().mapped_output_channels ();
183                 } else {
184                         mapped = film->mapped_audio_channels ();
185                 }
186
187                 BOOST_FOREACH (int i, mapped) {
188                         if (_channel_checkbox[i]) {
189                                 _channel_checkbox[i]->SetValue (true);
190                                 _plot->set_channel_visible (i, true);
191                         }
192                 }
193         }
194
195         i = 0;
196         while (i < AudioPoint::COUNT && !_type_checkbox[i]->GetValue ()) {
197                 i++;
198         }
199
200         if (i == AudioPoint::COUNT) {
201                 for (int i = 0; i < AudioPoint::COUNT; ++i) {
202                         _type_checkbox[i]->SetValue (true);
203                         _plot->set_type_visible (i, true);
204                 }
205         }
206
207         Refresh ();
208 }
209
210 void
211 AudioDialog::analysis_finished ()
212 {
213         shared_ptr<const Film> film = _film.lock ();
214         DCPOMATIC_ASSERT (film);
215
216         if (!boost::filesystem::exists (film->audio_analysis_path (_playlist))) {
217                 /* We analysed and still nothing showed up, so maybe it was cancelled or it failed.
218                    Give up.
219                 */
220                 _plot->set_message (_("Could not analyse audio."));
221                 return;
222         }
223
224         try_to_load_analysis ();
225 }
226
227 void
228 AudioDialog::channel_clicked (wxCommandEvent& ev)
229 {
230         int c = 0;
231         while (c < _channels && ev.GetEventObject() != _channel_checkbox[c]) {
232                 ++c;
233         }
234
235         DCPOMATIC_ASSERT (c < _channels);
236
237         _plot->set_channel_visible (c, _channel_checkbox[c]->GetValue ());
238 }
239
240 void
241 AudioDialog::content_changed (int p)
242 {
243         if (p == AudioContentProperty::STREAMS) {
244                 try_to_load_analysis ();
245         } else if (p == AudioContentProperty::GAIN) {
246                 if (_playlist->content().size() == 1 && _analysis) {
247                         /* We can use a short-cut to render the effect of this
248                            change, rather than recalculating everything.
249                         */
250                         _plot->set_gain_correction (_analysis->gain_correction (_playlist));
251                         setup_statistics ();
252                 } else {
253                         try_to_load_analysis ();
254                 }
255         }
256 }
257
258 void
259 AudioDialog::type_clicked (wxCommandEvent& ev)
260 {
261         int t = 0;
262         while (t < AudioPoint::COUNT && ev.GetEventObject() != _type_checkbox[t]) {
263                 ++t;
264         }
265
266         DCPOMATIC_ASSERT (t < AudioPoint::COUNT);
267
268         _plot->set_type_visible (t, _type_checkbox[t]->GetValue ());
269 }
270
271 void
272 AudioDialog::smoothing_changed ()
273 {
274         _plot->set_smoothing (_smoothing->GetValue ());
275 }
276
277 void
278 AudioDialog::setup_statistics ()
279 {
280         if (!_analysis) {
281                 return;
282         }
283
284         shared_ptr<Film> film = _film.lock ();
285         if (!film) {
286                 return;
287         }
288
289         if (static_cast<bool>(_analysis->sample_peak ())) {
290
291                 float const peak_dB = 20 * log10 (_analysis->sample_peak().get()) + _analysis->gain_correction (_playlist);
292
293                 _sample_peak->SetLabel (
294                         wxString::Format (
295                                 _("Sample peak is %.2fdB at %s"),
296                                 peak_dB,
297                                 time_to_timecode (_analysis->sample_peak_time().get(), film->video_frame_rate ()).data ()
298                                 )
299                         );
300
301                 if (peak_dB > -3) {
302                         _sample_peak->SetForegroundColour (wxColour (255, 0, 0));
303                 } else {
304                         _sample_peak->SetForegroundColour (wxColour (0, 0, 0));
305                 }
306         }
307
308         if (static_cast<bool>(_analysis->true_peak ())) {
309                 float const peak_dB = 20 * log10 (_analysis->true_peak().get()) + _analysis->gain_correction (_playlist);
310
311                 _true_peak->SetLabel (wxString::Format (_("True peak is %.2fdB"), peak_dB));
312
313                 if (peak_dB > -3) {
314                         _true_peak->SetForegroundColour (wxColour (255, 0, 0));
315                 } else {
316                         _true_peak->SetForegroundColour (wxColour (0, 0, 0));
317                 }
318         }
319
320         /* XXX: check whether it's ok to add dB gain to these quantities */
321
322         if (static_cast<bool>(_analysis->integrated_loudness ())) {
323                 _integrated_loudness->SetLabel (
324                         wxString::Format (
325                                 _("Integrated loudness %.2f LUFS"),
326                                 _analysis->integrated_loudness().get() + _analysis->gain_correction (_playlist)
327                                 )
328                         );
329         }
330
331         if (static_cast<bool>(_analysis->loudness_range ())) {
332                 _loudness_range->SetLabel (
333                         wxString::Format (
334                                 _("Loudness range %.2f LU"),
335                                 _analysis->loudness_range().get() + _analysis->gain_correction (_playlist)
336                                 )
337                         );
338         }
339 }
340
341 bool
342 AudioDialog::Show (bool show)
343 {
344         bool const r = wxDialog::Show (show);
345         try_to_load_analysis ();
346         return r;
347 }