1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-28 09:02:58 +08:00

Merge pull request #16571 from bdach/custom-meter

Add back editor support for non-standard time signatures
This commit is contained in:
Dean Herbert 2022-01-24 16:09:47 +09:00 committed by GitHub
commit 5a12496873
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 265 additions and 34 deletions

View File

@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
if (!effectPoint.KiaiMode)
return;
if (beatIndex % (int)timingPoint.TimeSignature != 0)
if (beatIndex % timingPoint.TimeSignature.Numerator != 0)
return;
double duration = timingPoint.BeatLength * 2;

View File

@ -178,17 +178,17 @@ namespace osu.Game.Tests.Beatmaps.Formats
var timingPoint = controlPoints.TimingPointAt(0);
Assert.AreEqual(956, timingPoint.Time);
Assert.AreEqual(329.67032967033, timingPoint.BeatLength);
Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature);
Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
timingPoint = controlPoints.TimingPointAt(48428);
Assert.AreEqual(956, timingPoint.Time);
Assert.AreEqual(329.67032967033d, timingPoint.BeatLength);
Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature);
Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
timingPoint = controlPoints.TimingPointAt(119637);
Assert.AreEqual(119637, timingPoint.Time);
Assert.AreEqual(659.340659340659, timingPoint.BeatLength);
Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature);
Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
var difficultyPoint = controlPoints.DifficultyPointAt(0);
Assert.AreEqual(0, difficultyPoint.Time);

View File

@ -26,7 +26,7 @@ namespace osu.Game.Tests.NonVisual
const int beat_length_numerator = 2000;
const int beat_length_denominator = 7;
const TimeSignatures signature = TimeSignatures.SimpleQuadruple;
TimeSignature signature = TimeSignature.SimpleQuadruple;
var beatmap = new Beatmap
{
@ -49,7 +49,7 @@ namespace osu.Game.Tests.NonVisual
for (int i = 0; i * beat_length_denominator < barLines.Count; i++)
{
var barLine = barLines[i * beat_length_denominator];
int expectedTime = beat_length_numerator * (int)signature * i;
int expectedTime = beat_length_numerator * signature.Numerator * i;
// every seventh bar's start time should be at least greater than the whole number we expect.
// It cannot be less, as that can affect overlapping scroll algorithms
@ -60,7 +60,7 @@ namespace osu.Game.Tests.NonVisual
Assert.IsTrue(Precision.AlmostEquals(barLine.StartTime, expectedTime));
// check major/minor lines for good measure too
Assert.AreEqual(i % (int)signature == 0, barLine.Major);
Assert.AreEqual(i % signature.Numerator == 0, barLine.Major);
}
}

View File

@ -0,0 +1,88 @@
// 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.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Beatmaps.Timing;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Edit.Timing;
namespace osu.Game.Tests.Visual.Editing
{
public class TestSceneLabelledTimeSignature : OsuManualInputManagerTestScene
{
private LabelledTimeSignature timeSignature;
private void createLabelledTimeSignature(TimeSignature initial) => AddStep("create labelled time signature", () =>
{
Child = timeSignature = new LabelledTimeSignature
{
Label = "Time Signature",
RelativeSizeAxes = Axes.None,
Width = 400,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Current = { Value = initial }
};
});
private OsuTextBox numeratorTextBox => timeSignature.ChildrenOfType<OsuTextBox>().Single();
[Test]
public void TestInitialValue()
{
createLabelledTimeSignature(TimeSignature.SimpleTriple);
AddAssert("current is 3/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleTriple));
}
[Test]
public void TestChangeViaCurrent()
{
createLabelledTimeSignature(TimeSignature.SimpleQuadruple);
AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
AddStep("set current to 5/4", () => timeSignature.Current.Value = new TimeSignature(5));
AddAssert("current is 5/4", () => timeSignature.Current.Value.Equals(new TimeSignature(5)));
AddAssert("numerator is 5", () => numeratorTextBox.Current.Value == "5");
AddStep("set current to 3/4", () => timeSignature.Current.Value = TimeSignature.SimpleTriple);
AddAssert("current is 3/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleTriple));
AddAssert("numerator is 3", () => numeratorTextBox.Current.Value == "3");
}
[Test]
public void TestChangeNumerator()
{
createLabelledTimeSignature(TimeSignature.SimpleQuadruple);
AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
AddStep("focus text box", () => InputManager.ChangeFocus(numeratorTextBox));
AddStep("set numerator to 7", () => numeratorTextBox.Current.Value = "7");
AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
AddStep("drop focus", () => InputManager.ChangeFocus(null));
AddAssert("current is 7/4", () => timeSignature.Current.Value.Equals(new TimeSignature(7)));
}
[Test]
public void TestInvalidChangeRollbackOnCommit()
{
createLabelledTimeSignature(TimeSignature.SimpleQuadruple);
AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
AddStep("focus text box", () => InputManager.ChangeFocus(numeratorTextBox));
AddStep("set numerator to 0", () => numeratorTextBox.Current.Value = "0");
AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
AddStep("drop focus", () => InputManager.ChangeFocus(null));
AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
AddAssert("numerator is 4", () => numeratorTextBox.Current.Value == "4");
}
}
}

View File

@ -24,8 +24,8 @@ namespace osu.Game.Tests.Visual.Gameplay
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));
AddStep("change signature to quadruple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignature.SimpleQuadruple));
AddStep("change signature to triple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignature.SimpleTriple));
}
}
}

