Merge.
authorCarl Hetherington <cth@carlh.net>
Fri, 1 Mar 2013 23:36:22 +0000 (23:36 +0000)
committerCarl Hetherington <cth@carlh.net>
Fri, 1 Mar 2013 23:36:22 +0000 (23:36 +0000)
26 files changed:
ChangeLog
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/encoder.cc
src/lib/ffmpeg_decoder.cc
src/lib/film.cc
src/lib/film.h
src/lib/job.cc
src/lib/job.h
src/lib/options.h
src/lib/util.cc
src/lib/util.h
src/lib/writer.cc
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/film_viewer.cc
src/wx/job_manager_view.cc
src/wx/wscript

index 553213938b63d81cf53173b3887667d78788c96b..b234f5baacc75b569f53853a522917dc3e50052e 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -4,6 +4,9 @@
 
 2013-03-01  Carl Hetherington  <cth@carlh.net>
 
+       * Add primitive feature to plot graphs
+       of the soundtrack (#67).
+
        * Version 0.76beta1 released.
 
 2013-02-27  Carl Hetherington  <cth@carlh.net>
diff --git a/src/lib/analyse_audio_job.cc b/src/lib/analyse_audio_job.cc
new file mode 100644 (file)
index 0000000..ca316f7
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+    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"
+
+#include "i18n.h"
+
+using std::string;
+using std::max;
+using std::cout;
+using boost::shared_ptr;
+
+int const AnalyseAudioJob::_num_points = 1024;
+
+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] = sqrt (_current[j][AudioPoint::RMS] / _samples_per_point);
+                               _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..9d708bb
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+    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 <boost/filesystem.hpp>
+#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;
+using std::max;
+using std::list;
+
+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 < channels ());
+       _data[c].push_back (p);
+}
+
+AudioPoint
+AudioAnalysis::get_point (int c, int p) const
+{
+       assert (p < points (c));
+       return _data[c][p];
+}
+
+int
+AudioAnalysis::channels () const
+{
+       return _data.size ();
+}
+
+int
+AudioAnalysis::points (int c) const
+{
+       assert (c < channels ());
+       return _data[c].size ();
+}
+
+void
+AudioAnalysis::write (string filename)
+{
+       string tmp = filename + ".tmp";
+       
+       ofstream f (tmp.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);
+               }
+       }
+
+       f.close ();
+       boost::filesystem::rename (tmp, filename);
+}
diff --git a/src/lib/audio_analysis.h b/src/lib/audio_analysis.h
new file mode 100644 (file)
index 0000000..6e0e2b7
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+    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>
+#include <list>
+
+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;
+       int channels () const;
+
+       void write (std::string);
+
+private:
+       std::vector<std::vector<AudioPoint> > _data;
+};
+
+#endif
index d4a27d01b5384f1fee2ced8133001b01b1184e43..3cc643cd6cf3fca7855611cff6a35f074bef72cd 100644 (file)
@@ -425,18 +425,20 @@ Encoder::encoder_thread (ServerDescription* server)
 void
 Encoder::write_audio (shared_ptr<const AudioBuffers> data)
 {
-       if (_film->audio_channels() == 1) {
-               /* We need to switch things around so that the mono channel is on
-                  the centre channel of a 5.1 set (with other channels silent).
-               */
-
-               shared_ptr<AudioBuffers> b (new AudioBuffers (6, data->frames ()));
-               b->make_silent (libdcp::LEFT);
-               b->make_silent (libdcp::RIGHT);
-               memcpy (b->data()[libdcp::CENTRE], data->data()[0], data->frames() * sizeof(float));
-               b->make_silent (libdcp::LFE);
-               b->make_silent (libdcp::LS);
-               b->make_silent (libdcp::RS);
+       AudioMapping m (_film->audio_channels ());
+       if (m.dcp_channels() != _film->audio_channels()) {
+
+               /* Remap (currently just for mono -> 5.1) */
+
+               shared_ptr<AudioBuffers> b (new AudioBuffers (m.dcp_channels(), data->frames ()));
+               for (int i = 0; i < m.dcp_channels(); ++i) {
+                       optional<int> s = m.dcp_to_source (static_cast<libdcp::Channel> (i));
+                       if (!s) {
+                               b->make_silent (i);
+                       } else {
+                               memcpy (b->data()[i], data->data()[s.get()], data->frames() * sizeof(float));
+                       }
+               }
 
                data = b;
        }
index 8834f28ed1c4f90152589bc2f200a9b4c49b9069..462db283ab8875dca384d96c045d44531513f4f7 100644 (file)
@@ -199,7 +199,7 @@ FFmpegDecoder::setup_audio ()
 void
 FFmpegDecoder::setup_subtitle ()
 {
-       if (!_subtitle_stream) {
+       if (!_subtitle_stream || _subtitle_stream->id() >= int (_format_context->nb_streams)) {
                return;
        }
 
@@ -238,8 +238,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) {
@@ -260,7 +262,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);
@@ -290,9 +292,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 c119f1515fb469dadb0abc709674f5076e5eb5e7..510158e942ad010e3ffcf87aa56a91e7ae656e96 100644 (file)
@@ -51,6 +51,7 @@
 #include "video_decoder.h"
 #include "audio_decoder.h"
 #include "external_audio_decoder.h"
+#include "analyse_audio_job.h"
 
 #include "i18n.h"
 
@@ -239,6 +240,15 @@ Film::video_mxf_filename () const
        return video_state_identifier() + N_(".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 ()
@@ -305,6 +315,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 ()
@@ -318,6 +341,15 @@ Film::examine_content ()
        JobManager::instance()->add (_examine_content_job);
 }
 
+void
+Film::analyse_audio_finished ()
+{
+       ensure_ui_thread ();
+       _analyse_audio_job.reset ();
+
+       AudioAnalysisFinished ();
+}
+
 void
 Film::examine_content_finished ()
 {
@@ -872,6 +904,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());
@@ -881,13 +920,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..847ab434ea06adfc98a169626da7e7b0b5f5fa78 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 ();
@@ -364,6 +367,8 @@ public:
        /** Emitted when some property has changed */
        mutable boost::signals2::signal<void (Property)> Changed;
 
+       boost::signals2::signal<void ()> AudioAnalysisFinished;
+
        /** Current version number of the state file */
        static int const state_version;
 
@@ -374,9 +379,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 77d3671369481b0728cac6b369827a6291fab1fb..8c1612a55f741f3cb008e503696fc7be0bc0b860 100644 (file)
@@ -150,7 +150,6 @@ Job::set_state (State s)
 
        if (_state == FINISHED_OK || _state == FINISHED_ERROR) {
                _ran_for = elapsed_time ();
-               Finished ();
        }
 }
 
index 1538e2779563910e67e998b2ec87c10f80838fcd..c98dbaea15dfb13133611f61727439d332158470 100644 (file)
@@ -65,6 +65,7 @@ public:
        void descend (float);
        float overall_progress () const;
 
+       /** Emitted by the JobManagerView from the UI thread */
        boost::signals2::signal<void()> Finished;
 
 protected:
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 3d70a3122166bce4f66b1f50f58fea05bb822954..de69636da7803e6359eb8e9fd0614eb703d7b003 100644 (file)
@@ -450,19 +450,6 @@ dcp_audio_sample_rate (int fs)
        return 96000;
 }
 
-int
-dcp_audio_channels (int f)
-{
-       if (f == 1) {
-               /* The source is mono, so to put the mono channel into
-                  the centre we need to generate a 5.1 soundtrack.
-               */
-               return 6;
-       }
-
-       return f;
-}
-
 bool operator== (Crop const & a, Crop const & b)
 {
        return (a.left == b.left && a.right == b.right && a.top == b.top && a.bottom == b.bottom);
@@ -903,3 +890,75 @@ 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];
+}
+
+AudioMapping::AudioMapping (int c)
+       : _source_channels (c)
+{
+
+}
+
+optional<libdcp::Channel>
+AudioMapping::source_to_dcp (int c) const
+{
+       if (c >= _source_channels) {
+               return optional<libdcp::Channel> ();
+       }
+
+       if (_source_channels == 1) {
+               /* mono sources to centre */
+               return libdcp::CENTRE;
+       }
+       
+       return static_cast<libdcp::Channel> (c);
+}
+
+optional<int>
+AudioMapping::dcp_to_source (libdcp::Channel c) const
+{
+       if (_source_channels == 1) {
+               if (c == libdcp::CENTRE) {
+                       return 0;
+               } else {
+                       return optional<int> ();
+               }
+       }
+
+       if (static_cast<int> (c) >= _source_channels) {
+               return optional<int> ();
+       }
+       
+       return static_cast<int> (c);
+}
+
+int
+AudioMapping::dcp_channels () const
+{
+       if (_source_channels == 1) {
+               /* The source is mono, so to put the mono channel into
+                  the centre we need to generate a 5.1 soundtrack.
+               */
+               return 6;
+       }
+
+       return _source_channels;
+}
index 87735ea8e82b8bc27e77b17515da26eedc8155ee..22c6ea95baafa4f1f2cb545c5f20468b2b5e5b80 100644 (file)
@@ -29,6 +29,7 @@
 #include <vector>
 #include <boost/shared_ptr.hpp>
 #include <boost/asio.hpp>
