Warning fixes.
[dcpomatic.git] / src / lib / audio_analysis.cc
1 /*
2     Copyright (C) 2012-2016 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
50 int const AudioAnalysis::_current_state_version = 2;
51
52 AudioAnalysis::AudioAnalysis (int channels)
53 {
54         _data.resize (channels);
55 }
56
57 AudioAnalysis::AudioAnalysis (boost::filesystem::path filename)
58 {
59         cxml::Document f ("AudioAnalysis");
60         f.read_file (filename);
61
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");
65         }
66
67         BOOST_FOREACH (cxml::NodePtr i, f.node_children ("Channel")) {
68                 vector<AudioPoint> channel;
69
70                 BOOST_FOREACH (cxml::NodePtr j, i->node_children ("Point")) {
71                         channel.push_back (AudioPoint (j));
72                 }
73
74                 _data.push_back (channel);
75         }
76
77         BOOST_FOREACH (cxml::ConstNodePtr i, f.node_children ("SamplePeak")) {
78                 _sample_peak.push_back (
79                         PeakTime (
80                                 dcp::raw_convert<float>(i->content()), DCPTime(i->number_attribute<Frame>("Time"))
81                                 )
82                         );
83         }
84
85         BOOST_FOREACH (cxml::ConstNodePtr i, f.node_children ("TruePeak")) {
86                 _true_peak.push_back (dcp::raw_convert<float> (i->content ()));
87         }
88
89         _integrated_loudness = f.optional_number_child<float> ("IntegratedLoudness");
90         _loudness_range = f.optional_number_child<float> ("LoudnessRange");
91
92         _analysis_gain = f.optional_number_child<double> ("AnalysisGain");
93 }
94
95 void
96 AudioAnalysis::add_point (int c, AudioPoint const & p)
97 {
98         DCPOMATIC_ASSERT (c < channels ());
99         _data[c].push_back (p);
100 }
101
102 AudioPoint
103 AudioAnalysis::get_point (int c, int p) const
104 {
105         DCPOMATIC_ASSERT (p < points (c));
106         return _data[c][p];
107 }
108
109 int
110 AudioAnalysis::channels () const
111 {
112         return _data.size ();
113 }
114
115 int
116 AudioAnalysis::points (int c) const
117 {
118         DCPOMATIC_ASSERT (c < channels ());
119         return _data[c].size ();
120 }
121
122 void
123 AudioAnalysis::write (boost::filesystem::path filename)
124 {
125         shared_ptr<xmlpp::Document> doc (new xmlpp::Document);
126         xmlpp::Element* root = doc->create_root_node ("AudioAnalysis");
127
128         root->add_child("Version")->add_child_text (raw_convert<string> (_current_state_version));
129
130         BOOST_FOREACH (vector<AudioPoint>& i, _data) {
131                 xmlpp::Element* channel = root->add_child ("Channel");
132                 BOOST_FOREACH (AudioPoint& j, i) {
133                         j.as_xml (channel->add_child ("Point"));
134                 }
135         }
136
137         for (size_t i = 0; i < _sample_peak.size(); ++i) {
138                 xmlpp::Element* n = root->add_child("SamplePeak");
139                 n->add_child_text (raw_convert<string> (_sample_peak[i].peak));
140                 n->set_attribute ("Time", raw_convert<string> (_sample_peak[i].time.get()));
141         }
142
143         BOOST_FOREACH (float i, _true_peak) {
144                 root->add_child("TruePeak")->add_child_text (raw_convert<string> (i));
145         }
146
147         if (_integrated_loudness) {
148                 root->add_child("IntegratedLoudness")->add_child_text (raw_convert<string> (_integrated_loudness.get ()));
149         }
150
151         if (_loudness_range) {
152                 root->add_child("LoudnessRange")->add_child_text (raw_convert<string> (_loudness_range.get ()));
153         }
154
155         if (_analysis_gain) {
156                 root->add_child("AnalysisGain")->add_child_text (raw_convert<string> (_analysis_gain.get ()));
157         }
158
159         doc->write_to_file_formatted (filename.string ());
160 }
161
162 float
163 AudioAnalysis::gain_correction (shared_ptr<const Playlist> playlist)
164 {
165         if (playlist->content().size() == 1 && analysis_gain ()) {
166                 /* In this case we know that the analysis was of a single piece of content and
167                    we know that content's gain when the analysis was run.  Hence we can work out
168                    what correction is now needed to make it look `right'.
169                 */
170                 DCPOMATIC_ASSERT (playlist->content().front()->audio);
171                 return playlist->content().front()->audio->gain() - analysis_gain().get ();
172         }
173
174         return 0.0f;
175 }
176
177 /** @return Peak across all channels, and the channel number it is on */
178 pair<AudioAnalysis::PeakTime, int>
179 AudioAnalysis::overall_sample_peak () const
180 {
181         DCPOMATIC_ASSERT (!_sample_peak.empty ());
182
183         optional<PeakTime> pt;
184         int c = 0;
185
186         for (size_t i = 0; i < _sample_peak.size(); ++i) {
187                 if (!pt || _sample_peak[i].peak > pt->peak) {
188                         pt = _sample_peak[i];
189                         c = i;
190                 }
191         }
192
193         return make_pair (pt.get(), c);
194 }
195
196 optional<float>
197 AudioAnalysis::overall_true_peak () const
198 {
199         optional<float> p;
200
201         BOOST_FOREACH (float i, _true_peak) {
202                 if (!p || i > *p) {
203                         p = i;
204                 }
205         }
206
207         return p;
208 }