-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSkinnableSound.cs
205 lines (160 loc) · 6.71 KB
/
SkinnableSound.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
// 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.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Audio;
using osu.Framework.Graphics.Containers;
using osu.Game.Audio;
namespace osu.Game.Skinning
{
/// <summary>
/// A sound consisting of one or more samples to be played.
/// </summary>
public partial class SkinnableSound : SkinReloadableDrawable, IAdjustableAudioComponent
{
public override bool RemoveWhenNotAlive => false;
public override bool RemoveCompletedTransforms => false;
/// <summary>
/// Whether to play the underlying sample when aggregate volume is zero.
/// Note that this is checked at the point of calling <see cref="Play"/>; changing the volume post-play will not begin playback.
/// Defaults to false unless <see cref="Looping"/>.
/// </summary>
/// <remarks>
/// Can serve as an optimisation if it is known ahead-of-time that this behaviour is allowed in a given use case.
/// </remarks>
protected bool PlayWhenZeroVolume => Looping;
/// <summary>
/// All raw <see cref="DrawableSamples"/>s contained in this <see cref="SkinnableSound"/>.
/// </summary>
protected IEnumerable<DrawableSample> DrawableSamples => samplesContainer.Select(c => c.Sample).Where(s => s != null);
private readonly AudioContainer<PoolableSkinnableSample> samplesContainer;
[Resolved]
private IPooledSampleProvider? samplePool { get; set; }
/// <summary>
/// Creates a new <see cref="SkinnableSound"/>.
/// </summary>
public SkinnableSound()
{
InternalChild = samplesContainer = new AudioContainer<PoolableSkinnableSample>();
}
/// <summary>
/// Creates a new <see cref="SkinnableSound"/> with some initial samples.
/// </summary>
/// <param name="samples">The initial samples.</param>
public SkinnableSound(IEnumerable<ISampleInfo> samples)
: this()
{
this.samples = samples.ToArray();
}
/// <summary>
/// Creates a new <see cref="SkinnableSound"/> with an initial sample.
/// </summary>
/// <param name="sample">The initial sample.</param>
public SkinnableSound(ISampleInfo sample)
: this(new[] { sample })
{
}
private ISampleInfo[] samples = Array.Empty<ISampleInfo>();
/// <summary>
/// The samples that should be played.
/// </summary>
public ISampleInfo[] Samples
{
get => samples;
set
{
if (samples == value)
return;
samples = value;
if (LoadState >= LoadState.Ready)
updateSamples();
}
}
public void ClearSamples() => Samples = Array.Empty<ISampleInfo>();
private bool looping;
/// <summary>
/// Whether the samples should loop on completion.
/// </summary>
public bool Looping
{
get => looping;
set
{
if (value == looping) return;
looping = value;
samplesContainer.ForEach(c => c.Looping = looping);
}
}
/// <summary>
/// Plays the samples.
/// </summary>
public virtual void Play()
{
FlushPendingSkinChanges();
samplesContainer.ForEach(c =>
{
if (PlayWhenZeroVolume || c.AggregateVolume.Value > 0)
{
c.Stop();
c.Play();
}
});
}
protected override void LoadAsyncComplete()
{
// ensure samples are constructed before SkinChanged() is called via base.LoadAsyncComplete().
if (!samplesContainer.Any())
updateSamples();
base.LoadAsyncComplete();
}
/// <summary>
/// Stops the samples.
/// </summary>
public virtual void Stop()
{
samplesContainer.ForEach(c => c.Stop());
}
private void updateSamples()
{
bool wasPlaying = IsPlaying;
// Remove all pooled samples (return them to the pool), and dispose the rest.
samplesContainer.RemoveAll(s => s.IsInPool, false);
samplesContainer.Clear();
foreach (var s in samples)
{
var sample = samplePool?.GetPooledSample(s) ?? new PoolableSkinnableSample(s);
sample.Looping = Looping;
sample.Volume.Value = s.Volume / 100.0;
samplesContainer.Add(sample);
}
if (wasPlaying && Looping)
Play();
}
#region Re-expose AudioContainer
public BindableNumber<double> Volume => samplesContainer.Volume;
public BindableNumber<double> Balance => samplesContainer.Balance;
public BindableNumber<double> Frequency => samplesContainer.Frequency;
public BindableNumber<double> Tempo => samplesContainer.Tempo;
public void BindAdjustments(IAggregateAudioAdjustment component) => samplesContainer.BindAdjustments(component);
public void UnbindAdjustments(IAggregateAudioAdjustment component) => samplesContainer.UnbindAdjustments(component);
public void AddAdjustment(AdjustableProperty type, IBindable<double> adjustBindable) => samplesContainer.AddAdjustment(type, adjustBindable);
public void RemoveAdjustment(AdjustableProperty type, IBindable<double> adjustBindable) => samplesContainer.RemoveAdjustment(type, adjustBindable);
public void RemoveAllAdjustments(AdjustableProperty type) => samplesContainer.RemoveAllAdjustments(type);
/// <summary>
/// Whether any samples are currently playing.
/// </summary>
public bool IsPlaying => samplesContainer.Any(s => s.Playing);
public bool IsPlayed => samplesContainer.Any(s => s.Played);
public IBindable<double> AggregateVolume => samplesContainer.AggregateVolume;
public IBindable<double> AggregateBalance => samplesContainer.AggregateBalance;
public IBindable<double> AggregateFrequency => samplesContainer.AggregateFrequency;
public IBindable<double> AggregateTempo => samplesContainer.AggregateTempo;
#endregion
}
}