Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TTS #82

Merged
merged 1 commit into from
Sep 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Content.Client/Audio/ContentAudioSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public sealed partial class ContentAudioSystem : SharedContentAudioSystem
public const float AmbientMusicMultiplier = 3f;
public const float LobbyMultiplier = 3f;
public const float InterfaceMultiplier = 2f;
public const float TtsMultiplier = 3f; // Corvax-TTS

public override void Initialize()
{
Expand Down
66 changes: 66 additions & 0 deletions Content.Client/Corvax/TTS/HumanoidProfileEditor.TTS.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System.Linq;
using Content.Client.Corvax.TTS;
using Content.Client.Lobby;
using Content.Shared.Corvax.TTS;
using Content.Shared.Preferences;

namespace Content.Client.Lobby.UI;

public sealed partial class HumanoidProfileEditor
{
private List<TTSVoicePrototype> _voiceList = new();

private void InitializeVoice()
{
_voiceList = _prototypeManager
.EnumeratePrototypes<TTSVoicePrototype>()
.Where(o => o.RoundStart)
.OrderBy(o => Loc.GetString(o.Name))
.ToList();

VoiceButton.OnItemSelected += args =>
{
VoiceButton.SelectId(args.Id);
SetVoice(_voiceList[args.Id].ID);
};

VoicePlayButton.OnPressed += _ => PlayPreviewTTS();
}

private void UpdateTTSVoicesControls()
{
if (Profile is null)
return;

VoiceButton.Clear();

var firstVoiceChoiceId = 1;
for (var i = 0; i < _voiceList.Count; i++)
{
var voice = _voiceList[i];
if (!HumanoidCharacterProfile.CanHaveVoice(voice, Profile.Sex))
continue;

var name = Loc.GetString(voice.Name);
VoiceButton.AddItem(name, i);

if (firstVoiceChoiceId == 1)
firstVoiceChoiceId = i;
}

var voiceChoiceId = _voiceList.FindIndex(x => x.ID == Profile.Voice);
if (!VoiceButton.TrySelectId(voiceChoiceId) &&
VoiceButton.TrySelectId(firstVoiceChoiceId))
{
SetVoice(_voiceList[firstVoiceChoiceId].ID);
}
}

private void PlayPreviewTTS()
{
if (Profile is null)
return;

_entManager.System<TTSSystem>().RequestPreviewTTS(Profile.Voice);
}
}
109 changes: 109 additions & 0 deletions Content.Client/Corvax/TTS/TTSSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
using Content.Shared.Chat;
using Content.Shared.Corvax.CCCVars;
using Content.Shared.Corvax.TTS;
using Robust.Client.Audio;
using Robust.Client.ResourceManagement;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.Utility;

namespace Content.Client.Corvax.TTS;

/// <summary>
/// Plays TTS audio in world
/// </summary>
// ReSharper disable once InconsistentNaming
public sealed class TTSSystem : EntitySystem
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IResourceManager _res = default!;
[Dependency] private readonly AudioSystem _audio = default!;

private ISawmill _sawmill = default!;
private readonly MemoryContentRoot _contentRoot = new();
private static readonly ResPath Prefix = ResPath.Root / "TTS";

/// <summary>
/// Reducing the volume of the TTS when whispering. Will be converted to logarithm.
/// </summary>
private const float WhisperFade = 4f;

/// <summary>
/// The volume at which the TTS sound will not be heard.
/// </summary>
private const float MinimalVolume = -10f;

private float _volume = 0.0f;
private int _fileIdx = 0;

public override void Initialize()
{
_sawmill = Logger.GetSawmill("tts");
_res.AddRoot(Prefix, _contentRoot);
_cfg.OnValueChanged(CCCVars.TTSVolume, OnTtsVolumeChanged, true);
SubscribeNetworkEvent<PlayTTSEvent>(OnPlayTTS);
}

public override void Shutdown()
{
base.Shutdown();
_cfg.UnsubValueChanged(CCCVars.TTSVolume, OnTtsVolumeChanged);
_contentRoot.Dispose();
}

public void RequestPreviewTTS(string voiceId)
{
RaiseNetworkEvent(new RequestPreviewTTSEvent(voiceId));
}

private void OnTtsVolumeChanged(float volume)
{
_volume = volume;
}

private void OnPlayTTS(PlayTTSEvent ev)
{
_sawmill.Verbose($"Play TTS audio {ev.Data.Length} bytes from {ev.SourceUid} entity");

var filePath = new ResPath($"{_fileIdx++}.ogg");
_contentRoot.AddOrUpdateFile(filePath, ev.Data);

var audioResource = new AudioResource();
audioResource.Load(IoCManager.Instance!, Prefix / filePath);

var audioParams = AudioParams.Default
.WithVolume(AdjustVolume(ev.IsWhisper))
.WithMaxDistance(AdjustDistance(ev.IsWhisper));

if (ev.SourceUid != null)
{
var sourceUid = GetEntity(ev.SourceUid.Value);
_audio.PlayEntity(audioResource.AudioStream, sourceUid, audioParams);
}
else
{
_audio.PlayGlobal(audioResource.AudioStream, audioParams);
}

_contentRoot.RemoveFile(filePath);
}

private float AdjustVolume(bool isWhisper)
{
var volume = MinimalVolume + SharedAudioSystem.GainToVolume(_volume);

if (isWhisper)
{
volume -= SharedAudioSystem.GainToVolume(WhisperFade);
}

return volume;
}

private float AdjustDistance(bool isWhisper)
{
return isWhisper ? SharedChatSystem.WhisperMuffledRange : SharedChatSystem.VoiceRange;
}
}
8 changes: 8 additions & 0 deletions Content.Client/Lobby/UI/HumanoidProfileEditor.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@
<Control HorizontalExpand="True"/>
<OptionButton Name="SpawnPriorityButton" HorizontalAlignment="Right" />
</BoxContainer>
<!-- Corvax-TTS-Start -->
<BoxContainer HorizontalExpand="True" Visible="False" Name="TTSContainer">
<Label Text="{Loc 'humanoid-profile-editor-voice-label'}" />
<Control HorizontalExpand="True"/>
<OptionButton Name="VoiceButton" HorizontalAlignment="Right" />
<Button Name="VoicePlayButton" Text="{Loc 'humanoid-profile-editor-voice-play'}" MaxWidth="80" />
</BoxContainer>
<!-- Corvax-TTS-End -->
</BoxContainer>
<!-- Skin -->
<BoxContainer Margin="10" HorizontalExpand="True" Orientation="Vertical">
Expand Down
23 changes: 23 additions & 0 deletions Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Content.Client.UserInterface.Systems.Guidebook;
using Content.Shared.CCVar;
using Content.Shared.Clothing;
using Content.Shared.Corvax.CCCVars; // Corvax-TTS
using Content.Shared.GameTicking;
using Content.Shared.Guidebook;
using Content.Shared.Humanoid;
Expand Down Expand Up @@ -210,6 +211,18 @@ public HumanoidProfileEditor(

#endregion Gender

// Corvax-TTS-Start
#region Voice

if (configurationManager.GetCVar(CCCVars.TTSEnabled))
{
TTSContainer.Visible = true;
InitializeVoice();
}

#endregion
// Corvax-TTS-End

RefreshSpecies();

SpeciesButton.OnItemSelected += args =>
Expand Down Expand Up @@ -753,6 +766,7 @@ public void SetProfile(HumanoidCharacterProfile? profile, int? slot)
UpdateEyePickers();
UpdateSaveButton();
UpdateMarkings();
UpdateTTSVoicesControls(); // Corvax-TTS
UpdateHairPickers();
UpdateCMarkingsHair();
UpdateCMarkingsFacialHair();
Expand Down Expand Up @@ -1178,6 +1192,7 @@ private void SetSex(Sex newSex)
}

