Port in the voice settings menu

This commit is contained in:
Kae 2023-07-19 23:16:59 +10:00
parent d682b164aa
commit 1f038540a5
35 changed files with 552 additions and 39 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 496 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 569 B

View File

Before

Width:  |  Height:  |  Size: 611 B

After

Width:  |  Height:  |  Size: 611 B

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 B

View File

@ -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]
}
}
}
}
}
}
}
}

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1017 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

View File

@ -1,25 +1,47 @@
{ {
"paneLayout" : { "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" : { "showKeybindings" : {
"type" : "button", "type" : "button",
"position" : [150, 95], "position" : [153, 95],
"caption" : "Game Binds", "caption" : "Game Binds",
"base" : "/interface/optionsmenu/controlsbutton.png", "base" : "/interface/optionsmenu/tricontrolsbutton.png",
"hover" : "/interface/optionsmenu/controlsbuttonhover.png" "hover" : "/interface/optionsmenu/tricontrolsbuttonhover.png"
}, },
"showModBindings" : { "showModBindings" : {
"type" : "button", "type" : "button",
"position" : [87, 95], "position" : [87, 95],
"caption" : "Mod Binds", "caption" : "Mod Binds",
"base" : "/interface/optionsmenu/controlsbutton.png", "base" : "/interface/optionsmenu/tricontrolsbutton.png",
"hover" : "/interface/optionsmenu/controlsbuttonhover.png" "hover" : "/interface/optionsmenu/tricontrolsbuttonhover.png"
}, },
"showGraphics" : { "showGraphics" : {
"type" : "button", "type" : "button",
"position" : [24, 95], "position" : [21, 95],
"caption" : "Graphics", "caption" : "Graphics",
"base" : "/interface/optionsmenu/controlsbutton.png", "base" : "/interface/optionsmenu/tricontrolsbutton.png",
"hover" : "/interface/optionsmenu/controlsbuttonhover.png" "hover" : "/interface/optionsmenu/tricontrolsbuttonhover.png"
} },
"sfxValueLabel" : { "position" : [192, 142] } // this is 2px too low in vanilla lol
} }
} }

View File

Before

Width:  |  Height:  |  Size: 213 B

After

Width:  |  Height:  |  Size: 213 B

View File

Before

Width:  |  Height:  |  Size: 213 B

After

Width:  |  Height:  |  Size: 213 B

View File

@ -7,7 +7,7 @@ local module = {}
modules.voice_manager = module modules.voice_manager = module
--constants --constants
local INDICATOR_PATH = "/interface/voicechat/indicator/" local INDICATOR_PATH = "/interface/opensb/voicechat/indicator/"
local BACK_INDICATOR_IMAGE = INDICATOR_PATH .. "back.png" local BACK_INDICATOR_IMAGE = INDICATOR_PATH .. "back.png"
local FRONT_INDICATOR_IMAGE = INDICATOR_PATH .. "front.png" local FRONT_INDICATOR_IMAGE = INDICATOR_PATH .. "front.png"
local FRONT_MUTED_INDICATOR_IMAGE = INDICATOR_PATH .. "front_muted.png" local FRONT_MUTED_INDICATOR_IMAGE = INDICATOR_PATH .. "front_muted.png"

View File

