mirror of https://github.com/ppy/osu
214 lines
7.1 KiB
C#
214 lines
7.1 KiB
C#
// 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.
|
|
|
|
#nullable disable
|
|
|
|
using System;
|
|
using System.Linq;
|
|
using JetBrains.Annotations;
|
|
using osu.Framework.Audio;
|
|
using osu.Framework.Audio.Sample;
|
|
using osu.Framework.Bindables;
|
|
using osu.Framework.Extensions.ObjectExtensions;
|
|
using osu.Framework.Graphics;
|
|
using osu.Framework.Graphics.Audio;
|
|
using osu.Framework.Graphics.Containers;
|
|
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 partial class PoolableSkinnableSample : SkinReloadableDrawable, IAdjustableAudioComponent
|
|
{
|
|
/// <summary>
|
|
/// The currently-loaded <see cref="DrawableSample"/>.
|
|
/// </summary>
|
|
[CanBeNull]
|
|
public DrawableSample Sample { get; private set; }
|
|
|
|
private readonly AudioContainer<DrawableSample> sampleContainer;
|
|
private ISampleInfo sampleInfo;
|
|
private SampleChannel activeChannel;
|
|
|
|
/// <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>
|
|
public PoolableSkinnableSample()
|
|
{
|
|
InternalChild = sampleContainer = new AudioContainer<DrawableSample> { RelativeSizeAxes = Axes.Both };
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new <see cref="PoolableSkinnableSample"/> with an applied <see cref="ISampleInfo"/>.
|
|
/// </summary>
|
|
/// <param name="sampleInfo">The <see cref="ISampleInfo"/> to attach.</param>
|
|
public PoolableSkinnableSample(ISampleInfo sampleInfo)
|
|
: this()
|
|
{
|
|
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>
|
|
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 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)
|
|
{
|
|
base.SkinChanged(skin);
|
|
updateSample();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether this sample was playing before a skin source change.
|
|
/// </summary>
|
|
private bool wasPlaying;
|
|
|
|
private void clearPreviousSamples()
|
|
{
|
|
// 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;
|
|
|
|
sampleContainer.Clear();
|
|
Sample = null;
|
|
}
|
|
|
|
private void updateSample()
|
|
{
|
|
if (sampleInfo == null)
|
|
return;
|
|
|
|
var sample = CurrentSkin.GetSample(sampleInfo);
|
|
|
|
if (sample == null)
|
|
return;
|
|
|
|
sampleContainer.Add(Sample = new DrawableSample(sample));
|
|
|
|
// Start playback internally for the new sample if the previous one was playing beforehand.
|
|
if (wasPlaying && Looping)
|
|
Play();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Plays the sample.
|
|
/// </summary>
|
|
public void Play()
|
|
{
|
|
if (Sample == null)
|
|
return;
|
|
|
|
activeChannel = Sample.GetChannel();
|
|
activeChannel.Looping = Looping;
|
|
activeChannel.Play();
|
|
|
|
Played = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stops the sample.
|
|
/// </summary>
|
|
public void Stop()
|
|
{
|
|
activeChannel?.Stop();
|
|
activeChannel = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether the sample is currently playing.
|
|
/// </summary>
|
|
public bool Playing => activeChannel?.Playing ?? false;
|
|
|
|
public bool Played { get; private set; }
|
|
|
|
private bool looping;
|
|
|
|
/// <summary>
|
|
/// Whether the sample should loop on completion.
|
|
/// </summary>
|
|
public bool Looping
|
|
{
|
|
get => looping;
|
|
set
|
|
{
|
|
looping = value;
|
|
|
|
if (activeChannel != null)
|
|
activeChannel.Looping = value;
|
|
}
|
|
}
|
|
|
|
protected override void Dispose(bool isDisposing)
|
|
{
|
|
base.Dispose(isDisposing);
|
|
|
|
if (CurrentSkin.IsNotNull())
|
|
CurrentSkin.SourceChanged -= skinChangedImmediate;
|
|
}
|
|
|
|
#region Re-expose AudioContainer
|
|
|
|
public BindableNumber<double> Volume => sampleContainer.Volume;
|
|
|
|
public BindableNumber<double> Balance => sampleContainer.Balance;
|
|
|
|
public BindableNumber<double> Frequency => sampleContainer.Frequency;
|
|
|
|
public BindableNumber<double> Tempo => sampleContainer.Tempo;
|
|
|
|
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);
|
|
|
|
public void RemoveAdjustment(AdjustableProperty type, IBindable<double> adjustBindable) => sampleContainer.RemoveAdjustment(type, adjustBindable);
|
|
|
|
public void RemoveAllAdjustments(AdjustableProperty type) => sampleContainer.RemoveAllAdjustments(type);
|
|
|
|
public IBindable<double> AggregateVolume => sampleContainer.AggregateVolume;
|
|
|
|
public IBindable<double> AggregateBalance => sampleContainer.AggregateBalance;
|
|
|
|
public IBindable<double> AggregateFrequency => sampleContainer.AggregateFrequency;
|
|
|
|
public IBindable<double> AggregateTempo => sampleContainer.AggregateTempo;
|
|
|
|
#endregion
|
|
}
|
|
}
|