UpdateGenderControls();
UpdateTTSVoicesControls(); // Corvax-TTS
Markings.SetSex(newSex);
ReloadPreview();
}
Expand All @@ -1188,6 +1203,14 @@ private void SetGender(Gender newGender)
ReloadPreview();
}

// Corvax-TTS-Start
private void SetVoice(string newVoice)
{
Profile = Profile?.WithVoice(newVoice);
IsDirty = true;
}
// Corvax-TTS-End

private void SetSpecies(string newSpecies)
{
Profile = Profile?.WithSpecies(newSpecies);
Expand Down
1 change: 1 addition & 0 deletions Content.Client/Options/UI/Tabs/AudioTab.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<BoxContainer Orientation="Vertical" Margin="0 3 0 0">
<ui:OptionSlider Name="SliderVolumeMaster" Title="{Loc 'ui-options-master-volume'}"
Margin="0 0 0 8" />
<!-- Corvax-TTS-Start --><ui:OptionSlider Name="SliderVolumeTts" Title="{Loc 'ui-options-tts-volume'}" /><!-- Corvax-TTS-End -->
<ui:OptionSlider Name="SliderVolumeMidi" Title="{Loc 'ui-options-midi-volume'}" />
<ui:OptionSlider Name="SliderVolumeAmbientMusic" Title="{Loc 'ui-options-ambient-music-volume'}" />
<ui:OptionSlider Name="SliderVolumeAmbience" Title="{Loc 'ui-options-ambience-volume'}" />
Expand Down
8 changes: 8 additions & 0 deletions Content.Client/Options/UI/Tabs/AudioTab.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Content.Client.Audio;
using Content.Shared.CCVar;
using Content.Shared.Corvax.CCCVars; // Corvax-TTS
using Robust.Client.Audio;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
Expand All @@ -26,6 +27,13 @@ public AudioTab()
scale: ContentAudioSystem.MasterVolumeMultiplier);
masterVolume.ImmediateValueChanged += OnMasterVolumeSliderChanged;

