Skip to content
17 changes: 17 additions & 0 deletions osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardEncoderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,23 @@ public void TestBackground()
Assert.That(decodedAfterEncode.Beatmap.BeatmapInfo.Metadata.BackgroundFile, Is.EqualTo("bg.jpg"));
}

[Test]
public void TestBackgroundOffset()
{
var initial = createComponents();
initial.Beatmap.BeatmapInfo.Metadata.BackgroundFile = "bg_offset.jpg";
initial.Storyboard.BackgroundOffset = new Vector2(0, 45);

var encoded = encode(initial);
var decodedAfterEncode = decode(encoded);

Assert.Multiple(() =>
{
Assert.That(decodedAfterEncode.Beatmap.BeatmapInfo.Metadata.BackgroundFile, Is.EqualTo("bg_offset.jpg"));
Assert.That(decodedAfterEncode.Storyboard.BackgroundOffset, Is.EqualTo(new Vector2(0, 45)));
});
}

[Test]
public void TestVideos()
{
Expand Down
6 changes: 6 additions & 0 deletions osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,12 @@ public TestSceneFormControls()
Caption = "File selector",
PlaceholderText = "Select a file",
},
new FormFileSelector
{
Caption = "File selector with deselection",
PlaceholderText = "Select a file",
AllowClear = true,
},
new FormBeatmapFileSelector(true)
{
Caption = "File selector with intermediate choice dialog",
Expand Down
39 changes: 39 additions & 0 deletions osu.Game.Tests/Visual/UserInterface/TestSceneMigrateAudioDialog.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// 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 NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Configuration;
using osu.Game.Overlays;

namespace osu.Game.Tests.Visual.UserInterface
{
public partial class TestSceneMigrateAudioDialog : OsuManualInputManagerTestScene
{
private DialogOverlay overlay = null!;

[SetUpSteps]
public void SetUpSteps()
{
AddStep("create dialog overlay", () => Child = overlay = new DialogOverlay());
}

[Test]
public void TestWasUsing()
{
AddStep("create dialog", () =>
{
overlay.Push(new MigrateNewAudioDialog(true));
});
}

[Test]
public void TestNotUsing()
{
AddStep("create dialog", () =>
{
overlay.Push(new MigrateNewAudioDialog(false));
});
}
}
}
4 changes: 2 additions & 2 deletions osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs
Original file line number Diff line number Diff line change
Expand Up @@ -244,10 +244,10 @@ private void load(BeatmapSetOverlay? beatmapSetOverlay)
});

if (BeatmapSet.HasVideo)
leftIconArea.Add(new VideoIconPill { IconSize = new Vector2(16) });
leftIconArea.Add(new VideoIconPill());

if (BeatmapSet.HasStoryboard)
leftIconArea.Add(new StoryboardIconPill { IconSize = new Vector2(16) });
leftIconArea.Add(new StoryboardIconPill());

if (BeatmapSet.FeaturedInSpotlight)
{
Expand Down
4 changes: 2 additions & 2 deletions osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,10 +226,10 @@ private void load()
});

if (BeatmapSet.HasVideo)
leftIconArea.Add(new VideoIconPill { IconSize = new Vector2(16) });
leftIconArea.Add(new VideoIconPill());

if (BeatmapSet.HasStoryboard)
leftIconArea.Add(new StoryboardIconPill { IconSize = new Vector2(16) });
leftIconArea.Add(new StoryboardIconPill());

