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;
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");
93 _samples_per_point = f.optional_number_child<int64_t> ("SamplesPerPoint");
94 _sample_rate = f.optional_number_child<int64_t> ("SampleRate");
98 AudioAnalysis::add_point (int c, AudioPoint const & p)
100 DCPOMATIC_ASSERT (c < channels ());
101 _data[c].push_back (p);
105 AudioAnalysis::get_point (int c, int p) const
107 DCPOMATIC_ASSERT (p < points (c));
112 AudioAnalysis::channels () const
114 return _data.size ();
118 AudioAnalysis::points (int c) const
120 DCPOMATIC_ASSERT (c < channels ());
121 return _data[c].size ();
125 AudioAnalysis::write (boost::filesystem::path filename)
127 shared_ptr<xmlpp::Document> doc (new xmlpp::Document);
128 xmlpp::Element* root = doc->create_root_node ("AudioAnalysis");
130 root->add_child("Version")->add_child_text (raw_convert<string> (_current_state_version));
132 BOOST_FOREACH (vector<AudioPoint>& i, _data) {
133 xmlpp::Element* channel = root->add_child ("Channel");
134 BOOST_FOREACH (AudioPoint& j, i) {
135 j.as_xml (channel->add_child ("Point"));
139 for (size_t i = 0; i < _sample_peak.size(); ++i) {
140 xmlpp::Element* n = root->add_child("SamplePeak");
141 n->add_child_text (raw_convert<string> (_sample_peak[i].peak));
142 n->set_attribute ("Time", raw_convert<string> (_sample_peak[i].time.get()));
145 BOOST_FOREACH (float i, _true_peak) {
146 root->add_child("TruePeak")->add_child_text (raw_convert<string> (i));
149 if (_integrated_loudness) {
150 root->add_child("IntegratedLoudness")->add_child_text (raw_convert<string> (_integrated_loudness.get ()));
153 if (_loudness_range) {
154 root->add_child("LoudnessRange")->add_child_text (raw_convert<string> (_loudness_range.get ()));
157 if (_analysis_gain) {
158 root->add_child("AnalysisGain")->add_child_text (raw_convert<string> (_analysis_gain.get ()));
161 if (_samples_per_point) {
162 root->add_child("SamplesPerPoint")->add_child_text (raw_convert<string> (_samples_per_point.get()));
166 root->add_child("SampleRate")->add_child_text (raw_convert<string> (_sample_rate.get()));
169 doc->write_to_file_formatted (filename.string ());
173 AudioAnalysis::gain_correction (shared_ptr<const Playlist> playlist)
175 if (playlist->content().size() == 1 && analysis_gain ()) {
176 /* In this case we know that the analysis was of a single piece of content and
177 we know that content's gain when the analysis was run. Hence we can work out
178 what correction is now needed to make it look `right'.
180 DCPOMATIC_ASSERT (playlist->content().front()->audio);
181 return playlist->content().front()->audio->gain() - analysis_gain().get ();
187 /** @return Peak across all channels, and the channel number it is on */
188 pair<AudioAnalysis::PeakTime, int>
189 AudioAnalysis::overall_sample_peak () const
191 DCPOMATIC_ASSERT (!_sample_peak.empty ());
193 optional<PeakTime> pt;
196 for (size_t i = 0; i < _sample_peak.size(); ++i) {
197 if (!pt || _sample_peak[i].peak > pt->peak) {
198 pt = _sample_peak[i];
203 return make_pair (pt.get(), c);
207 AudioAnalysis::overall_true_peak () const
211 BOOST_FOREACH (float i, _true_peak) {