mirror of
https://github.com/SK-la/osu-framework.git
synced 2026-03-15 03:20:30 +00:00
317 lines
11 KiB
C#
317 lines
11 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 NUnit.Framework;
|
||
using osu.Framework.Allocation;
|
||
using osu.Framework.Audio;
|
||
using osu.Framework.Audio.Track;
|
||
using osu.Framework.Bindables;
|
||
using osu.Framework.Graphics;
|
||
using osu.Framework.Graphics.Audio;
|
||
using osu.Framework.Graphics.Containers;
|
||
using osu.Framework.Graphics.Shapes;
|
||
using osu.Framework.Graphics.Sprites;
|
||
using osu.Framework.Graphics.UserInterface;
|
||
using osu.Framework.Input.Events;
|
||
using osu.Framework.Testing;
|
||
using osuTK;
|
||
using osuTK.Graphics;
|
||
|
||
namespace osu.Framework.Tests.Visual.Drawables
|
||
{
|
||
public partial class TestSceneWaveform : FrameworkTestScene
|
||
{
|
||
private BasicButton button;
|
||
private Track track;
|
||
private Waveform waveform;
|
||
private Container<Drawable> waveformContainer;
|
||
private readonly BindableFloat zoom = new BindableFloat(1) { MinValue = 0.1f, MaxValue = 2000 };
|
||
|
||
private ScrollContainer<Drawable> scroll;
|
||
|
||
private ITrackStore store;
|
||
|
||
[BackgroundDependencyLoader]
|
||
private void load(Game game, AudioManager audio)
|
||
{
|
||
store = audio.GetTrackStore(game.Resources);
|
||
|
||
const float track_width = 1366; // required because RelativeSizeAxes.X doesn't seem to work with horizontal scroll
|
||
|
||
Child = new FillFlowContainer
|
||
{
|
||
RelativeSizeAxes = Axes.Both,
|
||
Direction = FillDirection.Vertical,
|
||
Children = new Drawable[]
|
||
{
|
||
new FillFlowContainer
|
||
{
|
||
AutoSizeAxes = Axes.Y,
|
||
RelativeSizeAxes = Axes.X,
|
||
Spacing = new Vector2(10),
|
||
Children = new Drawable[]
|
||
{
|
||
button = new BasicButton
|
||
{
|
||
Text = "Start",
|
||
Size = new Vector2(100, 50),
|
||
BackgroundColour = Color4.DarkSlateGray,
|
||
Anchor = Anchor.CentreLeft,
|
||
Origin = Anchor.CentreLeft,
|
||
Action = startStop
|
||
},
|
||
new SpriteText
|
||
{
|
||
Text = "Zoom Level:",
|
||
Origin = Anchor.CentreLeft,
|
||
Anchor = Anchor.CentreLeft,
|
||
},
|
||
new BasicSliderBar<float>
|
||
{
|
||
Anchor = Anchor.CentreLeft,
|
||
Origin = Anchor.CentreLeft,
|
||
Size = new Vector2(200, 40),
|
||
Current = zoom
|
||
},
|
||
},
|
||
},
|
||
scroll = new BasicScrollContainer(Direction.Horizontal)
|
||
{
|
||
RelativeSizeAxes = Axes.Both,
|
||
Child = waveformContainer = new FillFlowContainer
|
||
{
|
||
RelativeSizeAxes = Axes.Y,
|
||
Width = track_width,
|
||
Direction = FillDirection.Vertical,
|
||
Spacing = new Vector2(0, 10)
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
zoom.ValueChanged += e => waveformContainer.Width = track_width * e.NewValue;
|
||
}
|
||
|
||
private void loadTrack(bool stereo)
|
||
{
|
||
string trackName = stereo
|
||
? "Tracks/sample-track.mp3"
|
||
: "Tracks/sample-track-mono.mp3";
|
||
|
||
track = store.Get(trackName);
|
||
waveform = new Waveform(store.GetStream(trackName));
|
||
}
|
||
|
||
[SetUpSteps]
|
||
public void SetUpSteps()
|
||
{
|
||
AddStep("Load stereo track", () => loadTrack(true));
|
||
}
|
||
|
||
/// <summary>
|
||
/// When zooming in very close – or even zooming in a normal amount on a very long track – the number of points in the waveform
|
||
/// can become very high (in the millions).
|
||
///
|
||
/// In this case, we need to be careful no iteration is performed over the point data. This tests the case of being scrolled to the
|
||
/// far end of the waveform, which is the worse-case-scenario and requires special consideration.
|
||
/// </summary>
|
||
[Test]
|
||
public void TestHighZoomEndOfTrackPerformance()
|
||
{
|
||
TestWaveform graph = null;
|
||
|
||
AddStep("create waveform", () => waveformContainer.Child = graph = new TestWaveform(track, 1) { Waveform = waveform });
|
||
AddUntilStep("wait for load", () => graph.Regenerated);
|
||
|
||
AddStep("set zoom to highest", () => zoom.Value = zoom.MaxValue);
|
||
|
||
AddStep("seek to end", () => scroll.ScrollToEnd());
|
||
}
|
||
|
||
[Test]
|
||
public void TestMonoTrack()
|
||
{
|
||
AddStep("Load mono track", () => loadTrack(false));
|
||
TestWaveform graph = null;
|
||
|
||
AddStep("create waveform", () => waveformContainer.Child = graph = new TestWaveform(track, 1) { Waveform = waveform });
|
||
AddUntilStep("wait for load", () => graph.Regenerated);
|
||
}
|
||
|
||
[TestCase(1f)]
|
||
[TestCase(1f / 2)]
|
||
[TestCase(1f / 4)]
|
||
[TestCase(1f / 8)]
|
||
[TestCase(1f / 16)]
|
||
[TestCase(0)]
|
||
public void TestResolution(float resolution)
|
||
{
|
||
TestWaveform graph = null;
|
||
|
||
AddStep("create waveform", () => waveformContainer.Child = graph = new TestWaveform(track, resolution) { Waveform = waveform });
|
||
AddUntilStep("wait for load", () => graph.Regenerated);
|
||
}
|
||
|
||
[Test]
|
||
public void TestNullWaveform()
|
||
{
|
||
TestWaveform graph = null;
|
||
|
||
AddStep("create waveform", () => waveformContainer.Child = graph = new TestWaveform(track, 1) { Waveform = new Waveform(null) });
|
||
AddUntilStep("wait for load", () => graph.Regenerated);
|
||
}
|
||
|
||
[Test]
|
||
public void TestWaveformAlpha()
|
||
{
|
||
TestWaveform graph = null;
|
||
|
||
AddStep("create waveform", () => waveformContainer.Child = graph = new TestWaveform(track, 1)
|
||
{
|
||
Waveform = waveform,
|
||
Alpha = 0.5f,
|
||
});
|
||
|
||
AddUntilStep("wait for load", () => graph.Regenerated);
|
||
|
||
AddUntilStep("wait for sampling", () => graph.Waveform.GetPoints().Length > 0);
|
||
}
|
||
|
||
private void startStop()
|
||
{
|
||
if (track.IsRunning)
|
||
{
|
||
track.Stop();
|
||
button.Text = "Start";
|
||
}
|
||
else
|
||
{
|
||
track.Start();
|
||
button.Text = "Stop";
|
||
}
|
||
}
|
||
|
||
protected override void Dispose(bool isDisposing)
|
||
{
|
||
base.Dispose(isDisposing);
|
||
track?.Stop();
|
||
}
|
||
|
||
private partial class TestWaveform : CompositeDrawable
|
||
{
|
||
private readonly Track track;
|
||
private readonly TestWaveformGraph graph;
|
||
private readonly Drawable marker;
|
||
|
||
public TestWaveform(Track track, float resolution)
|
||
{
|
||
this.track = track;
|
||
|
||
RelativeSizeAxes = Axes.X;
|
||
Height = 100;
|
||
|
||
InternalChildren = new[]
|
||
{
|
||
graph = new TestWaveformGraph
|
||
{
|
||
RelativeSizeAxes = Axes.Both,
|
||
Resolution = resolution,
|
||
BaseColour = new Color4(232, 78, 6, 255),
|
||
LowColour = new Color4(255, 232, 100, 255),
|
||
MidColour = new Color4(255, 153, 19, 255),
|
||
HighColour = new Color4(255, 46, 7, 255),
|
||
},
|
||
new Container
|
||
{
|
||
Anchor = Anchor.Centre,
|
||
Origin = Anchor.Centre,
|
||
AutoSizeAxes = Axes.Both,
|
||
Children = new Drawable[]
|
||
{
|
||
new Box
|
||
{
|
||
RelativeSizeAxes = Axes.Both,
|
||
Colour = Color4.Black,
|
||
Alpha = 0.75f
|
||
},
|
||
new SpriteText
|
||
{
|
||
Anchor = Anchor.Centre,
|
||
Origin = Anchor.Centre,
|
||
Padding = new MarginPadding(4),
|
||
Text = $"Resolution: {resolution:0.00}"
|
||
}
|
||
}
|
||
},
|
||
marker = new Box
|
||
{
|
||
RelativeSizeAxes = Axes.Y,
|
||
RelativePositionAxes = Axes.X,
|
||
Width = 2,
|
||
Colour = Color4.Blue
|
||
},
|
||
};
|
||
}
|
||
|
||
public bool Regenerated => graph.Regenerated;
|
||
|
||
public Waveform Waveform
|
||
{
|
||
get => graph.Waveform;
|
||
set => graph.Waveform = value;
|
||
}
|
||
|
||
protected override void Update()
|
||
{
|
||
base.Update();
|
||
|
||
if (track.IsLoaded)
|
||
marker.X = (float)(track.CurrentTime / track.Length);
|
||
}
|
||
|
||
private bool mouseDown;
|
||
|
||
protected override bool OnMouseDown(MouseDownEvent e)
|
||
{
|
||
mouseDown = true;
|
||
seekTo(ToLocalSpace(e.ScreenSpaceMousePosition).X);
|
||
return true;
|
||
}
|
||
|
||
protected override void OnMouseUp(MouseUpEvent e)
|
||
{
|
||
mouseDown = false;
|
||
}
|
||
|
||
protected override bool OnMouseMove(MouseMoveEvent e)
|
||
{
|
||
if (mouseDown)
|
||
{
|
||
seekTo(ToLocalSpace(e.ScreenSpaceMousePosition).X);
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private void seekTo(float x)
|
||
{
|
||
track.Seek(x / DrawWidth * track.Length);
|
||
}
|
||
}
|
||
|
||
private partial class TestWaveformGraph : WaveformGraph
|
||
{
|
||
public bool Regenerated { get; private set; }
|
||
|
||
protected override void OnWaveformRegenerated(Waveform waveform)
|
||
{
|
||
base.OnWaveformRegenerated(waveform);
|
||
Regenerated = true;
|
||
}
|
||
}
|
||
}
|
||
}
|