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 <libxml++/libxml++.h>
29 #include <boost/filesystem.hpp>
32 using boost::shared_ptr;
34 using boost::optional;
35 using boost::const_pointer_cast;
36 using boost::dynamic_pointer_cast;
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)
44 wxFont subheading_font (*wxNORMAL_FONT);
45 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
47 wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
48 wxBoxSizer* lr_sizer = new wxBoxSizer (wxHORIZONTAL);
50 wxBoxSizer* left = new wxBoxSizer (wxVERTICAL);
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);
57 lr_sizer->Add (left, 1, wxALL, 12);
59 wxBoxSizer* right = new wxBoxSizer (wxVERTICAL);
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);
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));
74 wxStaticText* m = new wxStaticText (this, wxID_ANY, _("Type"));
75 m->SetFont (subheading_font);
76 right->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP, 16);
79 wxString const types[] = {
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));
91 wxStaticText* m = new wxStaticText (this, wxID_ANY, _("Smoothing"));
92 m->SetFont (subheading_font);
93 right->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP, 16);
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);
100 lr_sizer->Add (right, 0, wxALL, 12);
102 overall_sizer->Add (lr_sizer);
104 #ifdef DCPOMATIC_LINUX
105 wxSizer* buttons = CreateSeparatedButtonSizer (wxCLOSE);
107 overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
111 SetSizer (overall_sizer);
112 overall_sizer->Layout ();
113 overall_sizer->SetSizeHints (this);
115 _film_connection = film->ContentChanged.connect (boost::bind (&AudioDialog::content_changed, this, _2));
116 SetTitle (_("DCP-o-matic audio"));
119 _playlist.reset (new Playlist ());
120 const_pointer_cast<Playlist> (_playlist)->add (content);
122 _playlist = film->playlist ();
127 AudioDialog::try_to_load_analysis ()
133 shared_ptr<const Film> film = _film.lock ();
134 DCPOMATIC_ASSERT (film);
136 boost::filesystem::path path = film->audio_analysis_path (_playlist);
138 if (!boost::filesystem::exists (path)) {
139 _plot->set_analysis (shared_ptr<AudioAnalysis> ());
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);
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);
157 _plot->set_analysis (_analysis);
158 _plot->set_gain_correction (gain_correction ());
161 /* Set up some defaults if no check boxes are checked */
164 while (i < MAX_DCP_AUDIO_CHANNELS && (!_channel_checkbox[i] || !_channel_checkbox[i]->GetValue ())) {
168 if (i == MAX_DCP_AUDIO_CHANNELS && _channel_checkbox[0]) {
169 _channel_checkbox[0]->SetValue (true);
170 _plot->set_channel_visible (0, true);
174 while (i < AudioPoint::COUNT && !_type_checkbox[i]->GetValue ()) {
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);
189 AudioDialog::analysis_finished ()
191 shared_ptr<const Film> film = _film.lock ();
192 DCPOMATIC_ASSERT (film);
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.
198 _plot->set_message (_("Could not analyse audio."));
202 try_to_load_analysis ();
206 AudioDialog::channel_clicked (wxCommandEvent& ev)
209 while (c < MAX_DCP_AUDIO_CHANNELS && ev.GetEventObject() != _channel_checkbox[c]) {
213 DCPOMATIC_ASSERT (c < MAX_DCP_AUDIO_CHANNELS);
215 _plot->set_channel_visible (c, _channel_checkbox[c]->GetValue ());
219 AudioDialog::content_changed (int p)
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.
228 _plot->set_gain_correction (gain_correction ());
231 try_to_load_analysis ();
237 AudioDialog::type_clicked (wxCommandEvent& ev)
240 while (t < AudioPoint::COUNT && ev.GetEventObject() != _type_checkbox[t]) {
244 DCPOMATIC_ASSERT (t < AudioPoint::COUNT);
246 _plot->set_type_visible (t, _type_checkbox[t]->GetValue ());
250 AudioDialog::smoothing_changed ()
252 _plot->set_smoothing (_smoothing->GetValue ());
256 AudioDialog::setup_peak_time ()
258 if (!_analysis || !_analysis->peak ()) {
262 shared_ptr<Film> film = _film.lock ();
267 float const peak_dB = 20 * log10 (_analysis->peak().get()) + gain_correction ();
269 _peak_time->SetLabel (
271 _("Peak is %.2fdB at %s"),
273 time_to_timecode (_analysis->peak_time().get(), film->video_frame_rate ()).data ()
278 _peak_time->SetForegroundColour (wxColour (255, 0, 0));
280 _peak_time->SetForegroundColour (wxColour (0, 0, 0));
285 AudioDialog::Show (bool show)
287 bool const r = wxDialog::Show (show);
288 try_to_load_analysis ();
292 /** @return gain correction in dB required to be added to raw gain values to render
293 * the dialog correctly.
296 AudioDialog::gain_correction ()
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'.
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 ();