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");
99 AudioAnalysis::add_point (int c, AudioPoint const & p)
101 DCPOMATIC_ASSERT (c < channels ());
102 _data[c].push_back (p);
106 AudioAnalysis::get_point (int c, int p) const
108 DCPOMATIC_ASSERT (p < points (c));
113 AudioAnalysis::channels () const
115 return _data.size ();
119 AudioAnalysis::points (int c) const
121 DCPOMATIC_ASSERT (c < channels ());
122 return _data[c].size ();
126 AudioAnalysis::write (boost::filesystem::path filename)
128 shared_ptr<xmlpp::Document> doc (new xmlpp::Document);
129 xmlpp::Element* root = doc->create_root_node ("AudioAnalysis");
131 root->add_child("Version")->add_child_text (raw_convert<string> (_current_state_version));
133 BOOST_FOREACH (vector<AudioPoint>& i, _data) {
134 xmlpp::Element* channel = root->add_child ("Channel");
135 BOOST_FOREACH (AudioPoint& j, i) {
136 j.as_xml (channel->add_child ("Point"));
140 for (size_t i = 0; i < _sample_peak.size(); ++i) {
141 xmlpp::Element* n = root->add_child("SamplePeak");
142 n->add_child_text (raw_convert<string> (_sample_peak[i].peak));
143 n->set_attribute ("Time", raw_convert<string> (_sample_peak[i].time.get()));
146 BOOST_FOREACH (float i, _true_peak) {
147 root->add_child("TruePeak")->add_child_text (raw_convert<string> (i));
150 if (_integrated_loudness) {
151 root->add_child("IntegratedLoudness")->add_child_text (raw_convert<string> (_integrated_loudness.get ()));
154 if (_loudness_range) {
155 root->add_child("LoudnessRange")->add_child_text (raw_convert<string> (_loudness_range.get ()));
158 if (_analysis_gain) {
159 root->add_child("AnalysisGain")->add_child_text (raw_convert<string> (_analysis_gain.get ()));
162 root->add_child("SamplesPerPoint")->add_child_text (raw_convert<string> (_samples_per_point));
163 root->add_child("SampleRate")->add_child_text (raw_convert<string> (_sample_rate));
165 doc->write_to_file_formatted (filename.string ());
169 AudioAnalysis::gain_correction (shared_ptr<const Playlist> playlist)
171 if (playlist->content().size() == 1 && analysis_gain ()) {
172 /* In this case we know that the analysis was of a single piece of content and
173 we know that content's gain when the analysis was run. Hence we can work out
174 what correction is now needed to make it look `right'.
176 DCPOMATIC_ASSERT (playlist->content().front()->audio);
177 return playlist->content().front()->audio->gain() - analysis_gain().get ();
183 /** @return Peak across all channels, and the channel number it is on */
184 pair<AudioAnalysis::PeakTime, int>
185 AudioAnalysis::overall_sample_peak () const
187 DCPOMATIC_ASSERT (!_sample_peak.empty ());
189 optional<PeakTime> pt;
192 for (size_t i = 0; i < _sample_peak.size(); ++i) {
193 if (!pt || _sample_peak[i].peak > pt->peak) {
194 pt = _sample_peak[i];
199 return make_pair (pt.get(), c);
203 AudioAnalysis::overall_true_peak () const
207 BOOST_FOREACH (float i, _true_peak) {