From e0b2ef3dbac6fc4900cad6fea4395c212578602b Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Wed, 18 Dec 2019 01:09:25 +0100 Subject: [PATCH] Add an output audio matrix (#1482). --- src/lib/config.cc | 60 ++++++++++++++++++++++++++++++++++++ src/lib/config.h | 10 +++++- src/wx/audio_mapping_view.cc | 32 +++++++++++-------- src/wx/audio_mapping_view.h | 7 ++++- src/wx/audio_panel.cc | 2 +- src/wx/config_dialog.cc | 44 ++++++++++++++++++++++++-- src/wx/config_dialog.h | 8 ++++- src/wx/film_viewer.cc | 48 +++++++++-------------------- 8 files changed, 159 insertions(+), 52 deletions(-) diff --git a/src/lib/config.cc b/src/lib/config.cc index 581620f83..7f32320df 100644 --- a/src/lib/config.cc +++ b/src/lib/config.cc @@ -53,6 +53,7 @@ using std::cout; using std::ifstream; using std::string; using std::list; +using std::min; using std::max; using std::remove; using std::exception; @@ -173,6 +174,7 @@ Config::set_defaults () _player_content_directory = boost::none; _player_playlist_directory = boost::none; _player_kdm_directory = boost::none; + _audio_mapping = boost::none; #ifdef DCPOMATIC_VARIANT_SWAROOP _player_background_image = boost::none; _kdm_server_url = "http://localhost:8000/{CPL}"; @@ -590,6 +592,11 @@ try _player_content_directory = f.optional_string_child("PlayerContentDirectory"); _player_playlist_directory = f.optional_string_child("PlayerPlaylistDirectory"); _player_kdm_directory = f.optional_string_child("PlayerKDMDirectory"); + + if (f.optional_node_child("AudioMapping")) { + _audio_mapping = AudioMapping (f.node_child("AudioMapping"), Film::current_state_version); + } + #ifdef DCPOMATIC_VARIANT_SWAROOP _player_background_image = f.optional_string_child("PlayerBackgroundImage"); _kdm_server_url = f.optional_string_child("KDMServerURL").get_value_or("http://localhost:8000/{CPL}"); @@ -1037,6 +1044,9 @@ Config::write_config () const /* [XML] PlayerKDMDirectory Directory to use for player KDMs in the dual-screen mode. */ root->add_child("PlayerKDMDirectory")->add_child_text(_player_kdm_directory->string()); } + if (_audio_mapping) { + _audio_mapping->as_xml (root->add_child("AudioMapping")); + } #ifdef DCPOMATIC_VARIANT_SWAROOP if (_player_background_image) { root->add_child("PlayerBackgroundImage")->add_child_text(_player_background_image->string()); @@ -1395,3 +1405,53 @@ Config::have_write_permission () const fclose (f); return true; } + +/** @param output_channels Number of output channels in use. + * @return Audio mapping for this output channel count (may be a default). + */ +AudioMapping +Config::audio_mapping (int output_channels) +{ + if (!_audio_mapping || _audio_mapping->output_channels() != output_channels) { + /* Set up a default */ + _audio_mapping = AudioMapping (MAX_DCP_AUDIO_CHANNELS, output_channels); + if (output_channels == 2) { + /* Special case for stereo output. + Map so that Lt = L(-3dB) + Ls(-3dB) + C(-6dB) + Lfe(-10dB) + Rt = R(-3dB) + Rs(-3dB) + C(-6dB) + Lfe(-10dB) + */ + _audio_mapping->set (dcp::LEFT, 0, 1 / sqrt(2)); // L -> Lt + _audio_mapping->set (dcp::RIGHT, 1, 1 / sqrt(2)); // R -> Rt + _audio_mapping->set (dcp::CENTRE, 0, 1 / 2.0); // C -> Lt + _audio_mapping->set (dcp::CENTRE, 1, 1 / 2.0); // C -> Rt + _audio_mapping->set (dcp::LFE, 0, 1 / sqrt(10)); // Lfe -> Lt + _audio_mapping->set (dcp::LFE, 1, 1 / sqrt(10)); // Lfe -> Rt + _audio_mapping->set (dcp::LS, 0, 1 / sqrt(2)); // Ls -> Lt + _audio_mapping->set (dcp::RS, 1, 1 / sqrt(2)); // Rs -> Rt + } else { + /* 1:1 mapping */ + for (int i = 0; i < min (MAX_DCP_AUDIO_CHANNELS, output_channels); ++i) { + _audio_mapping->set (i, i, 1); + } + } + } + + return *_audio_mapping; +} + +void +Config::set_audio_mapping (AudioMapping m) +{ + _audio_mapping = m; + changed (AUDIO_MAPPING); +} + +void +Config::set_audio_mapping_to_default () +{ + DCPOMATIC_ASSERT (_audio_mapping); + int const ch = _audio_mapping->output_channels (); + _audio_mapping = boost::none; + _audio_mapping = audio_mapping (ch); + changed (AUDIO_MAPPING); +} diff --git a/src/lib/config.h b/src/lib/config.h index ff7a0fe39..061d6ee7c 100644 --- a/src/lib/config.h +++ b/src/lib/config.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2018 Carl Hetherington + Copyright (C) 2012-2019 Carl Hetherington This file is part of DCP-o-matic. @@ -29,6 +29,7 @@ #include "types.h" #include "state.h" #include "edid.h" +#include "audio_mapping.h" #include #include #include @@ -83,6 +84,7 @@ public: PLAYER_DEBUG_LOG, HISTORY, SHOW_EXPERIMENTAL_AUDIO_PROCESSORS, + AUDIO_MAPPING, #ifdef DCPOMATIC_VARIANT_SWAROOP PLAYER_BACKGROUND_IMAGE, #endif @@ -522,6 +524,8 @@ public: return _player_kdm_directory; } + AudioMapping audio_mapping (int output_channels); + #ifdef DCPOMATIC_VARIANT_SWAROOP boost::optional player_background_image () const { return _player_background_image; @@ -1036,6 +1040,9 @@ public: changed (); } + void set_audio_mapping (AudioMapping m); + void set_audio_mapping_to_default (); + #ifdef DCPOMATIC_VARIANT_SWAROOP void set_player_background_image (boost::filesystem::path p) { maybe_set (_player_background_image, p, PLAYER_BACKGROUND_IMAGE); @@ -1293,6 +1300,7 @@ private: boost::optional _player_content_directory; boost::optional _player_playlist_directory; boost::optional _player_kdm_directory; + boost::optional _audio_mapping; #ifdef DCPOMATIC_VARIANT_SWAROOP boost::optional _player_background_image; std::string _kdm_server_url; diff --git a/src/wx/audio_mapping_view.cc b/src/wx/audio_mapping_view.cc index 98430da3b..140f18d60 100644 --- a/src/wx/audio_mapping_view.cc +++ b/src/wx/audio_mapping_view.cc @@ -60,10 +60,14 @@ enum { ID_edit = 4 }; -AudioMappingView::AudioMappingView (wxWindow* parent) +AudioMappingView::AudioMappingView (wxWindow* parent, wxString left_label, wxString from, wxString top_label, wxString to) : wxPanel (parent, wxID_ANY) , _menu_input (0) , _menu_output (1) + , _left_label (left_label) + , _from (from) + , _top_label (top_label) + , _to (to) { _menu = new wxMenu; _menu->Append (ID_off, _("Off")); @@ -159,16 +163,12 @@ AudioMappingView::paint_static (wxDC& dc, wxGraphicsContext* gc) wxCoord label_width; wxCoord label_height; - /* DCP label at the top */ + dc.GetTextExtent (_top_label, &label_width, &label_height); + dc.DrawText (_top_label, LEFT_WIDTH + (_output_channels.size() * GRID_SPACING - label_width) / 2, (GRID_SPACING - label_height) / 2); - dc.GetTextExtent (_("DCP"), &label_width, &label_height); - dc.DrawText (_("DCP"), LEFT_WIDTH + (_output_channels.size() * GRID_SPACING - label_width) / 2, (GRID_SPACING - label_height) / 2); - - /* Content label on the left */ - - dc.GetTextExtent (_("Content"), &label_width, &label_height); + dc.GetTextExtent (_left_label, &label_width, &label_height); dc.DrawRotatedText ( - _("Content"), + _left_label, (GRID_SPACING - label_height) / 2, TOP_HEIGHT + (_input_channels.size() * GRID_SPACING + label_width) / 2, 90 @@ -557,7 +557,7 @@ AudioMappingView::safe_input_channel_name (int n) const } } - if (group) { + if (group && !group->IsEmpty()) { return wxString::Format ("%s/%s", group->data(), std_to_wx(_input_channels[n]).data()); } @@ -584,21 +584,27 @@ AudioMappingView::motion (wxMouseEvent& ev) float const gain = _map.get(channels->first, channels->second); if (gain == 0) { s = wxString::Format ( - _("No audio will be passed from content channel '%s' to DCP channel '%s'."), + _("No audio will be passed from %s channel '%s' to %s channel '%s'."), + _from, safe_input_channel_name(channels->first), + _to, safe_output_channel_name(channels->second) ); } else if (gain == 1) { s = wxString::Format ( - _("Audio will be passed from content channel %s to DCP channel %s unaltered."), + _("Audio will be passed from %s channel %s to %s channel %s unaltered."), + _from, safe_input_channel_name(channels->first), + _to, safe_output_channel_name(channels->second) ); } else { float const dB = 20 * log10 (gain); s = wxString::Format ( - _("Audio will be passed from content channel %s to DCP channel %s with gain %.1fdB."), + _("Audio will be passed from %s channel %s to %s channel %s with gain %.1fdB."), + _from, safe_input_channel_name(channels->first), + _to, safe_output_channel_name(channels->second), dB ); diff --git a/src/wx/audio_mapping_view.h b/src/wx/audio_mapping_view.h index fd8a16d5c..aab6f64a6 100644 --- a/src/wx/audio_mapping_view.h +++ b/src/wx/audio_mapping_view.h @@ -45,7 +45,7 @@ class AudioMappingView : public wxPanel { public: - explicit AudioMappingView (wxWindow *); + explicit AudioMappingView (wxWindow *, wxString left_label, wxString from, wxString top_label, wxString to); void set (AudioMapping); void set_input_channels (std::vector const & names); @@ -107,6 +107,11 @@ private: int _menu_input; int _menu_output; + wxString _left_label; + wxString _from; + wxString _top_label; + wxString _to; + std::vector _input_channels; std::vector _output_channels; std::vector _input_groups; diff --git a/src/wx/audio_panel.cc b/src/wx/audio_panel.cc index 0cb062efd..183a3d4b9 100644 --- a/src/wx/audio_panel.cc +++ b/src/wx/audio_panel.cc @@ -88,7 +88,7 @@ AudioPanel::AudioPanel (ContentPanel* p) /// TRANSLATORS: this is an abbreviation for milliseconds, the unit of time _delay_ms_label = create_label (this, _("ms"), false); - _mapping = new AudioMappingView (this); + _mapping = new AudioMappingView (this, _("Content"), _("content"), _("DCP"), _("DCP")); _sizer->Add (_mapping, 1, wxEXPAND | wxALL, 6); _description = new StaticText (this, wxT(" \n"), wxDefaultPosition, wxDefaultSize); diff --git a/src/wx/config_dialog.cc b/src/wx/config_dialog.cc index 14948afe8..7f96e2c35 100644 --- a/src/wx/config_dialog.cc +++ b/src/wx/config_dialog.cc @@ -23,6 +23,8 @@ #include "check_box.h" #include "nag_dialog.h" #include "dcpomatic_button.h" +#include "audio_mapping_view.h" +#include #include using std::string; @@ -873,6 +875,16 @@ SoundPage::setup () table->Add (s, wxGBPosition(r, 1)); ++r; + add_label_to_sizer (table, _panel, _("Mapping"), true, wxGBPosition(r, 0)); + _map = new AudioMappingView (_panel, _("DCP"), _("DCP"), _("Output"), _("output")); + _map->SetSize (-1, 600); + table->Add (_map, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND); + ++r; + + _reset_to_default = new Button (_panel, _("Reset to default")); + table->Add (_reset_to_default, wxGBPosition(r, 1)); + ++r; + wxFont font = _sound_output_details->GetFont(); font.SetStyle (wxFONTSTYLE_ITALIC); font.SetPointSize (font.GetPointSize() - 1); @@ -886,8 +898,22 @@ SoundPage::setup () } } - _sound->Bind (wxEVT_CHECKBOX, bind (&SoundPage::sound_changed, this)); - _sound_output->Bind (wxEVT_CHOICE, bind (&SoundPage::sound_output_changed, this)); + _sound->Bind (wxEVT_CHECKBOX, bind(&SoundPage::sound_changed, this)); + _sound_output->Bind (wxEVT_CHOICE, bind(&SoundPage::sound_output_changed, this)); + _map->Changed.connect (bind(&SoundPage::map_changed, this, _1)); + _reset_to_default->Bind (wxEVT_BUTTON, bind(&SoundPage::reset_to_default, this)); +} + +void +SoundPage::reset_to_default () +{ + Config::instance()->set_audio_mapping_to_default (); +} + +void +SoundPage::map_changed (AudioMapping m) +{ + Config::instance()->set_audio_mapping (m); } void @@ -969,6 +995,20 @@ SoundPage::config_changed () wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()]) ); + _map->set (Config::instance()->audio_mapping(channels)); + + vector input; + for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) { + input.push_back (short_audio_channel_name(i)); + } + _map->set_input_channels (input); + + vector output; + for (int i = 0; i < channels; ++i) { + output.push_back (dcp::raw_convert(i)); + } + _map->set_output_channels (output); + setup_sensitivity (); } diff --git a/src/wx/config_dialog.h b/src/wx/config_dialog.h index 1a9d97a43..565ecf1c4 100644 --- a/src/wx/config_dialog.h +++ b/src/wx/config_dialog.h @@ -44,6 +44,8 @@ #include #include +class AudioMappingView; + class Page { public: @@ -200,12 +202,16 @@ private: void config_changed (); boost::optional get_sound_output (); void sound_changed (); - void sound_output_changed (); + void sound_output_changed (); void setup_sensitivity (); + void map_changed (AudioMapping m); + void reset_to_default (); wxCheckBox* _sound; wxChoice* _sound_output; wxStaticText* _sound_output_details; + AudioMappingView* _map; + Button* _reset_to_default; }; #endif diff --git a/src/wx/film_viewer.cc b/src/wx/film_viewer.cc index 893e1bf0f..509cc9426 100644 --- a/src/wx/film_viewer.cc +++ b/src/wx/film_viewer.cc @@ -209,40 +209,17 @@ FilmViewer::recreate_butler () return; } - AudioMapping map = AudioMapping (_film->audio_channels(), _audio_channels); - - if (_audio_channels != 2 || _film->audio_channels() < 3) { - for (int i = 0; i < min (_film->audio_channels(), _audio_channels); ++i) { - map.set (i, i, 1); - } - } else { - /* Special case: stereo output, at least 3 channel input. - Map so that Lt = L(-3dB) + Ls(-3dB) + C(-6dB) + Lfe(-10dB) - Rt = R(-3dB) + Rs(-3dB) + C(-6dB) + Lfe(-10dB) - */ - if (_film->audio_channels() > 0) { - map.set (dcp::LEFT, 0, 1 / sqrt(2)); // L -> Lt - } - if (_film->audio_channels() > 1) { - map.set (dcp::RIGHT, 1, 1 / sqrt(2)); // R -> Rt - } - if (_film->audio_channels() > 2) { - map.set (dcp::CENTRE, 0, 1 / 2.0); // C -> Lt - map.set (dcp::CENTRE, 1, 1 / 2.0); // C -> Rt - } - if (_film->audio_channels() > 3) { - map.set (dcp::LFE, 0, 1 / sqrt(10)); // Lfe -> Lt - map.set (dcp::LFE, 1, 1 / sqrt(10)); // Lfe -> Rt - } - if (_film->audio_channels() > 4) { - map.set (dcp::LS, 0, 1 / sqrt(2)); // Ls -> Lt - } - if (_film->audio_channels() > 5) { - map.set (dcp::RS, 1, 1 / sqrt(2)); // Rs -> Rt - } - } + _butler.reset( + new Butler( + _player, + Config::instance()->audio_mapping(_audio_channels), + _audio_channels, + bind(&PlayerVideo::force, _1, AV_PIX_FMT_RGB24), + false, + true + ) + ); - _butler.reset (new Butler(_player, map, _audio_channels, bind(&PlayerVideo::force, _1, AV_PIX_FMT_RGB24), false, true)); if (!Config::instance()->sound() && !_audio.isStreamOpen()) { _butler->disable_audio (); } @@ -626,6 +603,11 @@ FilmViewer::config_changed (Config::Property p) } #endif + if (p == Config::AUDIO_MAPPING) { + recreate_butler (); + return; + } + if (p != Config::SOUND && p != Config::SOUND_OUTPUT) { return; } -- 2.30.2