A game framework written with osu! in mind.
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2// See the LICENCE file in the repository root for full licence text.
3
4using NUnit.Framework;
5using osu.Framework.Allocation;
6using osu.Framework.Audio;
7using osu.Framework.Audio.Track;
8using osu.Framework.Bindables;
9using osu.Framework.Graphics;
10using osu.Framework.Graphics.Audio;
11using osu.Framework.Graphics.Containers;
12using osu.Framework.Graphics.Shapes;
13using osu.Framework.Graphics.Sprites;
14using osu.Framework.Graphics.UserInterface;
15using osu.Framework.Input.Events;
16using osuTK;
17using osuTK.Graphics;
18
19namespace osu.Framework.Tests.Visual.Drawables
20{
21 public class TestSceneWaveform : FrameworkTestScene
22 {
23 private BasicButton button;
24 private Track track;
25 private Waveform waveform;
26 private Container<Drawable> waveformContainer;
27 private readonly Bindable<float> zoom = new BindableFloat(1) { MinValue = 0.1f, MaxValue = 20 };
28
29 [BackgroundDependencyLoader]
30 private void load(Game game, AudioManager audio)
31 {
32 var store = audio.GetTrackStore(game.Resources);
33
34 const string track_name = "Tracks/sample-track.mp3";
35
36 track = store.Get(track_name);
37 waveform = new Waveform(store.GetStream(track_name));
38
39 const float track_width = 1366; // required because RelativeSizeAxes.X doesn't seem to work with horizontal scroll
40
41 Child = new FillFlowContainer
42 {
43 RelativeSizeAxes = Axes.Both,
44 Direction = FillDirection.Vertical,
45 Children = new Drawable[]
46 {
47 new FillFlowContainer
48 {
49 AutoSizeAxes = Axes.Y,
50 RelativeSizeAxes = Axes.X,
51 Spacing = new Vector2(10),
52 Children = new Drawable[]
53 {
54 button = new BasicButton
55 {
56 Text = "Start",
57 Size = new Vector2(100, 50),
58 BackgroundColour = Color4.DarkSlateGray,
59 Anchor = Anchor.CentreLeft,
60 Origin = Anchor.CentreLeft,
61 Action = startStop
62 },
63 new SpriteText
64 {
65 Text = "Zoom Level:",
66 Origin = Anchor.CentreLeft,
67 Anchor = Anchor.CentreLeft,
68 },
69 new BasicSliderBar<float>
70 {
71 Anchor = Anchor.CentreLeft,
72 Origin = Anchor.CentreLeft,
73 Size = new Vector2(200, 40),
74 Current = zoom
75 },
76 },
77 },
78 new BasicScrollContainer(Direction.Horizontal)
79 {
80 RelativeSizeAxes = Axes.Both,
81 Child = waveformContainer = new FillFlowContainer
82 {
83 RelativeSizeAxes = Axes.Y,
84 Width = track_width,
85 Direction = FillDirection.Vertical,
86 Spacing = new Vector2(0, 10)
87 }
88 }
89 }
90 };
91
92 zoom.ValueChanged += e => waveformContainer.Width = track_width * e.NewValue;
93 }
94
95 [TestCase(1f)]
96 [TestCase(1f / 2)]
97 [TestCase(1f / 4)]
98 [TestCase(1f / 8)]
99 [TestCase(1f / 16)]
100 [TestCase(0)]
101 public void TestResolution(float resolution)
102 {
103 TestWaveform graph = null;
104
105 AddStep("create waveform", () => waveformContainer.Child = graph = new TestWaveform(track, resolution) { Waveform = waveform });
106 AddUntilStep("wait for load", () => graph.Regenerated);
107 }
108
109 [Test]
110 public void TestNullWaveform()
111 {
112 TestWaveform graph = null;
113
114 AddStep("create waveform", () => waveformContainer.Child = graph = new TestWaveform(track, 1) { Waveform = new Waveform(null) });
115 AddUntilStep("wait for load", () => graph.Regenerated);
116 }
117
118 [Test]
119 public void TestWaveformAlpha()
120 {
121 TestWaveform graph = null;
122
123 AddStep("create waveform", () => waveformContainer.Child = graph = new TestWaveform(track, 1)
124 {
125 Waveform = waveform,
126 Alpha = 0.5f,
127 });
128
129 AddUntilStep("wait for load", () => graph.Regenerated);
130 }
131
132 private void startStop()
133 {
134 if (track.IsRunning)
135 {
136 track.Stop();
137 button.Text = "Start";
138 }
139 else
140 {
141 track.Start();
142 button.Text = "Stop";
143 }
144 }
145
146 protected override void Dispose(bool isDisposing)
147 {
148 base.Dispose(isDisposing);
149 track?.Stop();
150 }
151
152 private class TestWaveform : CompositeDrawable
153 {
154 private readonly Track track;
155 private readonly TestWaveformGraph graph;
156 private readonly Drawable marker;
157
158 public TestWaveform(Track track, float resolution)
159 {
160 this.track = track;
161
162 RelativeSizeAxes = Axes.X;
163 Height = 100;
164
165 InternalChildren = new[]
166 {
167 graph = new TestWaveformGraph
168 {
169 RelativeSizeAxes = Axes.Both,
170 Resolution = resolution,
171 BaseColour = new Color4(232, 78, 6, 255),
172 LowColour = new Color4(255, 232, 100, 255),
173 MidColour = new Color4(255, 153, 19, 255),
174 HighColour = new Color4(255, 46, 7, 255),
175 },
176 new Container
177 {
178 Anchor = Anchor.Centre,
179 Origin = Anchor.Centre,
180 AutoSizeAxes = Axes.Both,
181 Children = new Drawable[]
182 {
183 new Box
184 {
185 RelativeSizeAxes = Axes.Both,
186 Colour = Color4.Black,
187 Alpha = 0.75f
188 },
189 new SpriteText
190 {
191 Anchor = Anchor.Centre,
192 Origin = Anchor.Centre,
193 Padding = new MarginPadding(4),
194 Text = $"Resolution: {resolution:0.00}"
195 }
196 }
197 },
198 marker = new Box
199 {
200 RelativeSizeAxes = Axes.Y,
201 RelativePositionAxes = Axes.X,
202 Width = 2,
203 Colour = Color4.Blue
204 },
205 };
206 }
207
208 public bool Regenerated => graph.Regenerated;
209
210 public Waveform Waveform
211 {
212 set => graph.Waveform = value;
213 }
214
215 protected override void Update()
216 {
217 base.Update();
218
219 if (track.IsLoaded)
220 marker.X = (float)(track.CurrentTime / track.Length);
221 }
222
223 private bool mouseDown;
224
225 protected override bool OnMouseDown(MouseDownEvent e)
226 {
227 mouseDown = true;
228 seekTo(ToLocalSpace(e.ScreenSpaceMousePosition).X);
229 return true;
230 }
231
232 protected override void OnMouseUp(MouseUpEvent e)
233 {
234 mouseDown = false;
235 }
236
237 protected override bool OnMouseMove(MouseMoveEvent e)
238 {
239 if (mouseDown)
240 {
241 seekTo(ToLocalSpace(e.ScreenSpaceMousePosition).X);
242 return true;
243 }
244
245 return false;
246 }
247
248 private void seekTo(float x)
249 {
250 track.Seek(x / DrawWidth * track.Length);
251 }
252 }
253
254 private class TestWaveformGraph : WaveformGraph
255 {
256 public bool Regenerated { get; private set; }
257
258 protected override void OnWaveformRegenerated(Waveform waveform)
259 {
260 base.OnWaveformRegenerated(waveform);
261 Regenerated = true;
262 }
263 }
264 }
265}