diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
index d3c61960bb..ad203d2107 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -230,5 +230,23 @@ namespace osu.Game.Tests.Beatmaps.Formats
SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(new SampleInfo { Name = "hitnormal" });
}
+
+ [Test]
+ public void TestDecodeCustomHitObjectSamples()
+ {
+ var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
+ using (var resStream = Resource.OpenResource("custom-hitobject-samples.osu"))
+ using (var stream = new StreamReader(resStream))
+ {
+ var hitObjects = decoder.Decode(stream).HitObjects;
+
+ Assert.AreEqual("hit_1.wav", hitObjects[0].Samples[0].LookupNames.First());
+ Assert.AreEqual("hit_2.wav", hitObjects[1].Samples[0].LookupNames.First());
+ Assert.AreEqual("hitnormal2", getTestableSampleInfo(hitObjects[2]).Name);
+ Assert.AreEqual("hit_1.wav", hitObjects[3].Samples[0].LookupNames.First());
+ }
+
+ SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(new SampleInfo { Name = "hitnormal" });
+ }
}
}
diff --git a/osu.Game.Tests/Resources/custom-hitobject-samples.osu b/osu.Game.Tests/Resources/custom-hitobject-samples.osu
new file mode 100644
index 0000000000..588672e2d9
--- /dev/null
+++ b/osu.Game.Tests/Resources/custom-hitobject-samples.osu
@@ -0,0 +1,16 @@
+osu file format v14
+
+[General]
+SampleSet: Normal
+
+[TimingPoints]
+2170,468.75,4,1,0,40,1,0
+2638,-100,4,1,1,40,0,0
+3107,-100,4,1,2,40,0,0
+3576,-100,4,1,0,40,0,0
+
+[HitObjects]
+255,193,2170,1,0,0:0:0:0:hit_1.wav
+256,191,2638,5,0,0:0:0:0:hit_2.wav
+255,193,3107,1,0,0:0:0:0:
+256,191,3576,1,0,0:0:0:0:hit_1.wav
diff --git a/osu.Game/Audio/SampleInfo.cs b/osu.Game/Audio/SampleInfo.cs
index 53b6e439f5..7e329ac651 100644
--- a/osu.Game/Audio/SampleInfo.cs
+++ b/osu.Game/Audio/SampleInfo.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
+using System.Collections.Generic;
namespace osu.Game.Audio
{
@@ -33,6 +34,20 @@ namespace osu.Game.Audio
///
public int Volume;
+ ///
+ /// Retrieve all possible filenames that can be used as a source, returned in order of preference (highest first).
+ ///
+ public virtual IEnumerable LookupNames
+ {
+ get
+ {
+ if (!string.IsNullOrEmpty(Namespace))
+ yield return $"{Namespace}/{Bank}-{Name}";
+
+ yield return $"{Bank}-{Name}"; // Without namespace as a fallback even when we have a namespace
+ }
+ }
+
public SampleInfo Clone() => (SampleInfo)MemberwiseClone();
}
}
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 501d8544ff..922d228701 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -250,7 +250,8 @@ namespace osu.Game
new VolumeControlReceptor
{
RelativeSizeAxes = Axes.Both,
- ActionRequested = action => volume.Adjust(action)
+ ActionRequested = action => volume.Adjust(action),
+ ScrollActionRequested = (action, amount, isPrecise) => volume.Adjust(action, amount, isPrecise),
},
mainContent = new Container { RelativeSizeAxes = Axes.Both },
overlayContent = new Container { RelativeSizeAxes = Axes.Both, Depth = float.MinValue },
diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
index 29eb1094cf..5d4d627995 100644
--- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
+++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
@@ -186,7 +186,7 @@ namespace osu.Game.Overlays.KeyBinding
{
if (bindTarget.IsHovered)
{
- bindTarget.UpdateKeyCombination(new KeyCombination(KeyCombination.FromInputState(state).Keys.Append(state.Mouse.ScrollDelta.Y > 0 ? InputKey.MouseWheelUp : InputKey.MouseWheelDown)));
+ bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state, state.Mouse.ScrollDelta));
finalise();
return true;
}
diff --git a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs
index 572b3f0c27..3a64e12b27 100644
--- a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs
+++ b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs
@@ -9,11 +9,13 @@ using osu.Game.Input.Bindings;
namespace osu.Game.Overlays.Volume
{
- public class VolumeControlReceptor : Container, IKeyBindingHandler, IHandleGlobalInput
+ public class VolumeControlReceptor : Container, IScrollBindingHandler, IHandleGlobalInput
{
public Func ActionRequested;
+ public Func ScrollActionRequested;
public bool OnPressed(GlobalAction action) => ActionRequested?.Invoke(action) ?? false;
+ public bool OnScroll(GlobalAction action, float amount, bool isPrecise) => ScrollActionRequested?.Invoke(action, amount, isPrecise) ?? false;
public bool OnReleased(GlobalAction action) => false;
}
}
diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs
index 1d392e6ee8..9aeca1f35f 100644
--- a/osu.Game/Overlays/Volume/VolumeMeter.cs
+++ b/osu.Game/Overlays/Volume/VolumeMeter.cs
@@ -12,17 +12,15 @@ using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input;
-using osu.Framework.Input.Bindings;
using osu.Framework.MathUtils;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
-using osu.Game.Input.Bindings;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Overlays.Volume
{
- public class VolumeMeter : Container, IKeyBindingHandler
+ public class VolumeMeter : Container
{
private CircularProgress volumeCircle;
private CircularProgress volumeCircleGlow;
@@ -226,59 +224,27 @@ namespace osu.Game.Overlays.Volume
private const float adjust_step = 0.05f;
- public void Increase() => adjust(1);
- public void Decrease() => adjust(-1);
-
- private void adjust(int direction)
- {
- float amount = adjust_step * direction;
-
- // handle the case where the OnPressed action was actually a mouse wheel.
- // this allows for precise wheel handling.
- var state = GetContainingInputManager().CurrentState;
- if (state.Mouse?.ScrollDelta.Y != 0)
- {
- OnScroll(state);
- return;
- }
-
- Volume += amount;
- }
-
- public bool OnPressed(GlobalAction action)
- {
- if (!IsHovered) return false;
-
- switch (action)
- {
- case GlobalAction.DecreaseVolume:
- Decrease();
- return true;
- case GlobalAction.IncreaseVolume:
- Increase();
- return true;
- }
-
- return false;
- }
+ public void Increase(double amount = 1, bool isPrecise = false) => adjust(amount, isPrecise);
+ public void Decrease(double amount = 1, bool isPrecise = false) => adjust(-amount, isPrecise);
// because volume precision is set to 0.01, this local is required to keep track of more precise adjustments and only apply when possible.
- private double scrollAmount;
+ private double adjustAccumulator;
+
+ private void adjust(double delta, bool isPrecise)
+ {
+ adjustAccumulator += delta * adjust_step * (isPrecise ? 0.1 : 1);
+ if (Math.Abs(adjustAccumulator) < Bindable.Precision)
+ return;
+ Volume += adjustAccumulator;
+ adjustAccumulator = 0;
+ }
protected override bool OnScroll(InputState state)
{
- scrollAmount += adjust_step * state.Mouse.ScrollDelta.Y * (state.Mouse.HasPreciseScroll ? 0.1f : 1);
-
- if (Math.Abs(scrollAmount) < Bindable.Precision)
- return true;
-
- Volume += scrollAmount;
- scrollAmount = 0;
+ adjust(state.Mouse.ScrollDelta.Y, state.Mouse.HasPreciseScroll);
return true;
}
- public bool OnReleased(GlobalAction action) => false;
-
private const float transition_length = 500;
protected override bool OnHover(InputState state)
diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs
index 1c9c615bbb..e40597b2d4 100644
--- a/osu.Game/Overlays/VolumeOverlay.cs
+++ b/osu.Game/Overlays/VolumeOverlay.cs
@@ -93,7 +93,7 @@ namespace osu.Game.Overlays
muteButton.Current.ValueChanged += _ => Show();
}
- public bool Adjust(GlobalAction action)
+ public bool Adjust(GlobalAction action, float amount = 1, bool isPrecise = false)
{
if (!IsLoaded) return false;
@@ -103,13 +103,13 @@ namespace osu.Game.Overlays
if (State == Visibility.Hidden)
Show();
else
- volumeMeterMaster.Decrease();
+ volumeMeterMaster.Decrease(amount, isPrecise);
return true;
case GlobalAction.IncreaseVolume:
if (State == Visibility.Hidden)
Show();
else
- volumeMeterMaster.Increase();
+ volumeMeterMaster.Increase(amount, isPrecise);
return true;
case GlobalAction.ToggleMute:
Show();
diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
index 9edd0f1f34..95589d8953 100644
--- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
@@ -6,6 +6,7 @@ using osu.Game.Rulesets.Objects.Types;
using System;
using System.Collections.Generic;
using System.Globalization;
+using System.IO;
using osu.Game.Beatmaps.Formats;
using osu.Game.Audio;
using System.Linq;
@@ -196,9 +197,6 @@ namespace osu.Game.Rulesets.Objects.Legacy
var bank = (LegacyBeatmapDecoder.LegacySampleBank)Convert.ToInt32(split[0]);
var addbank = (LegacyBeatmapDecoder.LegacySampleBank)Convert.ToInt32(split[1]);
- // Let's not implement this for now, because this doesn't fit nicely into the bank structure
- //string sampleFile = split2.Length > 4 ? split2[4] : string.Empty;
-
string stringBank = bank.ToString().ToLower();
if (stringBank == @"none")
stringBank = null;
@@ -211,6 +209,8 @@ namespace osu.Game.Rulesets.Objects.Legacy
if (split.Length > 3)
bankInfo.Volume = int.Parse(split[3]);
+
+ bankInfo.Filename = split.Length > 4 ? split[4] : null;
}
///
@@ -252,6 +252,10 @@ namespace osu.Game.Rulesets.Objects.Legacy
private List convertSoundType(LegacySoundType type, SampleBankInfo bankInfo)
{
+ // Todo: This should return the normal SampleInfos if the specified sample file isn't found, but that's a pretty edge-case scenario
+ if (!string.IsNullOrEmpty(bankInfo.Filename))
+ return new List { new FileSampleInfo { Filename = bankInfo.Filename } };
+
var soundTypes = new List
{
new SampleInfo
@@ -297,14 +301,24 @@ namespace osu.Game.Rulesets.Objects.Legacy
private class SampleBankInfo
{
+ public string Filename;
+
public string Normal;
public string Add;
public int Volume;
- public SampleBankInfo Clone()
+ public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone();
+ }
+
+ private class FileSampleInfo : SampleInfo
+ {
+ public string Filename;
+
+ public override IEnumerable LookupNames => new[]
{
- return (SampleBankInfo)MemberwiseClone();
- }
+ Filename,
+ Path.ChangeExtension(Filename, null)
+ };
}
[Flags]
diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs
index 1a11b0c03e..2fc9cff463 100644
--- a/osu.Game/Skinning/SkinnableSound.cs
+++ b/osu.Game/Skinning/SkinnableSound.cs
@@ -44,19 +44,17 @@ namespace osu.Game.Skinning
private SampleChannel loadChannel(SampleInfo info, Func getSampleFunction)
{
- SampleChannel ch = null;
+ foreach (var lookup in info.LookupNames)
+ {
+ var ch = getSampleFunction($"Gameplay/{lookup}");
+ if (ch == null)
+ continue;
- if (info.Namespace != null)
- ch = getSampleFunction($"Gameplay/{info.Namespace}/{info.Bank}-{info.Name}");
-
- // try without namespace as a fallback.
- if (ch == null)
- ch = getSampleFunction($"Gameplay/{info.Bank}-{info.Name}");
-
- if (ch != null)
ch.Volume.Value = info.Volume / 100.0;
+ return ch;
+ }
- return ch;
+ return null;
}
}
}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 56c33c47af..da55726447 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -18,7 +18,7 @@
-
+