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>
47 using std::shared_ptr;
48 using boost::optional;
49 using std::dynamic_pointer_cast;
50 using dcp::raw_convert;
51 using namespace dcpomatic;
53 int const AudioAnalysis::_current_state_version = 3;
55 AudioAnalysis::AudioAnalysis (int channels)
57 _data.resize (channels);
60 AudioAnalysis::AudioAnalysis (boost::filesystem::path filename)
62 cxml::Document f ("AudioAnalysis");
63 f.read_file (filename);
65 if (f.optional_number_child<int>("Version").get_value_or(1) < _current_state_version) {
66 /* Too old. Throw an exception so that this analysis is re-run. */
67 throw OldFormatError ("Audio analysis file is too old");
70 for (auto i: f.node_children("Channel")) {
71 vector<AudioPoint> channel;
73 for (auto j: i->node_children("Point")) {
74 channel.push_back (AudioPoint(j));
77 _data.push_back (channel);
80 for (auto i: f.node_children ("SamplePeak")) {
81 _sample_peak.push_back (
83 dcp::raw_convert<float>(i->content()), DCPTime(i->number_attribute<Frame>("Time"))
88 for (auto i: f.node_children ("TruePeak")) {
89 _true_peak.push_back (dcp::raw_convert<float> (i->content ()));
92 _integrated_loudness = f.optional_number_child<float> ("IntegratedLoudness");
93 _loudness_range = f.optional_number_child<float> ("LoudnessRange");
95 _analysis_gain = f.optional_number_child<double> ("AnalysisGain");
96 _samples_per_point = f.number_child<int64_t> ("SamplesPerPoint");
97 _sample_rate = f.number_child<int64_t> ("SampleRate");
99 _leqm = f.optional_number_child<double>("Leqm");
103 AudioAnalysis::add_point (int c, AudioPoint const & p)
105 DCPOMATIC_ASSERT (c < channels ());
106 _data[c].push_back (p);
110 AudioAnalysis::get_point (int c, int p) const
112 DCPOMATIC_ASSERT (p < points (c));
117 AudioAnalysis::channels () const
119 return _data.size ();
123 AudioAnalysis::points (int c) const
125 DCPOMATIC_ASSERT (c < channels ());
126 return _data[c].size ();
130 AudioAnalysis::write (boost::filesystem::path filename)
132 shared_ptr<xmlpp::Document> doc (new xmlpp::Document);
133 xmlpp::Element* root = doc->create_root_node ("AudioAnalysis");
135 root->add_child("Version")->add_child_text (raw_convert<string> (_current_state_version));
137 for (auto& i: _data) {
138 xmlpp::Element* channel = root->add_child ("Channel");
140 j.as_xml (channel->add_child ("Point"));
144 for (size_t i = 0; i < _sample_peak.size(); ++i) {
145 xmlpp::Element* n = root->add_child("SamplePeak");
146 n->add_child_text (raw_convert<string> (_sample_peak[i].peak));
147 n->set_attribute ("Time", raw_convert<string> (_sample_peak[i].time.get()));
150 for (auto i: _true_peak) {
151 root->add_child("TruePeak")->add_child_text (raw_convert<string> (i));
154 if (_integrated_loudness) {
155 root->add_child("IntegratedLoudness")->add_child_text (raw_convert<string> (_integrated_loudness.get ()));
158 if (_loudness_range) {
159 root->add_child("LoudnessRange")->add_child_text (raw_convert<string> (_loudness_range.get ()));
162 if (_analysis_gain) {
163 root->add_child("AnalysisGain")->add_child_text (raw_convert<string> (_analysis_gain.get ()));
166 root->add_child("SamplesPerPoint")->add_child_text (raw_convert<string> (_samples_per_point));
167 root->add_child("SampleRate")->add_child_text (raw_convert<string> (_sample_rate));
170 root->add_child("Leqm")->add_child_text(raw_convert<string>(*_leqm));
173 doc->write_to_file_formatted (filename.string ());
177 AudioAnalysis::gain_correction (shared_ptr<const Playlist> playlist)
179 if (playlist->content().size() == 1 && analysis_gain ()) {
180 /* In this case we know that the analysis was of a single piece of content and
181 we know that content's gain when the analysis was run. Hence we can work out
182 what correction is now needed to make it look `right'.
184 DCPOMATIC_ASSERT (playlist->content().front()->audio);
185 return playlist->content().front()->audio->gain() - analysis_gain().get ();
191 /** @return Peak across all channels, and the channel number it is on */
192 pair<AudioAnalysis::PeakTime, int>
193 AudioAnalysis::overall_sample_peak () const
195 DCPOMATIC_ASSERT (!_sample_peak.empty ());
197 optional<PeakTime> pt;
200 for (size_t i = 0; i < _sample_peak.size(); ++i) {
201 if (!pt || _sample_peak[i].peak > pt->peak) {
202 pt = _sample_peak[i];
207 return make_pair (pt.get(), c);
211 AudioAnalysis::overall_true_peak () const
215 for (auto i: _true_peak) {