1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-23 02:47:26 +08:00

Merge pull request #10285 from peppy/fix-storyboard-sample-pausing

Fix storyboard samples not pausing
This commit is contained in:
Dan Balasescu 2020-09-30 23:30:18 +09:00 committed by GitHub
commit 5d76ce8350
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 90 additions and 94 deletions

View File

@ -4,10 +4,12 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics.Audio;
using osu.Framework.IO.Stores;
using osu.Framework.Testing;
using osu.Game.Audio;
@ -106,9 +108,14 @@ namespace osu.Game.Tests.Gameplay
Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, Audio);
SelectedMods.Value = new[] { testedMod };
Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, 0));
var beatmapSkinSourceContainer = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin);
gameplayContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1))
Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, 0)
{
Child = beatmapSkinSourceContainer
});
beatmapSkinSourceContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1))
{
Clock = gameplayContainer.GameplayClock
});
@ -116,7 +123,7 @@ namespace osu.Game.Tests.Gameplay
AddStep("start", () => gameplayContainer.Start());
AddAssert("sample playback rate matches mod rates", () => sample.Channel.AggregateFrequency.Value == expectedRate);
AddAssert("sample playback rate matches mod rates", () => sample.ChildrenOfType<DrawableSample>().First().AggregateFrequency.Value == expectedRate);
}
private class TestSkin : LegacySkin
@ -168,8 +175,6 @@ namespace osu.Game.Tests.Gameplay
: base(sampleInfo)
{
}
public new SampleChannel Channel => base.Channel;
}
}
}

View File

@ -158,7 +158,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestQuickRetryFromFailedGameplay()
{
AddUntilStep("wait for fail", () => Player.HasFailed);
AddStep("quick retry", () => Player.GameplayClockContainer.OfType<HotkeyRetryOverlay>().First().Action?.Invoke());
AddStep("quick retry", () => Player.GameplayClockContainer.ChildrenOfType<HotkeyRetryOverlay>().First().Action?.Invoke());
confirmExited();
}
@ -167,7 +167,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestQuickExitFromFailedGameplay()
{
AddUntilStep("wait for fail", () => Player.HasFailed);
AddStep("quick exit", () => Player.GameplayClockContainer.OfType<HotkeyExitOverlay>().First().Action?.Invoke());
AddStep("quick exit", () => Player.GameplayClockContainer.ChildrenOfType<HotkeyExitOverlay>().First().Action?.Invoke());
confirmExited();
}
@ -183,7 +183,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestQuickExitFromGameplay()
{
AddStep("quick exit", () => Player.GameplayClockContainer.OfType<HotkeyExitOverlay>().First().Action?.Invoke());
AddStep("quick exit", () => Player.GameplayClockContainer.ChildrenOfType<HotkeyExitOverlay>().First().Action?.Invoke());
confirmExited();
}

View File

@ -1,7 +1,7 @@
// 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.Audio.Sample;
using osu.Framework.Graphics.Audio;
namespace osu.Game.Rulesets.Mods
{
@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Mods
/// </summary>
public interface IApplicableToSample : IApplicableMod
{
void ApplyToSample(SampleChannel sample);
void ApplyToSample(DrawableSample sample);
}
}

View File

@ -2,9 +2,9 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Audio;
namespace osu.Game.Rulesets.Mods
{
@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mods
track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange);
}
public virtual void ApplyToSample(SampleChannel sample)
public virtual void ApplyToSample(DrawableSample sample)
{
sample.AddAdjustment(AdjustableProperty.Frequency, SpeedChange);
}

View File

@ -6,11 +6,11 @@ using System.Linq;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Audio;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.Objects;
using osu.Framework.Audio.Sample;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mods
{
@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Mods
AdjustPitch.TriggerChange();
}
public void ApplyToSample(SampleChannel sample)
public void ApplyToSample(DrawableSample sample)
{
sample.AddAdjustment(AdjustableProperty.Frequency, SpeedChange);
}

View File

