[Fix] Потенциальный фикс ТТС (#413)

* fix: possible tts fix??

* style: не втыкай
This commit is contained in:
Remuchi
2024-07-02 22:56:53 +07:00
committed by GitHub
parent 6be08b4506
commit 55a225f574
3 changed files with 95 additions and 163 deletions

View File

@@ -87,7 +87,7 @@ public sealed partial class HumanoidProfileEditor
if (_previewDummy is null || Profile is null) if (_previewDummy is null || Profile is null)
return; return;
_ttsSys.StopAllStreams(); _ttsSys.StopCurrentTTS(_previewDummy.Value);
_ttsMgr.RequestTTS(_previewDummy.Value, IoCManager.Resolve<IRobustRandom>().Pick(_sampleText), Profile.Voice); _ttsMgr.RequestTTS(_previewDummy.Value, IoCManager.Resolve<IRobustRandom>().Pick(_sampleText), Profile.Voice);
} }
} }

View File

@@ -1,38 +1,29 @@
using System.Diagnostics.CodeAnalysis; using System.IO;
using System.IO;
using System.Linq;
using Content.Shared.Physics;
using Content.Shared._White; using Content.Shared._White;
using Content.Shared._White.TTS; using Content.Shared._White.TTS;
using Robust.Client.Audio; using Robust.Client.Audio;
using Robust.Client.GameObjects; using Robust.Shared.Audio;
using Robust.Client.Graphics; using Robust.Shared.Audio.Components;
using Robust.Shared.Audio.Sources;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.Map; // ReSharper disable InconsistentNaming
using Robust.Shared.Physics;
using Robust.Shared.Physics.Systems;
namespace Content.Client._White.TTS; namespace Content.Client._White.TTS;
/// <summary> /// <summary>
/// Plays TTS audio in world /// Plays TTS audio in world
/// </summary> /// </summary>
// ReSharper disable once InconsistentNaming
public sealed class TTSSystem : EntitySystem public sealed class TTSSystem : EntitySystem
{ {
[Dependency] private readonly IAudioManager _audioSystem = default!; [Dependency] private readonly IAudioManager _audioManager = default!;
[Dependency] private readonly IEntityManager _entity = default!;
[Dependency] private readonly IEyeManager _eye = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly SharedPhysicsSystem _broadPhase = default!; [Dependency] private readonly AudioSystem _audioSystem = default!;
[Dependency] private readonly TransformSystem _transform = default!;
private float _volume; private float _volume;
private const int TTSCollisionMask = (int)CollisionGroup.Impassable; private readonly Dictionary<EntityUid, AudioComponent> _currentlyPlaying = new();
private readonly Dictionary<EntityUid, Queue<AudioStreamWithParams>> _enquedStreams = new();
private readonly HashSet<AudioStream> _currentStreams = new();
private readonly Dictionary<EntityUid, Queue<AudioStream>> _entityQueues = new(); // Same as Server.ChatSystem.VoiceRange
private const float VoiceRange = 10;
public override void Initialize() public override void Initialize()
{ {
@@ -44,54 +35,35 @@ public sealed class TTSSystem : EntitySystem
{ {
base.Shutdown(); base.Shutdown();
_cfg.UnsubValueChanged(WhiteCVars.TtsVolume, OnTtsVolumeChanged); _cfg.UnsubValueChanged(WhiteCVars.TtsVolume, OnTtsVolumeChanged);
EndStreams(); ClearQueues();
} }
// Little bit of duplication logic from AudioSystem
public override void FrameUpdate(float frameTime) public override void FrameUpdate(float frameTime)
{ {
var streamToRemove = new HashSet<AudioStream>(); foreach (var (uid, audioComponent) in _currentlyPlaying)
var ourPos = _eye.CurrentEye.Position.Position;
foreach (var stream in _currentStreams)
{ {
if (!stream.Source.Playing || if (audioComponent is { Running: true, Playing: true })
!_entity.TryGetComponent<MetaDataComponent>(stream.Uid, out var meta) ||
Deleted(stream.Uid, meta) ||
!_entity.TryGetComponent<TransformComponent>(stream.Uid, out var xform))
{
stream.Source.Dispose();
streamToRemove.Add(stream);
continue;
}
var mapPos = _transform.GetMapCoordinates(xform);
if (mapPos.MapId != MapId.Nullspace)
{
stream.Source.Position = mapPos.Position;
}
if (mapPos.MapId != _eye.CurrentMap)
{ {
continue; continue;
} }
var sourceRelative = ourPos - mapPos.Position; if (!_enquedStreams.TryGetValue(uid, out var queue))
var occlusion = 0f;
if (sourceRelative.Length() > 0)
{ {
occlusion = _broadPhase.IntersectRayPenetration(mapPos.MapId, continue;
new CollisionRay(mapPos.Position, sourceRelative.Normalized(), TTSCollisionMask),
sourceRelative.Length(), stream.Uid);
} }
stream.Source.Occlusion = occlusion; if (!queue.TryDequeue(out var toPlay))
} {
continue;
}
foreach (var audioStream in streamToRemove) var audio = _audioSystem.PlayEntity(toPlay.Stream, uid, toPlay.Params);
{ if (!audio.HasValue)
_currentStreams.Remove(audioStream); {
ProcessEntityQueue(audioStream.Uid); continue;
}
_currentlyPlaying[uid] = audio.Value.Component;
} }
} }
@@ -102,114 +74,76 @@ public sealed class TTSSystem : EntitySystem
private void OnPlayTTS(PlayTTSEvent ev) private void OnPlayTTS(PlayTTSEvent ev)
{ {
if (_volume <= -20f) PlayTTS(GetEntity(ev.Uid), ev.Data, ev.BoostVolume ? _volume + 5 : _volume);
return;
var volume = _volume;
if (ev.BoostVolume)
volume += 5f;
if (!TryCreateAudioSource(ev.Data, out var source, volume))
return;
var stream = new AudioStream(GetEntity(ev.Uid), source);
AddEntityStreamToQueue(stream);
} }
public void PlayCustomText(string text) public void PlayTTS(EntityUid uid, byte[] data, float volume)
{ {
RaiseNetworkEvent(new RequestTTSEvent(text)); if (_volume <= -20f)
{
return;
}
var stream = CreateAudioStream(data);
var audioParams = new AudioParams
{
Volume = volume,
MaxDistance = VoiceRange
};
var audioStream = new AudioStreamWithParams(stream, audioParams);
EnqueueAudio(uid, audioStream);
} }
private bool TryCreateAudioSource(byte[] data, [NotNullWhen(true)] out IAudioSource? source, float volume = 0f) public void StopCurrentTTS(EntityUid uid)
{
if (!_currentlyPlaying.TryGetValue(uid, out var audio))
{
return;
}
_audioSystem.Stop(audio.Owner);
}
private void EnqueueAudio(EntityUid uid, AudioStreamWithParams audioStream)
{
if (!_currentlyPlaying.ContainsKey(uid))
{
var audio = _audioSystem.PlayEntity(audioStream.Stream, uid, audioStream.Params);
if (!audio.HasValue)
{
return;
}
_currentlyPlaying[uid] = audio.Value.Component;
return;
}
if (_enquedStreams.TryGetValue(uid, out var queue))
{
queue.Enqueue(audioStream);
return;
}
queue = new Queue<AudioStreamWithParams>();
queue.Enqueue(audioStream);
_enquedStreams[uid] = queue;
}
private void ClearQueues()
{
foreach (var (_, queue) in _enquedStreams)
{
queue.Clear();
}
}
private AudioStream CreateAudioStream(byte[] data)
{ {
var dataStream = new MemoryStream(data) { Position = 0 }; var dataStream = new MemoryStream(data) { Position = 0 };
var audioStream = _audioSystem.LoadAudioOggVorbis(dataStream); return _audioManager.LoadAudioOggVorbis(dataStream);
source = _audioSystem.CreateAudioSource(audioStream);
if (source == null)
{
return false;
}
source.Volume = volume == 0f ? _volume : volume;
return true;
} }
private void AddEntityStreamToQueue(AudioStream stream) private record AudioStreamWithParams(AudioStream Stream, AudioParams Params);
{
if (_entityQueues.TryGetValue(stream.Uid, out var queue))
{
queue.Enqueue(stream);
}
else
{
_entityQueues.Add(stream.Uid, new Queue<AudioStream>(new[] { stream }));
if (!IsEntityCurrentlyPlayStream(stream.Uid))
ProcessEntityQueue(stream.Uid);
}
}
private bool IsEntityCurrentlyPlayStream(EntityUid 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;
}
stream = null;
return false;
}
private void PlayEntity(AudioStream stream)
{
if (!_entity.TryGetComponent<TransformComponent>(stream.Uid, out var xform))
return;
stream.Source.Position = _transform.GetWorldPosition(xform);
stream.Source.StartPlaying();
_currentStreams.Add(stream);
}
public void StopAllStreams()
{
foreach (var stream in _currentStreams)
{
stream.Source.StopPlaying();
}
}
private 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(EntityUid uid, IAudioSource source)
{
public EntityUid Uid { get; } = uid;
public IAudioSource Source { get; } = source;
}
} }

View File

@@ -63,7 +63,9 @@ public sealed partial class TTSSystem : EntitySystem
Filter filter; Filter filter;
if (ev.Global) if (ev.Global)
{
filter = Filter.Broadcast(); filter = Filter.Broadcast();
}
else else
{ {
var station = _stationSystem.GetOwningStation(ev.Source); var station = _stationSystem.GetOwningStation(ev.Source);
@@ -132,11 +134,7 @@ public sealed partial class TTSSystem : EntitySystem
private async void OnEntitySpoke(EntityUid uid, SharedTTSComponent component, EntitySpokeEvent args) private async void OnEntitySpoke(EntityUid uid, SharedTTSComponent component, EntitySpokeEvent args)
{ {
if (!_isEnabled || if (!_isEnabled || string.IsNullOrEmpty(_apiUrl) || args.Message.Length > MaxMessageChars)
args.Message.Length > MaxMessageChars)
return;
if (string.IsNullOrEmpty(_apiUrl))
{ {
return; return;
} }
@@ -154,6 +152,7 @@ public sealed partial class TTSSystem : EntitySystem
var soundData = await GenerateTTS(uid, message, protoVoice.Speaker); var soundData = await GenerateTTS(uid, message, protoVoice.Speaker);
if (soundData is null) if (soundData is null)
return; return;
var ttsEvent = new PlayTTSEvent(GetNetEntity(uid), soundData, false); var ttsEvent = new PlayTTSEvent(GetNetEntity(uid), soundData, false);
// Say // Say
@@ -182,7 +181,6 @@ public sealed partial class TTSSystem : EntitySystem
var sourcePos = _xforms.GetWorldPosition(xformQuery.GetComponent(uid), xformQuery); var sourcePos = _xforms.GetWorldPosition(xformQuery.GetComponent(uid), xformQuery);
var receptions = Filter.Pvs(uid).Recipients; var receptions = Filter.Pvs(uid).Recipients;
foreach (var session in receptions) foreach (var session in receptions)
{ {
if (!session.AttachedEntity.HasValue) if (!session.AttachedEntity.HasValue)