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.
|
-- 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
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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 },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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());
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user