exported waveform annotation
[ardour.git] / gtk2_ardour / export_report.cc
1 /*
2  * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  */
18
19 #include <pangomm/layout.h>
20 #include <gtkmm/label.h>
21 #include <gtkmm/stock.h>
22
23 #include "gtkmm2ext/utils.h"
24 #include "canvas/utils.h"
25 #include "canvas/colors.h"
26
27 #include "ui_config.h"
28 #include "export_report.h"
29
30 #include "i18n.h"
31
32 using namespace Gtk;
33 using namespace ARDOUR;
34
35 ExportReport::ExportReport (StatusPtr s)
36         : ArdourDialog (_("Export Report/Analysis"))
37         , status (s)
38 {
39
40         AnalysisResults & ar = status->result_map;
41
42         std::vector<double> dashes;
43         dashes.push_back (3.0);
44         dashes.push_back (5.0);
45
46         for (AnalysisResults::iterator i = ar.begin (); i != ar.end (); ++i) {
47                 Label *l;
48                 VBox *vb = manage (new VBox ());
49                 vb->set_spacing (6);
50
51                 l = manage (new Label (string_compose (_("File: %1"), i->first)));
52                 vb->pack_start (*l);
53
54                 ExportAnalysisPtr p = i->second;
55
56                 if (i->second->have_loudness) {
57                         /* EBU R128 loudness numerics and histogram */
58                         int w, h;
59                         Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create (get_pango_context ());
60                         Cairo::RefPtr<Cairo::ImageSurface> nums = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, 256, 128);
61                         Cairo::RefPtr<Cairo::ImageSurface> hist = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, 540, 128);
62
63                         Cairo::RefPtr<Cairo::Context> cr = Cairo::Context::create (nums);
64                         cr->rectangle (0, 0, 256, 128);
65                         cr->set_source_rgba (0, 0, 0, 1.0);
66                         cr->fill ();
67
68                         layout->set_font_description (UIConfiguration::instance ().get_SmallFont ());
69                         layout->set_alignment (Pango::ALIGN_LEFT);
70                         layout->set_text (_("Ebu R128"));
71                         layout->get_pixel_size (w, h);
72
73                         cr->save ();
74                         cr->set_source_rgba (.5, .5, .5, 1.0);
75                         cr->move_to (6, rint (64 + w * .5));
76                         cr->rotate (M_PI / -2.0);
77                         layout->show_in_cairo_context (cr);
78                         cr->restore ();
79
80                         cr->set_source_rgba (.7, .7, .7, 1.0);
81
82                         if (p->loudness == -200 &&  p->loudness_range == 0) {
83                                 layout->set_font_description (UIConfiguration::instance ().get_LargeFont ());
84                                 layout->set_text (string_compose (_("not available"), std::setprecision (1), std::fixed,  p->loudness));
85                                 layout->get_pixel_size (w, h);
86                                 cr->move_to (rint (128 - w * .5), rint (64 - h));
87                                 layout->show_in_cairo_context (cr);
88
89                                 layout->set_font_description (UIConfiguration::instance ().get_SmallFont ());
90                                 layout->set_text (_("(too short integration time)"));
91                                 layout->get_pixel_size (w, h);
92                                 cr->move_to (rint (128 - w * .5), rint (64 + h));
93                                 layout->show_in_cairo_context (cr);
94
95                         } else {
96                                 int y0 = 6;
97                                 layout->set_font_description (UIConfiguration::instance ().get_SmallFont ());
98                                 layout->set_text (string_compose (_("Loudness:"), std::setprecision (1), std::fixed,  p->loudness));
99                                 layout->get_pixel_size (w, h);
100                                 cr->move_to (rint (128 - w * .5), y0);
101                                 layout->show_in_cairo_context (cr);
102                                 y0 += h * 1.25;
103
104                                 layout->set_font_description (UIConfiguration::instance ().get_LargeFont ());
105                                 layout->set_text (string_compose (_("%1%2%3 LUFS"), std::setprecision (1), std::fixed,  p->loudness));
106                                 layout->get_pixel_size (w, h);
107                                 cr->move_to (rint (128 - w * .5), y0);
108                                 layout->show_in_cairo_context (cr);
109                                 y0 += h * 1.5;
110
111                                 layout->set_font_description (UIConfiguration::instance ().get_SmallFont ());
112                                 layout->set_text (string_compose (_("Loudness Range:"), std::setprecision (1), std::fixed,  p->loudness));
113                                 layout->get_pixel_size (w, h);
114                                 cr->move_to (rint (128 - w * .5), y0);
115                                 layout->show_in_cairo_context (cr);
116                                 y0 += h * 1.25;
117
118                                 layout->set_font_description (UIConfiguration::instance ().get_LargeFont ());
119                                 layout->set_text (string_compose (_("%1%2%3 LU"), std::setprecision (1), std::fixed, p->loudness_range));
120                                 layout->get_pixel_size (w, h);
121                                 cr->move_to (rint (128 - w * .5), y0);
122                                 layout->show_in_cairo_context (cr);
123                         }
124                         nums->flush ();
125
126                         /* draw loudness histogram */
127                         cr = Cairo::Context::create (hist);
128                         cr->rectangle (0, 0, 540, 128);
129                         cr->set_source_rgba (0, 0, 0, 1.0);
130                         cr->fill ();
131
132                         layout->set_font_description (UIConfiguration::instance ().get_SmallMonospaceFont ());
133                         layout->set_alignment (Pango::ALIGN_LEFT);
134                         layout->set_text (_("LUFS"));
135                         cr->move_to (6, 6);
136                         cr->set_source_rgba (.9, .9, .9, 1.0);
137                         layout->show_in_cairo_context (cr);
138
139                         for (int g = -53; g <= -8; g += 5) {
140                                 // grid-lines. [110] -59LUFS .. [650]: -5 LUFS
141                                 layout->set_text (string_compose ("%1", g));
142                                 layout->get_pixel_size (w, h);
143
144                                 cr->save ();
145                                 cr->set_source_rgba (.9, .9, .9, 1.0);
146                                 cr->move_to (rint ((g + 59.0) * 10.0 - h * .5), w + 6.0);
147                                 cr->rotate (M_PI / -2.0);
148                                 layout->show_in_cairo_context (cr);
149                                 cr->restore ();
150
151                                 cr->save ();
152                                 cr->set_source_rgba (.3, .3, .3, 1.0);
153                                 cr->set_dash (dashes, 1.0);
154                                 cr->set_line_cap (Cairo::LINE_CAP_ROUND);
155                                 cr->move_to (rint ((g + 59.0) * 10.0) + .5, w + 8.0);
156                                 cr->line_to (rint ((g + 59.0) * 10.0) + .5, 128.0);
157                                 cr->stroke ();
158                                 cr->restore ();
159                         }
160
161                         cr->set_operator (Cairo::OPERATOR_ADD);
162                         cr->set_source_rgba (.7, .7, .7, 1.0);
163                         cr->set_line_width (1.0);
164
165                         if (p->loudness_hist_max > 0) {
166                                 for (size_t x = 0 ; x < 510; ++x) {
167                                         cr->move_to (x - .5, 128.0);
168                                         cr->line_to (x - .5, 128.0 - 128.0 * p->loudness_hist[x] / (float) p->loudness_hist_max);
169                                         cr->stroke ();
170                                 }
171                         }
172
173                         hist->flush ();
174                         CimgArea *nu = manage (new CimgArea (nums));
175                         CimgArea *hi = manage (new CimgArea (hist));
176                         HBox *hb = manage (new HBox ());
177                         hb->set_spacing (4);
178                         hb->pack_start (*nu);
179                         hb->pack_start (*hi);
180                         vb->pack_start (*hb);
181                 }
182
183                 {
184                         /* draw waveform */
185                         // TODO re-use Canvas::WaveView::draw_image() somehow.
186                         const size_t peaks = sizeof (p->peaks) / sizeof (ARDOUR::PeakData::PeakDatum) / 2;
187                         const float height_2 = 100.0;
188                         Cairo::RefPtr<Cairo::ImageSurface> wave = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, peaks, 2 * height_2);
189                         Cairo::RefPtr<Cairo::Context> cr = Cairo::Context::create (wave);
190                         cr->rectangle (0, 0, peaks, 2 * height_2);
191                         cr->set_source_rgba (0, 0, 0, 1.0);
192                         cr->fill ();
193                         cr->set_source_rgba (.7, .7, .7, 1.0);
194                         cr->set_line_width (1.0);
195                         for (size_t x = 0 ; x < peaks; ++x) {
196                                 cr->move_to (x - .5, height_2 - height_2 * p->peaks[x].max);
197                                 cr->line_to (x - .5, height_2 - height_2 * p->peaks[x].min);
198                         }
199                         cr->stroke ();
200
201                         // zero line
202                         cr->set_source_rgba (.3, .3, .3, 0.7);
203                         cr->move_to (0, height_2 - .5);
204                         cr->line_to (peaks, height_2 - .5);
205                         cr->stroke ();
206
207                         cr->set_dash (dashes, 2.0);
208                         cr->set_line_cap (Cairo::LINE_CAP_ROUND);
209
210                         Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create (get_pango_context ());
211                         layout->set_alignment (Pango::ALIGN_LEFT);
212                         layout->set_font_description (UIConfiguration::instance ().get_SmallMonospaceFont ());
213                         int w, h;
214
215                         layout->set_text (_("dBFS"));
216                         layout->get_pixel_size (w, h);
217                         Gtkmm2ext::rounded_rectangle (cr,
218                                         5, rint (height_2 - w * .5 - 1), h + 2, w + 2, 4);
219                         cr->set_source_rgba (.1, .1, .1, 0.5);
220                         cr->fill ();
221                         cr->move_to (6, rint (height_2 + w * .5));
222                         cr->set_source_rgba (.9, .9, .9, 1.0);
223                         cr->save ();
224                         cr->rotate (M_PI / -2.0);
225                         layout->show_in_cairo_context (cr);
226                         cr->restore ();
227
228 #define PEAKANNOTATION(POS, TXT) {                            \
229                         const float yy = rint (POS);                            \
230                         layout->set_text (TXT);                                 \
231                         layout->get_pixel_size (w, h);                          \
232                         cr->set_operator (Cairo::OPERATOR_OVER);                \
233                         Gtkmm2ext::rounded_rectangle (cr,                       \
234                             5, rint ((POS) - h * .5 - 1), w + 2, h + 2, 4);     \
235                         cr->set_source_rgba (.1, .1, .1, 0.5);                  \
236                         cr->fill ();                                            \
237                         cr->move_to (6, rint ((POS) - h * .5));                 \
238                         cr->set_source_rgba (.9, .9, .9, 1.0);                  \
239                         layout->show_in_cairo_context (cr);                     \
240                         cr->move_to (8 + w, yy - .5);                           \
241                         cr->line_to (peaks, yy - .5);                           \
242                         cr->set_source_rgba (.3, .3, .3, 1.0);                  \
243                         cr->set_operator (Cairo::OPERATOR_ADD);                 \
244                         cr->stroke ();                                          \
245                         }
246
247                         PEAKANNOTATION (height_2 * 0.5, _("-6"));
248                         PEAKANNOTATION (height_2 * 1.5, _("-6"));
249                         PEAKANNOTATION (height_2 * 0.2921, _("-3"));
250                         PEAKANNOTATION (height_2 * 1.7079, _("-3"));
251
252                         wave->flush ();
253                         CimgArea *wv = manage (new CimgArea (wave));
254                         vb->pack_start (*wv);
255                 }
256
257                 {
258                         const size_t swh = sizeof (p->spectrum) / sizeof (float);
259                         const size_t height = sizeof (p->spectrum[0]) / sizeof (float);
260                         const size_t width = swh / height;
261
262                         Cairo::RefPtr<Cairo::ImageSurface> spec = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, width, height);
263                         Cairo::RefPtr<Cairo::Context> cr = Cairo::Context::create (spec);
264                         cr->rectangle (0, 0, width, height);
265                         cr->set_source_rgba (0, 0, 0, 1.0);
266                         cr->fill ();
267                         for (size_t x = 0 ; x < width; ++x) {
268                                 for (size_t y = 0 ; y < height; ++y) {
269                                         const float pk = p->spectrum[x][y];
270                                         ArdourCanvas::Color c = ArdourCanvas::hsva_to_color (252 - 260 * pk, .9, .3 + pk * .4);
271                                         ArdourCanvas::set_source_rgba (cr, c);
272                                         cr->rectangle (x - .5, y - .5, 1, 1);
273                                         cr->fill ();
274                                 }
275                         }
276                         spec->flush ();
277                         CimgArea *sp = manage (new CimgArea (spec));
278                         vb->pack_start (*sp);
279                 }
280
281                 // TODO ellipsize tab-text
282                 pages.pages ().push_back (Notebook_Helpers::TabElem (*vb, Glib::path_get_basename (i->first)));
283         }
284
285         pages.set_show_tabs (true);
286         pages.show_all ();
287         pages.set_name ("ExportReportNotebook");
288         pages.set_current_page (0);
289
290         get_vbox ()->set_spacing (12);
291         get_vbox ()->pack_start (pages);
292
293         add_button (Stock::CLOSE, RESPONSE_ACCEPT);
294         set_default_response (RESPONSE_ACCEPT);
295         show_all ();
296         //pages.signal_switch_page ().connect (sigc::mem_fun (*this, &ExportReport::handle_page_change));
297 }
298
299 int
300 ExportReport::run ()
301 {
302         return ArdourDialog::run ();
303 }