2 Copyright (C) 2012-2018 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;
49 using namespace dcpomatic;
51 int const AudioAnalysis::_current_state_version = 3;
53 AudioAnalysis::AudioAnalysis (int channels)
55 _data.resize (channels);
58 AudioAnalysis::AudioAnalysis (boost::filesystem::path filename)
60 cxml::Document f ("AudioAnalysis");
61 f.read_file (filename);
63 if (f.optional_number_child<int>("Version").get_value_or(1) < _current_state_version) {
64 /* Too old. Throw an exception so that this analysis is re-run. */
65 throw OldFormatError ("Audio analysis file is too old");
68 BOOST_FOREACH (cxml::NodePtr i, f.node_children ("Channel")) {
69 vector<AudioPoint> channel;
71 BOOST_FOREACH (cxml::NodePtr j, i->node_children ("Point")) {
72 channel.push_back (AudioPoint (j));
75 _data.push_back (channel);
78 BOOST_FOREACH (cxml::ConstNodePtr i, f.node_children ("SamplePeak")) {
79 _sample_peak.push_back (
81 dcp::raw_convert<float>(i->content()), DCPTime(i->number_attribute<Frame>("Time"))
86 BOOST_FOREACH (cxml::ConstNodePtr i, f.node_children ("TruePeak")) {
87 _true_peak.push_back (dcp::raw_convert<float> (i->content ()));
90 _integrated_loudness = f.optional_number_child<float> ("IntegratedLoudness");
91 _loudness_range = f.optional_number_child<float> ("LoudnessRange");
93 _analysis_gain = f.optional_number_child<double> ("AnalysisGain");
94 _samples_per_point = f.number_child<int64_t> ("SamplesPerPoint");
95 _sample_rate = f.number_child<int64_t> ("SampleRate");
97 _leqm = f.optional_number_child<double>("Leqm");
101 AudioAnalysis::add_point (int c, AudioPoint const & p)
103 DCPOMATIC_ASSERT (c < channels ());
104 _data[c].push_back (p);
108 AudioAnalysis::get_point (int c, int p) const
110 DCPOMATIC_ASSERT (p < points (c));
115 AudioAnalysis::channels () const
117 return _data.size ();
121 AudioAnalysis::points (int c) const
123 DCPOMATIC_ASSERT (c < channels ());
124 return _data[c].size ();
128 AudioAnalysis::write (boost::filesystem::path filename)
130 shared_ptr<xmlpp::Document> doc (new xmlpp::Document);
131 xmlpp::Element* root = doc->create_root_node ("AudioAnalysis");
133 root->add_child("Version")->add_child_text (raw_convert<string> (_current_state_version));
135 BOOST_FOREACH (vector<AudioPoint>& i, _data) {
136 xmlpp::Element* channel = root->add_child ("Channel");
137 BOOST_FOREACH (AudioPoint& j, i) {
138 j.as_xml (channel->add_child ("Point"));
142 for (size_t i = 0; i < _sample_peak.size(); ++i) {
143 xmlpp::Element* n = root->add_child("SamplePeak");
144 n->add_child_text (raw_convert<string> (_sample_peak[i].peak));
145 n->set_attribute ("Time", raw_convert<string> (_sample_peak[i].time.get()));
148 BOOST_FOREACH (float i, _true_peak) {
149 root->add_child("TruePeak")->add_child_text (raw_convert<string> (i));
152 if (_integrated_loudness) {
153 root->add_child("IntegratedLoudness")->add_child_text (raw_convert<string> (_integrated_loudness.get ()));
156 if (_loudness_range) {
157 root->add_child("LoudnessRange")->add_child_text (raw_convert<string> (_loudness_range.get ()));
160 if (_analysis_gain) {
161 root->add_child("AnalysisGain")->add_child_text (raw_convert<string> (_analysis_gain.get ()));
164 root->add_child("SamplesPerPoint")->add_child_text (raw_convert<string> (_samples_per_point));
165 root->add_child("SampleRate")->add_child_text (raw_convert<string> (_sample_rate));
168 root->add_child("Leqm")->add_child_text(raw_convert<string>(*_leqm));
171 doc->write_to_file_formatted (filename.string ());
175 AudioAnalysis::gain_correction (shared_ptr<const Playlist> playlist)
177 if (playlist->content().size() == 1 && analysis_gain ()) {
178 /* In this case we know that the analysis was of a single piece of content and
179 we know that content's gain when the analysis was run. Hence we can work out
180 what correction is now needed to make it look `right'.
182 DCPOMATIC_ASSERT (playlist->content().front()->audio);
183 return playlist->content().front()->audio->gain() - analysis_gain().get ();
189 /** @return Peak across all channels, and the channel number it is on */
190 pair<AudioAnalysis::PeakTime, int>
191 AudioAnalysis::overall_sample_peak () const
193 DCPOMATIC_ASSERT (!_sample_peak.empty ());
195 optional<PeakTime> pt;
198 for (size_t i = 0; i < _sample_peak.size(); ++i) {
199 if (!pt || _sample_peak[i].peak > pt->peak) {
200 pt = _sample_peak[i];
205 return make_pair (pt.get(), c);
209 AudioAnalysis::overall_true_peak () const
213 BOOST_FOREACH (float i, _true_peak) {