if (BeatmapSet.FeaturedInSpotlight)
{
Expand Down
14 changes: 14 additions & 0 deletions osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,20 @@ private void handleEvents(string line, bool isPrimaryStream)

switch (type)
{
case LegacyEventType.Background:
{
// the actual filename is handled in `LegacyBeatmapDecoder`.
// this only handles the background offset, because it does not logically belong in `Beatmap` or related classes.
if (split.Length > 4)
{
float x = Parsing.ParseFloat(split[3]);
float y = Parsing.ParseFloat(split[4]);
storyboard.BackgroundOffset = new Vector2(x, y);
}

break;
}

case LegacyEventType.Video:
{
int offset = Parsing.ParseInt(split[1]);
Expand Down
3 changes: 1 addition & 2 deletions osu.Game/Beatmaps/Formats/LegacyStoryboardEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,9 @@ private void encodeEvents(TextWriter writer, StoryboardElementSource target)
if (target == StoryboardElementSource.Beatmap)
{
// https://github.com/peppy/osu-stable-reference/blob/c34a74fb61c17c5667486a12548485d1f03baa2e/osu!/GameplayElements/HitObjectManager_LoadSave.cs#L1499
// TODO: handle nonzero background offset (https://github.com/ppy/osu/issues/14238)
writer.WriteLine(string.Format(CultureInfo.InvariantCulture,
@"{0},{1},""{2}"",{3},{4}",
(int)LegacyEventType.Background, 0, storyboard.BeatmapInfo.Metadata.BackgroundFile, 0, 0));
(int)LegacyEventType.Background, 0, storyboard.BeatmapInfo.Metadata.BackgroundFile, storyboard.BackgroundOffset.X, storyboard.BackgroundOffset.Y));
}

// https://github.com/peppy/osu-stable-reference/blob/c34a74fb61c17c5667486a12548485d1f03baa2e/osu!/GameplayElements/HitObjectManager_LoadSave.cs#L1496
Expand Down
46 changes: 43 additions & 3 deletions osu.Game/Beatmaps/FramedBeatmapClock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Diagnostics;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Timing;
Expand Down Expand Up @@ -49,6 +50,11 @@ public partial class FramedBeatmapClock : Component, IFrameBasedClock, IAdjustab
[Resolved]
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;

[Resolved]
private AudioManager audioManager { get; set; } = null!;

private Bindable<bool> experimentalAudio = null!;

public bool IsRewinding { get; private set; }

public FramedBeatmapClock(bool applyOffsets, bool requireDecoupling, IClock? source = null)
Expand All @@ -66,9 +72,7 @@ public FramedBeatmapClock(bool applyOffsets, bool requireDecoupling, IClock? sou

if (applyOffsets)
{
// Audio timings in general with newer BASS versions don't match stable.
// This only seems to be required on windows. We need to eventually figure out why, with a bit of luck.
platformOffsetClock = new OffsetCorrectionClock(interpolatedTrack) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 15 : 0 };
platformOffsetClock = new OffsetCorrectionClock(interpolatedTrack);

// User global offset (set in settings) should also be applied.
userGlobalOffsetClock = new OffsetCorrectionClock(platformOffsetClock);
Expand All @@ -94,6 +98,9 @@ protected override void LoadComplete()
userAudioOffset = config.GetBindable<double>(OsuSetting.AudioOffset);
userAudioOffset.BindValueChanged(offset => userGlobalOffsetClock.Offset = offset.NewValue, true);

experimentalAudio = audioManager.UseExperimentalWasapi.GetBoundCopy();
experimentalAudio.BindValueChanged(_ => updatePlatformOffset());

// TODO: this doesn't update when using ChangeSource() to change beatmap.
beatmapOffsetSubscription = realm.SubscribeToPropertyChanged(
r => r.Find<BeatmapInfo>(beatmap.Value.BeatmapInfo.ID)?.UserSettings,
Expand All @@ -105,6 +112,39 @@ protected override void LoadComplete()
}
}

/// <summary>
/// Audio timings in general with newer BASS versions don't match stable.
/// This only seems to be required on windows. We need to eventually figure out why, with a bit of luck.
/// </summary>
public const double WINDOWS_BASE_AUDIO_OFFSET = 15;

/// <summary>
/// An additional offset applied to account for experimental mode being much better.
/// </summary>
public const double WINDOWS_EXPERIMENTAL_AUDIO_OFFSET = -25;

private void updatePlatformOffset()
{
if (!applyOffsets)
return;

Debug.Assert(platformOffsetClock != null);

switch (RuntimeInfo.OS)
{
case RuntimeInfo.Platform.Windows:
platformOffsetClock.Offset = WINDOWS_BASE_AUDIO_OFFSET;

if (audioManager.UseExperimentalWasapi.Value)
platformOffsetClock.Offset += WINDOWS_EXPERIMENTAL_AUDIO_OFFSET;
return;

default:
platformOffsetClock.Offset = 0;
break;
}
}

protected override void Update()
{
base.Update();
Expand Down
69 changes: 69 additions & 0 deletions osu.Game/Configuration/MigrateNewAudioDialog.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Localisation;
using osu.Game.Overlays;
using osu.Game.Overlays.Dialog;
using osu.Game.Overlays.Settings.Sections.Audio;

namespace osu.Game.Configuration
{
public partial class MigrateNewAudioDialog : PopupDialog
{
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);

public MigrateNewAudioDialog(bool wasAlreadyUsing)
{
Icon = FontAwesome.Regular.Bell;

if (wasAlreadyUsing)
{
HeaderText = @"New audio engine is now default!";
BodyText =
$"""
We recently added a new "Experimental Audio" backend for Windows users to reduce hitsound latency. Due to overwhelmingly positive feedback, this is now the default mode.

As you were already using this engine, your audio offset has been adjusted to account for an internal offset change (no intervention required).

If you have any issues, you can switch back to the legacy engine from settings via the "{AudioSettingsStrings.LegacyAudioLabel}" checkbox.
""";
}
else
{
HeaderText = @"New audio engine has been enabled";
BodyText =
$"""
We recently added a new "Experimental Audio" backend for Windows users to reduce hitsound latency. Due to overwhelmingly positive feedback, this is now the default mode.

If you have any issues, you can switch back to the legacy engine below, or at any time in settings via the "{AudioSettingsStrings.LegacyAudioLabel}" checkbox.
""";

MainContent.Add(new Container
{
Margin = new MarginPadding { Top = 20 },
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Width = 400,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
new LegacyAudioCheckbox(),
}
});
}

Buttons = new PopupDialogButton[]
{
new PopupDialogOkButton
{
Text = BeatmapOverlayStrings.UserContentConfirmButtonText,
},
};
}
}
}
52 changes: 44 additions & 8 deletions osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
using osu.Framework.Platform;
using osu.Game.Database;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osu.Game.Resources.Localisation.Web;
using System.Numerics;

