1
0
Fork 0

Rename Start segment to Intro to reduce some confusion

Confusingly, "start" may refer to too many things in different places,
while "intro" would unambiguously refer to an audio clip that plays
first before the loop starts.
This commit is contained in:
ivan tkachenko 2025-08-14 14:48:33 +03:00
parent 5f0c890682
commit fc3a62e511
18 changed files with 37 additions and 37 deletions

View File

@ -110,7 +110,7 @@
--> -->
<Target Name="wav2ogg"> <Target Name="wav2ogg">
<ItemGroup> <ItemGroup>
<TrackNames Include="$(TrackName)Start" /> <TrackNames Include="$(TrackName)Intro" />
<TrackNames Include="$(TrackName)Loop" /> <TrackNames Include="$(TrackName)Loop" />
</ItemGroup> </ItemGroup>
<Exec Command="ffmpeg -bitexact -y -i $(WavExportDir)%(TrackNames.Identity).wav $(SolutionDir)Assets\%(TrackNames.Identity).ogg" /> <Exec Command="ffmpeg -bitexact -y -i $(WavExportDir)%(TrackNames.Identity).wav $(SolutionDir)Assets\%(TrackNames.Identity).ogg" />

View File

@ -563,7 +563,7 @@ namespace MuzikaGromche
for (int i = 0; i < Tracks.Length; i++) for (int i = 0; i < Tracks.Length; i++)
{ {
Track track = Tracks[i]; Track track = Tracks[i];
requests[i * 2] = UnityWebRequestMultimedia.GetAudioClip($"file://{dir}/{track.FileNameStart}", track.AudioType); requests[i * 2] = UnityWebRequestMultimedia.GetAudioClip($"file://{dir}/{track.FileNameIntro}", track.AudioType);
requests[i * 2 + 1] = UnityWebRequestMultimedia.GetAudioClip($"file://{dir}/{track.FileNameLoop}", track.AudioType); requests[i * 2 + 1] = UnityWebRequestMultimedia.GetAudioClip($"file://{dir}/{track.FileNameLoop}", track.AudioType);
requests[i * 2].SendWebRequest(); requests[i * 2].SendWebRequest();
requests[i * 2 + 1].SendWebRequest(); requests[i * 2 + 1].SendWebRequest();
@ -576,10 +576,10 @@ namespace MuzikaGromche
for (int i = 0; i < Tracks.Length; i++) for (int i = 0; i < Tracks.Length; i++)
{ {
Track track = Tracks[i]; Track track = Tracks[i];
track.LoadedStart = DownloadHandlerAudioClip.GetContent(requests[i * 2]); track.LoadedIntro = DownloadHandlerAudioClip.GetContent(requests[i * 2]);
track.LoadedLoop = DownloadHandlerAudioClip.GetContent(requests[i * 2 + 1]); track.LoadedLoop = DownloadHandlerAudioClip.GetContent(requests[i * 2 + 1]);
#if DEBUG #if DEBUG
Debug.Log($"{nameof(MuzikaGromche)} Track {track.Name} {track.LoadedStart.length:N4} {track.LoadedLoop.length:N4}"); Debug.Log($"{nameof(MuzikaGromche)} Track {track.Name} {track.LoadedIntro.length:N4} {track.LoadedLoop.length:N4}");
#endif #endif
} }
Config = new Config(base.Config); Config = new Config(base.Config);
@ -708,8 +708,8 @@ namespace MuzikaGromche
public required Language Language; public required Language Language;
// Whether this track has NSFW/explicit lyrics. // Whether this track has NSFW/explicit lyrics.
public bool IsExplicit = false; public bool IsExplicit = false;
// Wind-up time can and should be shorter than the Start audio track, // Wind-up time can and should be shorter than the Intro audio track,
// so that the "pop" effect can be baked into the Start audio and kept away // so that the "pop" effect can be baked into the Intro audio and kept away
// from the looped part. This also means that the light show starts before // from the looped part. This also means that the light show starts before
// the looped track does, so we need to sync them up as soon as we enter the Loop. // the looped track does, so we need to sync them up as soon as we enter the Loop.
public required float WindUpTimer; public required float WindUpTimer;
@ -734,13 +734,13 @@ namespace MuzikaGromche
// WAV is OK, but takes a lot of space. Try OGGVORBIS instead. // WAV is OK, but takes a lot of space. Try OGGVORBIS instead.
public AudioType AudioType = AudioType.MPEG; public AudioType AudioType = AudioType.MPEG;
public AudioClip LoadedStart = null!; public AudioClip LoadedIntro = null!;
public AudioClip LoadedLoop = null!; public AudioClip LoadedLoop = null!;
// How often this track should be chosen, relative to the sum of weights of all tracks. // How often this track should be chosen, relative to the sum of weights of all tracks.
public ConfigEntry<int> Weight = null!; public ConfigEntry<int> Weight = null!;
public string FileNameStart => $"{Name}Start.{Ext}"; public string FileNameIntro => $"{Name}Intro.{Ext}";
public string FileNameLoop => $"{Name}Loop.{Ext}"; public string FileNameLoop => $"{Name}Loop.{Ext}";
private string Ext => AudioType switch private string Ext => AudioType switch
{ {
@ -1111,36 +1111,36 @@ namespace MuzikaGromche
class JesterAudioSourcesState class JesterAudioSourcesState
{ {
private readonly float StartClipLength; private readonly float IntroClipLength;
// Neither start.isPlaying or loop.isPlaying are reliable indicators of which track is actually playing right now: // Neither intro.isPlaying or loop.isPlaying are reliable indicators of which track is actually playing right now:
// start.isPlaying would be true during the loop when Jester chases a player, // intro.isPlaying would be true during the loop when Jester chases a player,
// loop.isPlaying would be true when it is played delyaed but hasn't actually started playing yet. // loop.isPlaying would be true when it is played delyaed but hasn't actually started playing yet.
private readonly ExtrapolatedAudioSourceState Start = new(); private readonly ExtrapolatedAudioSourceState Intro = new();
private readonly ExtrapolatedAudioSourceState Loop = new(); private readonly ExtrapolatedAudioSourceState Loop = new();
// If true, use Start state as a reference, otherwise use Loop. // If true, use Start state as a reference, otherwise use Loop.
private bool ReferenceIsStart = true; private bool ReferenceIsIntro = true;
public bool HasStarted => Start.HasStarted; public bool HasStarted => Intro.HasStarted;
public bool IsExtrapolated => ReferenceIsStart ? Start.IsExtrapolated : Loop.IsExtrapolated; public bool IsExtrapolated => ReferenceIsIntro ? Intro.IsExtrapolated : Loop.IsExtrapolated;
// Time from the start of the start clip. It wraps when the loop AudioSource loops: // Time from the start of the start clip. It wraps when the loop AudioSource loops:
// [...start...][...loop...] // [...start...][...loop...]
// ^ | // ^ |
// `----------' // `----------'
public float Time => ReferenceIsStart public float Time => ReferenceIsIntro
? Start.Time ? Intro.Time
: StartClipLength + Loop.Time; : IntroClipLength + Loop.Time;
public JesterAudioSourcesState(float startClipLength) public JesterAudioSourcesState(float introClipLength)
{ {
StartClipLength = startClipLength; IntroClipLength = introClipLength;
} }
public void Update(AudioSource start, AudioSource loop, float realtime) public void Update(AudioSource intro, AudioSource loop, float realtime)
{ {
// It doesn't make sense to update start state after loop has started (because start.isPlaying occasionally becomes true). // It doesn't make sense to update start state after loop has started (because start.isPlaying occasionally becomes true).
// But always makes sense to update loop, so we can check if it has actually started. // But always makes sense to update loop, so we can check if it has actually started.
@ -1149,13 +1149,13 @@ namespace MuzikaGromche
if (!Loop.HasStarted) if (!Loop.HasStarted)
{ {
#if DEBUG #if DEBUG
Debug.Assert(ReferenceIsStart); Debug.Assert(ReferenceIsIntro);
#endif #endif
Start.Update(start, realtime); Intro.Update(intro, realtime);
} }
else else
{ {
ReferenceIsStart = false; ReferenceIsIntro = false;
} }
} }
} }
@ -1191,7 +1191,7 @@ namespace MuzikaGromche
// //
// NOTE 1: PlayDelayed also counts as isPlaying, so loop.isPlaying is always true and as such it's useful. // NOTE 1: PlayDelayed also counts as isPlaying, so loop.isPlaying is always true and as such it's useful.
// NOTE 2: There is a weird state when Jester has popped and chases a player: // NOTE 2: There is a weird state when Jester has popped and chases a player:
// Start/farAudio isPlaying is true but stays exactly at zero time, so we need to ignore that. // Intro/farAudio isPlaying is true but stays exactly at zero time, so we need to ignore that.
var offset = StartOfLoop + additionalOffset; var offset = StartOfLoop + additionalOffset;
@ -1246,15 +1246,15 @@ namespace MuzikaGromche
LyricsRandomPerLoop = LyricsRandom.Next(); LyricsRandomPerLoop = LyricsRandom.Next();
} }
this.track = track; this.track = track;
AudioState = new(track.LoadedStart.length); AudioState = new(track.LoadedIntro.length);
WindUpLoopingState = new(track.WindUpTimer, track.LoadedLoop.length, track.Beats); WindUpLoopingState = new(track.WindUpTimer, track.LoadedLoop.length, track.Beats);
LoopLoopingState = new(track.WindUpTimer + track.LoopOffsetInSeconds, track.LoadedLoop.length, track.Beats); LoopLoopingState = new(track.WindUpTimer + track.LoopOffsetInSeconds, track.LoadedLoop.length, track.Beats);
} }
public List<BaseEvent> Update(AudioSource start, AudioSource loop) public List<BaseEvent> Update(AudioSource intro, AudioSource loop)
{ {
var time = Time.realtimeSinceStartup; var time = Time.realtimeSinceStartup;
AudioState.Update(start, loop, time); AudioState.Update(intro, loop, time);
if (AudioState.HasStarted) if (AudioState.HasStarted)
{ {
@ -1772,7 +1772,7 @@ namespace MuzikaGromche
private void SetupEntriesToSkipWinding(ConfigFile configFile) private void SetupEntriesToSkipWinding(ConfigFile configFile)
{ {
var syncedEntry = configFile.BindSyncedEntry("General", "Skip Winding Phase", false, var syncedEntry = configFile.BindSyncedEntry("General", "Skip Winding Phase", false,
new ConfigDescription("Skip most of the wind-up/intro/start music.\n\nUse this option to test your Loop audio segment.")); new ConfigDescription("Skip most of the wind-up/intro music.\n\nUse this option to test your Loop audio segment."));
LethalConfigManager.AddConfigItem(new BoolCheckBoxConfigItem(syncedEntry.Entry, Default(new BoolCheckBoxOptions()))); LethalConfigManager.AddConfigItem(new BoolCheckBoxConfigItem(syncedEntry.Entry, Default(new BoolCheckBoxOptions())));
CSyncHackAddSyncedEntry(syncedEntry); CSyncHackAddSyncedEntry(syncedEntry);
syncedEntry.Changed += (sender, args) => apply(); syncedEntry.Changed += (sender, args) => apply();
@ -2073,7 +2073,7 @@ namespace MuzikaGromche
} }
} }
// farAudio is during windup, Start overrides popGoesTheWeaselTheme // farAudio is during windup, Intro overrides popGoesTheWeaselTheme
// creatureVoice is when popped, Loop overrides screamingSFX // creatureVoice is when popped, Loop overrides screamingSFX
[HarmonyPatch(typeof(JesterAI))] [HarmonyPatch(typeof(JesterAI))]
static class JesterPatch static class JesterPatch
@ -2104,7 +2104,7 @@ namespace MuzikaGromche
}; };
if (__instance.currentBehaviourStateIndex == 2 && __instance.previousState != 2) if (__instance.currentBehaviourStateIndex == 2 && __instance.previousState != 2)
{ {
// If just popped out, then override farAudio so that vanilla logic does not stop the modded Start music. // If just popped out, then override farAudio so that vanilla logic does not stop the modded Intro music.
// The game will stop farAudio it during its Update, so we temporarily set it to any other AudioSource // The game will stop farAudio it during its Update, so we temporarily set it to any other AudioSource
// which we don't care about stopping for now. // which we don't care about stopping for now.
// //
@ -2141,7 +2141,7 @@ namespace MuzikaGromche
// Override popGoesTheWeaselTheme with Start audio // Override popGoesTheWeaselTheme with Start audio
__instance.farAudio.maxDistance = Plugin.AudioMaxDistance; __instance.farAudio.maxDistance = Plugin.AudioMaxDistance;
__instance.farAudio.clip = Plugin.CurrentTrack.LoadedStart; __instance.farAudio.clip = Plugin.CurrentTrack.LoadedIntro;
__instance.farAudio.loop = false; __instance.farAudio.loop = false;
if (Config.ShouldSkipWindingPhase) if (Config.ShouldSkipWindingPhase)
{ {
@ -2156,7 +2156,7 @@ namespace MuzikaGromche
} }
__instance.farAudio.Play(); __instance.farAudio.Play();
Debug.Log($"{nameof(MuzikaGromche)} Playing start music: maxDistance: {__instance.farAudio.maxDistance}, minDistance: {__instance.farAudio.minDistance}, volume: {__instance.farAudio.volume}, spread: {__instance.farAudio.spread}"); Debug.Log($"{nameof(MuzikaGromche)} Playing Intro music: maxDistance: {__instance.farAudio.maxDistance}, minDistance: {__instance.farAudio.minDistance}, volume: {__instance.farAudio.volume}, spread: {__instance.farAudio.spread}");
} }
if (__instance.previousState != 2 && __state.previousState == 2) if (__instance.previousState != 2 && __state.previousState == 2)
@ -2171,22 +2171,22 @@ namespace MuzikaGromche
__instance.farAudio = __state.farAudio; __instance.farAudio = __state.farAudio;
var time = __instance.farAudio.time; var time = __instance.farAudio.time;
var delay = Plugin.CurrentTrack.LoadedStart.length - time; var delay = Plugin.CurrentTrack.LoadedIntro.length - time;
// Override screamingSFX with Loop, delayed by the remaining time of the Start audio // Override screamingSFX with Loop, delayed by the remaining time of the Intro audio
__instance.creatureVoice.Stop(); __instance.creatureVoice.Stop();
__instance.creatureVoice.maxDistance = Plugin.AudioMaxDistance; __instance.creatureVoice.maxDistance = Plugin.AudioMaxDistance;
__instance.creatureVoice.clip = Plugin.CurrentTrack.LoadedLoop; __instance.creatureVoice.clip = Plugin.CurrentTrack.LoadedLoop;
__instance.creatureVoice.PlayDelayed(delay); __instance.creatureVoice.PlayDelayed(delay);
Debug.Log($"{nameof(MuzikaGromche)} Start length: {Plugin.CurrentTrack.LoadedStart.length}; played time: {time}"); Debug.Log($"{nameof(MuzikaGromche)} Intro length: {Plugin.CurrentTrack.LoadedIntro.length}; played time: {time}");
Debug.Log($"{nameof(MuzikaGromche)} Playing loop music: maxDistance: {__instance.creatureVoice.maxDistance}, minDistance: {__instance.creatureVoice.minDistance}, volume: {__instance.creatureVoice.volume}, spread: {__instance.creatureVoice.spread}, in seconds: {delay}"); Debug.Log($"{nameof(MuzikaGromche)} Playing loop music: maxDistance: {__instance.creatureVoice.maxDistance}, minDistance: {__instance.creatureVoice.minDistance}, volume: {__instance.creatureVoice.volume}, spread: {__instance.creatureVoice.spread}, in seconds: {delay}");
} }
// Manage the timeline: switch color of the lights according to the current playback/beat position. // Manage the timeline: switch color of the lights according to the current playback/beat position.
if ((__instance.previousState == 1 || __instance.previousState == 2) && Plugin.BeatTimeState is { } beatTimeState) if ((__instance.previousState == 1 || __instance.previousState == 2) && Plugin.BeatTimeState is { } beatTimeState)
{ {
var events = beatTimeState.Update(start: __instance.farAudio, loop: __instance.creatureVoice); var events = beatTimeState.Update(intro: __instance.farAudio, loop: __instance.creatureVoice);
foreach (var ev in events) foreach (var ev in events)
{ {
switch (ev) switch (ev)