diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index 922439fcb8..3a7c8b2ec0 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using osu.Framework.Bindables; using osu.Game.Beatmaps.Timing; using osu.Game.Graphics; diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 1b0f0a3f5e..d0ab4d1f98 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -24,8 +24,8 @@ namespace osu.Game.Screens.Edit.Timing [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, OsuColour colours) { - Height = 200; RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; CornerRadius = LabelledDrawable.CORNER_RADIUS; Masking = true; @@ -39,20 +39,44 @@ namespace osu.Game.Screens.Edit.Timing }, new GridContainer { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, RowDimensions = new[] { - new Dimension(), + new Dimension(GridSizeMode.Absolute, 200), new Dimension(GridSizeMode.Absolute, 60), }, Content = new[] { new Drawable[] { - new MetronomeDisplay + new Container { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension() + }, + Content = new[] + { + new Drawable[] + { + new MetronomeDisplay + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + new WaveformComparisonDisplay(), + } + }, + } + } } }, new Drawable[] diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs new file mode 100644 index 0000000000..67a1d30ebe --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -0,0 +1,152 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System; +using System.Linq; +using osu.Framework.Allocation; +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.Input.Events; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Edit.Timing +{ + internal class WaveformComparisonDisplay : CompositeDrawable + { + private const int total_waveforms = 8; + + private OsuSpriteText beatIndexText = null!; + + private readonly BindableNumber beatLength = new BindableDouble(); + + [Resolved] + private IBindable beatmap { get; set; } = null!; + + [Resolved] + private Bindable selectedGroup { get; set; } = null!; + + [Resolved] + private EditorClock editorClock { get; set; } = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + private TimingControlPoint timingPoint = TimingControlPoint.DEFAULT; + + private int lastDisplayedBeatIndex; + + public WaveformComparisonDisplay() + { + RelativeSizeAxes = Axes.Both; + + CornerRadius = LabelledDrawable.CORNER_RADIUS; + Masking = true; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + for (int i = 0; i < total_waveforms; i++) + { + AddInternal(new WaveformGraph + { + RelativeSizeAxes = Axes.Both, + BaseColour = colourProvider.Colour0, + LowColour = colourProvider.Colour1, + MidColour = colourProvider.Colour2, + HighColour = colourProvider.Colour4, + Waveform = beatmap.Value.Waveform, + Resolution = 1, + RelativePositionAxes = Axes.Both, + Height = 1f / total_waveforms, + Y = (float)i / total_waveforms, + }); + } + + AddInternal(new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = Color4.White, + RelativeSizeAxes = Axes.Y, + Width = 3, + }); + + AddInternal(beatIndexText = new OsuSpriteText + { + Margin = new MarginPadding(5), + }); + + selectedGroup.BindValueChanged(selectedGroupChanged, true); + beatLength.BindValueChanged(_ => showFrom(lastDisplayedBeatIndex), true); + } + + private void selectedGroupChanged(ValueChangedEvent group) + { + timingPoint = selectedGroup.Value?.ControlPoints.OfType().FirstOrDefault() + ?? TimingControlPoint.DEFAULT; + + beatLength.UnbindBindings(); + beatLength.BindTo(timingPoint.BeatLengthBindable); + } + + protected override bool OnHover(HoverEvent e) => true; + + protected override bool OnMouseMove(MouseMoveEvent e) + { + float trackLength = (float)beatmap.Value.Track.Length; + int totalBeatsAvailable = (int)(trackLength / timingPoint.BeatLength); + + Scheduler.AddOnce(showFrom, (int)(e.MousePosition.X / DrawWidth * totalBeatsAvailable)); + + return base.OnMouseMove(e); + } + + protected override void Update() + { + base.Update(); + + if (!IsHovered) + { + int beatOffset = (int)Math.Max(0, ((editorClock.CurrentTime - selectedGroup.Value.Time) / timingPoint.BeatLength)); + + showFrom(beatOffset); + } + } + + private void showFrom(int beatIndex) + { + const float visible_width = 300; + + float trackLength = (float)beatmap.Value.Track.Length; + float scale = trackLength / visible_width; + + beatIndexText.Text = beatIndex.ToString(); + + foreach (var waveform in InternalChildren.OfType()) + { + // offset to the required beat index. + float offset = (float)(selectedGroup.Value.Time + (beatIndex * timingPoint.BeatLength - (visible_width / 2))) / trackLength * scale; + + waveform.X = -offset; + waveform.Scale = new Vector2(scale, 1); + + beatIndex++; + } + + lastDisplayedBeatIndex = beatIndex; + } + } +}