]> git.sesse.net Git - nageru/blobdiff - nageru/theme.cpp
Nomenclature cleanups around channels, signals and cards.
[nageru] / nageru / theme.cpp
index f3e1aa1c64bb53cdc913e7f5fdb6d326362ff860..65d82dae0bfa0c14d195e74f931496caf2f8514e 100644 (file)
@@ -29,6 +29,7 @@
 #include <new>
 #include <utility>
 
+#include "audio_mixer.h"
 #include "defs.h"
 #ifdef HAVE_CEF
 #include "cef_capture.h"
@@ -38,6 +39,7 @@
 #include "image_input.h"
 #include "input_state.h"
 #include "lua_utils.h"
+#include "mainwindow.h"
 #include "pbo_frame_allocator.h"
 #include "scene.h"
 
@@ -52,6 +54,9 @@ using namespace movit;
 
 extern Mixer *global_mixer;
 
+constexpr unsigned Theme::MenuEntry::CHECKABLE;
+constexpr unsigned Theme::MenuEntry::CHECKED;
+
 Theme *get_theme_updata(lua_State* L)
 {
        luaL_checktype(L, lua_upvalueindex(1), LUA_TLIGHTUSERDATA);
@@ -622,8 +627,8 @@ int InputStateInfo_get_width(lua_State* L)
        InputStateInfo *input_state_info = get_input_state_info(L, 1);
 
        Theme *theme = get_theme_updata(L);
-       int signal_num = theme->map_signal(luaL_checknumber(L, 2));
-       lua_pushnumber(L, input_state_info->last_width[signal_num]);
+       int card_idx = theme->map_signal_to_card(luaL_checknumber(L, 2));
+       lua_pushnumber(L, input_state_info->last_width[card_idx]);
        return 1;
 }
 
@@ -632,8 +637,8 @@ int InputStateInfo_get_height(lua_State* L)
        assert(lua_gettop(L) == 2);
        InputStateInfo *input_state_info = get_input_state_info(L, 1);
        Theme *theme = get_theme_updata(L);
-       int signal_num = theme->map_signal(luaL_checknumber(L, 2));
-       lua_pushnumber(L, input_state_info->last_height[signal_num]);
+       int card_idx = theme->map_signal_to_card(luaL_checknumber(L, 2));
+       lua_pushnumber(L, input_state_info->last_height[card_idx]);
        return 1;
 }
 
@@ -642,9 +647,9 @@ int InputStateInfo_get_frame_height(lua_State* L)
        assert(lua_gettop(L) == 2);
        InputStateInfo *input_state_info = get_input_state_info(L, 1);
        Theme *theme = get_theme_updata(L);
-       int signal_num = theme->map_signal(luaL_checknumber(L, 2));
-       unsigned height = input_state_info->last_height[signal_num];
-       if (input_state_info->last_interlaced[signal_num]) {
+       int card_idx = theme->map_signal_to_card(luaL_checknumber(L, 2));
+       unsigned height = input_state_info->last_height[card_idx];
+       if (input_state_info->last_interlaced[card_idx]) {
                height *= 2;
        }
        lua_pushnumber(L, height);
@@ -656,8 +661,8 @@ int InputStateInfo_get_interlaced(lua_State* L)
        assert(lua_gettop(L) == 2);
        InputStateInfo *input_state_info = get_input_state_info(L, 1);
        Theme *theme = get_theme_updata(L);
-       int signal_num = theme->map_signal(luaL_checknumber(L, 2));
-       lua_pushboolean(L, input_state_info->last_interlaced[signal_num]);
+       int card_idx = theme->map_signal_to_card(luaL_checknumber(L, 2));
+       lua_pushboolean(L, input_state_info->last_interlaced[card_idx]);
        return 1;
 }
 
@@ -666,8 +671,8 @@ int InputStateInfo_get_has_signal(lua_State* L)
        assert(lua_gettop(L) == 2);
        InputStateInfo *input_state_info = get_input_state_info(L, 1);
        Theme *theme = get_theme_updata(L);
