From 9f0dad80e423ad70a37366d5143f3b8ac74a16ef Mon Sep 17 00:00:00 2001 From: Hannah Giovanna Dawson Date: Wed, 6 Aug 2025 00:12:55 +0100 Subject: [PATCH] Fix instrument pausing when outside PVS range (#6113) --- Robust.Client/Audio/Midi/MidiManager.cs | 173 +++++++++++------------- 1 file changed, 78 insertions(+), 95 deletions(-) diff --git a/Robust.Client/Audio/Midi/MidiManager.cs b/Robust.Client/Audio/Midi/MidiManager.cs index a6e45a338..7002c2977 100644 --- a/Robust.Client/Audio/Midi/MidiManager.cs +++ b/Robust.Client/Audio/Midi/MidiManager.cs @@ -42,7 +42,7 @@ internal sealed partial class MidiManager : IMidiManager [Dependency] private readonly IRuntimeLog _runtime = default!; private AudioSystem _audioSys = default!; - private SharedPhysicsSystem _broadPhaseSystem = default!; + private SharedPhysicsSystem _physics = default!; private SharedTransformSystem _xformSystem = default!; public IReadOnlyList Renderers @@ -81,7 +81,7 @@ internal sealed partial class MidiManager : IMidiManager private Thread? _midiThread; private ISawmill _midiSawmill = default!; private float _gain = 0f; - private bool _volumeDirty = true; + private bool _gainDirty = true; // Not reliable until Fluidsynth is initialized! [ViewVariables(VVAccess.ReadWrite)] @@ -96,7 +96,7 @@ internal sealed partial class MidiManager : IMidiManager return; _cfgMan.SetCVar(CVars.MidiVolume, clamped); - _volumeDirty = true; + _gainDirty = true; } } @@ -114,7 +114,8 @@ internal sealed partial class MidiManager : IMidiManager "/usr/share/sounds/sf2/TimGM6mb.sf2", }; - private static readonly string WindowsSoundfont = $@"{Environment.GetEnvironmentVariable("SystemRoot")}\system32\drivers\gm.dls"; + private static readonly string WindowsSoundfont = + $@"{Environment.GetEnvironmentVariable("SystemRoot")}\system32\drivers\gm.dls"; private const string OsxSoundfont = "/System/Library/Components/CoreAudio.component/Contents/Resources/gs_instruments.dls"; @@ -145,11 +146,13 @@ internal sealed partial class MidiManager : IMidiManager { if (FluidsynthInitialized || _failedInitialize) return; - _cfgMan.OnValueChanged(CVars.MidiVolume, value => - { - _gain = value; - _volumeDirty = true; - }, true); + _cfgMan.OnValueChanged(CVars.MidiVolume, + value => + { + _gain = value; + _gainDirty = true; + }, + true); _midiSawmill = _logger.GetSawmill("midi"); #if DEBUG @@ -167,13 +170,15 @@ internal sealed partial class MidiManager : IMidiManager // not a directory, preserve the old file and create an actual directory else if (!_resourceManager.UserData.IsDir(CustomSoundfontDirectory)) { - _resourceManager.UserData.Rename(CustomSoundfontDirectory, CustomSoundfontDirectory.WithName(CustomSoundfontDirectory.Filename + ".old")); + _resourceManager.UserData.Rename(CustomSoundfontDirectory, + CustomSoundfontDirectory.WithName(CustomSoundfontDirectory.Filename + ".old")); _resourceManager.UserData.CreateDir(CustomSoundfontDirectory); } try { - NFluidsynth.Logger.SetLoggerMethod(_loggerDelegate); // Will cause a safe DllNotFoundException if not available. + NFluidsynth.Logger + .SetLoggerMethod(_loggerDelegate); // Will cause a safe DllNotFoundException if not available. _settings = new Settings(); _settings["synth.sample-rate"].DoubleValue = 44100; @@ -193,7 +198,7 @@ internal sealed partial class MidiManager : IMidiManager //_settings["synth.verbose"].IntValue = 1; // Useful for debugging. var midiParallel = _cfgMan.GetCVar(CVars.MidiParallelism); - _settings["synth.polyphony"].IntValue = Math.Clamp(1024 + (int)(Math.Log2(midiParallel) * 2048), 1, 65535); + _settings["synth.polyphony"].IntValue = Math.Clamp(1024 + (int) (Math.Log2(midiParallel) * 2048), 1, 65535); _settings["synth.cpu-cores"].IntValue = Math.Clamp(midiParallel, 1, 256); _midiSawmill.Debug($"Synth Cores: {_settings["synth.cpu-cores"].IntValue}"); @@ -219,7 +224,7 @@ internal sealed partial class MidiManager : IMidiManager }; _audioSys = _entityManager.EntitySysManager.GetEntitySystem(); - _broadPhaseSystem = _entityManager.EntitySysManager.GetEntitySystem(); + _physics = _entityManager.EntitySysManager.GetEntitySystem(); _xformSystem = _entityManager.System(); _entityManager.GetEntityQuery(); _entityManager.GetEntityQuery(); @@ -263,7 +268,8 @@ internal sealed partial class MidiManager : IMidiManager { soundfontLoader.SetCallbacks(_soundfontLoaderCallbacks); - var renderer = new MidiRenderer(_settings!, soundfontLoader, mono, this, _audio, _taskManager, _midiSawmill); + var renderer = + new MidiRenderer(_settings!, soundfontLoader, mono, this, _audio, _taskManager, _midiSawmill); LoadSoundFontSetup(renderer); @@ -273,6 +279,7 @@ internal sealed partial class MidiManager : IMidiManager { _renderers.Add(renderer); } + return renderer; } finally @@ -309,99 +316,23 @@ internal sealed partial class MidiManager : IMidiManager _updateSemaphore.Release(); - _volumeDirty = false; + _gainDirty = false; } private void UpdateRenderer(IMidiRenderer renderer, MapCoordinates listener) { - // TODO: This should be sharing more code with AudioSystem. try { if (renderer.Disposed) return; - if (_volumeDirty) - { - renderer.Source.Gain = Gain; - } - if (!renderer.Mono) - { renderer.Source.Global = true; - return; - } - MapCoordinates mapPos; - - if (renderer.TrackingEntity is {} trackedEntity && !_entityManager.Deleted(trackedEntity)) - { - renderer.TrackingCoordinates = _xformSystem.GetMapCoordinates(renderer.TrackingEntity.Value); - - // Pause it if the attached entity is paused. - if (_entityManager.IsPaused(renderer.TrackingEntity)) - { - renderer.Source.Pause(); - return; - } - } - else if (renderer.TrackingCoordinates == null) - { - renderer.Source.Pause(); - return; - } - - mapPos = renderer.TrackingCoordinates.Value; - - // If it's on a different map then just mute it, not pause. - if (mapPos.MapId == MapId.Nullspace || mapPos.MapId != listener.MapId) - { - renderer.Source.Gain = 0f; - return; - } - - // Was previously muted maybe so try unmuting it? - if (renderer.Source.Gain == 0f) - { - renderer.Source.Gain = Gain; - } - - var worldPos = mapPos.Position; - var delta = worldPos - listener.Position; - var distance = delta.Length(); - - // Update position - // Out of range so just clip it for us. - if (distance > renderer.Source.MaxDistance) - { - // Still keeps the source playing, just with no volume. - renderer.Source.Gain = 0f; - return; - } - - // Same imprecision suppression as audiosystem. - if (distance > 0f && distance < 0.01f) - { - worldPos = listener.Position; - delta = Vector2.Zero; - distance = 0f; - } - - renderer.Source.Position = worldPos; - - // Update velocity (doppler). - if (!_entityManager.Deleted(renderer.TrackingEntity)) - { - var velocity = _broadPhaseSystem.GetMapLinearVelocity(renderer.TrackingEntity.Value); - renderer.Source.Velocity = velocity; - } + if (!renderer.Source.Global) + UpdateLocalRenderer(renderer, listener); else - { - renderer.Source.Velocity = Vector2.Zero; - } - - // Update occlusion - var occlusion = _audioSys.GetOcclusion(listener, delta, distance, renderer.TrackingEntity); - renderer.Source.Occlusion = occlusion; + UpdateGlobalRenderer(renderer); } catch (Exception ex) { @@ -409,6 +340,58 @@ internal sealed partial class MidiManager : IMidiManager } } + private void UpdateLocalRenderer(IMidiRenderer renderer, MapCoordinates listener) + { + if (_entityManager.Deleted(renderer.TrackingEntity) || _entityManager.IsPaused(renderer.TrackingEntity)) + { + renderer.Source.Gain = 0f; + + return; + } + + MapCoordinates mapCoords = _xformSystem.GetMapCoordinates(renderer.TrackingEntity.Value); + renderer.TrackingCoordinates = mapCoords; + + if (mapCoords.MapId == MapId.Nullspace || mapCoords.MapId != listener.MapId) + { + renderer.Source.Gain = 0f; + + return; + } + + Vector2 mapPosition = mapCoords.Position; + Vector2 listenerDelta = mapPosition - listener.Position; + var listenerDeltaLength = listenerDelta.Length(); + + if (listenerDeltaLength > renderer.Source.MaxDistance) + { + renderer.Source.Gain = 0f; + + return; + } + + if (listenerDeltaLength is > 0f and < 0.01f) + { + mapPosition = listener.Position; + listenerDelta = Vector2.Zero; + listenerDeltaLength = 0f; + } + + if (_gainDirty || renderer.Source.Gain == 0f) + renderer.Source.Gain = Gain; + + renderer.Source.Position = mapPosition; + renderer.Source.Velocity = _physics.GetMapLinearVelocity(renderer.TrackingEntity.Value); + renderer.Source.Occlusion = + _audioSys.GetOcclusion(listener, listenerDelta, listenerDeltaLength, renderer.TrackingEntity); + } + + private void UpdateGlobalRenderer(IMidiRenderer renderer) + { + if (_gainDirty) + renderer.Source.Gain = Gain; + } + /// /// Main method for the thread rendering the midi audio. /// @@ -428,7 +411,7 @@ internal sealed partial class MidiManager : IMidiManager { if (!renderer.Disposed) { - if (renderer.Master is { Disposed: true }) + if (renderer.Master is {Disposed: true}) renderer.Master = null; renderer.Render();