// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Containers; using osu.Game.Audio; namespace osu.Game.Skinning { /// /// A sample corresponding to an that supports being pooled and responding to skin changes. /// public class PoolableSkinnableSample : SkinReloadableDrawable, IAggregateAudioAdjustment, IAdjustableAudioComponent { /// /// The currently-loaded . /// [CanBeNull] public DrawableSample Sample { get; private set; } private readonly AudioContainer sampleContainer; private ISampleInfo sampleInfo; [Resolved] private ISampleStore sampleStore { get; set; } /// /// Creates a new with no applied . /// An can be applied later via . /// public PoolableSkinnableSample() { InternalChild = sampleContainer = new AudioContainer { RelativeSizeAxes = Axes.Both }; } /// /// Creates a new with an applied . /// /// The to attach. public PoolableSkinnableSample(ISampleInfo sampleInfo) : this() { Apply(sampleInfo); } /// /// Applies an that describes the sample to retrieve. /// Only one can ever be applied to a . /// /// The to apply. /// If an has already been applied to this . 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; Volume.Value = sampleInfo.Volume / 100.0; if (LoadState >= LoadState.Ready) updateSample(); } protected override void SkinChanged(ISkinSource skin, bool allowFallback) { base.SkinChanged(skin, allowFallback); updateSample(); } private void updateSample() { if (sampleInfo == null) return; bool wasPlaying = Playing; sampleContainer.Clear(); Sample = null; 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; sampleContainer.Add(Sample = new DrawableSample(ch) { Looping = Looping }); // Start playback internally for the new sample if the previous one was playing beforehand. if (wasPlaying && Looping) Play(); } /// /// Plays the sample. /// /// Whether to play the sample from the beginning. public void Play(bool restart = true) => Sample?.Play(restart); /// /// Stops the sample. /// public void Stop() => Sample?.Stop(); /// /// Whether the sample is currently playing. /// public bool Playing => Sample?.Playing ?? false; private bool looping; /// /// Whether the sample should loop on completion. /// public bool Looping { get => looping; set { looping = value; if (Sample != null) Sample.Looping = value; } } #region Re-expose AudioContainer public BindableNumber Volume => sampleContainer.Volume; public BindableNumber Balance => sampleContainer.Balance; public BindableNumber Frequency => sampleContainer.Frequency; public BindableNumber Tempo => sampleContainer.Tempo; public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => sampleContainer.AddAdjustment(type, adjustBindable); public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => sampleContainer.RemoveAdjustment(type, adjustBindable); public void RemoveAllAdjustments(AdjustableProperty type) => sampleContainer.RemoveAllAdjustments(type); public IBindable AggregateVolume => sampleContainer.AggregateVolume; public IBindable AggregateBalance => sampleContainer.AggregateBalance; public IBindable AggregateFrequency => sampleContainer.AggregateFrequency; public IBindable AggregateTempo => sampleContainer.AggregateTempo; #endregion } }