osu/osu.Game/Skinning/PoolableSkinnableSample.cs

211 lines
7.0 KiB
C#
Raw Normal View History

2020-11-19 10:52:34 +00:00
// 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.
using System;
using System.Linq;
using JetBrains.Annotations;
2020-11-19 10:52:34 +00:00
using osu.Framework.Audio;
2021-01-18 12:24:10 +00:00
using osu.Framework.Audio.Sample;
2020-11-19 10:52:34 +00:00
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Audio;
2020-11-19 12:01:38 +00:00
using osu.Framework.Graphics.Containers;
2020-11-19 10:52:34 +00:00
using osu.Game.Audio;
namespace osu.Game.Skinning
{
/// <summary>
/// A sample corresponding to an <see cref="ISampleInfo"/> that supports being pooled and responding to skin changes.
/// </summary>
public class PoolableSkinnableSample : SkinReloadableDrawable, IAdjustableAudioComponent
2020-11-19 10:52:34 +00:00
{
/// <summary>
/// The currently-loaded <see cref="DrawableSample"/>.
/// </summary>
[CanBeNull]
public DrawableSample Sample { get; private set; }
2020-11-19 12:01:38 +00:00
private readonly AudioContainer<DrawableSample> sampleContainer;
2020-11-19 10:52:34 +00:00
private ISampleInfo sampleInfo;
2021-01-19 08:11:40 +00:00
private SampleChannel activeChannel;
2020-11-19 10:52:34 +00:00
/// <summary>
/// Creates a new <see cref="PoolableSkinnableSample"/> with no applied <see cref="ISampleInfo"/>.
/// An <see cref="ISampleInfo"/> can be applied later via <see cref="Apply"/>.
/// </summary>
2020-11-19 10:52:34 +00:00
public PoolableSkinnableSample()
{
2020-11-19 12:01:38 +00:00
InternalChild = sampleContainer = new AudioContainer<DrawableSample> { RelativeSizeAxes = Axes.Both };
2020-11-19 10:52:34 +00:00
}
/// <summary>
/// Creates a new <see cref="PoolableSkinnableSample"/> with an applied <see cref="ISampleInfo"/>.
/// </summary>
/// <param name="sampleInfo">The <see cref="ISampleInfo"/> to attach.</param>
2020-11-19 10:52:34 +00:00
public PoolableSkinnableSample(ISampleInfo sampleInfo)
2020-11-19 12:01:38 +00:00
: this()
2020-11-19 10:52:34 +00:00
{
Apply(sampleInfo);
}
/// <summary>
/// Applies an <see cref="ISampleInfo"/> that describes the sample to retrieve.
/// Only one <see cref="ISampleInfo"/> can ever be applied to a <see cref="PoolableSkinnableSample"/>.
/// </summary>
/// <param name="sampleInfo">The <see cref="ISampleInfo"/> to apply.</param>
/// <exception cref="InvalidOperationException">If an <see cref="ISampleInfo"/> has already been applied to this <see cref="PoolableSkinnableSample"/>.</exception>
2020-11-19 10:52:34 +00:00
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;
2020-11-19 12:01:38 +00:00
Volume.Value = sampleInfo.Volume / 100.0;
2020-11-19 10:52:34 +00:00
if (LoadState >= LoadState.Ready)
updateSample();
}
protected override void LoadComplete()
{
base.LoadComplete();
CurrentSkin.SourceChanged += skinChangedImmediate;
}
private void skinChangedImmediate()
{
// Clean up the previous sample immediately on a source change.
// This avoids a potential call to Play() of an already disposed sample (samples are disposed along with the skin, but SkinChanged is scheduled).
clearPreviousSamples();
}
protected override void SkinChanged(ISkinSource skin)
2020-11-19 10:52:34 +00:00
{
base.SkinChanged(skin);
2020-11-19 10:52:34 +00:00
updateSample();
}
/// <summary>
/// Whether this sample was playing before a skin source change.
/// </summary>
private bool wasPlaying;
private void clearPreviousSamples()
2020-11-19 10:52:34 +00:00
{
// only run if the samples aren't already cleared.
// this ensures the "wasPlaying" state is stored correctly even if multiple clear calls are executed.
if (!sampleContainer.Any()) return;
wasPlaying = Playing;
2020-11-19 12:01:38 +00:00
sampleContainer.Clear();
Sample = null;
}
private void updateSample()
{
if (sampleInfo == null)
return;
2020-11-19 10:52:34 +00:00
2021-03-19 11:25:21 +00:00
var sample = CurrentSkin.GetSample(sampleInfo);
2020-11-19 10:52:34 +00:00
2021-03-19 11:25:21 +00:00
if (sample == null)
2020-11-19 10:52:34 +00:00
return;
2021-03-19 11:25:21 +00:00
sampleContainer.Add(Sample = new DrawableSample(sample));
// Start playback internally for the new sample if the previous one was playing beforehand.
2020-12-06 17:59:38 +00:00
if (wasPlaying && Looping)
Play();
2020-11-19 10:52:34 +00:00
}
/// <summary>
/// Plays the sample.
/// </summary>
2021-01-19 08:11:40 +00:00
public void Play()
{
if (Sample == null)
return;
activeChannel = Sample.GetChannel();
activeChannel.Looping = Looping;
activeChannel.Play();
Played = true;
2021-01-19 08:11:40 +00:00
}
2020-11-19 10:52:34 +00:00
/// <summary>
/// Stops the sample.
/// </summary>
public void Stop()
{
activeChannel?.Stop();
activeChannel = null;
}
2020-11-19 10:52:34 +00:00
/// <summary>
/// Whether the sample is currently playing.
/// </summary>
2021-01-19 08:11:40 +00:00
public bool Playing => activeChannel?.Playing ?? false;
public bool Played { get; private set; }
2020-11-19 10:52:34 +00:00
private bool looping;
/// <summary>
2020-11-19 13:47:11 +00:00
/// Whether the sample should loop on completion.
/// </summary>
2020-11-19 10:52:34 +00:00
public bool Looping
{
get => looping;
set
{
looping = value;
2021-01-19 08:11:40 +00:00
if (activeChannel != null)
activeChannel.Looping = value;
2020-11-19 10:52:34 +00:00
}
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
2021-06-08 07:15:17 +00:00
if (CurrentSkin != null)
CurrentSkin.SourceChanged -= skinChangedImmediate;
}
2020-11-30 09:40:22 +00:00
#region Re-expose AudioContainer
2020-11-19 12:01:38 +00:00
public BindableNumber<double> Volume => sampleContainer.Volume;
2020-11-19 10:52:34 +00:00
2020-11-19 12:01:38 +00:00
public BindableNumber<double> Balance => sampleContainer.Balance;
2020-11-19 10:52:34 +00:00
2020-11-19 12:01:38 +00:00
public BindableNumber<double> Frequency => sampleContainer.Frequency;
2020-11-19 10:52:34 +00:00
2020-11-19 12:01:38 +00:00
public BindableNumber<double> Tempo => sampleContainer.Tempo;
2020-11-19 10:52:34 +00:00
public void BindAdjustments(IAggregateAudioAdjustment component) => sampleContainer.BindAdjustments(component);
public void UnbindAdjustments(IAggregateAudioAdjustment component) => sampleContainer.UnbindAdjustments(component);
public void AddAdjustment(AdjustableProperty type, IBindable<double> adjustBindable) => sampleContainer.AddAdjustment(type, adjustBindable);
2020-11-19 10:52:34 +00:00
public void RemoveAdjustment(AdjustableProperty type, IBindable<double> adjustBindable) => sampleContainer.RemoveAdjustment(type, adjustBindable);
2020-11-19 10:52:34 +00:00
2020-11-19 12:01:38 +00:00
public void RemoveAllAdjustments(AdjustableProperty type) => sampleContainer.RemoveAllAdjustments(type);
2020-11-19 10:52:34 +00:00
2020-11-19 12:01:38 +00:00
public IBindable<double> AggregateVolume => sampleContainer.AggregateVolume;
2020-11-19 10:52:34 +00:00
2020-11-19 12:01:38 +00:00
public IBindable<double> AggregateBalance => sampleContainer.AggregateBalance;
2020-11-19 10:52:34 +00:00
2020-11-19 12:01:38 +00:00
public IBindable<double> AggregateFrequency => sampleContainer.AggregateFrequency;
2020-11-19 10:52:34 +00:00
2020-11-19 12:01:38 +00:00
public IBindable<double> AggregateTempo => sampleContainer.AggregateTempo;
2020-11-30 09:40:22 +00:00
#endregion
2020-11-19 10:52:34 +00:00
}
}