@ -191,9 +191,25 @@ namespace osu.Game.Screens.Play
dependencies.CacheAs(gameplayBeatmap);
addUnderlayComponents(GameplayClockContainer);
addGameplayComponents(GameplayClockContainer, Beatmap.Value, playableBeatmap);
addOverlayComponents(GameplayClockContainer, Beatmap.Value);
var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin);
// the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation
// full access to all skin sources.
var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap));
// load the skinning hierarchy first.
// this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources.
GameplayClockContainer.Add(beatmapSkinProvider.WithChild(rulesetSkinProvider));
rulesetSkinProvider.AddRange(new[]
{
// underlay and gameplay should have access the to skinning sources.
createUnderlayComponents(),
createGameplayComponents(Beatmap.Value, playableBeatmap)
});
// add the overlay components as a separate step as they proxy some elements from the above underlay/gameplay components.
GameplayClockContainer.Add(createOverlayComponents(Beatmap.Value));
if (!DrawableRuleset.AllowGameplayOverlays)
{
@ -238,45 +254,31 @@ namespace osu.Game.Screens.Play
breakTracker.IsBreakTime.BindValueChanged(onBreakTimeChanged, true);
}
private void addUnderlayComponents(Container target)
private Drawable createUnderlayComponents() =>
DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both };
private Drawable createGameplayComponents(WorkingBeatmap working, IBeatmap playableBeatmap) => new ScalingContainer(ScalingMode.Gameplay)
{
target.Add(DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both });
}
private void addGameplayComponents(Container target, WorkingBeatmap working, IBeatmap playableBeatmap)
{
var beatmapSkinProvider = new BeatmapSkinProvidingContainer(working.Skin);
// the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation
// full access to all skin sources.
var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap));
// load the skinning hierarchy first.
// this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources.
target.Add(new ScalingContainer(ScalingMode.Gameplay)
.WithChild(beatmapSkinProvider
.WithChild(target = rulesetSkinProvider)));
target.AddRange(new Drawable[]
Children = new Drawable[]
{
DrawableRuleset,
DrawableRuleset.With(r =>
r.FrameStableComponents.Children = new Drawable[]
{
ScoreProcessor,
HealthProcessor,
breakTracker = new BreakTracker(DrawableRuleset.GameplayStartTime, ScoreProcessor)
{
Breaks = working.Beatmap.Breaks
}
}),
new ComboEffects(ScoreProcessor)
});
}
};
DrawableRuleset.FrameStableComponents.AddRange(new Drawable[]
{
ScoreProcessor,
HealthProcessor,
breakTracker = new BreakTracker(DrawableRuleset.GameplayStartTime, ScoreProcessor)
{
Breaks = working.Beatmap.Breaks
}
});
}
private void addOverlayComponents(Container target, WorkingBeatmap working)
private Drawable createOverlayComponents(WorkingBeatmap working) => new Container
{
target.AddRange(new[]
RelativeSizeAxes = Axes.Both,
Children = new[]
{
DimmableStoryboard.OverlayLayerContainer.CreateProxy(),
BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor)
@ -342,8 +344,8 @@ namespace osu.Game.Screens.Play
},
},
failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, },
});
}
}
};
private void onBreakTimeChanged(ValueChangedEvent<bool> isBreakTime)
{

View File

@ -34,7 +34,7 @@ namespace osu.Game.Skinning
/// </remarks>
protected bool PlayWhenZeroVolume => Looping;
private readonly AudioContainer<DrawableSample> samplesContainer;
protected readonly AudioContainer<DrawableSample> SamplesContainer;
public SkinnableSound(ISampleInfo hitSamples)
: this(new[] { hitSamples })
@ -44,7 +44,7 @@ namespace osu.Game.Skinning
public SkinnableSound(IEnumerable<ISampleInfo> hitSamples)
{
this.hitSamples = hitSamples.ToArray();
InternalChild = samplesContainer = new AudioContainer<DrawableSample>();
InternalChild = SamplesContainer = new AudioContainer<DrawableSample>();
}
private bool looping;
@ -58,13 +58,13 @@ namespace osu.Game.Skinning
looping = value;
samplesContainer.ForEach(c => c.Looping = looping);
SamplesContainer.ForEach(c => c.Looping = looping);
}
}
public virtual void Play()
{
samplesContainer.ForEach(c =>
SamplesContainer.ForEach(c =>
{
if (PlayWhenZeroVolume || c.AggregateVolume.Value > 0)
c.Play();
@ -73,7 +73,7 @@ namespace osu.Game.Skinning
public virtual void Stop()
{
samplesContainer.ForEach(c => c.Stop());
SamplesContainer.ForEach(c => c.Stop());
}
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
@ -102,7 +102,7 @@ namespace osu.Game.Skinning
return ch;
}).Where(c => c != null);
samplesContainer.ChildrenEnumerable = channels.Select(c => new DrawableSample(c));
SamplesContainer.ChildrenEnumerable = channels.Select(c => new DrawableSample(c));
// Start playback internally for the new samples if the previous ones were playing beforehand.
if (wasPlaying)
@ -111,24 +111,24 @@ namespace osu.Game.Skinning
#region Re-expose AudioContainer
public BindableNumber<double> Volume => samplesContainer.Volume;
public BindableNumber<double> Volume => SamplesContainer.Volume;
public BindableNumber<double> Balance => samplesContainer.Balance;
public BindableNumber<double> Balance => SamplesContainer.Balance;
public BindableNumber<double> Frequency => samplesContainer.Frequency;
public BindableNumber<double> Frequency => SamplesContainer.Frequency;
public BindableNumber<double> Tempo => samplesContainer.Tempo;
public BindableNumber<double> Tempo => SamplesContainer.Tempo;
public void AddAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable)
=> samplesContainer.AddAdjustment(type, adjustBindable);
=> SamplesContainer.AddAdjustment(type, adjustBindable);
public void RemoveAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable)
=> samplesContainer.RemoveAdjustment(type, adjustBindable);
=> SamplesContainer.RemoveAdjustment(type, adjustBindable);
public void RemoveAllAdjustments(AdjustableProperty type)
=> samplesContainer.RemoveAllAdjustments(type);
=> SamplesContainer.RemoveAllAdjustments(type);
public bool IsPlaying => samplesContainer.Any(s => s.Playing);
public bool IsPlaying => SamplesContainer.Any(s => s.Playing);
#endregion
}

