diff --git a/assets/opensb/interface/opensb/voicechat/activity.png b/assets/opensb/interface/opensb/voicechat/activity.png new file mode 100644 index 0000000..20286e7 Binary files /dev/null and b/assets/opensb/interface/opensb/voicechat/activity.png differ diff --git a/assets/opensb/interface/opensb/voicechat/activityback.png b/assets/opensb/interface/opensb/voicechat/activityback.png new file mode 100644 index 0000000..8308c3d Binary files /dev/null and b/assets/opensb/interface/opensb/voicechat/activityback.png differ diff --git a/assets/opensb/interface/opensb/voicechat/bigbutton.png b/assets/opensb/interface/opensb/voicechat/bigbutton.png new file mode 100644 index 0000000..2908feb Binary files /dev/null and b/assets/opensb/interface/opensb/voicechat/bigbutton.png differ diff --git a/assets/opensb/interface/opensb/voicechat/bigbuttonback.png b/assets/opensb/interface/opensb/voicechat/bigbuttonback.png new file mode 100644 index 0000000..fe10770 Binary files /dev/null and b/assets/opensb/interface/opensb/voicechat/bigbuttonback.png differ diff --git a/assets/opensb/interface/opensb/voicechat/body.png b/assets/opensb/interface/opensb/voicechat/body.png new file mode 100644 index 0000000..37f73a8 Binary files /dev/null and b/assets/opensb/interface/opensb/voicechat/body.png differ diff --git a/assets/opensb/interface/opensb/voicechat/device.png b/assets/opensb/interface/opensb/voicechat/device.png new file mode 100644 index 0000000..b686b60 Binary files /dev/null and b/assets/opensb/interface/opensb/voicechat/device.png differ diff --git a/assets/opensb/interface/opensb/voicechat/deviceback.png b/assets/opensb/interface/opensb/voicechat/deviceback.png new file mode 100644 index 0000000..b07990b Binary files /dev/null and b/assets/opensb/interface/opensb/voicechat/deviceback.png differ diff --git a/assets/opensb/interface/opensb/voicechat/footer.png b/assets/opensb/interface/opensb/voicechat/footer.png new file mode 100644 index 0000000..39069c9 Binary files /dev/null and b/assets/opensb/interface/opensb/voicechat/footer.png differ diff --git a/assets/opensb/interface/opensb/voicechat/header.png b/assets/opensb/interface/opensb/voicechat/header.png new file mode 100644 index 0000000..a1be25d Binary files /dev/null and b/assets/opensb/interface/opensb/voicechat/header.png differ diff --git a/assets/opensb/interface/voicechat/indicator/back.png b/assets/opensb/interface/opensb/voicechat/indicator/back.png similarity index 100% rename from assets/opensb/interface/voicechat/indicator/back.png rename to assets/opensb/interface/opensb/voicechat/indicator/back.png diff --git a/assets/opensb/interface/voicechat/indicator/front.png b/assets/opensb/interface/opensb/voicechat/indicator/front.png similarity index 100% rename from assets/opensb/interface/voicechat/indicator/front.png rename to assets/opensb/interface/opensb/voicechat/indicator/front.png diff --git a/assets/opensb/interface/voicechat/indicator/front_muted.png b/assets/opensb/interface/opensb/voicechat/indicator/front_muted.png similarity index 100% rename from assets/opensb/interface/voicechat/indicator/front_muted.png rename to assets/opensb/interface/opensb/voicechat/indicator/front_muted.png diff --git a/assets/opensb/interface/opensb/voicechat/pushtotalk.png b/assets/opensb/interface/opensb/voicechat/pushtotalk.png new file mode 100644 index 0000000..6d38221 Binary files /dev/null and b/assets/opensb/interface/opensb/voicechat/pushtotalk.png differ diff --git a/assets/opensb/interface/opensb/voicechat/pushtotalkback.png b/assets/opensb/interface/opensb/voicechat/pushtotalkback.png new file mode 100644 index 0000000..3cc3a73 Binary files /dev/null and b/assets/opensb/interface/opensb/voicechat/pushtotalkback.png differ diff --git a/assets/opensb/interface/opensb/voicechat/voicechat.config b/assets/opensb/interface/opensb/voicechat/voicechat.config new file mode 100644 index 0000000..20e88d9 --- /dev/null +++ b/assets/opensb/interface/opensb/voicechat/voicechat.config @@ -0,0 +1,174 @@ +{ + "scripts" : ["/interface/opensb/voicechat/voicechat.lua"], + "scriptDelta" : 1, + "scriptWidgetCallbacks" : [ + "selectDevice", + "voiceToggle", + "switchVoiceMode" + ], + + "canvasClickCallbacks" : { + "inputVolume" : "inputVolume", + "voiceVolume" : "voiceVolume", + "threshold" : "threshold" + }, + + "gui" : { + "panefeature" : { + "type" : "panefeature", + "positionLocked" : false + }, + "background" : { + "type" : "background", + "fileHeader" : "/interface/opensb/voicechat/header.png", + "fileBody" : "/interface/opensb/voicechat/body.png", + "fileFooter" : "/interface/opensb/voicechat/footer.png" + }, + + "voiceVolumeLabel" : { + "type" : "label", + "value" : "THEIR VOLUME", + "position" : [26, 178], + "wrapWidth" : 48, + "lineSpacing" : 0.75, + "hAnchor" : "mid", + "vAnchor" : "mid" + }, + "voiceVolume" : { + "type" : "canvas", + "rect" : [50, 171, 247, 186], + "captureMouseEvents" : true, + "captureKeyboardEvents" : false + }, + + "inputVolumeLabel" : { + "type" : "label", + "value" : "YOUR VOLUME", + "position" : [26, 158], + "wrapWidth" : 48, + "lineSpacing" : 0.75, + "hAnchor" : "mid", + "vAnchor" : "mid" + }, + "inputVolume" : { + "type" : "canvas", + "rect" : [50, 151, 247, 166], + "captureMouseEvents" : true, + "captureKeyboardEvents" : false + }, + + "enableVoiceToggleBack" : { + "type" : "image", + "file" : "/interface/opensb/voicechat/bigbuttonback.png?multiply=0f0", + "position" : [2, 189], + "zlevel" : -1 + }, + "enableVoiceToggle" : { + "type" : "button", + "pressedOffset" : [0, 0], + "position" : [2, 189], + "base" : "/interface/opensb/voicechat/bigbutton.png?replace;fff=fff0;000=0007", + "hover" : "/interface/opensb/voicechat/bigbutton.png?replace;fff=fff7;000=3337", + "press" : "/interface/opensb/voicechat/bigbutton.png?replace;fff=000;000=7777", + "callback" : "voiceToggle", + "fontSize" : 16, + "zlevel" : 1 + }, + + + "voiceModeLabel" : { + "type" : "label", + "value" : "VOICE MODE", + "position" : [26, 133], + "wrapWidth" : 32, + "lineSpacing" : 0.75, + "hAnchor" : "mid", + "vAnchor" : "mid" + }, + "pushToTalkBack" : { + "type" : "image", + "file" : "/interface/opensb/voicechat/pushtotalkback.png?multiply=0f0", + "position" : [50, 121], + "zlevel" : -1 + }, + "pushToTalk" : { + "type" : "button", + "pressedOffset" : [0, 0], + "position" : [50, 121], + "base" : "/interface/opensb/voicechat/pushtotalk.png?replace;fff=fff0;000=0007", + "hover" : "/interface/opensb/voicechat/pushtotalk.png?replace;fff=fff7;000=3337", + "press" : "/interface/opensb/voicechat/pushtotalk.png?replace;fff=000;000=7777", + "callback" : "switchVoiceMode", + "fontSize" : 16, + "zlevel" : 1 + }, + + "voiceActivityBack" : { + "type" : "image", + "file" : "/interface/opensb/voicechat/activityback.png?multiply=0f0", + "position" : [167, 121], + "zlevel" : -1 + }, + "voiceActivity" : { + "type" : "button", + "pressedOffset" : [0, 0], + "position" : [167, 121], + "base" : "/interface/opensb/voicechat/activity.png?replace;fff=fff0;000=0007", + "hover" : "/interface/opensb/voicechat/activity.png?replace;fff=fff7;000=3337", + "press" : "/interface/opensb/voicechat/activity.png?replace;fff=000;000=7777", + "callback" : "switchVoiceMode", + "fontSize" : 16, + "zlevel" : 1 + }, + + "thresholdLevel" : { + "type" : "label", + "value" : "THRESHOLD", + "position" : [26, 109], + "wrapWidth" : 48, + "lineSpacing" : 0.75, + "hAnchor" : "mid", + "vAnchor" : "mid" + }, + "threshold" : { + "type" : "canvas", + "rect" : [50, 102, 247, 117], + "captureMouseEvents" : true, + "captureKeyboardEvents" : false + }, + + "devices" : { + "type" : "scrollArea", + "rect" : [3, 16, 248, 98], + "children" : { + "list" : { + "type" : "list", + "schema" : { + "selectedBG" : "/interface/opensb/voicechat/deviceback.png?multiply=0f0", + "unselectedBG" : "/interface/opensb/voicechat/deviceback.png?multiply=222", + "spacing" : [0, 1], + "memberSize" : [234, 16], + "listTemplate" : { + "background" : { + "type" : "image", + "file" : "/interface/opensb/voicechat/deviceback.png?multiply=222", + "position" : [0, 0], + "zlevel" : -1 + }, + "button" : { + "type" : "button", + "callback" : "selectDevice", + "caption" : "Unnamed", + "base" : "/interface/opensb/voicechat/device.png?replace;fff=fff0;000=0007", + "hover" : "/interface/opensb/voicechat/device.png?replace;fff=fff7;000=3337", + "press" : "/interface/opensb/voicechat/device.png?replace;fff=000;000=7777", + "pressedOffset" : [0, 0], + "position" : [0, 0] + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/assets/opensb/interface/opensb/voicechat/voicechat.lua b/assets/opensb/interface/opensb/voicechat/voicechat.lua new file mode 100644 index 0000000..4e9f7e7 --- /dev/null +++ b/assets/opensb/interface/opensb/voicechat/voicechat.lua @@ -0,0 +1,227 @@ +--constants +local PATH = "/interface/opensb/voicechat/" +local DEVICE_LIST_WIDGET = "devices.list" +local DEFAULT_DEVICE_NAME = "Use System Default" +local NULL_DEVICE_NAME = "No Audio Device" +local COLD_COLOR = {25, 255, 255, 225} +local HOT_COLOR = {255, 96, 96, 225} +local MINIMUM_DB = -80 +local VOICE_MAX, INPUT_MAX = 1.75, 1.0 +local MID = 7.5 + +local fmt = string.format + +local debugging = false +local function log(...) + if not debugging then return end + sb.logInfo(...) +end + +local function mapToRange(x, min, max) + return math.min(1, math.max(0, (x - min)) / max) +end + +local function linear(a, b, c) + return a + (b - a) * c +end + +local settings = {} + +local function set(k, v) + settings[k] = v + local newSettings = jobject() + newSettings[k] = v + voice.mergeSettings(newSettings) + return v +end + +local devicesToWidgets = {} +local widgetsToDevices = {} +local nullWidget +local function addDeviceToList(deviceName) + local name = widget.addListItem(DEVICE_LIST_WIDGET) + widget.setText(fmt("%s.%s.button", DEVICE_LIST_WIDGET, name), deviceName) + widgetsToDevices[name] = deviceName + devicesToWidgets[deviceName] = name + log("Added audio device '%s' to list", deviceName) + return name +end + +function selectDevice() + local selected = widget.getListSelected(DEVICE_LIST_WIDGET) + if selected == nullWidget then + set("inputEnabled", false) + widget.setListSelected(DEVICE_LIST_WIDGET, nullWidget) + end + local deviceName = widgetsToDevices[selected] + if deviceName == DEFAULT_DEVICE_NAME then deviceName = nil end + + if settings.deviceName == deviceName then + local inputEnabled = set("inputEnabled", not settings.inputEnabled) + widget.setListSelected(DEVICE_LIST_WIDGET, inputEnabled and selected or nullWidget) + else + set("deviceName", deviceName) + set("inputEnabled", true) + end +end + +local function initCallbacks() + widget.registerMemberCallback(DEVICE_LIST_WIDGET, "selectDevice", selectDevice) +end + +local function updateVoiceButton() + local enabled = settings.enabled + widget.setText("enableVoiceToggle", enabled and "^#0f0;disable voice chat" or "^#f00;enable voice chat") + widget.setImage("enableVoiceToggleBack", PATH .. "bigbuttonback.png?multiply=" .. (enabled and "0f0" or "f00")) +end + +local function updateVoiceModeButtons() + local pushToTalk = settings.inputMode:lower() == "pushtotalk" + widget.setImage("pushToTalkBack", PATH .. "pushtotalkback.png?multiply=" .. (pushToTalk and "0f0" or "f00")) + widget.setImage("voiceActivityBack", PATH .. "activityback.png?multiply=" .. (pushToTalk and "f00" or "0f0")) + widget.setText("pushToTalk", pushToTalk and "^#0f0;PUSH TO TALK" or "^#f00;PUSH TO TALK") + widget.setText("voiceActivity", pushToTalk and "^#f00;ACTIVITY" or "^#0f0;ACTIVITY") +end + +local voiceCanvas, inputCanvas = nil, nil +local function updateVolumeCanvas(canvas, volume, multiplier) + canvas:clear() + local lineEnd = 1 + volume * 195 + local lineColor = {95, 110, 255, 225} + local multiplied = volume * multiplier + if multiplied > 1 then + local level = (multiplied - 1) / (multiplier - 1) + for i = 1, 4 do + lineColor[i] = linear(lineColor[i], HOT_COLOR[i], level) + end + else + for i = 1, 4 do + lineColor[i] = linear(lineColor[i], COLD_COLOR[i], 1 - multiplied) + end + end + + canvas:drawLine({1, MID}, {lineEnd, MID}, lineColor, 60) + canvas:drawLine({lineEnd - 0.5, MID}, {lineEnd + 0.5, MID}, {255, 255, 255, 200}, 60) + local str = volume == 0 and "^#f00,shadow;MUTED" or fmt("^shadow;%s%%", math.floor(volume * multiplier * 1000) * 0.1) + canvas:drawText(str, {position = {92.5, MID}, horizontalAnchor = "mid", verticalAnchor = "mid"}, 16, {255, 255, 255, 255}) +end + +local thresholdCanvas = nil +local function updateThresholdCanvas(canvas, dB) + canvas:clear() + local scale = pane.scale() + local lineEnd = 1 + (1 - (dB / MINIMUM_DB)) * 195 + local lineColor = {255, 255, 0, 127} + canvas:drawLine({1, 2}, {lineEnd, 2}, lineColor, scale) + canvas:drawLine({1, 13}, {lineEnd, 13}, lineColor, scale) + lineColor[4] = 64 + canvas:drawLine({lineEnd, 2}, {196, 2}, lineColor, scale) + canvas:drawLine({lineEnd, 13}, {196, 13}, lineColor, scale) + canvas:drawLine({lineEnd - 0.5, MID}, {lineEnd + 0.5, MID}, {255, 255, 255, 200}, 60) + + local loudness = 1 - (voice.speaker().smoothDecibels / MINIMUM_DB) + local loudnessEnd = math.min(1 + loudness * 195, 196) + if loudnessEnd > 0 then + lineColor[4] = 200 + canvas:drawLine({1, MID}, {loudnessEnd, MID}, lineColor, 4 * scale) + end + + local str = fmt("^shadow;%sdB", math.floor(dB * 10) * 0.1) + canvas:drawText(str, {position = {92.5, MID}, horizontalAnchor = "mid", verticalAnchor = "mid"}, 16, {255, 255, 255, 255}) +end + +function init() + settings = voice.getSettings() + voiceCanvas = widget.bindCanvas("voiceVolume") + inputCanvas = widget.bindCanvas("inputVolume") + thresholdCanvas = widget.bindCanvas("threshold") +end + +function displayed() + devicesToWidgets = {} + widgetsToDevices = {} + widget.clearListItems(DEVICE_LIST_WIDGET) + initCallbacks() + + addDeviceToList(DEFAULT_DEVICE_NAME) + + for i, v in pairs(voice.devices()) do + addDeviceToList(v) + end + + nullWidget = widget.addListItem(DEVICE_LIST_WIDGET) + local nullWidgetPath = fmt("%s.%s", DEVICE_LIST_WIDGET, nullWidget) + widget.setPosition(nullWidgetPath, {0, 10000}) + widget.setVisible(nullWidgetPath, false) + + local preferredDeviceWidget = devicesToWidgets[settings.deviceName or DEFAULT_DEVICE_NAME] + if preferredDeviceWidget and settings.inputEnabled then + widget.setListSelected(DEVICE_LIST_WIDGET, preferredDeviceWidget) + end + + updateVoiceButton() + updateVoiceModeButtons() + updateVolumeCanvas(voiceCanvas, settings.outputVolume / VOICE_MAX, VOICE_MAX) + updateVolumeCanvas(inputCanvas, settings.inputVolume / INPUT_MAX, INPUT_MAX) + updateThresholdCanvas(thresholdCanvas, settings.threshold) +end + +function update() + updateThresholdCanvas(thresholdCanvas, settings.threshold) +end + +local function sliderToValue(x) + return mapToRange(x, 5, 187) +end + +local function mouseInSlider(mouse) + return mouse[1] > 0 and mouse[1] < 197 + and mouse[2] > 0 and mouse[2] < 16 +end + +local function handleVolume(canvas, mouse, multiplier, setter) + if not mouseInSlider(mouse) then return end + + local volumePreMul = sliderToValue(mouse[1]) + local volume = volumePreMul * multiplier; + if math.abs(volume - 1) < 0.01 then + volumePreMul = 1 / multiplier + volume = 1 + end + + updateVolumeCanvas(canvas, volumePreMul, multiplier) + setter(volume) +end + +function voiceVolume(mouse, button) + if button ~= 0 then return end + handleVolume(voiceCanvas, mouse, VOICE_MAX, function(v) set("outputVolume", v) end) +end + +function inputVolume(mouse, button) + if button ~= 0 then return end + handleVolume(inputCanvas, mouse, INPUT_MAX, function(v) set("inputVolume", v) end) +end + +function threshold(mouse, button) + if button ~= 0 then return end + if not mouseInSlider(mouse) then return end + local dB = (1 - sliderToValue(mouse[1])) * MINIMUM_DB + set("threshold", dB) + + updateThresholdCanvas(thresholdCanvas, dB) +end + +function switchVoiceMode(mode) + log("switching voice mode to %s", tostring(mode)) + local success, err = pcall(function() + set("inputMode", mode) + updateVoiceModeButtons() + end) + if not success then log("%s", err) end +end + +function voiceToggle() + set("enabled", not settings.enabled) + updateVoiceButton() +end \ No newline at end of file diff --git a/assets/opensb/interface/optionsmenu/body_blank.png b/assets/opensb/interface/optionsmenu/body_blank.png new file mode 100644 index 0000000..7d5b0f1 Binary files /dev/null and b/assets/opensb/interface/optionsmenu/body_blank.png differ diff --git a/assets/opensb/interface/optionsmenu/duocontrolsbutton.png b/assets/opensb/interface/optionsmenu/duocontrolsbutton.png new file mode 100644 index 0000000..1d40af3 Binary files /dev/null and b/assets/opensb/interface/optionsmenu/duocontrolsbutton.png differ diff --git a/assets/opensb/interface/optionsmenu/duocontrolsbuttonhover.png b/assets/opensb/interface/optionsmenu/duocontrolsbuttonhover.png new file mode 100644 index 0000000..bdeb055 Binary files /dev/null and b/assets/opensb/interface/optionsmenu/duocontrolsbuttonhover.png differ diff --git a/assets/opensb/interface/optionsmenu/optionsmenu.config.patch b/assets/opensb/interface/optionsmenu/optionsmenu.config.patch index fe5def8..68b27f9 100644 --- a/assets/opensb/interface/optionsmenu/optionsmenu.config.patch +++ b/assets/opensb/interface/optionsmenu/optionsmenu.config.patch @@ -1,25 +1,47 @@ { "paneLayout" : { + "voiceLabel" : { + "type" : "label", + "position" : [119, 185], + "hAnchor" : "mid", + "value" : "VOICE" + }, + "showVoiceSettings" : { + "type" : "button", + "position" : [30, 169], + "caption" : "Settings", + "base" : "/interface/optionsmenu/duocontrolsbutton.png", + "hover" : "/interface/optionsmenu/duocontrolsbuttonhover.png" + }, + "showVoicePlayers" : { + "type" : "button", + "disabled" : true, + "position" : [133, 169], + "caption" : "^#a0a000,font=iosevka-semiboldoblique;TODO^#aa7;:^reset; Players", + "base" : "/interface/optionsmenu/duocontrolsbutton.png", + "hover" : "/interface/optionsmenu/duocontrolsbuttonhover.png" + }, "showKeybindings" : { "type" : "button", - "position" : [150, 95], + "position" : [153, 95], "caption" : "Game Binds", - "base" : "/interface/optionsmenu/controlsbutton.png", - "hover" : "/interface/optionsmenu/controlsbuttonhover.png" + "base" : "/interface/optionsmenu/tricontrolsbutton.png", + "hover" : "/interface/optionsmenu/tricontrolsbuttonhover.png" }, "showModBindings" : { "type" : "button", "position" : [87, 95], "caption" : "Mod Binds", - "base" : "/interface/optionsmenu/controlsbutton.png", - "hover" : "/interface/optionsmenu/controlsbuttonhover.png" + "base" : "/interface/optionsmenu/tricontrolsbutton.png", + "hover" : "/interface/optionsmenu/tricontrolsbuttonhover.png" }, "showGraphics" : { "type" : "button", - "position" : [24, 95], + "position" : [21, 95], "caption" : "Graphics", - "base" : "/interface/optionsmenu/controlsbutton.png", - "hover" : "/interface/optionsmenu/controlsbuttonhover.png" - } + "base" : "/interface/optionsmenu/tricontrolsbutton.png", + "hover" : "/interface/optionsmenu/tricontrolsbuttonhover.png" + }, + "sfxValueLabel" : { "position" : [192, 142] } // this is 2px too low in vanilla lol } } \ No newline at end of file diff --git a/assets/opensb/interface/optionsmenu/controlsbutton.png b/assets/opensb/interface/optionsmenu/tricontrolsbutton.png similarity index 100% rename from assets/opensb/interface/optionsmenu/controlsbutton.png rename to assets/opensb/interface/optionsmenu/tricontrolsbutton.png diff --git a/assets/opensb/interface/optionsmenu/controlsbuttonhover.png b/assets/opensb/interface/optionsmenu/tricontrolsbuttonhover.png similarity index 100% rename from assets/opensb/interface/optionsmenu/controlsbuttonhover.png rename to assets/opensb/interface/optionsmenu/tricontrolsbuttonhover.png diff --git a/assets/opensb/scripts/opensb/universeclient/voicemanager.lua b/assets/opensb/scripts/opensb/universeclient/voicemanager.lua index b7468ce..2f19f6a 100644 --- a/assets/opensb/scripts/opensb/universeclient/voicemanager.lua +++ b/assets/opensb/scripts/opensb/universeclient/voicemanager.lua @@ -7,7 +7,7 @@ local module = {} modules.voice_manager = module --constants -local INDICATOR_PATH = "/interface/voicechat/indicator/" +local INDICATOR_PATH = "/interface/opensb/voicechat/indicator/" local BACK_INDICATOR_IMAGE = INDICATOR_PATH .. "back.png" local FRONT_INDICATOR_IMAGE = INDICATOR_PATH .. "front.png" local FRONT_MUTED_INDICATOR_IMAGE = INDICATOR_PATH .. "front_muted.png" diff --git a/source/client/StarClientApplication.cpp b/source/client/StarClientApplication.cpp index d8d79fd..b264f15 100644 --- a/source/client/StarClientApplication.cpp +++ b/source/client/StarClientApplication.cpp @@ -376,6 +376,13 @@ void ClientApplication::update() { else if (m_state > MainAppState::Title) updateRunning(); + // swallow leftover encoded data incase we aren't in-game yet to allow mic read to continue. + // TODO: directly disable encoding at menu so we don't have to do this + { + DataStreamBuffer ext; + m_voice->send(ext); + } + m_guiContext->cleanup(); m_edgeKeyEvents.clear(); m_input->reset(); @@ -499,7 +506,7 @@ void ClientApplication::changeState(MainAppState newState) { m_statistics = make_shared(m_root->toStoragePath("player"), appController()->statisticsService()); m_universeClient = make_shared(m_playerStorage, m_statistics); m_universeClient->setLuaCallbacks("input", LuaBindings::makeInputCallbacks()); - m_universeClient->setLuaCallbacks("voice", LuaBindings::makeVoiceCallbacks(m_voice.get())); + m_universeClient->setLuaCallbacks("voice", LuaBindings::makeVoiceCallbacks()); m_mainMixer->setUniverseClient(m_universeClient); m_titleScreen = make_shared(m_playerStorage, m_mainMixer->mixer()); diff --git a/source/frontend/CMakeLists.txt b/source/frontend/CMakeLists.txt index 4c6b9c8..b002155 100644 --- a/source/frontend/CMakeLists.txt +++ b/source/frontend/CMakeLists.txt @@ -58,6 +58,7 @@ SET (star_frontend_HEADERS StarWireInterface.hpp StarVoice.hpp StarVoiceLuaBindings.hpp + StarVoiceSettingsMenu.hpp ) SET (star_frontend_SOURCES @@ -108,6 +109,7 @@ SET (star_frontend_SOURCES StarWireInterface.cpp StarVoice.cpp StarVoiceLuaBindings.cpp + StarVoiceSettingsMenu.cpp ) ADD_LIBRARY (star_frontend OBJECT ${star_frontend_SOURCES} ${star_frontend_HEADERS}) diff --git a/source/frontend/StarMainMixer.cpp b/source/frontend/StarMainMixer.cpp index 6d68ea2..d4a2e73 100644 --- a/source/frontend/StarMainMixer.cpp +++ b/source/frontend/StarMainMixer.cpp @@ -121,6 +121,9 @@ void MainMixer::update(bool muteSfx, bool muteMusic) { if (m_mixer->hasEffect("echo")) m_mixer->removeEffect("echo", 0); + if (Voice* voice = Voice::singletonPtr()) + voice->update(); + m_mixer->update(); } } diff --git a/source/frontend/StarOptionsMenu.cpp b/source/frontend/StarOptionsMenu.cpp index cef2e7d..caa1b21 100644 --- a/source/frontend/StarOptionsMenu.cpp +++ b/source/frontend/StarOptionsMenu.cpp @@ -7,6 +7,7 @@ #include "StarLabelWidget.hpp" #include "StarAssets.hpp" #include "StarKeybindingsMenu.hpp" +#include "StarVoiceSettingsMenu.hpp" #include "StarBindingsMenu.hpp" #include "StarGraphicsMenu.hpp" @@ -49,8 +50,14 @@ OptionsMenu::OptionsMenu(PaneManager* manager) reader.registerCallback("showKeybindings", [=](Widget*) { displayControls(); }); + reader.registerCallback("showVoiceSettings", [=](Widget*) { + displayVoiceSettings(); + }); + reader.registerCallback("showVoicePlayers", [=](Widget*) { + + }); reader.registerCallback("showModBindings", [=](Widget*) { - displayModBindings(); + displayModBindings(); }); reader.registerCallback("showGraphics", [=](Widget*) { displayGraphics(); @@ -74,6 +81,7 @@ OptionsMenu::OptionsMenu(PaneManager* manager) m_sfxSlider->setRange(m_sfxRange, assets->json("/interface/optionsmenu/optionsmenu.config:sfxDelta").toInt()); m_musicSlider->setRange(m_musicRange, assets->json("/interface/optionsmenu/optionsmenu.config:musicDelta").toInt()); + m_voiceSettingsMenu = make_shared(assets->json(config.getString("voiceSettingsPanePath", "/interface/opensb/voicechat/voicechat.config"))); m_modBindingsMenu = make_shared(assets->json(config.getString("bindingsPanePath", "/interface/opensb/bindings/bindings.config"))); m_keybindingsMenu = make_shared(); m_graphicsMenu = make_shared(); @@ -169,6 +177,10 @@ void OptionsMenu::displayControls() { m_paneManager->displayPane(PaneLayer::ModalWindow, m_keybindingsMenu); } +void OptionsMenu::displayVoiceSettings() { + m_paneManager->displayPane(PaneLayer::ModalWindow, m_voiceSettingsMenu); +} + void OptionsMenu::displayModBindings() { m_paneManager->displayPane(PaneLayer::ModalWindow, m_modBindingsMenu); } diff --git a/source/frontend/StarOptionsMenu.hpp b/source/frontend/StarOptionsMenu.hpp index bd8c4ba..b984f02 100644 --- a/source/frontend/StarOptionsMenu.hpp +++ b/source/frontend/StarOptionsMenu.hpp @@ -10,6 +10,7 @@ namespace Star { STAR_CLASS(SliderBarWidget); STAR_CLASS(ButtonWidget); STAR_CLASS(LabelWidget); +STAR_CLASS(VoiceSettingsMenu); STAR_CLASS(KeybindingsMenu); STAR_CLASS(GraphicsMenu); STAR_CLASS(BindingsMenu); @@ -38,6 +39,7 @@ private: void syncGuiToConf(); void displayControls(); + void displayVoiceSettings(); void displayModBindings(); void displayGraphics(); @@ -59,6 +61,7 @@ private: JsonObject m_origConfig; JsonObject m_localChanges; + VoiceSettingsMenuPtr m_voiceSettingsMenu; BindingsMenuPtr m_modBindingsMenu; KeybindingsMenuPtr m_keybindingsMenu; GraphicsMenuPtr m_graphicsMenu; diff --git a/source/frontend/StarVoice.cpp b/source/frontend/StarVoice.cpp index b7f8a6c..843203f 100644 --- a/source/frontend/StarVoice.cpp +++ b/source/frontend/StarVoice.cpp @@ -312,15 +312,19 @@ void Voice::readAudioData(uint8_t* stream, int len) { size_t sampleCount = len / 2; if (active) { - float volume = m_inputVolume; float decibels = getAudioLoudness((int16_t*)stream, sampleCount); + if (!m_loopback) + m_clientSpeaker->decibelLevel = getAudioLoudness((int16_t*)stream, sampleCount, m_inputVolume); + if (m_inputMode == VoiceInputMode::VoiceActivity) { if (decibels > m_threshold) m_lastThresholdTime = now; active = now - m_lastThresholdTime < 50; } } + else if (!m_loopback) + m_clientSpeaker->decibelLevel = -96.0f; if (!m_loopback) { if (active && !m_clientSpeaker->playing) @@ -405,6 +409,7 @@ void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) { } else { speaker->playing = false; + speaker->decibelLevel = -96.0f; it = m_activeSpeakers.erase(it); } } @@ -423,23 +428,25 @@ void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) { } void Voice::update(PositionalAttenuationFunction positionalAttenuationFunction) { - if (positionalAttenuationFunction) { - for (auto& entry : m_speakers) { - if (SpeakerPtr& speaker = entry.second) { - speaker->channelVolumes = { - 1.0f - positionalAttenuationFunction(0, speaker->position, 1.0f), + for (auto& entry : m_speakers) { + if (SpeakerPtr& speaker = entry.second) { + if (positionalAttenuationFunction) { + speaker->channelVolumes = { + 1.0f - positionalAttenuationFunction(0, speaker->position, 1.0f), 1.0f - positionalAttenuationFunction(1, speaker->position, 1.0f) - }; + }; + } + else + speaker->channelVolumes = Vec2F::filled(1.0f); - auto& dbHistory = speaker->dbHistory; - memcpy(&dbHistory[1], &dbHistory[0], (dbHistory.size() - 1) * sizeof(float)); - dbHistory[0] = speaker->decibelLevel; - float smoothDb = 0.0f; - for (float dB : dbHistory) - smoothDb += dB; + auto& dbHistory = speaker->dbHistory; + memcpy(&dbHistory[1], &dbHistory[0], (dbHistory.size() - 1) * sizeof(float)); + dbHistory[0] = speaker->decibelLevel; + float smoothDb = 0.0f; + for (float dB : dbHistory) + smoothDb += dB; - speaker->smoothDb = smoothDb / dbHistory.size(); - } + speaker->smoothDb = smoothDb / dbHistory.size(); } } @@ -467,6 +474,7 @@ StringList Voice::availableDevices() { for (size_t i = 0; i != devices; ++i) deviceList.emplace_back(SDL_GetAudioDeviceName(i, 1)); } + deviceList.sort(); return deviceList; } @@ -488,7 +496,7 @@ int Voice::send(DataStreamBuffer& out, size_t budget) { for (auto& chunk : encodedChunks) { out.write(chunk.size()); out.writeBytes(chunk); - if ((budget -= min(budget, chunk.size())) == 0) + if (budget && (budget -= min(budget, chunk.size())) == 0) break; } @@ -499,7 +507,7 @@ int Voice::send(DataStreamBuffer& out, size_t budget) { } bool Voice::receive(SpeakerPtr speaker, std::string_view view) { - if (!speaker || view.empty()) + if (!m_enabled || !speaker || view.empty()) return false; try { @@ -635,9 +643,8 @@ void Voice::closeDevice() { return; m_applicationController->closeAudioInputDevice(); - if (!m_loopback) - m_clientSpeaker->playing = false; - + m_clientSpeaker->playing = false; + m_clientSpeaker->decibelLevel = -96.0f; m_deviceOpen = false; } @@ -684,9 +691,6 @@ void Voice::thread() { samples[i] *= m_inputVolume; } - if (!m_loopback) - m_clientSpeaker->decibelLevel = getAudioLoudness(samples.data(), samples.size()); - if (int encodedSize = opus_encode(m_encoder.get(), samples.data(), VOICE_FRAME_SIZE, (unsigned char*)encoded.ptr(), encoded.size())) { if (encodedSize == 1) continue; diff --git a/source/frontend/StarVoice.hpp b/source/frontend/StarVoice.hpp index 4500aa6..95046af 100644 --- a/source/frontend/StarVoice.hpp +++ b/source/frontend/StarVoice.hpp @@ -141,7 +141,7 @@ public: void setDeviceName(Maybe device); StringList availableDevices(); - int send(DataStreamBuffer& out, size_t budget); + int send(DataStreamBuffer& out, size_t budget = 0); bool receive(SpeakerPtr speaker, std::string_view view); // Must be called every frame with input state, expires after 1s. diff --git a/source/frontend/StarVoiceLuaBindings.cpp b/source/frontend/StarVoiceLuaBindings.cpp index 1b62aef..934b294 100644 --- a/source/frontend/StarVoiceLuaBindings.cpp +++ b/source/frontend/StarVoiceLuaBindings.cpp @@ -6,9 +6,11 @@ namespace Star { typedef Voice::SpeakerId SpeakerId; -LuaCallbacks LuaBindings::makeVoiceCallbacks(Voice* voice) { +LuaCallbacks LuaBindings::makeVoiceCallbacks() { LuaCallbacks callbacks; + auto voice = Voice::singletonPtr(); + callbacks.registerCallbackWithSignature("devices", bind(&Voice::availableDevices, voice)); callbacks.registerCallback( "getSettings", [voice]() -> Json { return voice->saveJson(); }); callbacks.registerCallback("mergeSettings", [voice](Json const& settings) { voice->loadJson(settings); }); @@ -21,7 +23,13 @@ LuaCallbacks LuaBindings::makeVoiceCallbacks(Voice* voice) { callbacks.registerCallback("speakerPosition", [voice](SpeakerId speakerId) { return voice->speaker(speakerId)->position; }); - callbacks.registerCallback("speaker", [voice](SpeakerId speakerId) { return voice->speaker(speakerId)->toJson(); }); + callbacks.registerCallback("speaker", [voice](Maybe speakerId) { + if (speakerId) + return voice->speaker(*speakerId)->toJson(); + else + return voice->localSpeaker()->toJson(); + }); + callbacks.registerCallback("speakers", [voice](Maybe onlyPlaying) -> List { List list; diff --git a/source/frontend/StarVoiceLuaBindings.hpp b/source/frontend/StarVoiceLuaBindings.hpp index 8c83e54..5d670f3 100644 --- a/source/frontend/StarVoiceLuaBindings.hpp +++ b/source/frontend/StarVoiceLuaBindings.hpp @@ -8,7 +8,7 @@ namespace Star { STAR_CLASS(Voice); namespace LuaBindings { - LuaCallbacks makeVoiceCallbacks(Voice* voice); + LuaCallbacks makeVoiceCallbacks(); } } diff --git a/source/frontend/StarVoiceSettingsMenu.cpp b/source/frontend/StarVoiceSettingsMenu.cpp new file mode 100644 index 0000000..c815dee --- /dev/null +++ b/source/frontend/StarVoiceSettingsMenu.cpp @@ -0,0 +1,23 @@ +#include "StarVoiceSettingsMenu.hpp" +#include "StarVoiceLuaBindings.hpp" + +namespace Star { + +VoiceSettingsMenu::VoiceSettingsMenu(Json const& config) : BaseScriptPane(config) { + m_script.setLuaRoot(make_shared()); + m_script.addCallbacks("voice", LuaBindings::makeVoiceCallbacks()); +} + +void VoiceSettingsMenu::show() { + BaseScriptPane::show(); +} + +void VoiceSettingsMenu::displayed() { + BaseScriptPane::displayed(); +} + +void VoiceSettingsMenu::dismissed() { + BaseScriptPane::dismissed(); +} + +} \ No newline at end of file diff --git a/source/frontend/StarVoiceSettingsMenu.hpp b/source/frontend/StarVoiceSettingsMenu.hpp new file mode 100644 index 0000000..1f9e58d --- /dev/null +++ b/source/frontend/StarVoiceSettingsMenu.hpp @@ -0,0 +1,24 @@ +#ifndef STAR_VOICE_SETTINGS_MENU_HPP +#define STAR_VOICE_SETTINGS_MENU_HPP + +#include "StarBaseScriptPane.hpp" + +namespace Star { + +STAR_CLASS(VoiceSettingsMenu); + +class VoiceSettingsMenu : public BaseScriptPane { +public: + VoiceSettingsMenu(Json const& config); + + virtual void show() override; + void displayed() override; + void dismissed() override; + +private: + +}; + +} + +#endif diff --git a/source/windowing/StarPane.cpp b/source/windowing/StarPane.cpp index a2d32ad..04739cb 100644 --- a/source/windowing/StarPane.cpp +++ b/source/windowing/StarPane.cpp @@ -404,6 +404,10 @@ LuaCallbacks Pane::makePaneCallbacks() { return this->removeChild(widgetName); }); + callbacks.registerCallback("scale", []() -> int { + return GuiContext::singleton().interfaceScale(); + }); + return callbacks; }