forked from nikita/muzika-gromche
Split Track into Selectable and Audio interfaces, add support for groups
This commit is contained in:
parent
47f984cd28
commit
5649a18633
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
## MuzikaGromche 1337.420.9001
|
## MuzikaGromche 1337.420.9001
|
||||||
|
|
||||||
|
- Added support for tracks to rotate between multiple audio variants during a round.
|
||||||
|
|
||||||
## MuzikaGromche 1337.420.69 - It's All DiscoNnected Edition
|
## MuzikaGromche 1337.420.69 - It's All DiscoNnected Edition
|
||||||
|
|
||||||
|
|
|
@ -48,8 +48,8 @@ namespace MuzikaGromche
|
||||||
.Select(a => $" Trying... {a}")
|
.Select(a => $" Trying... {a}")
|
||||||
];
|
];
|
||||||
|
|
||||||
public static readonly Track[] Tracks = [
|
public static readonly ISelectableTrack[] Tracks = [
|
||||||
new Track
|
new SelectableAudioTrack
|
||||||
{
|
{
|
||||||
Name = "MuzikaGromche",
|
Name = "MuzikaGromche",
|
||||||
AudioType = AudioType.OGGVORBIS,
|
AudioType = AudioType.OGGVORBIS,
|
||||||
|
@ -89,7 +89,7 @@ namespace MuzikaGromche
|
||||||
(63, "Muzyka Gromche\nGlaza zakryty >_<"),
|
(63, "Muzyka Gromche\nGlaza zakryty >_<"),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
new Track
|
new SelectableAudioTrack
|
||||||
{
|
{
|
||||||
Name = "VseVZale",
|
Name = "VseVZale",
|
||||||
AudioType = AudioType.OGGVORBIS,
|
AudioType = AudioType.OGGVORBIS,
|
||||||
|
@ -124,7 +124,7 @@ namespace MuzikaGromche
|
||||||
(60, "Everybody shake your body"),
|
(60, "Everybody shake your body"),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
new Track
|
new SelectableAudioTrack
|
||||||
{
|
{
|
||||||
Name = "DeployDestroy",
|
Name = "DeployDestroy",
|
||||||
AudioType = AudioType.OGGVORBIS,
|
AudioType = AudioType.OGGVORBIS,
|
||||||
|
@ -167,7 +167,7 @@ namespace MuzikaGromche
|
||||||
(25, "Davaj-davaj!"),
|
(25, "Davaj-davaj!"),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
new Track
|
new SelectableAudioTrack
|
||||||
{
|
{
|
||||||
Name = "MoyaZhittya",
|
Name = "MoyaZhittya",
|
||||||
AudioType = AudioType.OGGVORBIS,
|
AudioType = AudioType.OGGVORBIS,
|
||||||
|
@ -214,7 +214,7 @@ namespace MuzikaGromche
|
||||||
( 30, "IT'S MY"),
|
( 30, "IT'S MY"),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
new Track
|
new SelectableAudioTrack
|
||||||
{
|
{
|
||||||
Name = "Gorgorod",
|
Name = "Gorgorod",
|
||||||
AudioType = AudioType.OGGVORBIS,
|
AudioType = AudioType.OGGVORBIS,
|
||||||
|
@ -232,7 +232,7 @@ namespace MuzikaGromche
|
||||||
FlickerLightsTimeSeries = [20],
|
FlickerLightsTimeSeries = [20],
|
||||||
Lyrics = [],
|
Lyrics = [],
|
||||||
},
|
},
|
||||||
new Track
|
new SelectableAudioTrack
|
||||||
{
|
{
|
||||||
Name = "Durochka",
|
Name = "Durochka",
|
||||||
AudioType = AudioType.OGGVORBIS,
|
AudioType = AudioType.OGGVORBIS,
|
||||||
|
@ -250,7 +250,7 @@ namespace MuzikaGromche
|
||||||
FlickerLightsTimeSeries = [-9],
|
FlickerLightsTimeSeries = [-9],
|
||||||
Lyrics = [],
|
Lyrics = [],
|
||||||
},
|
},
|
||||||
new Track
|
new SelectableAudioTrack
|
||||||
{
|
{
|
||||||
Name = "ZmeiGorynich",
|
Name = "ZmeiGorynich",
|
||||||
AudioType = AudioType.OGGVORBIS,
|
AudioType = AudioType.OGGVORBIS,
|
||||||
|
@ -268,7 +268,7 @@ namespace MuzikaGromche
|
||||||
FlickerLightsTimeSeries = [-5, 31],
|
FlickerLightsTimeSeries = [-5, 31],
|
||||||
Lyrics = [],
|
Lyrics = [],
|
||||||
},
|
},
|
||||||
new Track
|
new SelectableAudioTrack
|
||||||
{
|
{
|
||||||
Name = "GodMode",
|
Name = "GodMode",
|
||||||
AudioType = AudioType.OGGVORBIS,
|
AudioType = AudioType.OGGVORBIS,
|
||||||
|
@ -286,7 +286,7 @@ namespace MuzikaGromche
|
||||||
FlickerLightsTimeSeries = [-5],
|
FlickerLightsTimeSeries = [-5],
|
||||||
Lyrics = [],
|
Lyrics = [],
|
||||||
},
|
},
|
||||||
new Track
|
new SelectableAudioTrack
|
||||||
{
|
{
|
||||||
Name = "RiseAndShine",
|
Name = "RiseAndShine",
|
||||||
AudioType = AudioType.OGGVORBIS,
|
AudioType = AudioType.OGGVORBIS,
|
||||||
|
@ -304,7 +304,7 @@ namespace MuzikaGromche
|
||||||
FlickerLightsTimeSeries = [-5.5f, 31, 63.9f],
|
FlickerLightsTimeSeries = [-5.5f, 31, 63.9f],
|
||||||
Lyrics = [],
|
Lyrics = [],
|
||||||
},
|
},
|
||||||
new Track
|
new SelectableAudioTrack
|
||||||
{
|
{
|
||||||
Name = "Song2",
|
Name = "Song2",
|
||||||
AudioType = AudioType.OGGVORBIS,
|
AudioType = AudioType.OGGVORBIS,
|
||||||
|
@ -322,7 +322,7 @@ namespace MuzikaGromche
|
||||||
FlickerLightsTimeSeries = [2.5f],
|
FlickerLightsTimeSeries = [2.5f],
|
||||||
Lyrics = [],
|
Lyrics = [],
|
||||||
},
|
},
|
||||||
new Track
|
new SelectableAudioTrack
|
||||||
{
|
{
|
||||||
Name = "Peretasovka",
|
Name = "Peretasovka",
|
||||||
AudioType = AudioType.OGGVORBIS,
|
AudioType = AudioType.OGGVORBIS,
|
||||||
|
@ -340,7 +340,7 @@ namespace MuzikaGromche
|
||||||
FlickerLightsTimeSeries = [-8, 31],
|
FlickerLightsTimeSeries = [-8, 31],
|
||||||
Lyrics = [],
|
Lyrics = [],
|
||||||
},
|
},
|
||||||
new Track
|
new SelectableAudioTrack
|
||||||
{
|
{
|
||||||
Name = "Yalgaar",
|
Name = "Yalgaar",
|
||||||
AudioType = AudioType.OGGVORBIS,
|
AudioType = AudioType.OGGVORBIS,
|
||||||
|
@ -358,7 +358,7 @@ namespace MuzikaGromche
|
||||||
FlickerLightsTimeSeries = [-5],
|
FlickerLightsTimeSeries = [-5],
|
||||||
Lyrics = [],
|
Lyrics = [],
|
||||||
},
|
},
|
||||||
new Track
|
new SelectableAudioTrack
|
||||||
{
|
{
|
||||||
Name = "Chereshnya",
|
Name = "Chereshnya",
|
||||||
AudioType = AudioType.OGGVORBIS,
|
AudioType = AudioType.OGGVORBIS,
|
||||||
|
@ -379,7 +379,7 @@ namespace MuzikaGromche
|
||||||
FlickerLightsTimeSeries = [-5, 27, 29, 59, 61],
|
FlickerLightsTimeSeries = [-5, 27, 29, 59, 61],
|
||||||
Lyrics = [],
|
Lyrics = [],
|
||||||
},
|
},
|
||||||
new Track
|
new SelectableAudioTrack
|
||||||
{
|
{
|
||||||
Name = "PWNED",
|
Name = "PWNED",
|
||||||
AudioType = AudioType.OGGVORBIS,
|
AudioType = AudioType.OGGVORBIS,
|
||||||
|
@ -451,7 +451,7 @@ namespace MuzikaGromche
|
||||||
(98, $"\t\t\tresolving ur private IP\nP_WNED"),
|
(98, $"\t\t\tresolving ur private IP\nP_WNED"),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
new Track
|
new SelectableAudioTrack
|
||||||
{
|
{
|
||||||
Name = "Kach",
|
Name = "Kach",
|
||||||
AudioType = AudioType.OGGVORBIS,
|
AudioType = AudioType.OGGVORBIS,
|
||||||
|
@ -475,7 +475,7 @@ namespace MuzikaGromche
|
||||||
FlickerLightsTimeSeries = [-120.5f, -105, -89, -8, 44, 45],
|
FlickerLightsTimeSeries = [-120.5f, -105, -89, -8, 44, 45],
|
||||||
Lyrics = [],
|
Lyrics = [],
|
||||||
},
|
},
|
||||||
new Track
|
new SelectableAudioTrack
|
||||||
{
|
{
|
||||||
Name = "BeefLiver",
|
Name = "BeefLiver",
|
||||||
AudioType = AudioType.OGGVORBIS,
|
AudioType = AudioType.OGGVORBIS,
|
||||||
|
@ -498,7 +498,7 @@ namespace MuzikaGromche
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
public static Track ChooseTrack()
|
public static ISelectableTrack ChooseTrack()
|
||||||
{
|
{
|
||||||
var seed = RoundManager.Instance.dungeonGenerator.Generator.ChosenSeed;
|
var seed = RoundManager.Instance.dungeonGenerator.Generator.ChosenSeed;
|
||||||
var tracks = Config.SkipExplicitTracks.Value ? [.. Tracks.Where(track => !track.IsExplicit)] : Tracks;
|
var tracks = Config.SkipExplicitTracks.Value ? [.. Tracks.Where(track => !track.IsExplicit)] : Tracks;
|
||||||
|
@ -510,12 +510,12 @@ namespace MuzikaGromche
|
||||||
return tracks[trackId];
|
return tracks[trackId];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Track? FindTrackNamed(string name)
|
public static IAudioTrack? FindTrackNamed(string name)
|
||||||
{
|
{
|
||||||
return Tracks.FirstOrDefault(track => track.Name == name);
|
return Tracks.SelectMany(track => track.GetTracks()).FirstOrDefault(track => track.Name == name);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static Track? CurrentTrack;
|
internal static IAudioTrack? CurrentTrack;
|
||||||
internal static BeatTimeState? BeatTimeState;
|
internal static BeatTimeState? BeatTimeState;
|
||||||
|
|
||||||
public static void SetLightColor(Color color)
|
public static void SetLightColor(Color color)
|
||||||
|
@ -562,7 +562,7 @@ namespace MuzikaGromche
|
||||||
Dictionary<string, (UnityWebRequest Request, List<Action<AudioClip>> Setters)> requests = [];
|
Dictionary<string, (UnityWebRequest Request, List<Action<AudioClip>> Setters)> requests = [];
|
||||||
requests.EnsureCapacity(Tracks.Length * 2);
|
requests.EnsureCapacity(Tracks.Length * 2);
|
||||||
|
|
||||||
foreach (var track in Tracks)
|
foreach (var track in Tracks.SelectMany(track => track.GetTracks()))
|
||||||
{
|
{
|
||||||
foreach (var (fileName, setter) in new (string, Action<AudioClip>)[]
|
foreach (var (fileName, setter) in new (string, Action<AudioClip>)[]
|
||||||
{
|
{
|
||||||
|
@ -599,7 +599,7 @@ namespace MuzikaGromche
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
foreach (var track in Tracks)
|
foreach (var track in Tracks)
|
||||||
{
|
{
|
||||||
Debug.Log($"{nameof(MuzikaGromche)} Track {track.Name} {track.LoadedIntro.length:N4} {track.LoadedLoop.length:N4}");
|
track.Debug();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
Config = new Config(base.Config);
|
Config = new Config(base.Config);
|
||||||
|
@ -721,60 +721,80 @@ namespace MuzikaGromche
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Track
|
public struct SelectableTrackData()
|
||||||
{
|
{
|
||||||
public required string Name;
|
// Name of the track, as shown in config entry UI; also used for default file names.
|
||||||
|
public required string Name { get; init; }
|
||||||
|
|
||||||
// Language of the track's lyrics.
|
// Language of the track's lyrics.
|
||||||
public required Language Language;
|
public required Language Language { get; init; }
|
||||||
|
|
||||||
// Whether this track has NSFW/explicit lyrics.
|
// Whether this track has NSFW/explicit lyrics.
|
||||||
public bool IsExplicit = false;
|
public bool IsExplicit { get; init; } = false;
|
||||||
|
|
||||||
|
// How often this track should be chosen, relative to the sum of weights of all tracks.
|
||||||
|
public ConfigEntry<int> Weight { get; internal set; } = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
// An instance of a track which appears as a configuration entry and
|
||||||
|
// can be selected using weighted random from a list of selectable tracks.
|
||||||
|
public interface ISelectableTrack
|
||||||
|
{
|
||||||
|
// Name of the track, as shown in config entry UI; also used for default file names.
|
||||||
|
public string Name { get; init; }
|
||||||
|
|
||||||
|
// Language of the track's lyrics.
|
||||||
|
public Language Language { get; init; }
|
||||||
|
|
||||||
|
// Whether this track has NSFW/explicit lyrics.
|
||||||
|
public bool IsExplicit { get; init; }
|
||||||
|
|
||||||
|
// How often this track should be chosen, relative to the sum of weights of all tracks.
|
||||||
|
internal ConfigEntry<int> Weight { get; set; }
|
||||||
|
|
||||||
|
internal IAudioTrack[] GetTracks();
|
||||||
|
|
||||||
|
// Index is a non-negative monotonically increasing number of times
|
||||||
|
// this ISelectableTrack has been played for this Jester on this day.
|
||||||
|
// A group of tracks can use this index to rotate tracks sequentially.
|
||||||
|
internal IAudioTrack SelectTrack(int index);
|
||||||
|
|
||||||
|
internal void Debug();
|
||||||
|
}
|
||||||
|
|
||||||
|
// An instance of a track which has file names, timings data, palette; can be loaded and played.
|
||||||
|
public interface IAudioTrack
|
||||||
|
{
|
||||||
|
// Name of the track used for default file names.
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
// Wind-up time can and should be shorter than the Intro 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 Intro 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 float WindUpTimer { get; }
|
||||||
|
|
||||||
// Estimated number of beats per minute. Not used for light show, but might come in handy.
|
// Estimated number of beats per minute. Not used for light show, but might come in handy.
|
||||||
public float Bpm => 60f / (LoadedLoop.length / Beats);
|
public float Bpm => 60f / (LoadedLoop.length / Beats);
|
||||||
|
|
||||||
// How many beats the loop segment has. The default strategy is to switch color of lights on each beat.
|
// How many beats the loop segment has. The default strategy is to switch color of lights on each beat.
|
||||||
public int Beats;
|
public int Beats { get; }
|
||||||
|
|
||||||
// Number of beats between WindUpTimer and where looped segment starts (not the loop audio).
|
// Number of beats between WindUpTimer and where looped segment starts (not the loop audio).
|
||||||
public int LoopOffset = 0;
|
public int LoopOffset { get; }
|
||||||
public float LoopOffsetInSeconds => LoopOffset / Beats * LoadedLoop.length;
|
public float LoopOffsetInSeconds => LoopOffset / Beats * LoadedLoop.length;
|
||||||
|
|
||||||
// Shorthand for four beats
|
|
||||||
public int Bars
|
|
||||||
{
|
|
||||||
set => Beats = value * 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
// MPEG is basically mp3, and it can produce gaps at the start.
|
// MPEG is basically mp3, and it can produce gaps at the start.
|
||||||
// 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 { get; }
|
||||||
|
|
||||||
public AudioClip LoadedIntro = null!;
|
public AudioClip LoadedIntro { get; internal set; }
|
||||||
public AudioClip LoadedLoop = null!;
|
public AudioClip LoadedLoop { get; internal set; }
|
||||||
|
|
||||||
// How often this track should be chosen, relative to the sum of weights of all tracks.
|
public string FileNameIntro { get; }
|
||||||
public ConfigEntry<int> Weight = null!;
|
public string FileNameLoop { get; }
|
||||||
|
|
||||||
private string? FileNameIntroOverride = null;
|
public string Ext => AudioType switch
|
||||||
public string FileNameIntro
|
|
||||||
{
|
|
||||||
get => FileNameIntroOverride ?? $"{Name}Intro.{Ext}";
|
|
||||||
set => FileNameIntroOverride = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string? FileNameLoopOverride = null;
|
|
||||||
public string FileNameLoop
|
|
||||||
{
|
|
||||||
get => FileNameLoopOverride ?? $"{Name}Loop.{Ext}";
|
|
||||||
set => FileNameLoopOverride = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string Ext => AudioType switch
|
|
||||||
{
|
{
|
||||||
AudioType.MPEG => "mp3",
|
AudioType.MPEG => "mp3",
|
||||||
AudioType.WAV => "wav",
|
AudioType.WAV => "wav",
|
||||||
|
@ -783,28 +803,86 @@ namespace MuzikaGromche
|
||||||
};
|
};
|
||||||
|
|
||||||
// Offset of beats. Bigger offset => colors will change later.
|
// Offset of beats. Bigger offset => colors will change later.
|
||||||
|
public float BeatsOffset { get; }
|
||||||
|
|
||||||
|
// Offset of beats, in seconds. Bigger offset => colors will change later.
|
||||||
|
public float BeatsOffsetInSeconds => BeatsOffset / Beats * LoadedLoop.length;
|
||||||
|
|
||||||
|
public float FadeOutBeat { get; }
|
||||||
|
public float FadeOutDuration { get; }
|
||||||
|
|
||||||
|
// Duration of color transition, measured in beats.
|
||||||
|
public float ColorTransitionIn { get; }
|
||||||
|
public float ColorTransitionOut { get; }
|
||||||
|
|
||||||
|
// Easing function for color transitions.
|
||||||
|
public Easing ColorTransitionEasing { get; }
|
||||||
|
|
||||||
|
public float[] FlickerLightsTimeSeries { get; }
|
||||||
|
|
||||||
|
public float[] LyricsTimeSeries { get; }
|
||||||
|
|
||||||
|
// Lyrics line may contain multiple tab-separated alternatives.
|
||||||
|
// In such case, a random number chosen and updated once per loop
|
||||||
|
// is used to select an alternative.
|
||||||
|
// If the chosen alternative is an empty string, lyrics event shall be skipped.
|
||||||
|
public string[] LyricsLines { get; }
|
||||||
|
|
||||||
|
public Palette Palette { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Core audio track implementation with some defaults and config overrides.
|
||||||
|
// Suitable to declare elemnents of SelectableTracksGroup and as a base for standalone selectable tracks.
|
||||||
|
public class CoreAudioTrack : IAudioTrack
|
||||||
|
{
|
||||||
|
public required string Name { get; init; }
|
||||||
|
public required float WindUpTimer { get; init; }
|
||||||
|
public int Beats { get; init; }
|
||||||
|
|
||||||
|
// Shorthand for four beats
|
||||||
|
public int Bars
|
||||||
|
{
|
||||||
|
init => Beats = value * 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int LoopOffset { get; init; } = 0;
|
||||||
|
public AudioType AudioType { get; init; } = AudioType.MPEG;
|
||||||
|
public AudioClip LoadedIntro { get; set; } = null!;
|
||||||
|
public AudioClip LoadedLoop { get; set; } = null!;
|
||||||
|
|
||||||
|
private string? FileNameIntroOverride = null;
|
||||||
|
public string FileNameIntro
|
||||||
|
{
|
||||||
|
get => FileNameIntroOverride ?? $"{Name}Intro.{((IAudioTrack)this).Ext}";
|
||||||
|
init => FileNameIntroOverride = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? FileNameLoopOverride = null;
|
||||||
|
public string FileNameLoop
|
||||||
|
{
|
||||||
|
get => FileNameLoopOverride ?? $"{Name}Loop.{((IAudioTrack)this).Ext}";
|
||||||
|
init => FileNameLoopOverride = value;
|
||||||
|
}
|
||||||
|
|
||||||
public float _BeatsOffset = 0f;
|
public float _BeatsOffset = 0f;
|
||||||
public float BeatsOffset
|
public float BeatsOffset
|
||||||
{
|
{
|
||||||
get => Config.BeatsOffsetOverride ?? _BeatsOffset;
|
get => Config.BeatsOffsetOverride ?? _BeatsOffset;
|
||||||
set => _BeatsOffset = value;
|
init => _BeatsOffset = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Offset of beats, in seconds. Bigger offset => colors will change later.
|
|
||||||
public float BeatsOffsetInSeconds => BeatsOffset / Beats * LoadedLoop.length;
|
|
||||||
|
|
||||||
public float _FadeOutBeat = float.NaN;
|
public float _FadeOutBeat = float.NaN;
|
||||||
public float FadeOutBeat
|
public float FadeOutBeat
|
||||||
{
|
{
|
||||||
get => Config.FadeOutBeatOverride ?? _FadeOutBeat;
|
get => Config.FadeOutBeatOverride ?? _FadeOutBeat;
|
||||||
set => _FadeOutBeat = value;
|
init => _FadeOutBeat = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float _FadeOutDuration = 2f;
|
public float _FadeOutDuration = 2f;
|
||||||
public float FadeOutDuration
|
public float FadeOutDuration
|
||||||
{
|
{
|
||||||
get => Config.FadeOutDurationOverride ?? _FadeOutDuration;
|
get => Config.FadeOutDurationOverride ?? _FadeOutDuration;
|
||||||
set => _FadeOutDuration = value;
|
init => _FadeOutDuration = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Duration of color transition, measured in beats.
|
// Duration of color transition, measured in beats.
|
||||||
|
@ -812,14 +890,14 @@ namespace MuzikaGromche
|
||||||
public float ColorTransitionIn
|
public float ColorTransitionIn
|
||||||
{
|
{
|
||||||
get => Config.ColorTransitionInOverride ?? _ColorTransitionIn;
|
get => Config.ColorTransitionInOverride ?? _ColorTransitionIn;
|
||||||
set => _ColorTransitionIn = value;
|
init => _ColorTransitionIn = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float _ColorTransitionOut = 0.25f;
|
public float _ColorTransitionOut = 0.25f;
|
||||||
public float ColorTransitionOut
|
public float ColorTransitionOut
|
||||||
{
|
{
|
||||||
get => Config.ColorTransitionOutOverride ?? _ColorTransitionOut;
|
get => Config.ColorTransitionOutOverride ?? _ColorTransitionOut;
|
||||||
set => _ColorTransitionOut = value;
|
init => _ColorTransitionOut = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Easing function for color transitions.
|
// Easing function for color transitions.
|
||||||
|
@ -829,14 +907,14 @@ namespace MuzikaGromche
|
||||||
get => Config.ColorTransitionEasingOverride != null
|
get => Config.ColorTransitionEasingOverride != null
|
||||||
? Easing.FindByName(Config.ColorTransitionEasingOverride)
|
? Easing.FindByName(Config.ColorTransitionEasingOverride)
|
||||||
: _ColorTransitionEasing;
|
: _ColorTransitionEasing;
|
||||||
set => _ColorTransitionEasing = value;
|
init => _ColorTransitionEasing = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float[] _FlickerLightsTimeSeries = [];
|
public float[] _FlickerLightsTimeSeries = [];
|
||||||
public float[] FlickerLightsTimeSeries
|
public float[] FlickerLightsTimeSeries
|
||||||
{
|
{
|
||||||
get => Config.FlickerLightsTimeSeriesOverride ?? _FlickerLightsTimeSeries;
|
get => Config.FlickerLightsTimeSeriesOverride ?? _FlickerLightsTimeSeries;
|
||||||
set
|
init
|
||||||
{
|
{
|
||||||
Array.Sort(value);
|
Array.Sort(value);
|
||||||
_FlickerLightsTimeSeries = value;
|
_FlickerLightsTimeSeries = value;
|
||||||
|
@ -877,6 +955,53 @@ namespace MuzikaGromche
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Standalone, top-level, selectable audio track
|
||||||
|
public class SelectableAudioTrack : CoreAudioTrack, ISelectableTrack
|
||||||
|
{
|
||||||
|
public required Language Language { get; init; }
|
||||||
|
public bool IsExplicit { get; init; } = false;
|
||||||
|
ConfigEntry<int> ISelectableTrack.Weight { get; set; } = null!;
|
||||||
|
|
||||||
|
IAudioTrack[] ISelectableTrack.GetTracks() => [this];
|
||||||
|
|
||||||
|
IAudioTrack ISelectableTrack.SelectTrack(int index) => this;
|
||||||
|
|
||||||
|
void ISelectableTrack.Debug()
|
||||||
|
{
|
||||||
|
Debug.Log($"{nameof(MuzikaGromche)} Track \"{Name}\", Intro={LoadedIntro.length:N4}, Loop={LoadedLoop.length:N4}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SelectableTracksGroup : ISelectableTrack
|
||||||
|
{
|
||||||
|
public required string Name { get; init; }
|
||||||
|
public required Language Language { get; init; }
|
||||||
|
public bool IsExplicit { get; init; } = false;
|
||||||
|
ConfigEntry<int> ISelectableTrack.Weight { get; set; } = null!;
|
||||||
|
|
||||||
|
public required IAudioTrack[] Tracks;
|
||||||
|
|
||||||
|
IAudioTrack[] ISelectableTrack.GetTracks() => Tracks;
|
||||||
|
|
||||||
|
IAudioTrack ISelectableTrack.SelectTrack(int index)
|
||||||
|
{
|
||||||
|
if (Tracks.Length == 0)
|
||||||
|
{
|
||||||
|
throw new IndexOutOfRangeException("Tracks list is empty");
|
||||||
|
}
|
||||||
|
return Mod.Index(Tracks, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ISelectableTrack.Debug()
|
||||||
|
{
|
||||||
|
Debug.Log($"{nameof(MuzikaGromche)} Track Group \"{Name}\", Count={Tracks.Length}");
|
||||||
|
foreach (var (track, index) in Tracks.Select((x, i) => (x, i)))
|
||||||
|
{
|
||||||
|
Debug.Log($"{nameof(MuzikaGromche)} Track {index} \"{track.Name}\", Intro={track.LoadedIntro.length:N4}, Loop={track.LoadedLoop.length:N4}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
readonly record struct BeatTimestamp
|
readonly record struct BeatTimestamp
|
||||||
{
|
{
|
||||||
// Number of beats in the loop audio segment.
|
// Number of beats in the loop audio segment.
|
||||||
|
@ -1252,7 +1377,7 @@ namespace MuzikaGromche
|
||||||
|
|
||||||
class BeatTimeState
|
class BeatTimeState
|
||||||
{
|
{
|
||||||
private readonly Track track;
|
private readonly IAudioTrack track;
|
||||||
|
|
||||||
private readonly JesterAudioSourcesState AudioState;
|
private readonly JesterAudioSourcesState AudioState;
|
||||||
|
|
||||||
|
@ -1270,7 +1395,7 @@ namespace MuzikaGromche
|
||||||
|
|
||||||
private bool WindUpZeroBeatEventTriggered = false;
|
private bool WindUpZeroBeatEventTriggered = false;
|
||||||
|
|
||||||
public BeatTimeState(Track track)
|
public BeatTimeState(IAudioTrack track)
|
||||||
{
|
{
|
||||||
if (LyricsRandom == null)
|
if (LyricsRandom == null)
|
||||||
{
|
{
|
||||||
|
@ -1853,7 +1978,7 @@ namespace MuzikaGromche
|
||||||
|
|
||||||
void load()
|
void load()
|
||||||
{
|
{
|
||||||
var palette = Plugin.CurrentTrack?._Palette ?? Palette.DEFAULT;
|
var palette = (Plugin.CurrentTrack as CoreAudioTrack)?._Palette ?? Palette.DEFAULT;
|
||||||
var colors = palette.Colors;
|
var colors = palette.Colors;
|
||||||
var count = Math.Min(colors.Count(), maxCustomPaletteSize);
|
var count = Math.Min(colors.Count(), maxCustomPaletteSize);
|
||||||
|
|
||||||
|
@ -1886,7 +2011,7 @@ namespace MuzikaGromche
|
||||||
const string section = "Timings";
|
const string section = "Timings";
|
||||||
var colorTransitionRange = new AcceptableValueRange<float>(0f, 1f);
|
var colorTransitionRange = new AcceptableValueRange<float>(0f, 1f);
|
||||||
// Declare and initialize early to avoid "Use of unassigned local variable"
|
// Declare and initialize early to avoid "Use of unassigned local variable"
|
||||||
List<(Action<Track?> Load, Action Apply)> entries = [];
|
List<(Action<CoreAudioTrack?> Load, Action Apply)> entries = [];
|
||||||
SyncedEntry<bool> overrideTimingsSyncedEntry = null!;
|
SyncedEntry<bool> overrideTimingsSyncedEntry = null!;
|
||||||
SyncedEntry<float> fadeOutBeatSyncedEntry = null!;
|
SyncedEntry<float> fadeOutBeatSyncedEntry = null!;
|
||||||
SyncedEntry<float> fadeOutDurationSyncedEntry = null!;
|
SyncedEntry<float> fadeOutDurationSyncedEntry = null!;
|
||||||
|
@ -1945,12 +2070,12 @@ namespace MuzikaGromche
|
||||||
registerStruct(colorTransitionOutSyncedEntry, t => t._ColorTransitionOut, x => ColorTransitionOutOverride = x);
|
registerStruct(colorTransitionOutSyncedEntry, t => t._ColorTransitionOut, x => ColorTransitionOutOverride = x);
|
||||||
registerClass(colorTransitionEasingSyncedEntry, t => t._ColorTransitionEasing.Name, x => ColorTransitionEasingOverride = x);
|
registerClass(colorTransitionEasingSyncedEntry, t => t._ColorTransitionEasing.Name, x => ColorTransitionEasingOverride = x);
|
||||||
|
|
||||||
void register<T>(SyncedEntry<T> syncedEntry, Func<Track, T> getter, Action applier)
|
void register<T>(SyncedEntry<T> syncedEntry, Func<CoreAudioTrack, T> getter, Action applier)
|
||||||
{
|
{
|
||||||
CSyncHackAddSyncedEntry(syncedEntry);
|
CSyncHackAddSyncedEntry(syncedEntry);
|
||||||
syncedEntry.SyncHostToLocal();
|
syncedEntry.SyncHostToLocal();
|
||||||
syncedEntry.Changed += (sender, args) => applier();
|
syncedEntry.Changed += (sender, args) => applier();
|
||||||
void loader(Track? track)
|
void loader(CoreAudioTrack? track)
|
||||||
{
|
{
|
||||||
// if track is null, set everything to defaults
|
// if track is null, set everything to defaults
|
||||||
syncedEntry.LocalValue = track == null ? (T)syncedEntry.Entry.DefaultValue : getter(track);
|
syncedEntry.LocalValue = track == null ? (T)syncedEntry.Entry.DefaultValue : getter(track);
|
||||||
|
@ -1958,11 +2083,11 @@ namespace MuzikaGromche
|
||||||
entries.Add((loader, applier));
|
entries.Add((loader, applier));
|
||||||
}
|
}
|
||||||
|
|
||||||
void registerStruct<T>(SyncedEntry<T> syncedEntry, Func<Track, T> getter, Action<T?> setter) where T : struct =>
|
void registerStruct<T>(SyncedEntry<T> syncedEntry, Func<CoreAudioTrack, T> getter, Action<T?> setter) where T : struct =>
|
||||||
register(syncedEntry, getter, () => setter.Invoke(overrideTimingsSyncedEntry.Value ? syncedEntry.Value : null));
|
register(syncedEntry, getter, () => setter.Invoke(overrideTimingsSyncedEntry.Value ? syncedEntry.Value : null));
|
||||||
void registerClass<T>(SyncedEntry<T> syncedEntry, Func<Track, T> getter, Action<T?> setter) where T : class =>
|
void registerClass<T>(SyncedEntry<T> syncedEntry, Func<CoreAudioTrack, T> getter, Action<T?> setter) where T : class =>
|
||||||
register(syncedEntry, getter, () => setter.Invoke(overrideTimingsSyncedEntry.Value ? syncedEntry.Value : null));
|
register(syncedEntry, getter, () => setter.Invoke(overrideTimingsSyncedEntry.Value ? syncedEntry.Value : null));
|
||||||
void registerArray<T>(SyncedEntry<string> syncedEntry, Func<Track, T[]> getter, Action<T[]?> setter, Func<string, T> parser, bool sort = false) where T : struct =>
|
void registerArray<T>(SyncedEntry<string> syncedEntry, Func<CoreAudioTrack, T[]> getter, Action<T[]?> setter, Func<string, T> parser, bool sort = false) where T : struct =>
|
||||||
register(syncedEntry,
|
register(syncedEntry,
|
||||||
(track) => string.Join(", ", getter(track)),
|
(track) => string.Join(", ", getter(track)),
|
||||||
() =>
|
() =>
|
||||||
|
@ -1996,7 +2121,7 @@ namespace MuzikaGromche
|
||||||
var track = Plugin.CurrentTrack;
|
var track = Plugin.CurrentTrack;
|
||||||
foreach (var entry in entries)
|
foreach (var entry in entries)
|
||||||
{
|
{
|
||||||
entry.Load(track);
|
entry.Load(track as CoreAudioTrack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2043,6 +2168,11 @@ namespace MuzikaGromche
|
||||||
|
|
||||||
class MuzikaGromcheJesterNetworkBehaviour : NetworkBehaviour
|
class MuzikaGromcheJesterNetworkBehaviour : NetworkBehaviour
|
||||||
{
|
{
|
||||||
|
// Number of times a selected track has been played.
|
||||||
|
// Increases by 1 with each ChooseTrackServerRpc call.
|
||||||
|
// Resets on SettingChanged.
|
||||||
|
private int SelectedTrackIndex = 0;
|
||||||
|
|
||||||
public override void OnNetworkSpawn()
|
public override void OnNetworkSpawn()
|
||||||
{
|
{
|
||||||
ChooseTrackDeferred();
|
ChooseTrackDeferred();
|
||||||
|
@ -2069,6 +2199,7 @@ namespace MuzikaGromche
|
||||||
|
|
||||||
private void ChooseTrackDeferredDelegate(object sender, EventArgs e)
|
private void ChooseTrackDeferredDelegate(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
SelectedTrackIndex = 0;
|
||||||
ChooseTrackDeferred();
|
ChooseTrackDeferred();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2099,9 +2230,11 @@ namespace MuzikaGromche
|
||||||
[ServerRpc]
|
[ServerRpc]
|
||||||
public void ChooseTrackServerRpc()
|
public void ChooseTrackServerRpc()
|
||||||
{
|
{
|
||||||
var track = Plugin.ChooseTrack();
|
var selectableTrack = Plugin.ChooseTrack();
|
||||||
Debug.Log($"{nameof(MuzikaGromche)} ChooseTrackServerRpc {track.Name}");
|
var audioTrack = selectableTrack.SelectTrack(SelectedTrackIndex);
|
||||||
SetTrackClientRpc(track.Name);
|
Debug.Log($"{nameof(MuzikaGromche)} ChooseTrackServerRpc {selectableTrack.Name} #{SelectedTrackIndex} {audioTrack.Name}");
|
||||||
|
SetTrackClientRpc(audioTrack.Name);
|
||||||
|
SelectedTrackIndex += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2195,6 +2328,8 @@ namespace MuzikaGromche
|
||||||
{
|
{
|
||||||
Plugin.ResetLightColor();
|
Plugin.ResetLightColor();
|
||||||
DiscoBallManager.Disable();
|
DiscoBallManager.Disable();
|
||||||
|
// Rotate track groups
|
||||||
|
__instance.GetComponent<MuzikaGromcheJesterNetworkBehaviour>()?.ChooseTrackServerRpc();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (__instance.previousState == 2 && __state.previousState != 2)
|
if (__instance.previousState == 2 && __state.previousState != 2)
|
||||||
|
|
Loading…
Reference in New Issue