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 const path = film->audio_analysis_path (_playlist);
137 if (!boost::filesystem::exists (path)) {
138 _plot->set_analysis (shared_ptr<AudioAnalysis> ());
140 JobManager::instance()->analyse_audio (film, _playlist, _analysis_finished_connection, bind (&AudioDialog::analysis_finished, this));
145 _analysis.reset (new AudioAnalysis (path));
146 } catch (xmlpp::exception& e) {
147 /* Probably an old-style analysis file: recreate it */
148 JobManager::instance()->analyse_audio (film, _playlist, _analysis_finished_connection, bind (&AudioDialog::analysis_finished, this));
152 _plot->set_analysis (_analysis);
153 _plot->set_gain_correction (gain_correction ());
156 /* Set up some defaults if no check boxes are checked */
159 while (i < MAX_DCP_AUDIO_CHANNELS && (!_channel_checkbox[i] || !_channel_checkbox[i]->GetValue ())) {
163 if (i == MAX_DCP_AUDIO_CHANNELS && _channel_checkbox[0]) {
164 _channel_checkbox[0]->SetValue (true);
165 _plot->set_channel_visible (0, true);
169 while (i < AudioPoint::COUNT && !_type_checkbox[i]->GetValue ()) {
173 if (i == AudioPoint::COUNT) {
174 for (int i = 0; i < AudioPoint::COUNT; ++i) {
175 _type_checkbox[i]->SetValue (true);
176 _plot->set_type_visible (i, true);
184 AudioDialog::analysis_finished ()
186 shared_ptr<const Film> film = _film.lock ();
187 DCPOMATIC_ASSERT (film);
189 if (!boost::filesystem::exists (film->audio_analysis_path (_playlist))) {
190 /* We analysed and still nothing showed up, so maybe it was cancelled or it failed.
193 _plot->set_message (_("Could not analyse audio."));
197 try_to_load_analysis ();
201 AudioDialog::channel_clicked (wxCommandEvent& ev)
204 while (c < MAX_DCP_AUDIO_CHANNELS && ev.GetEventObject() != _channel_checkbox[c]) {
208 DCPOMATIC_ASSERT (c < MAX_DCP_AUDIO_CHANNELS);
210 _plot->set_channel_visible (c, _channel_checkbox[c]->GetValue ());
214 AudioDialog::content_changed (int p)
216 if (p == AudioContentProperty::AUDIO_STREAMS) {
217 try_to_load_analysis ();
218 } else if (p == AudioContentProperty::AUDIO_GAIN) {
219 if (_playlist->content().size() == 1) {
220 /* We can use a short-cut to render the effect of this
221 change, rather than recalculating everything.
223 _plot->set_gain_correction (gain_correction ());
226 try_to_load_analysis ();
232 AudioDialog::type_clicked (wxCommandEvent& ev)
235 while (t < AudioPoint::COUNT && ev.GetEventObject() != _type_checkbox[t]) {
239 DCPOMATIC_ASSERT (t < AudioPoint::COUNT);
241 _plot->set_type_visible (t, _type_checkbox[t]->GetValue ());
245 AudioDialog::smoothing_changed ()
247 _plot->set_smoothing (_smoothing->GetValue ());
251 AudioDialog::setup_peak_time ()
253 if (!_analysis || !_analysis->peak ()) {
257 shared_ptr<Film> film = _film.lock ();
262 float const peak_dB = 20 * log10 (_analysis->peak().get()) + gain_correction ();
264 _peak_time->SetLabel (
266 _("Peak is %.2fdB at %s"),
268 time_to_timecode (_analysis->peak_time().get(), film->video_frame_rate ()).data ()
273 _peak_time->SetForegroundColour (wxColour (255, 0, 0));
275 _peak_time->SetForegroundColour (wxColour (0, 0, 0));
280 AudioDialog::Show (bool show)
282 bool const r = wxDialog::Show (show);
283 try_to_load_analysis ();
287 /** @return gain correction in dB required to be added to raw gain values to render
288 * the dialog correctly.
291 AudioDialog::gain_correction ()
293 if (_playlist->content().size() == 1 && _analysis->analysis_gain ()) {
294 /* In this case we know that the analysis was of a single piece of content and
295 we know that content's gain when the analysis was run. Hence we can work out
296 what correction is now needed to make it look `right'.
298 shared_ptr<const AudioContent> ac = dynamic_pointer_cast<const AudioContent> (_playlist->content().front ());
299 DCPOMATIC_ASSERT (ac);
300 return ac->audio_gain() - _analysis->analysis_gain().get ();