Barely-functioning GL playback with new arrangement.
[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
98 void
99 AudioAnalysis::add_point (int c, AudioPoint const & p)
100 {
101         DCPOMATIC_ASSERT (c < channels ());
102         _data[c].push_back (p);
103 }
104
105 AudioPoint
106 AudioAnalysis::get_point (int c, int p) const
107 {
108         DCPOMATIC_ASSERT (p < points (c));
109         return _data[c][p];
110 }
111
112 int
113 AudioAnalysis::channels () const
114 {
115         return _data.size ();
116 }
117
118 int
119 AudioAnalysis::points (int c) const
120 {
121         DCPOMATIC_ASSERT (c < channels ());
122         return _data[c].size ();
123 }
124
125 void
126 AudioAnalysis::write (boost::filesystem::path filename)
127 {
128         shared_ptr<xmlpp::Document> doc (new xmlpp::Document);
129         xmlpp::Element* root = doc->create_root_node ("AudioAnalysis");
130
131         root->add_child("Version")->add_child_text (raw_convert<string> (_current_state_version));
132
133         BOOST_FOREACH (vector<AudioPoint>& i, _data) {
134                 xmlpp::Element* channel = root->add_child ("Channel");
135                 BOOST_FOREACH (AudioPoint& j, i) {
136                         j.as_xml (channel->add_child ("Point"));
137                 }
138         }
139
140         for (size_t i = 0; i < _sample_peak.size(); ++i) {
141                 xmlpp::Element* n = root->add_child("SamplePeak");
142                 n->add_child_text (raw_convert<string> (_sample_peak[i].peak));
143                 n->set_attribute ("Time", raw_convert<string> (_sample_peak[i].time.get()));
144         }
145
146         BOOST_FOREACH (float i, _true_peak) {
147                 root->add_child("TruePeak")->add_child_text (raw_convert<string> (i));
148         }
149
150         if (_integrated_loudness) {
151                 root->add_child("IntegratedLoudness")->add_child_text (raw_convert<string> (_integrated_loudness.get ()));
152         }
153
154         if (_loudness_range) {
155                 root->add_child("LoudnessRange")->add_child_text (raw_convert<string> (_loudness_range.get ()));
156         }
157
158         if (_analysis_gain) {
159                 root->add_child("AnalysisGain")->add_child_text (raw_convert<string> (_analysis_gain.get ()));
160         }
161
162         root->add_child("SamplesPerPoint")->add_child_text (raw_convert<string> (_samples_per_point));
163         root->add_child("SampleRate")->add_child_text (raw_convert<string> (_sample_rate));
164
165         doc->write_to_file_formatted (filename.string ());
166 }
167
168 float
169 AudioAnalysis::gain_correction (shared_ptr<const Playlist> playlist)
170 {
171         if (playlist->content().size() == 1 && analysis_gain ()) {
172                 /* In this case we know that the analysis was of a single piece of content and
173                    we know that content's gain when the analysis was run.  Hence we can work out
174                    what correction is now needed to make it look `right'.
175                 */
176                 DCPOMATIC_ASSERT (playlist->content().front()->audio);
177                 return playlist->content().front()->audio->gain() - analysis_gain().get ();
178         }
179
180         return 0.0f;
181 }
182
183 /** @return Peak across all channels, and the channel number it is on */
184 pair<AudioAnalysis::PeakTime, int>
185 AudioAnalysis::overall_sample_peak () const
186 {
187         DCPOMATIC_ASSERT (!_sample_peak.empty ());
188
189         optional<PeakTime> pt;
190         int c = 0;
191
192         for (size_t i = 0; i < _sample_peak.size(); ++i) {
193                 if (!pt || _sample_peak[i].peak > pt->peak) {
194                         pt = _sample_peak[i];
195                         c = i;
196                 }
197         }
198
199         return make_pair (pt.get(), c);
200 }
201
202 optional<float>
203 AudioAnalysis::overall_true_peak () const
204 {
205         optional<float> p;
206
207         BOOST_FOREACH (float i, _true_peak) {
208                 if (!p || i > *p) {
209                         p = i;
210                 }
211         }
212
213         return p;
214 }