voice fixes, add activation gain
This commit is contained in:
@@ -13,6 +13,8 @@ namespace
|
||||
constexpr int FOV_MAX = 110;
|
||||
constexpr int FOV_SLIDER_MAX = 100;
|
||||
static const int MAX_VOICE_VOLUME_PERCENT = 200;
|
||||
static const int MIN_VOICE_ACTIVATION_GAIN_PERCENT = 25;
|
||||
static const int MAX_VOICE_ACTIVATION_GAIN_PERCENT = 400;
|
||||
static const int MAX_DEVICE_LABELS = 64;
|
||||
|
||||
int ClampFov(int value)
|
||||
@@ -56,6 +58,7 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
[[maybe_unused]]
|
||||
static void SetControlY(UIScene &scene, UIControl &control, int y)
|
||||
{
|
||||
IggyName yName = scene.registerFastName(L"y");
|
||||
@@ -63,6 +66,7 @@ namespace
|
||||
control.UpdateControl();
|
||||
}
|
||||
|
||||
[[maybe_unused]]
|
||||
static void SetControlHeight(UIScene &scene, UIControl &control, int height)
|
||||
{
|
||||
IggyName heightName = scene.registerFastName(L"height");
|
||||
@@ -170,12 +174,14 @@ void UIScene_SettingsGraphicsMenu::initVoiceChatSliders()
|
||||
vcm.init();
|
||||
}
|
||||
|
||||
// Reuse graphics checkboxes as voice mode switches.
|
||||
const bool proximityEnabled = vcm.isProximityEnabled();
|
||||
const bool voiceActivationEnabled = vcm.getVoiceInputMode() == VoiceChatManager::VOICE_INPUT_VOICE_ACTIVATION;
|
||||
m_checkboxClouds.init(UIString(L"Proximity"), eControl_Clouds, proximityEnabled);
|
||||
m_checkboxBedrockFog.init(UIString(L"Voice Activate"), eControl_BedrockFog, !proximityEnabled);
|
||||
|
||||
m_checkboxBedrockFog.init(UIString(L"Voice Activation"), eControl_BedrockFog, voiceActivationEnabled);
|
||||
m_checkboxCustomSkinAnim.init(UIString(L"Select Input Device"), eControl_CustomSkinAnim, false);
|
||||
refreshVoiceDeviceCheckboxLabel();
|
||||
enforceVoiceModeSwitch(-1);
|
||||
applyVoiceModeFromCheckboxes();
|
||||
|
||||
WCHAR tempString[256];
|
||||
|
||||
@@ -191,88 +197,15 @@ void UIScene_SettingsGraphicsMenu::initVoiceChatSliders()
|
||||
swprintf(tempString, 256, L"Voice Chat Volume: %d%%", voiceVolume);
|
||||
m_sliderGamma.init(tempString, eControl_Gamma, 0, MAX_VOICE_VOLUME_PERCENT, voiceVolume);
|
||||
|
||||
vector<wstring> captureNames;
|
||||
vector<wstring> playbackNames;
|
||||
vcm.getCaptureDeviceNames(captureNames, true);
|
||||
vcm.getPlaybackDeviceNames(playbackNames, true);
|
||||
int voiceActivationGain = vcm.getVoiceActivationGainPercent();
|
||||
if (voiceActivationGain < MIN_VOICE_ACTIVATION_GAIN_PERCENT) voiceActivationGain = MIN_VOICE_ACTIVATION_GAIN_PERCENT;
|
||||
if (voiceActivationGain > MAX_VOICE_ACTIVATION_GAIN_PERCENT) voiceActivationGain = MAX_VOICE_ACTIVATION_GAIN_PERCENT;
|
||||
swprintf(tempString, 256, L"Voice Activation Gain: %d%%", voiceActivationGain);
|
||||
m_sliderFOV.init(tempString, eControl_FOV, MIN_VOICE_ACTIVATION_GAIN_PERCENT, MAX_VOICE_ACTIVATION_GAIN_PERCENT, voiceActivationGain);
|
||||
|
||||
wchar_t captureLabels[MAX_DEVICE_LABELS][256];
|
||||
wchar_t playbackLabels[MAX_DEVICE_LABELS][256];
|
||||
int captureCount = 0;
|
||||
int playbackCount = 0;
|
||||
BuildDeviceLabels(captureNames, L"Input", captureLabels, captureCount);
|
||||
BuildDeviceLabels(playbackNames, L"Output", playbackLabels, playbackCount);
|
||||
|
||||
int captureIndex = vcm.getSelectedCaptureMenuIndex();
|
||||
int playbackIndex = vcm.getSelectedPlaybackMenuIndex();
|
||||
if (captureIndex < 0) captureIndex = 0;
|
||||
if (captureIndex >= captureCount) captureIndex = 0;
|
||||
if (playbackIndex < 0) playbackIndex = 0;
|
||||
if (playbackIndex >= playbackCount) playbackIndex = 0;
|
||||
|
||||
m_sliderFOV.setAllPossibleLabels(captureCount, captureLabels);
|
||||
m_sliderFOV.init(captureLabels[captureIndex], eControl_FOV, 0, captureCount - 1, captureIndex);
|
||||
|
||||
m_sliderInterfaceOpacity.setAllPossibleLabels(playbackCount, playbackLabels);
|
||||
m_sliderInterfaceOpacity.init(playbackLabels[playbackIndex], eControl_InterfaceOpacity, 0, playbackCount - 1, playbackIndex);
|
||||
refreshVoiceDeviceSlider();
|
||||
|
||||
doHorizontalResizeCheck();
|
||||
|
||||
// Collapse the unused third checkbox row once, without any persistent layout workaround.
|
||||
m_checkboxBedrockFog.UpdateControl();
|
||||
m_checkboxCustomSkinAnim.UpdateControl();
|
||||
m_sliderRenderDistance.UpdateControl();
|
||||
m_sliderGamma.UpdateControl();
|
||||
m_sliderFOV.UpdateControl();
|
||||
m_sliderInterfaceOpacity.UpdateControl();
|
||||
|
||||
int collapseAmount = m_checkboxCustomSkinAnim.getYPos() - m_checkboxBedrockFog.getYPos();
|
||||
if (collapseAmount > 0 && collapseAmount < 128)
|
||||
{
|
||||
SetControlY(*this, m_sliderRenderDistance, m_sliderRenderDistance.getYPos() - collapseAmount);
|
||||
SetControlY(*this, m_sliderGamma, m_sliderGamma.getYPos() - collapseAmount);
|
||||
SetControlY(*this, m_sliderFOV, m_sliderFOV.getYPos() - collapseAmount);
|
||||
SetControlY(*this, m_sliderInterfaceOpacity, m_sliderInterfaceOpacity.getYPos() - collapseAmount);
|
||||
}
|
||||
|
||||
// Match the panel height to the collapsed content once.
|
||||
m_sliderInterfaceOpacity.UpdateControl();
|
||||
IggyValuePath *root = IggyPlayerRootPath(getMovie());
|
||||
static const char *kPanelCandidates[] =
|
||||
{
|
||||
"MainPanel",
|
||||
"Panel",
|
||||
"BackgroundPanel",
|
||||
"Background",
|
||||
"Dialog",
|
||||
"Window"
|
||||
};
|
||||
const int bottomPadding = 16;
|
||||
for (int i = 0; i < static_cast<int>(sizeof(kPanelCandidates) / sizeof(kPanelCandidates[0])); ++i)
|
||||
{
|
||||
UIControl panel;
|
||||
if (!panel.setupControl(this, root, kPanelCandidates[i]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
panel.UpdateControl();
|
||||
if (panel.getHeight() <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const int desiredHeight = (m_sliderInterfaceOpacity.getYPos() + m_sliderInterfaceOpacity.getHeight() + bottomPadding) - panel.getYPos();
|
||||
if (desiredHeight > 0)
|
||||
{
|
||||
SetControlHeight(*this, panel, desiredHeight);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Hide the unused checkbox without movie recenter/reflow.
|
||||
m_checkboxCustomSkinAnim.setVisible(false);
|
||||
m_checkboxCustomSkinAnim.setHidden(true);
|
||||
}
|
||||
|
||||
UIScene_SettingsGraphicsMenu::~UIScene_SettingsGraphicsMenu()
|
||||
@@ -282,15 +215,6 @@ UIScene_SettingsGraphicsMenu::~UIScene_SettingsGraphicsMenu()
|
||||
void UIScene_SettingsGraphicsMenu::tick()
|
||||
{
|
||||
UIScene::tick();
|
||||
|
||||
if (!m_bVoiceChatMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep mode switches mutually exclusive even if UI events arrive out-of-order.
|
||||
enforceVoiceModeSwitch(-1);
|
||||
applyVoiceModeFromCheckboxes();
|
||||
}
|
||||
|
||||
wstring UIScene_SettingsGraphicsMenu::getMoviePath()
|
||||
@@ -439,17 +363,21 @@ void UIScene_SettingsGraphicsMenu::handleCheckboxToggled(F64 controlId, bool sel
|
||||
{
|
||||
case eControl_Clouds:
|
||||
m_checkboxClouds.setChecked(selected);
|
||||
m_checkboxBedrockFog.setChecked(!selected);
|
||||
enforceVoiceModeSwitch(eControl_Clouds);
|
||||
applyVoiceModeFromCheckboxes();
|
||||
break;
|
||||
|
||||
case eControl_BedrockFog:
|
||||
m_checkboxBedrockFog.setChecked(selected);
|
||||
m_checkboxClouds.setChecked(!selected);
|
||||
enforceVoiceModeSwitch(eControl_BedrockFog);
|
||||
applyVoiceModeFromCheckboxes();
|
||||
break;
|
||||
|
||||
case eControl_CustomSkinAnim:
|
||||
m_checkboxCustomSkinAnim.setChecked(selected);
|
||||
refreshVoiceDeviceCheckboxLabel();
|
||||
refreshVoiceDeviceSlider();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,37 +402,35 @@ void UIScene_SettingsGraphicsMenu::handleVoiceSliderMove(int controlId, int valu
|
||||
break;
|
||||
|
||||
case eControl_FOV:
|
||||
if (vcm.selectCaptureMenuIndex(value))
|
||||
{
|
||||
vector<wstring> names;
|
||||
vcm.getCaptureDeviceNames(names, true);
|
||||
if (value >= 0 && value < static_cast<int>(names.size()))
|
||||
{
|
||||
swprintf(tempString, 256, L"Input: %ls", names[value].c_str());
|
||||
m_sliderFOV.setLabel(tempString);
|
||||
}
|
||||
}
|
||||
vcm.setVoiceActivationGainPercent(value);
|
||||
m_sliderFOV.handleSliderMove(value);
|
||||
swprintf(tempString, 256, L"Voice Activation Gain: %d%%", value);
|
||||
m_sliderFOV.setLabel(tempString);
|
||||
break;
|
||||
|
||||
case eControl_InterfaceOpacity:
|
||||
if (vcm.selectPlaybackMenuIndex(value))
|
||||
if (m_checkboxCustomSkinAnim.IsChecked())
|
||||
{
|
||||
vector<wstring> names;
|
||||
vcm.getPlaybackDeviceNames(names, true);
|
||||
if (value >= 0 && value < static_cast<int>(names.size()))
|
||||
if (vcm.selectPlaybackMenuIndex(value))
|
||||
{
|
||||
swprintf(tempString, 256, L"Output: %ls", names[value].c_str());
|
||||
m_sliderInterfaceOpacity.setLabel(tempString);
|
||||
refreshVoiceDeviceSlider();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (vcm.selectCaptureMenuIndex(value))
|
||||
{
|
||||
refreshVoiceDeviceSlider();
|
||||
}
|
||||
}
|
||||
m_sliderInterfaceOpacity.handleSliderMove(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void UIScene_SettingsGraphicsMenu::enforceVoiceModeSwitch(int preferredControl)
|
||||
{
|
||||
(void)preferredControl;
|
||||
|
||||
if (!m_bVoiceChatMode)
|
||||
{
|
||||
return;
|
||||
@@ -513,18 +439,9 @@ void UIScene_SettingsGraphicsMenu::enforceVoiceModeSwitch(int preferredControl)
|
||||
bool proximity = m_checkboxClouds.IsChecked();
|
||||
bool voiceActivate = m_checkboxBedrockFog.IsChecked();
|
||||
|
||||
if (proximity == voiceActivate)
|
||||
if (voiceActivate && !proximity)
|
||||
{
|
||||
if (preferredControl == eControl_BedrockFog)
|
||||
{
|
||||
proximity = false;
|
||||
voiceActivate = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
proximity = true;
|
||||
voiceActivate = false;
|
||||
}
|
||||
proximity = true;
|
||||
}
|
||||
|
||||
m_checkboxClouds.setChecked(proximity);
|
||||
@@ -537,7 +454,40 @@ void UIScene_SettingsGraphicsMenu::applyVoiceModeFromCheckboxes()
|
||||
enforceVoiceModeSwitch(-1);
|
||||
const bool proximityEnabled = m_checkboxClouds.IsChecked();
|
||||
vcm.setProximityEnabled(proximityEnabled);
|
||||
vcm.setVoiceInputMode(!proximityEnabled
|
||||
vcm.setVoiceInputMode(m_checkboxBedrockFog.IsChecked()
|
||||
? VoiceChatManager::VOICE_INPUT_VOICE_ACTIVATION
|
||||
: VoiceChatManager::VOICE_INPUT_PUSH_TO_TALK);
|
||||
}
|
||||
|
||||
void UIScene_SettingsGraphicsMenu::refreshVoiceDeviceCheckboxLabel()
|
||||
{
|
||||
const bool usePlaybackDevice = m_checkboxCustomSkinAnim.IsChecked();
|
||||
m_checkboxCustomSkinAnim.setLabel(usePlaybackDevice ? UIString(L"Select Output Device") : UIString(L"Select Input Device"), true, true);
|
||||
}
|
||||
|
||||
void UIScene_SettingsGraphicsMenu::refreshVoiceDeviceSlider()
|
||||
{
|
||||
VoiceChatManager &vcm = VoiceChatManager::getInstance();
|
||||
const bool usePlaybackDevice = m_checkboxCustomSkinAnim.IsChecked();
|
||||
|
||||
vector<wstring> deviceNames;
|
||||
if (usePlaybackDevice)
|
||||
{
|
||||
vcm.getPlaybackDeviceNames(deviceNames, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
vcm.getCaptureDeviceNames(deviceNames, true);
|
||||
}
|
||||
|
||||
wchar_t deviceLabels[MAX_DEVICE_LABELS][256];
|
||||
int deviceCount = 0;
|
||||
BuildDeviceLabels(deviceNames, usePlaybackDevice ? L"Output" : L"Input", deviceLabels, deviceCount);
|
||||
|
||||
int selectedIndex = usePlaybackDevice ? vcm.getSelectedPlaybackMenuIndex() : vcm.getSelectedCaptureMenuIndex();
|
||||
if (selectedIndex < 0) selectedIndex = 0;
|
||||
if (selectedIndex >= deviceCount) selectedIndex = 0;
|
||||
|
||||
m_sliderInterfaceOpacity.setAllPossibleLabels(deviceCount, deviceLabels);
|
||||
m_sliderInterfaceOpacity.init(deviceLabels[selectedIndex], eControl_InterfaceOpacity, 0, deviceCount - 1, selectedIndex);
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ private:
|
||||
void handleVoiceSliderMove(int controlId, int value);
|
||||
void enforceVoiceModeSwitch(int preferredControl);
|
||||
void applyVoiceModeFromCheckboxes();
|
||||
void refreshVoiceDeviceCheckboxLabel();
|
||||
void refreshVoiceDeviceSlider();
|
||||
public:
|
||||
UIScene_SettingsGraphicsMenu(int iPad, void *initData, UILayer *parentLayer);
|
||||
virtual ~UIScene_SettingsGraphicsMenu();
|
||||
|
||||
@@ -69,8 +69,9 @@ void UIScene_SettingsOptionsMenu::setupVoiceChatMenu()
|
||||
m_bMashUpWorldsUnhideOption = false;
|
||||
|
||||
const bool proximityEnabled = vcm.isProximityEnabled();
|
||||
const bool voiceActivationEnabled = vcm.getVoiceInputMode() == VoiceChatManager::VOICE_INPUT_VOICE_ACTIVATION;
|
||||
m_checkboxViewBob.init(UIString(L"Proximity"), eControl_ViewBob, proximityEnabled);
|
||||
m_checkboxShowHints.init(UIString(L"Voice Activate"), eControl_ShowHints, !proximityEnabled);
|
||||
m_checkboxShowHints.init(UIString(L"Voice Activation"), eControl_ShowHints, voiceActivationEnabled);
|
||||
|
||||
removeControl(&m_checkboxShowTooltips, true);
|
||||
removeControl(&m_checkboxInGameGamertags, true);
|
||||
@@ -247,6 +248,8 @@ void UIScene_SettingsOptionsMenu::setVoiceDifficultyLabel()
|
||||
|
||||
void UIScene_SettingsOptionsMenu::enforceVoiceModeSwitch(int preferredControl)
|
||||
{
|
||||
(void)preferredControl;
|
||||
|
||||
if (!m_bVoiceChatMode)
|
||||
{
|
||||
return;
|
||||
@@ -255,18 +258,9 @@ void UIScene_SettingsOptionsMenu::enforceVoiceModeSwitch(int preferredControl)
|
||||
bool proximity = m_checkboxViewBob.IsChecked();
|
||||
bool voiceActivate = m_checkboxShowHints.IsChecked();
|
||||
|
||||
if (proximity == voiceActivate)
|
||||
if (voiceActivate && !proximity)
|
||||
{
|
||||
if (preferredControl == eControl_ShowHints)
|
||||
{
|
||||
proximity = false;
|
||||
voiceActivate = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
proximity = true;
|
||||
voiceActivate = false;
|
||||
}
|
||||
proximity = true;
|
||||
}
|
||||
|
||||
m_checkboxViewBob.setChecked(proximity);
|
||||
@@ -403,20 +397,12 @@ void UIScene_SettingsOptionsMenu::handlePress(F64 controlId, F64 childId)
|
||||
case eControl_ViewBob:
|
||||
if (m_bVoiceChatMode)
|
||||
{
|
||||
m_checkboxViewBob.setChecked(true);
|
||||
m_checkboxShowHints.setChecked(false);
|
||||
enforceVoiceModeSwitch(eControl_ViewBob);
|
||||
setGameSettings();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case eControl_ShowHints:
|
||||
if (m_bVoiceChatMode)
|
||||
{
|
||||
m_checkboxViewBob.setChecked(false);
|
||||
m_checkboxShowHints.setChecked(true);
|
||||
enforceVoiceModeSwitch(eControl_ShowHints);
|
||||
setGameSettings();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@@ -446,13 +432,11 @@ void UIScene_SettingsOptionsMenu::handleCheckboxToggled(F64 controlId, bool sele
|
||||
{
|
||||
case eControl_ViewBob:
|
||||
m_checkboxViewBob.setChecked(selected);
|
||||
m_checkboxShowHints.setChecked(!selected);
|
||||
enforceVoiceModeSwitch(eControl_ViewBob);
|
||||
setGameSettings();
|
||||
break;
|
||||
case eControl_ShowHints:
|
||||
m_checkboxShowHints.setChecked(selected);
|
||||
m_checkboxViewBob.setChecked(!selected);
|
||||
enforceVoiceModeSwitch(eControl_ShowHints);
|
||||
setGameSettings();
|
||||
break;
|
||||
@@ -534,7 +518,7 @@ void UIScene_SettingsOptionsMenu::setGameSettings()
|
||||
VoiceChatManager &vcm = VoiceChatManager::getInstance();
|
||||
const bool proximityEnabled = m_checkboxViewBob.IsChecked();
|
||||
vcm.setProximityEnabled(proximityEnabled);
|
||||
vcm.setVoiceInputMode(!proximityEnabled
|
||||
vcm.setVoiceInputMode(m_checkboxShowHints.IsChecked()
|
||||
? VoiceChatManager::VOICE_INPUT_VOICE_ACTIVATION
|
||||
: VoiceChatManager::VOICE_INPUT_PUSH_TO_TALK);
|
||||
return;
|
||||
|
||||
@@ -8,12 +8,25 @@
|
||||
#include "..\Minecraft.World\Connection.h"
|
||||
#include "..\Minecraft.World\System.h"
|
||||
#include <cstring>
|
||||
#include <cmath>
|
||||
|
||||
// Include miniaudio
|
||||
#include "Common\Audio\miniaudio.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
static const int MIN_VOICE_ACTIVATION_GAIN_PERCENT = 25;
|
||||
static const int MAX_VOICE_ACTIVATION_GAIN_PERCENT = 400;
|
||||
static const int VOICE_ACTIVATION_OPEN_PEAK = 900;
|
||||
static const int VOICE_ACTIVATION_OPEN_RMS = 220;
|
||||
static const int VOICE_ACTIVATION_CLOSE_PEAK = 600;
|
||||
static const int VOICE_ACTIVATION_CLOSE_RMS = 160;
|
||||
static const int VOICE_ACTIVATION_HOLD_FRAMES = 10;
|
||||
static const int PUSH_TO_TALK_GATE_PEAK = 450;
|
||||
static const int PUSH_TO_TALK_GATE_RMS = 120;
|
||||
static const int PUSH_TO_TALK_HOLD_FRAMES = 4;
|
||||
static const float CAPTURE_HIGHPASS_ALPHA = 0.995f;
|
||||
|
||||
static void RefreshDeviceLists(
|
||||
std::vector<std::wstring> &playbackDevices,
|
||||
std::vector<std::wstring> &captureDevices,
|
||||
@@ -44,6 +57,48 @@ namespace
|
||||
if (selectedIndex >= static_cast<int>(count)) return nullptr;
|
||||
return &pInfos[selectedIndex].id;
|
||||
}
|
||||
|
||||
static int SampleMagnitude(int sample)
|
||||
{
|
||||
return sample >= 0 ? sample : -sample;
|
||||
}
|
||||
|
||||
static short ClampSampleToShort(int sample)
|
||||
{
|
||||
if (sample > 32767) return 32767;
|
||||
if (sample < -32768) return -32768;
|
||||
return static_cast<short>(sample);
|
||||
}
|
||||
|
||||
struct VoiceFrameMetrics
|
||||
{
|
||||
int peak;
|
||||
int rms;
|
||||
};
|
||||
|
||||
static VoiceFrameMetrics MeasureVoiceFrame(const short *samples, int sampleCount, int gainPercent)
|
||||
{
|
||||
VoiceFrameMetrics metrics = { 0, 0 };
|
||||
if (samples == nullptr || sampleCount <= 0)
|
||||
{
|
||||
return metrics;
|
||||
}
|
||||
|
||||
long long energy = 0;
|
||||
for (int i = 0; i < sampleCount; ++i)
|
||||
{
|
||||
int sample = (static_cast<int>(samples[i]) * gainPercent) / 100;
|
||||
const int magnitude = SampleMagnitude(sample);
|
||||
if (magnitude > metrics.peak)
|
||||
{
|
||||
metrics.peak = magnitude;
|
||||
}
|
||||
energy += static_cast<long long>(magnitude) * static_cast<long long>(magnitude);
|
||||
}
|
||||
|
||||
metrics.rms = static_cast<int>(std::sqrt(static_cast<double>(energy) / static_cast<double>(sampleCount)));
|
||||
return metrics;
|
||||
}
|
||||
}
|
||||
|
||||
VoiceChatManager &VoiceChatManager::getInstance()
|
||||
@@ -63,12 +118,16 @@ VoiceChatManager::VoiceChatManager()
|
||||
, m_isPushToTalkActive(false)
|
||||
, m_voiceInputMode(VOICE_INPUT_PUSH_TO_TALK)
|
||||
, m_proximityEnabled(true)
|
||||
, m_voiceThreshold(300)
|
||||
, m_selectedCaptureDevice(-1)
|
||||
, m_selectedPlaybackDevice(-1)
|
||||
, m_localSequence(0)
|
||||
, m_micVolumePercent(100)
|
||||
, m_voiceChatVolumePercent(100)
|
||||
, m_voiceActivationGainPercent(100)
|
||||
, m_voiceActivationHoldFrames(0)
|
||||
, m_pushToTalkHoldFrames(0)
|
||||
, m_captureFilterLastInput(0.0f)
|
||||
, m_captureFilterLastOutput(0.0f)
|
||||
{
|
||||
memset(m_captureBuffer, 0, sizeof(m_captureBuffer));
|
||||
InitializeCriticalSection(&m_captureLock);
|
||||
@@ -165,6 +224,14 @@ void VoiceChatManager::init()
|
||||
{
|
||||
if (m_initialized) return;
|
||||
|
||||
m_captureWritePos = 0;
|
||||
m_captureReadPos = 0;
|
||||
m_voiceActivationHoldFrames = 0;
|
||||
m_pushToTalkHoldFrames = 0;
|
||||
m_captureFilterLastInput = 0.0f;
|
||||
m_captureFilterLastOutput = 0.0f;
|
||||
memset(m_captureBuffer, 0, sizeof(m_captureBuffer));
|
||||
|
||||
m_audioContext = new ma_context();
|
||||
if (ma_context_init(nullptr, 0, nullptr, m_audioContext) != MA_SUCCESS)
|
||||
{
|
||||
@@ -288,6 +355,10 @@ void VoiceChatManager::shutdown()
|
||||
}
|
||||
m_captureDevices.clear();
|
||||
m_playbackDevices.clear();
|
||||
m_voiceActivationHoldFrames = 0;
|
||||
m_pushToTalkHoldFrames = 0;
|
||||
m_captureFilterLastInput = 0.0f;
|
||||
m_captureFilterLastOutput = 0.0f;
|
||||
|
||||
m_initialized = false;
|
||||
app.DebugPrintf("VoiceChat: Shutdown\n");
|
||||
@@ -295,7 +366,14 @@ void VoiceChatManager::shutdown()
|
||||
|
||||
void VoiceChatManager::setVoiceInputMode(VoiceInputMode mode)
|
||||
{
|
||||
if (m_voiceInputMode == mode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_voiceInputMode = mode;
|
||||
m_voiceActivationHoldFrames = 0;
|
||||
m_pushToTalkHoldFrames = 0;
|
||||
app.DebugPrintf("VoiceChat: Input mode set to %s\n", m_voiceInputMode == VOICE_INPUT_PUSH_TO_TALK ? "PTT" : "VoiceActivation");
|
||||
}
|
||||
|
||||
@@ -317,6 +395,17 @@ void VoiceChatManager::toggleProximityEnabled()
|
||||
app.DebugPrintf("VoiceChat: Proximity %s\n", m_proximityEnabled ? "enabled" : "disabled");
|
||||
}
|
||||
|
||||
void VoiceChatManager::setProximityEnabled(bool enabled)
|
||||
{
|
||||
if (m_proximityEnabled == enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_proximityEnabled = enabled;
|
||||
app.DebugPrintf("VoiceChat: Proximity %s\n", m_proximityEnabled ? "enabled" : "disabled");
|
||||
}
|
||||
|
||||
bool VoiceChatManager::cycleCaptureDevice(int dir)
|
||||
{
|
||||
if (!m_initialized) init();
|
||||
@@ -456,26 +545,64 @@ void VoiceChatManager::tick(Minecraft *minecraft)
|
||||
{
|
||||
int sample = m_captureBuffer[m_captureReadPos];
|
||||
sample = (sample * m_micVolumePercent) / 100;
|
||||
if (sample > 32767) sample = 32767;
|
||||
if (sample < -32768) sample = -32768;
|
||||
frameBuffer[i] = (short)sample;
|
||||
|
||||
// Strip DC bias and low-frequency rumble so we do not transmit a bed of hiss/hum when the mic is idle.
|
||||
const float filteredSample =
|
||||
static_cast<float>(sample) - m_captureFilterLastInput + (CAPTURE_HIGHPASS_ALPHA * m_captureFilterLastOutput);
|
||||
m_captureFilterLastInput = static_cast<float>(sample);
|
||||
m_captureFilterLastOutput = filteredSample;
|
||||
|
||||
frameBuffer[i] = ClampSampleToShort(static_cast<int>(filteredSample));
|
||||
m_captureReadPos = (m_captureReadPos + 1) % CAPTURE_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
const VoiceFrameMetrics pushToTalkMetrics = MeasureVoiceFrame(frameBuffer, FRAMES_PER_PACKET, 100);
|
||||
bool pushToTalkSpeechDetected =
|
||||
pushToTalkMetrics.peak >= PUSH_TO_TALK_GATE_PEAK ||
|
||||
pushToTalkMetrics.rms >= PUSH_TO_TALK_GATE_RMS;
|
||||
|
||||
if (m_isPushToTalkActive)
|
||||
{
|
||||
if (pushToTalkSpeechDetected)
|
||||
{
|
||||
m_pushToTalkHoldFrames = PUSH_TO_TALK_HOLD_FRAMES;
|
||||
}
|
||||
else if (m_pushToTalkHoldFrames > 0)
|
||||
{
|
||||
--m_pushToTalkHoldFrames;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_pushToTalkHoldFrames = 0;
|
||||
}
|
||||
|
||||
const VoiceFrameMetrics voiceActivationMetrics =
|
||||
MeasureVoiceFrame(frameBuffer, FRAMES_PER_PACKET, m_voiceActivationGainPercent);
|
||||
const bool useCloseThresholds = m_voiceActivationHoldFrames > 0;
|
||||
const int requiredPeak = useCloseThresholds ? VOICE_ACTIVATION_CLOSE_PEAK : VOICE_ACTIVATION_OPEN_PEAK;
|
||||
const int requiredRms = useCloseThresholds ? VOICE_ACTIVATION_CLOSE_RMS : VOICE_ACTIVATION_OPEN_RMS;
|
||||
const bool voiceActivationDetected =
|
||||
voiceActivationMetrics.peak >= requiredPeak ||
|
||||
voiceActivationMetrics.rms >= requiredRms;
|
||||
|
||||
if (voiceActivationDetected)
|
||||
{
|
||||
m_voiceActivationHoldFrames = VOICE_ACTIVATION_HOLD_FRAMES;
|
||||
}
|
||||
else if (m_voiceActivationHoldFrames > 0)
|
||||
{
|
||||
--m_voiceActivationHoldFrames;
|
||||
}
|
||||
|
||||
bool shouldSend = false;
|
||||
if (m_voiceInputMode == VOICE_INPUT_PUSH_TO_TALK)
|
||||
{
|
||||
shouldSend = m_isPushToTalkActive;
|
||||
shouldSend = m_isPushToTalkActive && (pushToTalkSpeechDetected || m_pushToTalkHoldFrames > 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
int peak = 0;
|
||||
for (int i = 0; i < FRAMES_PER_PACKET; ++i)
|
||||
{
|
||||
int sample = abs((int)frameBuffer[i]);
|
||||
if (sample > peak) peak = sample;
|
||||
}
|
||||
shouldSend = peak >= m_voiceThreshold;
|
||||
shouldSend = m_voiceActivationHoldFrames > 0;
|
||||
}
|
||||
|
||||
if (shouldSend)
|
||||
@@ -630,3 +757,10 @@ void VoiceChatManager::setVoiceChatVolumePercent(int percent)
|
||||
if (percent > 200) percent = 200;
|
||||
m_voiceChatVolumePercent = percent;
|
||||
}
|
||||
|
||||
void VoiceChatManager::setVoiceActivationGainPercent(int percent)
|
||||
{
|
||||
if (percent < MIN_VOICE_ACTIVATION_GAIN_PERCENT) percent = MIN_VOICE_ACTIVATION_GAIN_PERCENT;
|
||||
if (percent > MAX_VOICE_ACTIVATION_GAIN_PERCENT) percent = MAX_VOICE_ACTIVATION_GAIN_PERCENT;
|
||||
m_voiceActivationGainPercent = percent;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,14 @@ public:
|
||||
void setListenerPosition(double x, double y, double z);
|
||||
|
||||
// Push-to-talk control
|
||||
void setPushToTalk(bool active) { m_isPushToTalkActive = active; }
|
||||
void setPushToTalk(bool active)
|
||||
{
|
||||
m_isPushToTalkActive = active;
|
||||
if (!active)
|
||||
{
|
||||
m_pushToTalkHoldFrames = 0;
|
||||
}
|
||||
}
|
||||
bool isPushToTalkActive() const { return m_isPushToTalkActive; }
|
||||
|
||||
// Voice input mode controls
|
||||
@@ -43,7 +50,7 @@ public:
|
||||
void toggleVoiceInputMode();
|
||||
|
||||
// Proximity chat controls
|
||||
void setProximityEnabled(bool enabled) { m_proximityEnabled = enabled; }
|
||||
void setProximityEnabled(bool enabled);
|
||||
bool isProximityEnabled() const { return m_proximityEnabled; }
|
||||
void toggleProximityEnabled();
|
||||
|
||||
@@ -64,6 +71,8 @@ public:
|
||||
int getMicVolumePercent() const { return m_micVolumePercent; }
|
||||
void setVoiceChatVolumePercent(int percent);
|
||||
int getVoiceChatVolumePercent() const { return m_voiceChatVolumePercent; }
|
||||
void setVoiceActivationGainPercent(int percent);
|
||||
int getVoiceActivationGainPercent() const { return m_voiceActivationGainPercent; }
|
||||
|
||||
bool isInitialized() const { return m_initialized; }
|
||||
|
||||
@@ -92,7 +101,6 @@ private:
|
||||
bool m_isPushToTalkActive;
|
||||
VoiceInputMode m_voiceInputMode;
|
||||
bool m_proximityEnabled;
|
||||
int m_voiceThreshold;
|
||||
std::vector<std::wstring> m_captureDevices;
|
||||
std::vector<std::wstring> m_playbackDevices;
|
||||
int m_selectedCaptureDevice;
|
||||
@@ -100,6 +108,11 @@ private:
|
||||
unsigned short m_localSequence;
|
||||
int m_micVolumePercent;
|
||||
int m_voiceChatVolumePercent;
|
||||
int m_voiceActivationGainPercent;
|
||||
int m_voiceActivationHoldFrames;
|
||||
int m_pushToTalkHoldFrames;
|
||||
float m_captureFilterLastInput;
|
||||
float m_captureFilterLastOutput;
|
||||
|
||||
// Capture buffer (ring buffer for PCM data captured from mic)
|
||||
static const int CAPTURE_BUFFER_SIZE = 96000; // 2 seconds at 48kHz mono
|
||||
|
||||
Reference in New Issue
Block a user