diff --git a/Content.Client/Instruments/InstrumentComponent.cs b/Content.Client/Instruments/InstrumentComponent.cs index 9d36d4e571..66f0050723 100644 --- a/Content.Client/Instruments/InstrumentComponent.cs +++ b/Content.Client/Instruments/InstrumentComponent.cs @@ -1,11 +1,6 @@ -using System; -using System.Collections.Generic; using Content.Shared.Instruments; using Robust.Client.Audio.Midi; using Robust.Shared.Audio.Midi; -using Robust.Shared.GameObjects; -using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.ViewVariables; namespace Content.Client.Instruments; @@ -14,21 +9,26 @@ public sealed class InstrumentComponent : SharedInstrumentComponent { public event Action? OnMidiPlaybackEnded; + [ViewVariables] public IMidiRenderer? Renderer; + [ViewVariables] public uint SequenceDelay; + [ViewVariables] public uint SequenceStartTick; + [ViewVariables] public TimeSpan LastMeasured = TimeSpan.MinValue; + [ViewVariables] public int SentWithinASec; /// /// A queue of MidiEvents to be sent to the server. /// [ViewVariables] - public readonly List MidiEventBuffer = new(); + public readonly List MidiEventBuffer = new(); /// /// Whether a midi song will loop or not. diff --git a/Content.Client/Instruments/InstrumentSystem.cs b/Content.Client/Instruments/InstrumentSystem.cs index 0594171dc6..55a98a4827 100644 --- a/Content.Client/Instruments/InstrumentSystem.cs +++ b/Content.Client/Instruments/InstrumentSystem.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.Metrics; using System.Linq; using Content.Shared.CCVar; using Content.Shared.Instruments; @@ -9,372 +6,367 @@ using JetBrains.Annotations; using Robust.Client.Audio.Midi; using Robust.Shared.Audio.Midi; using Robust.Shared.Configuration; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; using Robust.Shared.Network; using Robust.Shared.Timing; -using SharpFont; -namespace Content.Client.Instruments +namespace Content.Client.Instruments; + +[UsedImplicitly] +public sealed class InstrumentSystem : SharedInstrumentSystem { - [UsedImplicitly] - public sealed class InstrumentSystem : SharedInstrumentSystem + [Dependency] private readonly IClientNetManager _netManager = default!; + [Dependency] private readonly IMidiManager _midiManager = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + + public readonly TimeSpan OneSecAgo = TimeSpan.FromSeconds(-1); + public int MaxMidiEventsPerBatch { get; private set; } + public int MaxMidiEventsPerSecond { get; private set; } + + public override void Initialize() { - [Dependency] private readonly IClientNetManager _netManager = default!; - [Dependency] private readonly IMidiManager _midiManager = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly IConfigurationManager _cfg = default!; + base.Initialize(); - public readonly TimeSpan OneSecAgo = TimeSpan.FromSeconds(-1); + UpdatesOutsidePrediction = true; - public readonly Comparer SortMidiEventTick - = Comparer.Create((x, y) - => x.Tick.CompareTo(y.Tick)); + _cfg.OnValueChanged(CCVars.MaxMidiEventsPerBatch, OnMaxMidiEventsPerBatchChanged, true); + _cfg.OnValueChanged(CCVars.MaxMidiEventsPerSecond, OnMaxMidiEventsPerSecondChanged, true); - public int MaxMidiEventsPerBatch { get; private set; } - public int MaxMidiEventsPerSecond { get; private set; } + SubscribeNetworkEvent(OnMidiEventRx); + SubscribeNetworkEvent(OnMidiStart); + SubscribeNetworkEvent(OnMidiStop); - public override void Initialize() + SubscribeLocalEvent(OnShutdown); + } + + public override void Shutdown() + { + base.Shutdown(); + + _cfg.UnsubValueChanged(CCVars.MaxMidiEventsPerBatch, OnMaxMidiEventsPerBatchChanged); + _cfg.UnsubValueChanged(CCVars.MaxMidiEventsPerSecond, OnMaxMidiEventsPerSecondChanged); + } + + private void OnShutdown(EntityUid uid, InstrumentComponent component, ComponentShutdown args) + { + EndRenderer(uid, false, component); + } + + public override void SetupRenderer(EntityUid uid, bool fromStateChange, SharedInstrumentComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + if (component is not InstrumentComponent instrument || instrument.IsRendererAlive) + return; + + instrument.SequenceDelay = 0; + instrument.SequenceStartTick = 0; + _midiManager.OcclusionCollisionMask = (int) CollisionGroup.Impassable; + instrument.Renderer = _midiManager.GetNewRenderer(); + + if (instrument.Renderer != null) { - base.Initialize(); - - UpdatesOutsidePrediction = true; - - _cfg.OnValueChanged(CCVars.MaxMidiEventsPerBatch, OnMaxMidiEventsPerBatchChanged, true); - _cfg.OnValueChanged(CCVars.MaxMidiEventsPerSecond, OnMaxMidiEventsPerSecondChanged, true); - - SubscribeNetworkEvent(OnMidiEventRx); - SubscribeNetworkEvent(OnMidiStart); - SubscribeNetworkEvent(OnMidiStop); - - SubscribeLocalEvent(OnShutdown); - } - - public override void Shutdown() - { - base.Shutdown(); - - _cfg.UnsubValueChanged(CCVars.MaxMidiEventsPerBatch, OnMaxMidiEventsPerBatchChanged); - _cfg.UnsubValueChanged(CCVars.MaxMidiEventsPerSecond, OnMaxMidiEventsPerSecondChanged); - } - - private void OnShutdown(EntityUid uid, InstrumentComponent component, ComponentShutdown args) - { - EndRenderer(uid, false, component); - } - - public override void SetupRenderer(EntityUid uid, bool fromStateChange, SharedInstrumentComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - if (component is not InstrumentComponent instrument || instrument.IsRendererAlive) - return; - - instrument.SequenceDelay = 0; - instrument.SequenceStartTick = 0; - _midiManager.OcclusionCollisionMask = (int) CollisionGroup.Impassable; - instrument.Renderer = _midiManager.GetNewRenderer(); - - if (instrument.Renderer != null) + instrument.Renderer.SendMidiEvent(RobustMidiEvent.SystemReset(instrument.Renderer.SequencerTick)); + UpdateRenderer(uid, instrument); + instrument.Renderer.OnMidiPlayerFinished += () => { - UpdateRenderer(uid, instrument); - instrument.Renderer.OnMidiPlayerFinished += () => - { - instrument.PlaybackEndedInvoke(); - EndRenderer(uid, fromStateChange, instrument); - }; - } - - if (!fromStateChange) - { - RaiseNetworkEvent(new InstrumentStartMidiEvent(uid)); - } + instrument.PlaybackEndedInvoke(); + EndRenderer(uid, fromStateChange, instrument); + }; } - public void UpdateRenderer(EntityUid uid, InstrumentComponent? instrument = null) + if (!fromStateChange) { - if (!Resolve(uid, ref instrument) || instrument.Renderer == null) - return; + RaiseNetworkEvent(new InstrumentStartMidiEvent(uid)); + } + } - instrument.Renderer.MidiBank = instrument.InstrumentBank; + public void UpdateRenderer(EntityUid uid, InstrumentComponent? instrument = null) + { + if (!Resolve(uid, ref instrument) || instrument.Renderer == null) + return; + + instrument.Renderer.TrackingEntity = instrument.Owner; + instrument.Renderer.DisablePercussionChannel = !instrument.AllowPercussion; + instrument.Renderer.DisableProgramChangeEvent = !instrument.AllowProgramChange; + + if (!instrument.AllowProgramChange) + { instrument.Renderer.MidiProgram = instrument.InstrumentProgram; - instrument.Renderer.TrackingEntity = instrument.Owner; - instrument.Renderer.DisablePercussionChannel = !instrument.AllowPercussion; - instrument.Renderer.DisableProgramChangeEvent = !instrument.AllowProgramChange; - instrument.Renderer.LoopMidi = instrument.LoopMidi; - instrument.DirtyRenderer = false; + instrument.Renderer.MidiBank = instrument.InstrumentBank; } - public override void EndRenderer(EntityUid uid, bool fromStateChange, SharedInstrumentComponent? component = null) + instrument.Renderer.LoopMidi = instrument.LoopMidi; + instrument.DirtyRenderer = false; + } + + public override void EndRenderer(EntityUid uid, bool fromStateChange, SharedInstrumentComponent? component = null) + { + if (!Resolve(uid, ref component, false)) + return; + + if (component is not InstrumentComponent instrument) + return; + + if (instrument.IsInputOpen) { - if (!Resolve(uid, ref component, false)) - return; - - if (component is not InstrumentComponent instrument) - return; - - if (instrument.IsInputOpen) - { - CloseInput(uid, fromStateChange, instrument); - return; - } - - if (instrument.IsMidiOpen) - { - CloseMidi(uid, fromStateChange, instrument); - return; - } - - instrument.Renderer?.StopAllNotes(); - - var renderer = instrument.Renderer; - - // We dispose of the synth two seconds from now to allow the last notes to stop from playing. - // Don't use timers bound to the entity in case it is getting deleted. - Timer.Spawn(2000, () => { renderer?.Dispose(); }); - instrument.Renderer = null; - instrument.MidiEventBuffer.Clear(); - - if (!fromStateChange && _netManager.IsConnected) - { - RaiseNetworkEvent(new InstrumentStopMidiEvent(uid)); - } + CloseInput(uid, fromStateChange, instrument); + return; } - public void SetPlayerTick(EntityUid uid, int playerTick, InstrumentComponent? instrument = null) + if (instrument.IsMidiOpen) { - if (!Resolve(uid, ref instrument)) - return; - - if (instrument.Renderer == null || instrument.Renderer.Status != MidiRendererStatus.File) - return; - - instrument.MidiEventBuffer.Clear(); - - instrument.Renderer.PlayerTick = playerTick; - var tick = instrument.Renderer.SequencerTick; - - // We add a "all notes off" message. - for (byte i = 0; i < 16; i++) - { - instrument.MidiEventBuffer.Add(new MidiEvent() - { - Tick = tick, Type = 176, - Control = 123, Velocity = 0, Channel = i, - }); - } - - // Now we add a Reset All Controllers message. - instrument.MidiEventBuffer.Add(new MidiEvent() - { - Tick = tick, Type = 176, - Control = 121, Value = 0, - }); + CloseMidi(uid, fromStateChange, instrument); + return; } - public bool OpenInput(EntityUid uid, InstrumentComponent? instrument = null) + instrument.Renderer?.StopAllNotes(); + + var renderer = instrument.Renderer; + + // We dispose of the synth two seconds from now to allow the last notes to stop from playing. + // Don't use timers bound to the entity in case it is getting deleted. + Timer.Spawn(2000, () => { renderer?.Dispose(); }); + instrument.Renderer = null; + instrument.MidiEventBuffer.Clear(); + + if (!fromStateChange && _netManager.IsConnected) { - if (!Resolve(uid, ref instrument, false)) - return false; + RaiseNetworkEvent(new InstrumentStopMidiEvent(uid)); + } + } - SetupRenderer(uid, false, instrument); + public void SetPlayerTick(EntityUid uid, int playerTick, InstrumentComponent? instrument = null) + { + if (!Resolve(uid, ref instrument)) + return; - if (instrument.Renderer != null && instrument.Renderer.OpenInput()) - { - instrument.Renderer.OnMidiEvent += instrument.MidiEventBuffer.Add; - return true; - } + if (instrument.Renderer is not { Status: MidiRendererStatus.File }) + return; + instrument.MidiEventBuffer.Clear(); + + var tick = instrument.Renderer.SequencerTick-1; + + instrument.MidiEventBuffer.Add(RobustMidiEvent.SystemReset(tick)); + + // We add a "all notes off" message. + for (byte i = 0; i < 16; i++) + { + //instrument.MidiEventBuffer.Add(RobustMidiEvent.AllNotesOff(i, tick)); + } + + instrument.Renderer.PlayerTick = playerTick; + } + + public bool OpenInput(EntityUid uid, InstrumentComponent? instrument = null) + { + if (!Resolve(uid, ref instrument, false)) return false; - } - public bool OpenMidi(EntityUid uid, ReadOnlySpan data, InstrumentComponent? instrument = null) + SetupRenderer(uid, false, instrument); + + if (instrument.Renderer != null && instrument.Renderer.OpenInput()) { - if (!Resolve(uid, ref instrument)) - return false; - - SetupRenderer(uid, false, instrument); - - if (instrument.Renderer == null || !instrument.Renderer.OpenMidi(data)) - { - return false; - } - instrument.Renderer.OnMidiEvent += instrument.MidiEventBuffer.Add; return true; } - public bool CloseInput(EntityUid uid, bool fromStateChange, InstrumentComponent? instrument = null) + return false; + } + + public bool OpenMidi(EntityUid uid, ReadOnlySpan data, InstrumentComponent? instrument = null) + { + if (!Resolve(uid, ref instrument)) + return false; + + SetupRenderer(uid, false, instrument); + + if (instrument.Renderer == null || !instrument.Renderer.OpenMidi(data)) { - if (!Resolve(uid, ref instrument)) - return false; - - if (instrument.Renderer == null || !instrument.Renderer.CloseInput()) - { - return false; - } - - EndRenderer(uid, fromStateChange, instrument); - return true; + return false; } - public bool CloseMidi(EntityUid uid, bool fromStateChange, InstrumentComponent? instrument = null) + instrument.MidiEventBuffer.Clear(); + + instrument.Renderer.OnMidiEvent += instrument.MidiEventBuffer.Add; + return true; + } + + public bool CloseInput(EntityUid uid, bool fromStateChange, InstrumentComponent? instrument = null) + { + if (!Resolve(uid, ref instrument)) + return false; + + if (instrument.Renderer == null || !instrument.Renderer.CloseInput()) { - if (!Resolve(uid, ref instrument)) - return false; - - if (instrument.Renderer == null || !instrument.Renderer.CloseMidi()) - { - return false; - } - - EndRenderer(uid, fromStateChange, instrument); - return true; + return false; } - private void OnMaxMidiEventsPerSecondChanged(int obj) + EndRenderer(uid, fromStateChange, instrument); + return true; + } + + public bool CloseMidi(EntityUid uid, bool fromStateChange, InstrumentComponent? instrument = null) + { + if (!Resolve(uid, ref instrument)) + return false; + + if (instrument.Renderer == null || !instrument.Renderer.CloseMidi()) { - MaxMidiEventsPerSecond = obj; + return false; } - private void OnMaxMidiEventsPerBatchChanged(int obj) - { - MaxMidiEventsPerBatch = obj; - } + EndRenderer(uid, fromStateChange, instrument); + return true; + } - private void OnMidiEventRx(InstrumentMidiEventEvent midiEv) - { - var uid = midiEv.Uid; + private void OnMaxMidiEventsPerSecondChanged(int obj) + { + MaxMidiEventsPerSecond = obj; + } - if (!EntityManager.TryGetComponent(uid, out InstrumentComponent? instrument)) + private void OnMaxMidiEventsPerBatchChanged(int obj) + { + MaxMidiEventsPerBatch = obj; + } + + private void OnMidiEventRx(InstrumentMidiEventEvent midiEv) + { + var uid = midiEv.Uid; + + if (!TryComp(uid, out InstrumentComponent? instrument)) + return; + + var renderer = instrument.Renderer; + + if (renderer != null) + { + // If we're the ones sending the MidiEvents, we ignore this message. + if (instrument.IsInputOpen || instrument.IsMidiOpen) return; - - var renderer = instrument.Renderer; - - if (renderer != null) + } + else + { + // if we haven't started or finished some sequence + if (instrument.SequenceStartTick == 0) { - // If we're the ones sending the MidiEvents, we ignore this message. - if (instrument.IsInputOpen || instrument.IsMidiOpen) - return; - } - else - { - // if we haven't started or finished some sequence - if (instrument.SequenceStartTick == 0) - { - // we may have arrived late - SetupRenderer(uid, true, instrument); - } - - // might be our own notes after we already finished playing - return; + // we may have arrived late + SetupRenderer(uid, true, instrument); } - if (instrument.SequenceStartTick <= 0) - { - instrument.SequenceStartTick = midiEv.MidiEvent.Min(x => x.Tick) - 1; - } - - var sqrtLag = MathF.Sqrt(_netManager.ServerChannel!.Ping / 1000f); - var delay = (uint) (renderer.SequencerTimeScale * (.2 + sqrtLag)); - var delta = delay - instrument.SequenceStartTick; - - instrument.SequenceDelay = Math.Max(instrument.SequenceDelay, delta); - - var currentTick = renderer.SequencerTick; - - // ReSharper disable once ForCanBeConvertedToForeach - for (var i = 0; i < midiEv.MidiEvent.Length; i++) - { - var ev = midiEv.MidiEvent[i]; - var scheduled = ev.Tick + instrument.SequenceDelay; - - if (scheduled <= currentTick) - { - instrument.SequenceDelay += currentTick - ev.Tick; - scheduled = ev.Tick + instrument.SequenceDelay; - } - - instrument.Renderer?.ScheduleMidiEvent(ev, scheduled, true); - } + // might be our own notes after we already finished playing + return; } - private void OnMidiStart(InstrumentStartMidiEvent ev) + if (instrument.SequenceStartTick <= 0) { - SetupRenderer(ev.Uid, true); + instrument.SequenceStartTick = midiEv.MidiEvent.Min(x => x.Tick) - 1; } - private void OnMidiStop(InstrumentStopMidiEvent ev) + var sqrtLag = MathF.Sqrt(_netManager.ServerChannel!.Ping / 1000f); + var delay = (uint) (renderer.SequencerTimeScale * (.2 + sqrtLag)); + var delta = delay - instrument.SequenceStartTick; + + instrument.SequenceDelay = Math.Max(instrument.SequenceDelay, delta); + + var currentTick = renderer.SequencerTick; + + // ReSharper disable once ForCanBeConvertedToForeach + for (uint i = 0; i < midiEv.MidiEvent.Length; i++) { - EndRenderer(ev.Uid, true); + var ev = midiEv.MidiEvent[i]; + + var scheduled = ev.Tick + instrument.SequenceDelay; + + if (scheduled < currentTick) + { + instrument.SequenceDelay += currentTick - ev.Tick; + scheduled = ev.Tick + instrument.SequenceDelay; + } + + // The order of events with the same timestamp is undefined in Fluidsynth's sequencer... + // Therefore we add the event index to the scheduled time to ensure every event has an unique timestamp. + instrument.Renderer?.ScheduleMidiEvent(ev, scheduled+i, true); + } + } + + private void OnMidiStart(InstrumentStartMidiEvent ev) + { + SetupRenderer(ev.Uid, true); + } + + private void OnMidiStop(InstrumentStopMidiEvent ev) + { + EndRenderer(ev.Uid, true); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + if (!_gameTiming.IsFirstTimePredicted) + { + return; } - public override void Update(float frameTime) + foreach (var instrument in EntityManager.EntityQuery(true)) { - base.Update(frameTime); + if (instrument.DirtyRenderer && instrument.Renderer != null) + UpdateRenderer(instrument.Owner, instrument); - if (!_gameTiming.IsFirstTimePredicted) + if (!instrument.IsMidiOpen && !instrument.IsInputOpen) + continue; + + var now = _gameTiming.RealTime; + var oneSecAGo = now.Add(OneSecAgo); + + if (instrument.LastMeasured <= oneSecAGo) { - return; + instrument.LastMeasured = now; + instrument.SentWithinASec = 0; } - foreach (var instrument in EntityManager.EntityQuery(true)) + if (instrument.MidiEventBuffer.Count == 0) continue; + + var max = instrument.RespectMidiLimits ? + Math.Min(MaxMidiEventsPerBatch, MaxMidiEventsPerSecond - instrument.SentWithinASec) + : instrument.MidiEventBuffer.Count; + + if (max <= 0) { - if (instrument.DirtyRenderer && instrument.Renderer != null) - UpdateRenderer(instrument.Owner, instrument); - - if (!instrument.IsMidiOpen && !instrument.IsInputOpen) - continue; - - var now = _gameTiming.RealTime; - var oneSecAGo = now.Add(OneSecAgo); - - if (instrument.LastMeasured <= oneSecAGo) - { - instrument.LastMeasured = now; - instrument.SentWithinASec = 0; - } - - if (instrument.MidiEventBuffer.Count == 0) continue; - - var max = instrument.RespectMidiLimits ? - Math.Min(MaxMidiEventsPerBatch, MaxMidiEventsPerSecond - instrument.SentWithinASec) - : instrument.MidiEventBuffer.Count; - - if (max <= 0) - { - // hit event/sec limit, have to lag the batch or drop events - continue; - } - - // fix cross-fade events generating retroactive events - // also handle any significant backlog of events after midi finished - - instrument.MidiEventBuffer.Sort(SortMidiEventTick); - var bufferTicks = instrument.IsRendererAlive && instrument.Renderer!.Status != MidiRendererStatus.None - ? instrument.Renderer.SequencerTimeScale * .2f - : 0; - var bufferedTick = instrument.IsRendererAlive - ? instrument.Renderer!.SequencerTick - bufferTicks - : int.MaxValue; - - var events = instrument.MidiEventBuffer - .TakeWhile(x => x.Tick < bufferedTick) - .Take(max) - .ToArray(); - - var eventCount = events.Length; - - if (eventCount == 0) - continue; - - RaiseNetworkEvent(new InstrumentMidiEventEvent(instrument.Owner, events)); - - instrument.SentWithinASec += eventCount; - - instrument.MidiEventBuffer.RemoveRange(0, eventCount); + // hit event/sec limit, have to lag the batch or drop events + continue; } + + // fix cross-fade events generating retroactive events + // also handle any significant backlog of events after midi finished + + var bufferTicks = instrument.IsRendererAlive && instrument.Renderer!.Status != MidiRendererStatus.None + ? instrument.Renderer.SequencerTimeScale * .2f + : 0; + + var bufferedTick = instrument.IsRendererAlive + ? instrument.Renderer!.SequencerTick - bufferTicks + : int.MaxValue; + + // TODO: Remove LINQ brain-rot. + var events = instrument.MidiEventBuffer + .TakeWhile(x => x.Tick < bufferedTick) + .Take(max) + .ToArray(); + + var eventCount = events.Length; + + if (eventCount == 0) + continue; + + RaiseNetworkEvent(new InstrumentMidiEventEvent(instrument.Owner, events)); + + instrument.SentWithinASec += eventCount; + + instrument.MidiEventBuffer.RemoveRange(0, eventCount); } } } diff --git a/Content.Server/Instruments/InstrumentComponent.cs b/Content.Server/Instruments/InstrumentComponent.cs index 5cb4721b74..13c98b99da 100644 --- a/Content.Server/Instruments/InstrumentComponent.cs +++ b/Content.Server/Instruments/InstrumentComponent.cs @@ -25,6 +25,9 @@ public sealed class InstrumentComponent : SharedInstrumentComponent [ViewVariables] public int MidiEventCount = 0; + [ViewVariables] + public uint LastSequencerTick = 0; + // TODO Instruments: Make this ECS public IPlayerSession? InstrumentPlayer => _entMan.GetComponentOrNull(Owner)?.CurrentSingleUser diff --git a/Content.Server/Instruments/InstrumentSystem.cs b/Content.Server/Instruments/InstrumentSystem.cs index da509f2a7f..fa36a84261 100644 --- a/Content.Server/Instruments/InstrumentSystem.cs +++ b/Content.Server/Instruments/InstrumentSystem.cs @@ -84,7 +84,7 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem { if (args.UiKey is not InstrumentUiKey) return; - + EnsureComp(uid); Clean(uid, component); } @@ -110,7 +110,7 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem { var uid = msg.Uid; - if (!EntityManager.TryGetComponent(uid, out InstrumentComponent? instrument)) + if (!TryComp(uid, out InstrumentComponent? instrument)) return; if (!instrument.Playing @@ -121,7 +121,20 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem var send = true; - var minTick = msg.MidiEvent.Min(x => x.Tick); + var minTick = uint.MaxValue; + var maxTick = uint.MinValue; + + for (var i = 0; i < msg.MidiEvent.Length; i++) + { + var tick = msg.MidiEvent[i].Tick; + + if (tick < minTick) + minTick = tick; + + if (tick > maxTick) + maxTick = tick; + } + if (instrument.LastSequencerTick > minTick) { instrument.LaggedBatches++; @@ -158,7 +171,6 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem RaiseNetworkEvent(msg); } - var maxTick = msg.MidiEvent.Max(x => x.Tick); instrument.LastSequencerTick = Math.Max(maxTick, minTick); } diff --git a/Content.Shared/Instruments/SharedInstrumentComponent.cs b/Content.Shared/Instruments/SharedInstrumentComponent.cs index b9d05acd74..b8dfa30f1d 100644 --- a/Content.Shared/Instruments/SharedInstrumentComponent.cs +++ b/Content.Shared/Instruments/SharedInstrumentComponent.cs @@ -15,9 +15,6 @@ public abstract class SharedInstrumentComponent : Component [ViewVariables] public bool Playing { get; set; } - [ViewVariables] - public uint LastSequencerTick { get; set; } - [DataField("program"), ViewVariables(VVAccess.ReadWrite)] public byte InstrumentProgram { get; set; } @@ -73,9 +70,9 @@ public sealed class InstrumentStartMidiEvent : EntityEventArgs public sealed class InstrumentMidiEventEvent : EntityEventArgs { public EntityUid Uid { get; } - public MidiEvent[] MidiEvent { get; } + public RobustMidiEvent[] MidiEvent { get; } - public InstrumentMidiEventEvent(EntityUid uid, MidiEvent[] midiEvent) + public InstrumentMidiEventEvent(EntityUid uid, RobustMidiEvent[] midiEvent) { Uid = uid; MidiEvent = midiEvent; @@ -92,7 +89,7 @@ public sealed class InstrumentState : ComponentState public bool AllowProgramChange { get; } public bool RespectMidiLimits { get; } - public InstrumentState(bool playing, byte instrumentProgram, byte instrumentBank, bool allowPercussion, bool allowProgramChange, bool respectMidiLimits, uint sequencerTick = 0) + public InstrumentState(bool playing, byte instrumentProgram, byte instrumentBank, bool allowPercussion, bool allowProgramChange, bool respectMidiLimits) { Playing = playing; InstrumentProgram = instrumentProgram; diff --git a/Content.Shared/Instruments/SharedInstrumentSystem.cs b/Content.Shared/Instruments/SharedInstrumentSystem.cs index 5d2b5985f8..79c7706df1 100644 --- a/Content.Shared/Instruments/SharedInstrumentSystem.cs +++ b/Content.Shared/Instruments/SharedInstrumentSystem.cs @@ -23,7 +23,7 @@ public abstract class SharedInstrumentSystem : EntitySystem { args.State = new InstrumentState(instrument.Playing, instrument.InstrumentProgram, instrument.InstrumentBank, - instrument.AllowPercussion, instrument.AllowProgramChange, instrument.RespectMidiLimits, instrument.LastSequencerTick); + instrument.AllowPercussion, instrument.AllowProgramChange, instrument.RespectMidiLimits); } private void OnHandleState(EntityUid uid, SharedInstrumentComponent instrument, ref ComponentHandleState args) diff --git a/RobustToolbox b/RobustToolbox index 4bb695121f..7094c29b2e 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 4bb695121feaee924b6c00a2f10c5e34f396c655 +Subproject commit 7094c29b2e080b0b348180618b9a2c6c0c4ac466 diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings index 2af5013a82..edd35665c8 100644 --- a/SpaceStation14.sln.DotSettings +++ b/SpaceStation14.sln.DotSettings @@ -283,6 +283,7 @@ True True True + True True True True