Move raw_convert into libdcp.
[dcpomatic.git] / src / lib / audio_analysis.cc
1 /*
2     Copyright (C) 2012-2015 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::list;
43 using boost::shared_ptr;
44 using boost::dynamic_pointer_cast;
45 using dcp::raw_convert;
46
47 AudioAnalysis::AudioAnalysis (int channels)
48 {
49         _data.resize (channels);
50 }
51
52 AudioAnalysis::AudioAnalysis (boost::filesystem::path filename)
53 {
54         cxml::Document f ("AudioAnalysis");
55         f.read_file (filename);
56
57         BOOST_FOREACH (cxml::NodePtr i, f.node_children ("Channel")) {
58                 vector<AudioPoint> channel;
59
60                 BOOST_FOREACH (cxml::NodePtr j, i->node_children ("Point")) {
61                         channel.push_back (AudioPoint (j));
62                 }
63
64                 _data.push_back (channel);
65         }
66
67         _sample_peak = f.optional_number_child<float> ("Peak");
68         if (!_sample_peak) {
69                 /* New key */
70                 _sample_peak = f.optional_number_child<float> ("SamplePeak");
71         }
72
73         if (f.optional_number_child<DCPTime::Type> ("PeakTime")) {
74                 _sample_peak_time = DCPTime (f.number_child<DCPTime::Type> ("PeakTime"));
75         } else if (f.optional_number_child<DCPTime::Type> ("SamplePeakTime")) {
76                 _sample_peak_time = DCPTime (f.number_child<DCPTime::Type> ("SamplePeakTime"));
77         }
78
79         _true_peak = f.optional_number_child<float> ("TruePeak");
80         _integrated_loudness = f.optional_number_child<float> ("IntegratedLoudness");
81         _loudness_range = f.optional_number_child<float> ("LoudnessRange");
82
83         _analysis_gain = f.optional_number_child<double> ("AnalysisGain");
84 }
85
86 void
87 AudioAnalysis::add_point (int c, AudioPoint const & p)
88 {
89         DCPOMATIC_ASSERT (c < channels ());
90         _data[c].push_back (p);
91 }
92
93 AudioPoint
94 AudioAnalysis::get_point (int c, int p) const
95 {
96         DCPOMATIC_ASSERT (p < points (c));
97         return _data[c][p];
98 }
99
100 int
101 AudioAnalysis::channels () const
102 {
103         return _data.size ();
104 }
105
106 int
107 AudioAnalysis::points (int c) const
108 {
109         DCPOMATIC_ASSERT (c < channels ());
110         return _data[c].size ();
111 }
112
113 void
114 AudioAnalysis::write (boost::filesystem::path filename)
115 {
116         shared_ptr<xmlpp::Document> doc (new xmlpp::Document);
117         xmlpp::Element* root = doc->create_root_node ("AudioAnalysis");
118
119         BOOST_FOREACH (vector<AudioPoint>& i, _data) {
120                 xmlpp::Element* channel = root->add_child ("Channel");
121                 BOOST_FOREACH (AudioPoint& j, i) {
122                         j.as_xml (channel->add_child ("Point"));
123                 }
124         }
125
126         if (_sample_peak) {
127                 root->add_child("SamplePeak")->add_child_text (raw_convert<string> (_sample_peak.get ()));
128                 root->add_child("SamplePeakTime")->add_child_text (raw_convert<string> (_sample_peak_time.get().get ()));
129         }
130
131         if (_true_peak) {
132                 root->add_child("TruePeak")->add_child_text (raw_convert<string> (_true_peak.get ()));
133         }
134
135         if (_integrated_loudness) {
136                 root->add_child("IntegratedLoudness")->add_child_text (raw_convert<string> (_integrated_loudness.get ()));
137         }
138
139         if (_loudness_range) {
140                 root->add_child("LoudnessRange")->add_child_text (raw_convert<string> (_loudness_range.get ()));
141         }
142
143         if (_analysis_gain) {
144                 root->add_child("AnalysisGain")->add_child_text (raw_convert<string> (_analysis_gain.get ()));
145         }
146
147         doc->write_to_file_formatted (filename.string ());
148 }
149
150 float
151 AudioAnalysis::gain_correction (shared_ptr<const Playlist> playlist)
152 {
153         if (playlist->content().size() == 1 && analysis_gain ()) {
154                 /* In this case we know that the analysis was of a single piece of content and
155                    we know that content's gain when the analysis was run.  Hence we can work out
156                    what correction is now needed to make it look `right'.
157                 */
158                 DCPOMATIC_ASSERT (playlist->content().front()->audio);
159                 return playlist->content().front()->audio->gain() - analysis_gain().get ();
160         }
161
162         return 0.0f;
163 }