-       int signal_num = theme->map_signal(luaL_checknumber(L, 2));
-       lua_pushboolean(L, input_state_info->last_has_signal[signal_num]);
+       int card_idx = theme->map_signal_to_card(luaL_checknumber(L, 2));
+       lua_pushboolean(L, input_state_info->last_has_signal[card_idx]);
        return 1;
 }
 
@@ -676,8 +681,8 @@ int InputStateInfo_get_is_connected(lua_State* L)
        assert(lua_gettop(L) == 2);
        InputStateInfo *input_state_info = get_input_state_info(L, 1);
        Theme *theme = get_theme_updata(L);
-       int signal_num = theme->map_signal(luaL_checknumber(L, 2));
-       lua_pushboolean(L, input_state_info->last_is_connected[signal_num]);
+       int card_idx = theme->map_signal_to_card(luaL_checknumber(L, 2));
+       lua_pushboolean(L, input_state_info->last_is_connected[card_idx]);
        return 1;
 }
 
@@ -686,8 +691,8 @@ int InputStateInfo_get_frame_rate_nom(lua_State* L)
        assert(lua_gettop(L) == 2);
        InputStateInfo *input_state_info = get_input_state_info(L, 1);
        Theme *theme = get_theme_updata(L);
-       int signal_num = theme->map_signal(luaL_checknumber(L, 2));
-       lua_pushnumber(L, input_state_info->last_frame_rate_nom[signal_num]);
+       int card_idx = theme->map_signal_to_card(luaL_checknumber(L, 2));
+       lua_pushnumber(L, input_state_info->last_frame_rate_nom[card_idx]);
        return 1;
 }
 
@@ -696,8 +701,8 @@ int InputStateInfo_get_frame_rate_den(lua_State* L)
        assert(lua_gettop(L) == 2);
        InputStateInfo *input_state_info = get_input_state_info(L, 1);
        Theme *theme = get_theme_updata(L);
-       int signal_num = theme->map_signal(luaL_checknumber(L, 2));
-       lua_pushnumber(L, input_state_info->last_frame_rate_den[signal_num]);
+       int card_idx = theme->map_signal_to_card(luaL_checknumber(L, 2));
+       lua_pushnumber(L, input_state_info->last_frame_rate_den[card_idx]);
        return 1;
 }
 
@@ -706,11 +711,11 @@ int InputStateInfo_get_last_subtitle(lua_State* L)
        assert(lua_gettop(L) == 2);
        InputStateInfo *input_state_info = get_input_state_info(L, 1);
        Theme *theme = get_theme_updata(L);
-       int signal_num = theme->map_signal(luaL_checknumber(L, 2));
-       if (!input_state_info->has_last_subtitle[signal_num]) {
+       int card_idx = theme->map_signal_to_card(luaL_checknumber(L, 2));
+       if (!input_state_info->has_last_subtitle[card_idx]) {
                lua_pushnil(L);
        } else {
-               lua_pushstring(L, input_state_info->last_subtitle[signal_num].c_str());
+               lua_pushstring(L, input_state_info->last_subtitle[card_idx].c_str());
        }
        return 1;
 }
@@ -755,22 +760,22 @@ int InputStateInfo_get_human_readable_resolution(lua_State* L)
        assert(lua_gettop(L) == 2);
        InputStateInfo *input_state_info = get_input_state_info(L, 1);
        Theme *theme = get_theme_updata(L);
-       int signal_num = theme->map_signal(luaL_checknumber(L, 2));
+       int card_idx = theme->map_signal_to_card(luaL_checknumber(L, 2));
 
        string str;
