Merge branch 'master' into 2.0
[dcpomatic.git] / src / wx / audio_plot.cc
1 /*
2     Copyright (C) 2013 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 <iostream>
21 #include <boost/bind.hpp>
22 #include <wx/graphics.h>
23 #include "audio_plot.h"
24 #include "lib/audio_decoder.h"
25 #include "lib/audio_analysis.h"
26 #include "wx/wx_util.h"
27
28 using std::cout;
29 using std::vector;
30 using std::list;
31 using std::max;
32 using std::min;
33 using boost::bind;
34 using boost::shared_ptr;
35
36 int const AudioPlot::_minimum = -70;
37 int const AudioPlot::max_smoothing = 128;
38
39 AudioPlot::AudioPlot (wxWindow* parent)
40         : wxPanel (parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE)
41         , _gain (0)
42         , _smoothing (max_smoothing / 2)
43         , _message (_("Please wait; audio is being analysed..."))
44 {
45 #ifndef __WXOSX__       
46         SetDoubleBuffered (true);
47 #endif  
48
49         for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
50                 _channel_visible[i] = false;
51         }
52
53         for (int i = 0; i < AudioPoint::COUNT; ++i) {
54                 _type_visible[i] = false;
55         }
56
57         _colours.push_back (wxColour (  0,   0,   0));
58         _colours.push_back (wxColour (255,   0,   0));
59         _colours.push_back (wxColour (  0, 255,   0));
60         _colours.push_back (wxColour (139,   0, 204));
61         _colours.push_back (wxColour (  0,   0, 255));
62         _colours.push_back (wxColour (  0, 139,   0));
63         _colours.push_back (wxColour (  0,   0, 139));
64         _colours.push_back (wxColour (255, 255,   0));
65
66 #if MAX_AUDIO_CHANNELS != 8
67 #warning AudioPlot::AudioPlot is expecting the wrong MAX_AUDIO_CHANNELS
68 #endif  
69         
70         Bind (wxEVT_PAINT, boost::bind (&AudioPlot::paint, this));
71         
72         SetMinSize (wxSize (640, 512));
73 }
74
75 void
76 AudioPlot::set_analysis (shared_ptr<AudioAnalysis> a)
77 {
78         _analysis = a;
79
80         for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) {
81                 _channel_visible[i] = false;
82         }
83
84         for (int i = 0; i < AudioPoint::COUNT; ++i) {
85                 _type_visible[i] = false;
86         }
87         
88         Refresh ();
89 }
90
91 void
92 AudioPlot::set_channel_visible (int c, bool v)
93 {
94         _channel_visible[c] = v;
95         Refresh ();
96 }
97
98 void
99 AudioPlot::set_type_visible (int t, bool v)
100 {
101         _type_visible[t] = v;
102         Refresh ();
103 }
104
105 void
106 AudioPlot::set_message (wxString s)
107 {
108         _message = s;
109         Refresh ();
110 }
111
112 void
113 AudioPlot::paint ()
114 {
115         wxPaintDC dc (this);
116
117         wxGraphicsContext* gc = wxGraphicsContext::Create (dc);
118         if (!gc) {
119                 return;
120         }
121
122         if (!_analysis || _analysis->channels() == 0) {
123                 gc->SetFont (gc->CreateFont (*wxNORMAL_FONT));
124                 gc->DrawText (_message, 32, 32);
125                 return;
126         }
127
128         wxGraphicsPath grid = gc->CreatePath ();
129         gc->SetFont (gc->CreateFont (*wxSMALL_FONT));
130         wxDouble db_label_height;
131         wxDouble db_label_descent;
132         wxDouble db_label_leading;
133         gc->GetTextExtent (wxT ("-80dB"), &_db_label_width, &db_label_height, &db_label_descent, &db_label_leading);
134
135         _db_label_width += 8;
136         
137         int const data_width = GetSize().GetWidth() - _db_label_width;
138         /* Assume all channels have the same number of points */
139         _x_scale = data_width / float (_analysis->points (0));
140         _height = GetSize().GetHeight ();
141         _y_origin = 32;
142         _y_scale = (_height - _y_origin) / -_minimum;
143
144         for (int i = _minimum; i <= 0; i += 10) {
145                 int const y = (_height - (i - _minimum) * _y_scale) - _y_origin;
146                 grid.MoveToPoint (_db_label_width - 4, y);
147                 grid.AddLineToPoint (_db_label_width + data_width, y);
148                 gc->DrawText (std_to_wx (String::compose ("%1dB", i)), 0, y - (db_label_height / 2));
149         }
150
151         gc->SetPen (*wxLIGHT_GREY_PEN);
152         gc->StrokePath (grid);
153
154         gc->DrawText (_("DCPTime"), data_width, _height - _y_origin + db_label_height / 2);
155         
156         if (_type_visible[AudioPoint::PEAK]) {
157                 for (int c = 0; c < MAX_AUDIO_CHANNELS; ++c) {
158                         wxGraphicsPath p = gc->CreatePath ();
159                         if (_channel_visible[c] && c < _analysis->channels()) {
160                                 plot_peak (p, c);
161                         }
162                         wxColour const col = _colours[c];
163                         gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (col.Red(), col.Green(), col.Blue(), col.Alpha() / 2), 1, wxPENSTYLE_SOLID));
164                         gc->StrokePath (p);
165                 }
166         }
167
168         if (_type_visible[AudioPoint::RMS]) {
169                 for (int c = 0; c < MAX_AUDIO_CHANNELS; ++c) {
170                         wxGraphicsPath p = gc->CreatePath ();
171                         if (_channel_visible[c] && c < _analysis->channels()) {
172                                 plot_rms (p, c);
173                         }
174                         wxColour const col = _colours[c];
175                         gc->SetPen (*wxThePenList->FindOrCreatePen (col, 1, wxPENSTYLE_SOLID));
176                         gc->StrokePath (p);
177                 }
178         }
179
180         wxGraphicsPath axes = gc->CreatePath ();
181         axes.MoveToPoint (_db_label_width, 0);
182         axes.AddLineToPoint (_db_label_width, _height - _y_origin);
183         axes.AddLineToPoint (_db_label_width + data_width, _height - _y_origin);
184         gc->SetPen (*wxBLACK_PEN);
185         gc->StrokePath (axes);
186
187         delete gc;
188 }
189
190 float
191 AudioPlot::y_for_linear (float p) const
192 {
193         return _height - (20 * log10(p) - _minimum + _gain) * _y_scale - _y_origin;
194 }
195
196 void
197 AudioPlot::plot_peak (wxGraphicsPath& path, int channel) const
198 {
199         if (_analysis->points (channel) == 0) {
200                 return;
201         }
202         
203         path.MoveToPoint (_db_label_width, y_for_linear (_analysis->get_point(channel, 0)[AudioPoint::PEAK]));
204
205         float peak = 0;
206         int const N = _analysis->points(channel);
207         for (int i = 0; i < N; ++i) {
208                 float const p = _analysis->get_point(channel, i)[AudioPoint::PEAK];
209                 peak -= 0.01f * (1 - log10 (_smoothing) / log10 (max_smoothing));
210                 if (p > peak) {
211                         peak = p;
212                 } else if (peak < 0) {
213                         peak = 0;
214                 }
215                 
216                 path.AddLineToPoint (_db_label_width + i * _x_scale, y_for_linear (peak));
217         }
218 }
219
220 void
221 AudioPlot::plot_rms (wxGraphicsPath& path, int channel) const
222 {
223         if (_analysis->points (channel) == 0) {
224                 return;
225         }
226         
227         path.MoveToPoint (_db_label_width, y_for_linear (_analysis->get_point(channel, 0)[AudioPoint::RMS]));
228
229         list<float> smoothing;
230
231         int const N = _analysis->points(channel);
232
233         float const first = _analysis->get_point(channel, 0)[AudioPoint::RMS];
234         float const last = _analysis->get_point(channel, N - 1)[AudioPoint::RMS];
235
236         int const before = _smoothing / 2;
237         int const after = _smoothing - before;
238         
239         /* Pre-load the smoothing list */
240         for (int i = 0; i < before; ++i) {
241                 smoothing.push_back (first);
242         }
243         for (int i = 0; i < after; ++i) {
244                 if (i < N) {
245                         smoothing.push_back (_analysis->get_point(channel, i)[AudioPoint::RMS]);
246                 } else {
247                         smoothing.push_back (last);
248                 }
249         }
250         
251         for (int i = 0; i < N; ++i) {
252
253                 int const next_for_window = i + after;
254
255                 if (next_for_window < N) {
256                         smoothing.push_back (_analysis->get_point(channel, i)[AudioPoint::RMS]);
257                 } else {
258                         smoothing.push_back (last);
259                 }
260
261                 smoothing.pop_front ();
262
263                 float p = 0;
264                 for (list<float>::const_iterator j = smoothing.begin(); j != smoothing.end(); ++j) {
265                         p += pow (*j, 2);
266                 }
267
268                 if (smoothing.size() > 0) {
269                         p = sqrt (p / smoothing.size ());
270                 }
271
272                 path.AddLineToPoint (_db_label_width + i * _x_scale, y_for_linear (p));
273         }
274 }
275
276 void
277 AudioPlot::set_gain (float g)
278 {
279         _gain = g;
280         Refresh ();
281 }
282
283 void
284 AudioPlot::set_smoothing (int s)
285 {
286         _smoothing = s;
287         Refresh ();
288 }