Lua functions for updating Voice settings, improve loudness visualization

This commit is contained in:
Kae 2023-07-19 18:15:49 +10:00
parent a9dac1b2df
commit 3cdbf8bf01
5 changed files with 63 additions and 21 deletions

View File

@ -1,7 +1,7 @@
-- Manages the voice HUD indicators and click-to-mute/unmute. -- Manages the voice HUD indicators and click-to-mute/unmute.
local fmt = string.format local fmt = string.format
local sqrt = math.sqrt local sqrt, min, max = math.sqrt, math.min, math.max
local module = {} local module = {}
modules.voice_manager = module modules.voice_manager = module
@ -19,6 +19,10 @@ local LINE_COLOR = {50, 210, 255, 255}
local FONT_DIRECTIVES = "?border=1;333;3337?border=1;333;3330" local FONT_DIRECTIVES = "?border=1;333;3337?border=1;333;3330"
local NAME_PREFIX = "^noshadow,white,set;" local NAME_PREFIX = "^noshadow,white,set;"
local function dbToLoudness(db) return 2 ^ (db / 6) end
local canvas
local linePaddingDrawable local linePaddingDrawable
do do
local drawable = { image = BACK_INDICATOR_IMAGE, position = {0, 0}, color = LINE_COLOR, centered = false } local drawable = { image = BACK_INDICATOR_IMAGE, position = {0, 0}, color = LINE_COLOR, centered = false }
@ -29,6 +33,7 @@ do
end end
end end
local lineDrawable = { line = {{LINE_PADDING, 24}, {10, 24}}, width = 48, color = LINE_COLOR }
local function line(pos, value) local function line(pos, value)
local width = math.floor((LINE_WIDTH * value) + 0.5) local width = math.floor((LINE_WIDTH * value) + 0.5)
LINE_COLOR[4] = 255 * math.min(1, sqrt(width / 350)) LINE_COLOR[4] = 255 * math.min(1, sqrt(width / 350))
@ -44,8 +49,6 @@ local function line(pos, value)
end end
end end
local canvas
local drawable = { image = BACK_INDICATOR_IMAGE, centered = false } local drawable = { image = BACK_INDICATOR_IMAGE, centered = false }
local textPositioning = { position = {0, 0}, horizontalAnchor = "left", verticalAnchor = "mid" } local textPositioning = { position = {0, 0}, horizontalAnchor = "left", verticalAnchor = "mid" }
@ -61,7 +64,7 @@ end
local function drawSpeakerBar(mouse, pos, speaker, i) local function drawSpeakerBar(mouse, pos, speaker, i)
drawable.image = BACK_INDICATOR_IMAGE drawable.image = BACK_INDICATOR_IMAGE
canvas:drawDrawable(drawable, pos) canvas:drawDrawable(drawable, pos)
line(pos, 1 - sqrt(math.min(1, math.max(0, speaker.loudness / -50)))) line(pos, dbToLoudness(speaker.smoothDecibels))
local hovering = not speaker.isLocal and mouseOverSpeaker(mouse, pos) local hovering = not speaker.isLocal and mouseOverSpeaker(mouse, pos)
textPositioning.position = {pos[1] + 49, pos[2] + 24} textPositioning.position = {pos[1] + 49, pos[2] + 24}
@ -94,11 +97,13 @@ end
local function simulateSpeakers() local function simulateSpeakers()
local speakers = {} local speakers = {}
for i = 2, 5 + math.floor((math.sin(os.clock()) * 4) + .5) do for i = 2, 5 + math.floor((math.sin(os.clock()) * 4) + .5) do
local dB = -48 + 48 * math.sin(os.clock() + (i * 0.5))
speakers[i] = { speakers[i] = {
speakerId = i, speakerId = i,
entityId = -65536 * i, entityId = -65536 * i,
name = "Player " .. i, name = "Player " .. i,
loudness = -48 + 48 * math.sin(os.clock() + (i * 0.5)), decibels = dB,
smoothDecibels = dB,
muted = false muted = false
} }
end end
@ -147,8 +152,6 @@ local function drawIndicators()
for i, v in pairs(speakers) do for i, v in pairs(speakers) do
if v then if v then
local entityId = v.entityId
local loudness = v.loudness
local pos = {basePos[1], basePos[2] + (i - 1) * 52} local pos = {basePos[1], basePos[2] + (i - 1) * 52}
drawSpeakerBar(mousePosition, pos, v, i) drawSpeakerBar(mousePosition, pos, v, i)
end end