// Corvax-TTS-Start
Control.AddOptionPercentSlider(
CCCVars.TTSVolume,
SliderVolumeTts,
scale: ContentAudioSystem.TtsMultiplier);
// Corvax-TTS-End

Control.AddOptionPercentSlider(
CVars.MidiVolume,
SliderVolumeMidi,
Expand Down
3 changes: 2 additions & 1 deletion Content.Client/VoiceMask/VoiceMaskBoundUserInterface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ protected override void Open()

_window.OnNameChange += OnNameSelected;
_window.OnVerbChange += verb => SendMessage(new VoiceMaskChangeVerbMessage(verb));
_window.OnVoiceChange += voice => SendMessage(new VoiceMaskChangeVoiceMessage(voice)); // Corvax-TTS
}

private void OnNameSelected(string name)
Expand All @@ -39,7 +40,7 @@ protected override void UpdateState(BoundUserInterfaceState state)
return;
}

_window.UpdateState(cast.Name, cast.Verb);
_window.UpdateState(cast.Name, cast.Voice, cast.Verb); // Corvax-TTS
}

protected override void Dispose(bool disposing)
Expand Down
6 changes: 6 additions & 0 deletions Content.Client/VoiceMask/VoiceMaskNameChangeWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,11 @@
<Label Text="{Loc 'voice-mask-name-change-speech-style'}" />
<OptionButton Name="SpeechVerbSelector" /> <!-- Populated in LoadVerbs -->
</BoxContainer>
<!-- Corvax-TTS-Start -->
<BoxContainer Orientation="Horizontal" Margin="5" Visible="False" Name="TTSContainer">
<Label Text="{Loc 'voice-mask-voice-change-info'}" />
<OptionButton Name="VoiceSelector" /> <!-- Populated in LoadVerbs -->
</BoxContainer>
<!-- Corvax-TTS-End -->
</BoxContainer>
</controls:FancyWindow>
46 changes: 44 additions & 2 deletions Content.Client/VoiceMask/VoiceMaskNameChangeWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
using System.Linq; // Corvax-TTS
using Content.Shared.Corvax.TTS; // Corvax-TTS
using Content.Client.UserInterface.Controls;
using Content.Shared.Corvax.CCCVars; // Corvax-TTS
using Content.Shared.Speech;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration; // Corvax-TTS
using Robust.Shared.Prototypes;

namespace Content.Client.VoiceMask;
Expand All @@ -12,8 +15,10 @@ public sealed partial class VoiceMaskNameChangeWindow : FancyWindow
{
public Action<string>? OnNameChange;
public Action<string?>? OnVerbChange;
public Action<string>? OnVoiceChange; // Corvax-TTS

private List<(string, string)> _verbs = new();
private List<TTSVoicePrototype> _voices = new(); // Corvax-TTS

private string? _verb;

Expand All @@ -32,6 +37,14 @@ public VoiceMaskNameChangeWindow()
SpeechVerbSelector.SelectId(args.Id);
};

// Corvax-TTS-Start
if (IoCManager.Resolve<IConfigurationManager>().GetCVar(CCCVars.TTSEnabled))
{
TTSContainer.Visible = true;
ReloadVoices(IoCManager.Resolve<IPrototypeManager>());
}
// Corvax-TTS-End

AddVerbs();
}

Expand Down Expand Up @@ -66,7 +79,30 @@ private void AddVerb(string name, string? verb)
SpeechVerbSelector.SelectId(id);
}

public void UpdateState(string name, string? verb)
// Corvax-TTS-Start
private void ReloadVoices(IPrototypeManager proto)
{
VoiceSelector.OnItemSelected += args =>
{
VoiceSelector.SelectId(args.Id);
if (VoiceSelector.SelectedMetadata != null)
OnVoiceChange!((string)VoiceSelector.SelectedMetadata);
};
_voices = proto
.EnumeratePrototypes<TTSVoicePrototype>()
.Where(o => o.RoundStart)
.OrderBy(o => Loc.GetString(o.Name))
.ToList();
for (var i = 0; i < _voices.Count; i++)
{
var name = Loc.GetString(_voices[i].Name);
VoiceSelector.AddItem(name);
VoiceSelector.SetItemMetadata(i, _voices[i].ID);
}
}
// Corvax-TTS-End

public void UpdateState(string name, string voice, string? verb) // Corvax-TTS
{
NameSelector.Text = name;
_verb = verb;
Expand All @@ -79,5 +115,11 @@ public void UpdateState(string name, string? verb)
break;
}
}

// Corvax-TTS-Start
var voiceIdx = _voices.FindIndex(v => v.ID == voice);
if (voiceIdx != -1)
VoiceSelector.Select(voiceIdx);
// Corvax-TTS-End
}
}
Loading
Loading