diff --git a/Assets/Scenes/Game.unity b/Assets/Scenes/Game.unity index f06805dd9..721b30be1 100644 --- a/Assets/Scenes/Game.unity +++ b/Assets/Scenes/Game.unity @@ -7690,6 +7690,7 @@ GameObject: m_Component: - component: {fileID: 462579777} - component: {fileID: 462579775} + - component: {fileID: 462579778} - component: {fileID: 462579776} m_Layer: 0 m_Name: Conductor @@ -7721,7 +7722,6 @@ MonoBehaviour: completedLoops: 0 loopPositionInBeats: 0 loopPositionInAnalog: 0 - beatThreshold: 0.125 --- !u!82 &462579776 AudioSource: m_ObjectHideFlags: 0 @@ -7832,6 +7832,22 @@ Transform: m_Father: {fileID: 0} m_RootOrder: 3 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &462579778 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 462579774} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: b2e012d929a258243a865112df346c34, type: 3} + m_Name: + m_EditorClassIdentifier: + currentSmoothedDSPTime: 0 + dspTime: 0 + audioTime: 0 + latencyAdjustment: 0 --- !u!1 &490794386 GameObject: m_ObjectHideFlags: 0 @@ -24116,7 +24132,7 @@ Transform: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1586095856} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalPosition: {x: 0, y: 0.66, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_Children: - {fileID: 255259936} diff --git a/Assets/Scripts/AudioDspTimeKeeper.cs b/Assets/Scripts/AudioDspTimeKeeper.cs new file mode 100644 index 000000000..0b8aea037 --- /dev/null +++ b/Assets/Scripts/AudioDspTimeKeeper.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace RhythmHeavenMania +{ + public class AudioDspTimeKeeper : MonoBehaviour + { + [SerializeField] private List xValuesL = new List(); + [SerializeField] private List yValuesL = new List(); + + private double coeff1, coeff2; + + private double audioDspStartTime; + + private AudioSource audioSource; + + public double currentSmoothedDSPTime; + + public double dspTime; + public float audioTime; + + private double musicDrift; + + private Conductor conductor; + + public float latencyAdjustment; + + public void Play() + { + audioSource.PlayScheduled(audioDspStartTime); + audioDspStartTime = AudioSettings.dspTime; + } + + private void Start() + { + conductor = GetComponent(); + audioSource = conductor.musicSource; + } + + private void Update() + { + if (!audioSource.isPlaying) return; + + float currentGameTime = Time.realtimeSinceStartup; + double currentDspTime = AudioSettings.dspTime; + + // Update our linear regression model by adding another data point. + UpdateLinearRegression(currentGameTime, currentDspTime); + CheckForDrift(); + + dspTime = GetCurrentTimeInSong(); + audioTime = audioSource.time; + } + + public double SmoothedDSPTime() + { + double result = Time.unscaledTimeAsDouble * coeff1 + coeff2; + if (result > currentSmoothedDSPTime) + { + currentSmoothedDSPTime = result; + } + return currentSmoothedDSPTime; + } + + public double GetCurrentTimeInSong() + { + return this.SmoothedDSPTime() - audioDspStartTime - latencyAdjustment; + } + + private void CheckForDrift() + { + double timeFromDSP = this.SmoothedDSPTime() - audioDspStartTime; + double timeFromAudioSource = audioSource.timeSamples / (float)audioSource.clip.frequency; + + double drift = timeFromDSP - timeFromAudioSource; + musicDrift = drift; + + if (Mathf.Abs((float)drift) > 0.05) + { + Debug.LogWarningFormat("Music drift of {0} detected, resyncing!", musicDrift); + audioDspStartTime += musicDrift; + } + } + + private void UpdateLinearRegression(float currentGameTime, double currentDspTime) + { + if (xValuesL.Count > 3000) + { + xValuesL.RemoveRange(0, 2000); + yValuesL.RemoveRange(0, 2000); + } + + xValuesL.Add((double)currentGameTime); + var xVals = xValuesL.ToArray(); + + yValuesL.Add((double)currentDspTime); + var yVals = yValuesL.ToArray(); + + if (xVals.Length != yVals.Length) + { + throw new Exception("Input values should be with the same length."); + } + + double sumOfX = 0; + double sumOfY = 0; + double sumOfXSq = 0; + double sumOfYSq = 0; + double sumCodeviates = 0; + + for (var i = 0; i < xVals.Length; i++) + { + var x = xVals[i]; + var y = yVals[i]; + sumCodeviates += x * y; + sumOfX += x; + sumOfY += y; + sumOfXSq += x * x; + sumOfYSq += y * y; + } + + var count = xVals.Length; + var ssX = sumOfXSq - ((sumOfX * sumOfX) / count); + var ssY = sumOfYSq - ((sumOfY * sumOfY) / count); + + var rNumerator = (count * sumCodeviates) - (sumOfX * sumOfY); + var rDenom = (count * sumOfXSq - (sumOfX * sumOfX)) * (count * sumOfYSq - (sumOfY * sumOfY)); + var sCo = sumCodeviates - ((sumOfX * sumOfY) / count); + + var meanX = sumOfX / count; + var meanY = sumOfY / count; + var dblR = rNumerator / Math.Sqrt(rDenom); + + // coeff1 = dblR * dblR; + coeff2 = meanY - ((sCo / ssX) * meanX); + coeff1 = sCo / ssX; + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/AudioDspTimeKeeper.cs.meta b/Assets/Scripts/AudioDspTimeKeeper.cs.meta new file mode 100644 index 000000000..84874a3f3 --- /dev/null +++ b/Assets/Scripts/AudioDspTimeKeeper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b2e012d929a258243a865112df346c34 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Conductor.cs b/Assets/Scripts/Conductor.cs index 76e241efc..30c9a59a3 100644 --- a/Assets/Scripts/Conductor.cs +++ b/Assets/Scripts/Conductor.cs @@ -1,11 +1,10 @@ using System.Collections; using System.Collections.Generic; using UnityEngine; +using UnityEngine.Audio; using Starpelly; -// I CANNOT STRESS THIS ENOUGH, SET "Project Settings/Audio/DSP Buffer Size" to "Best latency" or else AudioSource.time WILL NOT update every frame. - namespace RhythmHeavenMania { [RequireComponent(typeof(AudioSource))] @@ -48,14 +47,7 @@ namespace RhythmHeavenMania //Conductor instance public static Conductor instance; - //Pause times - // private int pauseTime = 0; - - public float beatThreshold; - - private float lastTime; - private float lastMst_F; - private int framesSinceLastSame; + private AudioDspTimeKeeper timeKeeper; void Awake() { @@ -67,11 +59,13 @@ namespace RhythmHeavenMania //Load the AudioSource attached to the Conductor GameObject musicSource = GetComponent(); + timeKeeper = GetComponent(); + //Calculate the number of seconds in each beat secPerBeat = 60f / songBpm; //Record the time when the music starts - dspSongTime = (float)musicSource.time; + // dspSongTime = (float)musicSource.time; //Start the music // musicSource.Play(); @@ -79,62 +73,22 @@ namespace RhythmHeavenMania public void Play(float startBeat) { - musicSource.Play(); - + timeKeeper.Play(); } public void Update() { - // Conductor.instance.musicSource.pitch = Time.timeScale; - - /*if (Input.GetKeyDown(KeyCode.Space)) - { - pauseTime++; - if (pauseTime == 1) - musicSource.Pause(); - else if (pauseTime > 1) { musicSource.UnPause(); pauseTime = 0; } - }*/ - - float mst = musicSource.timeSamples / (float)musicSource.clip.frequency; - float mst_f = mst + 0; - - if (mst == lastTime && musicSource.isPlaying) - { - framesSinceLastSame++; - - mst_f = mst_f + (Time.deltaTime * framesSinceLastSame) * musicSource.pitch; - - if (mst_f <= lastMst_F) - { - // mst_f = lastMst_F; - float b = lastMst_F + (Time.deltaTime) * musicSource.pitch; - - mst_f = b; - // print(b); - // print(mst_f + " " + b + " " + lastMst_F); - } - else if (mst_f < lastTime) - { - Debug.LogError("What the fuck."); - } - - // print($"{lastMst_F}, {mst_f}"); - } - else - { - framesSinceLastSame = 0; - } - - lastTime = mst; - lastMst_F = mst_f; + if (!musicSource.isPlaying) return; //determine how many seconds since the song started - songPosition = (float)(mst_f - dspSongTime - firstBeatOffset); + // songPosition = (float)(timeKeeper.dspTime - dspSongTime - firstBeatOffset); + songPosition = (float)timeKeeper.GetCurrentTimeInSong(); + print(songPosition); //determine how many beats since the song started songPositionInBeats = songPosition / secPerBeat; // print($"{mst_f}(AudioSource.time), {Time.frameCount}(Time.fasrameCount)"); - // print($"{musicSource.time}(0), {mst_f}"); + // print($"{musicSource.time}(0), {songPosition}"); //calculate the loop position @@ -156,68 +110,5 @@ namespace RhythmHeavenMania this.songBpm = bpm; secPerBeat = 60f / songBpm; } - - public bool InThreshold(float beat) - { - //Check if the beat sent falls within beatThreshold - //Written to handle the looping - if (beat <= beatThreshold + 1) - { - //Debug.Log("Case 1, beat is close to 1"); - if (loopPositionInBeats > beat + beatThreshold) - { - if (loopPositionInBeats >= (beat + songPositionInBeats - 1) + beatThreshold) - { - //Debug.Log("LoopPos just below loop point"); - return true; - } - else - { - //Debug.Log("LoopPos not within beat threshold"); - } - } - else - { - //Debug.Log("Case 1, loopPos between loop point and beat threshold"); - } - } - else if (beat < (songPositionInBeats + 1 - beatThreshold)) - { - //Debug.Log("Case 2, beat is far from loop point."); - if (loopPositionInBeats >= beat - beatThreshold && loopPositionInBeats <= beat + beatThreshold) - { - //Debug.Log("LoopPos within threshold"); - return true; - } - } - else if (beat >= (songPositionInBeats + 1 - beatThreshold)) - { - //Debug.Log("Case 3, beat is close to loop point"); - if (loopPositionInBeats < beat) - { - if (loopPositionInBeats >= beat - beatThreshold) - { - //Debug.Log("LoopPos just below beat"); - return true; - } - else if (loopPositionInBeats < (beat - songPositionInBeats + 1) - beatThreshold) - { - //Debug.Log("LoopPos just above loop point"); - return true; - } - } - else - { - //Debug.Log("LoopPos just above beat"); - return true; - } - - } - else - { - Debug.LogError("Strange Case. Where is this beat? This should never happen"); - } - return false; - } } } \ No newline at end of file diff --git a/Assets/Scripts/GameManager.cs b/Assets/Scripts/GameManager.cs index c70df4d76..1f826fc94 100644 --- a/Assets/Scripts/GameManager.cs +++ b/Assets/Scripts/GameManager.cs @@ -68,7 +68,7 @@ namespace RhythmHeavenMania private IEnumerator Begin() { yield return new WaitForSeconds(startOffset); - Conductor.instance.musicSource.Play(); + Conductor.instance.Play(0); } private void Update()