From d3a528a43a13629a9a431235898ac5d2183b02da Mon Sep 17 00:00:00 2001 From: minenice55 <43734252+minenice55@users.noreply.github.com> Date: Mon, 6 Jun 2022 12:54:57 -0400 Subject: [PATCH] Tempo changes restored (#92) * Prepwork for seeking + tempo change fixes TODO: make playing after seeking function (I'll need help with the offset stuff so if anyone can push to this branch please do) * functions to get the beat from a song position will need more testing but I think it works well enough to get into prod thanks @wooningcharithri#7419 for the psuedo-code --- Assets/Scripts/Conductor.cs | 94 ++++++++++++++++++- Assets/Scripts/GameManager.cs | 19 +++- .../LevelEditor/Timeline/TempoTimeline.cs | 6 +- .../Scripts/LevelEditor/Timeline/Timeline.cs | 2 +- 4 files changed, 108 insertions(+), 13 deletions(-) diff --git a/Assets/Scripts/Conductor.cs b/Assets/Scripts/Conductor.cs index 60b0efe46..93b760df7 100644 --- a/Assets/Scripts/Conductor.cs +++ b/Assets/Scripts/Conductor.cs @@ -85,6 +85,8 @@ namespace HeavenStudio bool negativeOffset = firstBeatOffset < 0f; bool negativeStartTime = false; + // Debug.Log("starting playback @ beat " + beat + ", offset is " + firstBeatOffset); + var startPos = GetSongPosFromBeat(beat); if (negativeOffset) { @@ -99,8 +101,10 @@ namespace HeavenStudio else time = startPos; } - - songPosBeat = time / secPerBeat; + + //TODO: make this take into account past tempo changes + songPosBeat = GetBeatFromSongPos(time - firstBeatOffset); + // Debug.Log("corrected starting playback @ beat " + songPosBeat); isPlaying = true; isPaused = false; @@ -232,11 +236,93 @@ namespace HeavenStudio return GetBeatFromPosition(position, targetBeat - margin, margin); } + private List GetSortedTempoChanges(Beatmap chart) + { + //iterate over all tempo changes, adding to counter + List tempoChanges = chart.tempoChanges; + tempoChanges.Sort((x, y) => x.beat.CompareTo(y.beat)); //sorts all tempo changes by ascending time (GameManager already does this but juste en cas...) + return tempoChanges; + } + public float GetSongPosFromBeat(float beat) { - return secPerBeat * beat; + Beatmap chart = GameManager.instance.Beatmap; + SetBpm(chart.bpm); + + //initial counter + float counter = 0f; + + //time of last tempo change, to know how much to add to counter + float lastTempoChangeBeat = 0f; + + //iterate over all tempo changes, adding to counter + List tempoChanges = GetSortedTempoChanges(chart); + foreach (var t in tempoChanges) + { + if (t.beat > beat) + { + // this tempo change is past our requested time, abort + break; + } + // Debug.Log("tempo change at " + t.beat); + + counter += (t.beat - lastTempoChangeBeat) * secPerBeat; + // Debug.Log("counter is now " + counter); + + // now update to new bpm + SetBpm(t.tempo); + lastTempoChangeBeat = t.beat; + } + + //passed all past tempo changes, now extrapolate from last tempo change until requested position + counter += (beat - lastTempoChangeBeat) * secPerBeat; + + // Debug.Log("GetSongPosFromBeat returning " + counter); + return counter; } + //thank you @wooningcharithri#7419 for the psuedo-code + private float BeatsToSecs(float beats, float bpm) + { + // Debug.Log("BeatsToSecs returning " + beats / bpm * 60); + return beats / bpm * 60f; + } + private float SecsToBeats(float s, float bpm) + { + // Debug.Log("SecsToBeats returning " + s / 60f / bpm); + return s / 60f * bpm; + } + + public float GetBeatFromSongPos(float seconds) + { + // Debug.Log("Getting beat of seconds " + seconds); + Beatmap chart = GameManager.instance.Beatmap; + float lastTempoChangeBeat = 0f; + float lastBpm = chart.bpm; + float counterSeconds = -firstBeatOffset; + + List tempoChanges = GetSortedTempoChanges(chart); + foreach (var t in tempoChanges) + { + float beatToNext = t.beat - lastTempoChangeBeat; + float secToNext = BeatsToSecs(beatToNext, lastBpm); + float nextSecs = counterSeconds + secToNext; + + // Debug.Log("nextSecs is " + nextSecs + ", seconds " + seconds); + if (nextSecs >= seconds) + break; + + lastTempoChangeBeat = t.beat; + lastBpm = t.tempo; + counterSeconds = nextSecs; + } + + // Debug.Log("lastTempoChangeBeat is " + lastTempoChangeBeat + ", counterSeconds is " + counterSeconds); + + return lastTempoChangeBeat + SecsToBeats(seconds - counterSeconds, lastBpm); + } + // + // convert real seconds to beats public float GetRestFromRealTime(float seconds) { @@ -257,7 +343,7 @@ namespace HeavenStudio public float SongLengthInBeats() { if (!musicSource.clip) return 0; - return musicSource.clip.length / secPerBeat; + return GetBeatFromSongPos(musicSource.clip.length); } public bool SongPosLessThanClipLength(float t) diff --git a/Assets/Scripts/GameManager.cs b/Assets/Scripts/GameManager.cs index 1263f070f..92f1a83d6 100644 --- a/Assets/Scripts/GameManager.cs +++ b/Assets/Scripts/GameManager.cs @@ -151,7 +151,7 @@ namespace HeavenStudio // LateUpdate works a bit better(?) but causes some bugs (like issues with bop animations). private void Update() { - if (Beatmap.entities.Count < 1) + if (BeatmapEntities() < 1) //bruh really you forgot to ckeck tempo changes return; if (!Conductor.instance.isPlaying) return; @@ -197,9 +197,11 @@ namespace HeavenStudio if (currentTempoEvent < Beatmap.tempoChanges.Count && currentTempoEvent >= 0) { + // Debug.Log("Checking Tempo Change at " + tempoChanges[currentTempoEvent] + ", current beat " + Conductor.instance.songPositionInBeats); if (Conductor.instance.songPositionInBeats >= tempoChanges[currentTempoEvent]) { - Conductor.instance.songBpm = Beatmap.tempoChanges[currentTempoEvent].tempo; + // Debug.Log("Tempo Change at " + Conductor.instance.songPositionInBeats + " of bpm " + Beatmap.tempoChanges[currentTempoEvent].tempo); + Conductor.instance.SetBpm(Beatmap.tempoChanges[currentTempoEvent].tempo); Conductor.instance.timeSinceLastTempoChange = Time.time; currentTempoEvent++; } @@ -325,9 +327,20 @@ namespace HeavenStudio if (Beatmap.tempoChanges.Count > 0) { + currentTempoEvent = 0; List tempoChanges = Beatmap.tempoChanges.Select(c => c.beat).ToList(); - currentTempoEvent = tempoChanges.IndexOf(Mathp.GetClosestInList(tempoChanges, beat)); + //for tempo changes, just go over all of em until the last one we pass + for (int t = 0; t < tempoChanges.Count; t++) + { + // Debug.Log("checking tempo event " + t + " against beat " + beat + "( tc beat " + tempoChanges[t] + ")"); + if (tempoChanges[t] > beat) + { + break; + } + currentTempoEvent = t; + } + // Debug.Log("currentTempoEvent is now " + currentTempoEvent); } } diff --git a/Assets/Scripts/LevelEditor/Timeline/TempoTimeline.cs b/Assets/Scripts/LevelEditor/Timeline/TempoTimeline.cs index 0da76709d..cbd67a541 100644 --- a/Assets/Scripts/LevelEditor/Timeline/TempoTimeline.cs +++ b/Assets/Scripts/LevelEditor/Timeline/TempoTimeline.cs @@ -129,11 +129,7 @@ namespace HeavenStudio.Editor.Track } private void AddTempoChange(bool create, Beatmap.TempoChange tempoChange_ = null) - { - // TEMP: DISABLED UNTIL CRITICAL FIXES - if (create) - return; - + { GameObject tempoChange = Instantiate(RefTempoChange.gameObject, this.transform); tempoChange.transform.GetChild(0).GetComponent().color = EditorTheme.theme.properties.TempoLayerCol.Hex2RGB(); diff --git a/Assets/Scripts/LevelEditor/Timeline/Timeline.cs b/Assets/Scripts/LevelEditor/Timeline/Timeline.cs index fbb8ab236..22360ed60 100644 --- a/Assets/Scripts/LevelEditor/Timeline/Timeline.cs +++ b/Assets/Scripts/LevelEditor/Timeline/Timeline.cs @@ -296,7 +296,7 @@ namespace HeavenStudio.Editor.Track if (movingPlayback) { RectTransformUtility.ScreenPointToLocalPointInRectangle(TimelineContent, Input.mousePosition, Editor.instance.EditorCamera, out lastMousePos); - TimelineSlider.localPosition = new Vector3(Mathf.Clamp(Mathp.Round2Nearest(lastMousePos.x + 0.12f, Timeline.SnapInterval()), 0, Mathf.Infinity), TimelineSlider.transform.localPosition.y); + TimelineSlider.localPosition = new Vector3(Mathf.Max(Mathp.Round2Nearest(lastMousePos.x + 0.12f, Timeline.SnapInterval()), 0), TimelineSlider.transform.localPosition.y); if (TimelineSlider.localPosition.x != lastBeatPos) Conductor.instance.SetBeat(TimelineSlider.transform.localPosition.x);