-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathInfiniteBeatSaberMode.cs
158 lines (129 loc) · 6.01 KB
/
InfiniteBeatSaberMode.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
using InfiniteBeatSaber.Patches;
using InfiniteJukeboxAlgorithm;
using IPA.Utilities;
using System;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using Zenject;
using InfiniteBeatSaber.Extensions;
namespace InfiniteBeatSaber
{
internal class InfiniteBeatSaberMode : IInitializable, IDisposable
{
#pragma warning disable 0649 // Suppress "Field X is never assigned to" b/c [Inject] assigns.
[Inject] private readonly AudioTimeSyncController _audioTimeSyncController;
[Inject] private readonly GameplayCoreSceneSetupData _gameplayCoreSceneSetupData;
#if DEBUG
[Inject] private readonly DebugTools.RemixVisualizer _remixVisualizer;
#endif
#pragma warning restore 0649
private CancellationTokenSource _generateRemixLoopCts;
private InfiniteRemix _infiniteRemix;
private IAudioRemixer _audioRemixer;
private BeatmapRemixer _beatmapRemixer;
public void Initialize()
{
if (!InfiniteBeatSaberMenuUI.IsInfiniteBeatSaberMode) return;
var level = _gameplayCoreSceneSetupData.previewBeatmapLevel;
// TODO: Stop blocking the thread by reading a file.
var spotifyAnalysis = RemixableSongs.ReadSpotifyAnalysis(level);
var random = new SystemRandom();
var initData = _audioTimeSyncController.GetField<AudioTimeSyncController.InitData, AudioTimeSyncController>("_initData");
var audioClip = Util.AssertNotNull(initData.audioClip, "audioClip");
var audioSource = Util.AssertNotNull(_audioTimeSyncController.GetField<AudioSource, AudioTimeSyncController>("_audioSource"), "_audioSource");
var originalBeatmap = Util.AssertNotNull(GameplayCoreSceneSetupDataPatches.OriginalBeatmap, "originalBeatmap");
var readonlyBeatmap = Util.AssertNotNull(_gameplayCoreSceneSetupData.transformedBeatmapData, "readonlyBeatmap");
var beatmap = Util.AssertNotNull(readonlyBeatmap as BeatmapData, "beatmap");
_infiniteRemix = new InfiniteRemix(spotifyAnalysis, level.beatsPerMinute, random);
//_audioRemixer = new RingBufferBasedAudioRemixer(audioClip, audioSource);
_audioRemixer = new QueueBasedAudioRemixer(_audioTimeSyncController, audioClip, audioSource);
_beatmapRemixer = new BeatmapRemixer(originalBeatmap, beatmap);
#if DEBUG
_remixVisualizer.InitializeData(spotifyAnalysis.SerializeToJson());
#endif
GenerateNextPartOfRemix(60);
if (_audioTimeSyncController.state != AudioTimeSyncController.State.Stopped)
throw new Exception("The song started before we had a chance to initialize it");
_audioTimeSyncController.stateChangedEvent += OnAudioTimeSyncControllerStateChanged;
Plugin.Log.Info(
"InfiniteBeatSaberMode.Initialize" +
$". Seed: {random.Seed}" +
$". Level: {level.songName} by {level.songAuthorName}, {level.levelAuthorName} (ID: {level.levelID})" +
"");
}
// Lifecycle hook for destruction called by the dependency injection framework.
public void Dispose()
{
if (!InfiniteBeatSaberMenuUI.IsInfiniteBeatSaberMode) return;
_generateRemixLoopCts?.Cancel();
_audioRemixer.Dispose();
}
private void OnAudioTimeSyncControllerStateChanged()
{
switch (_audioTimeSyncController.state)
{
case AudioTimeSyncController.State.Stopped:
case AudioTimeSyncController.State.Paused:
StopGenerateRemixLoop();
break;
case AudioTimeSyncController.State.Playing:
StartGenerateRemixLoop().LogOnFailure();
break;
}
}
// Generates at least the next `minSeconds` of the remix.
private Remix GenerateNextPartOfRemix(int minSeconds)
{
var startDuration = _infiniteRemix.Duration;
var remix = _infiniteRemix.GetNext(minSeconds);
_audioRemixer.AddRemix(remix);
_beatmapRemixer.AddRemix(remix);
#if DEBUG
_remixVisualizer.AddRemix(remix);
#endif
var durationDelta = _infiniteRemix.Duration - startDuration;
//Plugin.Log.Info("InfiniteBeatSaberMode.GenerateNextPartOfRemix: " + minSeconds + ". " + startDuration + " -> " + _infiniteRemix.Duration + " (" + durationDelta + ")");
return remix;
}
private async Task StartGenerateRemixLoop()
{
if (_generateRemixLoopCts != null)
{
// A loop is already running.
return;
}
_generateRemixLoopCts = new CancellationTokenSource();
var cancellationToken = _generateRemixLoopCts.Token;
//Plugin.Log.Info("InfiniteBeatSaberMode.StartGenerateRemixLoop: Started");
try
{
while (!cancellationToken.IsCancellationRequested)
{
var sleepUntilSongTime = _infiniteRemix.Duration - 25;
var sleepSeconds = (sleepUntilSongTime - _audioTimeSyncController.songTime) / _audioTimeSyncController.timeScale;
if (sleepSeconds > 0)
{
await Task.Delay(TimeSpan.FromSeconds(sleepSeconds), cancellationToken);
}
GenerateNextPartOfRemix(30);
}
}
catch (OperationCanceledException ex) when (ex.CancellationToken == cancellationToken)
{
// No-op
}
//Plugin.Log.Info("InfiniteBeatSaberMode.StartGenerateRemixLoop: Exited");
}
private void StopGenerateRemixLoop()
{
var cts = _generateRemixLoopCts;
if (cts != null)
{
// A loop is running. Stop it.
_generateRemixLoopCts = null;
cts.Cancel();
}
}
}
}