+#include <boost/optional.hpp>
 #include <libdcp/util.h>
 extern "C" {
 #include <libavcodec/avcodec.h>
@@ -57,6 +58,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;
 
@@ -178,7 +180,6 @@ struct Rect
 
 extern std::string crop_string (Position, libdcp::Size);
 extern int dcp_audio_sample_rate (int);
-extern int dcp_audio_channels (int);
 extern std::string colour_lut_index_to_name (int index);
 extern int stride_round_up (int, int const *, int);
 extern int stride_lookup (int c, int const * stride);
@@ -268,6 +269,19 @@ private:
        float** _data;
 };
 
+class AudioMapping
+{
+public:
+       AudioMapping (int);
+
+       boost::optional<libdcp::Channel> source_to_dcp (int c) const;
+       boost::optional<int> dcp_to_source (libdcp::Channel c) const;
+       int dcp_channels () const;
+
+private:
+       int _source_channels;
+};
+
 extern int64_t video_frames_to_audio_frames (SourceFrame v, float audio_sample_rate, float frames_per_second);
 extern bool still_image_file (std::string);
 extern std::pair<std::string, int> cpu_info ();
index d480d502afdd6ef7899f6c51da2b739427998ba8..334ecec6581ce60c060a5e8154c2f65502fbf1c5 100644 (file)
@@ -74,13 +74,15 @@ Writer::Writer (shared_ptr<Film> f)
 
        _picture_asset_writer = _picture_asset->start_write (_first_nonexistant_frame > 0);
 