-       if (!input_state_info->last_is_connected[signal_num]) {
+       if (!input_state_info->last_is_connected[card_idx]) {
                str = "disconnected";
-       } else if (input_state_info->last_height[signal_num] <= 0) {
+       } else if (input_state_info->last_height[card_idx] <= 0) {
                str = "no signal";
-       } else if (!input_state_info->last_has_signal[signal_num]) {
-               if (input_state_info->last_height[signal_num] == 525) {
+       } else if (!input_state_info->last_has_signal[card_idx]) {
+               if (input_state_info->last_height[card_idx] == 525) {
                        // Special mode for the USB3 cards.
                        str = "no signal";
                } else {
-                       str = get_human_readable_resolution(input_state_info, signal_num) + ", no signal";
+                       str = get_human_readable_resolution(input_state_info, card_idx) + ", no signal";
                }
        } else {
-               str = get_human_readable_resolution(input_state_info, signal_num);
+               str = get_human_readable_resolution(input_state_info, card_idx);
        }
 
        lua_pushstring(L, str.c_str());
@@ -1118,14 +1123,14 @@ bool LiveInputWrapper::connect_signal(int signal_num)
                return true;
        }
 
-       signal_num = theme->map_signal(signal_num);
-       connect_signal_raw(signal_num, *theme->input_state);
+       int card_idx = theme->map_signal_to_card(signal_num);
+       connect_signal_raw(card_idx, *theme->input_state);
        return true;
 }
 
