diff --git a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs index 85367b8bf6..ef96e4c48a 100644 --- a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs +++ b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs @@ -2,10 +2,9 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; using osu.Game.Audio; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Taiko.Audio { @@ -14,7 +13,9 @@ namespace osu.Game.Rulesets.Taiko.Audio private readonly ControlPointInfo controlPoints; private readonly Dictionary mappings = new Dictionary(); - public DrumSampleMapping(ControlPointInfo controlPoints, AudioManager audio) + public readonly List Drawables = new List(); + + public DrumSampleMapping(ControlPointInfo controlPoints) { this.controlPoints = controlPoints; @@ -27,20 +28,34 @@ namespace osu.Game.Rulesets.Taiko.Audio foreach (var s in samplePoints) { + var centre = s.GetSampleInfo(); + var rim = s.GetSampleInfo(SampleInfo.HIT_CLAP); + + // todo: this is ugly + centre.Namespace = "taiko"; + rim.Namespace = "taiko"; + mappings[s.Time] = new DrumSample { - Centre = s.GetSampleInfo().GetChannel(audio.Sample.Get, "Taiko"), - Rim = s.GetSampleInfo(SampleInfo.HIT_CLAP).GetChannel(audio.Sample.Get, "Taiko") + Centre = addDrawableSound(centre), + Rim = addDrawableSound(rim) }; } } + private SkinnableSound addDrawableSound(SampleInfo rim) + { + var drawable = new SkinnableSound(rim); + Drawables.Add(drawable); + return drawable; + } + public DrumSample SampleAt(double time) => mappings[controlPoints.SamplePointAt(time).Time]; public class DrumSample { - public SampleChannel Centre; - public SampleChannel Rim; + public SkinnableSound Centre; + public SkinnableSound Rim; } } } diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 98f20fd558..ac4c077515 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -4,7 +4,6 @@ using System; using OpenTK; using osu.Framework.Allocation; -using osu.Framework.Audio; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -34,9 +33,9 @@ namespace osu.Game.Rulesets.Taiko.UI } [BackgroundDependencyLoader] - private void load(AudioManager audio) + private void load() { - var sampleMappings = new DrumSampleMapping(controlPoints, audio); + var sampleMappings = new DrumSampleMapping(controlPoints); Children = new Drawable[] { @@ -63,6 +62,8 @@ namespace osu.Game.Rulesets.Taiko.UI CentreAction = TaikoAction.RightCentre } }; + + AddRangeInternal(sampleMappings.Drawables); } /// diff --git a/osu.Game/Audio/SampleInfo.cs b/osu.Game/Audio/SampleInfo.cs index 99d2da7ebc..2014db6c61 100644 --- a/osu.Game/Audio/SampleInfo.cs +++ b/osu.Game/Audio/SampleInfo.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using osu.Framework.Audio.Sample; namespace osu.Game.Audio { @@ -14,22 +13,10 @@ namespace osu.Game.Audio public const string HIT_NORMAL = @"hitnormal"; public const string HIT_CLAP = @"hitclap"; - public SampleChannel GetChannel(Func getChannel, string resourceNamespace = null) - { - SampleChannel channel = null; - - if (resourceNamespace != null) - channel = getChannel($"Gameplay/{resourceNamespace}/{Bank}-{Name}"); - - // try without namespace as a fallback. - if (channel == null) - channel = getChannel($"Gameplay/{Bank}-{Name}"); - - if (channel != null) - channel.Volume.Value = Volume / 100.0; - - return channel; - } + /// + /// An optional ruleset namespace. + /// + public string Namespace; /// /// The bank to load the sample from. diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 2ecdccc31f..fcb472995a 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -3,21 +3,19 @@ using System; using System.Collections.Generic; -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; -using osu.Game.Rulesets.Judgements; -using Container = osu.Framework.Graphics.Containers.Container; -using osu.Game.Rulesets.Objects.Types; -using OpenTK.Graphics; -using osu.Game.Audio; using System.Linq; -using osu.Game.Graphics; +using osu.Framework.Allocation; using osu.Framework.Configuration; -using OpenTK; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; +using osu.Game.Audio; +using osu.Game.Graphics; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; +using OpenTK; +using OpenTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables { @@ -33,8 +31,12 @@ namespace osu.Game.Rulesets.Objects.Drawables // Todo: Rulesets should be overriding the resources instead, but we need to figure out where/when to apply overrides first protected virtual string SampleNamespace => null; - protected List Samples = new List(); - protected virtual IEnumerable GetSamples() => HitObject.Samples; + protected SkinnableSound Samples; + + protected virtual IEnumerable GetSamples() + { + return HitObject.Samples; + } private List nestedHitObjects; public IReadOnlyList NestedHitObjects => nestedHitObjects; @@ -83,41 +85,23 @@ namespace osu.Game.Rulesets.Objects.Drawables HitObject = hitObject; } - private readonly Bindable skin = new Bindable(); - [BackgroundDependencyLoader] - private void load(AudioManager audio, SkinManager skins) + private void load() { - var samples = GetSamples(); + var samples = GetSamples().ToArray(); + if (samples.Any()) { if (HitObject.SampleControlPoint == null) throw new ArgumentNullException(nameof(HitObject.SampleControlPoint), $"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}." + $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}."); - void loadSamples(Skin skin) + AddInternal(Samples = new SkinnableSound(samples.Select(s => new SampleInfo { - Samples.Clear(); - - foreach (SampleInfo s in samples) - { - SampleInfo localSampleInfo = new SampleInfo - { - Bank = s.Bank ?? HitObject.SampleControlPoint.SampleBank, - Name = s.Name, - Volume = s.Volume > 0 ? s.Volume : HitObject.SampleControlPoint.SampleVolume - }; - - - SampleChannel channel = localSampleInfo.GetChannel(skin.GetSample, SampleNamespace) ?? localSampleInfo.GetChannel(audio.Sample.Get, SampleNamespace); - - if (channel == null) return; - - Samples.Add(channel); - } - } - - skin.ValueChanged += loadSamples; - skin.BindTo(skins.CurrentSkin); + Bank = s.Bank ?? HitObject.SampleControlPoint.SampleBank, + Name = s.Name, + Volume = s.Volume > 0 ? s.Volume : HitObject.SampleControlPoint.SampleVolume, + Namespace = SampleNamespace + }).ToArray())); } } @@ -149,7 +133,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Plays all the hitsounds for this . /// - public void PlaySamples() => Samples.ForEach(s => s?.Play()); + public void PlaySamples() => Samples?.Play(); protected override void Update() { @@ -231,10 +215,8 @@ namespace osu.Game.Rulesets.Objects.Drawables return false; if (NestedHitObjects != null) - { foreach (var d in NestedHitObjects) judgementOccurred |= d.UpdateJudgement(userTriggered); - } if (!ProvidesJudgement || judgementFinalized || judgementOccurred) return judgementOccurred; diff --git a/osu.Game/Skinning/SkinReloadableDrawable.cs b/osu.Game/Skinning/SkinReloadableDrawable.cs new file mode 100644 index 0000000000..1abfd8976e --- /dev/null +++ b/osu.Game/Skinning/SkinReloadableDrawable.cs @@ -0,0 +1,53 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Skinning +{ + /// + /// A drawable which has a callback when the skin changes. + /// + public abstract class SkinReloadableDrawable : CompositeDrawable + { + private Bindable skin; + + /// + /// Whether fallback to default skin should be allowed if the custom skin is missing this resource. + /// + private readonly bool allowDefaultFallback; + + /// + /// Create a new + /// + /// Whether fallback to default skin should be allowed if the custom skin is missing this resource. + protected SkinReloadableDrawable(bool fallback = true) + { + allowDefaultFallback = fallback; + } + + [BackgroundDependencyLoader] + private void load(SkinManager skinManager) + { + skin = skinManager.CurrentSkin.GetBoundCopy(); + skin.ValueChanged += skin => SkinChanged(skin, allowDefaultFallback || skin.SkinInfo == SkinInfo.Default); + } + + protected override void LoadAsyncComplete() + { + base.LoadAsyncComplete(); + skin.TriggerChange(); + } + + /// + /// Called when a change is made to the skin. + /// + /// The new skin. + /// Whether fallback to default skin should be allowed if the custom skin is missing this resource. + protected virtual void SkinChanged(Skin skin, bool allowFallback) + { + } + } +} diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs index c1c78fdb05..cd669778a6 100644 --- a/osu.Game/Skinning/SkinnableDrawable.cs +++ b/osu.Game/Skinning/SkinnableDrawable.cs @@ -2,10 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using osu.Framework.Allocation; -using osu.Framework.Configuration; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; namespace osu.Game.Skinning { @@ -14,40 +11,29 @@ namespace osu.Game.Skinning public SkinnableDrawable(string name, Func defaultImplementation, bool fallback = true) : base(name, defaultImplementation, fallback) { - RelativeSizeAxes = Axes.Both; } } - public class SkinnableDrawable : CompositeDrawable + public class SkinnableDrawable : SkinReloadableDrawable where T : Drawable { - private Bindable skin; - protected Func CreateDefault; + private readonly Func createDefault; - public readonly string ComponentName; + private readonly string componentName; - public readonly bool DefaultFallback; - - public SkinnableDrawable(string name, Func defaultImplementation, bool fallback = true) + public SkinnableDrawable(string name, Func defaultImplementation, bool fallback = true) : base(fallback) { - DefaultFallback = fallback; - ComponentName = name; - CreateDefault = defaultImplementation; + componentName = name; + createDefault = defaultImplementation; + + RelativeSizeAxes = Axes.Both; } - [BackgroundDependencyLoader] - private void load(SkinManager skinManager) + protected override void SkinChanged(Skin skin, bool allowFallback) { - skin = skinManager.CurrentSkin.GetBoundCopy(); - skin.ValueChanged += updateComponent; - skin.TriggerChange(); - } - - private void updateComponent(Skin skin) - { - var drawable = skin.GetDrawableComponent(ComponentName); - if (drawable == null && (DefaultFallback || skin.SkinInfo == SkinInfo.Default)) - drawable = CreateDefault(ComponentName); + var drawable = skin.GetDrawableComponent(componentName); + if (drawable == null && allowFallback) + drawable = createDefault(componentName); if (drawable != null) InternalChild = drawable; diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs new file mode 100644 index 0000000000..7cc13519da --- /dev/null +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -0,0 +1,62 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Audio; + +namespace osu.Game.Skinning +{ + public class SkinnableSound : SkinReloadableDrawable + { + private readonly SampleInfo[] samples; + private SampleChannel[] channels; + + private AudioManager audio; + + public SkinnableSound(params SampleInfo[] samples) + { + this.samples = samples; + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + this.audio = audio; + } + + public void Play() => channels?.ForEach(c => c.Play()); + + protected override void SkinChanged(Skin skin, bool allowFallback) + { + channels = samples.Select(s => + { + var ch = loadChannel(s, skin.GetSample); + if (ch == null && allowFallback) + ch = loadChannel(s, audio.Sample.Get); + return ch; + }).ToArray(); + } + + private SampleChannel loadChannel(SampleInfo info, Func getSampleFunction) + { + SampleChannel ch = null; + + 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; + } + } +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 6a2ce82b23..6a06bf540b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -1,4 +1,4 @@ - + @@ -861,6 +861,8 @@ + +