Rename TYPE_DEBUG_PLAYER to TYPE_DEBUG_VIDEO_VIEW.
[dcpomatic.git] / src / lib / audio_analysis.cc
1 /*
2     Copyright (C) 2012-2018 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
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.
10
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.
15
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/>.
18
19 */
20
21 #include "audio_analysis.h"
22 #include "cross.h"
23 #include "util.h"
24 #include "playlist.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>
30 #include <stdint.h>
31 #include <cmath>
32 #include <cstdio>
33 #include <iostream>
34 #include <inttypes.h>
35
36 using std::ostream;
37 using std::istream;
38 using std::string;
39 using std::vector;
40 using std::cout;
41 using std::max;
42 using std::pair;
43 using std::make_pair;
44 using std::list;
45 using boost::shared_ptr;
46 using boost::optional;
47 using boost::dynamic_pointer_cast;
48 using dcp::raw_convert;
49 using namespace dcpomatic;
50
51 int const AudioAnalysis::_current_state_version = 3;
52
53 AudioAnalysis::AudioAnalysis (int channels)
54 {
55         _data.resize (channels);
56 }
57
58 AudioAnalysis::AudioAnalysis (boost::filesystem::path filename)
59 {
60         cxml::Document f ("AudioAnalysis");
61         f.read_file (filename);
62
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");
66         }
67
68         BOOST_FOREACH (cxml::NodePtr i, f.node_children ("Channel")) {
69                 vector<AudioPoint> channel;
70
71                 BOOST_FOREACH (cxml::NodePtr j, i->node_children ("Point")) {
72                         channel.push_back (AudioPoint (j));
73                 }
74
75                 _data.push_back (channel);
76         }
77
78         BOOST_FOREACH (cxml::ConstNodePtr i, f.node_children ("SamplePeak")) {
79                 _sample_peak.push_back (
80                         PeakTime (
81                                 dcp::raw_convert<float>(i->content()), DCPTime(i->number_attribute<Frame>("Time"))
82                                 )
83                         );
84         }
85
86         BOOST_FOREACH (cxml::ConstNodePtr i, f.node_children ("TruePeak")) {
87                 _true_peak.push_back (dcp::raw_convert<float> (i->content ()));
88         }
89
90         _integrated_loudness = f.optional_number_child<float> ("IntegratedLoudness");
91         _loudness_range = f.optional_number_child<float> ("LoudnessRange");
92
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");
96
97         _leqm = f.optional_number_child<double>("Leqm");
98 }
99
100 void
101 AudioAnalysis::add_point (int c, AudioPoint const & p)
102 {
103         DCPOMATIC_ASSERT (c < channels ());
104         _data[c].push_back (p);
105 }
106
107 AudioPoint
108 AudioAnalysis::get_point (int c, int p) const
109 {
110         DCPOMATIC_ASSERT (p < points (c));
111         return _data[c][p];
112 }
113
114 int
115 AudioAnalysis::channels () const
116 {
117         return _data.size ();
118 }
119
120 int
121 AudioAnalysis::points (int c) const
122 {
123         DCPOMATIC_ASSERT (c < channels ());
124         return _data[c].size ();
125 }
126
127 void
128 AudioAnalysis::write (boost::filesystem::path filename)
129 {
130         shared_ptr<xmlpp::Document> doc (new xmlpp::Document);
131         xmlpp::Element* root = doc->create_root_node ("AudioAnalysis");
132
133         root->add_child("Version")->add_child_text (raw_convert<string> (_current_state_version));
134
135         BOOST_FOREACH (vector<AudioPoint>& i, _data) {
136                 xmlpp::Element* channel = root->add_child ("Channel");
137                 BOOST_FOREACH (AudioPoint& j, i) {
138                         j.as_xml (channel->add_child ("Point"));
139                 }
140         }
141
142         for (size_t i = 0; i < _sample_peak.size(); ++i) {
143                 xmlpp::Element* n = root->add_child("SamplePeak");
144                 n->add_child_text (raw_convert<string> (_sample_peak[i].peak));
145                 n->set_attribute ("Time", raw_convert<string> (_sample_peak[i].time.get()));
146         }
147
148         BOOST_FOREACH (float i, _true_peak) {
149                 root->add_child("TruePeak")->add_child_text (raw_convert<string> (i));
150         }
151
152         if (_integrated_loudness) {
153                 root->add_child("IntegratedLoudness")->add_child_text (raw_convert<string> (_integrated_loudness.get ()));
154         }
155
156         if (_loudness_range) {
157                 root->add_child("LoudnessRange")->add_child_text (raw_convert<string> (_loudness_range.get ()));
158         }
159
160         if (_analysis_gain) {
161                 root->add_child("AnalysisGain")->add_child_text (raw_convert<string> (_analysis_gain.get ()));
162         }
163
164         root->add_child("SamplesPerPoint")->add_child_text (raw_convert<string> (_samples_per_point));
165         root->add_child("SampleRate")->add_child_text (raw_convert<string> (_sample_rate));
166
167         if (_leqm) {
168                 root->add_child("Leqm")->add_child_text(raw_convert<string>(*_leqm));
169         }
170
171         doc->write_to_file_formatted (filename.string ());
172 }
173
174 float
175 AudioAnalysis::gain_correction (shared_ptr<const Playlist> playlist)
176 {
177         if (playlist->content().size() == 1 && analysis_gain ()) {
178                 /* In this case we know that the analysis was of a single piece of content and
179                    we know that content's gain when the analysis was run.  Hence we can work out
180                    what correction is now needed to make it look `right'.
181                 */
182                 DCPOMATIC_ASSERT (playlist->content().front()->audio);
183                 return playlist->content().front()->audio->gain() - analysis_gain().get ();
184         }
185
186         return 0.0f;
187 }
188
189 /** @return Peak across all channels, and the channel number it is on */
190 pair<AudioAnalysis::PeakTime, int>
191 AudioAnalysis::overall_sample_peak () const
192 {
193         DCPOMATIC_ASSERT (!_sample_peak.empty ());
194
195         optional<PeakTime> pt;
196         int c = 0;
197
198         for (size_t i = 0; i < _sample_peak.size(); ++i) {
199                 if (!pt || _sample_peak[i].peak > pt->peak) {
200                         pt = _sample_peak[i];
201                         c = i;
202                 }
203         }
204
205         return make_pair (pt.get(), c);
206 }
207
208 optional<float>
209 AudioAnalysis::overall_true_peak () const
210 {
211         optional<float> p;
212
213         BOOST_FOREACH (float i, _true_peak) {
214                 if (!p || i > *p) {
215                         p = i;
216                 }
217         }
218
219         return p;
220 }
221