mirror of
https://github.com/osukey/osukey.git
synced 2025-07-01 16:29:58 +09:00
Add initial hit sample pooling
This commit is contained in:
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Rulesets.Catch.Judgements;
|
using osu.Game.Rulesets.Catch.Judgements;
|
||||||
@ -26,11 +27,17 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
Samples = samples;
|
Samples = samples;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class BananaHitSampleInfo : HitSampleInfo
|
private class BananaHitSampleInfo : HitSampleInfo, IEquatable<BananaHitSampleInfo>
|
||||||
{
|
{
|
||||||
private static string[] lookupNames { get; } = { "metronomelow", "catch-banana" };
|
private static readonly string[] lookup_names = { "metronomelow", "catch-banana" };
|
||||||
|
|
||||||
public override IEnumerable<string> LookupNames => lookupNames;
|
public override IEnumerable<string> LookupNames => lookup_names;
|
||||||
|
|
||||||
|
public bool Equals(BananaHitSampleInfo other) => true;
|
||||||
|
|
||||||
|
public override bool Equals(object obj) => obj is BananaHitSampleInfo other && Equals(other);
|
||||||
|
|
||||||
|
public override int GetHashCode() => lookup_names.GetHashCode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
namespace osu.Game.Audio
|
namespace osu.Game.Audio
|
||||||
{
|
{
|
||||||
@ -10,7 +11,7 @@ namespace osu.Game.Audio
|
|||||||
/// Describes a gameplay hit sample.
|
/// Describes a gameplay hit sample.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public class HitSampleInfo : ISampleInfo
|
public class HitSampleInfo : ISampleInfo, IEquatable<HitSampleInfo>
|
||||||
{
|
{
|
||||||
public const string HIT_WHISTLE = @"hitwhistle";
|
public const string HIT_WHISTLE = @"hitwhistle";
|
||||||
public const string HIT_FINISH = @"hitfinish";
|
public const string HIT_FINISH = @"hitfinish";
|
||||||
@ -57,5 +58,17 @@ namespace osu.Game.Audio
|
|||||||
}
|
}
|
||||||
|
|
||||||
public HitSampleInfo Clone() => (HitSampleInfo)MemberwiseClone();
|
public HitSampleInfo Clone() => (HitSampleInfo)MemberwiseClone();
|
||||||
|
|
||||||
|
public bool Equals(HitSampleInfo other)
|
||||||
|
=> other != null && Bank == other.Bank && Name == other.Name && Suffix == other.Suffix;
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
=> obj is HitSampleInfo other && Equals(other);
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")] // This will have to be addressed eventually
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return HashCode.Combine(Bank, Name, Suffix);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace osu.Game.Audio
|
namespace osu.Game.Audio
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Describes a gameplay sample.
|
/// Describes a gameplay sample.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SampleInfo : ISampleInfo
|
public class SampleInfo : ISampleInfo, IEquatable<SampleInfo>
|
||||||
{
|
{
|
||||||
private readonly string[] sampleNames;
|
private readonly string[] sampleNames;
|
||||||
|
|
||||||
@ -20,5 +22,16 @@ namespace osu.Game.Audio
|
|||||||
public IEnumerable<string> LookupNames => sampleNames;
|
public IEnumerable<string> LookupNames => sampleNames;
|
||||||
|
|
||||||
public int Volume { get; } = 100;
|
public int Volume { get; } = 100;
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return HashCode.Combine(sampleNames, Volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(SampleInfo other)
|
||||||
|
=> other != null && sampleNames.SequenceEqual(other.sampleNames);
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
=> obj is SampleInfo other && Equals(other);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.TypeExtensions;
|
using osu.Framework.Extensions.TypeExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
@ -139,8 +138,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private IPooledHitObjectProvider pooledObjectProvider { get; set; }
|
private IPooledHitObjectProvider pooledObjectProvider { get; set; }
|
||||||
|
|
||||||
private Container<PausableSkinnableSound> samplesContainer;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="DrawableHitObject"/>.
|
/// Creates a new <see cref="DrawableHitObject"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -159,7 +156,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
config.BindWith(OsuSetting.PositionalHitSounds, userPositionalHitSounds);
|
config.BindWith(OsuSetting.PositionalHitSounds, userPositionalHitSounds);
|
||||||
|
|
||||||
// Explicit non-virtual function call.
|
// Explicit non-virtual function call.
|
||||||
base.AddInternal(samplesContainer = new Container<PausableSkinnableSound> { RelativeSizeAxes = Axes.Both });
|
base.AddInternal(Samples = new PausableSkinnableSound(Array.Empty<ISampleInfo>()));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadAsyncComplete()
|
protected override void LoadAsyncComplete()
|
||||||
@ -269,6 +266,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
// In order to stop this needless update, the event is unbound and re-bound as late as possible in Apply().
|
// In order to stop this needless update, the event is unbound and re-bound as late as possible in Apply().
|
||||||
samplesBindable.CollectionChanged -= onSamplesChanged;
|
samplesBindable.CollectionChanged -= onSamplesChanged;
|
||||||
|
|
||||||
|
Samples.Samples = Array.Empty<ISampleInfo>();
|
||||||
|
|
||||||
if (nestedHitObjects.IsValueCreated)
|
if (nestedHitObjects.IsValueCreated)
|
||||||
{
|
{
|
||||||
foreach (var obj in nestedHitObjects.Value)
|
foreach (var obj in nestedHitObjects.Value)
|
||||||
@ -335,8 +334,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual void LoadSamples()
|
protected virtual void LoadSamples()
|
||||||
{
|
{
|
||||||
samplesContainer.Clear();
|
Samples.Samples = Array.Empty<ISampleInfo>();
|
||||||
Samples = null;
|
|
||||||
|
|
||||||
var samples = GetSamples().ToArray();
|
var samples = GetSamples().ToArray();
|
||||||
|
|
||||||
@ -349,7 +347,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
+ $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}.");
|
+ $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
samplesContainer.Add(Samples = new PausableSkinnableSound(samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s))));
|
Samples.Samples = samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast<ISampleInfo>().ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSamplesChanged(object sender, NotifyCollectionChangedEventArgs e) => LoadSamples();
|
private void onSamplesChanged(object sender, NotifyCollectionChangedEventArgs e) => LoadSamples();
|
||||||
|
@ -5,6 +5,7 @@ using osuTK;
|
|||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using osu.Game.Beatmaps.Formats;
|
using osu.Game.Beatmaps.Formats;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
@ -500,7 +501,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone();
|
public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class LegacyHitSampleInfo : HitSampleInfo
|
public class LegacyHitSampleInfo : HitSampleInfo, IEquatable<LegacyHitSampleInfo>
|
||||||
{
|
{
|
||||||
private int customSampleBank;
|
private int customSampleBank;
|
||||||
|
|
||||||
@ -524,9 +525,21 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
/// using the <see cref="LegacySkinConfiguration.LegacySetting.LayeredHitSounds"/> skin config option.
|
/// using the <see cref="LegacySkinConfiguration.LegacySetting.LayeredHitSounds"/> skin config option.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public bool IsLayered { get; set; }
|
public bool IsLayered { get; set; }
|
||||||
|
|
||||||
|
public bool Equals(LegacyHitSampleInfo other)
|
||||||
|
=> other != null && base.Equals(other) && CustomSampleBank == other.CustomSampleBank;
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
=> obj is LegacyHitSampleInfo other && Equals(other);
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")] // This will have to be addressed eventually
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return HashCode.Combine(base.GetHashCode(), customSampleBank);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FileHitSampleInfo : LegacyHitSampleInfo
|
private class FileHitSampleInfo : LegacyHitSampleInfo, IEquatable<FileHitSampleInfo>
|
||||||
{
|
{
|
||||||
public string Filename;
|
public string Filename;
|
||||||
|
|
||||||
@ -542,6 +555,18 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
Filename,
|
Filename,
|
||||||
Path.ChangeExtension(Filename, null)
|
Path.ChangeExtension(Filename, null)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public bool Equals(FileHitSampleInfo other)
|
||||||
|
=> other != null && Filename == other.Filename;
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
=> obj is FileHitSampleInfo other && Equals(other);
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")] // This will have to be addressed eventually
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return HashCode.Combine(Filename);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,19 +8,23 @@ using JetBrains.Annotations;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio.Track;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Pooling;
|
using osu.Framework.Graphics.Pooling;
|
||||||
|
using osu.Game.Audio;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.UI
|
namespace osu.Game.Rulesets.UI
|
||||||
{
|
{
|
||||||
[Cached(typeof(IPooledHitObjectProvider))]
|
[Cached(typeof(IPooledHitObjectProvider))]
|
||||||
public abstract class Playfield : CompositeDrawable, IPooledHitObjectProvider
|
[Cached(typeof(IPooledSampleProvider))]
|
||||||
|
public abstract class Playfield : CompositeDrawable, IPooledHitObjectProvider, IPooledSampleProvider
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked when a <see cref="DrawableHitObject"/> is judged.
|
/// Invoked when a <see cref="DrawableHitObject"/> is judged.
|
||||||
@ -80,6 +84,12 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly BindableBool DisplayJudgements = new BindableBool(true);
|
public readonly BindableBool DisplayJudgements = new BindableBool(true);
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private IReadOnlyList<Mod> mods { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private ISampleStore sampleStore { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="Playfield"/>.
|
/// Creates a new <see cref="Playfield"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -96,9 +106,6 @@ namespace osu.Game.Rulesets.UI
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
|
||||||
private IReadOnlyList<Mod> mods { get; set; }
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
@ -336,6 +343,29 @@ namespace osu.Game.Rulesets.UI
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly Dictionary<ISampleInfo, DrawablePool<PoolableSkinnableSample>> samplePools = new Dictionary<ISampleInfo, DrawablePool<PoolableSkinnableSample>>();
|
||||||
|
|
||||||
|
public PoolableSkinnableSample GetPooledSample(ISampleInfo sampleInfo)
|
||||||
|
{
|
||||||
|
if (!samplePools.TryGetValue(sampleInfo, out var existingPool))
|
||||||
|
samplePools[sampleInfo] = existingPool = new DrawableSamplePool(sampleInfo, 5);
|
||||||
|
|
||||||
|
return existingPool.Get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DrawableSamplePool : DrawablePool<PoolableSkinnableSample>
|
||||||
|
{
|
||||||
|
private readonly ISampleInfo sampleInfo;
|
||||||
|
|
||||||
|
public DrawableSamplePool(ISampleInfo sampleInfo, int initialSize, int? maximumSize = null)
|
||||||
|
: base(initialSize, maximumSize)
|
||||||
|
{
|
||||||
|
this.sampleInfo = sampleInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override PoolableSkinnableSample CreateNewDrawable() => base.CreateNewDrawable().With(d => d.Apply(sampleInfo));
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Editor logic
|
#region Editor logic
|
||||||
|
@ -14,13 +14,13 @@ namespace osu.Game.Skinning
|
|||||||
{
|
{
|
||||||
protected bool RequestedPlaying { get; private set; }
|
protected bool RequestedPlaying { get; private set; }
|
||||||
|
|
||||||
public PausableSkinnableSound(ISampleInfo hitSamples)
|
public PausableSkinnableSound(ISampleInfo sample)
|
||||||
: base(hitSamples)
|
: base(sample)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public PausableSkinnableSound(IEnumerable<ISampleInfo> hitSamples)
|
public PausableSkinnableSound(IEnumerable<ISampleInfo> samples)
|
||||||
: base(hitSamples)
|
: base(samples)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Skinning
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether fallback to default skin should be allowed if the custom skin is missing this resource.
|
/// Whether fallback to default skin should be allowed if the custom skin is missing this resource.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool allowDefaultFallback => allowFallback == null || allowFallback.Invoke(CurrentSkin);
|
protected bool AllowDefaultFallback => allowFallback == null || allowFallback.Invoke(CurrentSkin);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new <see cref="SkinReloadableDrawable"/>
|
/// Create a new <see cref="SkinReloadableDrawable"/>
|
||||||
@ -58,7 +58,7 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
private void skinChanged()
|
private void skinChanged()
|
||||||
{
|
{
|
||||||
SkinChanged(CurrentSkin, allowDefaultFallback);
|
SkinChanged(CurrentSkin, AllowDefaultFallback);
|
||||||
OnSkinChanged?.Invoke();
|
OnSkinChanged?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,26 +1,149 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Audio.Track;
|
using osu.Framework.Audio.Track;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Audio;
|
using osu.Framework.Graphics.Audio;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
|
|
||||||
namespace osu.Game.Skinning
|
namespace osu.Game.Skinning
|
||||||
{
|
{
|
||||||
public class SkinnableSound : SkinReloadableDrawable, IAdjustableAudioComponent
|
public interface IPooledSampleProvider
|
||||||
{
|
{
|
||||||
private readonly ISampleInfo[] hitSamples;
|
[CanBeNull]
|
||||||
|
PoolableSkinnableSample GetPooledSample(ISampleInfo sampleInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PoolableSkinnableSample : SkinReloadableDrawable, IAggregateAudioAdjustment, IAdjustableAudioComponent
|
||||||
|
{
|
||||||
|
private ISampleInfo sampleInfo;
|
||||||
|
private DrawableSample sample;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private ISampleStore samples { get; set; }
|
private ISampleStore sampleStore { get; set; }
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private readonly AudioAdjustments adjustments = new AudioAdjustments();
|
||||||
|
|
||||||
|
public PoolableSkinnableSample()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public PoolableSkinnableSample(ISampleInfo sampleInfo)
|
||||||
|
{
|
||||||
|
Apply(sampleInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Apply(ISampleInfo sampleInfo)
|
||||||
|
{
|
||||||
|
if (this.sampleInfo != null)
|
||||||
|
throw new InvalidOperationException($"A {nameof(PoolableSkinnableSample)} cannot be applied multiple {nameof(ISampleInfo)}s.");
|
||||||
|
|
||||||
|
this.sampleInfo = sampleInfo;
|
||||||
|
|
||||||
|
if (LoadState >= LoadState.Ready)
|
||||||
|
updateSample();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
|
||||||
|
{
|
||||||
|
base.SkinChanged(skin, allowFallback);
|
||||||
|
updateSample();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSample()
|
||||||
|
{
|
||||||
|
ClearInternal();
|
||||||
|
|
||||||
|
var ch = CurrentSkin.GetSample(sampleInfo);
|
||||||
|
|
||||||
|
if (ch == null && AllowDefaultFallback)
|
||||||
|
{
|
||||||
|
foreach (var lookup in sampleInfo.LookupNames)
|
||||||
|
{
|
||||||
|
if ((ch = sampleStore.Get(lookup)) != null)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
AddInternal(sample = new DrawableSample(ch)
|
||||||
|
{
|
||||||
|
Looping = Looping,
|
||||||
|
Volume = { Value = sampleInfo.Volume / 100.0 }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Play(bool restart = true) => sample?.Play(restart);
|
||||||
|
|
||||||
|
public void Stop() => sample?.Stop();
|
||||||
|
|
||||||
|
public bool Playing => sample?.Playing ?? false;
|
||||||
|
|
||||||
|
private bool looping;
|
||||||
|
|
||||||
|
public bool Looping
|
||||||
|
{
|
||||||
|
get => looping;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
looping = value;
|
||||||
|
|
||||||
|
if (sample != null)
|
||||||
|
sample.Looping = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The volume of this component.
|
||||||
|
/// </summary>
|
||||||
|
public BindableNumber<double> Volume => adjustments.Volume;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The playback balance of this sample (-1 .. 1 where 0 is centered)
|
||||||
|
/// </summary>
|
||||||
|
public BindableNumber<double> Balance => adjustments.Balance;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rate at which the component is played back (affects pitch). 1 is 100% playback speed, or default frequency.
|
||||||
|
/// </summary>
|
||||||
|
public BindableNumber<double> Frequency => adjustments.Frequency;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rate at which the component is played back (does not affect pitch). 1 is 100% playback speed.
|
||||||
|
/// </summary>
|
||||||
|
public BindableNumber<double> Tempo => adjustments.Tempo;
|
||||||
|
|
||||||
|
public void AddAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable)
|
||||||
|
=> adjustments.AddAdjustment(type, adjustBindable);
|
||||||
|
|
||||||
|
public void RemoveAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable)
|
||||||
|
=> adjustments.RemoveAdjustment(type, adjustBindable);
|
||||||
|
|
||||||
|
public void RemoveAllAdjustments(AdjustableProperty type) => adjustments.RemoveAllAdjustments(type);
|
||||||
|
|
||||||
|
public IBindable<double> AggregateVolume => adjustments.AggregateVolume;
|
||||||
|
|
||||||
|
public IBindable<double> AggregateBalance => adjustments.AggregateBalance;
|
||||||
|
|
||||||
|
public IBindable<double> AggregateFrequency => adjustments.AggregateFrequency;
|
||||||
|
|
||||||
|
public IBindable<double> AggregateTempo => adjustments.AggregateTempo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SkinnableSound : SkinReloadableDrawable, IAdjustableAudioComponent
|
||||||
|
{
|
||||||
public override bool RemoveWhenNotAlive => false;
|
public override bool RemoveWhenNotAlive => false;
|
||||||
public override bool RemoveCompletedTransforms => false;
|
public override bool RemoveCompletedTransforms => false;
|
||||||
|
|
||||||
@ -34,17 +157,44 @@ namespace osu.Game.Skinning
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
protected bool PlayWhenZeroVolume => Looping;
|
protected bool PlayWhenZeroVolume => Looping;
|
||||||
|
|
||||||
protected readonly AudioContainer<DrawableSample> SamplesContainer;
|
protected readonly AudioContainer<PoolableSkinnableSample> SamplesContainer;
|
||||||
|
|
||||||
public SkinnableSound(ISampleInfo hitSamples)
|
[Resolved]
|
||||||
: this(new[] { hitSamples })
|
private ISampleStore sampleStore { get; set; }
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private IPooledSampleProvider pooledProvider { get; set; }
|
||||||
|
|
||||||
|
public SkinnableSound(ISampleInfo sample)
|
||||||
|
: this(new[] { sample })
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public SkinnableSound(IEnumerable<ISampleInfo> hitSamples)
|
public SkinnableSound(IEnumerable<ISampleInfo> samples)
|
||||||
{
|
{
|
||||||
this.hitSamples = hitSamples.ToArray();
|
this.samples = samples.ToArray();
|
||||||
InternalChild = SamplesContainer = new AudioContainer<DrawableSample>();
|
|
||||||
|
InternalChild = SamplesContainer = new AudioContainer<PoolableSkinnableSample>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ISampleInfo[] samples;
|
||||||
|
|
||||||
|
public ISampleInfo[] Samples
|
||||||
|
{
|
||||||
|
get => samples;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
throw new ArgumentNullException(nameof(value));
|
||||||
|
|
||||||
|
if (samples == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
samples = value;
|
||||||
|
|
||||||
|
if (LoadState >= LoadState.Ready)
|
||||||
|
updateSamples();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool looping;
|
private bool looping;
|
||||||
@ -77,34 +227,23 @@ namespace osu.Game.Skinning
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
|
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
|
||||||
|
{
|
||||||
|
// Start playback internally for the new samples if the previous ones were playing beforehand.
|
||||||
|
if (IsPlaying)
|
||||||
|
Play();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSamples()
|
||||||
{
|
{
|
||||||
bool wasPlaying = IsPlaying;
|
bool wasPlaying = IsPlaying;
|
||||||
|
|
||||||
var channels = hitSamples.Select(s =>
|
// Remove all pooled samples (return them to the pool), and dispose the rest.
|
||||||
{
|
SamplesContainer.RemoveAll(s => s.IsInPool);
|
||||||
var ch = skin.GetSample(s);
|
SamplesContainer.Clear();
|
||||||
|
|
||||||
if (ch == null && allowFallback)
|
foreach (var s in samples)
|
||||||
{
|
SamplesContainer.Add(pooledProvider?.GetPooledSample(s) ?? new PoolableSkinnableSample(s));
|
||||||
foreach (var lookup in s.LookupNames)
|
|
||||||
{
|
|
||||||
if ((ch = samples.Get(lookup)) != null)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ch != null)
|
|
||||||
{
|
|
||||||
ch.Looping = looping;
|
|
||||||
ch.Volume.Value = s.Volume / 100.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ch;
|
|
||||||
}).Where(c => c != null);
|
|
||||||
|
|
||||||
SamplesContainer.ChildrenEnumerable = channels.Select(c => new DrawableSample(c));
|
|
||||||
|
|
||||||
// Start playback internally for the new samples if the previous ones were playing beforehand.
|
|
||||||
if (wasPlaying)
|
if (wasPlaying)
|
||||||
Play();
|
Play();
|
||||||
}
|
}
|
||||||
|
@ -37,8 +37,8 @@ namespace osu.Game.Storyboards.Drawables
|
|||||||
|
|
||||||
foreach (var mod in mods.Value.OfType<IApplicableToSample>())
|
foreach (var mod in mods.Value.OfType<IApplicableToSample>())
|
||||||
{
|
{
|
||||||
foreach (var sample in SamplesContainer)
|
// foreach (var sample in SamplesContainer)
|
||||||
mod.ApplyToSample(sample);
|
// mod.ApplyToSample(sample.Sample);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user