2 Copyright (C) 2012-2021 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/>.
22 #include "audio_analysis.h"
23 #include "audio_content.h"
27 #include <dcp/raw_convert.h>
28 #include <dcp/warnings.h>
29 LIBDCP_DISABLE_WARNINGS
30 #include <libxml++/libxml++.h>
31 LIBDCP_ENABLE_WARNINGS
32 #include <boost/filesystem.hpp>
41 using std::dynamic_pointer_cast;
45 using std::make_shared;
49 using std::shared_ptr;
52 using boost::optional;
53 using dcp::raw_convert;
54 using namespace dcpomatic;
57 int const AudioAnalysis::_current_state_version = 3;
60 AudioAnalysis::AudioAnalysis (int channels)
62 _data.resize (channels);
66 AudioAnalysis::AudioAnalysis (boost::filesystem::path filename)
68 cxml::Document f ("AudioAnalysis");
69 f.read_file(dcp::filesystem::fix_long_path(filename));
71 if (f.optional_number_child<int>("Version").get_value_or(1) < _current_state_version) {
72 /* Too old. Throw an exception so that this analysis is re-run. */
73 throw OldFormatError ("Audio analysis file is too old");
76 for (auto i: f.node_children("Channel")) {
77 vector<AudioPoint> channel;
79 for (auto j: i->node_children("Point")) {
80 channel.push_back (AudioPoint(j));
83 _data.push_back (channel);
86 for (auto i: f.node_children ("SamplePeak")) {
87 _sample_peak.push_back (
89 dcp::raw_convert<float>(i->content()), DCPTime(i->number_attribute<Frame>("Time"))
94 for (auto i: f.node_children("TruePeak")) {
95 _true_peak.push_back (dcp::raw_convert<float>(i->content()));
98 _integrated_loudness = f.optional_number_child<float>("IntegratedLoudness");
99 _loudness_range = f.optional_number_child<float>("LoudnessRange");
101 _analysis_gain = f.optional_number_child<double>("AnalysisGain");
102 _samples_per_point = f.number_child<int64_t>("SamplesPerPoint");
103 _sample_rate = f.number_child<int64_t>("SampleRate");
105 _leqm = f.optional_number_child<double>("Leqm");
110 AudioAnalysis::add_point (int c, AudioPoint const & p)
112 DCPOMATIC_ASSERT (c < channels ());
113 _data[c].push_back (p);
118 AudioAnalysis::get_point (int c, int p) const
120 DCPOMATIC_ASSERT (p < points(c));
126 AudioAnalysis::channels () const
128 return _data.size ();
133 AudioAnalysis::points (int c) const
135 DCPOMATIC_ASSERT (c < channels());
136 return _data[c].size ();
141 AudioAnalysis::write (boost::filesystem::path filename)
143 auto doc = make_shared<xmlpp::Document>();
144 xmlpp::Element* root = doc->create_root_node ("AudioAnalysis");
146 root->add_child("Version")->add_child_text(raw_convert<string>(_current_state_version));
148 for (auto& i: _data) {
149 auto channel = root->add_child ("Channel");
151 j.as_xml (channel->add_child ("Point"));
155 for (size_t i = 0; i < _sample_peak.size(); ++i) {
156 auto n = root->add_child("SamplePeak");
157 n->add_child_text (raw_convert<string> (_sample_peak[i].peak));
158 n->set_attribute ("Time", raw_convert<string> (_sample_peak[i].time.get()));
161 for (auto i: _true_peak) {
162 root->add_child("TruePeak")->add_child_text (raw_convert<string> (i));
165 if (_integrated_loudness) {
166 root->add_child("IntegratedLoudness")->add_child_text (raw_convert<string> (_integrated_loudness.get ()));
169 if (_loudness_range) {
170 root->add_child("LoudnessRange")->add_child_text (raw_convert<string> (_loudness_range.get ()));
173 if (_analysis_gain) {
174 root->add_child("AnalysisGain")->add_child_text (raw_convert<string> (_analysis_gain.get ()));
177 root->add_child("SamplesPerPoint")->add_child_text (raw_convert<string> (_samples_per_point));
178 root->add_child("SampleRate")->add_child_text (raw_convert<string> (_sample_rate));
181 root->add_child("Leqm")->add_child_text(raw_convert<string>(*_leqm));
184 doc->write_to_file_formatted (filename.string ());
189 AudioAnalysis::gain_correction (shared_ptr<const Playlist> playlist)
191 if (playlist->content().size() == 1 && analysis_gain ()) {
192 /* In this case we know that the analysis was of a single piece of content and
193 we know that content's gain when the analysis was run. Hence we can work out
194 what correction is now needed to make it look `right'.
196 DCPOMATIC_ASSERT (playlist->content().front()->audio);
197 return playlist->content().front()->audio->gain() - analysis_gain().get();
204 /** @return Peak across all channels, and the channel number it is on */
205 pair<AudioAnalysis::PeakTime, int>
206 AudioAnalysis::overall_sample_peak () const
208 DCPOMATIC_ASSERT (!_sample_peak.empty());
210 optional<PeakTime> pt;
213 for (size_t i = 0; i < _sample_peak.size(); ++i) {
214 if (!pt || _sample_peak[i].peak > pt->peak) {
215 pt = _sample_peak[i];
220 return make_pair (pt.get(), c);
225 AudioAnalysis::overall_true_peak () const
229 for (auto i: _true_peak) {