@ -376,6 +376,13 @@ void ClientApplication::update() {
else if (m_state > MainAppState::Title) else if (m_state > MainAppState::Title)
updateRunning(); 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_guiContext->cleanup();
m_edgeKeyEvents.clear(); m_edgeKeyEvents.clear();
m_input->reset(); m_input->reset();
@ -499,7 +506,7 @@ void ClientApplication::changeState(MainAppState newState) {
m_statistics = make_shared<Statistics>(m_root->toStoragePath("player"), appController()->statisticsService()); m_statistics = make_shared<Statistics>(m_root->toStoragePath("player"), appController()->statisticsService());
m_universeClient = make_shared<UniverseClient>(m_playerStorage, m_statistics); m_universeClient = make_shared<UniverseClient>(m_playerStorage, m_statistics);
m_universeClient->setLuaCallbacks("input", LuaBindings::makeInputCallbacks()); 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_mainMixer->setUniverseClient(m_universeClient);
m_titleScreen = make_shared<TitleScreen>(m_playerStorage, m_mainMixer->mixer()); m_titleScreen = make_shared<TitleScreen>(m_playerStorage, m_mainMixer->mixer());

View File

@ -58,6 +58,7 @@ SET (star_frontend_HEADERS
StarWireInterface.hpp StarWireInterface.hpp
StarVoice.hpp StarVoice.hpp
StarVoiceLuaBindings.hpp StarVoiceLuaBindings.hpp
StarVoiceSettingsMenu.hpp
) )
SET (star_frontend_SOURCES SET (star_frontend_SOURCES
@ -108,6 +109,7 @@ SET (star_frontend_SOURCES
StarWireInterface.cpp StarWireInterface.cpp
StarVoice.cpp StarVoice.cpp
StarVoiceLuaBindings.cpp StarVoiceLuaBindings.cpp
StarVoiceSettingsMenu.cpp
) )
ADD_LIBRARY (star_frontend OBJECT ${star_frontend_SOURCES} ${star_frontend_HEADERS}) ADD_LIBRARY (star_frontend OBJECT ${star_frontend_SOURCES} ${star_frontend_HEADERS})

View File

@ -121,6 +121,9 @@ void MainMixer::update(bool muteSfx, bool muteMusic) {
if (m_mixer->hasEffect("echo")) if (m_mixer->hasEffect("echo"))
m_mixer->removeEffect("echo", 0); m_mixer->removeEffect("echo", 0);
if (Voice* voice = Voice::singletonPtr())
voice->update();
m_mixer->update(); m_mixer->update();
} }
} }

View File