View File

@ -13,7 +13,7 @@ namespace osu.Game.Beatmaps.ControlPoints
/// <summary>
/// The time signature at this control point.
/// </summary>
public readonly Bindable<TimeSignatures> TimeSignatureBindable = new Bindable<TimeSignatures>(TimeSignatures.SimpleQuadruple) { Default = TimeSignatures.SimpleQuadruple };
public readonly Bindable<TimeSignature> TimeSignatureBindable = new Bindable<TimeSignature>(TimeSignature.SimpleQuadruple);
/// <summary>
/// Default length of a beat in milliseconds. Used whenever there is no beatmap or track playing.
@ -35,7 +35,7 @@ namespace osu.Game.Beatmaps.ControlPoints
/// <summary>
/// The time signature at this control point.
/// </summary>
public TimeSignatures TimeSignature
public TimeSignature TimeSignature
{
get => TimeSignatureBindable.Value;
set => TimeSignatureBindable.Value = value;

View File

@ -340,9 +340,9 @@ namespace osu.Game.Beatmaps.Formats
double beatLength = Parsing.ParseDouble(split[1].Trim());
double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1;
TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple;
TimeSignature timeSignature = TimeSignature.SimpleQuadruple;
if (split.Length >= 3)
timeSignature = split[2][0] == '0' ? TimeSignatures.SimpleQuadruple : (TimeSignatures)Parsing.ParseInt(split[2]);
timeSignature = split[2][0] == '0' ? TimeSignature.SimpleQuadruple : new TimeSignature(Parsing.ParseInt(split[2]));
LegacySampleBank sampleSet = defaultSampleBank;
if (split.Length >= 4)

View File

@ -227,7 +227,7 @@ namespace osu.Game.Beatmaps.Formats
if (effectPoint.OmitFirstBarLine)
effectFlags |= LegacyEffectFlags.OmitFirstBarLine;
writer.Write(FormattableString.Invariant($"{(int)legacyControlPoints.TimingPointAt(time).TimeSignature},"));
writer.Write(FormattableString.Invariant($"{legacyControlPoints.TimingPointAt(time).TimeSignature.Numerator},"));
writer.Write(FormattableString.Invariant($"{(int)toLegacySampleBank(tempHitSample.Bank)},"));
writer.Write(FormattableString.Invariant($"{toLegacyCustomSampleBank(tempHitSample)},"));
writer.Write(FormattableString.Invariant($"{tempHitSample.Volume},"));

View File

@ -0,0 +1,45 @@
// 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;
namespace osu.Game.Beatmaps.Timing
{
/// <summary>
/// Stores the time signature of a track.
/// For now, the lower numeral can only be 4; support for other denominators can be considered at a later date.
/// </summary>
public class TimeSignature : IEquatable<TimeSignature>
{
/// <summary>
/// The numerator of a signature.
/// </summary>
public int Numerator { get; }
// TODO: support time signatures with a denominator other than 4
// this in particular requires a new beatmap format.
public TimeSignature(int numerator)
{
if (numerator < 1)
throw new ArgumentOutOfRangeException(nameof(numerator), numerator, "The numerator of a time signature must be positive.");
Numerator = numerator;
}
public static TimeSignature SimpleTriple { get; } = new TimeSignature(3);
public static TimeSignature SimpleQuadruple { get; } = new TimeSignature(4);
public override string ToString() => $"{Numerator}/4";
public bool Equals(TimeSignature other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Numerator == other.Numerator;
}
public override int GetHashCode() => Numerator;
}
}

View File

@ -1,11 +1,13 @@
// 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.ComponentModel;
namespace osu.Game.Beatmaps.Timing
{
public enum TimeSignatures
[Obsolete("Use osu.Game.Beatmaps.Timing.TimeSignature instead.")]
public enum TimeSignatures // can be removed 20220722
{
[Description("4/4")]
SimpleQuadruple = 4,

View File

@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Mods
if (!IsBeatSyncedWithTrack) return;
int timeSignature = (int)timingPoint.TimeSignature;
int timeSignature = timingPoint.TimeSignature.Numerator;
// play metronome from one measure before the first object.
if (BeatSyncClock.CurrentTime < firstHitTime - timingPoint.BeatLength * timeSignature)

View File

@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Mods
{
base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes);
int beatsPerBar = (int)timingPoint.TimeSignature;
int beatsPerBar = timingPoint.TimeSignature.Numerator;
int segmentLength = beatsPerBar * Divisor * bars_per_segment;
if (!IsBeatSyncedWithTrack)
@ -102,14 +102,14 @@ namespace osu.Game.Rulesets.Mods
playBeatFor(beatIndex % segmentLength, timingPoint.TimeSignature);
}
private void playBeatFor(int beatIndex, TimeSignatures signature)
private void playBeatFor(int beatIndex, TimeSignature signature)
{
if (beatIndex == 0)
finishSample?.Play();
switch (signature)
switch (signature.Numerator)
{
case TimeSignatures.SimpleTriple:
case 3:
switch (beatIndex % 6)
{
case 0:
@ -127,7 +127,7 @@ namespace osu.Game.Rulesets.Mods
break;
case TimeSignatures.SimpleQuadruple:
case 4:
switch (beatIndex % 4)
{
case 0:

View File

@ -41,9 +41,9 @@ namespace osu.Game.Rulesets.Objects
int currentBeat = 0;
// Stop on the beat before the next timing point, or if there is no next timing point stop slightly past the last object
double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time - currentTimingPoint.BeatLength : lastHitTime + currentTimingPoint.BeatLength * (int)currentTimingPoint.TimeSignature;
double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time - currentTimingPoint.BeatLength : lastHitTime + currentTimingPoint.BeatLength * currentTimingPoint.TimeSignature.Numerator;
double barLength = currentTimingPoint.BeatLength * (int)currentTimingPoint.TimeSignature;
double barLength = currentTimingPoint.BeatLength * currentTimingPoint.TimeSignature.Numerator;
for (double t = currentTimingPoint.Time; Precision.DefinitelyBigger(endTime, t); t += barLength, currentBeat++)
{
@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Objects
BarLines.Add(new TBarLine
{
StartTime = t,
Major = currentBeat % (int)currentTimingPoint.TimeSignature == 0
Major = currentBeat % currentTimingPoint.TimeSignature.Numerator == 0
});
}
}

View File

@ -125,7 +125,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
if (beat == 0 && i == 0)
nextMinTick = float.MinValue;
int indexInBar = beat % ((int)point.TimeSignature * beatDivisor.Value);
int indexInBar = beat % (point.TimeSignature.Numerator * beatDivisor.Value);
int divisor = BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value);
var colour = BindableBeatDivisor.GetColourFor(divisor, colours);

View File

@ -0,0 +1,97 @@
// 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.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Beatmaps.Timing;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
namespace osu.Game.Screens.Edit.Timing
{
public class LabelledTimeSignature : LabelledComponent<LabelledTimeSignature.TimeSignatureBox, TimeSignature>
{
public LabelledTimeSignature()
: base(false)
{
}
protected override TimeSignatureBox CreateComponent() => new TimeSignatureBox();
public class TimeSignatureBox : CompositeDrawable, IHasCurrentValue<TimeSignature>
{
private readonly BindableWithCurrent<TimeSignature> current = new BindableWithCurrent<TimeSignature>(TimeSignature.SimpleQuadruple);
public Bindable<TimeSignature> Current
{
get => current.Current;
set => current.Current = value;
}
private OsuNumberBox numeratorBox;
[BackgroundDependencyLoader]
private void load()
{
AutoSizeAxes = Axes.Both;
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
numeratorBox = new OsuNumberBox
{
Width = 40,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
CornerRadius = CORNER_RADIUS,
CommitOnFocusLost = true
},
new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Margin = new MarginPadding
{
Left = 5,
Right = CONTENT_PADDING_HORIZONTAL
},
Text = "/ 4",
Font = OsuFont.Default.With(size: 20)
}
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
Current.BindValueChanged(_ => updateFromCurrent(), true);
numeratorBox.OnCommit += (_, __) => updateFromNumeratorBox();
}
private void updateFromCurrent()
{
numeratorBox.Current.Value = Current.Value.Numerator.ToString();
}
private void updateFromNumeratorBox()
{
if (int.TryParse(numeratorBox.Current.Value, out int numerator) && numerator > 0)
Current.Value = new TimeSignature(numerator);
else
{
// trigger `Current` change to restore the numerator box's text to a valid value.
Current.TriggerChange();
}
}
}
}
}

View File

@ -13,7 +13,7 @@ namespace osu.Game.Screens.Edit.Timing.RowAttributes
public class TimingRowAttribute : RowAttribute
{
private readonly BindableNumber<double> beatLength;
private readonly Bindable<TimeSignatures> timeSignature;
private readonly Bindable<TimeSignature> timeSignature;
private OsuSpriteText text;
public TimingRowAttribute(TimingControlPoint timing)

View File

@ -6,7 +6,6 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Timing;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Overlays.Settings;
@ -15,7 +14,7 @@ namespace osu.Game.Screens.Edit.Timing
internal class TimingSection : Section<TimingControlPoint>
{
private SettingsSlider<double> bpmSlider;
private SettingsEnumDropdown<TimeSignatures> timeSignature;
private LabelledTimeSignature timeSignature;
private BPMTextBox bpmTextEntry;
[BackgroundDependencyLoader]
@ -25,10 +24,10 @@ namespace osu.Game.Screens.Edit.Timing
{
bpmTextEntry = new BPMTextBox(),
bpmSlider = new BPMSlider(),
timeSignature = new SettingsEnumDropdown<TimeSignatures>
timeSignature = new LabelledTimeSignature
{
LabelText = "Time Signature"
},
Label = "Time Signature"
}
});
}

View File

@ -94,9 +94,9 @@ namespace osu.Game.Screens.Menu
if (beatIndex < 0)
return;
if (effectPoint.KiaiMode ? beatIndex % 2 == 0 : beatIndex % (int)timingPoint.TimeSignature == 0)
if (effectPoint.KiaiMode ? beatIndex % 2 == 0 : beatIndex % timingPoint.TimeSignature.Numerator == 0)
flash(leftBox, timingPoint.BeatLength, effectPoint.KiaiMode, amplitudes);
if (effectPoint.KiaiMode ? beatIndex % 2 == 1 : beatIndex % (int)timingPoint.TimeSignature == 0)
if (effectPoint.KiaiMode ? beatIndex % 2 == 1 : beatIndex % timingPoint.TimeSignature.Numerator == 0)
flash(rightBox, timingPoint.BeatLength, effectPoint.KiaiMode, amplitudes);
}

View File

@ -282,7 +282,7 @@ namespace osu.Game.Screens.Menu
{
this.Delay(early_activation).Schedule(() =>
{
if (beatIndex % (int)timingPoint.TimeSignature == 0)
if (beatIndex % timingPoint.TimeSignature.Numerator == 0)
sampleDownbeat.Play();
else
sampleBeat.Play();