From ea255b22c43b6037b35189db81193808d1af6009 Mon Sep 17 00:00:00 2001 From: zumorica Date: Mon, 18 May 2020 13:29:31 +0200 Subject: [PATCH 01/14] Attempt at queueing midi messages --- .../Instruments/InstrumentComponent.cs | 63 +++++++++++++++++-- .../EntitySystems/InstrumentSystem.cs | 25 ++++++++ .../Instruments/SharedInstrumentComponent.cs | 6 +- 3 files changed, 86 insertions(+), 8 deletions(-) create mode 100644 Content.Client/GameObjects/EntitySystems/InstrumentSystem.cs diff --git a/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs b/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs index db85f689e1..7d06a4d41f 100644 --- a/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs +++ b/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs @@ -5,7 +5,6 @@ using JetBrains.Annotations; using Robust.Shared.GameObjects; using Robust.Client.Audio.Midi; using Robust.Shared.Audio.Midi; -using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Network; using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; @@ -20,6 +19,8 @@ namespace Content.Client.GameObjects.Components.Instruments [RegisterComponent] public class InstrumentComponent : SharedInstrumentComponent { + public const float TimeBetweenNetMessages = 1f; + /// /// Called when a midi song stops playing. /// @@ -27,13 +28,19 @@ namespace Content.Client.GameObjects.Components.Instruments #pragma warning disable 649 [Dependency] private IMidiManager _midiManager; - [Dependency] private readonly IGameTiming _timing; + [Dependency] private readonly IGameTiming _gameTiming; #pragma warning restore 649 [CanBeNull] private IMidiRenderer _renderer; private int _instrumentProgram = 1; + [ViewVariables] + private readonly Queue> _midiQueue = new Queue<(MidiEvent, double)>(); + + [ViewVariables] + private float _timer = 0f; + /// /// A queue of MidiEvents to be sent to the server. /// @@ -124,8 +131,14 @@ namespace Content.Client.GameObjects.Components.Instruments case InstrumentMidiEventMessage midiEventMessage: // If we're the ones sending the MidiEvents, we ignore this message. if (IsInputOpen || IsMidiOpen) break; - Timer.Spawn((int) (500 + _timing.CurTime.TotalMilliseconds - midiEventMessage.Timestamp), - () => _renderer.SendMidiEvent(midiEventMessage.MidiEvent)); + for (var i = 0; i < midiEventMessage.MidiEvent.Length; i++) + { + //_midiQueue.Enqueue((midiEventMessage.MidiEvent[i], (i == 0 ? 0 : 0) + _gameTiming.CurTime.TotalSeconds - midiEventMessage.Timestamp[i])); + + var j = i; + Timer.Spawn((int) ((TimeBetweenNetMessages)*1.5f + _gameTiming.CurTime.TotalSeconds - midiEventMessage.Timestamp[i])*1000, + () => _renderer.SendMidiEvent(midiEventMessage.MidiEvent[j])); + } break; case InstrumentStopMidiMessage _: @@ -190,7 +203,47 @@ namespace Content.Client.GameObjects.Components.Instruments /// The received midi event private void RendererOnMidiEvent(MidiEvent midiEvent) { - SendNetworkMessage(new InstrumentMidiEventMessage(midiEvent, _timing.CurTime.TotalMilliseconds)); + _midiQueue.Enqueue((midiEvent, _gameTiming.CurTime.TotalSeconds)); + } + + public void Update(float delta) + { + _timer -= delta; + + if (_timer > 0f) return; + + if (!IsMidiOpen && !IsInputOpen) + { + //UpdatePlaying(delta); + return; + } + + SendAllMidiMessages(); + _timer = TimeBetweenNetMessages; + } + + private void UpdatePlaying(float delta) + { + if(_renderer == null || _midiQueue.Count == 0) return; + var (midiEvent, timestamp) = _midiQueue.Dequeue(); + _renderer.SendMidiEvent(midiEvent); + _timer = _midiQueue.Count != 0 ? (float) (_midiQueue.Peek().Item2) : 0; + } + + private void SendAllMidiMessages() + { + var count = _midiQueue.Count; + var events = new MidiEvent[count]; + var timestamps = new double[count]; + + for (var i = 0; i < count; i++) + { + var (midiEvent, timestamp) = _midiQueue.Dequeue(); + events[i] = midiEvent; + timestamps[i] = timestamp; + } + + SendNetworkMessage(new InstrumentMidiEventMessage(events, timestamps)); } } } diff --git a/Content.Client/GameObjects/EntitySystems/InstrumentSystem.cs b/Content.Client/GameObjects/EntitySystems/InstrumentSystem.cs new file mode 100644 index 0000000000..6303475c30 --- /dev/null +++ b/Content.Client/GameObjects/EntitySystems/InstrumentSystem.cs @@ -0,0 +1,25 @@ +using Content.Client.GameObjects.Components.Instruments; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; + +namespace Content.Client.GameObjects.EntitySystems +{ + public class InstrumentSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + EntityQuery = new TypeEntityQuery(typeof(InstrumentComponent)); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + foreach (var entity in RelevantEntities) + { + entity.GetComponent().Update(frameTime); + } + } + } +} diff --git a/Content.Shared/GameObjects/Components/Instruments/SharedInstrumentComponent.cs b/Content.Shared/GameObjects/Components/Instruments/SharedInstrumentComponent.cs index aef625a057..9734425b3c 100644 --- a/Content.Shared/GameObjects/Components/Instruments/SharedInstrumentComponent.cs +++ b/Content.Shared/GameObjects/Components/Instruments/SharedInstrumentComponent.cs @@ -26,10 +26,10 @@ namespace Content.Shared.GameObjects.Components.Instruments [Serializable, NetSerializable] public class InstrumentMidiEventMessage : ComponentMessage { - public MidiEvent MidiEvent; - public double Timestamp; + public MidiEvent[] MidiEvent; + public double[] Timestamp; - public InstrumentMidiEventMessage(MidiEvent midiEvent, double timestamp) + public InstrumentMidiEventMessage(MidiEvent[] midiEvent, double[] timestamp) { MidiEvent = midiEvent; Timestamp = timestamp; From fede057bc4573a0ea3416743f35d37cd230096ca Mon Sep 17 00:00:00 2001 From: zumorica Date: Wed, 20 May 2020 15:14:52 +0200 Subject: [PATCH 02/14] Bunch of refactoring --- .../GameObjects/EntitySystems/SharedInstrumentSystem.cs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Content.Client/GameObjects/EntitySystems/InstrumentSystem.cs => Content.Shared/GameObjects/EntitySystems/SharedInstrumentSystem.cs (100%) diff --git a/Content.Client/GameObjects/EntitySystems/InstrumentSystem.cs b/Content.Shared/GameObjects/EntitySystems/SharedInstrumentSystem.cs similarity index 100% rename from Content.Client/GameObjects/EntitySystems/InstrumentSystem.cs rename to Content.Shared/GameObjects/EntitySystems/SharedInstrumentSystem.cs From 400a71f8a17cef952ecb6775af88cb4944975a8e Mon Sep 17 00:00:00 2001 From: zumorica Date: Wed, 20 May 2020 15:15:17 +0200 Subject: [PATCH 03/14] More refactors --- .../Instruments/InstrumentComponent.cs | 45 +++++++++++++++---- .../Instruments/InstrumentComponent.cs | 16 +++++++ .../Instruments/SharedInstrumentComponent.cs | 12 +++++ .../EntitySystems/SharedInstrumentSystem.cs | 8 ++-- 4 files changed, 69 insertions(+), 12 deletions(-) diff --git a/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs b/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs index 7d06a4d41f..039290ccd5 100644 --- a/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs +++ b/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs @@ -33,7 +33,7 @@ namespace Content.Client.GameObjects.Components.Instruments [CanBeNull] private IMidiRenderer _renderer; - private int _instrumentProgram = 1; + private byte _instrumentProgram = 1; [ViewVariables] private readonly Queue> _midiQueue = new Queue<(MidiEvent, double)>(); @@ -66,7 +66,7 @@ namespace Content.Client.GameObjects.Components.Instruments /// Changes the instrument the midi renderer will play. /// [ViewVariables(VVAccess.ReadWrite)] - public int InstrumentProgram + public byte InstrumentProgram { get => _instrumentProgram; set @@ -91,10 +91,23 @@ namespace Content.Client.GameObjects.Components.Instruments [ViewVariables] public bool IsInputOpen => _renderer?.Status == MidiRendererStatus.Input; + /// + /// Whether the midi renderer is alive or not. + /// + [ViewVariables] + public bool IsRendererAlive => _renderer != null; + public override void Initialize() { base.Initialize(); IoCManager.InjectDependencies(this); + } + + protected void SetupRenderer() + { + if (IsRendererAlive) + return; + _renderer = _midiManager.GetNewRenderer(); if (_renderer != null) @@ -105,16 +118,22 @@ namespace Content.Client.GameObjects.Components.Instruments } } + protected void EndRenderer() + { + Timer.Spawn(1000, () => { _renderer?.Dispose(); }); + _renderer = null; + } + protected override void Shutdown() { base.Shutdown(); - _renderer?.Dispose(); + EndRenderer(); } public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); - serializer.DataField(ref _instrumentProgram, "program", 1); + serializer.DataField(ref _instrumentProgram, "program", (byte)1); } public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession session = null) @@ -130,7 +149,7 @@ namespace Content.Client.GameObjects.Components.Instruments { case InstrumentMidiEventMessage midiEventMessage: // If we're the ones sending the MidiEvents, we ignore this message. - if (IsInputOpen || IsMidiOpen) break; + if (!IsRendererAlive || IsInputOpen || IsMidiOpen) break; for (var i = 0; i < midiEventMessage.MidiEvent.Length; i++) { //_midiQueue.Enqueue((midiEventMessage.MidiEvent[i], (i == 0 ? 0 : 0) + _gameTiming.CurTime.TotalSeconds - midiEventMessage.Timestamp[i])); @@ -146,12 +165,19 @@ namespace Content.Client.GameObjects.Components.Instruments if (IsInputOpen) CloseInput(); if (IsMidiOpen) CloseMidi(); break; + + case InstrumentStartMidiMessage _: + SetupRenderer(); + break; } } /// public bool OpenInput() { + SetupRenderer(); + SendNetworkMessage(new InstrumentStartMidiMessage()); + if (_renderer != null && _renderer.OpenInput()) { _renderer.OnMidiEvent += RendererOnMidiEvent; @@ -169,13 +195,16 @@ namespace Content.Client.GameObjects.Components.Instruments return false; } - _renderer.OnMidiEvent -= RendererOnMidiEvent; + EndRenderer(); return true; } /// public bool OpenMidi(string filename) { + SetupRenderer(); + SendNetworkMessage(new InstrumentStartMidiMessage()); + if (_renderer == null || !_renderer.OpenMidi(filename)) { return false; @@ -193,7 +222,7 @@ namespace Content.Client.GameObjects.Components.Instruments return false; } - _renderer.OnMidiEvent -= RendererOnMidiEvent; + EndRenderer(); return true; } @@ -206,7 +235,7 @@ namespace Content.Client.GameObjects.Components.Instruments _midiQueue.Enqueue((midiEvent, _gameTiming.CurTime.TotalSeconds)); } - public void Update(float delta) + public override void Update(float delta) { _timer -= delta; diff --git a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs index b6512b8ee6..e7c01d61a8 100644 --- a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs +++ b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs @@ -18,12 +18,17 @@ namespace Content.Server.GameObjects.Components.Instruments public class InstrumentComponent : SharedInstrumentComponent, IDropped, IHandSelected, IHandDeselected, IActivate, IUse, IThrown { + public const int MidiEventsPerSecond = 10; + /// /// The client channel currently playing the instrument, or null if there's none. /// private ICommonSession _instrumentPlayer; private bool _handheld; + private float _timer = 0f; + private int _midiEventCount = 1; + [ViewVariables] private BoundUserInterface _userInterface; @@ -57,6 +62,12 @@ namespace Content.Server.GameObjects.Components.Instruments case InstrumentMidiEventMessage midiEventMsg: SendNetworkMessage(midiEventMsg); break; + case InstrumentStartMidiMessage startMidi: + SendNetworkMessage(startMidi); + break; + case InstrumentStopMidiMessage stopMidi: + SendNetworkMessage(stopMidi); + break; } } @@ -124,5 +135,10 @@ namespace Content.Server.GameObjects.Components.Instruments { _userInterface.Open(session); } + + public override void Update(float delta) + { + base.Update(delta); + } } } diff --git a/Content.Shared/GameObjects/Components/Instruments/SharedInstrumentComponent.cs b/Content.Shared/GameObjects/Components/Instruments/SharedInstrumentComponent.cs index 9734425b3c..96d3ab9fc4 100644 --- a/Content.Shared/GameObjects/Components/Instruments/SharedInstrumentComponent.cs +++ b/Content.Shared/GameObjects/Components/Instruments/SharedInstrumentComponent.cs @@ -9,6 +9,10 @@ namespace Content.Shared.GameObjects.Components.Instruments { public override string Name => "Instrument"; public override uint? NetID => ContentNetIDs.INSTRUMENTS; + + public virtual void Update(float delta) + { + } } @@ -20,6 +24,14 @@ namespace Content.Shared.GameObjects.Components.Instruments { } + /// + /// This message is sent to the client to start the synth. + /// + [Serializable, NetSerializable] + public class InstrumentStartMidiMessage : ComponentMessage + { + } + /// /// This message carries a MidiEvent to be played on clients. /// diff --git a/Content.Shared/GameObjects/EntitySystems/SharedInstrumentSystem.cs b/Content.Shared/GameObjects/EntitySystems/SharedInstrumentSystem.cs index 6303475c30..577d1542bd 100644 --- a/Content.Shared/GameObjects/EntitySystems/SharedInstrumentSystem.cs +++ b/Content.Shared/GameObjects/EntitySystems/SharedInstrumentSystem.cs @@ -1,15 +1,15 @@ -using Content.Client.GameObjects.Components.Instruments; +using Content.Shared.GameObjects.Components.Instruments; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; -namespace Content.Client.GameObjects.EntitySystems +namespace Content.Shared.GameObjects.EntitySystems { public class InstrumentSystem : EntitySystem { public override void Initialize() { base.Initialize(); - EntityQuery = new TypeEntityQuery(typeof(InstrumentComponent)); + EntityQuery = new TypeEntityQuery(typeof(SharedInstrumentComponent)); } public override void Update(float frameTime) @@ -18,7 +18,7 @@ namespace Content.Client.GameObjects.EntitySystems foreach (var entity in RelevantEntities) { - entity.GetComponent().Update(frameTime); + entity.GetComponent().Update(frameTime); } } } From 731ab22568664b77d6f1dda5c71f47bcb772705e Mon Sep 17 00:00:00 2001 From: zumorica Date: Wed, 20 May 2020 17:13:49 +0200 Subject: [PATCH 04/14] Better timing --- .../Instruments/InstrumentComponent.cs | 61 +++++++++---------- .../EntitySystems/InstrumentSystem.cs | 8 +-- .../Instruments/InstrumentComponent.cs | 30 +++++++-- .../EntitySystems/InstrumentSystem.cs | 25 ++++++++ .../Instruments/SharedInstrumentComponent.cs | 5 +- 5 files changed, 86 insertions(+), 43 deletions(-) rename Content.Shared/GameObjects/EntitySystems/SharedInstrumentSystem.cs => Content.Client/GameObjects/EntitySystems/InstrumentSystem.cs (60%) create mode 100644 Content.Server/GameObjects/EntitySystems/InstrumentSystem.cs diff --git a/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs b/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs index 039290ccd5..59b77de12d 100644 --- a/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs +++ b/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs @@ -2,15 +2,17 @@ using System; using System.Collections.Generic; using Content.Shared.GameObjects.Components.Instruments; using JetBrains.Annotations; +using NFluidsynth; using Robust.Shared.GameObjects; using Robust.Client.Audio.Midi; -using Robust.Shared.Audio.Midi; using Robust.Shared.Interfaces.Network; using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; using Robust.Shared.Players; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; +using Logger = Robust.Shared.Log.Logger; +using MidiEvent = Robust.Shared.Audio.Midi.MidiEvent; using Timer = Robust.Shared.Timers.Timer; @@ -19,7 +21,7 @@ namespace Content.Client.GameObjects.Components.Instruments [RegisterComponent] public class InstrumentComponent : SharedInstrumentComponent { - public const float TimeBetweenNetMessages = 1f; + public const float TimeBetweenNetMessages = 0.5f; /// /// Called when a midi song stops playing. @@ -35,16 +37,16 @@ namespace Content.Client.GameObjects.Components.Instruments private IMidiRenderer _renderer; private byte _instrumentProgram = 1; + /// + /// A queue of MidiEvents to be sent to the server. + /// [ViewVariables] - private readonly Queue> _midiQueue = new Queue<(MidiEvent, double)>(); + private readonly Queue _midiQueue = new Queue(); [ViewVariables] private float _timer = 0f; - /// - /// A queue of MidiEvents to be sent to the server. - /// - private Queue _eventQueue = new Queue(); + private TimeSpan? _lastEvent = null; /// /// Whether a midi song will loop or not. @@ -140,11 +142,6 @@ namespace Content.Client.GameObjects.Components.Instruments { base.HandleNetworkMessage(message, channel, session); - if (_renderer == null) - { - return; - } - switch (message) { case InstrumentMidiEventMessage midiEventMessage: @@ -152,22 +149,27 @@ namespace Content.Client.GameObjects.Components.Instruments if (!IsRendererAlive || IsInputOpen || IsMidiOpen) break; for (var i = 0; i < midiEventMessage.MidiEvent.Length; i++) { - //_midiQueue.Enqueue((midiEventMessage.MidiEvent[i], (i == 0 ? 0 : 0) + _gameTiming.CurTime.TotalSeconds - midiEventMessage.Timestamp[i])); + var ev = midiEventMessage.MidiEvent[i]; + var delta = i != 0 ? ev.Timestamp.Subtract(midiEventMessage.MidiEvent[i-1].Timestamp) : _lastEvent.HasValue ? ev.Timestamp.Subtract(_lastEvent.Value) : TimeSpan.Zero; + ev.Timestamp = _gameTiming.CurTime + delta + TimeSpan.FromSeconds(TimeBetweenNetMessages*1.25); + _midiQueue.Enqueue(ev); + _lastEvent = ev.Timestamp; - var j = i; - Timer.Spawn((int) ((TimeBetweenNetMessages)*1.5f + _gameTiming.CurTime.TotalSeconds - midiEventMessage.Timestamp[i])*1000, - () => _renderer.SendMidiEvent(midiEventMessage.MidiEvent[j])); + //var j = i; + //Timer.Spawn((int) ((TimeBetweenNetMessages)*1.5f)*1000, + // () => _renderer.SendMidiEvent(midiEventMessage.MidiEvent[j])); } break; case InstrumentStopMidiMessage _: - _renderer.StopAllNotes(); + _renderer?.StopAllNotes(); if (IsInputOpen) CloseInput(); if (IsMidiOpen) CloseMidi(); break; case InstrumentStartMidiMessage _: SetupRenderer(); + Logger.Info("INITIALIZED MIDI RENDERER. I HOPE."); break; } } @@ -196,6 +198,7 @@ namespace Content.Client.GameObjects.Components.Instruments } EndRenderer(); + SendNetworkMessage(new InstrumentStopMidiMessage()); return true; } @@ -223,6 +226,7 @@ namespace Content.Client.GameObjects.Components.Instruments } EndRenderer(); + SendNetworkMessage(new InstrumentStopMidiMessage()); return true; } @@ -232,7 +236,8 @@ namespace Content.Client.GameObjects.Components.Instruments /// The received midi event private void RendererOnMidiEvent(MidiEvent midiEvent) { - _midiQueue.Enqueue((midiEvent, _gameTiming.CurTime.TotalSeconds)); + midiEvent.Timestamp = _gameTiming.CurTime; + _midiQueue.Enqueue(midiEvent); } public override void Update(float delta) @@ -243,7 +248,7 @@ namespace Content.Client.GameObjects.Components.Instruments if (!IsMidiOpen && !IsInputOpen) { - //UpdatePlaying(delta); + UpdatePlaying(delta); return; } @@ -254,25 +259,19 @@ namespace Content.Client.GameObjects.Components.Instruments private void UpdatePlaying(float delta) { if(_renderer == null || _midiQueue.Count == 0) return; - var (midiEvent, timestamp) = _midiQueue.Dequeue(); + var midiEvent = _midiQueue.Dequeue(); _renderer.SendMidiEvent(midiEvent); - _timer = _midiQueue.Count != 0 ? (float) (_midiQueue.Peek().Item2) : 0; + _timer = _midiQueue.Count != 0 ? (float) (midiEvent.Timestamp.Subtract(_gameTiming.CurTime).TotalSeconds) : 0; // ???? TODO: fix this } private void SendAllMidiMessages() { var count = _midiQueue.Count; - var events = new MidiEvent[count]; - var timestamps = new double[count]; + if (count == 0) return; + var events = _midiQueue.ToArray(); + _midiQueue.Clear(); - for (var i = 0; i < count; i++) - { - var (midiEvent, timestamp) = _midiQueue.Dequeue(); - events[i] = midiEvent; - timestamps[i] = timestamp; - } - - SendNetworkMessage(new InstrumentMidiEventMessage(events, timestamps)); + SendNetworkMessage(new InstrumentMidiEventMessage(events)); } } } diff --git a/Content.Shared/GameObjects/EntitySystems/SharedInstrumentSystem.cs b/Content.Client/GameObjects/EntitySystems/InstrumentSystem.cs similarity index 60% rename from Content.Shared/GameObjects/EntitySystems/SharedInstrumentSystem.cs rename to Content.Client/GameObjects/EntitySystems/InstrumentSystem.cs index 577d1542bd..6303475c30 100644 --- a/Content.Shared/GameObjects/EntitySystems/SharedInstrumentSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/InstrumentSystem.cs @@ -1,15 +1,15 @@ -using Content.Shared.GameObjects.Components.Instruments; +using Content.Client.GameObjects.Components.Instruments; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; -namespace Content.Shared.GameObjects.EntitySystems +namespace Content.Client.GameObjects.EntitySystems { public class InstrumentSystem : EntitySystem { public override void Initialize() { base.Initialize(); - EntityQuery = new TypeEntityQuery(typeof(SharedInstrumentComponent)); + EntityQuery = new TypeEntityQuery(typeof(InstrumentComponent)); } public override void Update(float frameTime) @@ -18,7 +18,7 @@ namespace Content.Shared.GameObjects.EntitySystems foreach (var entity in RelevantEntities) { - entity.GetComponent().Update(frameTime); + entity.GetComponent().Update(frameTime); } } } diff --git a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs index e7c01d61a8..32768d10ae 100644 --- a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs +++ b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs @@ -1,5 +1,6 @@ using Content.Server.GameObjects.EntitySystems; using Content.Shared.GameObjects.Components.Instruments; +using NFluidsynth; using Robust.Server.GameObjects; using Robust.Server.GameObjects.Components.UserInterface; using Robust.Server.Interfaces.GameObjects; @@ -18,7 +19,7 @@ namespace Content.Server.GameObjects.Components.Instruments public class InstrumentComponent : SharedInstrumentComponent, IDropped, IHandSelected, IHandDeselected, IActivate, IUse, IThrown { - public const int MidiEventsPerSecond = 10; + public const int MaxMidiEventsPerSecond = 20; /// /// The client channel currently playing the instrument, or null if there's none. @@ -26,8 +27,13 @@ namespace Content.Server.GameObjects.Components.Instruments private ICommonSession _instrumentPlayer; private bool _handheld; + [ViewVariables] + private bool _playing = false; + private float _timer = 0f; - private int _midiEventCount = 1; + + [ViewVariables] + private int _midiEventCount = 0; [ViewVariables] private BoundUserInterface _userInterface; @@ -60,19 +66,25 @@ namespace Content.Server.GameObjects.Components.Instruments switch (message) { case InstrumentMidiEventMessage midiEventMsg: - SendNetworkMessage(midiEventMsg); + if (!_playing) + return; + if(++_midiEventCount <= MaxMidiEventsPerSecond) + SendNetworkMessage(new InstrumentMidiEventMessage(midiEventMsg.MidiEvent)); break; case InstrumentStartMidiMessage startMidi: - SendNetworkMessage(startMidi); + _playing = true; + SendNetworkMessage(new InstrumentStartMidiMessage()); break; case InstrumentStopMidiMessage stopMidi: - SendNetworkMessage(stopMidi); + _playing = false; + SendNetworkMessage(new InstrumentStopMidiMessage()); break; } } public void Dropped(DroppedEventArgs eventArgs) { + _playing = false; SendNetworkMessage(new InstrumentStopMidiMessage()); _instrumentPlayer = null; _userInterface.CloseAll(); @@ -80,6 +92,7 @@ namespace Content.Server.GameObjects.Components.Instruments public void Thrown(ThrownEventArgs eventArgs) { + _playing = false; SendNetworkMessage(new InstrumentStopMidiMessage()); _instrumentPlayer = null; _userInterface.CloseAll(); @@ -96,6 +109,7 @@ namespace Content.Server.GameObjects.Components.Instruments public void HandDeselected(HandDeselectedEventArgs eventArgs) { + _playing = false; SendNetworkMessage(new InstrumentStopMidiMessage()); _userInterface.CloseAll(); } @@ -128,6 +142,7 @@ namespace Content.Server.GameObjects.Components.Instruments { _instrumentPlayer = null; SendNetworkMessage(new InstrumentStopMidiMessage()); + _playing = false; } } @@ -139,6 +154,11 @@ namespace Content.Server.GameObjects.Components.Instruments public override void Update(float delta) { base.Update(delta); + + _timer += delta; + if (_timer < 1) return; + _timer = 0f; + _midiEventCount = 0; } } } diff --git a/Content.Server/GameObjects/EntitySystems/InstrumentSystem.cs b/Content.Server/GameObjects/EntitySystems/InstrumentSystem.cs new file mode 100644 index 0000000000..a64f1fec01 --- /dev/null +++ b/Content.Server/GameObjects/EntitySystems/InstrumentSystem.cs @@ -0,0 +1,25 @@ +using Content.Server.GameObjects.Components.Instruments; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; + +namespace Content.Server.GameObjects.EntitySystems +{ + public class InstrumentSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + EntityQuery = new TypeEntityQuery(typeof(InstrumentComponent)); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + foreach (var entity in RelevantEntities) + { + entity.GetComponent().Update(frameTime); + } + } + } +} diff --git a/Content.Shared/GameObjects/Components/Instruments/SharedInstrumentComponent.cs b/Content.Shared/GameObjects/Components/Instruments/SharedInstrumentComponent.cs index 96d3ab9fc4..385a5a9b48 100644 --- a/Content.Shared/GameObjects/Components/Instruments/SharedInstrumentComponent.cs +++ b/Content.Shared/GameObjects/Components/Instruments/SharedInstrumentComponent.cs @@ -30,6 +30,7 @@ namespace Content.Shared.GameObjects.Components.Instruments [Serializable, NetSerializable] public class InstrumentStartMidiMessage : ComponentMessage { + } /// @@ -39,12 +40,10 @@ namespace Content.Shared.GameObjects.Components.Instruments public class InstrumentMidiEventMessage : ComponentMessage { public MidiEvent[] MidiEvent; - public double[] Timestamp; - public InstrumentMidiEventMessage(MidiEvent[] midiEvent, double[] timestamp) + public InstrumentMidiEventMessage(MidiEvent[] midiEvent) { MidiEvent = midiEvent; - Timestamp = timestamp; } } From 6db9ac7247715608968c0acb60188e7d955d7d39 Mon Sep 17 00:00:00 2001 From: zumorica Date: Wed, 20 May 2020 17:48:36 +0200 Subject: [PATCH 05/14] Improved timing --- .../Instruments/InstrumentComponent.cs | 33 ++++++++++++------- .../Instruments/InstrumentComponent.cs | 6 ++-- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs b/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs index 59b77de12d..fe10eb1dec 100644 --- a/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs +++ b/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Content.Shared.GameObjects.Components.Instruments; using JetBrains.Annotations; using NFluidsynth; @@ -21,7 +22,7 @@ namespace Content.Client.GameObjects.Components.Instruments [RegisterComponent] public class InstrumentComponent : SharedInstrumentComponent { - public const float TimeBetweenNetMessages = 0.5f; + public const float TimeBetweenNetMessages = 1.0f; /// /// Called when a midi song stops playing. @@ -116,7 +117,7 @@ namespace Content.Client.GameObjects.Components.Instruments { _renderer.MidiProgram = _instrumentProgram; _renderer.TrackingEntity = Owner; - _renderer.OnMidiPlayerFinished += () => { OnMidiPlaybackEnded?.Invoke(); }; + _renderer.OnMidiPlayerFinished += () => { OnMidiPlaybackEnded?.Invoke(); EndRenderer(); SendNetworkMessage(new InstrumentStopMidiMessage()); }; } } @@ -124,6 +125,7 @@ namespace Content.Client.GameObjects.Components.Instruments { Timer.Spawn(1000, () => { _renderer?.Dispose(); }); _renderer = null; + _midiQueue.Clear(); } protected override void Shutdown() @@ -147,24 +149,28 @@ namespace Content.Client.GameObjects.Components.Instruments case InstrumentMidiEventMessage midiEventMessage: // If we're the ones sending the MidiEvents, we ignore this message. if (!IsRendererAlive || IsInputOpen || IsMidiOpen) break; + Logger.Info($"NEW BATCH!!! LENGTH:{midiEventMessage.MidiEvent.Length} QUEUED:{_midiQueue.Count} LAST:{_lastEvent}"); for (var i = 0; i < midiEventMessage.MidiEvent.Length; i++) { var ev = midiEventMessage.MidiEvent[i]; - var delta = i != 0 ? ev.Timestamp.Subtract(midiEventMessage.MidiEvent[i-1].Timestamp) : _lastEvent.HasValue ? ev.Timestamp.Subtract(_lastEvent.Value) : TimeSpan.Zero; + var delta = i != 0 ? + ev.Timestamp.Subtract(midiEventMessage.MidiEvent[i-1].Timestamp) : _lastEvent.HasValue ? ev.Timestamp.Subtract(_lastEvent.Value) : TimeSpan.Zero; ev.Timestamp = _gameTiming.CurTime + delta + TimeSpan.FromSeconds(TimeBetweenNetMessages*1.25); _midiQueue.Enqueue(ev); _lastEvent = ev.Timestamp; //var j = i; - //Timer.Spawn((int) ((TimeBetweenNetMessages)*1.5f)*1000, - // () => _renderer.SendMidiEvent(midiEventMessage.MidiEvent[j])); + //Timer.Spawn((int)ev.Timestamp.Subtract(_gameTiming.CurTime).TotalMilliseconds, + // () => _renderer?.SendMidiEvent(midiEventMessage.MidiEvent[j])); } + + + break; case InstrumentStopMidiMessage _: _renderer?.StopAllNotes(); - if (IsInputOpen) CloseInput(); - if (IsMidiOpen) CloseMidi(); + EndRenderer(); break; case InstrumentStartMidiMessage _: @@ -258,10 +264,15 @@ namespace Content.Client.GameObjects.Components.Instruments private void UpdatePlaying(float delta) { - if(_renderer == null || _midiQueue.Count == 0) return; - var midiEvent = _midiQueue.Dequeue(); - _renderer.SendMidiEvent(midiEvent); - _timer = _midiQueue.Count != 0 ? (float) (midiEvent.Timestamp.Subtract(_gameTiming.CurTime).TotalSeconds) : 0; // ???? TODO: fix this + while (true) + { + if (_renderer == null || _midiQueue.Count == 0) return; + var midiEvent = _midiQueue.Dequeue(); + _renderer.SendMidiEvent(midiEvent); + _timer = _midiQueue.Count != 0 ? (MathF.Max((float) midiEvent.Timestamp.Subtract(_gameTiming.CurTime).TotalSeconds, 0f)) : 0; + if (_timer <= 0f) continue; + break; + } } private void SendAllMidiMessages() diff --git a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs index 32768d10ae..1fd5e8eff6 100644 --- a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs +++ b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs @@ -69,15 +69,15 @@ namespace Content.Server.GameObjects.Components.Instruments if (!_playing) return; if(++_midiEventCount <= MaxMidiEventsPerSecond) - SendNetworkMessage(new InstrumentMidiEventMessage(midiEventMsg.MidiEvent)); + SendNetworkMessage(midiEventMsg); break; case InstrumentStartMidiMessage startMidi: _playing = true; - SendNetworkMessage(new InstrumentStartMidiMessage()); + SendNetworkMessage(startMidi); break; case InstrumentStopMidiMessage stopMidi: _playing = false; - SendNetworkMessage(new InstrumentStopMidiMessage()); + SendNetworkMessage(stopMidi); break; } } From 1e36851dde2120d2dc4cffba282af55c2ed2ed79 Mon Sep 17 00:00:00 2001 From: zumorica Date: Thu, 21 May 2020 00:26:43 +0200 Subject: [PATCH 06/14] Timing boogaloo --- .../Instruments/InstrumentComponent.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs b/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs index fe10eb1dec..27a45c89a8 100644 --- a/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs +++ b/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs @@ -123,7 +123,14 @@ namespace Content.Client.GameObjects.Components.Instruments protected void EndRenderer() { - Timer.Spawn(1000, () => { _renderer?.Dispose(); }); + if (IsInputOpen) + CloseInput(); + + if (IsMidiOpen) + CloseMidi(); + + var renderer = _renderer; + Timer.Spawn(1000, () => { renderer?.Dispose(); }); _renderer = null; _midiQueue.Clear(); } @@ -149,13 +156,15 @@ namespace Content.Client.GameObjects.Components.Instruments case InstrumentMidiEventMessage midiEventMessage: // If we're the ones sending the MidiEvents, we ignore this message. if (!IsRendererAlive || IsInputOpen || IsMidiOpen) break; + var curTime = _gameTiming.CurTime; Logger.Info($"NEW BATCH!!! LENGTH:{midiEventMessage.MidiEvent.Length} QUEUED:{_midiQueue.Count} LAST:{_lastEvent}"); for (var i = 0; i < midiEventMessage.MidiEvent.Length; i++) { var ev = midiEventMessage.MidiEvent[i]; var delta = i != 0 ? ev.Timestamp.Subtract(midiEventMessage.MidiEvent[i-1].Timestamp) : _lastEvent.HasValue ? ev.Timestamp.Subtract(_lastEvent.Value) : TimeSpan.Zero; - ev.Timestamp = _gameTiming.CurTime + delta + TimeSpan.FromSeconds(TimeBetweenNetMessages*1.25); + ev.Timestamp = curTime + TimeSpan.FromSeconds(TimeBetweenNetMessages*1.25); + Logger.Info($"DT:{delta} TIM:{ev.Timestamp} TIMR:{midiEventMessage.MidiEvent[i].Timestamp} LST:{midiEventMessage.MidiEvent[Math.Max(0, i-1)].Timestamp}"); _midiQueue.Enqueue(ev); _lastEvent = ev.Timestamp; @@ -169,7 +178,6 @@ namespace Content.Client.GameObjects.Components.Instruments break; case InstrumentStopMidiMessage _: - _renderer?.StopAllNotes(); EndRenderer(); break; @@ -269,7 +277,7 @@ namespace Content.Client.GameObjects.Components.Instruments if (_renderer == null || _midiQueue.Count == 0) return; var midiEvent = _midiQueue.Dequeue(); _renderer.SendMidiEvent(midiEvent); - _timer = _midiQueue.Count != 0 ? (MathF.Max((float) midiEvent.Timestamp.Subtract(_gameTiming.CurTime).TotalSeconds, 0f)) : 0; + _timer = _midiQueue.Count != 0 ? (MathF.Max((float) _midiQueue.Peek().Timestamp.Subtract(_gameTiming.CurTime).TotalSeconds, 0f)) : 0; if (_timer <= 0f) continue; break; } From 78aa4fb8724dc984a1facf6558ef85e6b0162bde Mon Sep 17 00:00:00 2001 From: zumorica Date: Thu, 21 May 2020 15:20:37 +0200 Subject: [PATCH 07/14] Use Sequencer, improve MIDI a lot --- .../Instruments/InstrumentComponent.cs | 74 +++++++------------ .../Instruments/InstrumentComponent.cs | 42 ++++++++--- .../Instruments/SharedInstrumentComponent.cs | 14 ++++ 3 files changed, 73 insertions(+), 57 deletions(-) diff --git a/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs b/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs index 27a45c89a8..1f35d766bc 100644 --- a/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs +++ b/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs @@ -47,8 +47,6 @@ namespace Content.Client.GameObjects.Components.Instruments [ViewVariables] private float _timer = 0f; - private TimeSpan? _lastEvent = null; - /// /// Whether a midi song will loop or not. /// @@ -129,8 +127,12 @@ namespace Content.Client.GameObjects.Components.Instruments if (IsMidiOpen) CloseMidi(); + _renderer?.StopAllNotes(); + var renderer = _renderer; - Timer.Spawn(1000, () => { renderer?.Dispose(); }); + + // We dispose of the synth two seconds from now to allow the last notes to stop from playing. + Timer.Spawn(2000, () => { renderer?.Dispose(); }); _renderer = null; _midiQueue.Clear(); } @@ -156,38 +158,32 @@ namespace Content.Client.GameObjects.Components.Instruments case InstrumentMidiEventMessage midiEventMessage: // If we're the ones sending the MidiEvents, we ignore this message. if (!IsRendererAlive || IsInputOpen || IsMidiOpen) break; - var curTime = _gameTiming.CurTime; - Logger.Info($"NEW BATCH!!! LENGTH:{midiEventMessage.MidiEvent.Length} QUEUED:{_midiQueue.Count} LAST:{_lastEvent}"); for (var i = 0; i < midiEventMessage.MidiEvent.Length; i++) { var ev = midiEventMessage.MidiEvent[i]; - var delta = i != 0 ? - ev.Timestamp.Subtract(midiEventMessage.MidiEvent[i-1].Timestamp) : _lastEvent.HasValue ? ev.Timestamp.Subtract(_lastEvent.Value) : TimeSpan.Zero; - ev.Timestamp = curTime + TimeSpan.FromSeconds(TimeBetweenNetMessages*1.25); - Logger.Info($"DT:{delta} TIM:{ev.Timestamp} TIMR:{midiEventMessage.MidiEvent[i].Timestamp} LST:{midiEventMessage.MidiEvent[Math.Max(0, i-1)].Timestamp}"); - _midiQueue.Enqueue(ev); - _lastEvent = ev.Timestamp; + var delta = ((uint)TimeBetweenNetMessages*1250) + ev.Timestamp; - //var j = i; - //Timer.Spawn((int)ev.Timestamp.Subtract(_gameTiming.CurTime).TotalMilliseconds, - // () => _renderer?.SendMidiEvent(midiEventMessage.MidiEvent[j])); + _renderer?.ScheduleMidiEvent(ev, delta, true); } - - - - break; - - case InstrumentStopMidiMessage _: - EndRenderer(); - break; - - case InstrumentStartMidiMessage _: - SetupRenderer(); - Logger.Info("INITIALIZED MIDI RENDERER. I HOPE."); break; } } + public override void HandleComponentState(ComponentState curState, ComponentState nextState) + { + base.HandleComponentState(curState, nextState); + if (!(curState is InstrumentState state)) return; + + if (state.Playing) + { + Logger.Info($"WE GOT STATE: {state.Playing} {state.SequencerTick}"); + SetupRenderer(); + if (_renderer != null) _renderer.SequencerTick = state.SequencerTick; + } + else + EndRenderer(); + } + /// public bool OpenInput() { @@ -250,43 +246,25 @@ namespace Content.Client.GameObjects.Components.Instruments /// The received midi event private void RendererOnMidiEvent(MidiEvent midiEvent) { - midiEvent.Timestamp = _gameTiming.CurTime; _midiQueue.Enqueue(midiEvent); } public override void Update(float delta) { + if (!IsMidiOpen && !IsInputOpen) + return; + _timer -= delta; if (_timer > 0f) return; - if (!IsMidiOpen && !IsInputOpen) - { - UpdatePlaying(delta); - return; - } - SendAllMidiMessages(); _timer = TimeBetweenNetMessages; } - private void UpdatePlaying(float delta) - { - while (true) - { - if (_renderer == null || _midiQueue.Count == 0) return; - var midiEvent = _midiQueue.Dequeue(); - _renderer.SendMidiEvent(midiEvent); - _timer = _midiQueue.Count != 0 ? (MathF.Max((float) _midiQueue.Peek().Timestamp.Subtract(_gameTiming.CurTime).TotalSeconds, 0f)) : 0; - if (_timer <= 0f) continue; - break; - } - } - private void SendAllMidiMessages() { - var count = _midiQueue.Count; - if (count == 0) return; + if (_midiQueue.Count == 0) return; var events = _midiQueue.ToArray(); _midiQueue.Clear(); diff --git a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs index 1fd5e8eff6..81be788b5a 100644 --- a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs +++ b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs @@ -24,14 +24,19 @@ namespace Content.Server.GameObjects.Components.Instruments /// /// The client channel currently playing the instrument, or null if there's none. /// + [ViewVariables] private ICommonSession _instrumentPlayer; private bool _handheld; [ViewVariables] private bool _playing = false; + [ViewVariables] private float _timer = 0f; + [ViewVariables] + public uint _lastSequencerTick = 0; + [ViewVariables] private int _midiEventCount = 0; @@ -44,6 +49,20 @@ namespace Content.Server.GameObjects.Components.Instruments [ViewVariables] public bool Handheld => _handheld; + /// + /// Whether the instrument is currently playing or not. + /// + [ViewVariables] + public bool Playing + { + get => _playing; + set + { + _playing = value; + Dirty(); + } + } + public override void Initialize() { base.Initialize(); @@ -57,6 +76,11 @@ namespace Content.Server.GameObjects.Components.Instruments serializer.DataField(ref _handheld, "handheld", false); } + public override ComponentState GetComponentState() + { + return new InstrumentState(Playing, _lastSequencerTick); + } + public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession session = null) { base.HandleNetworkMessage(message, channel, session); @@ -66,25 +90,25 @@ namespace Content.Server.GameObjects.Components.Instruments switch (message) { case InstrumentMidiEventMessage midiEventMsg: - if (!_playing) + if (!Playing) return; if(++_midiEventCount <= MaxMidiEventsPerSecond) SendNetworkMessage(midiEventMsg); + + _lastSequencerTick = midiEventMsg.MidiEvent[-1].Timestamp; break; case InstrumentStartMidiMessage startMidi: - _playing = true; - SendNetworkMessage(startMidi); + Playing = true; break; case InstrumentStopMidiMessage stopMidi: - _playing = false; - SendNetworkMessage(stopMidi); + Playing = false; break; } } public void Dropped(DroppedEventArgs eventArgs) { - _playing = false; + Playing = false; SendNetworkMessage(new InstrumentStopMidiMessage()); _instrumentPlayer = null; _userInterface.CloseAll(); @@ -92,7 +116,7 @@ namespace Content.Server.GameObjects.Components.Instruments public void Thrown(ThrownEventArgs eventArgs) { - _playing = false; + Playing = false; SendNetworkMessage(new InstrumentStopMidiMessage()); _instrumentPlayer = null; _userInterface.CloseAll(); @@ -109,7 +133,7 @@ namespace Content.Server.GameObjects.Components.Instruments public void HandDeselected(HandDeselectedEventArgs eventArgs) { - _playing = false; + Playing = false; SendNetworkMessage(new InstrumentStopMidiMessage()); _userInterface.CloseAll(); } @@ -142,7 +166,7 @@ namespace Content.Server.GameObjects.Components.Instruments { _instrumentPlayer = null; SendNetworkMessage(new InstrumentStopMidiMessage()); - _playing = false; + Playing = false; } } diff --git a/Content.Shared/GameObjects/Components/Instruments/SharedInstrumentComponent.cs b/Content.Shared/GameObjects/Components/Instruments/SharedInstrumentComponent.cs index 385a5a9b48..ef94e31e86 100644 --- a/Content.Shared/GameObjects/Components/Instruments/SharedInstrumentComponent.cs +++ b/Content.Shared/GameObjects/Components/Instruments/SharedInstrumentComponent.cs @@ -1,4 +1,5 @@ using System; +using Content.Shared.BodySystem; using Robust.Shared.Audio.Midi; using Robust.Shared.GameObjects; using Robust.Shared.Serialization; @@ -47,6 +48,19 @@ namespace Content.Shared.GameObjects.Components.Instruments } } + [Serializable, NetSerializable] + public class InstrumentState : ComponentState + { + public bool Playing { get; } + public uint SequencerTick { get; } + + public InstrumentState(bool playing, uint sequencerTick = 0) : base(ContentNetIDs.INSTRUMENTS) + { + Playing = playing; + SequencerTick = sequencerTick; + } + } + [NetSerializable, Serializable] public enum InstrumentUiKey { From 0235f6096e060d423cd3b12108bc2eaff6922101 Mon Sep 17 00:00:00 2001 From: zumorica Date: Thu, 21 May 2020 17:39:48 +0200 Subject: [PATCH 08/14] Sync sequencer tick correctly --- .../Components/Instruments/InstrumentComponent.cs | 6 +++--- .../Components/Instruments/InstrumentComponent.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs b/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs index 1f35d766bc..08974e3867 100644 --- a/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs +++ b/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs @@ -37,6 +37,7 @@ namespace Content.Client.GameObjects.Components.Instruments [CanBeNull] private IMidiRenderer _renderer; private byte _instrumentProgram = 1; + private uint _syncSequencerTick; /// /// A queue of MidiEvents to be sent to the server. @@ -161,8 +162,7 @@ namespace Content.Client.GameObjects.Components.Instruments for (var i = 0; i < midiEventMessage.MidiEvent.Length; i++) { var ev = midiEventMessage.MidiEvent[i]; - var delta = ((uint)TimeBetweenNetMessages*1250) + ev.Timestamp; - + var delta = ((uint)TimeBetweenNetMessages*1250) + ev.Timestamp - _syncSequencerTick; _renderer?.ScheduleMidiEvent(ev, delta, true); } break; @@ -178,7 +178,7 @@ namespace Content.Client.GameObjects.Components.Instruments { Logger.Info($"WE GOT STATE: {state.Playing} {state.SequencerTick}"); SetupRenderer(); - if (_renderer != null) _renderer.SequencerTick = state.SequencerTick; + _syncSequencerTick = state.SequencerTick; } else EndRenderer(); diff --git a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs index 81be788b5a..659f51c15b 100644 --- a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs +++ b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs @@ -95,7 +95,7 @@ namespace Content.Server.GameObjects.Components.Instruments if(++_midiEventCount <= MaxMidiEventsPerSecond) SendNetworkMessage(midiEventMsg); - _lastSequencerTick = midiEventMsg.MidiEvent[-1].Timestamp; + _lastSequencerTick = midiEventMsg.MidiEvent[^1].Timestamp; break; case InstrumentStartMidiMessage startMidi: Playing = true; From bb31fcb2275449545f2c7facb2e8de331b3b1b01 Mon Sep 17 00:00:00 2001 From: zumorica Date: Thu, 21 May 2020 19:18:10 +0200 Subject: [PATCH 09/14] Drop midi batch if it exceeds event limit. --- .../Instruments/InstrumentComponent.cs | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs index 659f51c15b..09e1940d1a 100644 --- a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs +++ b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs @@ -11,6 +11,8 @@ using Robust.Shared.Interfaces.Network; using Robust.Shared.Players; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; +using Logger = Robust.Shared.Log.Logger; +using MidiEvent = Robust.Shared.Audio.Midi.MidiEvent; namespace Content.Server.GameObjects.Components.Instruments { @@ -19,7 +21,10 @@ namespace Content.Server.GameObjects.Components.Instruments public class InstrumentComponent : SharedInstrumentComponent, IDropped, IHandSelected, IHandDeselected, IActivate, IUse, IThrown { + // These 2 values are quite high for now, and this could be easily abused. Change this if people are abusing it. public const int MaxMidiEventsPerSecond = 20; + public const int MaxMidiEventsPerBatch = 50; + public const int MaxMidiBatchDropped = 20; /// /// The client channel currently playing the instrument, or null if there's none. @@ -35,7 +40,10 @@ namespace Content.Server.GameObjects.Components.Instruments private float _timer = 0f; [ViewVariables] - public uint _lastSequencerTick = 0; + private int _batchesDropped = 0; + + [ViewVariables] + private uint _lastSequencerTick = 0; [ViewVariables] private int _midiEventCount = 0; @@ -92,16 +100,22 @@ namespace Content.Server.GameObjects.Components.Instruments case InstrumentMidiEventMessage midiEventMsg: if (!Playing) return; - if(++_midiEventCount <= MaxMidiEventsPerSecond) + + if (++_midiEventCount <= MaxMidiEventsPerSecond && + midiEventMsg.MidiEvent.Length < MaxMidiEventsPerBatch) SendNetworkMessage(midiEventMsg); + else + _batchesDropped++; // Batch dropped! _lastSequencerTick = midiEventMsg.MidiEvent[^1].Timestamp; break; case InstrumentStartMidiMessage startMidi: Playing = true; + _batchesDropped = 0; break; case InstrumentStopMidiMessage stopMidi: Playing = false; + _lastSequencerTick = 0; break; } } @@ -179,6 +193,11 @@ namespace Content.Server.GameObjects.Components.Instruments { base.Update(delta); + if (_batchesDropped > MaxMidiBatchDropped && _instrumentPlayer != null) + { + //TODO + } + _timer += delta; if (_timer < 1) return; _timer = 0f; From fc0d54a6bb10ac3b8415abc73ca8199a3a41b224 Mon Sep 17 00:00:00 2001 From: zumorica Date: Thu, 21 May 2020 20:06:48 +0200 Subject: [PATCH 10/14] Playing black MIDIs stuns you. --- .../Components/Instruments/InstrumentComponent.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs index 09e1940d1a..a23ae7e2b8 100644 --- a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs +++ b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs @@ -1,4 +1,8 @@ +using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.EntitySystems; +using Content.Server.Interfaces; +using Content.Server.Interfaces.GameObjects; +using Content.Server.Mobs; using Content.Shared.GameObjects.Components.Instruments; using NFluidsynth; using Robust.Server.GameObjects; @@ -8,6 +12,7 @@ using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Network; +using Robust.Shared.IoC; using Robust.Shared.Players; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; @@ -21,6 +26,8 @@ namespace Content.Server.GameObjects.Components.Instruments public class InstrumentComponent : SharedInstrumentComponent, IDropped, IHandSelected, IHandDeselected, IActivate, IUse, IThrown { + [Dependency] private IServerNotifyManager _notifyManager; + // These 2 values are quite high for now, and this could be easily abused. Change this if people are abusing it. public const int MaxMidiEventsPerSecond = 20; public const int MaxMidiEventsPerBatch = 50; @@ -195,7 +202,13 @@ namespace Content.Server.GameObjects.Components.Instruments if (_batchesDropped > MaxMidiBatchDropped && _instrumentPlayer != null) { - //TODO + var mob = _instrumentPlayer.AttachedEntity; + if (mob.TryGetComponent(out StunnableComponent stun)) + stun.Stun(1); + else + StandingStateHelper.DropAllItemsInHands(mob); + + _notifyManager.PopupMessage(Owner, mob, "Your fingers cramp up from playing!"); } _timer += delta; From 9b0bc818c65b66c93d3722a8e4c65bac6d30a736 Mon Sep 17 00:00:00 2001 From: zumorica Date: Thu, 21 May 2020 20:11:09 +0200 Subject: [PATCH 11/14] Be slightly more harsh with dropped batches count to prevent abuse. --- .../GameObjects/Components/Instruments/InstrumentComponent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs index a23ae7e2b8..376ce82945 100644 --- a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs +++ b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs @@ -118,7 +118,6 @@ namespace Content.Server.GameObjects.Components.Instruments break; case InstrumentStartMidiMessage startMidi: Playing = true; - _batchesDropped = 0; break; case InstrumentStopMidiMessage stopMidi: Playing = false; @@ -202,6 +201,7 @@ namespace Content.Server.GameObjects.Components.Instruments if (_batchesDropped > MaxMidiBatchDropped && _instrumentPlayer != null) { + _batchesDropped = 0; var mob = _instrumentPlayer.AttachedEntity; if (mob.TryGetComponent(out StunnableComponent stun)) stun.Stun(1); From 15beb2b87a60aafe833582ae41d32e83a1ab1f08 Mon Sep 17 00:00:00 2001 From: zumorica Date: Thu, 21 May 2020 20:19:05 +0200 Subject: [PATCH 12/14] Remove useless log. --- .../GameObjects/Components/Instruments/InstrumentComponent.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs b/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs index 08974e3867..bcceead7b9 100644 --- a/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs +++ b/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs @@ -176,7 +176,6 @@ namespace Content.Client.GameObjects.Components.Instruments if (state.Playing) { - Logger.Info($"WE GOT STATE: {state.Playing} {state.SequencerTick}"); SetupRenderer(); _syncSequencerTick = state.SequencerTick; } From da128f13c4a74c1d1304d93d6e887136d37f16d2 Mon Sep 17 00:00:00 2001 From: zumorica Date: Fri, 22 May 2020 11:13:34 +0200 Subject: [PATCH 13/14] End playing song when mob can't interact or player disconnects --- .../Instruments/InstrumentComponent.cs | 59 ++++++++++++++----- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs index 376ce82945..5f07b3d8b0 100644 --- a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs +++ b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs @@ -9,6 +9,8 @@ using Robust.Server.GameObjects; using Robust.Server.GameObjects.Components.UserInterface; using Robust.Server.Interfaces.GameObjects; using Robust.Server.Interfaces.Player; +using Robust.Server.Player; +using Robust.Shared.Enums; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Network; @@ -37,7 +39,7 @@ namespace Content.Server.GameObjects.Components.Instruments /// The client channel currently playing the instrument, or null if there's none. /// [ViewVariables] - private ICommonSession _instrumentPlayer; + private IPlayerSession _instrumentPlayer; private bool _handheld; [ViewVariables] @@ -78,6 +80,29 @@ namespace Content.Server.GameObjects.Components.Instruments } } + public IPlayerSession InstrumentPlayer + { + get => _instrumentPlayer; + private set + { + Playing = false; + + if(_instrumentPlayer != null) + _instrumentPlayer.PlayerStatusChanged -= OnPlayerStatusChanged; + + _instrumentPlayer = value; + + if(value != null) + _instrumentPlayer.PlayerStatusChanged += OnPlayerStatusChanged; + } + } + + private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) + { + if (e.NewStatus == SessionStatus.Disconnected) + InstrumentPlayer = null; + } + public override void Initialize() { base.Initialize(); @@ -100,12 +125,10 @@ namespace Content.Server.GameObjects.Components.Instruments { base.HandleNetworkMessage(message, channel, session); - // If the client that sent the message isn't the client playing this instrument, we ignore it. - if (session != _instrumentPlayer) return; switch (message) { case InstrumentMidiEventMessage midiEventMsg: - if (!Playing) + if (!Playing || session != _instrumentPlayer) return; if (++_midiEventCount <= MaxMidiEventsPerSecond && @@ -130,7 +153,7 @@ namespace Content.Server.GameObjects.Components.Instruments { Playing = false; SendNetworkMessage(new InstrumentStopMidiMessage()); - _instrumentPlayer = null; + InstrumentPlayer = null; _userInterface.CloseAll(); } @@ -138,7 +161,7 @@ namespace Content.Server.GameObjects.Components.Instruments { Playing = false; SendNetworkMessage(new InstrumentStopMidiMessage()); - _instrumentPlayer = null; + InstrumentPlayer = null; _userInterface.CloseAll(); } @@ -148,7 +171,7 @@ namespace Content.Server.GameObjects.Components.Instruments if (session == null) return; - _instrumentPlayer = session; + InstrumentPlayer = session; } public void HandDeselected(HandDeselectedEventArgs eventArgs) @@ -163,10 +186,10 @@ namespace Content.Server.GameObjects.Components.Instruments if (Handheld || !eventArgs.User.TryGetComponent(out IActorComponent actor)) return; - if (_instrumentPlayer != null) + if (InstrumentPlayer != null) return; - _instrumentPlayer = actor.playerSession; + InstrumentPlayer = actor.playerSession; OpenUserInterface(actor.playerSession); } @@ -175,16 +198,16 @@ namespace Content.Server.GameObjects.Components.Instruments if (!eventArgs.User.TryGetComponent(out IActorComponent actor)) return false; - if(_instrumentPlayer == actor.playerSession) + if(InstrumentPlayer == actor.playerSession) OpenUserInterface(actor.playerSession); return false; } private void UserInterfaceOnClosed(IPlayerSession player) { - if (!Handheld && player == _instrumentPlayer) + if (!Handheld && player == InstrumentPlayer) { - _instrumentPlayer = null; + InstrumentPlayer = null; SendNetworkMessage(new InstrumentStopMidiMessage()); Playing = false; } @@ -199,15 +222,23 @@ namespace Content.Server.GameObjects.Components.Instruments { base.Update(delta); - if (_batchesDropped > MaxMidiBatchDropped && _instrumentPlayer != null) + if (_instrumentPlayer != null && !ActionBlockerSystem.CanInteract(_instrumentPlayer.AttachedEntity)) + InstrumentPlayer = null; + + if (_batchesDropped > MaxMidiBatchDropped && InstrumentPlayer != null) { _batchesDropped = 0; - var mob = _instrumentPlayer.AttachedEntity; + var mob = InstrumentPlayer.AttachedEntity; + + _userInterface.CloseAll(); + if (mob.TryGetComponent(out StunnableComponent stun)) stun.Stun(1); else StandingStateHelper.DropAllItemsInHands(mob); + InstrumentPlayer = null; + _notifyManager.PopupMessage(Owner, mob, "Your fingers cramp up from playing!"); } From dccd89b1f034d4587afa6e9ec93398afe95b93ca Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Fri, 22 May 2020 18:01:51 +0200 Subject: [PATCH 14/14] Update submodule --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index feb5050b32..5d3f573f3c 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit feb5050b32499c5f9e1e61ceae63bf74f6797a27 +Subproject commit 5d3f573f3c0dce859d22247d05f1f106075e8645