-void LiveInputWrapper::connect_signal_raw(int signal_num, const InputState &input_state)
+void LiveInputWrapper::connect_signal_raw(int card_idx, const InputState &input_state)
 {
-       BufferedFrame first_frame = input_state.buffered_frames[signal_num][0];
+       BufferedFrame first_frame = input_state.buffered_frames[card_idx][0];
        if (first_frame.frame == nullptr) {
                // No data yet.
                return;
@@ -1140,10 +1145,10 @@ void LiveInputWrapper::connect_signal_raw(int signal_num, const InputState &inpu
                }
        }
 
-       movit::YCbCrLumaCoefficients ycbcr_coefficients = input_state.ycbcr_coefficients[signal_num];
-       bool full_range = input_state.full_range[signal_num];
+       movit::YCbCrLumaCoefficients ycbcr_coefficients = input_state.ycbcr_coefficients[card_idx];
+       bool full_range = input_state.full_range[card_idx];
 
-       if (input_state.ycbcr_coefficients_auto[signal_num]) {
+       if (input_state.ycbcr_coefficients_auto[card_idx]) {
                full_range = false;
 
                // The Blackmagic driver docs claim that the device outputs Y'CbCr
@@ -1167,7 +1172,7 @@ void LiveInputWrapper::connect_signal_raw(int signal_num, const InputState &inpu
 
        BufferedFrame last_good_frame = first_frame;
        for (unsigned i = 0; i < max(ycbcr_inputs.size(), rgba_inputs.size()); ++i) {
-               BufferedFrame frame = input_state.buffered_frames[signal_num][i];
+               BufferedFrame frame = input_state.buffered_frames[card_idx][i];
                if (frame.frame == nullptr) {
                        // Not enough data; reuse last frame (well, field).
                        // This is suboptimal, but we have nothing better.
@@ -1219,7 +1224,7 @@ void LiveInputWrapper::connect_signal_raw(int signal_num, const InputState &inpu
        }
 
        if (deinterlace) {
-               BufferedFrame frame = input_state.buffered_frames[signal_num][0];
+               BufferedFrame frame = input_state.buffered_frames[card_idx][0];
                CHECK(deinterlace_effect->set_int("current_field_position", frame.field_number));
        }
 }
@@ -1295,6 +1300,164 @@ int Nageru_set_supports_wb(lua_State *L)
        return 0;
 }
 
+// NOTE: There's a race condition in all of the audio functions; if the mapping
+// is changed by the user underway, you might not be manipulating the bus you
+// expect. (You should not get crashes, though.) There's not all that much we
+// can do about it, short of locking the entire mixer while anything from the
+// theme runs.
+
+int Nageru_get_num_audio_buses(lua_State *L)
+{
+       if (global_audio_mixer == nullptr) {
+               // The audio mixer isn't set up until we know how many FFmpeg inputs we have.
+               luaL_error(L, "Audio functions can not be called before the theme is done initializing.");
+       }
+       lua_pushinteger(L, global_audio_mixer->num_buses());
+       return 1;
+}
+
+int Nageru_get_audio_bus_name(lua_State *L)
+{
+       if (global_audio_mixer == nullptr) {
+               // The audio mixer isn't set up until we know how many FFmpeg inputs we have.
+               luaL_error(L, "Audio functions can not be called before the theme is done initializing.");
+       }
+       int bus_index = luaL_checknumber(L, 1);
+       InputMapping input_mapping = global_audio_mixer->get_input_mapping();
+       if (bus_index < 0 || size_t(bus_index) >= input_mapping.buses.size()) {
+               // Doesn't fix the race, but fixes other out-of-bounds.
+               print_warning(L, "Theme called get_audio_bus_name() on nonexistent bus %d; returning nil.\n", bus_index);
+               lua_pushnil(L);
+       } else {
+               lua_pushstring(L, input_mapping.buses[bus_index].name.c_str());
+       }
+       return 1;
+}
+
+int Nageru_get_audio_bus_fader_level_db(lua_State *L)
+{
+       if (global_audio_mixer == nullptr) {
+               // The audio mixer isn't set up until we know how many FFmpeg inputs we have.
+               luaL_error(L, "Audio functions can not be called before the theme is done initializing.");
+       }
+
+       int bus_index = luaL_checknumber(L, 1);
+       if (bus_index < 0 || size_t(bus_index) >= global_audio_mixer->num_buses()) {
+               // Doesn't fix the race, but fixes other out-of-bounds.
+               print_warning(L, "Theme called get_audio_bus_fader_level_db() on nonexistent bus %d; returning 0.0.\n", bus_index);
+               lua_pushnumber(L, 0.0);
+       } else {
+               lua_pushnumber(L, global_audio_mixer->get_fader_volume(bus_index));
+       }
+       return 1;
+}
+
+int Nageru_set_audio_bus_fader_level_db(lua_State *L)
+{
+       if (global_audio_mixer == nullptr || global_mainwindow == nullptr) {
+               // The audio mixer isn't set up until we know how many FFmpeg inputs we have.
+               luaL_error(L, "Audio functions can not be called before the theme is done initializing.");
+       }
+
+       int bus_index = luaL_checknumber(L, 1);
+       if (bus_index < 0 || size_t(bus_index) >= global_audio_mixer->num_buses()) {
+               // Doesn't fix the race, but fixes other out-of-bounds.
+               print_warning(L, "Theme called set_audio_bus_fader_level_db() on nonexistent bus %d; ignoring.\n", bus_index);
+               return 0;
+       }
+       double level_db = luaL_checknumber(L, 2);
+
+       // Go through the UI, so that it gets updated.
+       global_mainwindow->set_fader_absolute(bus_index, level_db);
+       return 0;
+}
+
+int Nageru_get_audio_bus_mute(lua_State *L)
+{
+       if (global_audio_mixer == nullptr) {
+               // The audio mixer isn't set up until we know how many FFmpeg inputs we have.
+               luaL_error(L, "Audio functions can not be called before the theme is done initializing.");
+       }
+
+       int bus_index = luaL_checknumber(L, 1);
+       if (bus_index < 0 || size_t(bus_index) >= global_audio_mixer->num_buses()) {
+               // Doesn't fix the race, but fixes other out-of-bounds.
+               print_warning(L, "Theme called get_audio_bus_mute() on nonexistent bus %d; returning false.\n", bus_index);
+               lua_pushboolean(L, false);
+       } else {
+               lua_pushboolean(L, global_audio_mixer->get_mute(bus_index));
+       }
+       return 1;
+}
+
+int Nageru_set_audio_bus_mute(lua_State *L)
+{
+       if (global_audio_mixer == nullptr || global_mainwindow == nullptr) {
+               // The audio mixer isn't set up until we know how many FFmpeg inputs we have.
+               luaL_error(L, "Audio functions can not be called before the theme is done initializing.");
+       }
+
+       int bus_index = luaL_checknumber(L, 1);
+       if (bus_index < 0 || size_t(bus_index) >= global_audio_mixer->num_buses()) {
+               // Doesn't fix the race, but fixes other out-of-bounds.
+               print_warning(L, "Theme called set_audio_bus_mute() on nonexistent bus %d; ignoring.\n", bus_index);
+               return 0;
+       }
+       bool mute = checkbool(L, 2);
+
+       // Go through the UI, so that it gets updated.
+       if (mute != global_audio_mixer->get_mute(bus_index)) {
+               global_mainwindow->toggle_mute(bus_index);
+       }
+       return 0;
+}
+
+int Nageru_get_audio_bus_eq_level_db(lua_State *L)
+{
+       if (global_audio_mixer == nullptr) {
+               // The audio mixer isn't set up until we know how many FFmpeg inputs we have.
+               luaL_error(L, "Audio functions can not be called before the theme is done initializing.");
+       }
+
+       int bus_index = luaL_checknumber(L, 1);
+       int band = luaL_checknumber(L, 2);
+       if (bus_index < 0 || size_t(bus_index) >= global_audio_mixer->num_buses()) {
+               // Doesn't fix the race, but fixes other out-of-bounds.
+               print_warning(L, "Theme called get_audio_bus_eq_level_db() on nonexistent bus %d; returning 0.0.\n", bus_index);
+               lua_pushnumber(L, 0.0);
+       } else if (band != EQ_BAND_BASS && band != EQ_BAND_MID && band != EQ_BAND_TREBLE) {
+               print_warning(L, "Theme called get_audio_bus_eq_level_db() on nonexistent band; returning 0.0.\n", bus_index);
+               lua_pushnumber(L, 0.0);
+       } else {
+               lua_pushnumber(L, global_audio_mixer->get_eq(bus_index, EQBand(band)));
+       }
+       return 1;
+}
+
+int Nageru_set_audio_bus_eq_level_db(lua_State *L)
+{
+       if (global_audio_mixer == nullptr || global_mainwindow == nullptr) {
+               // The audio mixer isn't set up until we know how many FFmpeg inputs we have.
+               luaL_error(L, "Audio functions can not be called before the theme is done initializing.");
+       }
+
+       int bus_index = luaL_checknumber(L, 1);
+       int band = luaL_checknumber(L, 2);
+       if (bus_index < 0 || size_t(bus_index) >= global_audio_mixer->num_buses()) {
+               // Doesn't fix the race, but fixes other out-of-bounds.
+               print_warning(L, "Theme called set_audio_bus_eq_level_db() on nonexistent bus %d; ignoring.\n", bus_index);
+               return 0;
+       } else if (band != EQ_BAND_BASS && band != EQ_BAND_MID && band != EQ_BAND_TREBLE) {
+               print_warning(L, "Theme called set_audio_bus_eq_level_db() on nonexistent band; returning 0.0.\n", bus_index);
+               return 0;
+       }
+       double level_db = luaL_checknumber(L, 3);
+
+       // Go through the UI, so that it gets updated.
+       global_mainwindow->set_eq_absolute(bus_index, EQBand(band), level_db);
+       return 0;
+}
+
 Theme::Theme(const string &filename, const vector<string> &search_dirs, ResourcePool *resource_pool, unsigned num_cards)
        : resource_pool(resource_pool), num_cards(num_cards), signal_to_card_mapping(global_flags.default_stream_mapping)
 {
@@ -1421,6 +1584,9 @@ void Theme::register_globals()
                { "VIDEO_FORMAT_YCBCR", bmusb::PixelFormat_8BitYCbCrPlanar },
                { "CHECKABLE", MenuEntry::CHECKABLE },
                { "CHECKED", MenuEntry::CHECKED },
+               { "EQ_BAND_BASS", EQ_BAND_BASS },
+               { "EQ_BAND_MID", EQ_BAND_MID },
+               { "EQ_BAND_TREBLE", EQ_BAND_TREBLE },
        };
        const vector<pair<string, string>> str_constants = {
                { "THEME_PATH", theme_path },
@@ -1440,11 +1606,23 @@ void Theme::register_globals()
        }
 
        const luaL_Reg Nageru_funcs[] = {
+               // Channel information.
                { "set_channel_name", Nageru_set_channel_name },
                { "set_num_channels", Nageru_set_num_channels },
                { "set_channel_signal", Nageru_set_channel_signal },
                { "set_supports_wb", Nageru_set_supports_wb },
-               { NULL, NULL }
+
+               // Audio.
+               { "get_num_audio_buses", Nageru_get_num_audio_buses },
+               { "get_audio_bus_name", Nageru_get_audio_bus_name },
+               { "get_audio_bus_fader_level_db", Nageru_get_audio_bus_fader_level_db },
+               { "set_audio_bus_fader_level_db", Nageru_set_audio_bus_fader_level_db },
+               { "get_audio_bus_eq_level_db", Nageru_get_audio_bus_eq_level_db },
+               { "set_audio_bus_eq_level_db", Nageru_set_audio_bus_eq_level_db },
+               { "get_audio_bus_mute", Nageru_get_audio_bus_mute },
+               { "set_audio_bus_mute", Nageru_set_audio_bus_mute },
+
+               { nullptr, nullptr }
        };
        lua_pushlightuserdata(L, this);
        luaL_setfuncs(L, Nageru_funcs, 1);        // for (name,f in funcs) { mt[name] = f, with upvalue {theme} }
@@ -1610,7 +1788,7 @@ string Theme::get_channel_name(unsigned channel)
        return retstr;
 }
 
-int Theme::get_channel_signal(unsigned channel)
+int Theme::map_channel_to_signal(unsigned channel)
 {
        lock_guard<mutex> lock(m);
        lua_getglobal(L, "channel_signal");
@@ -1686,9 +1864,11 @@ bool Theme::get_supports_set_wb(unsigned channel)
 
 void Theme::set_wb(unsigned channel, float r, float g, float b)
 {
+       int signal = map_channel_to_signal(channel);
+
        lock_guard<mutex> lock(m);
-       if (channel_signals.count(channel)) {
-               white_balance_for_signal[channel_signals[channel]] = RGBTriplet{ r, g, b };
+       if (signal != -1) {
+               white_balance_for_signal[signal] = RGBTriplet{ r, g, b };
        }
 
        call_lua_wb_callback(channel, r, g, b);
@@ -1757,7 +1937,7 @@ vector<string> Theme::get_transition_names(float t)
        return ret;
 }
 
-int Theme::map_signal(int signal_num)
+int Theme::map_signal_to_card(int signal_num)
 {
        // Negative numbers map to raw signals.
        if (signal_num < 0) {
@@ -1792,11 +1972,11 @@ int Theme::map_signal(int signal_num)
        return card_index;
 }
 
-void Theme::set_signal_mapping(int signal_num, int card_num)
+void Theme::set_signal_mapping(int signal_num, int card_idx)
 {
        lock_guard<mutex> lock(map_m);
-       assert(card_num < int(num_cards));
-       signal_to_card_mapping[signal_num] = card_num;
+       assert(card_idx < int(num_cards));
+       signal_to_card_mapping[signal_num] = card_idx;
 }
 
 void Theme::transition_clicked(int transition_num, float t)