Include tidying src/lib/a-j*.h
[dcpomatic.git] / src / wx / audio_dialog.cc
1 /*
2     Copyright (C) 2013-2015 Carl Hetherington <cth@carlh.net>
3
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.
8
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.
13
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.
17
18 */
19
20 #include "audio_dialog.h"
21 #include "audio_plot.h"
22 #include "wx_util.h"
23 #include "lib/audio_analysis.h"
24 #include "lib/film.h"
25 #include "lib/analyse_audio_job.h"
26 #include "lib/audio_content.h"
27 #include "lib/job_manager.h"
28 #include <libxml++/libxml++.h>
29 #include <boost/filesystem.hpp>
30
31 using std::cout;
32 using boost::shared_ptr;
33 using boost::bind;
34 using boost::optional;
35 using boost::const_pointer_cast;
36 using boost::dynamic_pointer_cast;
37
38 /** @param content Content to analyse, or 0 to analyse all of the film's audio */
39 AudioDialog::AudioDialog (wxWindow* parent, shared_ptr<Film> film, shared_ptr<AudioContent> content)
40         : wxDialog (parent, wxID_ANY, _("Audio"), wxDefaultPosition, wxSize (640, 512), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE)
41         , _film (film)
42         , _plot (0)
43 {
44         wxFont subheading_font (*wxNORMAL_FONT);
45         subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
46
47         wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
48         wxBoxSizer* lr_sizer = new wxBoxSizer (wxHORIZONTAL);
49
50         wxBoxSizer* left = new wxBoxSizer (wxVERTICAL);
51
52         _plot = new AudioPlot (this);
53         left->Add (_plot, 1, wxALL | wxEXPAND, 12);
54         _peak_time = new wxStaticText (this, wxID_ANY, wxT (""));
55         left->Add (_peak_time, 0, wxALL, 12);
56
57         lr_sizer->Add (left, 1, wxALL, 12);
58
59         wxBoxSizer* right = new wxBoxSizer (wxVERTICAL);
60
61         {
62                 wxStaticText* m = new wxStaticText (this, wxID_ANY, _("Channels"));
63                 m->SetFont (subheading_font);
64                 right->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM, 16);
65         }
66
67         for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
68                 _channel_checkbox[i] = new wxCheckBox (this, wxID_ANY, std_to_wx (audio_channel_name (i)));
69                 right->Add (_channel_checkbox[i], 0, wxEXPAND | wxALL, 3);
70                 _channel_checkbox[i]->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AudioDialog::channel_clicked, this, _1));
71         }
72
73         {
74                 wxStaticText* m = new wxStaticText (this, wxID_ANY, _("Type"));
75                 m->SetFont (subheading_font);
76                 right->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP, 16);
77         }
78
79         wxString const types[] = {
80                 _("Peak"),
81                 _("RMS")
82         };
83
84         for (int i = 0; i < AudioPoint::COUNT; ++i) {
85                 _type_checkbox[i] = new wxCheckBox (this, wxID_ANY, types[i]);
86                 right->Add (_type_checkbox[i], 0, wxEXPAND | wxALL, 3);
87                 _type_checkbox[i]->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AudioDialog::type_clicked, this, _1));
88         }
89
90         {
91                 wxStaticText* m = new wxStaticText (this, wxID_ANY, _("Smoothing"));
92                 m->SetFont (subheading_font);
93                 right->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP, 16);
94         }
95
96         _smoothing = new wxSlider (this, wxID_ANY, AudioPlot::max_smoothing / 2, 1, AudioPlot::max_smoothing);
97         _smoothing->Bind (wxEVT_SCROLL_THUMBTRACK, boost::bind (&AudioDialog::smoothing_changed, this));
98         right->Add (_smoothing, 0, wxEXPAND);
99
100         lr_sizer->Add (right, 0, wxALL, 12);
101
102         overall_sizer->Add (lr_sizer);
103
104 #ifdef DCPOMATIC_LINUX
105         wxSizer* buttons = CreateSeparatedButtonSizer (wxCLOSE);
106         if (buttons) {
107                 overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
108         }
109 #endif
110
111         SetSizer (overall_sizer);
112         overall_sizer->Layout ();
113         overall_sizer->SetSizeHints (this);
114
115         _film_connection = film->ContentChanged.connect (boost::bind (&AudioDialog::content_changed, this, _2));
116         SetTitle (_("DCP-o-matic audio"));
117
118         if (content) {
119                 _playlist.reset (new Playlist ());
120                 const_pointer_cast<Playlist> (_playlist)->add (content);
121         } else {
122                 _playlist = film->playlist ();
123         }
124 }
125
126 void
127 AudioDialog::try_to_load_analysis ()
128 {
129         if (!IsShown ()) {
130                 return;
131         }
132
133         shared_ptr<const Film> film = _film.lock ();
134         DCPOMATIC_ASSERT (film);
135
136         boost::filesystem::path path = film->audio_analysis_path (_playlist);
137
138         if (!boost::filesystem::exists (path)) {
139                 _plot->set_analysis (shared_ptr<AudioAnalysis> ());
140                 _analysis.reset ();
141                 shared_ptr<AnalyseAudioJob> job (new AnalyseAudioJob (film, _playlist));
142                 _analysis_finished_connection = job->Finished.connect (bind (&AudioDialog::analysis_finished, this));
143                 JobManager::instance()->add (job);
144                 return;
145         }
146
147         try {
148                 _analysis.reset (new AudioAnalysis (path));
149         } catch (xmlpp::exception& e) {
150                 /* Probably an old-style analysis file: recreate it */
151                 shared_ptr<AnalyseAudioJob> job (new AnalyseAudioJob (film, _playlist));
152                 _analysis_finished_connection = job->Finished.connect (bind (&AudioDialog::analysis_finished, this));
153                 JobManager::instance()->add (job);
154                 return;
155         }
156
157         _plot->set_analysis (_analysis);
158         _plot->set_gain_correction (gain_correction ());
159         setup_peak_time ();
160
161         /* Set up some defaults if no check boxes are checked */
162
163         int i = 0;
164         while (i < MAX_DCP_AUDIO_CHANNELS && (!_channel_checkbox[i] || !_channel_checkbox[i]->GetValue ())) {
165                 ++i;
166         }
167
168         if (i == MAX_DCP_AUDIO_CHANNELS && _channel_checkbox[0]) {
169                 _channel_checkbox[0]->SetValue (true);
170                 _plot->set_channel_visible (0, true);
171         }
172
173         i = 0;
174         while (i < AudioPoint::COUNT && !_type_checkbox[i]->GetValue ()) {
175                 i++;
176         }
177
178         if (i == AudioPoint::COUNT) {
179                 for (int i = 0; i < AudioPoint::COUNT; ++i) {
180                         _type_checkbox[i]->SetValue (true);
181                         _plot->set_type_visible (i, true);
182                 }
183         }
184
185         Refresh ();
186 }
187
188 void
189 AudioDialog::analysis_finished ()
190 {
191         shared_ptr<const Film> film = _film.lock ();
192         DCPOMATIC_ASSERT (film);
193
194         if (!boost::filesystem::exists (film->audio_analysis_path (_playlist))) {
195                 /* We analysed and still nothing showed up, so maybe it was cancelled or it failed.
196                    Give up.
197                 */
198                 _plot->set_message (_("Could not analyse audio."));
199                 return;
200         }
201
202         try_to_load_analysis ();
203 }
204
205 void
206 AudioDialog::channel_clicked (wxCommandEvent& ev)
207 {
208         int c = 0;
209         while (c < MAX_DCP_AUDIO_CHANNELS && ev.GetEventObject() != _channel_checkbox[c]) {
210                 ++c;
211         }
212
213         DCPOMATIC_ASSERT (c < MAX_DCP_AUDIO_CHANNELS);
214
215         _plot->set_channel_visible (c, _channel_checkbox[c]->GetValue ());
216 }
217
218 void
219 AudioDialog::content_changed (int p)
220 {
221         if (p == AudioContentProperty::AUDIO_STREAMS) {
222                 try_to_load_analysis ();
223         } else if (p == AudioContentProperty::AUDIO_GAIN) {
224                 if (_playlist->content().size() == 1) {
225                         /* We can use a short-cut to render the effect of this
226                            change, rather than recalculating everything.
227                         */
228                         _plot->set_gain_correction (gain_correction ());
229                         setup_peak_time ();
230                 } else {
231                         try_to_load_analysis ();
232                 }
233         }
234 }
235
236 void
237 AudioDialog::type_clicked (wxCommandEvent& ev)
238 {
239         int t = 0;
240         while (t < AudioPoint::COUNT && ev.GetEventObject() != _type_checkbox[t]) {
241                 ++t;
242         }
243
244         DCPOMATIC_ASSERT (t < AudioPoint::COUNT);
245
246         _plot->set_type_visible (t, _type_checkbox[t]->GetValue ());
247 }
248
249 void
250 AudioDialog::smoothing_changed ()
251 {
252         _plot->set_smoothing (_smoothing->GetValue ());
253 }
254
255 void
256 AudioDialog::setup_peak_time ()
257 {
258         if (!_analysis || !_analysis->peak ()) {
259                 return;
260         }
261
262         shared_ptr<Film> film = _film.lock ();
263         if (!film) {
264                 return;
265         }
266
267         float const peak_dB = 20 * log10 (_analysis->peak().get()) + gain_correction ();
268
269         _peak_time->SetLabel (
270                 wxString::Format (
271                         _("Peak is %.2fdB at %s"),
272                         peak_dB,
273                         time_to_timecode (_analysis->peak_time().get(), film->video_frame_rate ()).data ()
274                         )
275                 );
276
277         if (peak_dB > -3) {
278                 _peak_time->SetForegroundColour (wxColour (255, 0, 0));
279         } else {
280                 _peak_time->SetForegroundColour (wxColour (0, 0, 0));
281         }
282 }
283
284 bool
285 AudioDialog::Show (bool show)
286 {
287         bool const r = wxDialog::Show (show);
288         try_to_load_analysis ();
289         return r;
290 }
291
292 /** @return gain correction in dB required to be added to raw gain values to render
293  *  the dialog correctly.
294  */
295 float
296 AudioDialog::gain_correction ()
297 {
298         if (_playlist->content().size() == 1 && _analysis->analysis_gain ()) {
299                 /* In this case we know that the analysis was of a single piece of content and
300                    we know that content's gain when the analysis was run.  Hence we can work out
301                    what correction is now needed to make it look `right'.
302                 */
303                 shared_ptr<const AudioContent> ac = dynamic_pointer_cast<const AudioContent> (_playlist->content().front ());
304                 DCPOMATIC_ASSERT (ac);
305                 return ac->audio_gain() - _analysis->analysis_gain().get ();
306         }
307
308         return 0.0f;
309 }