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 auto const time = number_attribute<Frame>(i, "Time", "time");
88 _sample_peak.push_back(PeakTime(dcp::raw_convert<float>(i->content()), DCPTime(time)));
91 for (auto i: f.node_children("TruePeak")) {
92 _true_peak.push_back (dcp::raw_convert<float>(i->content()));
95 _integrated_loudness = f.optional_number_child<float>("IntegratedLoudness");
96 _loudness_range = f.optional_number_child<float>("LoudnessRange");
98 _analysis_gain = f.optional_number_child<double>("AnalysisGain");
99 _samples_per_point = f.number_child<int64_t>("SamplesPerPoint");
100 _sample_rate = f.number_child<int64_t>("SampleRate");
102 _leqm = f.optional_number_child<double>("Leqm");
107 AudioAnalysis::add_point (int c, AudioPoint const & p)
109 DCPOMATIC_ASSERT (c < channels ());
110 _data[c].push_back (p);
115 AudioAnalysis::get_point (int c, int p) const
117 DCPOMATIC_ASSERT (p < points(c));
123 AudioAnalysis::channels () const
125 return _data.size ();
130 AudioAnalysis::points (int c) const
132 DCPOMATIC_ASSERT (c < channels());
133 return _data[c].size ();
138 AudioAnalysis::write (boost::filesystem::path filename)
140 auto doc = make_shared<xmlpp::Document>();
141 xmlpp::Element* root = doc->create_root_node ("AudioAnalysis");
143 root->add_child("Version")->add_child_text(raw_convert<string>(_current_state_version));
145 for (auto& i: _data) {
146 auto channel = root->add_child ("Channel");
148 j.as_xml (channel->add_child ("Point"));
152 for (size_t i = 0; i < _sample_peak.size(); ++i) {
153 auto n = root->add_child("SamplePeak");
154 n->add_child_text (raw_convert<string> (_sample_peak[i].peak));
155 n->set_attribute("time", raw_convert<string> (_sample_peak[i].time.get()));
158 for (auto i: _true_peak) {
159 root->add_child("TruePeak")->add_child_text (raw_convert<string> (i));
162 if (_integrated_loudness) {
163 root->add_child("IntegratedLoudness")->add_child_text (raw_convert<string> (_integrated_loudness.get ()));
166 if (_loudness_range) {
167 root->add_child("LoudnessRange")->add_child_text (raw_convert<string> (_loudness_range.get ()));
170 if (_analysis_gain) {
171 root->add_child("AnalysisGain")->add_child_text (raw_convert<string> (_analysis_gain.get ()));
174 root->add_child("SamplesPerPoint")->add_child_text (raw_convert<string> (_samples_per_point));
175 root->add_child("SampleRate")->add_child_text (raw_convert<string> (_sample_rate));
178 root->add_child("Leqm")->add_child_text(raw_convert<string>(*_leqm));
181 doc->write_to_file_formatted (filename.string ());
186 AudioAnalysis::gain_correction (shared_ptr<const Playlist> playlist)
188 if (playlist->content().size() == 1 && analysis_gain ()) {
189 /* In this case we know that the analysis was of a single piece of content and
190 we know that content's gain when the analysis was run. Hence we can work out
191 what correction is now needed to make it look `right'.
193 DCPOMATIC_ASSERT (playlist->content().front()->audio);
194 return playlist->content().front()->audio->gain() - analysis_gain().get();
201 /** @return Peak across all channels, and the channel number it is on */
202 pair<AudioAnalysis::PeakTime, int>
203 AudioAnalysis::overall_sample_peak () const
205 DCPOMATIC_ASSERT (!_sample_peak.empty());
207 optional<PeakTime> pt;
210 for (size_t i = 0; i < _sample_peak.size(); ++i) {
211 if (!pt || _sample_peak[i].peak > pt->peak) {
212 pt = _sample_peak[i];
217 return make_pair (pt.get(), c);
222 AudioAnalysis::overall_true_peak () const
226 for (auto i: _true_peak) {