View File

@ -4,15 +4,13 @@
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Skinning;
namespace osu.Game.Storyboards.Drawables
{
public class DrawableStoryboardSample : Component
public class DrawableStoryboardSample : PausableSkinnableSound
{
/// <summary>
/// The amount of time allowable beyond the start time of the sample, for the sample to start.
@ -21,38 +19,37 @@ namespace osu.Game.Storyboards.Drawables
private readonly StoryboardSampleInfo sampleInfo;
protected SampleChannel Channel { get; private set; }
public override bool RemoveWhenNotAlive => false;
public DrawableStoryboardSample(StoryboardSampleInfo sampleInfo)
: base(sampleInfo)
{
this.sampleInfo = sampleInfo;
LifetimeStart = sampleInfo.StartTime;
}
[BackgroundDependencyLoader]
private void load(IBindable<WorkingBeatmap> beatmap, IBindable<IReadOnlyList<Mod>> mods)
{
Channel = beatmap.Value.Skin.GetSample(sampleInfo);
if (Channel == null)
return;
[Resolved]
private IBindable<IReadOnlyList<Mod>> mods { get; set; }
Channel.Volume.Value = sampleInfo.Volume / 100.0;
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
base.SkinChanged(skin, allowFallback);
foreach (var mod in mods.Value.OfType<IApplicableToSample>())
mod.ApplyToSample(Channel);
{
foreach (var sample in SamplesContainer)
mod.ApplyToSample(sample);
}
}
protected override void Update()
{
base.Update();
// TODO: this logic will need to be consolidated with other game samples like hit sounds.
if (Time.Current < sampleInfo.StartTime)
{
// We've rewound before the start time of the sample
Channel?.Stop();
Stop();
// In the case that the user fast-forwards to a point far beyond the start time of the sample,
// we want to be able to fall into the if-conditional below (therefore we must not have a life time end)
@ -63,8 +60,8 @@ namespace osu.Game.Storyboards.Drawables
{
// We've passed the start time of the sample. We only play the sample if we're within an allowable range
// from the sample's start, to reduce layering if we've been fast-forwarded far into the future
if (Time.Current - sampleInfo.StartTime < allowable_late_start)
Channel?.Play();
if (!RequestedPlaying && Time.Current - sampleInfo.StartTime < allowable_late_start)
Play();
// In the case that the user rewinds to a point far behind the start time of the sample,
// we want to be able to fall into the if-conditional above (therefore we must not have a life time start)
@ -72,13 +69,5 @@ namespace osu.Game.Storyboards.Drawables
LifetimeEnd = sampleInfo.StartTime;
}
}
protected override void Dispose(bool isDisposing)
{
Channel?.Stop();
Channel = null;
base.Dispose(isDisposing);
}
}
}