Port in the voice settings menu
BIN
assets/opensb/interface/opensb/voicechat/activity.png
Normal file
After Width: | Height: | Size: 201 B |
BIN
assets/opensb/interface/opensb/voicechat/activityback.png
Normal file
After Width: | Height: | Size: 326 B |
BIN
assets/opensb/interface/opensb/voicechat/bigbutton.png
Normal file
After Width: | Height: | Size: 223 B |
BIN
assets/opensb/interface/opensb/voicechat/bigbuttonback.png
Normal file
After Width: | Height: | Size: 496 B |
BIN
assets/opensb/interface/opensb/voicechat/body.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
assets/opensb/interface/opensb/voicechat/device.png
Normal file
After Width: | Height: | Size: 195 B |
BIN
assets/opensb/interface/opensb/voicechat/deviceback.png
Normal file
After Width: | Height: | Size: 447 B |
BIN
assets/opensb/interface/opensb/voicechat/footer.png
Normal file
After Width: | Height: | Size: 250 B |
BIN
assets/opensb/interface/opensb/voicechat/header.png
Normal file
After Width: | Height: | Size: 569 B |
Before Width: | Height: | Size: 611 B After Width: | Height: | Size: 611 B |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
BIN
assets/opensb/interface/opensb/voicechat/pushtotalk.png
Normal file
After Width: | Height: | Size: 207 B |
BIN
assets/opensb/interface/opensb/voicechat/pushtotalkback.png
Normal file
After Width: | Height: | Size: 378 B |
174
assets/opensb/interface/opensb/voicechat/voicechat.config
Normal 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]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
227
assets/opensb/interface/opensb/voicechat/voicechat.lua
Normal 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
|
BIN
assets/opensb/interface/optionsmenu/body_blank.png
Normal file
After Width: | Height: | Size: 1017 B |
BIN
assets/opensb/interface/optionsmenu/duocontrolsbutton.png
Normal file
After Width: | Height: | Size: 159 B |
BIN
assets/opensb/interface/optionsmenu/duocontrolsbuttonhover.png
Normal file
After Width: | Height: | Size: 154 B |
@ -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
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 213 B After Width: | Height: | Size: 213 B |
Before Width: | Height: | Size: 213 B After Width: | Height: | Size: 213 B |
@ -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"
|
||||
|
@ -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<Statistics>(m_root->toStoragePath("player"), appController()->statisticsService());
|
||||
m_universeClient = make_shared<UniverseClient>(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<TitleScreen>(m_playerStorage, m_mainMixer->mixer());
|
||||
|
@ -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})
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "StarLabelWidget.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
#include "StarKeybindingsMenu.hpp"
|
||||
#include "StarVoiceSettingsMenu.hpp"
|
||||
#include "StarBindingsMenu.hpp"
|
||||
#include "StarGraphicsMenu.hpp"
|
||||
|
||||
@ -49,6 +50,12 @@ OptionsMenu::OptionsMenu(PaneManager* manager)
|
||||
reader.registerCallback("showKeybindings", [=](Widget*) {
|
||||
displayControls();
|
||||
});
|
||||
reader.registerCallback("showVoiceSettings", [=](Widget*) {
|
||||
displayVoiceSettings();
|
||||
});
|
||||
reader.registerCallback("showVoicePlayers", [=](Widget*) {
|
||||
|
||||
});
|
||||
reader.registerCallback("showModBindings", [=](Widget*) {
|
||||
displayModBindings();
|
||||
});
|
||||
@ -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<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_keybindingsMenu = make_shared<KeybindingsMenu>();
|
||||
m_graphicsMenu = make_shared<GraphicsMenu>();
|
||||
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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,13 +428,16 @@ 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) {
|
||||
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));
|
||||
@ -441,7 +449,6 @@ void Voice::update(PositionalAttenuationFunction positionalAttenuationFunction)
|
||||
speaker->smoothDb = smoothDb / dbHistory.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_nextSaveTime && Time::monotonicMilliseconds() > m_nextSaveTime) {
|
||||
m_nextSaveTime = 0;
|
||||
@ -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<uint32_t>(chunk.size());
|
||||
out.writeBytes(chunk);
|
||||
if ((budget -= min<size_t>(budget, chunk.size())) == 0)
|
||||
if (budget && (budget -= min<size_t>(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->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;
|
||||
|
@ -141,7 +141,7 @@ public:
|
||||
void setDeviceName(Maybe<String> 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.
|
||||
|
@ -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<StringList>("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> speakerId) {
|
||||
if (speakerId)
|
||||
return voice->speaker(*speakerId)->toJson();
|
||||
else
|
||||
return voice->localSpeaker()->toJson();
|
||||
});
|
||||
|
||||
callbacks.registerCallback("speakers", [voice](Maybe<bool> onlyPlaying) -> List<Json> {
|
||||
List<Json> list;
|
||||
|
||||
|
@ -8,7 +8,7 @@ namespace Star {
|
||||
STAR_CLASS(Voice);
|
||||
|
||||
namespace LuaBindings {
|
||||
LuaCallbacks makeVoiceCallbacks(Voice* voice);
|
||||
LuaCallbacks makeVoiceCallbacks();
|
||||
}
|
||||
|
||||
}
|
||||
|
23
source/frontend/StarVoiceSettingsMenu.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
24
source/frontend/StarVoiceSettingsMenu.hpp
Normal 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
|
@ -404,6 +404,10 @@ LuaCallbacks Pane::makePaneCallbacks() {
|
||||
return this->removeChild(widgetName);
|
||||
});
|
||||
|
||||
callbacks.registerCallback("scale", []() -> int {
|
||||
return GuiContext::singleton().interfaceScale();
|
||||
});
|
||||
|
||||
return callbacks;
|
||||
}
|
||||
|
||||
|