-       if (dcp_audio_channels (_film->audio_channels()) > 0) {
+       AudioMapping m (_film->audio_channels ());
+       
+       if (m.dcp_channels() > 0) {
                _sound_asset.reset (
                        new libdcp::SoundAsset (
                                _film->dir (_film->dcp_name()),
                                N_("audio.mxf"),
                                DCPFrameRate (_film->frames_per_second()).frames_per_second,
-                               dcp_audio_channels (_film->audio_channels()),
+                               m.dcp_channels (),
                                dcp_audio_sample_rate (_film->audio_stream()->sample_rate())
                                )
                        );
index 6ddb948515e53c2e8b872bd7c8696093be01c479..d36a24e7a100f220427a2827b6e4190e74134e19 100644 (file)
@@ -4,6 +4,8 @@ import i18n
 sources = """
           ab_transcode_job.cc
          ab_transcoder.cc
+          analyse_audio_job.cc
+          audio_analysis.cc
           audio_decoder.cc
           audio_source.cc
           config.cc
index ab34924aa56726e0164fe2a62393a67b9fdc858d..f5d9bdf181a3e82b3a40dac108c75bc129cca020 100644 (file)
@@ -149,7 +149,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
 };
 
@@ -174,7 +174,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);
@@ -211,7 +211,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));
@@ -390,10 +390,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..5bac8ea
--- /dev/null
@@ -0,0 +1,201 @@
+/*
+    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/filesystem.hpp>
+#include "audio_dialog.h"
+#include "audio_plot.h"
+#include "audio_analysis.h"
+#include "film.h"
+#include "wx_util.h"
+
+using boost::shared_ptr;
+using boost::bind;
+using boost::optional;
+
+AudioDialog::AudioDialog (wxWindow* parent)
+       : 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, wxALL, 12);
+
+       wxBoxSizer* side = new wxBoxSizer (wxVERTICAL);
+
+       {
+               wxStaticText* m = new wxStaticText (this, wxID_ANY, _("Channels"));
+               side->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP, 16);
+       }
+       
+
+       for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
+               _channel_checkbox[i] = new wxCheckBox (this, wxID_ANY, std_to_wx (audio_channel_name (i)));
+               side->Add (_channel_checkbox[i], 1, wxEXPAND | wxALL, 3);
+               _channel_checkbox[i]->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (AudioDialog::channel_clicked), 0, this);
+       }
+
+       {
+               wxStaticText* m = new wxStaticText (this, wxID_ANY, _("Type"));
+               side->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP, 16);
+       }
+       
+       wxString const types[] = {
+               _("Peak"),
+               _("RMS")
+       };
+
+       for (int i = 0; i < AudioPoint::COUNT; ++i) {
+               _type_checkbox[i] = new wxCheckBox (this, wxID_ANY, types[i]);
+               side->Add (_type_checkbox[i], 1, wxEXPAND | wxALL, 3);
+               _type_checkbox[i]->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (AudioDialog::type_clicked), 0, this);
+       }
+
+       {
+               wxStaticText* m = new wxStaticText (this, wxID_ANY, _("Smoothing"));
+               side->Add (m, 1, wxALIGN_CENTER_VERTICAL | wxTOP, 16);
+       }
+       
+       _smoothing = new wxSlider (this, wxID_ANY, AudioPlot::max_smoothing / 2, 1, AudioPlot::max_smoothing);
+       _smoothing->Connect (wxID_ANY, wxEVT_SCROLL_THUMBTRACK, wxScrollEventHandler (AudioDialog::smoothing_changed), 0, this);
+       side->Add (_smoothing, 1, wxEXPAND);
+
+       sizer->Add (side, 0, wxALL, 12);
+
+       SetSizer (sizer);
+       sizer->Layout ();
+       sizer->SetSizeHints (this);
+}
+
+void
+AudioDialog::set_film (boost::shared_ptr<Film> f)
+{
+       _film_changed_connection.disconnect ();
+       _film_audio_analysis_finished_connection.disconnect ();
+       
+       _film = f;
+
+       try_to_load_analysis ();
+       setup_channels ();
+       _plot->set_gain (_film->audio_gain ());
+
+       _film_changed_connection = _film->Changed.connect (bind (&AudioDialog::film_changed, this, _1));
+       _film_audio_analysis_finished_connection = _film->AudioAnalysisFinished.connect (bind (&AudioDialog::try_to_load_analysis, this));
+
+       SetTitle (std_to_wx (String::compose (wx_to_std (_("DVD-o-matic audio - %1")), _film->name())));
+}
+
+void
+AudioDialog::setup_channels ()
+{
+       if (!_film->audio_stream()) {
+               return;
+       }
+
+       AudioMapping m (_film->audio_stream()->channels ());
+       
+       for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
+               if (m.dcp_to_source(static_cast<libdcp::Channel>(i))) {
+                       _channel_checkbox[i]->Show ();
+               } else {
+                       _channel_checkbox[i]->Hide ();
+               }
+       }
+}      
+
+void
+AudioDialog::try_to_load_analysis ()
+{
+       shared_ptr<AudioAnalysis> a;
+
+       if (boost::filesystem::exists (_film->audio_analysis_path())) {
+               a.reset (new AudioAnalysis (_film->audio_analysis_path ()));
+       } else {
+               if (IsShown ()) {
+                       _film->analyse_audio ();
+               }
+       }
+               
+       _plot->set_analysis (a);
+
+       AudioMapping m (_film->audio_stream()->channels ());
+       optional<libdcp::Channel> c = m.source_to_dcp (0);
+       if (c) {
+               _channel_checkbox[c.get()]->SetValue (true);
+               _plot->set_channel_visible (0, true);
+       }
+
+       for (int i = 0; i < AudioPoint::COUNT; ++i) {
+               _type_checkbox[i]->SetValue (true);
+               _plot->set_type_visible (i, true);
+       }
+}
+
+void
+AudioDialog::channel_clicked (wxCommandEvent& ev)
+{
+       int c = 0;
+       while (c < MAX_AUDIO_CHANNELS && ev.GetEventObject() != _channel_checkbox[c]) {
+               ++c;
+       }
+
+       assert (c < MAX_AUDIO_CHANNELS);
+
+       AudioMapping m (_film->audio_stream()->channels ());
+       optional<int> s = m.dcp_to_source (static_cast<libdcp::Channel> (c));
+       if (s) {
+               _plot->set_channel_visible (s.get(), _channel_checkbox[c]->GetValue ());
+       }
+}
+
+void
+AudioDialog::film_changed (Film::Property p)
+{
+       switch (p) {
+       case Film::AUDIO_GAIN:
+               _plot->set_gain (_film->audio_gain ());
+               break;
+       case Film::CONTENT_AUDIO_STREAM:
+       case Film::EXTERNAL_AUDIO:
+       case Film::USE_CONTENT_AUDIO:
+               setup_channels ();
+               break;
+       default:
+               break;
+       }
+}
+
+void
+AudioDialog::type_clicked (wxCommandEvent& ev)
+{
+       int t = 0;
+       while (t < AudioPoint::COUNT && ev.GetEventObject() != _type_checkbox[t]) {
+               ++t;
+       }
+
+       assert (t < AudioPoint::COUNT);
+
+       _plot->set_type_visible (t, _type_checkbox[t]->GetValue ());
+}
+
+void
+AudioDialog::smoothing_changed (wxScrollEvent &)
+{
+       _plot->set_smoothing (_smoothing->GetValue ());
+}
diff --git a/src/wx/audio_dialog.h b/src/wx/audio_dialog.h
new file mode 100644 (file)
index 0000000..16cb356
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+    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"
+#include "lib/audio_analysis.h"
+
+class AudioPlot;
+class Film;
+
+class AudioDialog : public wxDialog
+{
+public:
+       AudioDialog (wxWindow *);
+
+       void set_film (boost::shared_ptr<Film>);
+
+private:
+       void film_changed (Film::Property);
+       void channel_clicked (wxCommandEvent &);
+       void type_clicked (wxCommandEvent &);
+       void smoothing_changed (wxScrollEvent &);
+       void try_to_load_analysis ();
+       void setup_channels ();
+
+       boost::shared_ptr<Film> _film;
+       AudioPlot* _plot;
+       wxCheckBox* _channel_checkbox[MAX_AUDIO_CHANNELS];
+       wxCheckBox* _type_checkbox[AudioPoint::COUNT];
+       wxSlider* _smoothing;
+       boost::signals2::scoped_connection _film_changed_connection;
+       boost::signals2::scoped_connection _film_audio_analysis_finished_connection;
+};
diff --git a/src/wx/audio_plot.cc b/src/wx/audio_plot.cc
new file mode 100644 (file)
index 0000000..af8460a
--- /dev/null
@@ -0,0 +1,272 @@
+/*
+    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::list;
+using std::max;
+using std::min;
+using boost::bind;
+using boost::shared_ptr;
+
+int const AudioPlot::_minimum = -70;
+int const AudioPlot::max_smoothing = 128;
+
+AudioPlot::AudioPlot (wxWindow* parent)
+       : wxPanel (parent)
+       , _gain (0)
+       , _smoothing (max_smoothing / 2)
+{
+       SetDoubleBuffered (true);
+
+       for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
+               _channel_visible[i] = false;
+       }
+
+       for (int i = 0; i < AudioPoint::COUNT; ++i) {
+               _type_visible[i] = false;
+       }
+
+       _colours.push_back (wxColour (  0,   0,   0));
+       _colours.push_back (wxColour (255,   0,   0));
+       _colours.push_back (wxColour (  0, 255,   0));
+       _colours.push_back (wxColour (139,   0, 204));
+       _colours.push_back (wxColour (  0,   0, 255));
+       _colours.push_back (wxColour (100, 100, 100));
+       
+       Connect (wxID_ANY, wxEVT_PAINT, wxPaintEventHandler (AudioPlot::paint), 0, this);
+       
+       SetMinSize (wxSize (640, 512));
+}
+
+void
+AudioPlot::set_analysis (shared_ptr<AudioAnalysis> a)
+{
+       _analysis = a;
+
+       for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
+               _channel_visible[i] = false;
+       }
+
+       for (int i = 0; i < AudioPoint::COUNT; ++i) {
+               _type_visible[i] = false;
+       }
+       
+       Refresh ();
+}
+
+void
+AudioPlot::set_channel_visible (int c, bool v)
+{
+       _channel_visible[c] = v;
+       Refresh ();
+}
+
+void
+AudioPlot::set_type_visible (int t, bool v)
+{
+       _type_visible[t] = v;
+       Refresh ();
+}
+
+void
+AudioPlot::paint (wxPaintEvent &)
+{
+       wxPaintDC dc (this);
+
+       wxGraphicsContext* gc = wxGraphicsContext::Create (dc);
+       if (!gc) {
+               return;
+       }
+
+       if (!_analysis || _analysis->channels() == 0) {
+               gc->SetFont (gc->CreateFont (*wxNORMAL_FONT));
+               gc->DrawText (_("Please wait; audio is being analysed..."), 32, 32);
+               return;
+       }
+
+       wxGraphicsPath grid = gc->CreatePath ();
+       gc->SetFont (gc->CreateFont (*wxSMALL_FONT));
+       wxDouble db_label_height;
+       wxDouble db_label_descent;
+       wxDouble db_label_leading;
+       gc->GetTextExtent ("-80dB", &_db_label_width, &db_label_height, &db_label_descent, &db_label_leading);
+
+       _db_label_width += 8;
+       
+       int const data_width = GetSize().GetWidth() - _db_label_width;
+       /* Assume all channels have the same number of points */
+       _x_scale = data_width / float (_analysis->points (0));
+       _height = GetSize().GetHeight ();
+       _y_origin = 32;
+       _y_scale = (_height - _y_origin) / -_minimum;
+
+       for (int i = _minimum; i <= 0; i += 10) {
+               int const y = (_height - (i - _minimum) * _y_scale) - _y_origin;
+               grid.MoveToPoint (_db_label_width - 4, y);
+               grid.AddLineToPoint (_db_label_width + data_width, y);
+               gc->DrawText (std_to_wx (String::compose ("%1dB", i)), 0, y - (db_label_height / 2));
+       }
+
+       gc->SetPen (*wxLIGHT_GREY_PEN);
+       gc->StrokePath (grid);
+
+       gc->DrawText (_("Time"), data_width, _height - _y_origin + db_label_height / 2);
+
+       
+       if (_type_visible[AudioPoint::PEAK]) {
+               for (int c = 0; c < MAX_AUDIO_CHANNELS; ++c) {
+                       wxGraphicsPath p = gc->CreatePath ();
+                       if (_channel_visible[c] && c < _analysis->channels()) {
+                               plot_peak (p, c);
+                       }
+                       wxColour const col = _colours[c];
+#if wxMAJOR_VERSION == 2 && wxMINOR_VERSION >= 9
+                       gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (col.Red(), col.Green(), col.Blue(), col.Alpha() / 2), 1, wxPENSTYLE_SOLID));
+#else                  
+                       gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (col.Red(), col.Green(), col.Blue(), col.Alpha() / 2), 1, wxSOLID));
+#endif
+                       gc->StrokePath (p);
+               }
+       }
+
+       if (_type_visible[AudioPoint::RMS]) {
+               for (int c = 0; c < MAX_AUDIO_CHANNELS; ++c) {
+                       wxGraphicsPath p = gc->CreatePath ();
+                       if (_channel_visible[c] && c < _analysis->channels()) {
+                               plot_rms (p, c);
+                       }
+                       wxColour const col = _colours[c];
+#if wxMAJOR_VERSION == 2 && wxMINOR_VERSION >= 9
+                       gc->SetPen (*wxThePenList->FindOrCreatePen (col, 1, wxPENSTYLE_SOLID));
+#else
+                       gc->SetPen (*wxThePenList->FindOrCreatePen (col, 1, wxSOLID));
+#endif                 
+                       gc->StrokePath (p);
+               }
+       }
+
+       wxGraphicsPath axes = gc->CreatePath ();
+       axes.MoveToPoint (_db_label_width, 0);
+       axes.AddLineToPoint (_db_label_width, _height - _y_origin);
+       axes.AddLineToPoint (_db_label_width + data_width, _height - _y_origin);
+       gc->SetPen (*wxBLACK_PEN);
+       gc->StrokePath (axes);
+
+       delete gc;
+}
+
+float
+AudioPlot::y_for_linear (float p) const
+{
+       return _height - (20 * log10(p) - _minimum + _gain) * _y_scale - _y_origin;
+}
+
+void
+AudioPlot::plot_peak (wxGraphicsPath& path, int channel) const
+{
+       path.MoveToPoint (_db_label_width, y_for_linear (_analysis->get_point(channel, 0)[AudioPoint::PEAK]));
+
+       float peak = 0;
+       int const N = _analysis->points(channel);
+       for (int i = 0; i < N; ++i) {
+               float const p = _analysis->get_point(channel, i)[AudioPoint::PEAK];
+               peak -= 0.01f * (1 - log10 (_smoothing) / log10 (max_smoothing));
+               if (p > peak) {
+                       peak = p;
+               } else if (peak < 0) {
+                       peak = 0;
+               }
+               
+               path.AddLineToPoint (_db_label_width + i * _x_scale, y_for_linear (peak));
+       }
+}
+
+void
+AudioPlot::plot_rms (wxGraphicsPath& path, int channel) const
+{
+       path.MoveToPoint (_db_label_width, y_for_linear (_analysis->get_point(channel, 0)[AudioPoint::RMS]));
+
+       list<float> smoothing;
+
+       int const N = _analysis->points(channel);
+
+       float const first = _analysis->get_point(channel, 0)[AudioPoint::RMS];
+       float const last = _analysis->get_point(channel, N - 1)[AudioPoint::RMS];
+
+       int const before = _smoothing / 2;
+       int const after = _smoothing - before;
+       
+       /* Pre-load the smoothing list */
+       for (int i = 0; i < before; ++i) {
+               smoothing.push_back (first);
+       }
+       for (int i = 0; i < after; ++i) {
+               if (i < N) {
+                       smoothing.push_back (_analysis->get_point(channel, i)[AudioPoint::RMS]);
+               } else {
+                       smoothing.push_back (last);
+               }
+       }
+       
+       for (int i = 0; i < N; ++i) {
+
+               int const next_for_window = i + after;
+
+               if (next_for_window < N) {
+                       smoothing.push_back (_analysis->get_point(channel, i)[AudioPoint::RMS]);
+               } else {
+                       smoothing.push_back (last);
+               }
+
+               smoothing.pop_front ();
+
+               float p = 0;
+               for (list<float>::const_iterator j = smoothing.begin(); j != smoothing.end(); ++j) {
+                       p += pow (*j, 2);
+               }
+
+               p = sqrt (p / smoothing.size ());
+
+               path.AddLineToPoint (_db_label_width + i * _x_scale, y_for_linear (p));
+       }
+}
+
+void
+AudioPlot::set_gain (float g)
+{
+       _gain = g;
+       Refresh ();
+}
+
+void
+AudioPlot::set_smoothing (int s)
+{
+       _smoothing = s;
+       Refresh ();
+}
diff --git a/src/wx/audio_plot.h b/src/wx/audio_plot.h
new file mode 100644 (file)
index 0000000..7b2955e
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+    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>
+#include "util.h"
+#include "audio_analysis.h"
+
+class AudioPlot : public wxPanel
+{
+public:
+       AudioPlot (wxWindow *);
+
+       void set_analysis (boost::shared_ptr<AudioAnalysis>);
+       void set_channel_visible (int c, bool v);
+       void set_type_visible (int t, bool v);
+       void set_gain (float);
+       void set_smoothing (int);
+
+       static const int max_smoothing;
+
+private:
+       void paint (wxPaintEvent &);
+
+       boost::shared_ptr<AudioAnalysis> _analysis;
+       bool _channel_visible[MAX_AUDIO_CHANNELS];
+       bool _type_visible[AudioPoint::COUNT];
+       /** gain to apply in dB */
+       float _gain;
+       int _smoothing;
+       std::vector<wxColour> _colours;
+
+       void plot_peak (wxGraphicsPath &, int) const;
+       void plot_rms (wxGraphicsPath &, int) const;
+       float y_for_linear (float) const;
+
+       double _db_label_width;
+       int _height;
+       int _y_origin;
+       float _x_scale;
+       float _y_scale;
+
+       static const int _minimum;
+};
index 85046644506de7672131087d6ef1f83918a37dec..362243ef300fd227afef169450fc2cdae2867de4 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,21 +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, std_to_wx (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);
        }
@@ -755,6 +749,10 @@ FilmEditor::set_film (shared_ptr<Film> f)
        } else {
                FileChanged (wx_to_std (N_("")));
        }
+
+       if (_audio_dialog) {
+               _audio_dialog->set_film (_film);
+       }
        
        film_changed (Film::NAME);
        film_changed (Film::USE_DCI_NAME);
@@ -816,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);
 
@@ -1174,3 +1173,16 @@ 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);
+       _audio_dialog->Show ();
+       _audio_dialog->set_film (_film);
+}
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 3705f38bb618eb6630ca314517d06f590d582a26..5eba7fd809c1d11e948988eaacc497d8d842ae22 100644 (file)
@@ -101,7 +101,14 @@ FilmViewer::film_changed (Film::Property p)
                o.decode_audio = false;
                o.decode_subtitles = true;
                o.video_sync = false;
-               _decoders = decoder_factory (_film, o);
+
+               try {
+                       _decoders = decoder_factory (_film, o);
+               } catch (StringError& e) {
+                       error_dialog (this, wxString::Format (_("Could not open content file (%s)"), e.what()));
+                       return;
+               }
+               
                if (_decoders.video == 0) {
                        break;
                }
index 7537da287dca0f4ae545de67f902c5b5ec81e7c0..7361f29a815896d1021a5a1ed06a1500cc293f8c 100644 (file)
@@ -109,6 +109,7 @@ JobManagerView::update ()
                if ((*i)->finished() && !_job_records[*i].finalised) {
                        _job_records[*i].gauge->SetValue (100);
                        checked_set (_job_records[*i].message, st);
+                       (*i)->Finished ();
                        _job_records[*i].finalised = true;
                        if (!(*i)->error_details().empty ()) {
                                _job_records[*i].details->Enable (true);
index 92ff440cd49be89a50fddb930d2a94beac8589f1..cc303f5e801d0b4011cd41f9ed02a507e87350f5 100644 (file)
@@ -4,6 +4,8 @@ from waflib import Logs
 import i18n
 
 sources = """
+          audio_dialog.cc
+          audio_plot.cc
           config_dialog.cc
           dci_metadata_dialog.cc
           dir_picker_ctrl.cc