feat(audio): add custom sound volume slider
This commit is contained in:
@@ -187,6 +187,8 @@ public class Configuration : IPluginConfiguration
|
||||
public bool CollapseKeepUniqueLinks;
|
||||
public bool SymbolPickerEnabled = true;
|
||||
public bool PlaySounds = true;
|
||||
// AUDIO-1: playback volume (0-1) for the three bundled custom sounds.
|
||||
public float CustomSoundVolume = 0.5f;
|
||||
|
||||
// Toast when a tell the user sent could not be delivered.
|
||||
public bool NotifyFailedTell = true;
|
||||
@@ -285,6 +287,7 @@ public class Configuration : IPluginConfiguration
|
||||
CollapseKeepUniqueLinks = other.CollapseKeepUniqueLinks;
|
||||
SymbolPickerEnabled = other.SymbolPickerEnabled;
|
||||
PlaySounds = other.PlaySounds;
|
||||
CustomSoundVolume = other.CustomSoundVolume;
|
||||
NotifyFailedTell = other.NotifyFailedTell;
|
||||
KeepInputFocus = other.KeepInputFocus;
|
||||
MaxLinesToRender = other.MaxLinesToRender;
|
||||
|
||||
@@ -9,8 +9,9 @@ namespace HellionChat.Integrations;
|
||||
// WaveOutEvent/WinMM is the correct backend for FFXIV on Wine: it works
|
||||
// without Media Foundation (which Wine does not support for MP3/AAC).
|
||||
//
|
||||
// Volume is fixed at 0.8. No per-user slider in this iteration so we can
|
||||
// ship quickly and gather feedback before adding UX complexity.
|
||||
// Playback volume comes from Configuration.CustomSoundVolume via the Play
|
||||
// parameter, clamped to [0,1]. The 16 game sounds are unaffected — they go
|
||||
// through UIGlobals.PlaySoundEffect, which the plugin cannot scale.
|
||||
internal sealed class CustomAudioPlayer : IDisposable
|
||||
{
|
||||
// Sound bytes are read once at construction so each Play() wraps a fresh
|
||||
@@ -55,7 +56,7 @@ internal sealed class CustomAudioPlayer : IDisposable
|
||||
// customIndex is 1, 2, or 3, matching the sound file suffix.
|
||||
// Stops any currently playing sound before starting the new one.
|
||||
// NAudio playback runs on its own thread; this method returns immediately.
|
||||
public void Play(int customIndex)
|
||||
public void Play(int customIndex, float volume)
|
||||
{
|
||||
if (customIndex < 1 || customIndex > 3)
|
||||
{
|
||||
@@ -90,7 +91,10 @@ internal sealed class CustomAudioPlayer : IDisposable
|
||||
// must be set after Init, otherwise waveOutSetVolume fails with
|
||||
// InvalidHandle.
|
||||
_outputDevice.Init(_reader);
|
||||
_outputDevice.Volume = 0.8f;
|
||||
// AUDIO-1: volume comes from Configuration.CustomSoundVolume.
|
||||
// Clamp here too — a hand-edited config could carry an
|
||||
// out-of-range value, and WaveOutEvent.Volume rejects those.
|
||||
_outputDevice.Volume = Math.Clamp(volume, 0f, 1f);
|
||||
_outputDevice.Play();
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -380,7 +380,7 @@ internal class MessageManager : IAsyncDisposable
|
||||
{
|
||||
// Custom bundled sounds (ids 17-19) go through NAudio WaveOutEvent.
|
||||
// NAudio manages its own playback thread, so no framework marshalling needed.
|
||||
Plugin.CustomAudioPlayer.Play((int)soundId - 16);
|
||||
Plugin.CustomAudioPlayer.Play((int)soundId - 16, Plugin.Config.CustomSoundVolume);
|
||||
}
|
||||
// soundId == 0 (hand-edited config) falls through: plays nothing.
|
||||
}
|
||||
|
||||
+30
@@ -469,4 +469,34 @@ internal class HellionStrings
|
||||
internal static string ChatLog_ScrollToBottom_Tooltip => Get(nameof(ChatLog_ScrollToBottom_Tooltip));
|
||||
internal static string ChatLog_Insert_MapFlag => Get(nameof(ChatLog_Insert_MapFlag));
|
||||
internal static string ChatLog_Insert_ItemLink => Get(nameof(ChatLog_Insert_ItemLink));
|
||||
|
||||
// v1.5.6: per-tab regex filter
|
||||
internal static string Settings_Tabs_MessageRegex_Name => Get(nameof(Settings_Tabs_MessageRegex_Name));
|
||||
internal static string Settings_Tabs_MessageRegex_Description => Get(nameof(Settings_Tabs_MessageRegex_Description));
|
||||
internal static string Settings_Tabs_MessageRegex_Invalid => Get(nameof(Settings_Tabs_MessageRegex_Invalid));
|
||||
|
||||
// v1.5.6: plugin-disclosure warning
|
||||
internal static string Settings_Chat_NotifyPluginDisclosure_Name => Get(nameof(Settings_Chat_NotifyPluginDisclosure_Name));
|
||||
internal static string Settings_Chat_NotifyPluginDisclosure_Description => Get(nameof(Settings_Chat_NotifyPluginDisclosure_Description));
|
||||
internal static string ChatInput_PluginDisclosure_Warning => Get(nameof(ChatInput_PluginDisclosure_Warning));
|
||||
|
||||
// v1.5.6: world suffix + name format display options
|
||||
internal static string Settings_Chat_WorldSuffix_Name => Get(nameof(Settings_Chat_WorldSuffix_Name));
|
||||
internal static string Settings_Chat_WorldSuffix_Description => Get(nameof(Settings_Chat_WorldSuffix_Description));
|
||||
internal static string Settings_Chat_NameForm_Name => Get(nameof(Settings_Chat_NameForm_Name));
|
||||
internal static string Settings_Chat_NameForm_Description => Get(nameof(Settings_Chat_NameForm_Description));
|
||||
internal static string NameDisplay_WorldSuffix_Never => Get(nameof(NameDisplay_WorldSuffix_Never));
|
||||
internal static string NameDisplay_WorldSuffix_OtherWorldOnly => Get(nameof(NameDisplay_WorldSuffix_OtherWorldOnly));
|
||||
internal static string NameDisplay_WorldSuffix_Always => Get(nameof(NameDisplay_WorldSuffix_Always));
|
||||
internal static string NameDisplay_NameForm_Full => Get(nameof(NameDisplay_NameForm_Full));
|
||||
internal static string NameDisplay_NameForm_FirstNameOnly => Get(nameof(NameDisplay_NameForm_FirstNameOnly));
|
||||
internal static string NameDisplay_NameForm_Initials => Get(nameof(NameDisplay_NameForm_Initials));
|
||||
|
||||
// v1.5.6: inactive window opacity
|
||||
internal static string Settings_ThemeAndLayout_WindowOpacityInactive_Name => Get(nameof(Settings_ThemeAndLayout_WindowOpacityInactive_Name));
|
||||
internal static string Settings_ThemeAndLayout_WindowOpacityInactive_Description => Get(nameof(Settings_ThemeAndLayout_WindowOpacityInactive_Description));
|
||||
|
||||
// v1.5.6: custom sound volume
|
||||
internal static string Settings_General_CustomSoundVolume_Name => Get(nameof(Settings_General_CustomSoundVolume_Name));
|
||||
internal static string Settings_General_CustomSoundVolume_Description => Get(nameof(Settings_General_CustomSoundVolume_Description));
|
||||
}
|
||||
|
||||
@@ -1090,4 +1090,74 @@
|
||||
<data name="ChatLog_Insert_ItemLink" xml:space="preserve">
|
||||
<value>Insert linked item <item></value>
|
||||
</data>
|
||||
|
||||
<!-- v1.5.6: per-tab regex filter -->
|
||||
<data name="Settings_Tabs_MessageRegex_Name" xml:space="preserve">
|
||||
<value>Message filter (regex)</value>
|
||||
</data>
|
||||
<data name="Settings_Tabs_MessageRegex_Description" xml:space="preserve">
|
||||
<value>Only keep messages whose text matches this regular expression. Applied on top of the channel filter. Leave empty to disable. Matching is case-insensitive.</value>
|
||||
</data>
|
||||
<data name="Settings_Tabs_MessageRegex_Invalid" xml:space="preserve">
|
||||
<value>Invalid pattern: {0}</value>
|
||||
</data>
|
||||
|
||||
<!-- v1.5.6: plugin-disclosure warning -->
|
||||
<data name="Settings_Chat_NotifyPluginDisclosure_Name" xml:space="preserve">
|
||||
<value>Warn before sending plugin-only symbols</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_NotifyPluginDisclosure_Description" xml:space="preserve">
|
||||
<value>Show a warning when a message you are about to send contains symbols that only display correctly for players running HellionChat or a similar plugin.</value>
|
||||
</data>
|
||||
<data name="ChatInput_PluginDisclosure_Warning" xml:space="preserve">
|
||||
<value>This message contains plugin-only symbols that other players may see as empty boxes. Press Enter again to send anyway.</value>
|
||||
</data>
|
||||
|
||||
<!-- v1.5.6: world suffix + name format display options -->
|
||||
<data name="Settings_Chat_WorldSuffix_Name" xml:space="preserve">
|
||||
<value>World suffix</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_WorldSuffix_Description" xml:space="preserve">
|
||||
<value>When to append the home world to a sender's name in the chat log.</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_NameForm_Name" xml:space="preserve">
|
||||
<value>Name format</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_NameForm_Description" xml:space="preserve">
|
||||
<value>How sender names are shown in the chat log. The full name is the default.</value>
|
||||
</data>
|
||||
<data name="NameDisplay_WorldSuffix_Never" xml:space="preserve">
|
||||
<value>Never</value>
|
||||
</data>
|
||||
<data name="NameDisplay_WorldSuffix_OtherWorldOnly" xml:space="preserve">
|
||||
<value>Other worlds only</value>
|
||||
</data>
|
||||
<data name="NameDisplay_WorldSuffix_Always" xml:space="preserve">
|
||||
<value>Always</value>
|
||||
</data>
|
||||
<data name="NameDisplay_NameForm_Full" xml:space="preserve">
|
||||
<value>Full name</value>
|
||||
</data>
|
||||
<data name="NameDisplay_NameForm_FirstNameOnly" xml:space="preserve">
|
||||
<value>First name only</value>
|
||||
</data>
|
||||
<data name="NameDisplay_NameForm_Initials" xml:space="preserve">
|
||||
<value>Initials</value>
|
||||
</data>
|
||||
|
||||
<!-- v1.5.6: inactive window opacity -->
|
||||
<data name="Settings_ThemeAndLayout_WindowOpacityInactive_Name" xml:space="preserve">
|
||||
<value>Inactive window opacity</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeAndLayout_WindowOpacityInactive_Description" xml:space="preserve">
|
||||
<value>Background opacity of the main chat window while it is not focused. The slider above sets the focused value. A per-window override in Dalamud's window pinning menu still takes precedence over both.</value>
|
||||
</data>
|
||||
|
||||
<!-- v1.5.6: custom sound volume -->
|
||||
<data name="Settings_General_CustomSoundVolume_Name" xml:space="preserve">
|
||||
<value>Custom sound volume</value>
|
||||
</data>
|
||||
<data name="Settings_General_CustomSoundVolume_Description" xml:space="preserve">
|
||||
<value>Playback volume for the three bundled custom notification sounds. Does not affect the 16 game sounds.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -95,6 +95,24 @@ internal sealed class General : ISettingsTab
|
||||
{
|
||||
ImGui.Checkbox(Language.Options_PlaySounds_Name, ref Mutable.PlaySounds);
|
||||
ImGuiUtil.HelpMarker(Language.Options_PlaySounds_Description);
|
||||
// Volume is stored as a 0-1 float but shown as 0-100% to match user
|
||||
// intuition. Full range — unlike opacity there is no unsafe floor.
|
||||
var customSoundVolumePercent = Mutable.CustomSoundVolume * 100f;
|
||||
if (
|
||||
ImGuiUtil.DragFloatVertical(
|
||||
HellionStrings.Settings_General_CustomSoundVolume_Name,
|
||||
ref customSoundVolumePercent,
|
||||
1f,
|
||||
0f,
|
||||
100f,
|
||||
$"{customSoundVolumePercent:N0}%%",
|
||||
ImGuiSliderFlags.AlwaysClamp
|
||||
)
|
||||
)
|
||||
{
|
||||
Mutable.CustomSoundVolume = customSoundVolumePercent / 100f;
|
||||
}
|
||||
ImGuiUtil.HelpMarker(HellionStrings.Settings_General_CustomSoundVolume_Description);
|
||||
|
||||
ImGui.Checkbox(Language.Options_ShowNoviceNetwork_Name, ref Mutable.ShowNoviceNetwork);
|
||||
ImGuiUtil.HelpMarker(Language.Options_ShowNoviceNetwork_Description);
|
||||
|
||||
@@ -234,7 +234,7 @@ internal sealed class Tabs : ISettingsTab
|
||||
}
|
||||
else
|
||||
{
|
||||
Plugin.CustomAudioPlayer.Play((int)previewId - 16);
|
||||
Plugin.CustomAudioPlayer.Play((int)previewId - 16, Mutable.CustomSoundVolume);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user