namespace osu.Game.Graphics.UserInterfaceV2
Expand Down Expand Up @@ -66,6 +68,12 @@ public Bindable<FileInfo?> Current
/// </summary>
public LocalisableString PlaceholderText { get; init; }

/// <summary>
/// If set to <see langword="true"/>, the selector will display a button,
/// which when clicked, will change <see cref="Current"/>'s value to <see langword="null"/>.
/// </summary>
public bool AllowClear { get; init; }

public Container PreviewContainer { get; private set; } = null!;

private FormControlBackground background = null!;
Expand Down Expand Up @@ -179,7 +187,7 @@ protected override void LoadComplete()

private void onFileSelected()
{
if (Current.Value != null)
if (Current.Value != null || AllowClear)
this.HidePopover();

initialChooserPath = Current.Value?.DirectoryName;
Expand Down Expand Up @@ -237,12 +245,12 @@ Task ICanAcceptFiles.Import(params string[] paths)

Task ICanAcceptFiles.Import(ImportTask[] tasks, ImportParameters parameters) => throw new NotImplementedException();

protected virtual FileChooserPopover CreatePopover(string[] handledExtensions, Bindable<FileInfo?> current, string? chooserPath) =>
new FileChooserPopover(handledExtensions, current, chooserPath);
protected virtual FileChooserPopover CreatePopover(string[] handledExtensions, Bindable<FileInfo?> current, string? chooserPath, bool allowClear) =>
new FileChooserPopover(handledExtensions, current, chooserPath, allowClear);

public Popover GetPopover()
{
var popover = CreatePopover(handledExtensions, Current, initialChooserPath);
var popover = CreatePopover(handledExtensions, Current, initialChooserPath, AllowClear);
popoverState.UnbindBindings();
popoverState.BindTo(popover.State);
return popover;
Expand All @@ -257,7 +265,7 @@ public partial class FileChooserPopover : OsuPopover

protected OsuFileSelector FileSelector;

public FileChooserPopover(string[] handledExtensions, Bindable<FileInfo?> current, string? chooserPath)
public FileChooserPopover(string[] handledExtensions, Bindable<FileInfo?> current, string? chooserPath, bool allowClear)
: base(false)
{
Child = new Container
Expand All @@ -266,9 +274,37 @@ public FileChooserPopover(string[] handledExtensions, Bindable<FileInfo?> curren
// simplest solution to avoid underlying text to bleed through the bottom border
// https://github.com/ppy/osu/pull/30005#issuecomment-2378884430
Padding = new MarginPadding { Bottom = 1 },
Child = FileSelector = new OsuFileSelector(chooserPath, handledExtensions)
Children = new[]
{
RelativeSizeAxes = Axes.Both,
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Bottom = allowClear ? 50 : 0 },
Child = FileSelector = new OsuFileSelector(chooserPath, handledExtensions)
{
RelativeSizeAxes = Axes.Both,
},
},
allowClear
? new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Padding = new MarginPadding(5),
Child = new DangerousRoundedButton
{
Text = CommonStrings.ButtonsClear,
Action = () => OnFileSelected(null),
Enabled = { Value = current.Value != null },
Padding = new MarginPadding(5),
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Width = 60,
}
}
: Empty()
},
};

Expand Down Expand Up @@ -307,7 +343,7 @@ protected override void LoadComplete()
};
}

protected virtual void OnFileSelected(FileInfo file) => current.Value = file;
protected virtual void OnFileSelected(FileInfo? file) => current.Value = file;
}
}
}
Loading
Loading