2020-11-19 18:52:34 +08: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.
2022-06-17 15:37:17 +08:00
#nullable disable
2020-11-19 18:52:34 +08:00
using System ;
2021-06-08 14:05:14 +08:00
using System.Linq ;
2020-11-19 21:30:21 +08:00
using JetBrains.Annotations ;
2020-11-19 18:52:34 +08:00
using osu.Framework.Audio ;
2021-01-18 20:24:10 +08:00
using osu.Framework.Audio.Sample ;
2020-11-19 18:52:34 +08:00
using osu.Framework.Bindables ;
2022-11-09 15:45:06 +08:00
using osu.Framework.Extensions.ObjectExtensions ;
2020-11-19 18:52:34 +08:00
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Audio ;
2020-11-19 20:01:38 +08:00
using osu.Framework.Graphics.Containers ;
2020-11-19 18:52:34 +08:00
using osu.Game.Audio ;
namespace osu.Game.Skinning
{
2020-11-19 21:30:21 +08:00
/// <summary>
/// A sample corresponding to an <see cref="ISampleInfo"/> that supports being pooled and responding to skin changes.
/// </summary>
2021-02-22 13:54:48 +08:00
public class PoolableSkinnableSample : SkinReloadableDrawable , IAdjustableAudioComponent
2020-11-19 18:52:34 +08:00
{
2020-11-19 21:30:21 +08:00
/// <summary>
/// The currently-loaded <see cref="DrawableSample"/>.
/// </summary>
[CanBeNull]
public DrawableSample Sample { get ; private set ; }
2020-11-19 20:01:38 +08:00
2020-11-19 21:30:21 +08:00
private readonly AudioContainer < DrawableSample > sampleContainer ;
2020-11-19 18:52:34 +08:00
private ISampleInfo sampleInfo ;
2021-01-19 16:11:40 +08:00
private SampleChannel activeChannel ;
2020-11-19 18:52:34 +08:00
2020-11-19 21:30:21 +08: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 18:52:34 +08:00
public PoolableSkinnableSample ( )
{
2020-11-19 20:01:38 +08:00
InternalChild = sampleContainer = new AudioContainer < DrawableSample > { RelativeSizeAxes = Axes . Both } ;
2020-11-19 18:52:34 +08:00
}
2020-11-19 21:30:21 +08: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 18:52:34 +08:00
public PoolableSkinnableSample ( ISampleInfo sampleInfo )
2020-11-19 20:01:38 +08:00
: this ( )
2020-11-19 18:52:34 +08:00
{
Apply ( sampleInfo ) ;
}
2020-11-19 21:30:21 +08:00
/// <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 18:52:34 +08: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 20:01:38 +08:00
Volume . Value = sampleInfo . Volume / 100.0 ;
2020-11-19 18:52:34 +08:00
if ( LoadState > = LoadState . Ready )
updateSample ( ) ;
}
2021-06-08 14:05:14 +08:00
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 ( ) ;
}
2021-05-27 13:50:42 +08:00
protected override void SkinChanged ( ISkinSource skin )
2020-11-19 18:52:34 +08:00
{
2021-05-27 13:50:42 +08:00
base . SkinChanged ( skin ) ;
2020-11-19 18:52:34 +08:00
updateSample ( ) ;
}
2021-06-08 14:05:14 +08:00
/// <summary>
/// Whether this sample was playing before a skin source change.
/// </summary>
private bool wasPlaying ;
private void clearPreviousSamples ( )
2020-11-19 18:52:34 +08:00
{
2021-06-08 14:05:14 +08: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 ;
2020-11-19 21:30:21 +08:00
2021-06-08 14:05:14 +08:00
wasPlaying = Playing ;
2020-11-19 21:30:21 +08:00
2020-11-19 20:01:38 +08:00
sampleContainer . Clear ( ) ;
2020-11-30 18:19:24 +08:00
Sample = null ;
2021-06-08 14:05:14 +08:00
}
private void updateSample ( )
{
if ( sampleInfo = = null )
return ;
2020-11-19 18:52:34 +08:00
2021-03-19 19:25:21 +08:00
var sample = CurrentSkin . GetSample ( sampleInfo ) ;
2020-11-19 18:52:34 +08:00
2021-03-19 19:25:21 +08:00
if ( sample = = null )
2020-11-19 18:52:34 +08:00
return ;
2021-03-19 19:25:21 +08:00
sampleContainer . Add ( Sample = new DrawableSample ( sample ) ) ;
2020-11-19 21:30:21 +08:00
// Start playback internally for the new sample if the previous one was playing beforehand.
2020-12-07 01:59:38 +08:00
if ( wasPlaying & & Looping )
2020-11-19 21:30:21 +08:00
Play ( ) ;
2020-11-19 18:52:34 +08:00
}
2020-11-19 21:30:21 +08:00
/// <summary>
/// Plays the sample.
/// </summary>
2021-01-19 16:11:40 +08:00
public void Play ( )
{
if ( Sample = = null )
return ;
2021-02-12 18:05:17 +08:00
activeChannel = Sample . GetChannel ( ) ;
activeChannel . Looping = Looping ;
activeChannel . Play ( ) ;
2021-01-20 13:05:35 +08:00
Played = true ;
2021-01-19 16:11:40 +08:00
}
2020-11-19 18:52:34 +08:00
2020-11-19 21:30:21 +08:00
/// <summary>
/// Stops the sample.
/// </summary>
2021-01-20 13:05:35 +08:00
public void Stop ( )
{
activeChannel ? . Stop ( ) ;
activeChannel = null ;
}
2020-11-19 18:52:34 +08:00
2020-11-19 21:30:21 +08:00
/// <summary>
/// Whether the sample is currently playing.
/// </summary>
2021-01-19 16:11:40 +08:00
public bool Playing = > activeChannel ? . Playing ? ? false ;
2021-01-20 13:05:35 +08:00
public bool Played { get ; private set ; }
2020-11-19 18:52:34 +08:00
private bool looping ;
2020-11-19 21:30:21 +08:00
/// <summary>
2020-11-19 21:47:11 +08:00
/// Whether the sample should loop on completion.
2020-11-19 21:30:21 +08:00
/// </summary>
2020-11-19 18:52:34 +08:00
public bool Looping
{
get = > looping ;
set
{
looping = value ;
2021-01-19 16:11:40 +08:00
if ( activeChannel ! = null )
activeChannel . Looping = value ;
2020-11-19 18:52:34 +08:00
}
}
2021-06-08 14:05:14 +08:00
protected override void Dispose ( bool isDisposing )
{
base . Dispose ( isDisposing ) ;
2022-11-09 15:45:06 +08:00
if ( CurrentSkin . IsNotNull ( ) )
2021-06-08 15:15:17 +08:00
CurrentSkin . SourceChanged - = skinChangedImmediate ;
2021-06-08 14:05:14 +08:00
}
2020-11-30 17:40:22 +08:00
#region Re - expose AudioContainer
2020-11-19 20:01:38 +08:00
public BindableNumber < double > Volume = > sampleContainer . Volume ;
2020-11-19 18:52:34 +08:00
2020-11-19 20:01:38 +08:00
public BindableNumber < double > Balance = > sampleContainer . Balance ;
2020-11-19 18:52:34 +08:00
2020-11-19 20:01:38 +08:00
public BindableNumber < double > Frequency = > sampleContainer . Frequency ;
2020-11-19 18:52:34 +08:00
2020-11-19 20:01:38 +08:00
public BindableNumber < double > Tempo = > sampleContainer . Tempo ;
2020-11-19 18:52:34 +08:00
2021-02-22 13:18:52 +08:00
public void BindAdjustments ( IAggregateAudioAdjustment component ) = > sampleContainer . BindAdjustments ( component ) ;
public void UnbindAdjustments ( IAggregateAudioAdjustment component ) = > sampleContainer . UnbindAdjustments ( component ) ;
2021-02-18 14:42:26 +08:00
public void AddAdjustment ( AdjustableProperty type , IBindable < double > adjustBindable ) = > sampleContainer . AddAdjustment ( type , adjustBindable ) ;
2020-11-19 18:52:34 +08:00
2021-02-18 14:42:26 +08:00
public void RemoveAdjustment ( AdjustableProperty type , IBindable < double > adjustBindable ) = > sampleContainer . RemoveAdjustment ( type , adjustBindable ) ;
2020-11-19 18:52:34 +08:00
2020-11-19 20:01:38 +08:00
public void RemoveAllAdjustments ( AdjustableProperty type ) = > sampleContainer . RemoveAllAdjustments ( type ) ;
2020-11-19 18:52:34 +08:00
2020-11-19 20:01:38 +08:00
public IBindable < double > AggregateVolume = > sampleContainer . AggregateVolume ;
2020-11-19 18:52:34 +08:00
2020-11-19 20:01:38 +08:00
public IBindable < double > AggregateBalance = > sampleContainer . AggregateBalance ;
2020-11-19 18:52:34 +08:00
2020-11-19 20:01:38 +08:00
public IBindable < double > AggregateFrequency = > sampleContainer . AggregateFrequency ;
2020-11-19 18:52:34 +08:00
2020-11-19 20:01:38 +08:00
public IBindable < double > AggregateTempo = > sampleContainer . AggregateTempo ;
2020-11-30 17:40:22 +08:00
#endregion
2020-11-19 18:52:34 +08:00
}
}