@ -7,6 +7,7 @@
#include "StarLabelWidget.hpp" #include "StarLabelWidget.hpp"
#include "StarAssets.hpp" #include "StarAssets.hpp"
#include "StarKeybindingsMenu.hpp" #include "StarKeybindingsMenu.hpp"
#include "StarVoiceSettingsMenu.hpp"
#include "StarBindingsMenu.hpp" #include "StarBindingsMenu.hpp"
#include "StarGraphicsMenu.hpp" #include "StarGraphicsMenu.hpp"
@ -49,8 +50,14 @@ OptionsMenu::OptionsMenu(PaneManager* manager)
reader.registerCallback("showKeybindings", [=](Widget*) { reader.registerCallback("showKeybindings", [=](Widget*) {
displayControls(); displayControls();
}); });
reader.registerCallback("showVoiceSettings", [=](Widget*) {
displayVoiceSettings();
});
reader.registerCallback("showVoicePlayers", [=](Widget*) {
});
reader.registerCallback("showModBindings", [=](Widget*) { reader.registerCallback("showModBindings", [=](Widget*) {
displayModBindings(); displayModBindings();
}); });
reader.registerCallback("showGraphics", [=](Widget*) { reader.registerCallback("showGraphics", [=](Widget*) {
displayGraphics(); displayGraphics();
@ -74,6 +81,7 @@ OptionsMenu::OptionsMenu(PaneManager* manager)
m_sfxSlider->setRange(m_sfxRange, assets->json("/interface/optionsmenu/optionsmenu.config:sfxDelta").toInt()); 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_musicSlider->setRange(m_musicRange, assets->json("/interface/optionsmenu/optionsmenu.config:musicDelta").toInt());
m_voiceSettingsMenu = make_shared<VoiceSettingsMenu>(assets->json(config.getString("voiceSettingsPanePath", "/interface/opensb/voicechat/voicechat.config")));
m_modBindingsMenu = make_shared<BindingsMenu>(assets->json(config.getString("bindingsPanePath", "/interface/opensb/bindings/bindings.config"))); m_modBindingsMenu = make_shared<BindingsMenu>(assets->json(config.getString("bindingsPanePath", "/interface/opensb/bindings/bindings.config")));
m_keybindingsMenu = make_shared<KeybindingsMenu>(); m_keybindingsMenu = make_shared<KeybindingsMenu>();
m_graphicsMenu = make_shared<GraphicsMenu>(); m_graphicsMenu = make_shared<GraphicsMenu>();
@ -169,6 +177,10 @@ void OptionsMenu::displayControls() {
m_paneManager->displayPane(PaneLayer::ModalWindow, m_keybindingsMenu); m_paneManager->displayPane(PaneLayer::ModalWindow, m_keybindingsMenu);
} }
void OptionsMenu::displayVoiceSettings() {
m_paneManager->displayPane(PaneLayer::ModalWindow, m_voiceSettingsMenu);
}
void OptionsMenu::displayModBindings() { void OptionsMenu::displayModBindings() {
m_paneManager->displayPane(PaneLayer::ModalWindow, m_modBindingsMenu); m_paneManager->displayPane(PaneLayer::ModalWindow, m_modBindingsMenu);
} }

View File

@ -10,6 +10,7 @@ namespace Star {
STAR_CLASS(SliderBarWidget); STAR_CLASS(SliderBarWidget);
STAR_CLASS(ButtonWidget); STAR_CLASS(ButtonWidget);
STAR_CLASS(LabelWidget); STAR_CLASS(LabelWidget);
STAR_CLASS(VoiceSettingsMenu);
STAR_CLASS(KeybindingsMenu); STAR_CLASS(KeybindingsMenu);
STAR_CLASS(GraphicsMenu); STAR_CLASS(GraphicsMenu);
STAR_CLASS(BindingsMenu); STAR_CLASS(BindingsMenu);
@ -38,6 +39,7 @@ private:
void syncGuiToConf(); void syncGuiToConf();
void displayControls(); void displayControls();
void displayVoiceSettings();
void displayModBindings(); void displayModBindings();
void displayGraphics(); void displayGraphics();
@ -59,6 +61,7 @@ private:
JsonObject m_origConfig; JsonObject m_origConfig;
JsonObject m_localChanges; JsonObject m_localChanges;
VoiceSettingsMenuPtr m_voiceSettingsMenu;
BindingsMenuPtr m_modBindingsMenu; BindingsMenuPtr m_modBindingsMenu;
KeybindingsMenuPtr m_keybindingsMenu; KeybindingsMenuPtr m_keybindingsMenu;
GraphicsMenuPtr m_graphicsMenu; GraphicsMenuPtr m_graphicsMenu;

View File

@ -312,15 +312,19 @@ void Voice::readAudioData(uint8_t* stream, int len) {
size_t sampleCount = len / 2; size_t sampleCount = len / 2;
if (active) { if (active) {
float volume = m_inputVolume;
float decibels = getAudioLoudness((int16_t*)stream, sampleCount); 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 (m_inputMode == VoiceInputMode::VoiceActivity) {
if (decibels > m_threshold) if (decibels > m_threshold)
m_lastThresholdTime = now; m_lastThresholdTime = now;
active = now - m_lastThresholdTime < 50; active = now - m_lastThresholdTime < 50;
} }
} }
else if (!m_loopback)
m_clientSpeaker->decibelLevel = -96.0f;
if (!m_loopback) { if (!m_loopback) {
if (active && !m_clientSpeaker->playing) if (active && !m_clientSpeaker->playing)
@ -405,6 +409,7 @@ void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) {
} }
else { else {
speaker->playing = false; speaker->playing = false;
speaker->decibelLevel = -96.0f;
it = m_activeSpeakers.erase(it); 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) { void Voice::update(PositionalAttenuationFunction positionalAttenuationFunction) {
if (positionalAttenuationFunction) { for (auto& entry : m_speakers) {
for (auto& entry : m_speakers) { if (SpeakerPtr& speaker = entry.second) {
if (SpeakerPtr& speaker = entry.second) { if (positionalAttenuationFunction) {
speaker->channelVolumes = { speaker->channelVolumes = {
1.0f - positionalAttenuationFunction(0, speaker->position, 1.0f), 1.0f - positionalAttenuationFunction(0, speaker->position, 1.0f),
1.0f - positionalAttenuationFunction(1, speaker->position, 1.0f) 1.0f - positionalAttenuationFunction(1, speaker->position, 1.0f)
}; };
}
else
speaker->channelVolumes = Vec2F::filled(1.0f);
auto& dbHistory = speaker->dbHistory; auto& dbHistory = speaker->dbHistory;
memcpy(&dbHistory[1], &dbHistory[0], (dbHistory.size() - 1) * sizeof(float)); memcpy(&dbHistory[1], &dbHistory[0], (dbHistory.size() - 1) * sizeof(float));
dbHistory[0] = speaker->decibelLevel; dbHistory[0] = speaker->decibelLevel;
float smoothDb = 0.0f; float smoothDb = 0.0f;
for (float dB : dbHistory) for (float dB : dbHistory)
smoothDb += dB; 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) for (size_t i = 0; i != devices; ++i)
deviceList.emplace_back(SDL_GetAudioDeviceName(i, 1)); deviceList.emplace_back(SDL_GetAudioDeviceName(i, 1));
} }
deviceList.sort();
return deviceList; return deviceList;
} }
@ -488,7 +496,7 @@ int Voice::send(DataStreamBuffer& out, size_t budget) {
for (auto& chunk : encodedChunks) { for (auto& chunk : encodedChunks) {
out.write<uint32_t>(chunk.size()); out.write<uint32_t>(chunk.size());
out.writeBytes(chunk); out.writeBytes(chunk);
if ((budget -= min<size_t>(budget, chunk.size())) == 0) if (budget && (budget -= min<size_t>(budget, chunk.size())) == 0)
break; break;
} }
@ -499,7 +507,7 @@ int Voice::send(DataStreamBuffer& out, size_t budget) {
} }
bool Voice::receive(SpeakerPtr speaker, std::string_view view) { bool Voice::receive(SpeakerPtr speaker, std::string_view view) {
if (!speaker || view.empty()) if (!m_enabled || !speaker || view.empty())
return false; return false;
try { try {
@ -635,9 +643,8 @@ void Voice::closeDevice() {
return; return;
m_applicationController->closeAudioInputDevice(); m_applicationController->closeAudioInputDevice();
if (!m_loopback) m_clientSpeaker->playing = false;
m_clientSpeaker->playing = false; m_clientSpeaker->decibelLevel = -96.0f;
m_deviceOpen = false; m_deviceOpen = false;
} }
@ -684,9 +691,6 @@ void Voice::thread() {
samples[i] *= m_inputVolume; 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 (int encodedSize = opus_encode(m_encoder.get(), samples.data(), VOICE_FRAME_SIZE, (unsigned char*)encoded.ptr(), encoded.size())) {
if (encodedSize == 1) if (encodedSize == 1)
continue; continue;

View File

@ -141,7 +141,7 @@ public:
void setDeviceName(Maybe<String> device); void setDeviceName(Maybe<String> device);
StringList availableDevices(); 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); bool receive(SpeakerPtr speaker, std::string_view view);
// Must be called every frame with input state, expires after 1s. // Must be called every frame with input state, expires after 1s.

View File

@ -6,9 +6,11 @@
namespace Star { namespace Star {
typedef Voice::SpeakerId SpeakerId; typedef Voice::SpeakerId SpeakerId;
LuaCallbacks LuaBindings::makeVoiceCallbacks(Voice* voice) { LuaCallbacks LuaBindings::makeVoiceCallbacks() {
LuaCallbacks callbacks; LuaCallbacks callbacks;
auto voice = Voice::singletonPtr();
callbacks.registerCallbackWithSignature<StringList>("devices", bind(&Voice::availableDevices, voice)); callbacks.registerCallbackWithSignature<StringList>("devices", bind(&Voice::availableDevices, voice));
callbacks.registerCallback( "getSettings", [voice]() -> Json { return voice->saveJson(); }); callbacks.registerCallback( "getSettings", [voice]() -> Json { return voice->saveJson(); });
callbacks.registerCallback("mergeSettings", [voice](Json const& settings) { voice->loadJson(settings); }); 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("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> speakerId) {
if (speakerId)
return voice->speaker(*speakerId)->toJson();
else
return voice->localSpeaker()->toJson();
});
callbacks.registerCallback("speakers", [voice](Maybe<bool> onlyPlaying) -> List<Json> { callbacks.registerCallback("speakers", [voice](Maybe<bool> onlyPlaying) -> List<Json> {
List<Json> list; List<Json> list;

View File

@ -8,7 +8,7 @@ namespace Star {
STAR_CLASS(Voice); STAR_CLASS(Voice);
namespace LuaBindings { namespace LuaBindings {
LuaCallbacks makeVoiceCallbacks(Voice* voice); LuaCallbacks makeVoiceCallbacks();
} }
} }

View File

@ -0,0 +1,23 @@
#include "StarVoiceSettingsMenu.hpp"
#include "StarVoiceLuaBindings.hpp"
namespace Star {
VoiceSettingsMenu::VoiceSettingsMenu(Json const& config) : BaseScriptPane(config) {
m_script.setLuaRoot(make_shared<LuaRoot>());
m_script.addCallbacks("voice", LuaBindings::makeVoiceCallbacks());
}
void VoiceSettingsMenu::show() {
BaseScriptPane::show();
}
void VoiceSettingsMenu::displayed() {
BaseScriptPane::displayed();
}
void VoiceSettingsMenu::dismissed() {
BaseScriptPane::dismissed();
}
}

View File

@ -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

View File

@ -404,6 +404,10 @@ LuaCallbacks Pane::makePaneCallbacks() {
return this->removeChild(widgetName); return this->removeChild(widgetName);
}); });
callbacks.registerCallback("scale", []() -> int {
return GuiContext::singleton().interfaceScale();
});
return callbacks; return callbacks;
} }