From 82b7cfa947a95719c9eb9fbb07d1359624f58cd8 Mon Sep 17 00:00:00 2001 From: Ataman Date: Tue, 28 Jul 2026 06:29:08 +0200 Subject: [PATCH] Instrument UI MinVolume slider (#41272) * Added localization string for MIDI input volume. * Added MinVolume property to SharedInstrumentComponent & InstrumentComponentState. * Implemented MinVolume property into InstrumentSystem's. * Added UI slider for MinVolume to InstrumentMenu. * Added network event for updating MinVolume. * Removed unnecessary empty line. * Readability fix as suggested by Pok27 Co-authored-by: Pok <113675512+Pok27@users.noreply.github.com> * Added MinVolumeControl * Added MinVolumeControl to instrument menu configuration (and cleaned up Content.Client.InstrumentSystem.SetMinVolume) * Added label to MinVolumeSlider indicating the current value * Minor cleanup --------- Co-authored-by: Pok <113675512+Pok27@users.noreply.github.com> --- .../Instruments/InstrumentSystem.cs | 27 +++++++++ .../UI/InstrumentBoundUserInterface.cs | 12 ++++ .../Instruments/UI/MinVolumeControl.xaml | 6 ++ .../Instruments/UI/MinVolumeControl.xaml.cs | 60 +++++++++++++++++++ .../Instruments/InstrumentSystem.cs | 15 ++++- .../Instruments/SharedInstrumentComponent.cs | 19 ++++++ .../instruments/instruments-component.ftl | 1 + 7 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 Content.Client/Instruments/UI/MinVolumeControl.xaml create mode 100644 Content.Client/Instruments/UI/MinVolumeControl.xaml.cs diff --git a/Content.Client/Instruments/InstrumentSystem.cs b/Content.Client/Instruments/InstrumentSystem.cs index 17f6d0c65d..f2377d5444 100644 --- a/Content.Client/Instruments/InstrumentSystem.cs +++ b/Content.Client/Instruments/InstrumentSystem.cs @@ -15,6 +15,9 @@ namespace Content.Client.Instruments; public sealed partial class InstrumentSystem : SharedInstrumentSystem { + private const int MidiMinVolume = 0; + private const int MidiMaxVolume = 127; + [Dependency] private IClientNetManager _netManager = default!; [Dependency] private IMidiManager _midiManager = default!; [Dependency] private IGameTiming _gameTiming = default!; @@ -38,6 +41,7 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem SubscribeNetworkEvent(OnMidiEventRx); SubscribeNetworkEvent(OnMidiStart); SubscribeNetworkEvent(OnMidiStop); + SubscribeNetworkEvent(OnSetMidiMinVolume); SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnHandleState); @@ -75,6 +79,7 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem component.AllowProgramChange = state.AllowProgramChange; component.RespectMidiLimits = state.RespectMidiLimits; component.Master = EnsureEntity(state.Master, uid); + component.MinVolume = state.MinVolume; component.FilteredChannels = state.FilteredChannels; if (component.Playing) @@ -96,6 +101,18 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem RaiseNetworkEvent(new InstrumentSetMasterEvent(GetNetEntity(uid), GetNetEntity(masterUid))); } + public void SetMinVolume(EntityUid uid, int volume) + { + if (!TryComp(uid, out InstrumentComponent? instrument)) + return; + + var byteMinVolume = (byte)Math.Min(Math.Max(MidiMinVolume, volume), MidiMaxVolume); + instrument.MinVolume = byteMinVolume; + + RaiseNetworkEvent(new InstrumentSetMidiMinVolumeEvent(GetNetEntity(uid), byteMinVolume)); + UpdateRenderer(uid); + } + public void SetFilteredChannel(EntityUid uid, int channel, bool value) { if (!TryComp(uid, out InstrumentComponent? instrument)) @@ -186,6 +203,7 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem UpdateRendererMaster(instrument); instrument.Renderer.LoopMidi = instrument.LoopMidi; + instrument.Renderer.MinVolume = instrument.MinVolume; } private void UpdateRendererMaster(InstrumentComponent instrument) @@ -418,6 +436,15 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem EndRenderer(GetEntity(ev.Uid), true); } + private void OnSetMidiMinVolume(InstrumentSetMidiMinVolumeEvent ev) + { + if (!TryComp(GetEntity(ev.Uid), out InstrumentComponent? instrument)) + return; + + instrument.MinVolume = ev.MinVolume; + UpdateRenderer(GetEntity(ev.Uid), instrument); + } + public override void Update(float frameTime) { base.Update(frameTime); diff --git a/Content.Client/Instruments/UI/InstrumentBoundUserInterface.cs b/Content.Client/Instruments/UI/InstrumentBoundUserInterface.cs index e12f2e4e53..d502158bac 100644 --- a/Content.Client/Instruments/UI/InstrumentBoundUserInterface.cs +++ b/Content.Client/Instruments/UI/InstrumentBoundUserInterface.cs @@ -30,6 +30,7 @@ public sealed partial class InstrumentBoundUserInterface : BoundUserInterface private readonly ChannelsControl _channelsControl = new(); private readonly MidiCollectionUtilsControl _midiCollectionUtilsControl = new(); + private readonly MinVolumeControl _minVolumeControl = new(); private InstrumentMenu? _instrumentMenu; @@ -63,6 +64,9 @@ public sealed partial class InstrumentBoundUserInterface : BoundUserInterface _channelsControl.ChannelsUpdateRequest += OnChannelsUpdateRequest; _channelsControl.SwitchFilteredChannel += OnSwitchFilteredChannel; + _minVolumeControl.MinVolumeChanged += OnMinVolumeChanged; + _minVolumeControl.MinVolume = instrument.MinVolume; + _instrumentMenu = this.CreateWindow(); if (EntMan.TryGetComponent(Owner, out var metaData)) @@ -84,6 +88,9 @@ public sealed partial class InstrumentBoundUserInterface : BoundUserInterface _instrumentMenu.AddConfigurationControl( _loc.GetString("instruments-component-midi-file-collection-label"), _midiCollectionUtilsControl); + _instrumentMenu.AddConfigurationControl( + _loc.GetString("instruments-component-menu-midi-min-volume-slider-label"), + _minVolumeControl); } protected override void ReceiveMessage(BoundUserInterfaceMessage message) @@ -139,6 +146,11 @@ public sealed partial class InstrumentBoundUserInterface : BoundUserInterface UpdateChannels(); } + private void OnMinVolumeChanged(int volume) + { + _instruments.SetMinVolume(Owner, volume); + } + private void OnMidiPlaybackEnded() { _fileSource.SelectNextTrack(); diff --git a/Content.Client/Instruments/UI/MinVolumeControl.xaml b/Content.Client/Instruments/UI/MinVolumeControl.xaml new file mode 100644 index 0000000000..8508d69cf7 --- /dev/null +++ b/Content.Client/Instruments/UI/MinVolumeControl.xaml @@ -0,0 +1,6 @@ + + + + + diff --git a/Content.Client/Instruments/UI/MinVolumeControl.xaml.cs b/Content.Client/Instruments/UI/MinVolumeControl.xaml.cs new file mode 100644 index 0000000000..41fa7dd11e --- /dev/null +++ b/Content.Client/Instruments/UI/MinVolumeControl.xaml.cs @@ -0,0 +1,60 @@ +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Input; + +namespace Content.Client.Instruments.UI; + +[GenerateTypedNameReferences] +public sealed partial class MinVolumeControl : Control +{ + /// + /// Raised when the user changes the min volume for MIDI input. + /// + public event Action? MinVolumeChanged; + + public int MinVolume + { + get => (int)Math.Ceiling(MidiMinVolumeSlider.Value); + set + { + MidiMinVolumeSlider.Value = value; + UpdateLabel(); + } + } + + public MinVolumeControl() + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + + MidiMinVolumeSlider.OnValueChanged += OnMidiMinVolumeSliderValueChanged; + MidiMinVolumeSlider.OnKeyBindUp += OnMidiMinVolumeSliderKeyUp; + MidiMinVolumeSlider.Value = 0; + } + + private void OnMidiMinVolumeSliderValueChanged(Robust.Client.UserInterface.Controls.Range _) + { + UpdateLabel(); + + // Do not update while still grabbed. + if (MidiMinVolumeSlider.Grabbed) + return; + + MinVolumeChanged?.Invoke(MinVolume); + } + + private void OnMidiMinVolumeSliderKeyUp(GUIBoundKeyEventArgs args) + { + if (args.Function != EngineKeyFunctions.UIClick) + return; + + UpdateLabel(); + MinVolumeChanged?.Invoke(MinVolume); + } + + private void UpdateLabel() + { + MidiMinVolumeValueLabel.Text = ((int)Math.Ceiling(MidiMinVolumeSlider.Value)).ToString(); + } +} diff --git a/Content.Server/Instruments/InstrumentSystem.cs b/Content.Server/Instruments/InstrumentSystem.cs index 9ac4547c8e..a59293d083 100644 --- a/Content.Server/Instruments/InstrumentSystem.cs +++ b/Content.Server/Instruments/InstrumentSystem.cs @@ -58,6 +58,7 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem SubscribeNetworkEvent(OnMidiSetMaster); SubscribeNetworkEvent(OnMidiSetFilteredChannel); SubscribeNetworkEvent(OnMidiSetChannels); + SubscribeNetworkEvent(OnMidiSetMinVolume); Subs.BuiEvents(InstrumentUiKey.Key, subs => { @@ -82,7 +83,8 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem AllowProgramChange = component.AllowProgramChange, RespectMidiLimits = component.RespectMidiLimits, Master = GetNetEntity(component.Master), - FilteredChannels = component.FilteredChannels + FilteredChannels = component.FilteredChannels, + MinVolume = component.MinVolume, }; } @@ -140,6 +142,17 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem Clean(uid, instrument); } + private void OnMidiSetMinVolume(InstrumentSetMidiMinVolumeEvent msg, EntitySessionEventArgs args) + { + var uid = GetEntity(msg.Uid); + + if (!TryComp(uid, out InstrumentComponent? instrument)) + return; + + instrument.MinVolume = msg.MinVolume; + + Dirty(uid, instrument); + } private void OnMidiSetChannels(InstrumentSetChannelsEvent msg, EntitySessionEventArgs args) { diff --git a/Content.Shared/Instruments/SharedInstrumentComponent.cs b/Content.Shared/Instruments/SharedInstrumentComponent.cs index 7d97e51d8a..f76cf1b857 100644 --- a/Content.Shared/Instruments/SharedInstrumentComponent.cs +++ b/Content.Shared/Instruments/SharedInstrumentComponent.cs @@ -28,6 +28,9 @@ public abstract partial class SharedInstrumentComponent : Component [DataField] public bool RespectMidiLimits { get; set; } = true; + [DataField] + public byte MinVolume { get; set; } + [ViewVariables(VVAccess.ReadWrite)] public EntityUid? Master { get; set; } = null; @@ -63,10 +66,26 @@ public sealed class InstrumentComponentState : ComponentState public bool RespectMidiLimits; public NetEntity? Master; + public byte MinVolume; public BitArray FilteredChannels = default!; } +/// +/// This message is sent to the client to update midi min volume. +/// +[Serializable, NetSerializable] +public sealed class InstrumentSetMidiMinVolumeEvent : EntityEventArgs +{ + public NetEntity Uid { get; } + public byte MinVolume { get; set; } + + public InstrumentSetMidiMinVolumeEvent(NetEntity uid, byte minVolume) + { + Uid = uid; + MinVolume = minVolume; + } +} /// /// This message is sent to the client to completely stop midi input and midi playback. diff --git a/Resources/Locale/en-US/instruments/instruments-component.ftl b/Resources/Locale/en-US/instruments/instruments-component.ftl index 330246d8a9..deea34c524 100644 --- a/Resources/Locale/en-US/instruments/instruments-component.ftl +++ b/Resources/Locale/en-US/instruments/instruments-component.ftl @@ -45,6 +45,7 @@ instruments-component-midi-file-collection-size-label = {$count} MIDI files load instruments-component-midi-file-collection-open-dir-button = Open Folder instruments-component-midi-file-collection-reload-button = Reload Collection instruments-component-midi-file-collection-delete-all-button = Clear Collection +instruments-component-menu-midi-min-volume-slider-label = MIDI Input Min Volume # SwappableInstrumentComponent