/* * Copyright (C) 2020 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include "pbd/unwind.h" #include "ardour/dB.h" #include "ardour/plugin_insert.h" #include "ardour/plugin_manager.h" #include "ardour/processor.h" #include "ardour/session.h" #include "ardour/export_channel_configuration.h" #include "ardour/export_filename.h" #include "ardour/export_format_base.h" #include "ardour/export_format_specification.h" #include "ardour/export_handler.h" #include "ardour/export_status.h" #include "ardour/export_timespan.h" #include "gtkmm2ext/menu_elems.h" #include "gtkmm2ext/utils.h" #include "widgets/ardour_spacer.h" #include "widgets/prompter.h" #include "widgets/tooltips.h" #include "ardour_message.h" #include "export_analysis_graphs.h" #include "export_report.h" #include "loudness_dialog.h" #include "ui_config.h" #include "pbd/i18n.h" using namespace Gtk; using namespace ARDOUR; using namespace ArdourWidgets; bool LoudnessDialog::_first_time = true; CLoudnessPreset LoudnessDialog::_last_preset; LoudnessDialog::LoudnessDialog (Session* s, TimelineRange const& ar, bool as) : ArdourDialog (as ? _("Loudness Assistant") : _("Loudness Analyzer and Normalizer")) , _lp (false) , _session (s) , _range (ar) , _status (s->get_export_status ()) , _autostart (as) , _conformity_frame (_("Conformity Analysis (with gain applied)")) , _save_preset (_("Save")) , _remove_preset (_("Remove")) , _dbfs_btn (_("Peak:"), ArdourButton::led_default_elements, true) , _dbtp_btn (_("True Peak:"), ArdourButton::led_default_elements, true) , _lufs_i_btn (_("Integrated Loudness:"), ArdourButton::led_default_elements, true) , _lufs_s_btn (_("Max. Short Loudness:"), ArdourButton::led_default_elements, true) , _lufs_m_btn (_("Max. Momentary Loudness:"), ArdourButton::led_default_elements, true) , _rt_analysis_button (_("Realtime"), ArdourButton::led_default_elements, true) , _start_analysis_button (_("Analyze")) , _show_report_button (_("Analysis Report")) , _custom_pos_button (_("Custom Amplifier Position"), ArdourButton::led_default_elements, true) , _dbfs_adjustment (0.00, -10.00, 0.00, 0.1, 0.2) , _dbtp_adjustment (-1.0, -10.00, 0.00, 0.1, 0.2) , _lufs_i_adjustment (-23.0, -90.00, 0.00, 0.5, 1.0) , _lufs_s_adjustment (-20.0, -90.00, 0.00, 0.5, 1.0) , _lufs_m_adjustment (-17.0, -90.00, 0.00, 0.5, 1.0) , _dbfs_spinbutton (_dbfs_adjustment, 0.1, 1) , _dbtp_spinbutton (_dbtp_adjustment, 0.1, 1) , _lufs_i_spinbutton (_lufs_i_adjustment, 0.1, 1) , _lufs_s_spinbutton (_lufs_s_adjustment, 0.1, 1) , _lufs_m_spinbutton (_lufs_m_adjustment, 0.1, 1) , _gain_out (0) , _gain_norm (0) , _ignore_preset (false) , _ignore_change (false) { _preset = _lp[0]; if (_first_time) { _first_time = false; _last_preset = _lp[0]; } _preset = _last_preset; /* Dialog can be displayed from the mixer, override global transient_parent */ unset_transient_for (); /* query initial gain */ _gain_out = accurate_coefficient_to_dB (_session->master_volume ()->get_value ()); /* setup styles */ _start_analysis_button.set_name ("generic button"); _rt_analysis_button.set_name ("generic button"); _show_report_button.set_name ("generic button"); _custom_pos_button.set_name ("generic button"); _custom_pos_button.set_active (!_session->master_out()->volume_applies_to_output ()); GtkRequisition req = _start_analysis_button.size_request (); _start_analysis_button.set_size_request (-1, req.height * 1.1); _rt_analysis_button.set_size_request (-1, req.height * 1.1); _save_preset.set_name ("generic button"); _remove_preset.set_name ("generic button"); _dbfs_btn.set_name ("generic button"); _dbtp_btn.set_name ("generic button"); _lufs_i_btn.set_name ("generic button"); _lufs_s_btn.set_name ("generic button"); _lufs_m_btn.set_name ("generic button"); _dbfs_btn.set_led_left (true); _dbtp_btn.set_led_left (true); _lufs_i_btn.set_led_left (true); _lufs_s_btn.set_led_left (true); _lufs_m_btn.set_led_left (true); _preset_dropdown.set_can_focus (true); _start_analysis_button.set_can_focus (true); _rt_analysis_button.set_can_focus (true); _show_report_button.set_can_focus (true); _custom_pos_button.set_can_focus (true); _save_preset.set_can_focus (true); _remove_preset.set_can_focus (true); _dbfs_btn.set_can_focus (true); _dbtp_btn.set_can_focus (true); _lufs_i_btn.set_can_focus (true); _lufs_s_btn.set_can_focus (true); _lufs_m_btn.set_can_focus (true); _dbfs_label.modify_font (UIConfiguration::instance ().get_NormalMonospaceFont ()); _dbtp_label.modify_font (UIConfiguration::instance ().get_NormalMonospaceFont ()); _lufs_i_label.modify_font (UIConfiguration::instance ().get_NormalMonospaceFont ()); _lufs_s_label.modify_font (UIConfiguration::instance ().get_NormalMonospaceFont ()); _lufs_m_label.modify_font (UIConfiguration::instance ().get_NormalMonospaceFont ()); _delta_dbfs_label.modify_font (UIConfiguration::instance ().get_NormalMonospaceFont ()); _delta_dbtp_label.modify_font (UIConfiguration::instance ().get_NormalMonospaceFont ()); _delta_lufs_i_label.modify_font (UIConfiguration::instance ().get_NormalMonospaceFont ()); _delta_lufs_s_label.modify_font (UIConfiguration::instance ().get_NormalMonospaceFont ()); _delta_lufs_m_label.modify_font (UIConfiguration::instance ().get_NormalMonospaceFont ()); _gain_out_label.modify_font (UIConfiguration::instance ().get_NormalMonospaceFont ()); _gain_norm_label.modify_font (UIConfiguration::instance ().get_NormalMonospaceFont ()); _gain_total_label.modify_font (UIConfiguration::instance ().get_NormalMonospaceFont ()); _gain_exceeds_label.modify_font (UIConfiguration::instance ().get_NormalFont ()); /* result display layout */ #define ROW row, row + 1 int row = 0; Label* l; Table* t = manage (new Table (11, 8, false)); t->set_spacings (4); l = manage (new Label (_("Preset:"), ALIGN_START)); t->attach (*l, 0, 1, ROW, SHRINK | FILL); t->attach (_preset_dropdown, 1, 3, ROW); t->attach (_save_preset, 3, 4, ROW, SHRINK | FILL); t->attach (_remove_preset, 4, 5, ROW, SHRINK | FILL); /* horiz space */ l = manage (new Label (" ", ALIGN_START)); t->attach (*l, 5, 6, ROW, SHRINK, SHRINK, 6, 0); l = manage (new Label (" ", ALIGN_START)); t->attach (*l, 6, 7, ROW); t->attach (_show_report_button, 7, 8, ROW, SHRINK | FILL); ++row; l = manage (new Label (_("Target"), ALIGN_CENTER)); l->set_use_markup (true); t->attach (*l, 2, 3, ROW); l = manage (new Label (_("Measured"), ALIGN_CENTER)); l->set_use_markup (true); t->attach (*l, 3, 4, ROW); l = manage (new Label (_("Delta"), ALIGN_CENTER)); l->set_use_markup (true); t->attach (*l, 4, 5, ROW); row = 2; t->attach (_dbfs_btn, 0, 2, ROW); ++row; t->attach (_dbtp_btn, 0, 2, ROW); ++row; t->attach (_lufs_i_btn, 0, 2, ROW); ++row; t->attach (_lufs_s_btn, 0, 2, ROW); ++row; t->attach (_lufs_m_btn, 0, 2, ROW); ++row; ++row; // spacer l = manage (new Label (_("Gain to normalize:"), ALIGN_START)); t->attach (*l, 0, 2, ROW); ++row; l = manage (new Label (_("Previous output gain:"), ALIGN_START)); t->attach (*l, 0, 2, ROW); ++row; l = manage (new Label (_("Total gain:"), ALIGN_START)); t->attach (*l, 0, 2, ROW); row = 2; t->attach (_dbfs_spinbutton, 2, 3, ROW, EXPAND|FILL, EXPAND|FILL, 8, 0); ++row; t->attach (_dbtp_spinbutton, 2, 3, ROW, EXPAND|FILL, EXPAND|FILL, 8, 0); ++row; t->attach (_lufs_i_spinbutton, 2, 3, ROW, EXPAND|FILL, EXPAND|FILL, 8, 0); ++row; t->attach (_lufs_s_spinbutton, 2, 3, ROW, EXPAND|FILL, EXPAND|FILL, 8, 0); ++row; t->attach (_lufs_m_spinbutton, 2, 3, ROW, EXPAND|FILL, EXPAND|FILL, 8, 0); ++row; row = 2; t->attach (_dbfs_label, 3, 4, ROW); ++row; t->attach (_dbtp_label, 3, 4, ROW); ++row; t->attach (_lufs_i_label, 3, 4, ROW); ++row; t->attach (_lufs_s_label, 3, 4, ROW); ++row; t->attach (_lufs_m_label, 3, 4, ROW); ++row; row = 2; t->attach (_delta_dbfs_label, 4, 5, ROW); ++row; t->attach (_delta_dbtp_label, 4, 5, ROW); ++row; t->attach (_delta_lufs_i_label, 4, 5, ROW); ++row; t->attach (_delta_lufs_s_label, 4, 5, ROW); ++row; t->attach (_delta_lufs_m_label, 4, 5, ROW); ++row; ArdourHSpacer* spc = manage (new ArdourHSpacer (1.0)); t->attach (*spc, 3, 5, ROW); ++row; t->attach (_gain_norm_label, 4, 5, ROW); ++row; t->attach (_gain_out_label, 4, 5, ROW); ++row; t->attach (_gain_exceeds_label, 3, 4, ROW); t->attach (_gain_total_label, 4, 5, ROW); t->attach (_custom_pos_button, 7, 8, row - 1, row + 1, SHRINK | FILL, SHRINK); t->attach (_conformity_frame, 6, 8, 1, row - 3, SHRINK | FILL, EXPAND | FILL, 0, 0); set_tooltip (_custom_pos_button, _("When enabled an amplifier processor is used to apply the gain. " "This allows for custom positioning of the gain-stage in the master-bus' signal flow, " "potentially followed by a limiter to conform to both loudness and peak requirements. " "Depending on limiter settings or DSP after the gain-stage, repeat loudness measurements may produce different results.\n" "When disabled, the gain is applied directly to the output of the master-bus. This results in an efficient and reliable volume adjustment." )); _dbfs_label.set_alignment (ALIGN_END); _dbtp_label.set_alignment (ALIGN_END); _lufs_i_label.set_alignment (ALIGN_END); _lufs_s_label.set_alignment (ALIGN_END); _lufs_m_label.set_alignment (ALIGN_END); _delta_dbfs_label.set_alignment (ALIGN_END); _delta_dbtp_label.set_alignment (ALIGN_END); _delta_lufs_i_label.set_alignment (ALIGN_END); _delta_lufs_s_label.set_alignment (ALIGN_END); _delta_lufs_m_label.set_alignment (ALIGN_END); _gain_norm_label.set_alignment (ALIGN_END); _gain_out_label.set_alignment (ALIGN_END); _gain_total_label.set_alignment (ALIGN_END); _gain_exceeds_label.set_alignment (ALIGN_END); HBox* hb = manage (new (HBox)); hb->pack_start (_loudness_graph, true, false); _result_box.pack_start (*hb, false, false, 0); _result_box.pack_start (*t, false, false, 6); /* analysis progress layout */ _progress_box.pack_start (_progress_bar, false, false, 6); /* setup and info layout */ t = manage (new Table (2, 3, false)); t->set_spacings (4); l = manage (new Label ()); l->set_markup (_("Loudness Analysis\n")); l->set_alignment (ALIGN_START, ALIGN_START); t->attach (*l, 0, 1, 0, 1, EXPAND | FILL, FILL, 8, 2); l = manage (new Label ()); l->set_line_wrap (); l->set_alignment (ALIGN_START, ALIGN_START); l->set_markup (_( "This allows the user to analyze and conform the loudness of the signal at the master-bus " "output of the complete session, as it would be exported. " "When using this feature, remember to disable normalization in the session export profile.")); t->attach (*l, 0, 1, 1, 2, EXPAND | FILL, FILL, 8, 2); l = manage (new Label ()); l->set_line_wrap (); l->set_alignment (ALIGN_START, ALIGN_START); l->set_markup (_( "By default, a faster-than-realtime export is used to assess the loudness of the " "session. If any outboard gear is used, a realtime export is available, to " "play at normal speed.")); t->attach (*l, 0, 1, 2, 3, EXPAND | FILL, FILL, 8, 2); Alignment* align = manage (new Alignment (0, 0, 1, 0)); align->add (_start_analysis_button); t->attach (*align, 1, 2, 1, 2, FILL, FILL, 2, 0); align = manage (new Alignment (0, 0, 1, 0)); align->add (_rt_analysis_button); t->attach (*align, 1, 2, 2, 3, FILL, FILL, 2, 0); _setup_box.pack_start (*t, false, false, 6); /* global layout */ get_vbox ()->pack_start (_setup_box); get_vbox ()->pack_start (_progress_box); get_vbox ()->pack_start (_result_box); _progress_box.set_size_request (400, -1); _ok_button = add_button (Stock::APPLY, RESPONSE_APPLY); _cancel_button = add_button (Stock::CANCEL, RESPONSE_CANCEL); /* fill in presets */ for (size_t i = 0; i < _lp.n_presets (); ++i) { using namespace Gtkmm2ext; _preset_dropdown.AddMenuElem (MenuElemNoMnemonic (_lp[i].label, sigc::bind (sigc::mem_fun (*this, &LoudnessDialog::load_preset), i))); } apply_preset (); if (!_lp.find_preset (_preset)) { _preset.label = _("Custom"); } check_preset (); _gain_out_label.set_text (string_compose (_("%1 dB"), std::setprecision (2), std::showpos, std::fixed, _gain_out)); /* setup graph */ _loudness_graph.signal_size_request ().connect (sigc::mem_fun (*this, &LoudnessDialog::graph_size_request)); _loudness_graph.signal_expose_event ().connect (sigc::mem_fun (*this, &LoudnessDialog::graph_expose_event)); /* connect signals */ _cancel_button->signal_clicked ().connect (sigc::mem_fun (this, &LoudnessDialog::cancel_analysis)); _dbfs_spinbutton.signal_value_changed ().connect (mem_fun (*this, &LoudnessDialog::update_settings)); _dbtp_spinbutton.signal_value_changed ().connect (mem_fun (*this, &LoudnessDialog::update_settings)); _lufs_i_spinbutton.signal_value_changed ().connect (mem_fun (*this, &LoudnessDialog::update_settings)); _lufs_s_spinbutton.signal_value_changed ().connect (mem_fun (*this, &LoudnessDialog::update_settings)); _lufs_m_spinbutton.signal_value_changed ().connect (mem_fun (*this, &LoudnessDialog::update_settings)); _save_preset.signal_clicked.connect (mem_fun (*this, &LoudnessDialog::save_preset)); _remove_preset.signal_clicked.connect (mem_fun (*this, &LoudnessDialog::remove_preset)); _show_report_button.signal_clicked.connect (mem_fun (*this, &LoudnessDialog::display_report)); _start_analysis_button.signal_clicked.connect (mem_fun (*this, &LoudnessDialog::start_analysis)); _dbfs_btn.signal_clicked.connect (mem_fun (*this, &LoudnessDialog::update_settings)); _dbtp_btn.signal_clicked.connect (mem_fun (*this, &LoudnessDialog::update_settings)); _lufs_i_btn.signal_clicked.connect (mem_fun (*this, &LoudnessDialog::update_settings)); _lufs_s_btn.signal_clicked.connect (mem_fun (*this, &LoudnessDialog::update_settings)); _lufs_m_btn.signal_clicked.connect (mem_fun (*this, &LoudnessDialog::update_settings)); _ok_button->set_sensitive (false); _show_report_button.set_sensitive (false); show_all_children (); _result_box.hide (); _progress_box.hide (); } void LoudnessDialog::cancel_analysis () { if (_status->running ()) { _status->abort (); } } void LoudnessDialog::start_analysis () { if (0 == analyze ()) { display_results (); } else { _setup_box.show (); } } bool LoudnessDialog::on_delete_event (GdkEventAny* ev) { cancel_analysis (); return ArdourDialog::on_delete_event (ev); } int LoudnessDialog::run () { if (_autostart) { show (); if (0 == analyze ()) { display_results (); } else { return RESPONSE_CANCEL; } } int const r = ArdourDialog::run (); cancel_analysis (); if (r == RESPONSE_APPLY) { _session->master_volume ()->set_value (dB_to_coefficient (gain_db ()), PBD::Controllable::NoGroup); _session->master_out()->set_volume_applies_to_output (!_custom_pos_button.get_active ()); _last_preset = _preset; } return r; } gint LoudnessDialog::progress_timeout () { float progress = ((float)_status->processed_samples_current_timespan) / _status->total_samples_current_timespan; _progress_bar.set_text ("Analyzing"); _progress_bar.set_fraction (progress); return true; } int LoudnessDialog::analyze () { /* These are ensured in Editor::measure_master_loudness () */ assert (_session->master_out ()); assert (_session->master_volume ()); assert (_session->master_out()->output()); assert (_session->master_out()->output()->n_ports().n_audio() == 2); assert (_range.start() < _range.end()); ExportTimespanPtr tsp = _session->get_export_handler ()->add_timespan (); std::shared_ptr ccp = _session->get_export_handler ()->add_channel_config (); std::shared_ptr fnp = _session->get_export_handler ()->add_filename (); std::shared_ptr fmp = _session->get_export_handler ()->add_format (); /* setup format */ fmp->set_sample_format (ExportFormatBase::SF_Float); fmp->set_sample_rate (ExportFormatBase::SR_Session); fmp->set_format_id (ExportFormatBase::F_None); fmp->set_type (ExportFormatBase::T_None); fmp->set_extension ("wav"); fmp->set_analyse (true); /* setup range */ tsp->set_range (_range.start().samples(), _range.end().samples()); tsp->set_range_id ("selection"); tsp->set_realtime (_rt_analysis_button.get_active ()); tsp->set_name ("master"); /* setup channels, use master out */ IO* master_out = _session->master_out ()->output ().get (); for (uint32_t n = 0; n < master_out->n_ports ().n_audio (); ++n) { PortExportChannel* channel = new PortExportChannel (); channel->add_port (master_out->audio (n)); ExportChannelPtr chan_ptr (channel); ccp->register_channel (chan_ptr); } /* do audio export */ std::shared_ptr b; _session->get_export_handler ()->reset (); _session->get_export_handler ()->add_export_config (tsp, ccp, fmp, fnp, b); _session->get_export_handler ()->do_export (); /* show progress */ _setup_box.hide (); _progress_box.show_all (); /* shrink window height (setup box) */ Gtk::Requisition wr; get_size (wr.width, wr.height); resize (wr.width, 60); sigc::connection progress_connection = Glib::signal_timeout ().connect (sigc::mem_fun (*this, &LoudnessDialog::progress_timeout), 100); while (_status->running ()) { if (gtk_events_pending ()) { gtk_main_iteration (); } else { Glib::usleep (10000); } } progress_connection.disconnect (); _progress_box.hide_all (); /* done */ _status->finish (TRS_UI); if (!_status->aborted () && _status->result_map.size () != 1) { ArdourMessageDialog (_("Loudness measurement returned no results. Likely because the analyzed range is to short."), false, MESSAGE_ERROR).run (); return 1; } return _status->aborted () ? 1 : 0; } void LoudnessDialog::display_report () { ExportReport er (_("Export Loudness Report"), _status->result_map); er.set_transient_for (*this); er.run (); } void LoudnessDialog::save_preset () { assert (_preset.user); ArdourWidgets::Prompter name_prompter (*this, true, true); name_prompter.set_title (_("Save Loudness Preset")); name_prompter.set_prompt (_("Name:")); name_prompter.add_button (_("Save"), Gtk::RESPONSE_ACCEPT); name_prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false); name_prompter.set_initial_text (_preset.label, _preset.label != _("Custom")); name_prompter.show_all (); bool saved = false; bool done = false; std::string result; while (!done) { switch (name_prompter.run ()) { case Gtk::RESPONSE_ACCEPT: name_prompter.get_result (result); name_prompter.hide (); if (result.length()) { if (result == _("Custom")) { /* ask again */ continue; } _preset.label = result; _preset.report = false; if (_lp.push_back (_preset)) { done = true; saved = true; } else { /* Invalid Name, ask again */ } } else { /* nothing entered, just get out of here */ done = true; } break; default: done = true; break; } } if (saved) { _preset_dropdown.clear_items (); for (size_t i = 0; i < _lp.n_presets (); ++i) { using namespace Gtkmm2ext; _preset_dropdown.AddMenuElem (MenuElemNoMnemonic (_lp[i].label, sigc::bind (sigc::mem_fun (*this, &LoudnessDialog::load_preset), i))); } PBD::Unwinder uw (_ignore_preset, true); _preset_dropdown.set_active (_preset.label); _save_preset.set_sensitive (false); _remove_preset.set_sensitive (_preset.user); } } void LoudnessDialog::remove_preset () { assert (_preset.user); if (_lp.erase (_preset)) { _preset_dropdown.clear_items (); for (size_t i = 0; i < _lp.n_presets (); ++i) { using namespace Gtkmm2ext; _preset_dropdown.AddMenuElem (MenuElemNoMnemonic (_lp[i].label, sigc::bind (sigc::mem_fun (*this, &LoudnessDialog::load_preset), i))); } _preset.label = _("Custom"); update_settings (); } } void LoudnessDialog::load_preset (size_t n) { if (_ignore_preset) { return; } _preset = _lp[n]; _save_preset.set_sensitive (false); _remove_preset.set_sensitive (_preset.user); apply_preset (); calculate_gain (); } void LoudnessDialog::apply_preset () { PBD::Unwinder uw (_ignore_change, true); _preset_dropdown.set_text (_preset.label); _dbfs_btn.set_active (_preset.enable[0]); _dbtp_btn.set_active (_preset.enable[1]); _lufs_i_btn.set_active (_preset.enable[2]); _lufs_s_btn.set_active (_preset.enable[3]); _lufs_m_btn.set_active (_preset.enable[4]); _dbfs_spinbutton.set_value (_preset.level[0]); _dbtp_spinbutton.set_value (_preset.level[1]); _lufs_i_spinbutton.set_value (_preset.level[2]); _lufs_s_spinbutton.set_value (_preset.level[3]); _lufs_m_spinbutton.set_value (_preset.level[4]); update_sensitivity (); } void LoudnessDialog::update_sensitivity () { _dbfs_spinbutton.set_sensitive (_dbfs_btn.get_active () && _dbfs_btn.is_sensitive ()); _dbtp_spinbutton.set_sensitive (_dbtp_btn.get_active () && _dbtp_btn.is_sensitive ()); _lufs_i_spinbutton.set_sensitive (_lufs_i_btn.get_active () && _dbtp_btn.is_sensitive ()); _lufs_s_spinbutton.set_sensitive (_lufs_s_btn.get_active () && _lufs_s_btn.is_sensitive ()); _lufs_m_spinbutton.set_sensitive (_lufs_m_btn.get_active () && _lufs_m_btn.is_sensitive ()); } void LoudnessDialog::check_preset () { if (_lp.find_preset (_preset)) { _save_preset.set_sensitive (false); _remove_preset.set_sensitive (_preset.user); } else { if (!_preset.user) { _preset.label = _("Custom"); } _preset.user = true; _preset.report = false; _save_preset.set_sensitive (true); _remove_preset.set_sensitive (false); } _preset_dropdown.set_text (_preset.label); } void LoudnessDialog::update_settings () { if (_ignore_change) { return; } /* check if preset with current settings exists */ _preset.level[0] = _dbfs_spinbutton.get_value (); _preset.level[1] = _dbtp_spinbutton.get_value (); _preset.level[2] = _lufs_i_spinbutton.get_value (); _preset.level[3] = _lufs_s_spinbutton.get_value (); _preset.level[4] = _lufs_m_spinbutton.get_value (); _preset.enable[0] = _dbfs_btn.get_active (); _preset.enable[1] = _dbtp_btn.get_active (); _preset.enable[2] = _lufs_i_btn.get_active (); _preset.enable[3] = _lufs_s_btn.get_active (); _preset.enable[4] = _lufs_m_btn.get_active (); check_preset (); update_sensitivity (); calculate_gain (); } float LoudnessDialog::gain_db () const { return _gain_norm + _gain_out; } void LoudnessDialog::display_results () { AnalysisResults const& ar (_status->result_map); assert (ar.size () == 1); ExportAnalysisPtr p = ar.begin ()->second; if (!p->have_loudness || !p->have_dbtp || !p->have_lufs_graph) { ArdourMessageDialog ( string_compose (_("True-peak and loudness measurement failed. %1-VAMP analysis plugin is missing on your system. Please contact your vendor."), PROGRAM_NAME), false, MESSAGE_ERROR) .run (); } plot_graph (p); _dbfs = accurate_coefficient_to_dB (p->peak); _dbtp = accurate_coefficient_to_dB (p->truepeak); _lufs_i = p->integrated_loudness > -200 ? p->integrated_loudness : -std::numeric_limits::infinity (); _lufs_s = p->max_loudness_short > -200 ? p->max_loudness_short : -std::numeric_limits::infinity (); _lufs_m = p->max_loudness_momentary > -200 ? p->max_loudness_momentary : -std::numeric_limits::infinity (); _dbfs_btn.set_sensitive (_dbfs > -300); _dbtp_btn.set_sensitive (_dbtp > -300); _lufs_i_btn.set_sensitive (p->integrated_loudness > -200); _lufs_s_btn.set_sensitive (p->max_loudness_short > -200); _lufs_m_btn.set_sensitive (p->max_loudness_momentary > -200); _dbfs_label.set_text (string_compose (_("%1 dBFS"), std::setprecision (1), std::fixed, _dbfs)); _dbtp_label.set_text (string_compose (_("%1 dBTP"), std::setprecision (1), std::fixed, _dbtp)); _lufs_i_label.set_text (string_compose (_("%1 LUFS"), std::setprecision (1), std::fixed, _lufs_i)); _lufs_s_label.set_text (string_compose (_("%1 LUFS"), std::setprecision (1), std::fixed, _lufs_s)); _lufs_m_label.set_text (string_compose (_("%1 LUFS"), std::setprecision (1), std::fixed, _lufs_m)); update_sensitivity (); calculate_gain (); _result_box.show_all (); _show_report_button.set_sensitive (true); } void LoudnessDialog::calculate_gain () { float dbfs = _dbfs_spinbutton.get_value (); float dbtp = _dbtp_spinbutton.get_value (); float lufs_i = _lufs_i_spinbutton.get_value (); float lufs_s = _lufs_s_spinbutton.get_value (); float lufs_m = _lufs_m_spinbutton.get_value (); float gain = 0; bool set = false; #define MIN_IF_SET(A, B) \ { \ if (set) { \ gain = std::min (gain, (A) - (B)); \ } else { \ gain = (A) - (B); \ } \ set = true; \ } if (_dbfs_btn.get_active () && _dbfs_btn.is_sensitive ()) { MIN_IF_SET (dbfs, _dbfs); } if (_dbtp_btn.get_active () && _dbtp_btn.is_sensitive ()) { MIN_IF_SET (dbtp, _dbtp); } if (_lufs_i_btn.get_active () && _lufs_i_btn.is_sensitive ()) { MIN_IF_SET (lufs_i, _lufs_i); } if (_lufs_s_btn.get_active () && _lufs_s_btn.is_sensitive ()) { MIN_IF_SET (lufs_s, _lufs_s); } if (_lufs_m_btn.get_active () && _lufs_m_btn.is_sensitive ()) { MIN_IF_SET (lufs_m, _lufs_m); } _delta_dbfs_label.set_text (string_compose (_("%1 dB"), std::setprecision (2), std::showpos, std::fixed, dbfs - _dbfs)); _delta_dbtp_label.set_text (string_compose (_("%1 dB"), std::setprecision (2), std::showpos, std::fixed, dbtp - _dbtp)); _delta_lufs_i_label.set_text (string_compose (_("%1 LU"), std::setprecision (2), std::showpos, std::fixed, lufs_i - _lufs_i)); _delta_lufs_s_label.set_text (string_compose (_("%1 LU"), std::setprecision (2), std::showpos, std::fixed, lufs_s - _lufs_s)); _delta_lufs_m_label.set_text (string_compose (_("%1 LU"), std::setprecision (2), std::showpos, std::fixed, lufs_m - _lufs_m)); _delta_dbfs_label.set_sensitive (_dbfs_btn.get_active ()); _delta_dbtp_label.set_sensitive (_dbtp_btn.get_active ()); _delta_lufs_i_label.set_sensitive (_lufs_i_btn.get_active ()); _delta_lufs_s_label.set_sensitive (_lufs_s_btn.get_active ()); _delta_lufs_m_label.set_sensitive (_lufs_m_btn.get_active ()); _gain_norm = gain; bool in_range = gain_db () >= -40 && gain_db () <= 40; _gain_norm_label.set_text (string_compose (_("%1 dB"), std::setprecision (2), std::showpos, std::fixed, _gain_norm)); if (!in_range) { _gain_exceeds_label.set_text (_("exceeds")); _gain_total_label.set_markup (_(u8" \u00B140 dB")); } else { _gain_exceeds_label.set_text (X_("")); _gain_total_label.set_markup (string_compose (_("%1 dB"), std::setw (7), std::setprecision (2), std::showpos, std::fixed, gain_db ())); } test_conformity (); _ok_button->set_sensitive (in_range); } void LoudnessDialog::test_conformity () { if (_conformity_frame.get_child ()) { _conformity_frame.remove (); } const float dbfs = rintf ((_dbfs + _gain_norm) * 10.f) / 10.f; const float dbtp = rintf ((_dbtp + _gain_norm) * 10.f) / 10.f; const float lufs_i = rintf ((_lufs_i + _gain_norm) * 10.f) / 10.f; Table* t = manage (new Table ()); size_t n_pset = _lp.n_presets (); size_t n_rows = ceil (n_pset / 3.0); size_t row = 0; size_t col = 0; uint32_t c_good = UIConfigurationBase::instance ().color ("alert:green"); // OK / green uint32_t c_warn = UIConfigurationBase::instance ().color ("alert:yellow"); // Warning / yellow uint32_t c_fail = UIConfigurationBase::instance ().color ("alert:red"); // Fail / red Gdk::Color color_good = Gtkmm2ext::gdk_color_from_rgba (c_good); Gdk::Color color_warn = Gtkmm2ext::gdk_color_from_rgba (c_warn); Gdk::Color color_fail = Gtkmm2ext::gdk_color_from_rgba (c_fail); for (size_t i = 1; i < n_pset; ++i) { CLoudnessPreset const& preset = _lp[i]; Label* l = manage (new Label (preset.label + ":", ALIGN_START)); t->attach (*l, col, col + 1, row, row + 1, EXPAND | FILL, SHRINK, 2, 0); if (lufs_i > preset.LUFS_range[0] || (preset.enable[0] && dbfs > preset.level[0]) || (preset.enable[1] && dbtp > preset.level[1]) ) { l = manage (new Label (u8"\u274C", ALIGN_CENTER)); // cross mark l->modify_font (UIConfiguration::instance ().get_ArdourBigFont ()); l->modify_fg (Gtk::STATE_NORMAL, color_fail); set_tooltip (*l, "The signal is too loud."); } else if (lufs_i < preset.LUFS_range[1]) { l = manage (new Label (u8"\U0001F509", ALIGN_CENTER)); // speaker icon w/1 bar l->modify_font (UIConfiguration::instance ().get_ArdourBigFont ()); l->modify_fg (Gtk::STATE_NORMAL, color_warn); set_tooltip (*l, "The signal is too quiet, but satisfies the max. loudness spec."); } else { l = manage (new Label (u8"\u2714", ALIGN_CENTER)); // heavy check mark l->modify_font (UIConfiguration::instance ().get_ArdourBigFont ()); l->modify_fg (Gtk::STATE_NORMAL, color_good); set_tooltip (*l, "Signal loudness is within the spec."); Gtkmm2ext::set_size_request_to_display_given_text (*l, u8"\u274C\u2713", 0, 0); } t->attach (*l, col + 1, col + 2, row, row + 1, SHRINK, SHRINK, 2, 0); if (++row == n_rows) { ArdourVSpacer* spc = manage (new ArdourVSpacer (1.0)); t->attach (*spc, col + 2, col + 3, 0, n_rows, FILL, EXPAND | FILL, 8, 0); row = 0; col += 3; } } t->set_border_width (6); _conformity_frame.add (*t); _conformity_frame.show_all (); } void LoudnessDialog::graph_size_request (Gtk::Requisition* req) { if (_loudness_surf) { req->width = _loudness_surf->get_width (); req->height = _loudness_surf->get_height (); } else { req->width = 1; req->height = 1; } } bool LoudnessDialog::graph_expose_event (GdkEventExpose* ev) { Cairo::RefPtr cr = _loudness_graph.get_window ()->create_cairo_context (); #if 0 Gtk::Allocation a = _loudness_graph.get_allocation (); double const width = a.get_width (); double const height = a.get_height (); #endif cr->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height); cr->clip (); if (_loudness_surf) { cr->set_source (_loudness_surf, 0, 0); cr->set_operator (Cairo::OPERATOR_OVER); cr->paint (); } return true; } void LoudnessDialog::plot_graph (ExportAnalysisPtr p) { _loudness_surf = ArdourGraphs::plot_loudness (get_pango_context (), p, -1, 0, _session->nominal_sample_rate ()); _loudness_graph.queue_resize (); }