2 Copyright (C) 2012-2016 Carl Hetherington <cth@carlh.net>
4 This file is part of DCP-o-matic.
6 DCP-o-matic is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 DCP-o-matic is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
21 #include "audio_analysis.h"
25 #include "audio_content.h"
26 #include <dcp/raw_convert.h>
27 #include <libxml++/libxml++.h>
28 #include <boost/filesystem.hpp>
29 #include <boost/foreach.hpp>
45 using boost::shared_ptr;
46 using boost::optional;
47 using boost::dynamic_pointer_cast;
48 using dcp::raw_convert;
50 int const AudioAnalysis::_current_state_version = 2;
52 AudioAnalysis::AudioAnalysis (int channels)
54 _data.resize (channels);
57 AudioAnalysis::AudioAnalysis (boost::filesystem::path filename)
59 cxml::Document f ("AudioAnalysis");
60 f.read_file (filename);
62 if (f.optional_number_child<int>("Version").get_value_or(1) < _current_state_version) {
63 /* Too old. Throw an exception so that this analysis is re-run. */
64 throw OldFormatError ("Audio analysis file is too old");
67 BOOST_FOREACH (cxml::NodePtr i, f.node_children ("Channel")) {
68 vector<AudioPoint> channel;
70 BOOST_FOREACH (cxml::NodePtr j, i->node_children ("Point")) {
71 channel.push_back (AudioPoint (j));
74 _data.push_back (channel);
77 BOOST_FOREACH (cxml::ConstNodePtr i, f.node_children ("SamplePeak")) {
78 _sample_peak.push_back (
80 dcp::raw_convert<float>(i->content()), DCPTime(i->number_attribute<Frame>("Time"))
85 BOOST_FOREACH (cxml::ConstNodePtr i, f.node_children ("TruePeak")) {
86 _true_peak.push_back (dcp::raw_convert<float> (i->content ()));
89 _integrated_loudness = f.optional_number_child<float> ("IntegratedLoudness");
90 _loudness_range = f.optional_number_child<float> ("LoudnessRange");
92 _analysis_gain = f.optional_number_child<double> ("AnalysisGain");
96 AudioAnalysis::add_point (int c, AudioPoint const & p)
98 DCPOMATIC_ASSERT (c < channels ());
99 _data[c].push_back (p);
103 AudioAnalysis::get_point (int c, int p) const
105 DCPOMATIC_ASSERT (p < points (c));
110 AudioAnalysis::channels () const
112 return _data.size ();
116 AudioAnalysis::points (int c) const
118 DCPOMATIC_ASSERT (c < channels ());
119 return _data[c].size ();
123 AudioAnalysis::write (boost::filesystem::path filename)
125 shared_ptr<xmlpp::Document> doc (new xmlpp::Document);
126 xmlpp::Element* root = doc->create_root_node ("AudioAnalysis");
128 root->add_child("Version")->add_child_text (raw_convert<string> (_current_state_version));
130 BOOST_FOREACH (vector<AudioPoint>& i, _data) {
131 xmlpp::Element* channel = root->add_child ("Channel");
132 BOOST_FOREACH (AudioPoint& j, i) {
133 j.as_xml (channel->add_child ("Point"));
137 for (size_t i = 0; i < _sample_peak.size(); ++i) {
138 xmlpp::Element* n = root->add_child("SamplePeak");
139 n->add_child_text (raw_convert<string> (_sample_peak[i].peak));
140 n->set_attribute ("Time", raw_convert<string> (_sample_peak[i].time.get()));
143 BOOST_FOREACH (float i, _true_peak) {
144 root->add_child("TruePeak")->add_child_text (raw_convert<string> (i));
147 if (_integrated_loudness) {
148 root->add_child("IntegratedLoudness")->add_child_text (raw_convert<string> (_integrated_loudness.get ()));
151 if (_loudness_range) {
152 root->add_child("LoudnessRange")->add_child_text (raw_convert<string> (_loudness_range.get ()));
155 if (_analysis_gain) {
156 root->add_child("AnalysisGain")->add_child_text (raw_convert<string> (_analysis_gain.get ()));
159 doc->write_to_file_formatted (filename.string ());
163 AudioAnalysis::gain_correction (shared_ptr<const Playlist> playlist)
165 if (playlist->content().size() == 1 && analysis_gain ()) {
166 /* In this case we know that the analysis was of a single piece of content and
167 we know that content's gain when the analysis was run. Hence we can work out
168 what correction is now needed to make it look `right'.
170 DCPOMATIC_ASSERT (playlist->content().front()->audio);
171 return playlist->content().front()->audio->gain() - analysis_gain().get ();
177 /** @return Peak across all channels, and the channel number it is on */
178 pair<AudioAnalysis::PeakTime, int>
179 AudioAnalysis::overall_sample_peak () const
181 optional<PeakTime> pt;
184 for (size_t i = 0; i < _sample_peak.size(); ++i) {
185 if (!pt || _sample_peak[i].peak > pt->peak) {
186 pt = _sample_peak[i];
191 return make_pair (pt.get(), c);
195 AudioAnalysis::overall_true_peak () const
199 BOOST_FOREACH (float i, _true_peak) {