From 0e52b7d92bbdd7ec2be38e42062d76e1e239fe72 Mon Sep 17 00:00:00 2001 From: Morb <14136326+Morb0@users.noreply.github.com> Date: Wed, 27 Sep 2023 20:18:18 +0300 Subject: [PATCH] Cleanup TTS & fix voice preview (#1458) --- .../Corvax/TTS/HumanoidProfileEditor.TTS.cs | 5 +- Content.Client/Corvax/TTS/TTSManager.cs | 22 --- Content.Client/Corvax/TTS/TTSSystem.cs | 182 +++--------------- Content.Client/Entry/EntryPoint.cs | 2 - Content.Client/IoC/ClientContentIoC.cs | 1 - Content.Server/Corvax/TTS/TTSSystem.cs | 20 +- Content.Shared/Corvax/TTS/MsgRequestTTS.cs | 29 --- Content.Shared/Corvax/TTS/PlayTTSEvent.cs | 6 +- .../Corvax/TTS/RequestGlobalTTSEvent.cs | 17 ++ 9 files changed, 56 insertions(+), 228 deletions(-) delete mode 100644 Content.Client/Corvax/TTS/TTSManager.cs delete mode 100644 Content.Shared/Corvax/TTS/MsgRequestTTS.cs create mode 100644 Content.Shared/Corvax/TTS/RequestGlobalTTSEvent.cs diff --git a/Content.Client/Corvax/TTS/HumanoidProfileEditor.TTS.cs b/Content.Client/Corvax/TTS/HumanoidProfileEditor.TTS.cs index 67c984f0223f01..f7bfedf845221c 100644 --- a/Content.Client/Corvax/TTS/HumanoidProfileEditor.TTS.cs +++ b/Content.Client/Corvax/TTS/HumanoidProfileEditor.TTS.cs @@ -9,7 +9,6 @@ namespace Content.Client.Preferences.UI; public sealed partial class HumanoidProfileEditor { - private TTSManager _ttsMgr = default!; private TTSSystem _ttsSys = default!; private List _voiceList = default!; private readonly List _sampleText = new() @@ -22,7 +21,6 @@ public sealed partial class HumanoidProfileEditor private void InitializeVoice() { - _ttsMgr = IoCManager.Resolve(); _ttsSys = _entMan.System(); _voiceList = _prototypeManager .EnumeratePrototypes() @@ -80,7 +78,6 @@ private void PlayTTS() if (_previewDummy is null || Profile is null) return; - _ttsSys.StopAllStreams(); - _ttsMgr.RequestTTS(_previewDummy.Value, _random.Pick(_sampleText), Profile.Voice); + _ttsSys.RequestGlobalTTS(_random.Pick(_sampleText), Profile.Voice); } } diff --git a/Content.Client/Corvax/TTS/TTSManager.cs b/Content.Client/Corvax/TTS/TTSManager.cs deleted file mode 100644 index d129495dcee7c2..00000000000000 --- a/Content.Client/Corvax/TTS/TTSManager.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Content.Shared.Corvax.TTS; -using Robust.Shared.Network; - -namespace Content.Client.Corvax.TTS; - -// ReSharper disable once InconsistentNaming -public sealed class TTSManager -{ - [Dependency] private readonly IClientNetManager _netMgr = default!; - - public void Initialize() - { - _netMgr.RegisterNetMessage(); - } - - // ReSharper disable once InconsistentNaming - public void RequestTTS(EntityUid uid, string text, string voiceId) - { - var msg = new MsgRequestTTS() { Text = text, Uid = uid, VoiceId = voiceId }; - _netMgr.ClientSendMessage(msg); - } -} diff --git a/Content.Client/Corvax/TTS/TTSSystem.cs b/Content.Client/Corvax/TTS/TTSSystem.cs index df21f792be8b89..a053d016fe3048 100644 --- a/Content.Client/Corvax/TTS/TTSSystem.cs +++ b/Content.Client/Corvax/TTS/TTSSystem.cs @@ -1,15 +1,12 @@ -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; using Content.Shared.Corvax.CCCVars; using Content.Shared.Corvax.TTS; using Content.Shared.Corvax.TTS.Commands; -using Content.Shared.Physics; -using Robust.Client.Graphics; +using Robust.Client.ResourceManagement; +using Robust.Shared.Audio; using Robust.Shared.Configuration; -using Robust.Shared.Map; -using Robust.Shared.Physics; -using Robust.Shared.Physics.Systems; +using Robust.Shared.ContentPack; +using Robust.Shared.Player; +using Robust.Shared.Utility; namespace Content.Client.Corvax.TTS; @@ -19,24 +16,23 @@ namespace Content.Client.Corvax.TTS; // ReSharper disable once InconsistentNaming public sealed class TTSSystem : EntitySystem { - [Dependency] private readonly IClydeAudio _clyde = default!; - [Dependency] private readonly IEntityManager _entity = default!; - [Dependency] private readonly IEyeManager _eye = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; - [Dependency] private readonly SharedPhysicsSystem _broadPhase = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly IResourceCache _resourceCache = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; private ISawmill _sawmill = default!; + private readonly MemoryContentRoot _contentRoot = new(); + private static readonly ResPath Prefix = ResPath.Root / "TTS"; + private float _volume = 0.0f; private float _radioVolume = 0.0f; - - private readonly HashSet _currentStreams = new(); - private readonly Dictionary> _entityQueues = new(); + private int _fileIdx = 0; public override void Initialize() { _sawmill = Logger.GetSawmill("tts"); + _resourceCache.AddRoot(Prefix, _contentRoot); _cfg.OnValueChanged(CCCVars.TTSVolume, OnTtsVolumeChanged, true); _cfg.OnValueChanged(CCCVars.TTSRadioVolume, OnTtsRadioVolumeChanged, true); SubscribeNetworkEvent(OnPlayTTS); @@ -48,58 +44,12 @@ public override void Shutdown() base.Shutdown(); _cfg.UnsubValueChanged(CCCVars.TTSVolume, OnTtsVolumeChanged); _cfg.UnsubValueChanged(CCCVars.TTSRadioVolume, OnTtsRadioVolumeChanged); - EndStreams(); + _contentRoot.Dispose(); } - // Little bit of duplication logic from AudioSystem - public override void FrameUpdate(float frameTime) + public void RequestGlobalTTS(string text, string voiceId) { - var streamToRemove = new HashSet(); - - var ourPos = _eye.CurrentEye.Position.Position; - foreach (var stream in _currentStreams) - { - var streamUid = GetEntity(stream.Uid); - if (!stream.Source.IsPlaying || - !_entity.TryGetComponent(streamUid, out var meta) || - Deleted(streamUid, meta) || - !_entity.TryGetComponent(streamUid, out var xform)) - { - stream.Source.Dispose(); - streamToRemove.Add(stream); - continue; - } - - var mapPos = xform.MapPosition; - if (mapPos.MapId != MapId.Nullspace) - { - if (!stream.Source.SetPosition(mapPos.Position)) - { - _sawmill.Warning("Can't set position for audio stream, stop stream."); - stream.Source.StopPlaying(); - } - } - - if (mapPos.MapId == _eye.CurrentMap) - { - var collisionMask = (int) CollisionGroup.Impassable; - var sourceRelative = ourPos - mapPos.Position; - var occlusion = 0f; - if (sourceRelative.Length() > 0) - { - occlusion = _broadPhase.IntersectRayPenetration(mapPos.MapId, - new CollisionRay(mapPos.Position, sourceRelative.Normalized(), collisionMask), - sourceRelative.Length(), streamUid); - } - stream.Source.SetOcclusion(occlusion); - } - } - - foreach (var audioStream in streamToRemove) - { - _currentStreams.Remove(audioStream); - ProcessEntityQueue(GetEntity(audioStream.Uid)); - } + RaiseNetworkEvent(new RequestGlobalTTSEvent(text, voiceId)); } private void OnTtsVolumeChanged(float volume) @@ -114,109 +64,31 @@ private void OnTtsRadioVolumeChanged(float volume) private void OnQueueResetRequest(TtsQueueResetMessage ev) { - EndStreams(); + //EndStreams(); _sawmill.Debug("TTS queue was cleared by request from the server."); } private void OnPlayTTS(PlayTTSEvent ev) { - var volume = (ev.IsRadio ? _radioVolume : _volume) * ev.VolumeModifier; - - if (!TryCreateAudioSource(ev.Data, volume, out var source)) - return; - - var stream = new AudioStream(ev.Uid, source); - AddEntityStreamToQueue(stream); - } + _sawmill.Debug($"Play TTS audio {ev.Data.Length} bytes from {ev.SourceUid} entity"); - public void StopAllStreams() - { - foreach (var stream in _currentStreams) - stream.Source.StopPlaying(); - } + var volume = (ev.IsRadio ? _radioVolume : _volume) * ev.VolumeModifier; - private bool TryCreateAudioSource(byte[] data, float volume, [NotNullWhen(true)] out IClydeAudioSource? source) - { - var dataStream = new MemoryStream(data) { Position = 0 }; - var audioStream = _clyde.LoadAudioOggVorbis(dataStream); - source = _clyde.CreateAudioSource(audioStream); - source?.SetVolume(volume); - return source != null; - } + var filePath = new ResPath($"{_fileIdx++}.ogg"); + _contentRoot.AddOrUpdateFile(filePath, ev.Data); - private void AddEntityStreamToQueue(AudioStream stream) - { - var uid = GetEntity(stream.Uid); - if (_entityQueues.TryGetValue(uid, out var queue)) + var audioParams = AudioParams.Default.WithVolume(volume); + var soundPath = new SoundPathSpecifier(Prefix / filePath, audioParams); + if (ev.SourceUid != null) { - queue.Enqueue(stream); + var sourceUid = GetEntity(ev.SourceUid.Value); + _audio.PlayEntity(soundPath, new EntityUid(), sourceUid); // recipient arg ignored on client } else { - _entityQueues.Add(uid, new Queue(new[] { stream })); - - if (!IsEntityCurrentlyPlayStream(stream.Uid)) - ProcessEntityQueue(uid); - } - } - - private bool IsEntityCurrentlyPlayStream(NetEntity uid) - { - return _currentStreams.Any(s => s.Uid == uid); - } - - private void ProcessEntityQueue(EntityUid uid) - { - if (TryTakeEntityStreamFromQueue(uid, out var stream)) - PlayEntity(stream); - } - - private bool TryTakeEntityStreamFromQueue(EntityUid uid, [NotNullWhen(true)] out AudioStream? stream) - { - if (_entityQueues.TryGetValue(uid, out var queue)) - { - stream = queue.Dequeue(); - if (queue.Count == 0) - _entityQueues.Remove(uid); - return true; + _audio.PlayGlobal(soundPath, Filter.Local(), false); } - stream = null; - return false; - } - - private void PlayEntity(AudioStream stream) - { - if (!_entity.TryGetComponent(GetEntity(stream.Uid), out var xform) || - !stream.Source.SetPosition(_transform.GetWorldPosition(xform))) - return; - - stream.Source.StartPlaying(); - _currentStreams.Add(stream); - } - - public void EndStreams() - { - foreach (var stream in _currentStreams) - { - stream.Source.StopPlaying(); - stream.Source.Dispose(); - } - - _currentStreams.Clear(); - _entityQueues.Clear(); - } - - // ReSharper disable once InconsistentNaming - private sealed class AudioStream - { - public NetEntity Uid { get; } - public IClydeAudioSource Source { get; } - - public AudioStream(NetEntity uid, IClydeAudioSource source) - { - Uid = uid; - Source = source; - } + _contentRoot.RemoveFile(filePath); } } diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs index 65acdb2d06a094..1014beb666166a 100644 --- a/Content.Client/Entry/EntryPoint.cs +++ b/Content.Client/Entry/EntryPoint.cs @@ -73,7 +73,6 @@ public sealed class EntryPoint : GameClient [Dependency] private readonly ContentLocalizationManager _contentLoc = default!; [Dependency] private readonly SponsorsManager _sponsorsManager = default!; // Corvax-Sponsors [Dependency] private readonly JoinQueueManager _queueManager = default!; // Corvax-Queue - [Dependency] private readonly TTSManager _ttsManager = default!; // Corvax-TTS [Dependency] private readonly DiscordAuthManager _discordAuthManager = default!; // Corvax-DiscordAuth [Dependency] private readonly ContentReplayPlaybackManager _playbackMan = default!; [Dependency] private readonly IResourceManager _resourceManager = default!; @@ -174,7 +173,6 @@ public override void PostInit() _userInterfaceManager.SetDefaultTheme("SS14DefaultTheme"); _sponsorsManager.Initialize(); // Corvax-Sponsors _queueManager.Initialize(); // Corvax-Queue - _ttsManager.Initialize(); // Corvax-TTS _discordAuthManager.Initialize(); // Corvax-DiscordAuth _userInterfaceManager.SetActiveTheme(_configManager.GetCVar(CVars.InterfaceTheme)); _documentParsingManager.Initialize(); diff --git a/Content.Client/IoC/ClientContentIoC.cs b/Content.Client/IoC/ClientContentIoC.cs index aa0d3908ed6083..59d125a81db7cb 100644 --- a/Content.Client/IoC/ClientContentIoC.cs +++ b/Content.Client/IoC/ClientContentIoC.cs @@ -53,7 +53,6 @@ public static void Register() IoCManager.Register(); IoCManager.Register(); // Corvax-Sponsors IoCManager.Register(); // Corvax-Queue - IoCManager.Register(); // Corvax-TTS IoCManager.Register(); // Corvax-DiscordAuth IoCManager.Register(); IoCManager.Register(); diff --git a/Content.Server/Corvax/TTS/TTSSystem.cs b/Content.Server/Corvax/TTS/TTSSystem.cs index 2b66cfd27a245d..0332b54b23f955 100644 --- a/Content.Server/Corvax/TTS/TTSSystem.cs +++ b/Content.Server/Corvax/TTS/TTSSystem.cs @@ -4,9 +4,7 @@ using Content.Shared.Corvax.TTS; using Content.Shared.GameTicking; using Content.Shared.SS220.AnnounceTTS; -using Robust.Server.Player; using Robust.Shared.Configuration; -using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Prototypes; @@ -17,8 +15,6 @@ public sealed partial class TTSSystem : EntitySystem { [Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IServerNetManager _netMgr = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly TTSManager _ttsManager = default!; [Dependency] private readonly SharedTransformSystem _xforms = default!; @@ -40,7 +36,7 @@ public override void Initialize() SubscribeLocalEvent(OnAnnouncementSpoke); SubscribeLocalEvent(OnRoundRestartCleanup); - _netMgr.RegisterNetMessage(OnRequestTTS); + SubscribeNetworkEvent(OnRequestGlobalTTS); } private void OnRadioReceiveEvent(RadioSpokeEvent args) @@ -85,18 +81,18 @@ private void OnRoundRestartCleanup(RoundRestartCleanupEvent ev) _ttsManager.ResetCache(); } - private async void OnRequestTTS(MsgRequestTTS ev) + private async void OnRequestGlobalTTS(RequestGlobalTTSEvent ev, EntitySessionEventArgs args) { if (!_isEnabled || ev.Text.Length > MaxMessageChars || - !_playerManager.TryGetSessionByChannel(ev.MsgChannel, out var session) || !_prototypeManager.TryIndex(ev.VoiceId, out var protoVoice)) return; var soundData = await GenerateTTS(ev.Text, protoVoice.Speaker); - if (soundData is null) return; + if (soundData is null) + return; - RaiseNetworkEvent(new PlayTTSEvent(GetNetEntity(ev.Uid), soundData, false), Filter.SinglePlayer(session)); + RaiseNetworkEvent(new PlayTTSEvent(soundData), Filter.SinglePlayer(args.SenderSession)); } private async void OnEntitySpoke(EntityUid uid, TTSComponent component, EntitySpokeEvent args) @@ -127,7 +123,7 @@ private async void HandleSay(EntityUid uid, string message, string speaker) { var soundData = await GenerateTTS(message, speaker); if (soundData is null) return; - RaiseNetworkEvent(new PlayTTSEvent(GetNetEntity(uid), soundData, false), Filter.Pvs(uid)); + RaiseNetworkEvent(new PlayTTSEvent(soundData, GetNetEntity(uid)), Filter.Pvs(uid)); } private async void HandleWhisper(EntityUid uid, string message, string speaker, bool isRadio) @@ -154,8 +150,8 @@ private async void HandleWhisper(EntityUid uid, string message, string speaker, continue; var ttsEvent = new PlayTTSEvent( - GetNetEntity(uid), soundData, + GetNetEntity(uid), false, WhisperVoiceVolumeModifier * (1f - distance / WhisperVoiceRange)); RaiseNetworkEvent(ttsEvent, session); @@ -170,7 +166,7 @@ private async void HandleRadio(EntityUid[] uids, string message, string speaker) foreach (var uid in uids) { - RaiseNetworkEvent(new PlayTTSEvent(GetNetEntity(uid), soundData, true), Filter.Entities(uid)); + RaiseNetworkEvent(new PlayTTSEvent(soundData, GetNetEntity(uid), true), Filter.Entities(uid)); } } diff --git a/Content.Shared/Corvax/TTS/MsgRequestTTS.cs b/Content.Shared/Corvax/TTS/MsgRequestTTS.cs deleted file mode 100644 index 485de198002ebe..00000000000000 --- a/Content.Shared/Corvax/TTS/MsgRequestTTS.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Lidgren.Network; -using Robust.Shared.Network; -using Robust.Shared.Serialization; - -namespace Content.Shared.Corvax.TTS; - -// ReSharper disable once InconsistentNaming -public sealed class MsgRequestTTS : NetMessage -{ - public override MsgGroups MsgGroup => MsgGroups.Command; - - public EntityUid Uid { get; set; } = EntityUid.Invalid; - public string Text { get; set; } = String.Empty; - public string VoiceId { get; set; } = String.Empty; - - public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) - { - Uid = new EntityUid(buffer.ReadInt32()); - Text = buffer.ReadString(); - VoiceId = buffer.ReadString(); - } - - public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer) - { - buffer.Write((int)Uid); - buffer.Write(Text); - buffer.Write(VoiceId); - } -} diff --git a/Content.Shared/Corvax/TTS/PlayTTSEvent.cs b/Content.Shared/Corvax/TTS/PlayTTSEvent.cs index fb92aaf2dd72f6..18bee32bdfaf15 100644 --- a/Content.Shared/Corvax/TTS/PlayTTSEvent.cs +++ b/Content.Shared/Corvax/TTS/PlayTTSEvent.cs @@ -6,15 +6,15 @@ namespace Content.Shared.Corvax.TTS; // ReSharper disable once InconsistentNaming public sealed class PlayTTSEvent : EntityEventArgs { - public NetEntity Uid { get; } public byte[] Data { get; } + public NetEntity? SourceUid { get; } public bool IsRadio { get; } public float VolumeModifier { get; set; } - public PlayTTSEvent(NetEntity uid, byte[] data, bool isRadio, float volumeModifier = 1f) + public PlayTTSEvent(byte[] data, NetEntity? sourceUid = null, bool isRadio = false, float volumeModifier = 1f) { - Uid = uid; Data = data; + SourceUid = sourceUid; IsRadio = isRadio; VolumeModifier = volumeModifier; } diff --git a/Content.Shared/Corvax/TTS/RequestGlobalTTSEvent.cs b/Content.Shared/Corvax/TTS/RequestGlobalTTSEvent.cs new file mode 100644 index 00000000000000..48a1ca5ca29d7a --- /dev/null +++ b/Content.Shared/Corvax/TTS/RequestGlobalTTSEvent.cs @@ -0,0 +1,17 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Corvax.TTS; + +// ReSharper disable once InconsistentNaming +[Serializable, NetSerializable] +public sealed class RequestGlobalTTSEvent : EntityEventArgs +{ + public string Text { get; } + public string VoiceId { get; } + + public RequestGlobalTTSEvent(string text, string voiceId) + { + Text = text; + VoiceId = voiceId; + } +}