mirror of
https://github.com/Outer-Wilds-New-Horizons/new-horizons.git
synced 2025-12-11 20:15:44 +01:00
148 lines
5.5 KiB
C#
148 lines
5.5 KiB
C#
using NewHorizons.Utility;
|
|
using NewHorizons.Utility.OWML;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEngine;
|
|
|
|
namespace NewHorizons.Components.EyeOfTheUniverse
|
|
{
|
|
public class EyeMusicController : MonoBehaviour
|
|
{
|
|
// Delay between game logic and audio to ensure audio system has time to schedule all loops for the same tick
|
|
const double TIME_BUFFER_WINDOW = 0.5;
|
|
|
|
private List<OWAudioSource> _loopSources = new();
|
|
private List<OWAudioSource> _finaleSources = new();
|
|
private bool _transitionToFinale;
|
|
private bool _isPlaying;
|
|
private double _segmentEndAudioTime;
|
|
private float _segmentEndGameTime;
|
|
public CosmicInflationController CosmicInflationController { get; private set; }
|
|
|
|
public void RegisterLoopSource(OWAudioSource src)
|
|
{
|
|
src.loop = false;
|
|
src.SetLocalVolume(1f);
|
|
src.Stop();
|
|
src.playOnAwake = false;
|
|
_loopSources.Add(src);
|
|
}
|
|
|
|
public void RegisterFinaleSource(OWAudioSource src)
|
|
{
|
|
src.loop = false;
|
|
src.SetLocalVolume(1f);
|
|
src.Stop();
|
|
src.playOnAwake = false;
|
|
_finaleSources.Add(src);
|
|
}
|
|
|
|
public void StartPlaying()
|
|
{
|
|
if (_isPlaying) return;
|
|
_isPlaying = true;
|
|
StartCoroutine(DoLoop());
|
|
}
|
|
|
|
public void TransitionToFinale()
|
|
{
|
|
_transitionToFinale = true;
|
|
|
|
// Schedule finale for as soon as the current segment loop ends
|
|
double finaleAudioTime = _segmentEndAudioTime;
|
|
float finaleGameTime = _segmentEndGameTime;
|
|
|
|
// Cancel loop audio
|
|
foreach (var loopSrc in _loopSources)
|
|
{
|
|
loopSrc._audioSource.SetScheduledEndTime(finaleAudioTime);
|
|
}
|
|
|
|
// Set quantum sphere inflation timer
|
|
var finaleDuration = CosmicInflationController._travelerFinaleSource.clip.length;
|
|
CosmicInflationController._startFormationTime = Time.time;
|
|
CosmicInflationController._finishFormationTime = finaleGameTime + finaleDuration - 4f;
|
|
|
|
// Play finale in sync
|
|
foreach (var finaleSrc in _finaleSources)
|
|
{
|
|
finaleSrc._audioSource.PlayScheduled(finaleAudioTime);
|
|
}
|
|
}
|
|
|
|
public void Awake()
|
|
{
|
|
// EOTP makes 2 new CosmicInflationControllers for no reason
|
|
CosmicInflationController = SearchUtilities.Find("EyeOfTheUniverse_Body/Sector_EyeOfTheUniverse/Sector_Campfire/InflationController").GetComponent<CosmicInflationController>();
|
|
}
|
|
|
|
private IEnumerator DoLoop()
|
|
{
|
|
// Determine timing using the first loop audio clip (should be Riebeck's banjo loop)
|
|
var referenceLoopClip = _loopSources.First().clip;
|
|
double loopDuration = referenceLoopClip.samples / (double)referenceLoopClip.frequency;
|
|
|
|
// Vanilla audio divides the loop into 4 segments, but that actually causes weird key shifting during the crossfade
|
|
int segmentCount = 2;
|
|
double segmentDuration = loopDuration / segmentCount;
|
|
|
|
// Track when the next loop will play, in both audio system time and game time
|
|
double nextLoopAudioTime = AudioSettings.dspTime + TIME_BUFFER_WINDOW;
|
|
float nextLoopGameTime = Time.time + (float)TIME_BUFFER_WINDOW;
|
|
|
|
while (!_transitionToFinale)
|
|
{
|
|
// Play loops in sync
|
|
double loopStartAudioTime = nextLoopAudioTime;
|
|
float loopStartGameTime = nextLoopGameTime;
|
|
|
|
foreach (var loopSrc in _loopSources)
|
|
{
|
|
if (!loopSrc.gameObject.activeInHierarchy) continue;
|
|
if (loopSrc.loop) continue;
|
|
// We only need to schedule once and then Unity will loop it for us
|
|
loopSrc._audioSource.PlayScheduled(loopStartAudioTime);
|
|
loopSrc.loop = true;
|
|
}
|
|
|
|
// Schedule next loop
|
|
nextLoopAudioTime += loopDuration;
|
|
nextLoopGameTime += (float)loopDuration;
|
|
|
|
// Track loop segment timing (the current musical verse should always finish playing before the finale)
|
|
for (int i = 0; i < segmentCount; i++)
|
|
{
|
|
_segmentEndAudioTime = loopStartAudioTime + segmentDuration * (i + 1);
|
|
_segmentEndGameTime = loopStartGameTime + (float)(segmentDuration * (i + 1));
|
|
|
|
// Wait until the next segment
|
|
while (Time.time < _segmentEndGameTime && !_transitionToFinale)
|
|
{
|
|
yield return null;
|
|
}
|
|
|
|
// Interrupt the remaining segments for the finale
|
|
if (_transitionToFinale) break;
|
|
}
|
|
}
|
|
|
|
// Wait until the bubble has finished expanding
|
|
while (Time.time < CosmicInflationController._finishFormationTime)
|
|
{
|
|
yield return null;
|
|
}
|
|
|
|
// Disable audio signals
|
|
foreach (var loopSrc in _loopSources)
|
|
{
|
|
var signal = loopSrc.GetComponent<AudioSignal>();
|
|
if (signal != null)
|
|
{
|
|
signal.SetSignalActivation(false, 0f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|