1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-15 22:27:46 +08:00

Merge pull request #7221 from LeNitrous/nightcore-beats

Implement backing beats for Nightcore mods
This commit is contained in:
Dean Herbert 2019-12-18 18:28:16 +09:00 committed by GitHub
commit 06287f72f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 176 additions and 9 deletions

View File

@ -1,11 +1,12 @@
// 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.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Mods
{
public class CatchModNightcore : ModNightcore
public class CatchModNightcore : ModNightcore<CatchHitObject>
{
public override double ScoreMultiplier => 1.06;
}

View File

@ -1,11 +1,12 @@
// 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.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModNightcore : ModNightcore
public class ManiaModNightcore : ModNightcore<ManiaHitObject>
{
public override double ScoreMultiplier => 1;
}

View File

@ -2,10 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModNightcore : ModNightcore
public class OsuModNightcore : ModNightcore<OsuHitObject>
{
public override double ScoreMultiplier => 1.12;
}

View File

@ -2,10 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Mods
{
public class TaikoModNightcore : ModNightcore
public class TaikoModNightcore : ModNightcore<TaikoHitObject>
{
public override double ScoreMultiplier => 1.12;
}

View File

@ -0,0 +1,38 @@
// 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;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu;
using osu.Game.Tests.Visual.UserInterface;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneNightcoreBeatContainer : TestSceneBeatSyncedContainer
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(ModNightcore<>)
};
protected override void LoadComplete()
{
base.LoadComplete();
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
Beatmap.Value.Track.Start();
Beatmap.Value.Track.Seek(Beatmap.Value.Beatmap.HitObjects.First().StartTime - 1000);
Add(new ModNightcore<HitObject>.NightcoreBeatContainer());
AddStep("change signature to quadruple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignatures.SimpleQuadruple));
AddStep("change signature to triple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignatures.SimpleTriple));
}
}
}

View File

@ -33,6 +33,11 @@ namespace osu.Game.Graphics.Containers
/// </summary>
public double TimeSinceLastBeat { get; private set; }
/// <summary>
/// How many beats per beatlength to trigger. Defaults to 1.
/// </summary>
public int Divisor { get; set; } = 1;
/// <summary>
/// Default length of a beat in milliseconds. Used whenever there is no beatmap or track playing.
/// </summary>
@ -42,6 +47,8 @@ namespace osu.Game.Graphics.Containers
private EffectControlPoint defaultEffect;
private TrackAmplitudes defaultAmplitudes;
protected bool IsBeatSyncedWithTrack { get; private set; }
protected override void Update()
{
Track track = null;
@ -65,26 +72,34 @@ namespace osu.Game.Graphics.Containers
effectPoint = beatmap.ControlPointInfo.EffectPointAt(currentTrackTime);
if (timingPoint.BeatLength == 0)
{
IsBeatSyncedWithTrack = false;
return;
}
IsBeatSyncedWithTrack = true;
}
else
{
IsBeatSyncedWithTrack = false;
currentTrackTime = Clock.CurrentTime;
timingPoint = defaultTiming;
effectPoint = defaultEffect;
}
int beatIndex = (int)((currentTrackTime - timingPoint.Time) / timingPoint.BeatLength);
double beatLength = timingPoint.BeatLength / Divisor;
int beatIndex = (int)((currentTrackTime - timingPoint.Time) / beatLength) - (effectPoint.OmitFirstBarLine ? 1 : 0);
// The beats before the start of the first control point are off by 1, this should do the trick
if (currentTrackTime < timingPoint.Time)
beatIndex--;
TimeUntilNextBeat = (timingPoint.Time - currentTrackTime) % timingPoint.BeatLength;
TimeUntilNextBeat = (timingPoint.Time - currentTrackTime) % beatLength;
if (TimeUntilNextBeat < 0)
TimeUntilNextBeat += timingPoint.BeatLength;
TimeUntilNextBeat += beatLength;
TimeSinceLastBeat = timingPoint.BeatLength - TimeUntilNextBeat;
TimeSinceLastBeat = beatLength - TimeUntilNextBeat;
if (timingPoint.Equals(lastTimingPoint) && beatIndex == lastBeat)
return;

View File

@ -1,15 +1,25 @@
// 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.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Timing;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mods
{
public abstract class ModNightcore : ModDoubleTime
public abstract class ModNightcore<TObject> : ModDoubleTime, IApplicableToDrawableRuleset<TObject>
where TObject : HitObject
{
public override string Name => "Nightcore";
public override string Acronym => "NC";
@ -34,5 +44,105 @@ namespace osu.Game.Rulesets.Mods
track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust);
track.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust);
}
public void ApplyToDrawableRuleset(DrawableRuleset<TObject> drawableRuleset)
{
drawableRuleset.Overlays.Add(new NightcoreBeatContainer());
}
public class NightcoreBeatContainer : BeatSyncedContainer
{
private SkinnableSound hatSample;
private SkinnableSound clapSample;
private SkinnableSound kickSample;
private SkinnableSound finishSample;
private int? firstBeat;
public NightcoreBeatContainer()
{
Divisor = 2;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChildren = new Drawable[]
{
hatSample = new SkinnableSound(new SampleInfo("nightcore-hat")),
clapSample = new SkinnableSound(new SampleInfo("nightcore-clap")),
kickSample = new SkinnableSound(new SampleInfo("nightcore-kick")),
finishSample = new SkinnableSound(new SampleInfo("nightcore-finish")),
};
}
private const int bars_per_segment = 4;
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)
{
base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes);
int beatsPerBar = (int)timingPoint.TimeSignature;
int segmentLength = beatsPerBar * Divisor * bars_per_segment;
if (!IsBeatSyncedWithTrack)
{
firstBeat = null;
return;
}
if (!firstBeat.HasValue || beatIndex < firstBeat)
// decide on a good starting beat index if once has not yet been decided.
firstBeat = beatIndex < 0 ? 0 : (beatIndex / segmentLength + 1) * segmentLength;
if (beatIndex >= firstBeat)
playBeatFor(beatIndex % segmentLength, timingPoint.TimeSignature);
}
private void playBeatFor(int beatIndex, TimeSignatures signature)
{
if (beatIndex == 0)
finishSample?.Play();
switch (signature)
{
case TimeSignatures.SimpleTriple:
switch (beatIndex % 6)
{
case 0:
kickSample?.Play();
break;
case 3:
clapSample?.Play();
break;
default:
hatSample?.Play();
break;
}
break;
case TimeSignatures.SimpleQuadruple:
switch (beatIndex % 4)
{
case 0:
kickSample?.Play();
break;
case 2:
clapSample?.Play();
break;
default:
hatSample?.Play();
break;
}
break;
}
}
}
}
}