Add an output audio matrix (#1482).
authorCarl Hetherington <cth@carlh.net>
Wed, 18 Dec 2019 00:09:25 +0000 (01:09 +0100)
committerCarl Hetherington <cth@carlh.net>
Thu, 19 Dec 2019 21:11:47 +0000 (22:11 +0100)
src/lib/config.cc
src/lib/config.h
src/wx/audio_mapping_view.cc
src/wx/audio_mapping_view.h
src/wx/audio_panel.cc
src/wx/config_dialog.cc
src/wx/config_dialog.h
src/wx/film_viewer.cc

index 581620f832846f8a2f8e7418478fb43666dbc5ab..7f32320dfb3f49e50fab88d2d20dcee39deb9e8b 100644 (file)
@@ -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);
+}
index ff7a0fe396f9af9ac7e91d914d692ab870ff8381..061d6ee7c59045b721529b9a5c9fdd40d94bc8fe 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2018 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2019 Carl Hetherington <cth@carlh.net>
 
     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 <dcp/name_format.h>
 #include <dcp/certificate_chain.h>
 #include <dcp/encrypted_kdm.h>
@@ -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<boost::filesystem::path> 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<boost::filesystem::path> _player_content_directory;
        boost::optional<boost::filesystem::path> _player_playlist_directory;
        boost::optional<boost::filesystem::path> _player_kdm_directory;
+       boost::optional<AudioMapping> _audio_mapping;
 #ifdef DCPOMATIC_VARIANT_SWAROOP
        boost::optional<boost::filesystem::path> _player_background_image;
        std::string _kdm_server_url;
index 98430da3bddf0cc0123d4bfd627b42113636cfd1..140f18d60341207cd03ad2bdc89a4999c32e4a56 100644 (file)
@@ -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
                                        );
index fd8a16d5c667d3645c1b02e4aaabe1d12104d136..aab6f64a61f7513f9dbf6d110b48711cd24014f9 100644 (file)
@@ -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<std::string> 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<std::string> _input_channels;
        std::vector<std::string> _output_channels;
        std::vector<Group> _input_groups;
index 0cb062efd3f2c4cc127b093c29f8df3b12b233f3..183a3d4b9817ca8cfe89425e583eb69966f2cbcc 100644 (file)
@@ -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);
index 14948afe8d13e65fd5ee739a484fb991e68d5efb..7f96e2c351217620e86a3aa0fbb5d332ff28e3aa 100644 (file)
@@ -23,6 +23,8 @@
 #include "check_box.h"
 #include "nag_dialog.h"
 #include "dcpomatic_button.h"
+#include "audio_mapping_view.h"
+#include <dcp/raw_convert.h>
 #include <iostream>
 
 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<string> 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<string> output;
+       for (int i = 0; i < channels; ++i) {
+               output.push_back (dcp::raw_convert<string>(i));
+       }
+       _map->set_output_channels (output);
+
        setup_sensitivity ();
 }
 
index 1a9d97a436f0eee010a34ddac1941c596f108b62..565ecf1c4f738c026aa0901a1bee0256335b5885 100644 (file)
@@ -44,6 +44,8 @@
 #include <boost/foreach.hpp>
 #include <iostream>
 
+class AudioMappingView;
+
 class Page
 {
 public:
@@ -200,12 +202,16 @@ private:
        void config_changed ();
         boost::optional<std::string> 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
index 893e1bf0fe4569dec8ede2d80bf75acc213b7beb..509cc9426dd279ed14e960f6b12a8a4f5f2263dc 100644 (file)
@@ -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;
        }