Skip to content

Commit

Permalink
Add TTS (#82)
Browse files Browse the repository at this point in the history
  • Loading branch information
Lokilife authored Sep 21, 2024
1 parent 2c97834 commit 8599755
Show file tree
Hide file tree
Showing 49 changed files with 6,336 additions and 779 deletions.
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

0 comments on commit 8599755

Please sign in to comment.