View File

@ -4,6 +4,7 @@
#include "StarTime.hpp" #include "StarTime.hpp"
#include "StarRoot.hpp" #include "StarRoot.hpp"
#include "StarLogging.hpp" #include "StarLogging.hpp"
#include "StarInterpolation.hpp"
#include "opus/include/opus.h" #include "opus/include/opus.h"
#include "SDL.h" #include "SDL.h"
@ -153,6 +154,8 @@ inline bool change(T& value, T newValue) {
} }
void Voice::loadJson(Json const& config) { void Voice::loadJson(Json const& config) {
// Not all keys are required
{ {
bool enabled = shouldEnableInput(); bool enabled = shouldEnableInput();
m_enabled = config.getBool("enabled", m_enabled); m_enabled = config.getBool("enabled", m_enabled);
@ -161,18 +164,26 @@ void Voice::loadJson(Json const& config) {
resetDevice(); resetDevice();
} }
if (change(m_deviceName, config.optQueryString("inputDevice"))) if (config.contains("deviceName") // Make sure null-type key exists
&& change(m_deviceName, config.optString("deviceName")))
resetDevice(); resetDevice();
m_threshold = config.getFloat("threshold", m_threshold); m_threshold = config.getFloat("threshold", m_threshold);
m_inputVolume = config.getFloat("inputVolume", m_inputVolume); m_inputVolume = config.getFloat("inputVolume", m_inputVolume);
m_outputVolume = config.getFloat("outputVolume", m_outputVolume); m_outputVolume = config.getFloat("outputVolume", m_outputVolume);
if (change(m_inputMode, VoiceInputModeNames.getLeft(config.getString("inputMode", "PushToTalk")))) if (auto inputMode = config.optString("inputMode")) {
m_lastInputTime = 0; if (change(m_inputMode, VoiceInputModeNames.getLeft(*inputMode)))
m_lastInputTime = 0;
}
if (change(m_channelMode, VoiceChannelModeNames.getLeft(config.getString("channelMode", "Mono")))) if (auto channelMode = config.optString("channelMode")) {
resetEncoder(); if (change(m_channelMode, VoiceChannelModeNames.getLeft(*channelMode))) {
closeDevice();
resetEncoder();
resetDevice();
}
}
} }
@ -366,6 +377,15 @@ void Voice::update(PositionalAttenuationFunction positionalAttenuationFunction)
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)
}; };
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();
} }
} }
} }
@ -386,6 +406,17 @@ void Voice::setDeviceName(Maybe<String> deviceName) {
openDevice(); openDevice();
} }
StringList Voice::availableDevices() {
int devices = SDL_GetNumAudioDevices(1);
StringList deviceList;
if (devices > 0) {
deviceList.reserve(devices);
for (size_t i = 0; i != devices; ++i)
deviceList.emplace_back(SDL_GetAudioDeviceName(i, 1));
}
return deviceList;
}
int Voice::send(DataStreamBuffer& out, size_t budget) { int Voice::send(DataStreamBuffer& out, size_t budget) {
out.setByteOrder(ByteOrder::LittleEndian); out.setByteOrder(ByteOrder::LittleEndian);
out.write<uint16_t>(VOICE_VERSION); out.write<uint16_t>(VOICE_VERSION);
@ -520,6 +551,7 @@ OpusEncoder* Voice::createEncoder(int channels) {
void Voice::resetEncoder() { void Voice::resetEncoder() {
int channels = encoderChannels(); int channels = encoderChannels();
MutexLocker locker(m_threadMutex);
m_encoder.reset(createEncoder(channels)); m_encoder.reset(createEncoder(channels));
opus_encoder_ctl(m_encoder.get(), OPUS_SET_BITRATE(channels == 2 ? 50000 : 24000)); opus_encoder_ctl(m_encoder.get(), OPUS_SET_BITRATE(channels == 2 ? 50000 : 24000));
} }

View File

@ -82,11 +82,13 @@ public:
int64_t lastReceiveTime = 0; int64_t lastReceiveTime = 0;
int64_t lastPlayTime = 0; int64_t lastPlayTime = 0;
float smoothDb = -96.0f;
Array<float, 10> dbHistory = Array<float, 10>::filled(0);
atomic<bool> muted = false; atomic<bool> muted = false;
atomic<bool> playing = 0; atomic<bool> playing = 0;
atomic<float> decibelLevel = 0.0f; atomic<float> decibelLevel = -96.0f;
atomic<Array<float, 2>> channelVolumes = Array<float, 2>::filled(1.0f); atomic<Array<float, 2>> channelVolumes = Array<float, 2>::filled(1);
unsigned int minimumPlaySamples = 4096; unsigned int minimumPlaySamples = 4096;
@ -133,6 +135,7 @@ public:
void update(PositionalAttenuationFunction positionalAttenuationFunction = {}); void update(PositionalAttenuationFunction positionalAttenuationFunction = {});
void setDeviceName(Maybe<String> device); void setDeviceName(Maybe<String> device);
StringList availableDevices();
int send(DataStreamBuffer& out, size_t budget); int send(DataStreamBuffer& out, size_t budget);
bool receive(SpeakerPtr speaker, std::string_view view); bool receive(SpeakerPtr speaker, std::string_view view);

View File

@ -6,17 +6,21 @@ namespace Star {
LuaCallbacks LuaBindings::makeVoiceCallbacks(Voice* voice) { LuaCallbacks LuaBindings::makeVoiceCallbacks(Voice* voice) {
LuaCallbacks callbacks; LuaCallbacks callbacks;
callbacks.registerCallback("getSettings", [voice]() -> Json { return voice->saveJson(); });
callbacks.registerCallback("mergeSettings", [voice](Json const& settings) { voice->loadJson(settings); });
callbacks.registerCallback("speakers", [voice](Maybe<bool> onlyPlaying) -> List<Json> { callbacks.registerCallback("speakers", [voice](Maybe<bool> onlyPlaying) -> List<Json> {
List<Json> list; List<Json> list;
for (auto& speaker : voice->speakers(onlyPlaying.value(true))) { for (auto& speaker : voice->speakers(onlyPlaying.value(true))) {
list.append(JsonObject{ list.append(JsonObject{
{"speakerId", speaker->speakerId }, {"speakerId", speaker->speakerId },
{"entityId", speaker->entityId }, {"entityId", speaker->entityId },
{"name", speaker->name }, {"name", speaker->name },
{"playing", (bool)speaker->playing }, {"playing", (bool)speaker->playing },
{"muted", (bool)speaker->muted }, {"muted", (bool)speaker->muted },
{"loudness", (float)speaker->decibelLevel }, {"decibels", (float)speaker->decibelLevel },
{"smoothDecibels", (float)speaker->smoothDb },
}); });
} }

View File

@ -107,7 +107,7 @@ LuaContext LuaRoot::createContext(StringList const& scriptPaths) {
}); });
newContext.set("loadstring", m_luaEngine->createFunction([newContext](String const& source, Maybe<String> const& name, Maybe<LuaValue> const& env) -> LuaFunction { newContext.set("loadstring", m_luaEngine->createFunction([newContext](String const& source, Maybe<String> const& name, Maybe<LuaValue> const& env) -> LuaFunction {
String functionName = name ? strf("loadstring: {}", name) : "loadstring"; String functionName = name ? strf("loadstring: {}", *name) : "loadstring";
return newContext.engine().createFunctionFromSource(newContext.handleIndex(), source.utf8Ptr(), source.utf8Size(), functionName.utf8Ptr()); return newContext.engine().createFunctionFromSource(newContext.handleIndex(), source.utf8Ptr(), source.utf8Size(), functionName.utf8Ptr());
})); }));