mirror of
https://github.com/ppy/osu.git
synced 2025-01-15 12:42:54 +08:00
Merge pull request #9301 from H2n9/storyboard-sample-rate
Implement rate adjustment for storyboard samples
This commit is contained in:
commit
f7301571bb
@ -11,8 +11,10 @@ using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Storyboards;
|
||||
@ -70,6 +72,50 @@ namespace osu.Game.Tests.Gameplay
|
||||
AddUntilStep("sample playback succeeded", () => sample.LifetimeEnd < double.MaxValue);
|
||||
}
|
||||
|
||||
[TestCase(typeof(OsuModDoubleTime), 1.5)]
|
||||
[TestCase(typeof(OsuModHalfTime), 0.75)]
|
||||
[TestCase(typeof(ModWindUp), 1.5)]
|
||||
[TestCase(typeof(ModWindDown), 0.75)]
|
||||
[TestCase(typeof(OsuModDoubleTime), 2)]
|
||||
[TestCase(typeof(OsuModHalfTime), 0.5)]
|
||||
[TestCase(typeof(ModWindUp), 2)]
|
||||
[TestCase(typeof(ModWindDown), 0.5)]
|
||||
public void TestSamplePlaybackWithRateMods(Type expectedMod, double expectedRate)
|
||||
{
|
||||
GameplayClockContainer gameplayContainer = null;
|
||||
TestDrawableStoryboardSample sample = null;
|
||||
|
||||
Mod testedMod = Activator.CreateInstance(expectedMod) as Mod;
|
||||
|
||||
switch (testedMod)
|
||||
{
|
||||
case ModRateAdjust m:
|
||||
m.SpeedChange.Value = expectedRate;
|
||||
break;
|
||||
|
||||
case ModTimeRamp m:
|
||||
m.InitialRate.Value = m.FinalRate.Value = expectedRate;
|
||||
break;
|
||||
}
|
||||
|
||||
AddStep("setup storyboard sample", () =>
|
||||
{
|
||||
Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, Audio);
|
||||
SelectedMods.Value = new[] { testedMod };
|
||||
|
||||
Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, SelectedMods.Value, 0));
|
||||
|
||||
gameplayContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1))
|
||||
{
|
||||
Clock = gameplayContainer.GameplayClock
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("start", () => gameplayContainer.Start());
|
||||
|
||||
AddAssert("sample playback rate matches mod rates", () => sample.Channel.AggregateFrequency.Value == expectedRate);
|
||||
}
|
||||
|
||||
private class TestSkin : LegacySkin
|
||||
{
|
||||
public TestSkin(string resourceName, AudioManager audioManager)
|
||||
@ -99,5 +145,28 @@ namespace osu.Game.Tests.Gameplay
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class TestCustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap
|
||||
{
|
||||
private readonly AudioManager audio;
|
||||
|
||||
public TestCustomSkinWorkingBeatmap(RulesetInfo ruleset, AudioManager audio)
|
||||
: base(ruleset, null, audio)
|
||||
{
|
||||
this.audio = audio;
|
||||
}
|
||||
|
||||
protected override ISkin GetSkin() => new TestSkin("test-sample", audio);
|
||||
}
|
||||
|
||||
private class TestDrawableStoryboardSample : DrawableStoryboardSample
|
||||
{
|
||||
public TestDrawableStoryboardSample(StoryboardSampleInfo sampleInfo)
|
||||
: base(sampleInfo)
|
||||
{
|
||||
}
|
||||
|
||||
public new SampleChannel Channel => base.Channel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
9
osu.Game/Rulesets/Mods/IApplicableToAudio.cs
Normal file
9
osu.Game/Rulesets/Mods/IApplicableToAudio.cs
Normal file
@ -0,0 +1,9 @@
|
||||
// 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.
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
public interface IApplicableToAudio : IApplicableToTrack, IApplicableToSample
|
||||
{
|
||||
}
|
||||
}
|
15
osu.Game/Rulesets/Mods/IApplicableToSample.cs
Normal file
15
osu.Game/Rulesets/Mods/IApplicableToSample.cs
Normal file
@ -0,0 +1,15 @@
|
||||
// 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;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface for mods that make adjustments to a sample.
|
||||
/// </summary>
|
||||
public interface IApplicableToSample : IApplicableMod
|
||||
{
|
||||
void ApplyToSample(SampleChannel sample);
|
||||
}
|
||||
}
|
@ -2,12 +2,13 @@
|
||||
// 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;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
public abstract class ModRateAdjust : Mod, IApplicableToTrack
|
||||
public abstract class ModRateAdjust : Mod, IApplicableToAudio
|
||||
{
|
||||
public abstract BindableNumber<double> SpeedChange { get; }
|
||||
|
||||
@ -16,6 +17,11 @@ namespace osu.Game.Rulesets.Mods
|
||||
track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange);
|
||||
}
|
||||
|
||||
public virtual void ApplyToSample(SampleChannel sample)
|
||||
{
|
||||
sample.AddAdjustment(AdjustableProperty.Frequency, SpeedChange);
|
||||
}
|
||||
|
||||
public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value:N2}x";
|
||||
}
|
||||
}
|
||||
|
@ -10,10 +10,11 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Framework.Audio.Sample;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToBeatmap, IApplicableToTrack
|
||||
public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToBeatmap, IApplicableToAudio
|
||||
{
|
||||
/// <summary>
|
||||
/// The point in the beatmap at which the final ramping rate should be reached.
|
||||
@ -58,6 +59,11 @@ namespace osu.Game.Rulesets.Mods
|
||||
AdjustPitch.TriggerChange();
|
||||
}
|
||||
|
||||
public void ApplyToSample(SampleChannel sample)
|
||||
{
|
||||
sample.AddAdjustment(AdjustableProperty.Frequency, SpeedChange);
|
||||
}
|
||||
|
||||
public virtual void ApplyToBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
HitObject lastObject = beatmap.HitObjects.LastOrDefault();
|
||||
|
@ -1,11 +1,14 @@
|
||||
// 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 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;
|
||||
|
||||
namespace osu.Game.Storyboards.Drawables
|
||||
{
|
||||
@ -17,7 +20,8 @@ namespace osu.Game.Storyboards.Drawables
|
||||
private const double allowable_late_start = 100;
|
||||
|
||||
private readonly StoryboardSampleInfo sampleInfo;
|
||||
private SampleChannel channel;
|
||||
|
||||
protected SampleChannel Channel { get; private set; }
|
||||
|
||||
public override bool RemoveWhenNotAlive => false;
|
||||
|
||||
@ -28,12 +32,16 @@ namespace osu.Game.Storyboards.Drawables
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IBindable<WorkingBeatmap> beatmap)
|
||||
private void load(IBindable<WorkingBeatmap> beatmap, IBindable<IReadOnlyList<Mod>> mods)
|
||||
{
|
||||
channel = beatmap.Value.Skin.GetSample(sampleInfo);
|
||||
Channel = beatmap.Value.Skin.GetSample(sampleInfo);
|
||||
if (Channel == null)
|
||||
return;
|
||||
|
||||
if (channel != null)
|
||||
channel.Volume.Value = sampleInfo.Volume / 100.0;
|
||||
Channel.Volume.Value = sampleInfo.Volume / 100.0;
|
||||
|
||||
foreach (var mod in mods.Value.OfType<IApplicableToSample>())
|
||||
mod.ApplyToSample(Channel);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
@ -44,7 +52,7 @@ namespace osu.Game.Storyboards.Drawables
|
||||
if (Time.Current < sampleInfo.StartTime)
|
||||
{
|
||||
// We've rewound before the start time of the sample
|
||||
channel?.Stop();
|
||||
Channel?.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)
|
||||
@ -56,7 +64,7 @@ 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();
|
||||
Channel?.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)
|
||||
@ -67,7 +75,7 @@ namespace osu.Game.Storyboards.Drawables
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
channel?.Stop();
|
||||
Channel?.Stop();
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user