1 /*
2 * Copyright (C) 2020 Robin Gareus <robin@gareus.org>
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 along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19 #include <gtkmm/alignment.h>
20 #include <gtkmm/label.h>
21 #include <gtkmm/stock.h>
22
23 #include "pbd/unwind.h"
24
25 #include "ardour/dB.h"
26 #include "ardour/plugin_insert.h"
27 #include "ardour/plugin_manager.h"
28 #include "ardour/processor.h"
29 #include "ardour/session.h"
30
31 #include "ardour/export_channel_configuration.h"
32 #include "ardour/export_filename.h"
33 #include "ardour/export_format_base.h"
34 #include "ardour/export_format_specification.h"
35 #include "ardour/export_handler.h"
36 #include "ardour/export_status.h"
37 #include "ardour/export_timespan.h"
38
39 #include "gtkmm2ext/menu_elems.h"
40 #include "gtkmm2ext/utils.h"
41
42 #include "widgets/ardour_spacer.h"
43 #include "widgets/prompter.h"
44 #include "widgets/tooltips.h"
45
46 #include "ardour_message.h"
47 #include "export_analysis_graphs.h"
48 #include "export_report.h"
49 #include "loudness_dialog.h"
50 #include "ui_config.h"
51
52 #include "pbd/i18n.h"
53
54 using namespace Gtk;
55 using namespace ARDOUR;
56 using namespace ArdourWidgets;
57
58 bool LoudnessDialog::_first_time = true;
59 CLoudnessPreset LoudnessDialog::_last_preset;
60
LoudnessDialog(Session * s,AudioRange const & ar,bool as)61 LoudnessDialog::LoudnessDialog (Session* s, AudioRange const& ar, bool as)
62 : ArdourDialog (as ? _("Loudness Assistant") : _("Loudness Analyzer and Normalizer"))
63 , _lp (false)
64 , _session (s)
65 , _range (ar)
66 , _status (s->get_export_status ())
67 , _autostart (as)
68 , _conformity_frame (_("Conformity Analysis (with gain applied)"))
69 , _save_preset (_("Save"))
70 , _remove_preset (_("Remove"))
71 , _dbfs_btn (_("Peak:"), ArdourButton::led_default_elements, true)
72 , _dbtp_btn (_("True Peak:"), ArdourButton::led_default_elements, true)
73 , _lufs_i_btn (_("Integrated Loudness:"), ArdourButton::led_default_elements, true)
74 , _lufs_s_btn (_("Max. Short Loudness:"), ArdourButton::led_default_elements, true)
75 , _lufs_m_btn (_("Max. Momentary Loudness:"), ArdourButton::led_default_elements, true)
76 , _rt_analysis_button (_("Realtime"), ArdourButton::led_default_elements, true)
77 , _start_analysis_button (_("Analyze"))
78 , _show_report_button (_("Analysis Report"))
79 , _custom_pos_button (_("Custom Amplifier Position"), ArdourButton::led_default_elements, true)
80 , _dbfs_adjustment (0.00, -10.00, 0.00, 0.1, 0.2)
81 , _dbtp_adjustment (-1.0, -10.00, 0.00, 0.1, 0.2)
82 , _lufs_i_adjustment (-23.0, -90.00, 0.00, 0.5, 1.0)
83 , _lufs_s_adjustment (-20.0, -90.00, 0.00, 0.5, 1.0)
84 , _lufs_m_adjustment (-17.0, -90.00, 0.00, 0.5, 1.0)
85 , _dbfs_spinbutton (_dbfs_adjustment, 0.1, 1)
86 , _dbtp_spinbutton (_dbtp_adjustment, 0.1, 1)
87 , _lufs_i_spinbutton (_lufs_i_adjustment, 0.1, 1)
88 , _lufs_s_spinbutton (_lufs_s_adjustment, 0.1, 1)
89 , _lufs_m_spinbutton (_lufs_m_adjustment, 0.1, 1)
90 , _gain_out (0)
91 , _gain_norm (0)
92 , _ignore_preset (false)
93 , _ignore_change (false)
94 {
95 _preset = _lp[0];
96 if (_first_time) {
97 _first_time = false;
98 _last_preset = _lp[0];
99 }
100
101 _preset = _last_preset;
102
103 /* Dialog can be displayed from the mixer, override global transient_parent */
104 unset_transient_for ();
105
106 /* query initial gain */
107 _gain_out = accurate_coefficient_to_dB (_session->master_volume ()->get_value ());
108
109 /* setup styles */
110 _start_analysis_button.set_name ("generic button");
111 _rt_analysis_button.set_name ("generic button");
112 _show_report_button.set_name ("generic button");
113 _custom_pos_button.set_name ("generic button");
114
115 _custom_pos_button.set_active (!_session->master_out()->volume_applies_to_output ());
116
117 GtkRequisition req = _start_analysis_button.size_request ();
118 _start_analysis_button.set_size_request (-1, req.height * 1.1);
119 _rt_analysis_button.set_size_request (-1, req.height * 1.1);
120
121 _save_preset.set_name ("generic button");
122 _remove_preset.set_name ("generic button");
123
124 _dbfs_btn.set_name ("generic button");
125 _dbtp_btn.set_name ("generic button");
126 _lufs_i_btn.set_name ("generic button");
127 _lufs_s_btn.set_name ("generic button");
128 _lufs_m_btn.set_name ("generic button");
129
130 _dbfs_btn.set_led_left (true);
131 _dbtp_btn.set_led_left (true);
132 _lufs_i_btn.set_led_left (true);
133 _lufs_s_btn.set_led_left (true);
134 _lufs_m_btn.set_led_left (true);
135
136 _preset_dropdown.set_can_focus (true);
137 _start_analysis_button.set_can_focus (true);
138 _rt_analysis_button.set_can_focus (true);
139 _show_report_button.set_can_focus (true);
140 _custom_pos_button.set_can_focus (true);
141 _save_preset.set_can_focus (true);
142 _remove_preset.set_can_focus (true);
143 _dbfs_btn.set_can_focus (true);
144 _dbtp_btn.set_can_focus (true);
145 _lufs_i_btn.set_can_focus (true);
146 _lufs_s_btn.set_can_focus (true);
147 _lufs_m_btn.set_can_focus (true);
148
149 _dbfs_label.modify_font (UIConfiguration::instance ().get_NormalMonospaceFont ());
150 _dbtp_label.modify_font (UIConfiguration::instance ().get_NormalMonospaceFont ());
151 _lufs_i_label.modify_font (UIConfiguration::instance ().get_NormalMonospaceFont ());
152 _lufs_s_label.modify_font (UIConfiguration::instance ().get_NormalMonospaceFont ());
153 _lufs_m_label.modify_font (UIConfiguration::instance ().get_NormalMonospaceFont ());
154
155 _delta_dbfs_label.modify_font (UIConfiguration::instance ().get_NormalMonospaceFont ());
156 _delta_dbtp_label.modify_font (UIConfiguration::instance ().get_NormalMonospaceFont ());
157 _delta_lufs_i_label.modify_font (UIConfiguration::instance ().get_NormalMonospaceFont ());
158 _delta_lufs_s_label.modify_font (UIConfiguration::instance ().get_NormalMonospaceFont ());
159 _delta_lufs_m_label.modify_font (UIConfiguration::instance ().get_NormalMonospaceFont ());
160
161 _gain_out_label.modify_font (UIConfiguration::instance ().get_NormalMonospaceFont ());
162 _gain_norm_label.modify_font (UIConfiguration::instance ().get_NormalMonospaceFont ());
163 _gain_total_label.modify_font (UIConfiguration::instance ().get_NormalMonospaceFont ());
164 _gain_exceeds_label.modify_font (UIConfiguration::instance ().get_NormalFont ());
165
166 /* result display layout */
167 #define ROW row, row + 1
168
169 int row = 0;
170 Label* l;
171 Table* t = manage (new Table (11, 8, false));
172 t->set_spacings (4);
173
174 l = manage (new Label (_("Preset:"), ALIGN_LEFT));
175 t->attach (*l, 0, 1, ROW, SHRINK | FILL);
176 t->attach (_preset_dropdown, 1, 3, ROW);
177
178 t->attach (_save_preset, 3, 4, ROW, SHRINK | FILL);
179 t->attach (_remove_preset, 4, 5, ROW, SHRINK | FILL);
180
181 /* horiz space */
182 l = manage (new Label (" ", ALIGN_LEFT));
183 t->attach (*l, 5, 6, ROW, SHRINK, SHRINK, 6, 0);
184
185 l = manage (new Label (" ", ALIGN_LEFT));
186 t->attach (*l, 6, 7, ROW);
187
188 t->attach (_show_report_button, 7, 8, ROW, SHRINK | FILL);
189
190 ++row;
191 l = manage (new Label (_("<b>Target</b>"), ALIGN_CENTER));
192 l->set_use_markup (true);
193 t->attach (*l, 2, 3, ROW);
194 l = manage (new Label (_("<b>Measured</b>"), ALIGN_CENTER));
195 l->set_use_markup (true);
196 t->attach (*l, 3, 4, ROW);
197 l = manage (new Label (_("<b>Delta</b>"), ALIGN_CENTER));
198 l->set_use_markup (true);
199 t->attach (*l, 4, 5, ROW);
200
201 row = 2;
202 t->attach (_dbfs_btn, 0, 2, ROW); ++row;
203 t->attach (_dbtp_btn, 0, 2, ROW); ++row;
204 t->attach (_lufs_i_btn, 0, 2, ROW); ++row;
205 t->attach (_lufs_s_btn, 0, 2, ROW); ++row;
206 t->attach (_lufs_m_btn, 0, 2, ROW); ++row;
207
208 ++row; // spacer
209
210 l = manage (new Label (_("Gain to normalize:"), ALIGN_LEFT));
211 t->attach (*l, 0, 2, ROW); ++row;
212 l = manage (new Label (_("Previous output gain:"), ALIGN_LEFT));
213 t->attach (*l, 0, 2, ROW); ++row;
214
215 l = manage (new Label (_("Total gain:"), ALIGN_LEFT));
216 t->attach (*l, 0, 2, ROW);
217
218 row = 2;
219 t->attach (_dbfs_spinbutton, 2, 3, ROW, EXPAND|FILL, EXPAND|FILL, 8, 0); ++row;
220 t->attach (_dbtp_spinbutton, 2, 3, ROW, EXPAND|FILL, EXPAND|FILL, 8, 0); ++row;
221 t->attach (_lufs_i_spinbutton, 2, 3, ROW, EXPAND|FILL, EXPAND|FILL, 8, 0); ++row;
222 t->attach (_lufs_s_spinbutton, 2, 3, ROW, EXPAND|FILL, EXPAND|FILL, 8, 0); ++row;
223 t->attach (_lufs_m_spinbutton, 2, 3, ROW, EXPAND|FILL, EXPAND|FILL, 8, 0); ++row;
224
225 row = 2;
226 t->attach (_dbfs_label, 3, 4, ROW); ++row;
227 t->attach (_dbtp_label, 3, 4, ROW); ++row;
228 t->attach (_lufs_i_label, 3, 4, ROW); ++row;
229 t->attach (_lufs_s_label, 3, 4, ROW); ++row;
230 t->attach (_lufs_m_label, 3, 4, ROW); ++row;
231
232 row = 2;
233 t->attach (_delta_dbfs_label, 4, 5, ROW); ++row;
234 t->attach (_delta_dbtp_label, 4, 5, ROW); ++row;
235 t->attach (_delta_lufs_i_label, 4, 5, ROW); ++row;
236 t->attach (_delta_lufs_s_label, 4, 5, ROW); ++row;
237 t->attach (_delta_lufs_m_label, 4, 5, ROW); ++row;
238
239 ArdourHSpacer* spc = manage (new ArdourHSpacer (1.0));
240 t->attach (*spc, 3, 5, ROW); ++row;
241 t->attach (_gain_norm_label, 4, 5, ROW); ++row;
242 t->attach (_gain_out_label, 4, 5, ROW); ++row;
243 t->attach (_gain_exceeds_label, 3, 4, ROW);
244 t->attach (_gain_total_label, 4, 5, ROW);
245 t->attach (_custom_pos_button, 7, 8, row - 1, row + 1, SHRINK | FILL, SHRINK);
246 t->attach (_conformity_frame, 6, 8, 1, row - 3, SHRINK | FILL, EXPAND | FILL, 0, 0);
247
248 set_tooltip (_custom_pos_button,
249 _("<b>When enabled</b> an amplifier processor is used to apply the gain. "
250 "This allows for custom positioning of the gain-stage in the master-bus' signal flow, "
251 "potentially followed by a limiter to conform to both loudness and peak requirements. "
252 "Depending on limiter settings or DSP after the gain-stage, repeat loudness measurements may produce different results.\n"
253 "<b>When disabled</b>, the gain is applied directly to the output of the master-bus. This results in an efficient and reliable volume adjustment."
254 ));
255
256 _dbfs_label.set_alignment (ALIGN_RIGHT);
257 _dbtp_label.set_alignment (ALIGN_RIGHT);
258 _lufs_i_label.set_alignment (ALIGN_RIGHT);
259 _lufs_s_label.set_alignment (ALIGN_RIGHT);
260 _lufs_m_label.set_alignment (ALIGN_RIGHT);
261
262 _delta_dbfs_label.set_alignment (ALIGN_RIGHT);
263 _delta_dbtp_label.set_alignment (ALIGN_RIGHT);
264 _delta_lufs_i_label.set_alignment (ALIGN_RIGHT);
265 _delta_lufs_s_label.set_alignment (ALIGN_RIGHT);
266 _delta_lufs_m_label.set_alignment (ALIGN_RIGHT);
267
268 _gain_norm_label.set_alignment (ALIGN_RIGHT);
269 _gain_out_label.set_alignment (ALIGN_RIGHT);
270 _gain_total_label.set_alignment (ALIGN_RIGHT);
271 _gain_exceeds_label.set_alignment (ALIGN_RIGHT);
272
273 HBox* hb = manage (new (HBox));
274 hb->pack_start (_loudness_graph, true, false);
275
276 _result_box.pack_start (*hb, false, false, 0);
277 _result_box.pack_start (*t, false, false, 6);
278
279 /* analysis progress layout */
280 _progress_box.pack_start (_progress_bar, false, false, 6);
281
282 /* setup and info layout */
283 t = manage (new Table (2, 3, false));
284 t->set_spacings (4);
285 l = manage (new Label ());
286 l->set_markup (_("<b>Loudness Analysis</b>\n"));
287 l->set_alignment (ALIGN_LEFT, ALIGN_TOP);
288 t->attach (*l, 0, 1, 0, 1, EXPAND | FILL, FILL, 8, 2);
289
290 l = manage (new Label ());
291 l->set_line_wrap ();
292 l->set_alignment (ALIGN_LEFT, ALIGN_TOP);
293 l->set_markup (_(
294 "This allows the user to analyze and conform the loudness of the signal at the master-bus "
295 "output of the complete session, as it would be exported. "
296 "When using this feature, remember to disable normalization in the session export profile."));
297 t->attach (*l, 0, 1, 1, 2, EXPAND | FILL, FILL, 8, 2);
298
299 l = manage (new Label ());
300 l->set_line_wrap ();
301 l->set_alignment (ALIGN_LEFT, ALIGN_TOP);
302 l->set_markup (_(
303 "By default, a faster-than-realtime export is used to assess the loudness of the "
304 "session. If any outboard gear is used, a <i>realtime</i> export is available, to "
305 "play at normal speed."));
306 t->attach (*l, 0, 1, 2, 3, EXPAND | FILL, FILL, 8, 2);
307
308 Alignment* align = manage (new Alignment (0, 0, 1, 0));
309 align->add (_start_analysis_button);
310 t->attach (*align, 1, 2, 1, 2, FILL, FILL, 2, 0);
311
312 align = manage (new Alignment (0, 0, 1, 0));
313 align->add (_rt_analysis_button);
314 t->attach (*align, 1, 2, 2, 3, FILL, FILL, 2, 0);
315
316 _setup_box.pack_start (*t, false, false, 6);
317
318 /* global layout */
319 get_vbox ()->pack_start (_setup_box);
320 get_vbox ()->pack_start (_progress_box);
321 get_vbox ()->pack_start (_result_box);
322
323 _progress_box.set_size_request (400, -1);
324
325 _ok_button = add_button (Stock::APPLY, RESPONSE_APPLY);
326 _cancel_button = add_button (Stock::CANCEL, RESPONSE_CANCEL);
327
328 /* fill in presets */
329 for (size_t i = 0; i < _lp.n_presets (); ++i) {
330 using namespace Gtkmm2ext;
331 _preset_dropdown.AddMenuElem (MenuElemNoMnemonic (_lp[i].label, sigc::bind (sigc::mem_fun (*this, &LoudnessDialog::load_preset), i)));
332 }
333
334 apply_preset ();
335
336 if (!_lp.find_preset (_preset)) {
337 _preset.label = _("Custom");
338 }
339
340 check_preset ();
341
342 _gain_out_label.set_text (string_compose (_("%1 dB"), std::setprecision (2), std::showpos, std::fixed, _gain_out));
343
344 /* setup graph */
345 _loudness_graph.signal_size_request ().connect (sigc::mem_fun (*this, &LoudnessDialog::graph_size_request));
346 _loudness_graph.signal_expose_event ().connect (sigc::mem_fun (*this, &LoudnessDialog::graph_expose_event));
347
348 /* connect signals */
349 _cancel_button->signal_clicked ().connect (sigc::mem_fun (this, &LoudnessDialog::cancel_analysis));
350 _dbfs_spinbutton.signal_value_changed ().connect (mem_fun (*this, &LoudnessDialog::update_settings));
351 _dbtp_spinbutton.signal_value_changed ().connect (mem_fun (*this, &LoudnessDialog::update_settings));
352 _lufs_i_spinbutton.signal_value_changed ().connect (mem_fun (*this, &LoudnessDialog::update_settings));
353 _lufs_s_spinbutton.signal_value_changed ().connect (mem_fun (*this, &LoudnessDialog::update_settings));
354 _lufs_m_spinbutton.signal_value_changed ().connect (mem_fun (*this, &LoudnessDialog::update_settings));
355 _save_preset.signal_clicked.connect (mem_fun (*this, &LoudnessDialog::save_preset));
356 _remove_preset.signal_clicked.connect (mem_fun (*this, &LoudnessDialog::remove_preset));
357 _show_report_button.signal_clicked.connect (mem_fun (*this, &LoudnessDialog::display_report));
358 _start_analysis_button.signal_clicked.connect (mem_fun (*this, &LoudnessDialog::start_analysis));
359 _dbfs_btn.signal_clicked.connect (mem_fun (*this, &LoudnessDialog::update_settings));
360 _dbtp_btn.signal_clicked.connect (mem_fun (*this, &LoudnessDialog::update_settings));
361 _lufs_i_btn.signal_clicked.connect (mem_fun (*this, &LoudnessDialog::update_settings));
362 _lufs_s_btn.signal_clicked.connect (mem_fun (*this, &LoudnessDialog::update_settings));
363 _lufs_m_btn.signal_clicked.connect (mem_fun (*this, &LoudnessDialog::update_settings));
364
365 _ok_button->set_sensitive (false);
366 _show_report_button.set_sensitive (false);
367
368 show_all_children ();
369
370 _result_box.hide ();
371 _progress_box.hide ();
372 }
373
374 void
cancel_analysis()375 LoudnessDialog::cancel_analysis ()
376 {
377 if (_status->running ()) {
378 _status->abort ();
379 }
380 }
381
382 void
start_analysis()383 LoudnessDialog::start_analysis ()
384 {
385 if (0 == analyze ()) {
386 display_results ();
387 } else {
388 _setup_box.show ();
389 }
390 }
391
392 bool
on_delete_event(GdkEventAny * ev)393 LoudnessDialog::on_delete_event (GdkEventAny* ev)
394 {
395 cancel_analysis ();
396 return ArdourDialog::on_delete_event (ev);
397 }
398
399 int
run()400 LoudnessDialog::run ()
401 {
402 if (_autostart) {
403 show ();
404 if (0 == analyze ()) {
405 display_results ();
406 } else {
407 return RESPONSE_CANCEL;
408 }
409 }
410
411 int const r = ArdourDialog::run ();
412 cancel_analysis ();
413
414 if (r == RESPONSE_APPLY) {
415 _session->master_volume ()->set_value (dB_to_coefficient (gain_db ()), PBD::Controllable::NoGroup);
416 _session->master_out()->set_volume_applies_to_output (!_custom_pos_button.get_active ());
417
418 _last_preset = _preset;
419 }
420
421 return r;
422 }
423
424 gint
progress_timeout()425 LoudnessDialog::progress_timeout ()
426 {
427 float progress = ((float)_status->processed_samples_current_timespan) / _status->total_samples_current_timespan;
428 _progress_bar.set_text ("Analyzing");
429 _progress_bar.set_fraction (progress);
430 return true;
431 }
432
433 int
analyze()434 LoudnessDialog::analyze ()
435 {
436 /* These are ensured in Editor::measure_master_loudness () */
437 assert (_session->master_out ());
438 assert (_session->master_volume ());
439 assert (_session->master_out ()->output ());
440 assert (_session->master_out ()->output ()->n_ports ().n_audio () == 2);
441 assert (_range.start < _range.end);
442
443
444 ExportTimespanPtr tsp = _session->get_export_handler ()->add_timespan ();
445
446 boost::shared_ptr<ExportChannelConfiguration> ccp = _session->get_export_handler ()->add_channel_config ();
447 boost::shared_ptr<ARDOUR::ExportFilename> fnp = _session->get_export_handler ()->add_filename ();
448 boost::shared_ptr<ExportFormatSpecification> fmp = _session->get_export_handler ()->add_format ();
449
450 /* setup format */
451 fmp->set_tag (false);
452 fmp->set_sample_format (ExportFormatBase::SF_Float);
453 fmp->set_sample_rate (ExportFormatBase::SR_Session);
454 fmp->set_format_id (ExportFormatBase::F_None);
455 fmp->set_type (ExportFormatBase::T_None);
456 fmp->set_extension ("wav");
457 fmp->set_soundcloud_upload (false);
458 fmp->set_analyse (true);
459
460 /* setup range */
461 tsp->set_range (_range.start, _range.end);
462 tsp->set_range_id ("selection");
463 tsp->set_realtime (_rt_analysis_button.get_active ());
464 tsp->set_name ("master");
465
466 /* setup channels, use master out */
467 IO* master_out = _session->master_out ()->output ().get ();
468 for (uint32_t n = 0; n < master_out->n_ports ().n_audio (); ++n) {
469 PortExportChannel* channel = new PortExportChannel ();
470 channel->add_port (master_out->audio (n));
471 ExportChannelPtr chan_ptr (channel);
472 ccp->register_channel (chan_ptr);
473 }
474
475 /* do audio export */
476 boost::shared_ptr<AudioGrapher::BroadcastInfo> b;
477 _session->get_export_handler ()->reset ();
478 _session->get_export_handler ()->add_export_config (tsp, ccp, fmp, fnp, b);
479 _session->get_export_handler ()->do_export ();
480
481 /* show progress */
482 _setup_box.hide ();
483 _progress_box.show_all ();
484
485 /* shrink window height (setup box) */
486 Gtk::Requisition wr;
487 get_size (wr.width, wr.height);
488 resize (wr.width, 60);
489
490 sigc::connection progress_connection = Glib::signal_timeout ().connect (sigc::mem_fun (*this, &LoudnessDialog::progress_timeout), 100);
491
492 while (_status->running ()) {
493 if (gtk_events_pending ()) {
494 gtk_main_iteration ();
495 } else {
496 Glib::usleep (10000);
497 }
498 }
499 progress_connection.disconnect ();
500 _progress_box.hide_all ();
501
502 /* done */
503 _status->finish (TRS_UI);
504
505 if (!_status->aborted () && _status->result_map.size () != 1) {
506 ArdourMessageDialog (_("Loudness measurement returned no results. Likely because the analyzed range is to short."), false, MESSAGE_ERROR).run ();
507 return 1;
508 }
509
510 return _status->aborted () ? 1 : 0;
511 }
512
513 void
display_report()514 LoudnessDialog::display_report ()
515 {
516 ExportReport er (_("Export Loudness Report"), _status->result_map);
517 er.set_transient_for (*this);
518 er.run ();
519 }
520
521 void
save_preset()522 LoudnessDialog::save_preset ()
523 {
524 assert (_preset.user);
525 ArdourWidgets::Prompter name_prompter (*this, true, true);
526 name_prompter.set_title (_("Save Loudness Preset"));
527 name_prompter.set_prompt (_("Name:"));
528 name_prompter.add_button (_("Save"), Gtk::RESPONSE_ACCEPT);
529 name_prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
530 name_prompter.set_initial_text (_preset.label, _preset.label != _("Custom"));
531 name_prompter.show_all ();
532
533 bool saved = false;
534 bool done = false;
535 std::string result;
536
537 while (!done) {
538 switch (name_prompter.run ()) {
539 case Gtk::RESPONSE_ACCEPT:
540 name_prompter.get_result (result);
541 name_prompter.hide ();
542 if (result.length()) {
543 if (result == _("Custom")) {
544 /* ask again */
545 continue;
546 }
547 _preset.label = result;
548 _preset.report = false;
549 if (_lp.push_back (_preset)) {
550 done = true;
551 saved = true;
552 } else {
553 /* Invalid Name, ask again */
554 }
555 } else {
556 /* nothing entered, just get out of here */
557 done = true;
558 }
559 break;
560 default:
561 done = true;
562 break;
563 }
564 }
565 if (saved) {
566 _preset_dropdown.clear_items ();
567 for (size_t i = 0; i < _lp.n_presets (); ++i) {
568 using namespace Gtkmm2ext;
569 _preset_dropdown.AddMenuElem (MenuElemNoMnemonic (_lp[i].label, sigc::bind (sigc::mem_fun (*this, &LoudnessDialog::load_preset), i)));
570 }
571 PBD::Unwinder<bool> uw (_ignore_preset, true);
572 _preset_dropdown.set_active (_preset.label);
573 _save_preset.set_sensitive (false);
574 _remove_preset.set_sensitive (_preset.user);
575 }
576 }
577
578 void
remove_preset()579 LoudnessDialog::remove_preset ()
580 {
581 assert (_preset.user);
582 if (_lp.erase (_preset)) {
583 _preset_dropdown.clear_items ();
584 for (size_t i = 0; i < _lp.n_presets (); ++i) {
585 using namespace Gtkmm2ext;
586 _preset_dropdown.AddMenuElem (MenuElemNoMnemonic (_lp[i].label, sigc::bind (sigc::mem_fun (*this, &LoudnessDialog::load_preset), i)));
587 }
588 _preset.label = _("Custom");
589 update_settings ();
590 }
591 }
592
593 void
load_preset(size_t n)594 LoudnessDialog::load_preset (size_t n)
595 {
596 if (_ignore_preset) {
597 return;
598 }
599 _preset = _lp[n];
600 _save_preset.set_sensitive (false);
601 _remove_preset.set_sensitive (_preset.user);
602 apply_preset ();
603 calculate_gain ();
604 }
605
606 void
apply_preset()607 LoudnessDialog::apply_preset ()
608 {
609 PBD::Unwinder<bool> uw (_ignore_change, true);
610 _preset_dropdown.set_text (_preset.label);
611
612 _dbfs_btn.set_active (_preset.enable[0]);
613 _dbtp_btn.set_active (_preset.enable[1]);
614 _lufs_i_btn.set_active (_preset.enable[2]);
615 _lufs_s_btn.set_active (_preset.enable[3]);
616 _lufs_m_btn.set_active (_preset.enable[4]);
617 _dbfs_spinbutton.set_value (_preset.level[0]);
618 _dbtp_spinbutton.set_value (_preset.level[1]);
619 _lufs_i_spinbutton.set_value (_preset.level[2]);
620 _lufs_s_spinbutton.set_value (_preset.level[3]);
621 _lufs_m_spinbutton.set_value (_preset.level[4]);
622 update_sensitivity ();
623 }
624
625 void
update_sensitivity()626 LoudnessDialog::update_sensitivity ()
627 {
628 _dbfs_spinbutton.set_sensitive (_dbfs_btn.get_active () && _dbfs_btn.sensitive ());
629 _dbtp_spinbutton.set_sensitive (_dbtp_btn.get_active () && _dbtp_btn.sensitive ());
630 _lufs_i_spinbutton.set_sensitive (_lufs_i_btn.get_active () && _dbtp_btn.sensitive ());
631 _lufs_s_spinbutton.set_sensitive (_lufs_s_btn.get_active () && _lufs_s_btn.sensitive ());
632 _lufs_m_spinbutton.set_sensitive (_lufs_m_btn.get_active () && _lufs_m_btn.sensitive ());
633 }
634
635 void
check_preset()636 LoudnessDialog::check_preset ()
637 {
638 if (_lp.find_preset (_preset)) {
639 _save_preset.set_sensitive (false);
640 _remove_preset.set_sensitive (_preset.user);
641 } else {
642 if (!_preset.user) {
643 _preset.label = _("Custom");
644 }
645 _preset.user = true;
646 _preset.report = false;
647 _save_preset.set_sensitive (true);
648 _remove_preset.set_sensitive (false);
649 }
650 _preset_dropdown.set_text (_preset.label);
651 }
652
653 void
update_settings()654 LoudnessDialog::update_settings ()
655 {
656 if (_ignore_change) {
657 return;
658 }
659
660 /* check if preset with current settings exists */
661 _preset.level[0] = _dbfs_spinbutton.get_value ();
662 _preset.level[1] = _dbtp_spinbutton.get_value ();
663 _preset.level[2] = _lufs_i_spinbutton.get_value ();
664 _preset.level[3] = _lufs_s_spinbutton.get_value ();
665 _preset.level[4] = _lufs_m_spinbutton.get_value ();
666 _preset.enable[0] = _dbfs_btn.get_active ();
667 _preset.enable[1] = _dbtp_btn.get_active ();
668 _preset.enable[2] = _lufs_i_btn.get_active ();
669 _preset.enable[3] = _lufs_s_btn.get_active ();
670 _preset.enable[4] = _lufs_m_btn.get_active ();
671
672 check_preset ();
673 update_sensitivity ();
674 calculate_gain ();
675 }
676
677 float
gain_db() const678 LoudnessDialog::gain_db () const
679 {
680 return _gain_norm + _gain_out;
681 }
682
683 void
display_results()684 LoudnessDialog::display_results ()
685 {
686 AnalysisResults const& ar (_status->result_map);
687 assert (ar.size () == 1);
688 ExportAnalysisPtr p = ar.begin ()->second;
689
690 if (!p->have_loudness || !p->have_dbtp || !p->have_lufs_graph) {
691 ArdourMessageDialog (
692 string_compose (_("True-peak and loudness measurement failed. %1-VAMP analysis plugin is missing on your system. Please contact your vendor."), PROGRAM_NAME),
693 false, MESSAGE_ERROR)
694 .run ();
695 }
696
697 plot_graph (p);
698
699 _dbfs = accurate_coefficient_to_dB (p->peak);
700 _dbtp = accurate_coefficient_to_dB (p->truepeak);
701 _lufs_i = p->integrated_loudness > -200 ? p->integrated_loudness : -std::numeric_limits<float>::infinity ();
702 _lufs_s = p->max_loudness_short > -200 ? p->max_loudness_short : -std::numeric_limits<float>::infinity ();
703 _lufs_m = p->max_loudness_momentary > -200 ? p->max_loudness_momentary : -std::numeric_limits<float>::infinity ();
704
705 _dbfs_btn.set_sensitive (_dbfs > -300);
706 _dbtp_btn.set_sensitive (_dbtp > -300);
707 _lufs_i_btn.set_sensitive (p->integrated_loudness > -200);
708 _lufs_s_btn.set_sensitive (p->max_loudness_short > -200);
709 _lufs_m_btn.set_sensitive (p->max_loudness_momentary > -200);
710
711 _dbfs_label.set_text (string_compose (_("%1 dBFS"), std::setprecision (1), std::fixed, _dbfs));
712 _dbtp_label.set_text (string_compose (_("%1 dBTP"), std::setprecision (1), std::fixed, _dbtp));
713 _lufs_i_label.set_text (string_compose (_("%1 LUFS"), std::setprecision (1), std::fixed, _lufs_i));
714 _lufs_s_label.set_text (string_compose (_("%1 LUFS"), std::setprecision (1), std::fixed, _lufs_s));
715 _lufs_m_label.set_text (string_compose (_("%1 LUFS"), std::setprecision (1), std::fixed, _lufs_m));
716
717 update_sensitivity ();
718 calculate_gain ();
719
720 _result_box.show_all ();
721 _show_report_button.set_sensitive (true);
722 }
723
724 void
calculate_gain()725 LoudnessDialog::calculate_gain ()
726 {
727 float dbfs = _dbfs_spinbutton.get_value ();
728 float dbtp = _dbtp_spinbutton.get_value ();
729 float lufs_i = _lufs_i_spinbutton.get_value ();
730 float lufs_s = _lufs_s_spinbutton.get_value ();
731 float lufs_m = _lufs_m_spinbutton.get_value ();
732
733 float gain = 0;
734 bool set = false;
735
736 #define MIN_IF_SET(A, B) \
737 { \
738 if (set) { \
739 gain = std::min (gain, (A) - (B)); \
740 } else { \
741 gain = (A) - (B); \
742 } \
743 set = true; \
744 }
745
746 if (_dbfs_btn.get_active () && _dbfs_btn.sensitive ()) {
747 MIN_IF_SET (dbfs, _dbfs);
748 }
749 if (_dbtp_btn.get_active () && _dbtp_btn.sensitive ()) {
750 MIN_IF_SET (dbtp, _dbtp);
751 }
752 if (_lufs_i_btn.get_active () && _lufs_i_btn.sensitive ()) {
753 MIN_IF_SET (lufs_i, _lufs_i);
754 }
755 if (_lufs_s_btn.get_active () && _lufs_s_btn.sensitive ()) {
756 MIN_IF_SET (lufs_s, _lufs_s);
757 }
758 if (_lufs_m_btn.get_active () && _lufs_m_btn.sensitive ()) {
759 MIN_IF_SET (lufs_m, _lufs_m);
760 }
761
762 _delta_dbfs_label.set_text (string_compose (_("%1 dB"), std::setprecision (2), std::showpos, std::fixed, dbfs - _dbfs));
763 _delta_dbtp_label.set_text (string_compose (_("%1 dB"), std::setprecision (2), std::showpos, std::fixed, dbtp - _dbtp));
764 _delta_lufs_i_label.set_text (string_compose (_("%1 LU"), std::setprecision (2), std::showpos, std::fixed, lufs_i - _lufs_i));
765 _delta_lufs_s_label.set_text (string_compose (_("%1 LU"), std::setprecision (2), std::showpos, std::fixed, lufs_s - _lufs_s));
766 _delta_lufs_m_label.set_text (string_compose (_("%1 LU"), std::setprecision (2), std::showpos, std::fixed, lufs_m - _lufs_m));
767
768 _delta_dbfs_label.set_sensitive (_dbfs_btn.get_active ());
769 _delta_dbtp_label.set_sensitive (_dbtp_btn.get_active ());
770 _delta_lufs_i_label.set_sensitive (_lufs_i_btn.get_active ());
771 _delta_lufs_s_label.set_sensitive (_lufs_s_btn.get_active ());
772 _delta_lufs_m_label.set_sensitive (_lufs_m_btn.get_active ());
773
774 _gain_norm = gain;
775 bool in_range = gain_db () >= -40 && gain_db () <= 40;
776
777 _gain_norm_label.set_text (string_compose (_("%1 dB"), std::setprecision (2), std::showpos, std::fixed, _gain_norm));
778 if (!in_range) {
779 _gain_exceeds_label.set_text (_("exceeds"));
780 _gain_total_label.set_markup (_("<b> \u00B140 dB</b>"));
781 } else {
782 _gain_exceeds_label.set_text (X_(""));
783 _gain_total_label.set_markup (string_compose (_("<b>%1 dB</b>"), std::setw (7), std::setprecision (2), std::showpos, std::fixed, gain_db ()));
784 }
785
786 test_conformity ();
787 _ok_button->set_sensitive (in_range);
788 }
789
790 void
test_conformity()791 LoudnessDialog::test_conformity ()
792 {
793 if (_conformity_frame.get_child ()) {
794 _conformity_frame.remove ();
795 }
796
797 const float dbfs = rintf ((_dbfs + _gain_norm) * 10.f) / 10.f;
798 const float dbtp = rintf ((_dbtp + _gain_norm) * 10.f) / 10.f;
799 const float lufs_i = rintf ((_lufs_i + _gain_norm) * 10.f) / 10.f;
800
801 Table* t = manage (new Table ());
802 size_t n_pset = _lp.n_presets ();
803 size_t n_rows = ceil (n_pset / 3.0);
804
805 size_t row = 0;
806 size_t col = 0;
807
808 uint32_t c_good = UIConfigurationBase::instance ().color ("alert:green"); // OK / green
809 uint32_t c_warn = UIConfigurationBase::instance ().color ("alert:yellow"); // Warning / yellow
810 uint32_t c_fail = UIConfigurationBase::instance ().color ("alert:red"); // Fail / red
811
812 Gdk::Color color_good = ARDOUR_UI_UTILS::gdk_color_from_rgba (c_good);
813 Gdk::Color color_warn = ARDOUR_UI_UTILS::gdk_color_from_rgba (c_warn);
814 Gdk::Color color_fail = ARDOUR_UI_UTILS::gdk_color_from_rgba (c_fail);
815
816 for (size_t i = 1; i < n_pset; ++i) {
817 CLoudnessPreset const& preset = _lp[i];
818 Label* l = manage (new Label (preset.label + ":", ALIGN_LEFT));
819 t->attach (*l, col, col + 1, row, row + 1, EXPAND | FILL, SHRINK, 2, 0);
820
821 if (lufs_i > preset.LUFS_range[0]
822 || (preset.enable[0] && dbfs > preset.level[0])
823 || (preset.enable[1] && dbtp > preset.level[1])
824 ) {
825 #ifdef PLATFORM_WINDOWS
826 l = manage (new Label ("X", ALIGN_CENTER)); // cross mark
827 #else
828 l = manage (new Label ("\u274C", ALIGN_CENTER)); // cross mark
829 #endif
830 l->modify_font (UIConfiguration::instance ().get_BigFont ());
831 l->modify_fg (Gtk::STATE_NORMAL, color_fail);
832 Gtkmm2ext::set_size_request_to_display_given_text (*l, "\u274C\u2713", 0, 0);
833 set_tooltip (*l, "The signal is too loud.");
834 } else if (lufs_i < preset.LUFS_range[1]) {
835 l = manage (new Label ("\u2713", ALIGN_CENTER)); // check mark
836 l->modify_font (UIConfiguration::instance ().get_BigFont ());
837 l->modify_fg (Gtk::STATE_NORMAL, color_warn);
838 Gtkmm2ext::set_size_request_to_display_given_text (*l, "\u274C\u2713", 0, 0);
839 set_tooltip (*l, "The signal is too quiet, but satisfies the max. loudness spec.");
840 } else {
841 l = manage (new Label ("\u2714", ALIGN_CENTER)); // heavy check mark
842 l->modify_font (UIConfiguration::instance ().get_BigFont ());
843 l->modify_fg (Gtk::STATE_NORMAL, color_good);
844 set_tooltip (*l, "Signal loudness is within the spec.");
845 Gtkmm2ext::set_size_request_to_display_given_text (*l, "\u274C\u2713", 0, 0);
846 }
847
848 t->attach (*l, col + 1, col + 2, row, row + 1, SHRINK, SHRINK, 2, 0);
849
850 if (++row == n_rows) {
851 ArdourVSpacer* spc = manage (new ArdourVSpacer (1.0));
852 t->attach (*spc, col + 2, col + 3, 0, n_rows, FILL, EXPAND | FILL, 8, 0);
853 row = 0;
854 col += 3;
855 }
856 }
857
858 t->set_border_width (6);
859 _conformity_frame.add (*t);
860 _conformity_frame.show_all ();
861 }
862
863 void
graph_size_request(Gtk::Requisition * req)864 LoudnessDialog::graph_size_request (Gtk::Requisition* req)
865 {
866 if (_loudness_surf) {
867 req->width = _loudness_surf->get_width ();
868 req->height = _loudness_surf->get_height ();
869 } else {
870 req->width = 1;
871 req->height = 1;
872 }
873 }
874
875 bool
graph_expose_event(GdkEventExpose * ev)876 LoudnessDialog::graph_expose_event (GdkEventExpose* ev)
877 {
878 Cairo::RefPtr<Cairo::Context> cr = _loudness_graph.get_window ()->create_cairo_context ();
879 #if 0
880 Gtk::Allocation a = _loudness_graph.get_allocation ();
881 double const width = a.get_width ();
882 double const height = a.get_height ();
883 #endif
884
885 cr->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
886 cr->clip ();
887
888 if (_loudness_surf) {
889 cr->set_source (_loudness_surf, 0, 0);
890 cr->set_operator (Cairo::OPERATOR_OVER);
891 cr->paint ();
892 }
893
894 return true;
895 }
896
897 void
plot_graph(ExportAnalysisPtr p)898 LoudnessDialog::plot_graph (ExportAnalysisPtr p)
899 {
900 _loudness_surf = ArdourGraphs::plot_loudness (get_pango_context (), p, -1, 0, _session->nominal_sample_rate ());
901 _loudness_graph.queue_resize ();
902 }
903