e04468ba28f7e6868b854455a1259eb36c375f9f
[dcpomatic.git] / src / lib / audio_analysis.cc
1 /*
2     Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include "audio_analysis.h"
21 #include "cross.h"
22 #include "util.h"
23 #include "raw_convert.h"
24 #include "playlist.h"
25 #include "audio_content.h"
26 #include <libxml++/libxml++.h>
27 #include <boost/filesystem.hpp>
28 #include <boost/foreach.hpp>
29 #include <stdint.h>
30 #include <cmath>
31 #include <cstdio>
32 #include <iostream>
33 #include <inttypes.h>
34
35 using std::ostream;
36 using std::istream;
37 using std::string;
38 using std::vector;
39 using std::cout;
40 using std::max;
41 using std::list;
42 using boost::shared_ptr;
43 using boost::dynamic_pointer_cast;
44
45 AudioAnalysis::AudioAnalysis (int channels)
46 {
47         _data.resize (channels);
48 }
49
50 AudioAnalysis::AudioAnalysis (boost::filesystem::path filename)
51 {
52         cxml::Document f ("AudioAnalysis");
53         f.read_file (filename);
54
55         BOOST_FOREACH (cxml::NodePtr i, f.node_children ("Channel")) {
56                 vector<AudioPoint> channel;
57
58                 BOOST_FOREACH (cxml::NodePtr j, i->node_children ("Point")) {
59                         channel.push_back (AudioPoint (j));
60                 }
61
62                 _data.push_back (channel);
63         }
64
65         _sample_peak = f.optional_number_child<float> ("Peak");
66         if (!_sample_peak) {
67                 /* New key */
68                 _sample_peak = f.optional_number_child<float> ("SamplePeak");
69         }
70
71         if (f.optional_number_child<DCPTime::Type> ("PeakTime")) {
72                 _sample_peak_time = DCPTime (f.number_child<DCPTime::Type> ("PeakTime"));
73         } else if (f.optional_number_child<DCPTime::Type> ("SamplePeakTime")) {
74                 _sample_peak_time = DCPTime (f.number_child<DCPTime::Type> ("SamplePeakTime"));
75         }
76
77         _true_peak = f.optional_number_child<float> ("TruePeak");
78         _integrated_loudness = f.optional_number_child<float> ("IntegratedLoudness");
79         _loudness_range = f.optional_number_child<float> ("LoudnessRange");
80
81         _analysis_gain = f.optional_number_child<double> ("AnalysisGain");
82 }
83
84 void
85 AudioAnalysis::add_point (int c, AudioPoint const & p)
86 {
87         DCPOMATIC_ASSERT (c < channels ());
88         _data[c].push_back (p);
89 }
90
91 AudioPoint
92 AudioAnalysis::get_point (int c, int p) const
93 {
94         DCPOMATIC_ASSERT (p < points (c));
95         return _data[c][p];
96 }
97
98 int
99 AudioAnalysis::channels () const
100 {
101         return _data.size ();
102 }
103
104 int
105 AudioAnalysis::points (int c) const
106 {
107         DCPOMATIC_ASSERT (c < channels ());
108         return _data[c].size ();
109 }
110
111 void
112 AudioAnalysis::write (boost::filesystem::path filename)
113 {
114         shared_ptr<xmlpp::Document> doc (new xmlpp::Document);
115         xmlpp::Element* root = doc->create_root_node ("AudioAnalysis");
116
117         BOOST_FOREACH (vector<AudioPoint>& i, _data) {
118                 xmlpp::Element* channel = root->add_child ("Channel");
119                 BOOST_FOREACH (AudioPoint& j, i) {
120                         j.as_xml (channel->add_child ("Point"));
121                 }
122         }
123
124         if (_sample_peak) {
125                 root->add_child("SamplePeak")->add_child_text (raw_convert<string> (_sample_peak.get ()));
126                 root->add_child("SamplePeakTime")->add_child_text (raw_convert<string> (_sample_peak_time.get().get ()));
127         }
128
129         if (_true_peak) {
130                 root->add_child("TruePeak")->add_child_text (raw_convert<string> (_true_peak.get ()));
131         }
132
133         if (_integrated_loudness) {
134                 root->add_child("IntegratedLoudness")->add_child_text (raw_convert<string> (_integrated_loudness.get ()));
135         }
136
137         if (_loudness_range) {
138                 root->add_child("LoudnessRange")->add_child_text (raw_convert<string> (_loudness_range.get ()));
139         }
140
141         if (_analysis_gain) {
142                 root->add_child("AnalysisGain")->add_child_text (raw_convert<string> (_analysis_gain.get ()));
143         }
144
145         doc->write_to_file_formatted (filename.string ());
146 }
147
148 float
149 AudioAnalysis::gain_correction (shared_ptr<const Playlist> playlist)
150 {
151         if (playlist->content().size() == 1 && analysis_gain ()) {
152                 /* In this case we know that the analysis was of a single piece of content and
153                    we know that content's gain when the analysis was run.  Hence we can work out
154                    what correction is now needed to make it look `right'.
155                 */
156                 DCPOMATIC_ASSERT (playlist->content().front()->audio);
157                 return playlist->content().front()->audio->gain() - analysis_gain().get ();
158         }
159
160         return 0.0f;
161 }