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"
27 #include <dcp/raw_convert.h>
28 DCPOMATIC_DISABLE_WARNINGS
29 #include <libxml++/libxml++.h>
30 DCPOMATIC_ENABLE_WARNINGS
31 #include <boost/filesystem.hpp>
32 #include <boost/foreach.hpp>
48 using boost::shared_ptr;
49 using boost::optional;
50 using boost::dynamic_pointer_cast;
51 using dcp::raw_convert;
52 using namespace dcpomatic;
54 int const AudioAnalysis::_current_state_version = 3;
56 AudioAnalysis::AudioAnalysis (int channels)
58 _data.resize (channels);
61 AudioAnalysis::AudioAnalysis (boost::filesystem::path filename)
63 cxml::Document f ("AudioAnalysis");
64 f.read_file (filename);
66 if (f.optional_number_child<int>("Version").get_value_or(1) < _current_state_version) {
67 /* Too old. Throw an exception so that this analysis is re-run. */
68 throw OldFormatError ("Audio analysis file is too old");
71 BOOST_FOREACH (cxml::NodePtr i, f.node_children ("Channel")) {
72 vector<AudioPoint> channel;
74 BOOST_FOREACH (cxml::NodePtr j, i->node_children ("Point")) {
75 channel.push_back (AudioPoint (j));
78 _data.push_back (channel);
81 BOOST_FOREACH (cxml::ConstNodePtr i, f.node_children ("SamplePeak")) {
82 _sample_peak.push_back (
84 dcp::raw_convert<float>(i->content()), DCPTime(i->number_attribute<Frame>("Time"))
89 BOOST_FOREACH (cxml::ConstNodePtr i, f.node_children ("TruePeak")) {
90 _true_peak.push_back (dcp::raw_convert<float> (i->content ()));
93 _integrated_loudness = f.optional_number_child<float> ("IntegratedLoudness");
94 _loudness_range = f.optional_number_child<float> ("LoudnessRange");
96 _analysis_gain = f.optional_number_child<double> ("AnalysisGain");
97 _samples_per_point = f.number_child<int64_t> ("SamplesPerPoint");
98 _sample_rate = f.number_child<int64_t> ("SampleRate");
100 _leqm = f.optional_number_child<double>("Leqm");
104 AudioAnalysis::add_point (int c, AudioPoint const & p)
106 DCPOMATIC_ASSERT (c < channels ());
107 _data[c].push_back (p);
111 AudioAnalysis::get_point (int c, int p) const
113 DCPOMATIC_ASSERT (p < points (c));
118 AudioAnalysis::channels () const
120 return _data.size ();
124 AudioAnalysis::points (int c) const
126 DCPOMATIC_ASSERT (c < channels ());
127 return _data[c].size ();
131 AudioAnalysis::write (boost::filesystem::path filename)
133 shared_ptr<xmlpp::Document> doc (new xmlpp::Document);
134 xmlpp::Element* root = doc->create_root_node ("AudioAnalysis");
136 root->add_child("Version")->add_child_text (raw_convert<string> (_current_state_version));
138 BOOST_FOREACH (vector<AudioPoint>& i, _data) {
139 xmlpp::Element* channel = root->add_child ("Channel");
140 BOOST_FOREACH (AudioPoint& j, i) {
141 j.as_xml (channel->add_child ("Point"));
145 for (size_t i = 0; i < _sample_peak.size(); ++i) {
146 xmlpp::Element* n = root->add_child("SamplePeak");
147 n->add_child_text (raw_convert<string> (_sample_peak[i].peak));
148 n->set_attribute ("Time", raw_convert<string> (_sample_peak[i].time.get()));
151 BOOST_FOREACH (float i, _true_peak) {
152 root->add_child("TruePeak")->add_child_text (raw_convert<string> (i));
155 if (_integrated_loudness) {
156 root->add_child("IntegratedLoudness")->add_child_text (raw_convert<string> (_integrated_loudness.get ()));
159 if (_loudness_range) {
160 root->add_child("LoudnessRange")->add_child_text (raw_convert<string> (_loudness_range.get ()));
163 if (_analysis_gain) {
164 root->add_child("AnalysisGain")->add_child_text (raw_convert<string> (_analysis_gain.get ()));
167 root->add_child("SamplesPerPoint")->add_child_text (raw_convert<string> (_samples_per_point));
168 root->add_child("SampleRate")->add_child_text (raw_convert<string> (_sample_rate));
171 root->add_child("Leqm")->add_child_text(raw_convert<string>(*_leqm));
174 doc->write_to_file_formatted (filename.string ());
178 AudioAnalysis::gain_correction (shared_ptr<const Playlist> playlist)
180 if (playlist->content().size() == 1 && analysis_gain ()) {
181 /* In this case we know that the analysis was of a single piece of content and
182 we know that content's gain when the analysis was run. Hence we can work out
183 what correction is now needed to make it look `right'.
185 DCPOMATIC_ASSERT (playlist->content().front()->audio);
186 return playlist->content().front()->audio->gain() - analysis_gain().get ();
192 /** @return Peak across all channels, and the channel number it is on */
193 pair<AudioAnalysis::PeakTime, int>
194 AudioAnalysis::overall_sample_peak () const
196 DCPOMATIC_ASSERT (!_sample_peak.empty ());
198 optional<PeakTime> pt;
201 for (size_t i = 0; i < _sample_peak.size(); ++i) {
202 if (!pt || _sample_peak[i].peak > pt->peak) {
203 pt = _sample_peak[i];
208 return make_pair (pt.get(), c);
212 AudioAnalysis::overall_true_peak () const
216 BOOST_FOREACH (float i, _true_peak) {