Lua functions for updating Voice settings, improve loudness visualization
This commit is contained in:
parent
a9dac1b2df
commit
3cdbf8bf01
@ -1,7 +1,7 @@
|
||||
-- Manages the voice HUD indicators and click-to-mute/unmute.
|
||||
|
||||
local fmt = string.format
|
||||
local sqrt = math.sqrt
|
||||
local sqrt, min, max = math.sqrt, math.min, math.max
|
||||
|
||||
local 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 NAME_PREFIX = "^noshadow,white,set;"
|
||||
|
||||
local function dbToLoudness(db) return 2 ^ (db / 6) end
|
||||
|
||||
local canvas
|
||||
|
||||
local linePaddingDrawable
|
||||
do
|
||||
local drawable = { image = BACK_INDICATOR_IMAGE, position = {0, 0}, color = LINE_COLOR, centered = false }
|
||||
@ -29,6 +33,7 @@ do
|
||||
end
|
||||
end
|
||||
|
||||
local lineDrawable = { line = {{LINE_PADDING, 24}, {10, 24}}, width = 48, color = LINE_COLOR }
|
||||
local function line(pos, value)
|
||||
local width = math.floor((LINE_WIDTH * value) + 0.5)
|
||||
LINE_COLOR[4] = 255 * math.min(1, sqrt(width / 350))
|
||||
@ -44,8 +49,6 @@ local function line(pos, value)
|
||||
end
|
||||
end
|
||||
|
||||
local canvas
|
||||
|
||||
local drawable = { image = BACK_INDICATOR_IMAGE, centered = false }
|
||||
local textPositioning = { position = {0, 0}, horizontalAnchor = "left", verticalAnchor = "mid" }
|
||||
|
||||
@ -61,7 +64,7 @@ end
|
||||
local function drawSpeakerBar(mouse, pos, speaker, i)
|
||||
drawable.image = BACK_INDICATOR_IMAGE
|
||||
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)
|
||||
|
||||
textPositioning.position = {pos[1] + 49, pos[2] + 24}
|
||||
@ -94,11 +97,13 @@ end
|
||||
local function simulateSpeakers()
|
||||
local speakers = {}
|
||||
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] = {
|
||||
speakerId = i,
|
||||
entityId = -65536 * i,
|
||||
name = "Player " .. i,
|
||||
loudness = -48 + 48 * math.sin(os.clock() + (i * 0.5)),
|
||||
decibels = dB,
|
||||
smoothDecibels = dB,
|
||||
muted = false
|
||||
}
|
||||
end
|
||||
@ -147,8 +152,6 @@ local function drawIndicators()
|
||||
|
||||
for i, v in pairs(speakers) do
|
||||
if v then
|
||||
local entityId = v.entityId
|
||||
local loudness = v.loudness
|
||||
local pos = {basePos[1], basePos[2] + (i - 1) * 52}
|
||||
drawSpeakerBar(mousePosition, pos, v, i)
|
||||
end
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include "StarTime.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarLogging.hpp"
|
||||
#include "StarInterpolation.hpp"
|
||||
#include "opus/include/opus.h"
|
||||
|
||||
#include "SDL.h"
|
||||
@ -153,6 +154,8 @@ inline bool change(T& value, T newValue) {
|
||||
}
|
||||
|
||||
void Voice::loadJson(Json const& config) {
|
||||
// Not all keys are required
|
||||
|
||||
{
|
||||
bool enabled = shouldEnableInput();
|
||||
m_enabled = config.getBool("enabled", m_enabled);
|
||||
@ -161,18 +164,26 @@ void Voice::loadJson(Json const& config) {
|
||||
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();
|
||||
|
||||
m_threshold = config.getFloat("threshold", m_threshold);
|
||||
m_inputVolume = config.getFloat("inputVolume", m_inputVolume);
|
||||
m_outputVolume = config.getFloat("outputVolume", m_outputVolume);
|
||||
|
||||
if (change(m_inputMode, VoiceInputModeNames.getLeft(config.getString("inputMode", "PushToTalk"))))
|
||||
m_lastInputTime = 0;
|
||||
if (auto inputMode = config.optString("inputMode")) {
|
||||
if (change(m_inputMode, VoiceInputModeNames.getLeft(*inputMode)))
|
||||
m_lastInputTime = 0;
|
||||
}
|
||||
|
||||
if (change(m_channelMode, VoiceChannelModeNames.getLeft(config.getString("channelMode", "Mono"))))
|
||||
resetEncoder();
|
||||
if (auto channelMode = config.optString("channelMode")) {
|
||||
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(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();
|
||||
}
|
||||
|
||||
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) {
|
||||
out.setByteOrder(ByteOrder::LittleEndian);
|
||||
out.write<uint16_t>(VOICE_VERSION);
|
||||
@ -520,6 +551,7 @@ OpusEncoder* Voice::createEncoder(int channels) {
|
||||
|
||||
void Voice::resetEncoder() {
|
||||
int channels = encoderChannels();
|
||||
MutexLocker locker(m_threadMutex);
|
||||
m_encoder.reset(createEncoder(channels));
|
||||
opus_encoder_ctl(m_encoder.get(), OPUS_SET_BITRATE(channels == 2 ? 50000 : 24000));
|
||||
}
|
||||
|
@ -82,11 +82,13 @@ public:
|
||||
|
||||
int64_t lastReceiveTime = 0;
|
||||
int64_t lastPlayTime = 0;
|
||||
float smoothDb = -96.0f;
|
||||
Array<float, 10> dbHistory = Array<float, 10>::filled(0);
|
||||
|
||||
atomic<bool> muted = false;
|
||||
atomic<bool> playing = 0;
|
||||
atomic<float> decibelLevel = 0.0f;
|
||||
atomic<Array<float, 2>> channelVolumes = Array<float, 2>::filled(1.0f);
|
||||
atomic<float> decibelLevel = -96.0f;
|
||||
atomic<Array<float, 2>> channelVolumes = Array<float, 2>::filled(1);
|
||||
|
||||
unsigned int minimumPlaySamples = 4096;
|
||||
|
||||
@ -133,6 +135,7 @@ public:
|
||||
void update(PositionalAttenuationFunction positionalAttenuationFunction = {});
|
||||
|
||||
void setDeviceName(Maybe<String> device);
|
||||
StringList availableDevices();
|
||||
|
||||
int send(DataStreamBuffer& out, size_t budget);
|
||||
bool receive(SpeakerPtr speaker, std::string_view view);
|
||||
|
@ -6,17 +6,21 @@ namespace Star {
|
||||
LuaCallbacks LuaBindings::makeVoiceCallbacks(Voice* voice) {
|
||||
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> {
|
||||
List<Json> list;
|
||||
|
||||
for (auto& speaker : voice->speakers(onlyPlaying.value(true))) {
|
||||
list.append(JsonObject{
|
||||
{"speakerId", speaker->speakerId },
|
||||
{"entityId", speaker->entityId },
|
||||
{"name", speaker->name },
|
||||
{"playing", (bool)speaker->playing },
|
||||
{"muted", (bool)speaker->muted },
|
||||
{"loudness", (float)speaker->decibelLevel },
|
||||
{"speakerId", speaker->speakerId },
|
||||
{"entityId", speaker->entityId },
|
||||
{"name", speaker->name },
|
||||
{"playing", (bool)speaker->playing },
|
||||
{"muted", (bool)speaker->muted },
|
||||
{"decibels", (float)speaker->decibelLevel },
|
||||
{"smoothDecibels", (float)speaker->smoothDb },
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
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());
|
||||
}));
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user