mirror of
https://github.com/RHeavenStudio/HeavenStudio.git
synced 2025-06-12 21:37:40 +02:00
Timekeeping Improvements and Small Optimizations (#544)
* make BeatActions coroutines instead of componentrs * pooled scheduled sounds implement S' entity seek * remove debug prints from last two changes * implement absolute time tracking implement DSP time resyncing * optimize GameManager * update TMPro * update IDE packages * fix dsp sync making the drift worse * fix issue with the JSL dll * relocate debug print * make scheduled pitch setter functional * any cpu
This commit is contained in:
@ -1,16 +1,25 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
using Starpelly;
|
||||
using Jukebox;
|
||||
using Jukebox.Legacy;
|
||||
using HeavenStudio.Util;
|
||||
|
||||
namespace HeavenStudio
|
||||
{
|
||||
// [RequireComponent(typeof(AudioSource))]
|
||||
public class Conductor : MonoBehaviour
|
||||
{
|
||||
public struct AddedPitchChange
|
||||
{
|
||||
public double time;
|
||||
public float pitch;
|
||||
}
|
||||
|
||||
public List<AddedPitchChange> addedPitchChanges = new List<AddedPitchChange>();
|
||||
|
||||
// Song beats per minute
|
||||
// This is determined by the song you're trying to sync up to
|
||||
public float songBpm;
|
||||
@ -25,18 +34,19 @@ namespace HeavenStudio
|
||||
|
||||
// Current song position, in seconds
|
||||
private double songPos; // for Conductor use only
|
||||
public float songPosition => (float) songPos;
|
||||
public float songPosition => (float)songPos;
|
||||
public double songPositionAsDouble => songPos;
|
||||
|
||||
// Current song position, in beats
|
||||
public double songPosBeat; // for Conductor use only
|
||||
public float songPositionInBeats => (float) songPosBeat;
|
||||
public float songPositionInBeats => (float)songPosBeat;
|
||||
public double songPositionInBeatsAsDouble => songPosBeat;
|
||||
|
||||
// Current time of the song
|
||||
private double time;
|
||||
double dspTime, lastDspTime;
|
||||
double absTime, lastAbsTime;
|
||||
double dspTime;
|
||||
double absTime, absTimeAdjust;
|
||||
double dspMargin = 128 / 44100.0;
|
||||
|
||||
// the dspTime we started at
|
||||
private double dspStart;
|
||||
@ -45,6 +55,7 @@ namespace HeavenStudio
|
||||
DateTime startTime;
|
||||
|
||||
//the beat we started at
|
||||
private double startPos;
|
||||
private double startBeat;
|
||||
public double startBeatAsDouble => startBeat;
|
||||
|
||||
@ -59,7 +70,7 @@ namespace HeavenStudio
|
||||
|
||||
// Conductor is currently playing song
|
||||
public bool isPlaying;
|
||||
|
||||
|
||||
// Conductor is currently paused, but not fully stopped
|
||||
public bool isPaused;
|
||||
|
||||
@ -79,16 +90,40 @@ namespace HeavenStudio
|
||||
|
||||
public void SetTimelinePitch(float pitch)
|
||||
{
|
||||
if (pitch != 0 && pitch * minigamePitch != SongPitch)
|
||||
{
|
||||
Debug.Log("added pitch change " + pitch * minigamePitch + " at" + absTime);
|
||||
addedPitchChanges.Add(new AddedPitchChange { time = absTime, pitch = pitch * minigamePitch });
|
||||
}
|
||||
|
||||
timelinePitch = pitch;
|
||||
musicSource.pitch = SongPitch;
|
||||
|
||||
}
|
||||
|
||||
public void SetMinigamePitch(float pitch)
|
||||
{
|
||||
if (pitch != 0 && pitch * timelinePitch != SongPitch)
|
||||
{
|
||||
Debug.Log("added pitch change " + pitch * timelinePitch + " at" + absTime);
|
||||
addedPitchChanges.Add(new AddedPitchChange { time = absTime, pitch = pitch * timelinePitch });
|
||||
}
|
||||
|
||||
minigamePitch = pitch;
|
||||
musicSource.pitch = SongPitch;
|
||||
}
|
||||
|
||||
public void SetMinigamePitch(float pitch, double beat)
|
||||
{
|
||||
BeatAction.New( this,
|
||||
new List<BeatAction.Action> {
|
||||
new BeatAction.Action(beat, delegate {
|
||||
SetMinigamePitch(pitch);
|
||||
}),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void Awake()
|
||||
{
|
||||
instance = this;
|
||||
@ -97,13 +132,16 @@ namespace HeavenStudio
|
||||
void Start()
|
||||
{
|
||||
musicSource.priority = 0;
|
||||
AudioConfiguration config = AudioSettings.GetConfiguration();
|
||||
dspMargin = 2 * (config.dspBufferSize / (double)config.sampleRate);
|
||||
addedPitchChanges.Clear();
|
||||
}
|
||||
|
||||
public void SetBeat(double beat)
|
||||
{
|
||||
var chart = GameManager.instance.Beatmap;
|
||||
double offset = chart.data.offset;
|
||||
double startPos = GetSongPosFromBeat(beat);
|
||||
startPos = GetSongPosFromBeat(beat);
|
||||
|
||||
double dspTime = AudioSettings.dspTime;
|
||||
|
||||
@ -113,19 +151,32 @@ namespace HeavenStudio
|
||||
SeekMusicToTime(startPos);
|
||||
|
||||
songPosBeat = GetBeatFromSongPos(time);
|
||||
|
||||
|
||||
GameManager.instance.SetCurrentEventToClosest(beat);
|
||||
}
|
||||
|
||||
public void Play(double beat)
|
||||
{
|
||||
if (isPlaying) return;
|
||||
|
||||
if (!isPaused)
|
||||
{
|
||||
AudioConfiguration config = AudioSettings.GetConfiguration();
|
||||
dspMargin = 2 * (config.dspBufferSize / (double)config.sampleRate);
|
||||
addedPitchChanges.Clear();
|
||||
addedPitchChanges.Add(new AddedPitchChange { time = 0, pitch = SongPitch });
|
||||
}
|
||||
|
||||
var chart = GameManager.instance.Beatmap;
|
||||
double offset = chart.data.offset;
|
||||
double dspTime = AudioSettings.dspTime;
|
||||
absTimeAdjust = 0;
|
||||
dspStart = dspTime;
|
||||
startTime = DateTime.Now;
|
||||
|
||||
GameManager.instance.SortEventsList();
|
||||
|
||||
double startPos = GetSongPosFromBeat(beat);
|
||||
startPos = GetSongPosFromBeat(beat);
|
||||
firstBeatOffset = offset;
|
||||
time = startPos;
|
||||
|
||||
@ -149,12 +200,8 @@ namespace HeavenStudio
|
||||
}
|
||||
|
||||
songPosBeat = GetBeatFromSongPos(time);
|
||||
startTime = DateTime.Now;
|
||||
lastAbsTime = 0;
|
||||
lastDspTime = AudioSettings.dspTime;
|
||||
dspStart = dspTime;
|
||||
startBeat = songPosBeat;
|
||||
|
||||
|
||||
isPlaying = true;
|
||||
isPaused = false;
|
||||
}
|
||||
@ -170,10 +217,15 @@ namespace HeavenStudio
|
||||
|
||||
public void Stop(double time)
|
||||
{
|
||||
if (absTimeAdjust != 0)
|
||||
{
|
||||
Debug.Log($"Last playthrough had a dsp (audio) drift of {absTimeAdjust}.\nConsider increasing audio buffer size if audio distortion was present.");
|
||||
}
|
||||
this.time = time;
|
||||
|
||||
songPos = time;
|
||||
songPosBeat = 0;
|
||||
absTimeAdjust = 0;
|
||||
|
||||
isPlaying = false;
|
||||
isPaused = false;
|
||||
@ -181,13 +233,48 @@ namespace HeavenStudio
|
||||
musicSource.Stop();
|
||||
}
|
||||
|
||||
void SeekMusicToTime(double startPos)
|
||||
/// <summary>
|
||||
/// stops playback of the audio without stopping beatkeeping
|
||||
/// </summary>
|
||||
public void StopOnlyAudio()
|
||||
{
|
||||
musicSource.Stop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// fades out the audio over a duration
|
||||
/// </summary>
|
||||
/// <param name="duration">duration of the fade</param>
|
||||
public void FadeOutAudio(float duration)
|
||||
{
|
||||
StartCoroutine(FadeOutAudioCoroutine(duration));
|
||||
}
|
||||
|
||||
IEnumerator FadeOutAudioCoroutine(float duration)
|
||||
{
|
||||
float startVolume = musicSource.volume;
|
||||
float endVolume = 0f;
|
||||
float startTime = Time.time;
|
||||
float endTime = startTime + duration;
|
||||
|
||||
while (Time.time < endTime)
|
||||
{
|
||||
float t = (Time.time - startTime) / duration;
|
||||
musicSource.volume = Mathf.Lerp(startVolume, endVolume, t);
|
||||
yield return null;
|
||||
}
|
||||
|
||||
musicSource.volume = endVolume;
|
||||
StopOnlyAudio();
|
||||
}
|
||||
|
||||
void SeekMusicToTime(double fStartPos)
|
||||
{
|
||||
double offset = GameManager.instance.Beatmap.data.offset;
|
||||
if (musicSource.clip != null && startPos < musicSource.clip.length - offset)
|
||||
if (musicSource.clip != null && fStartPos < musicSource.clip.length - offset)
|
||||
{
|
||||
// https://www.desmos.com/calculator/81ywfok6xk
|
||||
double musicStartDelay = -offset - startPos;
|
||||
double musicStartDelay = -offset - fStartPos;
|
||||
if (musicStartDelay > 0)
|
||||
{
|
||||
musicSource.timeSamples = 0;
|
||||
@ -195,24 +282,13 @@ namespace HeavenStudio
|
||||
else
|
||||
{
|
||||
int freq = musicSource.clip.frequency;
|
||||
int samples = (int)(freq * (startPos + offset));
|
||||
int samples = (int)(freq * (fStartPos + offset));
|
||||
|
||||
musicSource.timeSamples = samples;
|
||||
}
|
||||
}
|
||||
}
|
||||
double deltaTimeReal { get {
|
||||
double ret = absTime - lastAbsTime;
|
||||
lastAbsTime = absTime;
|
||||
return ret;
|
||||
}}
|
||||
|
||||
double deltaTimeDsp { get {
|
||||
double ret = dspTime - lastDspTime;
|
||||
lastDspTime = dspTime;
|
||||
return ret;
|
||||
}}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (isPlaying)
|
||||
@ -229,24 +305,54 @@ namespace HeavenStudio
|
||||
musicSource.UnPause();
|
||||
musicScheduledPitch = SongPitch;
|
||||
|
||||
musicScheduledTime = (AudioSettings.dspTime + (-GameManager.instance.Beatmap.data.offset - songPositionAsDouble)/(double)SongPitch);
|
||||
musicScheduledTime = (AudioSettings.dspTime + (-GameManager.instance.Beatmap.data.offset - songPositionAsDouble) / (double)SongPitch);
|
||||
musicSource.SetScheduledStartTime(musicScheduledTime);
|
||||
}
|
||||
}
|
||||
|
||||
absTime = (DateTime.Now - startTime).TotalSeconds;
|
||||
|
||||
//dspTime to sync with audio thread in case of drift
|
||||
dspTime = AudioSettings.dspTime - dspStart;
|
||||
double dt = deltaTimeReal;
|
||||
if (Math.Abs(absTime + absTimeAdjust - dspTime) > dspMargin)
|
||||
{
|
||||
int i = 0;
|
||||
while (Math.Abs(absTime + absTimeAdjust - dspTime) > dspMargin)
|
||||
{
|
||||
i++;
|
||||
absTimeAdjust = (dspTime - absTime + absTimeAdjust) * 0.5;
|
||||
if (i > 8) break;
|
||||
}
|
||||
}
|
||||
|
||||
//todo: dspTime to sync with audio thread in case of drift
|
||||
time = MapTimeToPitchChanges(absTime + absTimeAdjust);
|
||||
|
||||
time += dt * SongPitch;
|
||||
|
||||
songPos = time;
|
||||
songPos = startPos + time;
|
||||
songPosBeat = GetBeatFromSongPos(songPos);
|
||||
}
|
||||
}
|
||||
|
||||
double MapTimeToPitchChanges(double time)
|
||||
{
|
||||
double counter = 0;
|
||||
double lastChangeTime = 0;
|
||||
float pitch = addedPitchChanges[0].pitch;
|
||||
foreach (var pch in addedPitchChanges)
|
||||
{
|
||||
double changeTime = pch.time;
|
||||
if (changeTime > time)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
counter += (changeTime - lastChangeTime) * pitch;
|
||||
lastChangeTime = changeTime;
|
||||
pitch = pch.pitch;
|
||||
}
|
||||
|
||||
counter += (time - lastChangeTime) * pitch;
|
||||
return counter;
|
||||
}
|
||||
|
||||
public void LateUpdate()
|
||||
{
|
||||
@ -265,7 +371,7 @@ namespace HeavenStudio
|
||||
{
|
||||
if (metronomeSound != null)
|
||||
{
|
||||
metronomeSound.Delete();
|
||||
metronomeSound.Stop();
|
||||
metronomeSound = null;
|
||||
}
|
||||
}
|
||||
@ -352,53 +458,53 @@ namespace HeavenStudio
|
||||
break;
|
||||
}
|
||||
|
||||
counter += (t.beat - lastTempoChangeBeat) * 60/bpm;
|
||||
counter += (t.beat - lastTempoChangeBeat) * 60 / bpm;
|
||||
bpm = t["tempo"];
|
||||
lastTempoChangeBeat = t.beat;
|
||||
}
|
||||
|
||||
counter += (beat - lastTempoChangeBeat) * 60/bpm;
|
||||
counter += (beat - lastTempoChangeBeat) * 60 / bpm;
|
||||
|
||||
return counter;
|
||||
}
|
||||
|
||||
//thank you @wooningcharithri#7419 for the psuedo-code
|
||||
public double BeatsToSecs(double beats, float bpm)
|
||||
{
|
||||
return beats / bpm * 60f;
|
||||
}
|
||||
public double SecsToBeats(double s, float bpm)
|
||||
{
|
||||
return s / 60f * bpm;
|
||||
}
|
||||
public double BeatsToSecs(double beats, float bpm)
|
||||
{
|
||||
return beats / bpm * 60f;
|
||||
}
|
||||
public double SecsToBeats(double s, float bpm)
|
||||
{
|
||||
return s / 60f * bpm;
|
||||
}
|
||||
|
||||
public double GetBeatFromSongPos(double seconds)
|
||||
{
|
||||
double lastTempoChangeBeat = 0f;
|
||||
double counterSeconds = 0;
|
||||
float lastBpm = 120f;
|
||||
|
||||
foreach (RiqEntity t in GameManager.instance.Beatmap.TempoChanges)
|
||||
{
|
||||
double beatToNext = t.beat - lastTempoChangeBeat;
|
||||
double secToNext = BeatsToSecs(beatToNext, lastBpm);
|
||||
double nextSecs = counterSeconds + secToNext;
|
||||
public double GetBeatFromSongPos(double seconds)
|
||||
{
|
||||
double lastTempoChangeBeat = 0f;
|
||||
double counterSeconds = 0;
|
||||
float lastBpm = 120f;
|
||||
|
||||
if (nextSecs >= seconds)
|
||||
break;
|
||||
|
||||
lastTempoChangeBeat = t.beat;
|
||||
lastBpm = t["tempo"];
|
||||
counterSeconds = nextSecs;
|
||||
}
|
||||
return lastTempoChangeBeat + SecsToBeats(seconds - counterSeconds, lastBpm);
|
||||
foreach (RiqEntity t in GameManager.instance.Beatmap.TempoChanges)
|
||||
{
|
||||
double beatToNext = t.beat - lastTempoChangeBeat;
|
||||
double secToNext = BeatsToSecs(beatToNext, lastBpm);
|
||||
double nextSecs = counterSeconds + secToNext;
|
||||
|
||||
if (nextSecs >= seconds)
|
||||
break;
|
||||
|
||||
lastTempoChangeBeat = t.beat;
|
||||
lastBpm = t["tempo"];
|
||||
counterSeconds = nextSecs;
|
||||
}
|
||||
return lastTempoChangeBeat + SecsToBeats(seconds - counterSeconds, lastBpm);
|
||||
}
|
||||
//
|
||||
|
||||
// convert real seconds to beats
|
||||
public double GetRestFromRealTime(double seconds)
|
||||
{
|
||||
return seconds/pitchedSecPerBeat;
|
||||
return seconds / pitchedSecPerBeat;
|
||||
}
|
||||
|
||||
public void SetBpm(float bpm)
|
||||
|
Reference in New Issue
Block a user