diff --git a/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs b/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs
index 85d6119014..e24e60cf4e 100644
--- a/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs
+++ b/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs
@@ -3,7 +3,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Content.Client.GameObjects.EntitySystems;
-using Content.Shared;
using Content.Shared.GameObjects.Components.Instruments;
using Content.Shared.Physics;
using Robust.Client.Audio.Midi;
@@ -11,13 +10,11 @@ using Robust.Shared.Audio.Midi;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.Timers;
using Robust.Shared.GameObjects.Systems;
-using Robust.Shared.Interfaces.Configuration;
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.Timers;
using Robust.Shared.ViewVariables;
namespace Content.Client.GameObjects.Components.Instruments
@@ -162,6 +159,41 @@ namespace Content.Client.GameObjects.Components.Instruments
[ViewVariables]
public bool IsRendererAlive => _renderer != null;
+ [ViewVariables]
+ public int PlayerTotalTick => _renderer?.PlayerTotalTick ?? 0;
+
+ [ViewVariables]
+ public int PlayerTick
+ {
+ get => _renderer?.PlayerTick ?? 0;
+ set
+ {
+ if (!IsRendererAlive || _renderer!.Status != MidiRendererStatus.File) return;
+
+ _midiEventBuffer.Clear();
+
+ _renderer.PlayerTick = value;
+ var tick = _renderer.SequencerTick;
+
+ // We add a "all notes off" message.
+ for (byte i = 0; i < 16; i++)
+ {
+ _midiEventBuffer.Add(new MidiEvent()
+ {
+ Tick = tick, Type = 176,
+ Control = 123, Velocity = 0, Channel = i,
+ });
+ }
+
+ // Now we add a Reset All Controllers message.
+ _midiEventBuffer.Add(new MidiEvent()
+ {
+ Tick = tick, Type = 176,
+ Control = 121, Value = 0,
+ });
+ }
+ }
+
public override void Initialize()
{
base.Initialize();
@@ -390,20 +422,6 @@ namespace Content.Client.GameObjects.Components.Instruments
/// The received midi event
private void RendererOnMidiEvent(MidiEvent midiEvent)
{
- // avoid of out-of-band, unimportant or unsupported events
- switch (midiEvent.Type)
- {
- case 0x80: // NOTE_OFF
- case 0x90: // NOTE_ON
- case 0xa0: // KEY_PRESSURE
- case 0xb0: // CONTROL_CHANGE
- case 0xd0: // CHANNEL_PRESSURE
- case 0xe0: // PITCH_BEND
- break;
- default:
- return;
- }
-
_midiEventBuffer.Add(midiEvent);
}
diff --git a/Content.Client/Instruments/InstrumentMenu.xaml b/Content.Client/Instruments/InstrumentMenu.xaml
index 0825557f22..23e4b75a9b 100644
--- a/Content.Client/Instruments/InstrumentMenu.xaml
+++ b/Content.Client/Instruments/InstrumentMenu.xaml
@@ -1,18 +1,23 @@
-
+
-
+
+
+
+
+
+
diff --git a/Content.Client/Instruments/InstrumentMenu.xaml.cs b/Content.Client/Instruments/InstrumentMenu.xaml.cs
index e9debae7e1..9ead93cc77 100644
--- a/Content.Client/Instruments/InstrumentMenu.xaml.cs
+++ b/Content.Client/Instruments/InstrumentMenu.xaml.cs
@@ -14,11 +14,14 @@ using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Containers;
+using Robust.Shared.Input;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Timers;
+using Robust.Shared.Timing;
+using Range = Robust.Client.UserInterface.Controls.Range;
namespace Content.Client.Instruments
{
@@ -46,6 +49,7 @@ namespace Content.Client.Instruments
LoopButton.Disabled = !_owner.Instrument.IsMidiOpen;
LoopButton.Pressed = _owner.Instrument.LoopMidi;
StopButton.Disabled = !_owner.Instrument.IsMidiOpen;
+ PlaybackSlider.MouseFilter = _owner.Instrument.IsMidiOpen ? MouseFilterMode.Pass : MouseFilterMode.Ignore;
if (!_midiManager.IsAvailable)
{
@@ -74,6 +78,8 @@ namespace Content.Client.Instruments
FileButton.OnPressed += MidiFileButtonOnOnPressed;
LoopButton.OnToggled += MidiLoopButtonOnOnToggled;
StopButton.OnPressed += MidiStopButtonOnPressed;
+ PlaybackSlider.OnValueChanged += PlaybackSliderSeek;
+ PlaybackSlider.OnKeyBindUp += PlaybackSliderKeyUp;
}
private void InstrumentOnMidiPlaybackEnded()
@@ -85,12 +91,15 @@ namespace Content.Client.Instruments
{
LoopButton.Disabled = disabled;
StopButton.Disabled = disabled;
+
+ // Whether to allow the slider to receive events..
+ PlaybackSlider.MouseFilter = !disabled ? MouseFilterMode.Pass : MouseFilterMode.Ignore;
}
private async void MidiFileButtonOnOnPressed(BaseButton.ButtonEventArgs obj)
{
var filters = new FileDialogFilters(new FileDialogFilters.Group("mid", "midi"));
- var file = await _fileDialogManager.OpenFile(filters);
+ await using var file = await _fileDialogManager.OpenFile(filters);
// The following checks are only in place to prevent players from playing MIDI songs locally.
// There are equivalents for these checks on the server.
@@ -107,7 +116,7 @@ namespace Content.Client.Instruments
return;
MidiStopButtonOnPressed(null);
- var memStream = new MemoryStream((int) file.Length);
+ await using var memStream = new MemoryStream((int) file.Length);
// 100ms delay is due to a race condition or something idk.
// While we're waiting, load it into memory.
await Task.WhenAll(Timer.Delay(100), file.CopyToAsync(memStream));
@@ -165,5 +174,36 @@ namespace Content.Client.Instruments
{
_owner.Instrument.LoopMidi = obj.Pressed;
}
+
+ private void PlaybackSliderSeek(Range _)
+ {
+ // Do not seek while still grabbing.
+ if (PlaybackSlider.Grabbed) return;
+
+ _owner.Instrument.PlayerTick = (int)Math.Ceiling(PlaybackSlider.Value);
+ }
+
+ private void PlaybackSliderKeyUp(GUIBoundKeyEventArgs args)
+ {
+ if (args.Function != EngineKeyFunctions.UIClick) return;
+ _owner.Instrument.PlayerTick = (int)Math.Ceiling(PlaybackSlider.Value);
+ }
+
+ protected override void Update(FrameEventArgs args)
+ {
+ base.Update(args);
+
+ if (!_owner.Instrument.IsMidiOpen)
+ {
+ PlaybackSlider.MaxValue = 1;
+ PlaybackSlider.SetValueWithoutEvent(0);
+ return;
+ }
+
+ if (PlaybackSlider.Grabbed) return;
+
+ PlaybackSlider.MaxValue = _owner.Instrument.PlayerTotalTick;
+ PlaybackSlider.SetValueWithoutEvent(_owner.Instrument.PlayerTick);
+ }
}
}
diff --git a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs
index 233874b533..6651e20708 100644
--- a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs
+++ b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs
@@ -4,9 +4,7 @@ using System.Linq;
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Utility;
-using Content.Shared;
using Content.Shared.GameObjects.Components.Instruments;
-using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.GameObjects.EntitySystems.ActionBlocker;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components;
@@ -18,10 +16,7 @@ using Robust.Server.Player;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
-using Robust.Shared.Interfaces.Configuration;
using Robust.Shared.Interfaces.Network;
-using Robust.Shared.Interfaces.Timing;
-using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
@@ -41,9 +36,6 @@ namespace Content.Server.GameObjects.Components.Instruments
IUse,
IThrown
{
- [Dependency] private readonly IGameTiming _gameTiming = default!;
-
- private static readonly TimeSpan OneSecAgo = TimeSpan.FromSeconds(-1);
private InstrumentSystem _instrumentSystem = default!;
///
@@ -60,9 +52,6 @@ namespace Content.Server.GameObjects.Components.Instruments
[ViewVariables]
private float _timer = 0f;
- [ViewVariables(VVAccess.ReadOnly)]
- private TimeSpan _lastMeasured = TimeSpan.MinValue;
-
[ViewVariables]
private int _batchesDropped = 0;
@@ -213,15 +202,6 @@ namespace Content.Server.GameObjects.Components.Instruments
var minTick = midiEventMsg.MidiEvent.Min(x => x.Tick);
if (_lastSequencerTick > minTick)
{
- var now = _gameTiming.RealTime;
- var oneSecAGo = now.Add(OneSecAgo);
- if (_lastMeasured < oneSecAGo)
- {
- _lastMeasured = now;
- _laggedBatches = 0;
- _batchesDropped = 0;
- }
-
_laggedBatches++;
if (_respectMidiLimits)
@@ -246,15 +226,6 @@ namespace Content.Server.GameObjects.Components.Instruments
if (++_midiEventCount > maxMidiEventsPerSecond
|| midiEventMsg.MidiEvent.Length > maxMidiEventsPerBatch)
{
- var now = _gameTiming.RealTime;
- var oneSecAGo = now.Add(OneSecAgo);
- if (_lastMeasured < oneSecAGo)
- {
- _lastMeasured = now;
- _laggedBatches = 0;
- _batchesDropped = 0;
- }
-
_batchesDropped++;
send = false;
@@ -266,7 +237,7 @@ namespace Content.Server.GameObjects.Components.Instruments
}
var maxTick = midiEventMsg.MidiEvent.Max(x => x.Tick);
- _lastSequencerTick = Math.Max(maxTick, minTick + 1);
+ _lastSequencerTick = Math.Max(maxTick, minTick);
break;
case InstrumentStartMidiMessage startMidi:
if (session != _instrumentPlayer)
@@ -386,15 +357,14 @@ namespace Content.Server.GameObjects.Components.Instruments
UserInterface?.CloseAll();
+ if(Handheld)
+ EntitySystem.Get().DropAllItemsInHands(mob, false);
+
if (mob != null && mob.TryGetComponent(out StunnableComponent? stun))
{
stun.Stun(1);
Clean();
}
- else
- {
- EntitySystem.Get().DropAllItemsInHands(mob, false);
- }
InstrumentPlayer = null;
@@ -406,6 +376,8 @@ namespace Content.Server.GameObjects.Components.Instruments
_timer = 0f;
_midiEventCount = 0;
+ _laggedBatches = 0;
+ _batchesDropped = 0;
}
}