diff --git a/Assets/TestsEditMode/BeatmapBpmInfoTest.cs b/Assets/TestsEditMode/BeatmapBpmInfoTest.cs index 0c70909cd..65218d1ee 100644 --- a/Assets/TestsEditMode/BeatmapBpmInfoTest.cs +++ b/Assets/TestsEditMode/BeatmapBpmInfoTest.cs @@ -82,16 +82,16 @@ public void GetFromJson_V2BpmInfo() [Test] public void GetFromJson_V4AudioData() { - var bpmInfo = V4AudioData.GetFromJson(JSON.Parse(audioDataJson)); + var bpmInfo = V4AudioData.GetFromJson(JSON.Parse(audioDataJson)); - AssertCommonGetFromJson(bpmInfo); - - Assert.AreEqual("4.0.0", bpmInfo.Version); + AssertCommonGetFromJson(bpmInfo); + + Assert.AreEqual("4.0.0", bpmInfo.Version); - Assert.AreEqual(1, bpmInfo.LufsRegions.Count); - Assert.AreEqual(0, bpmInfo.LufsRegions[0].StartSampleIndex); - Assert.AreEqual(882000, bpmInfo.LufsRegions[0].EndSampleIndex); - Assert.AreEqual(3.1f, bpmInfo.LufsRegions[0].Loudness); + Assert.AreEqual(1, bpmInfo.LufsRegions.Count); + Assert.AreEqual(0, bpmInfo.LufsRegions[0].StartSampleIndex); + Assert.AreEqual(882000, bpmInfo.LufsRegions[0].EndSampleIndex); + Assert.AreEqual(3.1f, bpmInfo.LufsRegions[0].Loudness); } private void AssertCommonGetFromJson(BaseBpmInfo bpmInfo) @@ -162,13 +162,17 @@ public void GetBpmInfoRegions() var songBpm = 60f; var audioFrequency = 44100; var audiosamples = 44100 * 20; - var bpmEvents = new List + var difficulty = new BaseDifficulty { - new() { JsonTime = 0, Bpm = 60 }, - new() { JsonTime = 10, Bpm = 120 } + BpmEvents = new List + { + new() { JsonTime = 0, Bpm = 60 }, + new() { JsonTime = 10, Bpm = 120 } + } }; + difficulty.BootstrapBpmEvents(songBpm); - var regions = BaseBpmInfo.GetBpmInfoRegions(bpmEvents, songBpm, audiosamples, audioFrequency); + var regions = BaseBpmInfo.GetBpmInfoRegions(difficulty.BpmEvents, songBpm, audiosamples, audioFrequency); Assert.AreEqual(2, regions.Count); Assert.AreEqual(0f, regions[0].StartBeat); @@ -205,27 +209,41 @@ public void ConversionDoesNotIntroduceDriftOverTime() var songBpm = 60f; var audioFrequency = 44100; var audiosamples = 44100 * 20; - - var initialBpmEvents = new List + + var initialDifficulty = new BaseDifficulty { - new() { JsonTime = 0, Bpm = 60 }, - new() { JsonTime = 10, Bpm = 120 } + BpmEvents = new List + { + new() { JsonTime = 0, Bpm = 60 }, + new() { JsonTime = 10, Bpm = 120 } + } }; + initialDifficulty.BootstrapBpmEvents(songBpm); + + var initialBpmEvents = initialDifficulty.BpmEvents; var initialRegions = BaseBpmInfo.GetBpmInfoRegions(initialBpmEvents, songBpm, audiosamples, audioFrequency); // Loop conversion to and from a bunch of times - List bpmEvents = new List + var difficulty = new BaseDifficulty { - new() { JsonTime = 0, Bpm = 60 }, - new() { JsonTime = 10, Bpm = 120 } + BpmEvents = new List + { + new() { JsonTime = 0, Bpm = 60 }, + new() { JsonTime = 10, Bpm = 120 } + } }; + difficulty.BootstrapBpmEvents(songBpm); + List regions = new List(); + for (var i = 0; i < 100; i++) { - regions = BaseBpmInfo.GetBpmInfoRegions(bpmEvents, songBpm, audiosamples, audioFrequency); - bpmEvents = BaseBpmInfo.GetBpmEvents(regions, audioFrequency); + regions = BaseBpmInfo.GetBpmInfoRegions(difficulty.BpmEvents, songBpm, audiosamples, audioFrequency); + difficulty.BpmEvents = BaseBpmInfo.GetBpmEvents(regions, audioFrequency); + difficulty.BootstrapBpmEvents(songBpm); } - + var bpmEvents = difficulty.BpmEvents; + // Compare bpm events Assert.AreEqual(initialBpmEvents.Count, bpmEvents.Count); diff --git a/Assets/__Scripts/Beatmap/Animations/ObjectAnimator.cs b/Assets/__Scripts/Beatmap/Animations/ObjectAnimator.cs index f907328e5..c249d6c78 100644 --- a/Assets/__Scripts/Beatmap/Animations/ObjectAnimator.cs +++ b/Assets/__Scripts/Beatmap/Animations/ObjectAnimator.cs @@ -178,7 +178,7 @@ public void SetData(BaseGrid obj) { continue; } - var bpmChangeGridContainer = BeatmapObjectContainerCollection.GetCollectionForType(ObjectType.BpmChange); + var map = BeatSaberSongContainer.Instance.Map; foreach (var ce in events.Where(ev => ev.Type == "AssignPathAnimation")) { foreach (var jprop in ce.Data) @@ -196,7 +196,7 @@ public void SetData(BaseGrid obj) TimeEnd = time_end, }; if (p.Transition != 0) { - p.Transition = bpmChangeGridContainer.JsonTimeToSongBpmTime(ce.JsonTime + p.Transition) - ce.SongBpmTime; + p.Transition = (float)map.JsonTimeToSongBpmTime(ce.JsonTime + p.Transition) - ce.SongBpmTime; } AddPointDef(p, jprop.Key); } diff --git a/Assets/__Scripts/Beatmap/Base/BaseArc.cs b/Assets/__Scripts/Beatmap/Base/BaseArc.cs index a92f39adf..0498f0dfa 100644 --- a/Assets/__Scripts/Beatmap/Base/BaseArc.cs +++ b/Assets/__Scripts/Beatmap/Base/BaseArc.cs @@ -35,13 +35,13 @@ public BaseArc() public BaseArc(BaseArc other) { - SetTimes(other.JsonTime, other.SongBpmTime); + SetTimes(other.JsonTime); Color = other.Color; PosX = other.PosX; PosY = other.PosY; CutDirection = other.CutDirection; HeadControlPointLengthMultiplier = other.HeadControlPointLengthMultiplier; - SetTailTimes(other.TailJsonTime, other.TailSongBpmTime); + SetTailTimes(other.TailJsonTime); TailPosX = other.TailPosX; TailPosY = other.TailPosY; TailCutDirection = other.TailCutDirection; @@ -52,13 +52,13 @@ public BaseArc(BaseArc other) public BaseArc(BaseNote start, BaseNote end) { - SetTimes(start.JsonTime, start.SongBpmTime); + SetTimes(start.JsonTime); Color = start.Color; PosX = start.PosX; PosY = start.PosY; CutDirection = start.CutDirection; HeadControlPointLengthMultiplier = 1f; - SetTailTimes(end.JsonTime, end.SongBpmTime); + SetTailTimes(end.JsonTime); TailPosX = end.PosX; TailPosY = end.PosY; TailCutDirection = end.CutDirection; diff --git a/Assets/__Scripts/Beatmap/Base/BaseBpmEvent.cs b/Assets/__Scripts/Beatmap/Base/BaseBpmEvent.cs index 501290394..9a24d6003 100644 --- a/Assets/__Scripts/Beatmap/Base/BaseBpmEvent.cs +++ b/Assets/__Scripts/Beatmap/Base/BaseBpmEvent.cs @@ -26,7 +26,7 @@ public BaseBpmEvent() {} public BaseBpmEvent(BaseBpmEvent other) { - SetTimes(other.JsonTime, other.SongBpmTime); + SetTimes(other.JsonTime); Bpm = other.Bpm; CustomData = other.CustomData.Clone(); } diff --git a/Assets/__Scripts/Beatmap/Base/BaseChain.cs b/Assets/__Scripts/Beatmap/Base/BaseChain.cs index bd2622671..a6fa1f1f8 100644 --- a/Assets/__Scripts/Beatmap/Base/BaseChain.cs +++ b/Assets/__Scripts/Beatmap/Base/BaseChain.cs @@ -33,12 +33,12 @@ public BaseChain() public BaseChain(BaseChain other) { - SetTimes(other.JsonTime, other.SongBpmTime); + SetTimes(other.JsonTime); Color = other.Color; PosX = other.PosX; PosY = other.PosY; CutDirection = other.CutDirection; - SetTailTimes(other.TailJsonTime, other.TailSongBpmTime); + SetTailTimes(other.TailJsonTime); TailPosX = other.TailPosX; TailPosY = other.TailPosY; SliceCount = other.SliceCount; @@ -48,12 +48,12 @@ public BaseChain(BaseChain other) public BaseChain(BaseNote start, BaseNote end) { - SetTimes(start.JsonTime, start.SongBpmTime); + SetTimes(start.JsonTime); Color = start.Color; PosX = start.PosX; PosY = start.PosY; CutDirection = start.CutDirection; - SetTailTimes(end.JsonTime, end.SongBpmTime); + SetTailTimes(end.JsonTime); TailPosX = end.PosX; TailPosY = end.PosY; SliceCount = 5; diff --git a/Assets/__Scripts/Beatmap/Base/BaseDifficulty.cs b/Assets/__Scripts/Beatmap/Base/BaseDifficulty.cs index 149359913..f3c926b6f 100644 --- a/Assets/__Scripts/Beatmap/Base/BaseDifficulty.cs +++ b/Assets/__Scripts/Beatmap/Base/BaseDifficulty.cs @@ -93,6 +93,102 @@ public List> new List(CustomEvents), }; + #region BPM Time Conversion Logic + + private float? songBpm; + + public void BootstrapBpmEvents(float songBpm) + { + this.songBpm = songBpm; + + // remove invalid bpm events + BpmEvents.RemoveAll(x => x.JsonTime < 0); + BpmEvents.RemoveAll(x => x.Bpm < 0); + + if (!BpmEvents.Any()) return; + + BpmEvents.Sort(); + + // insert beat 0 bpm event if needed + if (BpmEvents.First().JsonTime > 0) + { + var newBpmEvent = new BaseBpmEvent(0, songBpm); + BpmEvents.Insert(0, newBpmEvent); + } + + BaseBpmEvent lastBpmEvent = null; + foreach (var bpmEvent in BpmEvents) + { + if (lastBpmEvent is null) + { + bpmEvent.songBpmTime = bpmEvent.JsonTime; + } + else + { + bpmEvent.songBpmTime = lastBpmEvent.songBpmTime + (bpmEvent.JsonTime - lastBpmEvent.JsonTime) * (songBpm / lastBpmEvent.Bpm); + } + + lastBpmEvent = bpmEvent; + } + } + + public float? JsonTimeToSongBpmTime(float jsonTime) + { + if (songBpm is null) return null; + var lastBpmEvent = FindLastBpmEventByJsonTime(jsonTime, inclusive: false); + if (lastBpmEvent is null) + { + return jsonTime; + } + return lastBpmEvent.SongBpmTime + (jsonTime - lastBpmEvent.JsonTime) * (songBpm / lastBpmEvent.Bpm); + } + + public float? SongBpmTimeToJsonTime(float songBpmTime) + { + if (songBpm is null) return null; + var lastBpmEvent = FindLastBpmEventBySongBpmTime(songBpmTime, inclusive: false); + if (lastBpmEvent is null) + { + return songBpmTime; + } + return lastBpmEvent.JsonTime + (songBpmTime - lastBpmEvent.SongBpmTime) * (lastBpmEvent.Bpm / songBpm); + } + + public BaseBpmEvent FindLastBpmEventByJsonTime(float jsonTime, bool inclusive = false) + { + return BpmEvents.LastOrDefault(x => inclusive ? x.JsonTime <= jsonTime : x.JsonTime < jsonTime); + } + + public BaseBpmEvent FindLastBpmEventBySongBpmTime(float songBpmTime, bool inclusive = false) + { + if (songBpm is null) return null; + return BpmEvents.LastOrDefault(x => inclusive ? x.SongBpmTime <= songBpmTime : x.SongBpmTime < songBpmTime); + } + + public float? BpmAtJsonTime(float jsonTime) + { + return FindLastBpmEventByJsonTime(jsonTime, inclusive: true)?.Bpm ?? songBpm; + } + + public float? BpmAtSongBpmTime(float songBpmTime) + { + return FindLastBpmEventBySongBpmTime(songBpmTime, inclusive: true)?.Bpm ?? songBpm; + } + + public void RecomputeAllObjectSongBpmTimes() + { + foreach (var objList in AllBaseObjectProperties()) + { + if (objList is null) continue; + foreach (var obj in objList) + { + obj.RecomputeSongBpmTime(); + } + } + } + + #endregion + public void ConvertCustomBpmToOfficial() { var songBpm = BeatSaberSongContainer.Instance.Info.BeatsPerMinute; diff --git a/Assets/__Scripts/Beatmap/Base/BaseEvent.cs b/Assets/__Scripts/Beatmap/Base/BaseEvent.cs index 6e661b443..24d730271 100644 --- a/Assets/__Scripts/Beatmap/Base/BaseEvent.cs +++ b/Assets/__Scripts/Beatmap/Base/BaseEvent.cs @@ -37,7 +37,7 @@ public BaseEvent() public BaseEvent(BaseEvent other) { - SetTimes(other.JsonTime, other.SongBpmTime); + SetTimes(other.JsonTime); Type = other.Type; Value = other.Value; FloatValue = other.FloatValue; diff --git a/Assets/__Scripts/Beatmap/Base/BaseGrid.cs b/Assets/__Scripts/Beatmap/Base/BaseGrid.cs index b04d53cad..2f6741cd5 100644 --- a/Assets/__Scripts/Beatmap/Base/BaseGrid.cs +++ b/Assets/__Scripts/Beatmap/Base/BaseGrid.cs @@ -53,8 +53,8 @@ protected BaseGrid(float jsonTime, float songBpmTime, int posX, int posY, JSONNo public float EditorScale { get; private set; } - public virtual float SpawnSongBpmTime { get { return SongBpmTime - Hjd; } } - public virtual float DespawnSongBpmTime { get { return SongBpmTime + Hjd; } } + public virtual float SpawnSongBpmTime => SongBpmTime - Hjd; + public virtual float DespawnSongBpmTime => SongBpmTime + Hjd; public virtual JSONNode CustomAnimation { get; set; } diff --git a/Assets/__Scripts/Beatmap/Base/BaseNJSEvent.cs b/Assets/__Scripts/Beatmap/Base/BaseNJSEvent.cs index 43701cb3c..b306d6ace 100644 --- a/Assets/__Scripts/Beatmap/Base/BaseNJSEvent.cs +++ b/Assets/__Scripts/Beatmap/Base/BaseNJSEvent.cs @@ -29,7 +29,7 @@ public BaseNJSEvent() public BaseNJSEvent(BaseNJSEvent other) { - SetTimes(other.JsonTime, other.SongBpmTime); + SetTimes(other.JsonTime); UsePrevious = other.UsePrevious; Easing = other.Easing; RelativeNJS = other.RelativeNJS; diff --git a/Assets/__Scripts/Beatmap/Base/BaseNote.cs b/Assets/__Scripts/Beatmap/Base/BaseNote.cs index d9b2e333b..f452501e6 100644 --- a/Assets/__Scripts/Beatmap/Base/BaseNote.cs +++ b/Assets/__Scripts/Beatmap/Base/BaseNote.cs @@ -36,7 +36,7 @@ public BaseNote() public BaseNote(BaseNote other) { - SetTimes(other.JsonTime, other.SongBpmTime); + SetTimes(other.JsonTime); PosX = other.PosX; PosY = other.PosY; Color = other.Color; diff --git a/Assets/__Scripts/Beatmap/Base/BaseObject.cs b/Assets/__Scripts/Beatmap/Base/BaseObject.cs index 6af3d9195..0d59384f1 100644 --- a/Assets/__Scripts/Beatmap/Base/BaseObject.cs +++ b/Assets/__Scripts/Beatmap/Base/BaseObject.cs @@ -12,7 +12,7 @@ public abstract class BaseObject : BaseItem, ICustomData, IHeckObject, IChromaOb public virtual void Serialize(NetDataWriter writer) { writer.Put(jsonTime); - writer.Put(songBpmTime); + writer.Put((float)songBpmTime); writer.Put(CustomData?.ToString()); } @@ -25,6 +25,7 @@ public virtual void Deserialize(NetDataReader reader) protected BaseObject() { + JsonTime = 0; // needed to set songBpmTime } protected BaseObject(float time, JSONNode customData = null) @@ -49,28 +50,21 @@ public float JsonTime get => jsonTime; set { - var bpmChangeGridContainer = BeatmapObjectContainerCollection.GetCollectionForType(ObjectType.BpmChange); - songBpmTime = bpmChangeGridContainer?.JsonTimeToSongBpmTime(value) ?? value; + var map = BeatSaberSongContainer.Instance != null ? BeatSaberSongContainer.Instance.Map : null; + songBpmTime = map?.JsonTimeToSongBpmTime(value); jsonTime = value; } } - private float songBpmTime; - public float SongBpmTime - { - get => songBpmTime; - set - { - var bpmChangeGridContainer = BeatmapObjectContainerCollection.GetCollectionForType(ObjectType.BpmChange); - jsonTime = bpmChangeGridContainer?.SongBpmTimeToJsonTime(value) ?? value; - songBpmTime = value; - } - } + // should only be set directly when initializing + // read from SongBpmTime instead, and write to JsonTime to update this + // really should be private but we need to set this from BaseDifficulty on init + internal float? songBpmTime; + public float SongBpmTime => (float)songBpmTime; - public void SetTimes(float jsonTime, float songBpmTime) + public void SetTimes(float jsonTime) { this.jsonTime = jsonTime; - this.songBpmTime = songBpmTime; RecomputeSongBpmTime(); } diff --git a/Assets/__Scripts/Beatmap/Base/BaseObstacle.cs b/Assets/__Scripts/Beatmap/Base/BaseObstacle.cs index 6c606d621..ed27fac88 100644 --- a/Assets/__Scripts/Beatmap/Base/BaseObstacle.cs +++ b/Assets/__Scripts/Beatmap/Base/BaseObstacle.cs @@ -40,7 +40,7 @@ public BaseObstacle() private BaseObstacle(BaseObstacle other) { - SetTimes(other.JsonTime, other.SongBpmTime); + SetTimes(other.JsonTime); PosX = other.PosX; InternalPosY = other.PosY; InternalType = other.Type; @@ -99,11 +99,22 @@ public int Height set => InternalHeight = value; } - public float Duration { get; set; } - public float DurationSongBpm { get; set; } + private float duration; + public float Duration + { + get => duration; + set + { + var map = BeatSaberSongContainer.Instance != null ? BeatSaberSongContainer.Instance.Map : null; + durationSongBpm = map?.JsonTimeToSongBpmTime(value + JsonTime) - songBpmTime; + duration = value; + } + } + private float? durationSongBpm; + public float DurationSongBpm => (float)durationSongBpm; public int Width { get; set; } - - public override float DespawnSongBpmTime { get { return SongBpmTime + DurationSongBpm + Hjd; } } + + public override float DespawnSongBpmTime => SongBpmTime + DurationSongBpm + Hjd; public virtual JSONNode CustomSize { get; set; } @@ -277,8 +288,7 @@ public ObstacleBounds GetShape() public override void RecomputeSongBpmTime() { base.RecomputeSongBpmTime(); - DurationSongBpm = (BeatmapObjectContainerCollection.GetCollectionForType(ObjectType.BpmChange) - ?.JsonTimeToSongBpmTime(JsonTime + Duration) ?? (JsonTime + Duration)) - SongBpmTime; + Duration = Duration; } protected void InferType() => diff --git a/Assets/__Scripts/Beatmap/Base/BaseSlider.cs b/Assets/__Scripts/Beatmap/Base/BaseSlider.cs index 9870175b8..3043f2aa8 100644 --- a/Assets/__Scripts/Beatmap/Base/BaseSlider.cs +++ b/Assets/__Scripts/Beatmap/Base/BaseSlider.cs @@ -34,6 +34,7 @@ public override void Deserialize(NetDataReader reader) protected BaseSlider() { + TailJsonTime = 0; // needed to set tailSongBpmTime } protected BaseSlider(float time, int posX, int posY, int color, int cutDirection, int angleOffset, @@ -71,27 +72,17 @@ public float TailJsonTime get => tailJsonTime; set { - var bpmChangeGridContainer = BeatmapObjectContainerCollection.GetCollectionForType(ObjectType.BpmChange); - tailSongBpmTime = bpmChangeGridContainer?.JsonTimeToSongBpmTime(value) ?? value; + var map = BeatSaberSongContainer.Instance != null ? BeatSaberSongContainer.Instance.Map : null; + tailSongBpmTime = map?.JsonTimeToSongBpmTime(value); tailJsonTime = value; } } - private float tailSongBpmTime { get; set; } - public float TailSongBpmTime - { - get => tailSongBpmTime; - set - { - var bpmChangeGridContainer = BeatmapObjectContainerCollection.GetCollectionForType(ObjectType.BpmChange); - tailJsonTime = bpmChangeGridContainer?.SongBpmTimeToJsonTime(value) ?? value; - tailSongBpmTime = value; - } - } + private float? tailSongBpmTime; + public float TailSongBpmTime => (float)tailSongBpmTime; - public void SetTailTimes(float jsonTime, float songBpmTime) + public void SetTailTimes(float jsonTime) { - this.tailJsonTime = jsonTime; - this.tailSongBpmTime = songBpmTime; + TailJsonTime = jsonTime; } public int TailPosX { get; set; } @@ -140,9 +131,8 @@ public override void Apply(BaseObject originalData) public virtual void SwapHeadAndTail() { var tempJsonTime = JsonTime; - var tempJsonSongBpmTime = SongBpmTime; - SetTimes(tailJsonTime, tailSongBpmTime); - SetTailTimes(tempJsonTime, tempJsonSongBpmTime); + SetTimes(tailJsonTime); + SetTailTimes(tempJsonTime); (PosX, TailPosX) = (TailPosX, PosX); (PosY, TailPosY) = (TailPosY, PosY); } diff --git a/Assets/__Scripts/Beatmap/Base/BaseWaypoint.cs b/Assets/__Scripts/Beatmap/Base/BaseWaypoint.cs index 37f16031f..142a70f4b 100644 --- a/Assets/__Scripts/Beatmap/Base/BaseWaypoint.cs +++ b/Assets/__Scripts/Beatmap/Base/BaseWaypoint.cs @@ -14,7 +14,7 @@ public BaseWaypoint() public BaseWaypoint(BaseWaypoint other) { - SetTimes(other.JsonTime, other.SongBpmTime); + SetTimes(other.JsonTime); PosX = other.PosX; PosY = other.PosY; OffsetDirection = other.OffsetDirection; diff --git a/Assets/__Scripts/Beatmap/Base/Customs/BaseBookmark.cs b/Assets/__Scripts/Beatmap/Base/Customs/BaseBookmark.cs index 625ef2edb..b739d4051 100644 --- a/Assets/__Scripts/Beatmap/Base/Customs/BaseBookmark.cs +++ b/Assets/__Scripts/Beatmap/Base/Customs/BaseBookmark.cs @@ -41,7 +41,7 @@ public BaseBookmark() protected BaseBookmark(BaseBookmark other) { - SetTimes(other.JsonTime, other.SongBpmTime); + SetTimes(other.JsonTime); Name = other.Name; Color = other.Color; } diff --git a/Assets/__Scripts/Beatmap/Base/Customs/BaseBpmChange.cs b/Assets/__Scripts/Beatmap/Base/Customs/BaseBpmChange.cs index 1409cf6c1..d6ff3c7a2 100644 --- a/Assets/__Scripts/Beatmap/Base/Customs/BaseBpmChange.cs +++ b/Assets/__Scripts/Beatmap/Base/Customs/BaseBpmChange.cs @@ -13,7 +13,7 @@ public BaseBpmChange() protected BaseBpmChange(BaseBpmChange other) { - SetTimes(other.JsonTime, other.SongBpmTime); + SetTimes(other.JsonTime); Bpm = other.Bpm; BeatsPerBar = other.BeatsPerBar; MetronomeOffset = other.MetronomeOffset; @@ -21,7 +21,7 @@ protected BaseBpmChange(BaseBpmChange other) protected BaseBpmChange(BaseBpmEvent other) { - SetTimes(other.JsonTime, other.SongBpmTime); + SetTimes(other.JsonTime); Bpm = other.Bpm; BeatsPerBar = 4; MetronomeOffset = 4; diff --git a/Assets/__Scripts/Beatmap/Containers/ObstacleContainer.cs b/Assets/__Scripts/Beatmap/Containers/ObstacleContainer.cs index 51ae88445..1f04bf4e3 100644 --- a/Assets/__Scripts/Beatmap/Containers/ObstacleContainer.cs +++ b/Assets/__Scripts/Beatmap/Containers/ObstacleContainer.cs @@ -58,9 +58,7 @@ public float GetLength() if (ObstacleData.CustomSize != null && ObstacleData.CustomSize.IsArray && ObstacleData.CustomSize[2].IsNumber) return ObstacleData.CustomSize[2]; - var obstacleStart = ObstacleData.SongBpmTime; - var obstacleEnd = bpmChangeGridContainer?.JsonTimeToSongBpmTime(ObstacleData.JsonTime + ObstacleData.Duration) ?? 0; - var length = obstacleEnd - obstacleStart; + var length = ObstacleData.DurationSongBpm; //Take half jump duration into account if the setting is enabled. if (ObstacleData.Duration < 0 && Settings.Instance.ShowMoreAccurateFastWalls && !UIMode.AnimationMode) diff --git a/Assets/__Scripts/Beatmap/Helper/BeatmapFactory.cs b/Assets/__Scripts/Beatmap/Helper/BeatmapFactory.cs index 8c239afd6..3f13ee39c 100644 --- a/Assets/__Scripts/Beatmap/Helper/BeatmapFactory.cs +++ b/Assets/__Scripts/Beatmap/Helper/BeatmapFactory.cs @@ -17,28 +17,36 @@ public static class BeatmapFactory { public static BaseDifficulty GetDifficultyFromJson(JSONNode mainNode, string directoryAndFile) { + var info = BeatSaberSongContainer.Instance.Info; + var infoDifficulty = BeatSaberSongContainer.Instance.MapDifficultyInfo; + BaseDifficulty difficulty; + var v = PeekMapVersionFromJson(mainNode); switch (v[0]) { case '4': Settings.Instance.MapVersion = 4; - var difficulty = V4Difficulty.GetFromJson(mainNode, directoryAndFile); - var info = BeatSaberSongContainer.Instance.Info; - var infoDifficulty = BeatSaberSongContainer.Instance.MapDifficultyInfo; + difficulty = V4Difficulty.GetFromJson(mainNode, directoryAndFile); V4Difficulty.LoadBpmFromAudioData(difficulty, info); V4Difficulty.LoadLightsFromLightshowFile(difficulty, info, infoDifficulty); V4Difficulty.LoadBookmarksFromOfficialEditor(difficulty, info, infoDifficulty); - return difficulty; + break; case '3': Settings.Instance.MapVersion = 3; - return V3Difficulty.GetFromJson(mainNode, directoryAndFile); + difficulty = V3Difficulty.GetFromJson(mainNode, directoryAndFile); + break; case '2': Settings.Instance.MapVersion = 2; - return V2Difficulty.GetFromJson(mainNode, directoryAndFile); + difficulty = V2Difficulty.GetFromJson(mainNode, directoryAndFile); + break; default: return null; } + + difficulty.BootstrapBpmEvents(info.BeatsPerMinute); + difficulty.RecomputeAllObjectSongBpmTimes(); + return difficulty; } private static string PeekMapVersionFromJson(JSONNode mainNode) diff --git a/Assets/__Scripts/Beatmap/Info/Base/BaseBpmInfo.cs b/Assets/__Scripts/Beatmap/Info/Base/BaseBpmInfo.cs index 5983ad620..406a0b0e2 100644 --- a/Assets/__Scripts/Beatmap/Info/Base/BaseBpmInfo.cs +++ b/Assets/__Scripts/Beatmap/Info/Base/BaseBpmInfo.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Beatmap.Base; namespace Beatmap.Info @@ -40,10 +41,17 @@ public static List GetBpmEvents(List bpmRegions, { var samples = bpmRegion.EndSampleIndex - bpmRegion.StartSampleIndex; var beats = bpmRegion.EndBeat - bpmRegion.StartBeat; - + var rawBpm = beats / samples * audioFrequency * 60; + + // use rounded bpm if unrounding was caused by numerical error in conversion to BPM regions + var roundedBpm = (float)Math.Round(beats / samples * audioFrequency * 60); + var roundedBpmSamples = beats * 60 / roundedBpm * audioFrequency; + const float threshold = 1.1f; // expect max of 0.5 samples either direction for both start and end, so max of 1 total, plus some margin + var useRounded = Math.Abs(roundedBpmSamples - samples) < threshold; + bpmEvents.Add(new BaseBpmEvent { - Bpm = beats / samples * audioFrequency * 60, + Bpm = useRounded ? roundedBpm : rawBpm, JsonTime = bpmRegion.StartBeat }); } @@ -67,28 +75,33 @@ public static List GetBpmInfoRegions(List bpmEve } else { + // This SHOULD be 0, since the fist BpmEvent is supposed to be at beat 0 + var previousEndSampleIndex = (int)Math.Round(bpmEvents[0].SongBpmTime * (60f / songBpm) * audioFrequency, MidpointRounding.AwayFromZero); + for (var i = 0; i < bpmEvents.Count - 1; i++) { var currentBpmEvent = bpmEvents[i]; var nextBpmEvent = bpmEvents[i + 1]; + var endSampleIndex = (int)Math.Round(nextBpmEvent.SongBpmTime * (60f / songBpm) * audioFrequency, MidpointRounding.AwayFromZero); regions.Add(new BpmInfoBpmRegion { - StartSampleIndex = (int)(currentBpmEvent.SongBpmTime * (60f / songBpm) * audioFrequency), - EndSampleIndex = (int)(nextBpmEvent.SongBpmTime * (60f / songBpm) * audioFrequency), + StartSampleIndex = previousEndSampleIndex, + EndSampleIndex = endSampleIndex, StartBeat = currentBpmEvent.JsonTime, EndBeat = nextBpmEvent.JsonTime, }); + + previousEndSampleIndex = endSampleIndex; } var lastBpmEvent = bpmEvents[^1]; - var lastStartSampleIndex = lastBpmEvent.SongBpmTime * (60f / songBpm) * audioFrequency; - var secondsDiff = (audioSamples - lastStartSampleIndex) / audioFrequency; + var secondsDiff = (float)(audioSamples - previousEndSampleIndex) / audioFrequency; var jsonBeatsDiff = secondsDiff * (lastBpmEvent.Bpm / 60f); regions.Add(new BpmInfoBpmRegion { - StartSampleIndex = (int)lastStartSampleIndex, + StartSampleIndex = previousEndSampleIndex, EndSampleIndex = audioSamples, StartBeat = lastBpmEvent.JsonTime, EndBeat = lastBpmEvent.JsonTime + jsonBeatsDiff, diff --git a/Assets/__Scripts/MapEditor/Audio/MetronomeHandler.cs b/Assets/__Scripts/MapEditor/Audio/MetronomeHandler.cs index fd828103f..4e14395a0 100644 --- a/Assets/__Scripts/MapEditor/Audio/MetronomeHandler.cs +++ b/Assets/__Scripts/MapEditor/Audio/MetronomeHandler.cs @@ -49,13 +49,14 @@ private void LateUpdate() if (atsc.CurrentAudioBeats > queuedDingSongBpmTime) { var nextJsonTime = Mathf.Ceil(atsc.CurrentJsonTime); - - if (Mathf.Abs(Mathf.Floor(bpmChangeGridContainer.SongBpmTimeToJsonTime(atsc.CurrentAudioBeats)) + + var map = BeatSaberSongContainer.Instance.Map; + if (Mathf.Abs(Mathf.Floor((float)map.SongBpmTimeToJsonTime(atsc.CurrentAudioBeats)) - Mathf.Floor(atsc.CurrentJsonTime)) > 0.01f) { nextJsonTime = Mathf.Ceil(nextJsonTime + 1f); } - queuedDingSongBpmTime = bpmChangeGridContainer.JsonTimeToSongBpmTime(nextJsonTime); + queuedDingSongBpmTime = (float)map.JsonTimeToSongBpmTime(nextJsonTime); var delay = atsc.GetSecondsFromBeat(queuedDingSongBpmTime - atsc.CurrentAudioBeats) / songSpeed; audioUtil.PlayOneShotSound(CowBell ? cowbellSound : metronomeSound, Settings.Instance.MetronomeVolume, diff --git a/Assets/__Scripts/MapEditor/AudioTimeSyncController.cs b/Assets/__Scripts/MapEditor/AudioTimeSyncController.cs index fb8719b37..1fc3a868e 100644 --- a/Assets/__Scripts/MapEditor/AudioTimeSyncController.cs +++ b/Assets/__Scripts/MapEditor/AudioTimeSyncController.cs @@ -68,7 +68,7 @@ public float CurrentJsonTime private set { currentJsonTime = value; - currentSongBpmTime = bpmChangeGridContainer?.JsonTimeToSongBpmTime(value) ?? value; + currentSongBpmTime = (float)BeatSaberSongContainer.Instance.Map.JsonTimeToSongBpmTime(value); currentSeconds = GetSecondsFromBeat(currentSongBpmTime); ValidatePosition(); UpdateMovables(); @@ -85,7 +85,7 @@ public float CurrentSongBpmTime private set { currentSongBpmTime = value; - currentJsonTime = bpmChangeGridContainer?.SongBpmTimeToJsonTime(value) ?? value; + currentJsonTime = (float)BeatSaberSongContainer.Instance.Map.SongBpmTimeToJsonTime(value); currentSeconds = GetSecondsFromBeat(value); ValidatePosition(); UpdateMovables(); @@ -99,7 +99,7 @@ private set { currentSeconds = value; currentSongBpmTime = GetBeatFromSeconds(value); - currentJsonTime = bpmChangeGridContainer.SongBpmTimeToJsonTime(currentSongBpmTime); + currentJsonTime = (float)BeatSaberSongContainer.Instance.Map.SongBpmTimeToJsonTime(currentSongBpmTime); ValidatePosition(); UpdateMovables(); } @@ -412,24 +412,22 @@ public void SnapToGrid(float seconds) { if (IsPlaying) return; var songBpmTime = GetBeatFromSeconds(seconds); - UpdateCurrentTimes(songBpmTime); + currentJsonTime = (float)BeatSaberSongContainer.Instance.Map.SongBpmTimeToJsonTime(songBpmTime); + + SnapToGrid(); SongAudioSource.time = CurrentSeconds; - ValidatePosition(); - UpdateMovables(); } public void SnapToGrid(bool positionValidated = false) { - UpdateCurrentTimes(currentSongBpmTime); - if (!positionValidated) ValidatePosition(); - UpdateMovables(); - } + var jsonTime = (float)Math.Round(CurrentJsonTime * GridMeasureSnapping, MidpointRounding.AwayFromZero) / GridMeasureSnapping; - private void UpdateCurrentTimes(float songBpmTime) - { - currentJsonTime = bpmChangeGridContainer.SongBpmTimeToRoundedJsonTime(songBpmTime); - currentSongBpmTime = bpmChangeGridContainer.JsonTimeToSongBpmTime(currentJsonTime); + currentJsonTime = jsonTime; + currentSongBpmTime = (float)BeatSaberSongContainer.Instance.Map.JsonTimeToSongBpmTime(jsonTime); currentSeconds = GetSecondsFromBeat(currentSongBpmTime); + + if (!positionValidated) ValidatePosition(); + UpdateMovables(); } public void RefreshGridSnapping() => GridMeasureSnappingChanged?.Invoke(GridMeasureSnapping); @@ -457,8 +455,6 @@ public void MoveToJsonTime(float jsonTime) SongAudioSource.time = CurrentSeconds; } - public float FindRoundedBeatTime(float beat, float snap = -1) => bpmChangeGridContainer.FindRoundedBpmTime(beat, snap); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public float GetBeatFromSeconds(float seconds) => MapInfo.BeatsPerMinute / 60 * seconds; diff --git a/Assets/__Scripts/MapEditor/Detection/DingOnNotePassingGrid.cs b/Assets/__Scripts/MapEditor/Detection/DingOnNotePassingGrid.cs index a707d6ef7..db7a3cdb9 100644 --- a/Assets/__Scripts/MapEditor/Detection/DingOnNotePassingGrid.cs +++ b/Assets/__Scripts/MapEditor/Detection/DingOnNotePassingGrid.cs @@ -88,10 +88,10 @@ private void OnPlayToggle(bool playing) if (!playing) return; // Since we schedule hit sounds ahead of time using the note callback, there will be a small period ahead of the - // playback cursor when start play is toggled where hit sounds are not scheduled play on play so we do that here - var bpmCollection = BeatmapObjectContainerCollection.GetCollectionForType(ObjectType.BpmChange); - var currentJsonTime = bpmCollection.SongBpmTimeToJsonTime(atsc.CurrentAudioBeats); - var endJsonTime = bpmCollection.SongBpmTimeToJsonTime(atsc.CurrentAudioBeats + beatSaberCutCallbackController.Offset); + // playback cursor when start play is toggled where hit sounds are not scheduled play on play so we do that here + var map = BeatSaberSongContainer.Instance.Map; + var currentJsonTime = (float)map.SongBpmTimeToJsonTime(atsc.CurrentAudioBeats); + var endJsonTime = (float)map.SongBpmTimeToJsonTime(atsc.CurrentAudioBeats + beatSaberCutCallbackController.Offset); var notes = container.GetBetween(currentJsonTime, endJsonTime); foreach (var n in notes) PlaySound(false, 0, n); diff --git a/Assets/__Scripts/MapEditor/Grid/Collections/BPMChangeGridContainer.cs b/Assets/__Scripts/MapEditor/Grid/Collections/BPMChangeGridContainer.cs index c9dab80e1..b051aa894 100644 --- a/Assets/__Scripts/MapEditor/Grid/Collections/BPMChangeGridContainer.cs +++ b/Assets/__Scripts/MapEditor/Grid/Collections/BPMChangeGridContainer.cs @@ -108,14 +108,15 @@ public void RefreshModifiedBeat() public void RefreshGridProperties() { + var songContainer = BeatSaberSongContainer.Instance; // Could probably save a tiny bit of performance since this should always be constant (0, Song BPM) but whatever var bpmChangeCount = 1; bpmShaderTimes[0] = 0; bpmShaderJsonTimes[0] = 0; - bpmShaderBpMs[0] = BeatSaberSongContainer.Instance.Info.BeatsPerMinute; + bpmShaderBpMs[0] = songContainer.Info.BeatsPerMinute; // Grab the last object before grid ends - var lastBpmChange = FindLastBpm(AudioTimeSyncController.CurrentSongBpmTime - firstVisibleBeatTime, false); + var lastBpmChange = songContainer.Map.FindLastBpmEventBySongBpmTime(AudioTimeSyncController.CurrentSongBpmTime - firstVisibleBeatTime); // Plug this last bpm change in // Believe it or not, I cannot actually skip this BPM change if it exists @@ -163,114 +164,6 @@ protected override void OnContainerSpawn(ObjectContainer container, BaseObject o protected override void OnContainerDespawn(ObjectContainer container, BaseObject obj) => RefreshGridProperties(); - public float FindRoundedBpmTime(float beatTimeInSongBpm, float snap = -1) - { - if (snap == -1) snap = 1f / AudioTimeSyncController.GridMeasureSnapping; - var lastBpm = FindLastBpm(beatTimeInSongBpm); //Find the last BPM Change before our beat time - if (lastBpm is null) - { - return (float)Math.Round(beatTimeInSongBpm / snap, MidpointRounding.AwayFromZero) * - snap; //If its null, return rounded song bpm - } - - var jsonTime = SongBpmTimeToJsonTime(beatTimeInSongBpm); - var roundedJsonTime = (float)Math.Round(jsonTime / snap, MidpointRounding.AwayFromZero) * snap; - - return JsonTimeToSongBpmTime(roundedJsonTime); - } - - public float SongBpmTimeToRoundedJsonTime(float songBpmTime, float snap = -1) - { - if (snap == -1) snap = 1f / AudioTimeSyncController.GridMeasureSnapping; - - var jsonTime = SongBpmTimeToJsonTime(songBpmTime); - return (float)Math.Round(jsonTime / snap, MidpointRounding.AwayFromZero) * snap; - } - - /// - /// Find the last before a given beat time. - /// - /// Time in raw beats (Unmodified by any BPM Changes) - /// Whether or not to include s with the same time value. - /// The last before the given beat (or if there is none). - public BaseBpmEvent FindLastBpm(float beatTimeInSongBpm, bool inclusive = true) - { - return inclusive - ? MapObjects.FindLast(x => x.SongBpmTime <= beatTimeInSongBpm + 0.01f) - : MapObjects.FindLast(x => x.SongBpmTime + 0.01f < beatTimeInSongBpm); - } - - /// - /// Find the next after a given beat time. - /// - /// Time in raw beats (Unmodified by any BPM Changes) - /// Whether or not to include s with the same time value. - /// The next after the given beat (or if there is none). - public BaseBpmEvent FindNextBpm(float beatTimeInSongBpm, bool inclusive = false) - { - return inclusive - ? MapObjects.Find(x => x.SongBpmTime >= beatTimeInSongBpm - 0.01f) - : MapObjects.Find(x => x.SongBpmTime - 0.01f > beatTimeInSongBpm); - } - - private BaseBpmEvent DefaultEvent() - { - var defaultEvent = new BaseBpmEvent { Bpm = BeatSaberSongContainer.Instance.Info.BeatsPerMinute }; - return defaultEvent; - } - - public float JsonTimeToSongBpmTime(float jsonTime) - { - var songBpm = BeatSaberSongContainer.Instance.Info.BeatsPerMinute; - var bpms = MapObjects.FindAll(x => x.JsonTime <= jsonTime); - bpms.Insert(0, DefaultEvent()); - - var currentSongBeats = 0f; - for (int i = 0; i < bpms.Count - 1; i++) - { - var bpmChange = bpms[i]; - var nextBpmChange = bpms[i + 1]; - - var timeDiff = nextBpmChange.JsonTime - bpmChange.JsonTime; - - currentSongBeats += timeDiff * (songBpm / bpmChange.Bpm); - } - - currentSongBeats += (jsonTime - bpms.Last().JsonTime) * (songBpm / bpms.Last().Bpm); - return currentSongBeats; - } - - public float SongBpmTimeToJsonTime(float songBpmTime) - { - var songBpm = BeatSaberSongContainer.Instance.Info.BeatsPerMinute; - var bpms = MapObjects.FindAll(x => x.SongBpmTime <= songBpmTime); - bpms.Insert(0, DefaultEvent()); - - var seconds = songBpmTime * (60f / songBpm); - - var currentSeconds = 0f; - var nextSeconds = 0f; - for (int i = 0; i < bpms.Count - 1; i++) - { - var bpmChange = bpms[i]; - var nextBpmChange = bpms[i + 1]; - - var timeDiff = nextBpmChange.JsonTime - bpmChange.JsonTime; - var scale = bpmChange.Bpm / 60; - nextSeconds += timeDiff / scale; - - if (nextSeconds > seconds) - { - return bpmChange.JsonTime + scale * (seconds - currentSeconds); - } - - currentSeconds = nextSeconds; - } - - var lastBpm = bpms.Last(); - return lastBpm.JsonTime + lastBpm.Bpm / 60 * (seconds - currentSeconds); - } - public override ObjectContainer CreateContainer() => BpmEventContainer.SpawnBpmChange(null, ref bpmPrefab); diff --git a/Assets/__Scripts/MapEditor/Grid/Collections/BeatmapObjectContainerCollection.cs b/Assets/__Scripts/MapEditor/Grid/Collections/BeatmapObjectContainerCollection.cs index 5519ff14f..fe45e145f 100644 --- a/Assets/__Scripts/MapEditor/Grid/Collections/BeatmapObjectContainerCollection.cs +++ b/Assets/__Scripts/MapEditor/Grid/Collections/BeatmapObjectContainerCollection.cs @@ -358,9 +358,25 @@ public abstract void SpawnObject(BaseObject obj, out List conflictin public static void RefreshFutureObjectsPosition(float jsonTime) { - foreach (var objectType in System.Enum.GetValues(typeof(Beatmap.Enums.ObjectType))) + // we have to refresh bpm events FIRST, and only then can we refresh other objects + var objectTypes = new List + { + ObjectType.BpmChange, + ObjectType.Note, + ObjectType.Event, + ObjectType.Obstacle, + ObjectType.CustomNote, + ObjectType.CustomEvent, + ObjectType.Arc, + ObjectType.Chain, + ObjectType.Bookmark, + ObjectType.Waypoint, + ObjectType.NJSEvent + }; + + foreach (var objectType in objectTypes) { - var collection = BeatmapObjectContainerCollection.GetCollectionForType((Beatmap.Enums.ObjectType)objectType); + var collection = BeatmapObjectContainerCollection.GetCollectionForType(objectType); if (collection == null) continue; // REVIEW: not sure if allocation is avoidable foreach (var obj in collection.LoadedObjects) @@ -376,6 +392,13 @@ public static void RefreshFutureObjectsPosition(float jsonTime) obj.RecomputeSongBpmTime(); } } + else if (collection is ObstacleGridContainer) + { + if ((obj as BaseObstacle).Duration + obj.JsonTime > jsonTime) + { + obj.RecomputeSongBpmTime(); + } + } } foreach (var container in collection.LoadedContainers) { diff --git a/Assets/__Scripts/MapEditor/Grid/MeasureLinesController.cs b/Assets/__Scripts/MapEditor/Grid/MeasureLinesController.cs index 1569ab3ec..dfc5524da 100644 --- a/Assets/__Scripts/MapEditor/Grid/MeasureLinesController.cs +++ b/Assets/__Scripts/MapEditor/Grid/MeasureLinesController.cs @@ -54,10 +54,12 @@ public void RefreshMeasureLines() var existing = new Queue(measureTextsByBeat.Select(x => x.Item2)); measureTextsByBeat.Clear(); + var songContainer = BeatSaberSongContainer.Instance; + var rawBeatsInSong = - Mathf.FloorToInt(atsc.GetBeatFromSeconds(BeatSaberSongContainer.Instance.LoadedSong.length)); + Mathf.FloorToInt(atsc.GetBeatFromSeconds(songContainer.LoadedSong.length)); var modifiedBeatsInSong = - Mathf.FloorToInt(bpmChangeGridContainer.SongBpmTimeToJsonTime(rawBeatsInSong)); + Mathf.FloorToInt((float)songContainer.Map.SongBpmTimeToJsonTime(rawBeatsInSong)); // This stops CM freezing for a few seconds as a result of instantiating a bajillion lines from insanely // high bpm events. Should be reasonable to assume that you're not mapping at >10x the info bpm @@ -68,7 +70,7 @@ public void RefreshMeasureLines() { var text = existing.Count > 0 ? existing.Dequeue() : Instantiate(measureLinePrefab, parent); text.text = $"{jsonBeat}"; - var jsonBeatPosition = bpmChangeGridContainer.JsonTimeToSongBpmTime(jsonBeat); + var jsonBeatPosition = (float)songContainer.Map.JsonTimeToSongBpmTime(jsonBeat); text.transform.localPosition = new Vector3(0, jsonBeatPosition * EditorScaleController.EditorScale, 0); measureTextsByBeat.Add((jsonBeatPosition, text)); diff --git a/Assets/__Scripts/MapEditor/Input/BeatmapBPMChangeInputController.cs b/Assets/__Scripts/MapEditor/Input/BeatmapBPMChangeInputController.cs index f88d9859a..e03bae7bd 100644 --- a/Assets/__Scripts/MapEditor/Input/BeatmapBPMChangeInputController.cs +++ b/Assets/__Scripts/MapEditor/Input/BeatmapBPMChangeInputController.cs @@ -42,21 +42,15 @@ public void OnTweakBPMValue(InputAction.CallbackContext context) BeatmapActionContainer.AddAction(new BeatmapObjectModifiedAction(containerToEdit.ObjectData, containerToEdit.ObjectData, original, "Tweaked bpm")); + BeatmapObjectContainerCollection.RefreshFutureObjectsPosition(containerToEdit.BpmData.JsonTime); + bpmChanges.RefreshModifiedBeat(); + // Update cursor position var atsc = bpmChanges.AudioTimeSyncController; - if (containerToEdit.BpmData.SongBpmTime < atsc.CurrentSongBpmTime) + if (containerToEdit.BpmData.JsonTime < atsc.CurrentJsonTime) { - var lastBpmChange = bpmChanges.FindLastBpm(atsc.CurrentSongBpmTime); - if (lastBpmChange == containerToEdit.BpmData) - { - var newTime = lastBpmChange.SongBpmTime + ((atsc.CurrentSongBpmTime - lastBpmChange.SongBpmTime) * - (lastBpmChange.Bpm - modifier) / lastBpmChange.Bpm); - atsc.MoveToSongBpmTime(newTime); - } + atsc.MoveToJsonTime(atsc.CurrentJsonTime); } - - BeatmapObjectContainerCollection.RefreshFutureObjectsPosition(containerToEdit.BpmData.JsonTime); - bpmChanges.RefreshModifiedBeat(); } } } diff --git a/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/ArcIndicatorPlacement.cs b/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/ArcIndicatorPlacement.cs index aa03378db..dd56a76e0 100644 --- a/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/ArcIndicatorPlacement.cs +++ b/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/ArcIndicatorPlacement.cs @@ -91,7 +91,7 @@ public override void TransferQueuedToDraggedObject(ref BaseArc dragged, BaseArc { if (DraggedObjectContainer.IndicatorType == IndicatorType.Head) { - dragged.SetTimes(queued.JsonTime, queued.SongBpmTime); + dragged.SetTimes(queued.JsonTime); dragged.PosX = queued.PosX; dragged.PosY = queued.PosY; dragged.CutDirection = queued.CutDirection; @@ -100,7 +100,7 @@ public override void TransferQueuedToDraggedObject(ref BaseArc dragged, BaseArc if (DraggedObjectContainer.IndicatorType == IndicatorType.Tail) { - dragged.SetTailTimes(queued.JsonTime, queued.SongBpmTime); + dragged.SetTailTimes(queued.JsonTime); dragged.TailPosX = queued.PosX; dragged.TailPosY = queued.PosY; dragged.TailCutDirection = queued.TailCutDirection; diff --git a/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/BPMChangePlacement.cs b/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/BPMChangePlacement.cs index 2f5a43176..7f66e8c78 100644 --- a/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/BPMChangePlacement.cs +++ b/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/BPMChangePlacement.cs @@ -20,7 +20,7 @@ public override void OnPhysicsRaycast(Intersections.IntersectionHit _, Vector3 _ public override void TransferQueuedToDraggedObject(ref BaseBpmEvent dragged, BaseBpmEvent queued) { - dragged.SetTimes(queued.JsonTime, queued.SongBpmTime); + dragged.SetTimes(queued.JsonTime); objectContainerCollection.RefreshModifiedBeat(); } @@ -39,9 +39,8 @@ private void AttemptPlaceBpmChange(string obj, bool willResetGrid) if (willResetGrid && (Mathf.Abs(queuedData.JsonTime - Mathf.Round(queuedData.JsonTime)) > BeatmapObjectContainerCollection.Epsilon)) { // e.g. Placing a bpm event at beat 3.5 will create a bpm event at beat 3 and 4. - // The bpm on beat 3 will be such that the bpm event on beat 4 lines with where the cursor is. - var prevBpm = objectContainerCollection.FindLastBpm(SongBpmTime, false)?.Bpm ?? - BeatSaberSongContainer.Instance.Info.BeatsPerMinute; + // The bpm on beat 3 will be such that the bpm event on beat 4 lines with where the cursor is. + var prevBpm = (float)BeatSaberSongContainer.Instance.Map.BpmAtSongBpmTime(SongBpmTime); var prevBeat = Mathf.Floor(queuedData.JsonTime); var nextBeat = Mathf.Ceil(queuedData.JsonTime); @@ -89,8 +88,8 @@ private void CreateAndOpenBpmDialogue(bool isInitialPlacement) .WithInitialValue("Mapper", "bpm.dialogue.invalidnumber"); } - var lastBpm = objectContainerCollection.FindLastBpm(SongBpmTime, false)?.Bpm ?? - BeatSaberSongContainer.Instance.Info.BeatsPerMinute; + var lastBpm = (float)BeatSaberSongContainer.Instance.Map.BpmAtSongBpmTime(SongBpmTime); + var bpmTextInput = createBpmEventDialogueBox .AddComponent() .WithLabel("Mapper", "bpm.dialogue.beatsperminute") diff --git a/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/BombPlacement.cs b/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/BombPlacement.cs index 32c729376..aa706e4b6 100644 --- a/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/BombPlacement.cs +++ b/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/BombPlacement.cs @@ -88,7 +88,7 @@ public override void OnPhysicsRaycast(Intersections.IntersectionHit hit, Vector3 public override void TransferQueuedToDraggedObject(ref BaseNote dragged, BaseNote queued) { - dragged.SetTimes(queued.JsonTime, queued.SongBpmTime); + dragged.SetTimes(queued.JsonTime); dragged.PosX = queued.PosX; dragged.PosY = queued.PosY; dragged.CustomCoordinate = queued.CustomCoordinate; diff --git a/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/ChainIndicatorPlacement.cs b/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/ChainIndicatorPlacement.cs index 5542cc8ac..0501f9181 100644 --- a/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/ChainIndicatorPlacement.cs +++ b/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/ChainIndicatorPlacement.cs @@ -90,7 +90,7 @@ public override void TransferQueuedToDraggedObject(ref BaseChain dragged, BaseCh { if (DraggedObjectContainer.IndicatorType == IndicatorType.Head) { - dragged.SetTimes(queued.JsonTime, queued.SongBpmTime); + dragged.SetTimes(queued.JsonTime); dragged.PosX = queued.PosX; dragged.PosY = queued.PosY; dragged.CutDirection = queued.CutDirection; @@ -99,7 +99,7 @@ public override void TransferQueuedToDraggedObject(ref BaseChain dragged, BaseCh if (DraggedObjectContainer.IndicatorType == IndicatorType.Tail) { - dragged.SetTailTimes(queued.JsonTime, queued.SongBpmTime); + dragged.SetTailTimes(queued.JsonTime); dragged.TailPosX = queued.PosX; dragged.TailPosY = queued.PosY; dragged.CustomTailCoordinate = queued.CustomTailCoordinate; diff --git a/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/EventPlacement.cs b/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/EventPlacement.cs index d8a652e74..b66d06cc4 100644 --- a/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/EventPlacement.cs +++ b/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/EventPlacement.cs @@ -263,7 +263,7 @@ internal override void ApplyToMap() public override void TransferQueuedToDraggedObject(ref BaseEvent dragged, BaseEvent queued) { - dragged.SetTimes(queued.JsonTime, queued.SongBpmTime); + dragged.SetTimes(queued.JsonTime); dragged.Type = queued.Type; // Instead of copying the whole custom data, only copy prop ID if (dragged.CustomData != null && queued.CustomData != null) diff --git a/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/NJSEventPlacement.cs b/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/NJSEventPlacement.cs index e58ba721d..9717cc278 100644 --- a/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/NJSEventPlacement.cs +++ b/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/NJSEventPlacement.cs @@ -16,7 +16,7 @@ public override void OnPhysicsRaycast(Intersections.IntersectionHit _, Vector3 _ new Vector3(0.5f, 0.5f, instantiatedContainer.transform.localPosition.z); public override void TransferQueuedToDraggedObject(ref BaseNJSEvent dragged, BaseNJSEvent queued) => - dragged.SetTimes(queued.JsonTime, queued.SongBpmTime); + dragged.SetTimes(queued.JsonTime); internal override void ApplyToMap() => CreateAndOpenNJSDialogue(isInitialPlacement: true); diff --git a/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/NotePlacement.cs b/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/NotePlacement.cs index c11888601..d7ae9cd16 100644 --- a/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/NotePlacement.cs +++ b/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/NotePlacement.cs @@ -235,7 +235,7 @@ private void UpdateAppearance() public override void TransferQueuedToDraggedObject(ref BaseNote dragged, BaseNote queued) { - dragged.SetTimes(queued.JsonTime, queued.SongBpmTime); + dragged.SetTimes(queued.JsonTime); dragged.PosX = queued.PosX; dragged.PosY = queued.PosY; dragged.CutDirection = queued.CutDirection; @@ -255,7 +255,7 @@ private void TransferQueuedToAttachedDraggedSliders(BaseNote queued) var epsilon = BeatmapObjectContainerCollection.Epsilon; foreach (var baseSlider in DraggedAttachedSliderDatas[IndicatorType.Head]) { - baseSlider.SetTimes(queued.JsonTime, queued.SongBpmTime); + baseSlider.SetTimes(queued.JsonTime); baseSlider.PosX = queued.PosX; baseSlider.PosY = queued.PosY; baseSlider.CutDirection = queued.CutDirection; @@ -264,7 +264,7 @@ private void TransferQueuedToAttachedDraggedSliders(BaseNote queued) foreach (var baseSlider in DraggedAttachedSliderDatas[IndicatorType.Tail]) { - baseSlider.SetTailTimes(queued.JsonTime, queued.SongBpmTime); + baseSlider.SetTailTimes(queued.JsonTime); baseSlider.TailPosX = queued.PosX; baseSlider.TailPosY = queued.PosY; baseSlider.CustomTailCoordinate = queued.CustomCoordinate; diff --git a/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/ObstaclePlacement.cs b/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/ObstaclePlacement.cs index 7d76a952b..2b488af9a 100644 --- a/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/ObstaclePlacement.cs +++ b/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/ObstaclePlacement.cs @@ -57,7 +57,7 @@ public override void OnPhysicsRaycast(Intersections.IntersectionHit hit, Vector3 instantiatedContainer.ObstacleData.Duration = RoundedJsonTime - startJsonTime; obstacleAppearanceSo.SetObstacleAppearance(instantiatedContainer); var roundedHit = ParentTrack.InverseTransformPoint(hit.Point); - var songBpmDuration = BpmChangeGridContainer.JsonTimeToSongBpmTime(RoundedJsonTime) - startSongBpmTime; + var songBpmDuration = (float)BeatSaberSongContainer.Instance.Map.JsonTimeToSongBpmTime(RoundedJsonTime) - startSongBpmTime; // Check if Chroma Color notes button is active and apply _color queuedData.CustomColor = (CanPlaceChromaObjects && dropdown.Visible) @@ -214,15 +214,14 @@ internal override void ApplyToMap() if (IsPlacing) { IsPlacing = false; - queuedData.SetTimes(startJsonTime, startSongBpmTime); + queuedData.SetTimes(startJsonTime); var endSongBpmTime = startSongBpmTime + (instantiatedContainer.GetScale().z / EditorScaleController.EditorScale); - var endJsonTime = BpmChangeGridContainer.SongBpmTimeToJsonTime(endSongBpmTime); if (endSongBpmTime - startSongBpmTime < SmallestRankableWallDuration) { endSongBpmTime = startSongBpmTime + SmallestRankableWallDuration; - endJsonTime = BpmChangeGridContainer.SongBpmTimeToJsonTime(endSongBpmTime); + var endJsonTime = (float)BeatSaberSongContainer.Instance.Map.SongBpmTimeToJsonTime(endSongBpmTime); queuedData.Duration = endJsonTime - startJsonTime; } @@ -246,7 +245,7 @@ internal override void ApplyToMap() public override void TransferQueuedToDraggedObject(ref BaseObstacle dragged, BaseObstacle queued) { - dragged.SetTimes(queued.JsonTime, queued.SongBpmTime); + dragged.SetTimes(queued.JsonTime); dragged.PosX = queued.PosX; dragged.CustomCoordinate = queued.CustomCoordinate; } diff --git a/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/PlacementController.cs b/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/PlacementController.cs index ed483c479..25f3e6daa 100644 --- a/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/PlacementController.cs +++ b/Assets/__Scripts/MapEditor/Mapping/PlacementControllers/PlacementController.cs @@ -77,7 +77,7 @@ internal float RoundedJsonTime get => roundedJsonTime; set { - SongBpmTime = BpmChangeGridContainer.JsonTimeToSongBpmTime(value); + SongBpmTime = (float)BeatSaberSongContainer.Instance.Map.JsonTimeToSongBpmTime(value); roundedJsonTime = value; } } @@ -184,7 +184,7 @@ protected virtual void Update() Mathf.Round(Mathf.Clamp(y, farBottomPoint, farTopPoint - 1)), roundedHit.z); - queuedData.SetTimes(roundedJsonTime, SongBpmTime); + queuedData.SetTimes(roundedJsonTime); OnPhysicsRaycast(hit, roundedHit); if ((IsDraggingObject || IsDraggingObjectAtTime) && queuedData != null) { @@ -348,7 +348,7 @@ protected void CalculateTimes(Intersections.IntersectionHit hit, out Vector3 rou EditorScaleController.EditorScale; } - var hitPointJsonTime = BpmChangeGridContainer.SongBpmTimeToJsonTime(realTime); + var hitPointJsonTime = (float)BeatSaberSongContainer.Instance.Map.SongBpmTimeToJsonTime(realTime); roundedJsonTime = (float)Math.Round((hitPointJsonTime - offsetJsonTime) / snap, MidpointRounding.AwayFromZero) * snap; if (!Atsc.IsPlaying) roundedJsonTime += offsetJsonTime; diff --git a/Assets/__Scripts/MapEditor/UI/Bookmarks/BookmarkManager.cs b/Assets/__Scripts/MapEditor/UI/Bookmarks/BookmarkManager.cs index 78dbb03e1..4f30a538f 100644 --- a/Assets/__Scripts/MapEditor/UI/Bookmarks/BookmarkManager.cs +++ b/Assets/__Scripts/MapEditor/UI/Bookmarks/BookmarkManager.cs @@ -108,18 +108,19 @@ private List InstantiateBookmarkContainers() // while the objects did. This ensures maps in this period display bookmarks in the correct place. private void ConvertBookmarkTimesFromOldDevVersions() { - var bookmarksUseOfficialBpmEventsKey = BeatSaberSongContainer.Instance.Map.BookmarksUseOfficialBpmEventsKey; - var bookmarksNeedConversion = !BeatSaberSongContainer.Instance.Map.CustomData.HasKey(bookmarksUseOfficialBpmEventsKey) - || !BeatSaberSongContainer.Instance.Map.CustomData[bookmarksUseOfficialBpmEventsKey].IsBoolean - || !BeatSaberSongContainer.Instance.Map.CustomData[bookmarksUseOfficialBpmEventsKey].AsBool; + var map = BeatSaberSongContainer.Instance.Map; + var bookmarksUseOfficialBpmEventsKey = map.BookmarksUseOfficialBpmEventsKey; + var bookmarksNeedConversion = !map.CustomData.HasKey(bookmarksUseOfficialBpmEventsKey) + || !map.CustomData[bookmarksUseOfficialBpmEventsKey].IsBoolean + || !map.CustomData[bookmarksUseOfficialBpmEventsKey].AsBool; - bookmarksNeedConversion &= BeatSaberSongContainer.Instance.Map.MajorVersion != 4; + bookmarksNeedConversion &= map.MajorVersion != 4; - foreach (var bookmark in BeatSaberSongContainer.Instance.Map.Bookmarks) + foreach (var bookmark in map.Bookmarks) { if (bookmarksNeedConversion) { - bookmark.SongBpmTime = bookmark.JsonTime; + bookmark.JsonTime = (float)map.SongBpmTimeToJsonTime(bookmark.JsonTime); } else { diff --git a/Assets/__Scripts/MapEditor/UI/Counters+/CountersPlusController.cs b/Assets/__Scripts/MapEditor/UI/Counters+/CountersPlusController.cs index 18c6e6e38..c4cf91e53 100644 --- a/Assets/__Scripts/MapEditor/UI/Counters+/CountersPlusController.cs +++ b/Assets/__Scripts/MapEditor/UI/Counters+/CountersPlusController.cs @@ -86,8 +86,7 @@ public int BombCount public float OverallSPS => swingsPerSecond.Total.Overall; - public float CurrentBPM - => bpm.FindLastBpm(atsc.CurrentSongBpmTime)?.Bpm ?? BeatSaberSongContainer.Instance.Info.BeatsPerMinute; + public float CurrentBPM => (float)BeatSaberSongContainer.Instance.Map.BpmAtJsonTime(atsc.CurrentJsonTime); public float RedBlueRatio {