Merge branch 'master' into plot-audio
authorCarl Hetherington <cth@carlh.net>
Mon, 25 Feb 2013 08:00:31 +0000 (08:00 +0000)
committerCarl Hetherington <cth@carlh.net>
Mon, 25 Feb 2013 08:00:31 +0000 (08:00 +0000)
19 files changed:
src/lib/analyse_audio_job.cc [new file with mode: 0644]
src/lib/analyse_audio_job.h [new file with mode: 0644]
src/lib/audio_analysis.cc [new file with mode: 0644]
src/lib/audio_analysis.h [new file with mode: 0644]
src/lib/ffmpeg_decoder.cc
src/lib/film.cc
src/lib/film.h
src/lib/options.h
src/lib/util.cc
src/lib/util.h
src/lib/wscript
src/tools/dvdomatic.cc
src/wx/audio_dialog.cc [new file with mode: 0644]
src/wx/audio_dialog.h [new file with mode: 0644]
src/wx/audio_plot.cc [new file with mode: 0644]
src/wx/audio_plot.h [new file with mode: 0644]
src/wx/film_editor.cc
src/wx/film_editor.h
src/wx/wscript

diff --git a/src/lib/analyse_audio_job.cc b/src/lib/analyse_audio_job.cc
new file mode 100644 (file)
index 0000000..fb5f286
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "audio_analysis.h"
+#include "analyse_audio_job.h"
+#include "compose.hpp"
+#include "film.h"
+#include "options.h"
+#include "decoder_factory.h"
+#include "audio_decoder.h"
+
+using std::string;
+using std::max;
+using std::cout;
+using boost::shared_ptr;
+
+int const AnalyseAudioJob::_num_points = 128;
+
+AnalyseAudioJob::AnalyseAudioJob (shared_ptr<Film> f)
+       : Job (f)
+       , _done (0)
+       , _samples_per_point (1)
+{
+
+}
+
+string
+AnalyseAudioJob::name () const
+{
+       return String::compose ("Analyse audio of %1", _film->name());
+}
+
+void
+AnalyseAudioJob::run ()
+{
+       if (!_film->audio_stream () || !_film->length()) {
+               set_progress (1);
+               set_state (FINISHED_ERROR);
+               return;
+       }
+               
+       DecodeOptions options;
+       options.decode_video = false;
+
+       Decoders decoders = decoder_factory (_film, options);
+       assert (decoders.audio);
+       
+       decoders.audio->set_audio_stream (_film->audio_stream ());
+       decoders.audio->Audio.connect (bind (&AnalyseAudioJob::audio, this, _1));
+
+       int64_t total_audio_frames = video_frames_to_audio_frames (_film->length().get(), _film->audio_stream()->sample_rate(), _film->frames_per_second());
+       _samples_per_point = total_audio_frames / _num_points;
+
+       _current.resize (_film->audio_stream()->channels ());
+       _analysis.reset (new AudioAnalysis (_film->audio_stream()->channels()));
+                        
+       while (!decoders.audio->pass()) {
+               set_progress (float (_done) / total_audio_frames);
+       }
+
+       _analysis->write (_film->audio_analysis_path ());
+       
+       set_progress (1);
+       set_state (FINISHED_OK);
+}
+
+void
+AnalyseAudioJob::audio (shared_ptr<AudioBuffers> b)
+{
+       for (int i = 0; i < b->frames(); ++i) {
+               for (int j = 0; j < b->channels(); ++j) {
+                       float s = b->data(j)[i];
+                       if (fabsf (s) < 10e-7) {
+                               /* stringstream can't serialise and recover inf or -inf, so prevent such
+                                  values by replacing with this (140dB down) */
+                               s = 10e-7;
+                       }
+                       _current[j][AudioPoint::RMS] += pow (s, 2);
+                       _current[j][AudioPoint::PEAK] = max (_current[j][AudioPoint::PEAK], fabsf (s));
+
+                       if ((_done % _samples_per_point) == 0) {
+                               _current[j][AudioPoint::RMS] = 20 * log10 (sqrt (_current[j][AudioPoint::RMS] / _samples_per_point));
+                               _current[j][AudioPoint::PEAK] = 20 * log10 (_current[j][AudioPoint::PEAK]);
+                               _analysis->add_point (j, _current[j]);
+                               
+                               _current[j] = AudioPoint ();
+                       }
+               }
+
+               ++_done;
+       }
+}
+
diff --git a/src/lib/analyse_audio_job.h b/src/lib/analyse_audio_job.h
new file mode 100644 (file)
index 0000000..dc1e073
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "job.h"
+#include "audio_analysis.h"
+
+class AudioBuffers;
+
+class AnalyseAudioJob : public Job
+{
+public:
+       AnalyseAudioJob (boost::shared_ptr<Film> f);
+
+       std::string name () const;
+       void run ();
+
+private:
+       void audio (boost::shared_ptr<AudioBuffers>);
+
+       int64_t _done;
+       int64_t _samples_per_point;
+       std::vector<AudioPoint> _current;
+
+       boost::shared_ptr<AudioAnalysis> _analysis;
+
+       static const int _num_points;
+};
+
diff --git a/src/lib/audio_analysis.cc b/src/lib/audio_analysis.cc
new file mode 100644 (file)
index 0000000..39c1ba2
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <stdint.h>
+#include <cmath>
+#include <cassert>
+#include <fstream>
+#include "audio_analysis.h"
+
+using std::ostream;
+using std::istream;
+using std::string;
+using std::ofstream;
+using std::ifstream;
+using std::vector;
+using std::cout;
+
+AudioPoint::AudioPoint ()
+{
+       for (int i = 0; i < COUNT; ++i) {
+               _data[i] = 0;
+       }
+}
+
+AudioPoint::AudioPoint (istream& s)
+{
+       for (int i = 0; i < COUNT; ++i) {
+               s >> _data[i];
+       }
+}
+
+void
+AudioPoint::write (ostream& s) const
+{
+       for (int i = 0; i < COUNT; ++i) {
+               s << _data[i] << "\n";
+       }
+}
+       
+
+AudioAnalysis::AudioAnalysis (int channels)
+{
+       _data.resize (channels);
+}
+
+AudioAnalysis::AudioAnalysis (string filename)
+{
+       ifstream f (filename.c_str ());
+
+       int channels;
+       f >> channels;
+       _data.resize (channels);
+
+       for (int i = 0; i < channels; ++i) {
+               int points;
+               f >> points;
+               for (int j = 0; j < points; ++j) {
+                       _data[i].push_back (AudioPoint (f));
+               }
+       }
+}
+
+void
+AudioAnalysis::add_point (int c, AudioPoint const & p)
+{
+       assert (c < int (_data.size ()));
+       _data[c].push_back (p);
+}
+
+AudioPoint
+AudioAnalysis::get_point (int c, int p) const
+{
+       assert (c < int (_data.size ()));
+       assert (p < int (_data[c].size ()));
+       return _data[c][p];
+}
+
+int
+AudioAnalysis::points (int c) const
+{
+       assert (c < int (_data.size ()));
+       return _data[c].size ();
+}
+
+void
+AudioAnalysis::write (string filename)
+{
+       ofstream f (filename.c_str ());
+       f << _data.size() << "\n";
+       for (vector<vector<AudioPoint> >::iterator i = _data.begin(); i != _data.end(); ++i) {
+               f << i->size () << "\n";
+               for (vector<AudioPoint>::iterator j = i->begin(); j != i->end(); ++j) {
+                       j->write (f);
+               }
+       }
+}
diff --git a/src/lib/audio_analysis.h b/src/lib/audio_analysis.h
new file mode 100644 (file)
index 0000000..c26c058
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DVDOMATIC_AUDIO_ANALYSIS_H
+#define DVDOMATIC_AUDIO_ANALYSIS_H
+
+#include <iostream>
+#include <vector>
+
+class AudioPoint
+{
+public:
+       enum Type {
+               PEAK,
+               RMS,
+               COUNT
+       };
+
+       AudioPoint ();
+       AudioPoint (std::istream &);
+
+       void write (std::ostream &) const;
+       
+       float& operator[] (int t) {
+               return _data[t];
+       }
+
+private:
+       float _data[COUNT];
+};
+
+class AudioAnalysis
+{
+public:
+       AudioAnalysis (int c);
+       AudioAnalysis (std::string);
+
+       void add_point (int c, AudioPoint const & p);
+       
+       AudioPoint get_point (int c, int p) const;
+       int points (int c) const;
+
+       void write (std::string);
+
+
+private:
+       std::vector<std::vector<AudioPoint> > _data;
+};
+
+#endif
index d4ed76e371ec414109bd36bc17a487ea6b9c8940..148764162c20fe70bfdd9d4be2e7ed96faaf8d57 100644 (file)
@@ -197,7 +197,7 @@ FFmpegDecoder::setup_audio ()
 void
 FFmpegDecoder::setup_subtitle ()
 {
-       if (!_subtitle_stream) {
+       if (!_subtitle_stream || _subtitle_stream->id() >= _format_context->nb_streams) {
                return;
        }
 
@@ -236,8 +236,10 @@ FFmpegDecoder::pass ()
 
                int frame_finished;
 
-               while (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
-                       filter_and_emit_video (_frame);
+               if (_opt.decode_video) {
+                       while (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
+                               filter_and_emit_video (_frame);
+                       }
                }
 
                if (_audio_stream && _opt.decode_audio) {
@@ -258,7 +260,7 @@ FFmpegDecoder::pass ()
 
        shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
 
-       if (_packet.stream_index == _video_stream) {
+       if (_packet.stream_index == _video_stream && _opt.decode_video) {
 
                int frame_finished;
                int const r = avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet);
@@ -288,9 +290,9 @@ FFmpegDecoder::pass ()
                           was before this packet.  Until then audio is thrown away.
                        */
                                
-                       if (_first_video && _first_video.get() <= source_pts_seconds) {
+                       if ((_first_video && _first_video.get() <= source_pts_seconds) || !_opt.decode_video) {
 
-                               if (!_first_audio) {
+                               if (!_first_audio && _opt.decode_video) {
                                        _first_audio = source_pts_seconds;
                                        
                                        /* This is our first audio frame, and if we've arrived here we must have had our
index 1cf161259a92e6f5e66b8f4128af396e2a7f9886..f36614689c64a23d9bc65f09083fd2a2a91f27ba 100644 (file)
@@ -51,6 +51,7 @@
 #include "video_decoder.h"
 #include "audio_decoder.h"
 #include "external_audio_decoder.h"
+#include "analyse_audio_job.h"
 
 using std::string;
 using std::stringstream;
@@ -237,6 +238,15 @@ Film::video_mxf_filename () const
        return video_state_identifier() + ".mxf";
 }
 
+string
+Film::audio_analysis_path () const
+{
+       boost::filesystem::path p;
+       p /= "analysis";
+       p /= content_digest();
+       return file (p.string ());
+}
+
 /** Add suitable Jobs to the JobManager to create a DCP for this Film */
 void
 Film::make_dcp ()
@@ -303,6 +313,19 @@ Film::make_dcp ()
        }
 }
 
+/** Start a job to analyse the audio of our content file */
+void
+Film::analyse_audio ()
+{
+       if (_analyse_audio_job) {
+               return;
+       }
+
+       _analyse_audio_job.reset (new AnalyseAudioJob (shared_from_this()));
+       _analyse_audio_job->Finished.connect (bind (&Film::analyse_audio_finished, this));
+       JobManager::instance()->add (_analyse_audio_job);
+}
+
 /** Start a job to examine our content file */
 void
 Film::examine_content ()
@@ -316,6 +339,12 @@ Film::examine_content ()
        JobManager::instance()->add (_examine_content_job);
 }
 
+void
+Film::analyse_audio_finished ()
+{
+       _analyse_audio_job.reset ();
+}
+
 void
 Film::examine_content_finished ()
 {
@@ -870,6 +899,13 @@ Film::set_content (string c)
                        set_content_audio_streams (d.audio->audio_streams ());
                }
 
+               {
+                       boost::mutex::scoped_lock lm (_state_mutex);
+                       _content = c;
+               }
+               
+               signal_changed (CONTENT);
+               
                /* Start off with the first audio and subtitle streams */
                if (d.audio && !d.audio->audio_streams().empty()) {
                        set_content_audio_stream (d.audio->audio_streams().front());
@@ -879,13 +915,6 @@ Film::set_content (string c)
                        set_subtitle_stream (d.video->subtitle_streams().front());
                }
                
-               {
-                       boost::mutex::scoped_lock lm (_state_mutex);
-                       _content = c;
-               }
-               
-               signal_changed (CONTENT);
-               
                examine_content ();
 
        } catch (...) {
index 04a483998fe20eee2e26f3047d79471c07e7e7b4..c268d3eac9a01498505d50e6369089a19caeb279 100644 (file)
@@ -45,6 +45,7 @@ class Job;
 class Filter;
 class Log;
 class ExamineContentJob;
+class AnalyseAudioJob;
 class ExternalAudioStream;
 
 /** @class Film
@@ -65,8 +66,10 @@ public:
        std::string info_path (int f) const;
        std::string video_mxf_dir () const;
        std::string video_mxf_filename () const;
+       std::string audio_analysis_path () const;
 
        void examine_content ();
+       void analyse_audio ();
        void send_dcp_to_tms ();
 
        void make_dcp ();
@@ -374,9 +377,12 @@ private:
 
        /** Any running ExamineContentJob, or 0 */
        boost::shared_ptr<ExamineContentJob> _examine_content_job;
+       /** Any running AnalyseAudioJob, or 0 */
+       boost::shared_ptr<AnalyseAudioJob> _analyse_audio_job;
 
        void signal_changed (Property);
        void examine_content_finished ();
+       void analyse_audio_finished ();
        std::string video_state_identifier () const;
 
        /** Complete path to directory containing the film metadata;
index 2cd7dffdedaff57a9ba8bc107045dc710d0bbb03..0d2c07fd5674117d7afff91179b25885fbe9388a 100644 (file)
@@ -28,11 +28,13 @@ class DecodeOptions
 {
 public:
        DecodeOptions ()
-               : decode_audio (true)
+               : decode_video (true)
+               , decode_audio (true)
                , decode_subtitles (false)
                , video_sync (true)
        {}
-       
+
+       bool decode_video;
        bool decode_audio;
        bool decode_subtitles;
        bool video_sync;
index 4ee304600f0d368cbba0acd367b7cc0eaa03c60b..f807bf32936a04b5100ff906d85b1c79973c92d3 100644 (file)
@@ -898,3 +898,23 @@ cpu_info ()
 
        return info;
 }
+
+string
+audio_channel_name (int c)
+{
+       assert (MAX_AUDIO_CHANNELS == 6);
+
+       /* TRANSLATORS: these are the names of audio channels; Lfe (sub) is the low-frequency
+          enhancement channel (sub-woofer)./
+       */
+       string const channels[] = {
+               "Left",
+               "Right",
+               "Centre",
+               "Lfe (sub)",
+               "Left surround",
+               "Right surround",
+       };
+
+       return channels[c];
+}
index 87735ea8e82b8bc27e77b17515da26eedc8155ee..b76aead41fcc127305a2ba8bf9a7f3a8bda20d13 100644 (file)
@@ -57,6 +57,7 @@ extern std::vector<std::string> split_at_spaces_considering_quotes (std::string)
 extern std::string md5_digest (std::string);
 extern std::string md5_digest (void const *, int);
 extern void ensure_ui_thread ();
+extern std::string audio_channel_name (int);
 
 typedef int SourceFrame;
 
index eee04190c1119c19510cdd6f551d712dbe4c0d6c..c2b46112cbe0de658ed5c9906661378bfcffd2fe 100644 (file)
@@ -16,6 +16,8 @@ def build(bld):
     obj.source = """
                 ab_transcode_job.cc
                 ab_transcoder.cc
+                 analyse_audio_job.cc
+                 audio_analysis.cc
                  audio_decoder.cc
                  audio_source.cc
                 config.cc
index 1b76132f604931f4bada50fc7c23d50eb74048ba..4f380b5ad87e6cc7e218415c03b91930c06691b7 100644 (file)
@@ -145,7 +145,7 @@ enum {
        ID_jobs_make_dcp,
        ID_jobs_send_dcp_to_tms,
        ID_jobs_show_dcp,
-       ID_jobs_examine_content,
+       ID_jobs_analyse_audio,
        ID_help_about
 };
 
@@ -170,7 +170,7 @@ setup_menu (wxMenuBar* m)
        add_item (jobs_menu, "&Send DCP to TMS", ID_jobs_send_dcp_to_tms, NEEDS_FILM);
        add_item (jobs_menu, "S&how DCP", ID_jobs_show_dcp, NEEDS_FILM);
        jobs_menu->AppendSeparator ();
-       add_item (jobs_menu, "&Examine content", ID_jobs_examine_content, NEEDS_FILM);
+       add_item (jobs_menu, "&Analyse audio", ID_jobs_analyse_audio, NEEDS_FILM);
 
        wxMenu* help = new wxMenu;
        add_item (help, "About", ID_help_about, ALWAYS);
@@ -207,7 +207,7 @@ public:
                Connect (ID_jobs_make_dcp, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_make_dcp));
                Connect (ID_jobs_send_dcp_to_tms, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_send_dcp_to_tms));
                Connect (ID_jobs_show_dcp, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_show_dcp));
-               Connect (ID_jobs_examine_content, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_examine_content));
+               Connect (ID_jobs_analyse_audio, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_analyse_audio));
                Connect (ID_help_about, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::help_about));
 
                Connect (wxID_ANY, wxEVT_MENU_OPEN, wxMenuEventHandler (Frame::menu_opened));
@@ -386,10 +386,10 @@ private:
                }
 #endif         
        }
-       
-       void jobs_examine_content (wxCommandEvent &)
+
+       void jobs_analyse_audio (wxCommandEvent &)
        {
-               film->examine_content ();
+               film->analyse_audio ();
        }
        
        void help_about (wxCommandEvent &)
diff --git a/src/wx/audio_dialog.cc b/src/wx/audio_dialog.cc
new file mode 100644 (file)
index 0000000..32864ca
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "audio_dialog.h"
+#include "audio_plot.h"
+#include "audio_analysis.h"
+#include "film.h"
+#include "wx_util.h"
+
+using boost::shared_ptr;
+
+AudioDialog::AudioDialog (wxWindow* parent, boost::shared_ptr<Film> film)
+       : wxDialog (parent, wxID_ANY, _("Audio"), wxDefaultPosition, wxSize (640, 512), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
+       , _plot (0)
+{
+       wxBoxSizer* sizer = new wxBoxSizer (wxHORIZONTAL);
+
+       _plot = new AudioPlot (this);
+       sizer->Add (_plot, 1);
+
+       wxFlexGridSizer* table = new wxFlexGridSizer (2, 6, 6);
+
+       add_label_to_sizer (table, this, _("Channel"));
+       _channel = new wxChoice (this, wxID_ANY);
+       table->Add (_channel, 1, wxEXPAND | wxALL, 6);
+
+       sizer->Add (table);
+
+       set_film (film);
+
+       SetSizer (sizer);
+       sizer->Layout ();
+       sizer->SetSizeHints (this);
+
+       _channel->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (AudioDialog::channel_changed), 0, this);
+}
+
+void
+AudioDialog::set_film (boost::shared_ptr<Film> f)
+{
+       _film_connection.disconnect ();
+       _film = f;
+       
+       shared_ptr<AudioAnalysis> a;
+
+       try {
+               a.reset (new AudioAnalysis (f->audio_analysis_path ()));
+       } catch (...) {
+
+       }
+               
+       _plot->set_analysis (a);
+
+       _channel->Clear ();
+       for (int i = 0; i < f->audio_stream()->channels(); ++i) {
+               _channel->Append (audio_channel_name (i));
+       }
+
+       _channel->SetSelection (0);
+
+       _film_connection = f->Changed.connect (bind (&AudioDialog::film_changed, this, _1));
+}
+
+void
+AudioDialog::channel_changed (wxCommandEvent &)
+{
+       _plot->set_channel (_channel->GetSelection ());
+}
+
+void
+AudioDialog::film_changed (Film::Property p)
+{
+       if (p == Film::AUDIO_GAIN) {
+               _plot->set_gain (_film->audio_gain ());
+       }
+}
diff --git a/src/wx/audio_dialog.h b/src/wx/audio_dialog.h
new file mode 100644 (file)
index 0000000..968fd0a
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/shared_ptr.hpp>
+#include <boost/signals2.hpp>
+#include <wx/wx.h>
+#include "lib/film.h"
+
+class AudioPlot;
+class Film;
+
+class AudioDialog : public wxDialog
+{
+public:
+       AudioDialog (wxWindow *, boost::shared_ptr<Film>);
+
+       void set_film (boost::shared_ptr<Film>);
+
+private:
+       void film_changed (Film::Property);
+       void channel_changed (wxCommandEvent &);
+
+       boost::shared_ptr<Film> _film;
+       AudioPlot* _plot;
+       wxChoice* _channel;
+       boost::signals2::scoped_connection _film_connection;
+};
diff --git a/src/wx/audio_plot.cc b/src/wx/audio_plot.cc
new file mode 100644 (file)
index 0000000..1ad07fc
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <iostream>
+#include <boost/bind.hpp>
+#include <wx/graphics.h>
+#include "audio_plot.h"
+#include "lib/decoder_factory.h"
+#include "lib/audio_decoder.h"
+#include "lib/audio_analysis.h"
+#include "wx/wx_util.h"
+
+using std::cout;
+using std::vector;
+using std::max;
+using std::min;
+using boost::bind;
+using boost::shared_ptr;
+
+int const AudioPlot::_minimum = -90;
+
+AudioPlot::AudioPlot (wxWindow* parent)
+       : wxPanel (parent)
+       , _channel (0)
+       , _gain (0)
+{
+       Connect (wxID_ANY, wxEVT_PAINT, wxPaintEventHandler (AudioPlot::paint), 0, this);
+
+       SetMinSize (wxSize (640, 512));
+}
+
+void
+AudioPlot::set_analysis (shared_ptr<AudioAnalysis> a)
+{
+       _analysis = a;
+       _channel = 0;
+       Refresh ();
+}
+
+void
+AudioPlot::set_channel (int c)
+{
+       _channel = c;
+       Refresh ();
+}
+
+void
+AudioPlot::paint (wxPaintEvent &)
+{
+       wxPaintDC dc (this);
+
+       if (!_analysis) {
+               return;
+       }
+       
+       wxGraphicsContext* gc = wxGraphicsContext::Create (dc);
+       if (!gc) {
+               return;
+       }
+
+       int const width = GetSize().GetWidth();
+       float const xs = width / float (_analysis->points (_channel));
+       int const height = GetSize().GetHeight ();
+       float const ys = height / -_minimum;
+
+       wxGraphicsPath grid = gc->CreatePath ();
+       gc->SetFont (gc->CreateFont (*wxSMALL_FONT));
+       for (int i = _minimum; i <= 0; i += 10) {
+               int const y = height - (i - _minimum) * ys;
+               grid.MoveToPoint (0, y);
+               grid.AddLineToPoint (width, y);
+               gc->DrawText (std_to_wx (String::compose ("%1dB", i)), width - 32, y - 12);
+       }
+       gc->SetPen (*wxLIGHT_GREY_PEN);
+       gc->StrokePath (grid);
+
+       wxGraphicsPath path[AudioPoint::COUNT];
+
+       for (int i = 0; i < AudioPoint::COUNT; ++i) {
+               path[i] = gc->CreatePath ();
+               path[i].MoveToPoint (0, height - (max (_analysis->get_point(_channel, 0)[i], float (_minimum)) - _minimum + _gain) * ys);
+       }
+
+       for (int i = 0; i < _analysis->points(_channel); ++i) {
+               for (int j = 0; j < AudioPoint::COUNT; ++j) {
+                       path[j].AddLineToPoint (i * xs, height - (max (_analysis->get_point(_channel, i)[j], float (_minimum)) - _minimum + _gain) * ys);
+               }
+       }
+
+       gc->SetPen (*wxBLUE_PEN);
+       gc->StrokePath (path[AudioPoint::RMS]);
+
+       gc->SetPen (*wxRED_PEN);
+       gc->StrokePath (path[AudioPoint::PEAK]);
+
+       delete gc;
+}
+
+void
+AudioPlot::set_gain (float g)
+{
+       _gain = g;
+       Refresh ();
+}
diff --git a/src/wx/audio_plot.h b/src/wx/audio_plot.h
new file mode 100644 (file)
index 0000000..b2ae139
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <vector>
+#include <boost/shared_ptr.hpp>
+#include <wx/wx.h>
+
+class AudioAnalysis;
+
+class AudioPlot : public wxPanel
+{
+public:
+       AudioPlot (wxWindow *);
+
+       void set_analysis (boost::shared_ptr<AudioAnalysis>);
+       void set_channel (int c);
+       void set_gain (float);
+
+private:
+       void paint (wxPaintEvent &);
+
+       boost::shared_ptr<AudioAnalysis> _analysis;
+       int _channel;
+       /** gain to apply in dB */
+       float _gain;
+
+       static const int _minimum;
+};
index 68291a812df03d5414d76dd258caae838a736940..725f2d1b3c517f2dd4178fee92a20cc380798514 100644 (file)
@@ -45,6 +45,7 @@
 #include "sound_processor.h"
 #include "dci_metadata_dialog.h"
 #include "scaler.h"
+#include "audio_dialog.h"
 
 using std::string;
 using std::cout;
@@ -62,6 +63,7 @@ FilmEditor::FilmEditor (shared_ptr<Film> f, wxWindow* parent)
        : wxPanel (parent)
        , _film (f)
        , _generally_sensitive (true)
+       , _audio_dialog (0)
 {
        wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
        SetSizer (s);
@@ -204,6 +206,7 @@ FilmEditor::connect_to_widgets ()
        _audio_gain_calculate_button->Connect (
                wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::audio_gain_calculate_button_clicked), 0, this
                );
+       _show_audio->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::show_audio_clicked), 0, this);
        _audio_delay->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::audio_delay_changed), 0, this);
        _use_content_audio->Connect (wxID_ANY, wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler (FilmEditor::use_audio_changed), 0, this);
        _use_external_audio->Connect (wxID_ANY, wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler (FilmEditor::use_audio_changed), 0, this);
@@ -303,6 +306,10 @@ FilmEditor::make_audio_panel ()
        wxFlexGridSizer* grid = new wxFlexGridSizer (2, 4, 4);
        _audio_sizer->Add (grid, 0, wxALL, 8);
 
+       _show_audio = new wxButton (_audio_panel, wxID_ANY, _("Show Audio..."));
+       grid->Add (_show_audio, 1);
+       grid->AddSpacer (0);
+
        {
                video_control (add_label_to_sizer (grid, _audio_panel, _("Audio Gain")));
                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
@@ -340,22 +347,8 @@ FilmEditor::make_audio_panel ()
        grid->Add (_use_external_audio);
        grid->AddSpacer (0);
 
-       assert (MAX_AUDIO_CHANNELS == 6);
-
-       /* TRANSLATORS: these are the names of audio channels; Lfe (sub) is the low-frequency
-          enhancement channel (sub-woofer)./
-       */
-       wxString const channels[] = {
-               _("Left"),
-               _("Right"),
-               _("Centre"),
-               _("Lfe (sub)"),
-               _("Left surround"),
-               _("Right surround"),
-       };
-
        for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
-               add_label_to_sizer (grid, _audio_panel, channels[i]);
+               add_label_to_sizer (grid, _audio_panel, audio_channel_name (i));
                _external_audio[i] = new wxFilePickerCtrl (_audio_panel, wxID_ANY, wxT (""), _("Select Audio File"), wxT ("*.wav"));
                grid->Add (_external_audio[i], 1, wxEXPAND);
        }
@@ -756,6 +749,10 @@ FilmEditor::set_film (shared_ptr<Film> f)
        } else {
                FileChanged ("");
        }
+
+       if (_audio_dialog) {
+               _audio_dialog->set_film (_film);
+       }
        
        film_changed (Film::NAME);
        film_changed (Film::USE_DCI_NAME);
@@ -817,6 +814,7 @@ FilmEditor::set_things_sensitive (bool s)
        _j2k_bandwidth->Enable (s);
        _audio_gain->Enable (s);
        _audio_gain_calculate_button->Enable (s);
+       _show_audio->Enable (s);
        _audio_delay->Enable (s);
        _still_duration->Enable (s);
 
@@ -1175,3 +1173,15 @@ FilmEditor::setup_dcp_name ()
                _dcp_name->SetLabel (std_to_wx (s));
        }
 }
+
+void
+FilmEditor::show_audio_clicked (wxCommandEvent &)
+{
+       if (_audio_dialog) {
+               _audio_dialog->Destroy ();
+               _audio_dialog = 0;
+       }
+       
+       _audio_dialog = new AudioDialog (this, _film);
+       _audio_dialog->Show ();
+}
index 8cb90b481dd7523578f57731ecbc044ff3d96a85..2af747bb0447f35e2d4e666626f5f8c4b37173f1 100644 (file)
@@ -29,8 +29,8 @@
 #include "lib/film.h"
 
 class wxNotebook;
-
 class Film;
+class AudioDialog;
 
 /** @class FilmEditor
  *  @brief A wx widget to edit a film's metadata, and perform various functions.
@@ -70,6 +70,7 @@ private:
        void scaler_changed (wxCommandEvent &);
        void audio_gain_changed (wxCommandEvent &);
        void audio_gain_calculate_button_clicked (wxCommandEvent &);
+       void show_audio_clicked (wxCommandEvent &);
        void audio_delay_changed (wxCommandEvent &);
        void with_subtitles_toggled (wxCommandEvent &);
        void subtitle_offset_changed (wxCommandEvent &);
@@ -145,6 +146,7 @@ private:
        wxSpinCtrl* _audio_gain;
        /** A button to open the gain calculation dialogue */
        wxButton* _audio_gain_calculate_button;
+       wxButton* _show_audio;
        /** The Film's audio delay */
        wxSpinCtrl* _audio_delay;
        wxCheckBox* _with_subtitles;
@@ -177,4 +179,5 @@ private:
        std::vector<Format const *> _formats;
 
        bool _generally_sensitive;
+       AudioDialog* _audio_dialog;
 };
index d844b1f1b7a10e13e0810944295a4fc8898634c8..3fa40f55a7f811d74e1bf1186d99d3ee0a53288a 100644 (file)
@@ -13,6 +13,8 @@ def build(bld):
     obj.uselib = 'WXWIDGETS'
     obj.use = 'libdvdomatic'
     obj.source = """
+                 audio_dialog.cc
+                 audio_plot.cc
                  config_dialog.cc
                  dci_metadata_dialog.cc
                  dir_picker_ctrl.cc