Clean separation between track data and config overrides

In debug builds Config keeps a reference to the last set original track
instance from which it can load original values.
This commit is contained in:
ivan tkachenko 2025-08-22 14:13:10 +03:00
parent 525c0e108f
commit d4d3e15de3
1 changed files with 103 additions and 89 deletions

View File

@ -917,7 +917,32 @@ namespace MuzikaGromche
public Palette Palette { get; } public Palette Palette { get; }
} }
// A proxy audio track with default implementation for every IAudioTrack method that simply forwards requests to the inner IAudioTrack.
public abstract class ProxyAudioTrack(IAudioTrack track) : IAudioTrack
{
internal IAudioTrack Track = track;
string IAudioTrack.Name => Track.Name;
float IAudioTrack.WindUpTimer => Track.WindUpTimer;
int IAudioTrack.Beats => Track.Beats;
int IAudioTrack.LoopOffset => Track.LoopOffset;
AudioType IAudioTrack.AudioType => Track.AudioType;
AudioClip IAudioTrack.LoadedIntro { get => Track.LoadedIntro; set => Track.LoadedIntro = value; }
AudioClip IAudioTrack.LoadedLoop { get => Track.LoadedLoop; set => Track.LoadedLoop = value; }
string IAudioTrack.FileNameIntro => Track.FileNameIntro;
string IAudioTrack.FileNameLoop => Track.FileNameLoop;
float IAudioTrack.BeatsOffset => Track.BeatsOffset;
float IAudioTrack.FadeOutBeat => Track.FadeOutBeat;
float IAudioTrack.FadeOutDuration => Track.FadeOutDuration;
float IAudioTrack.ColorTransitionIn => Track.ColorTransitionIn;
float IAudioTrack.ColorTransitionOut => Track.ColorTransitionOut;
Easing IAudioTrack.ColorTransitionEasing => Track.ColorTransitionEasing;
float[] IAudioTrack.FlickerLightsTimeSeries => Track.FlickerLightsTimeSeries;
float[] IAudioTrack.LyricsTimeSeries => Track.LyricsTimeSeries;
string[] IAudioTrack.LyricsLines => Track.LyricsLines;
Palette IAudioTrack.Palette => Track.Palette;
}
// Core audio track implementation with some defaults and config overrides. // Core audio track implementation with some defaults and config overrides.
// Suitable to declare elemnents of SelectableTracksGroup and as a base for standalone selectable tracks. // Suitable to declare elemnents of SelectableTracksGroup and as a base for standalone selectable tracks.
public class CoreAudioTrack : IAudioTrack public class CoreAudioTrack : IAudioTrack
@ -951,56 +976,17 @@ namespace MuzikaGromche
init => FileNameLoopOverride = value; init => FileNameLoopOverride = value;
} }
public float _BeatsOffset = 0f; public float BeatsOffset { get; init; } = 0f;
public float BeatsOffset public float FadeOutBeat { get; init; } = float.NaN;
{ public float FadeOutDuration { get; init; } = 2f;
get => Config.BeatsOffsetOverride ?? _BeatsOffset; public float ColorTransitionIn { get; init; } = 0.25f;
init => _BeatsOffset = value; public float ColorTransitionOut { get; init; } = 0.25f;
} public Easing ColorTransitionEasing { get; init; } = Easing.OutExpo;
public float _FadeOutBeat = float.NaN;
public float FadeOutBeat
{
get => Config.FadeOutBeatOverride ?? _FadeOutBeat;
init => _FadeOutBeat = value;
}
public float _FadeOutDuration = 2f;
public float FadeOutDuration
{
get => Config.FadeOutDurationOverride ?? _FadeOutDuration;
init => _FadeOutDuration = value;
}
// Duration of color transition, measured in beats.
public float _ColorTransitionIn = 0.25f;
public float ColorTransitionIn
{
get => Config.ColorTransitionInOverride ?? _ColorTransitionIn;
init => _ColorTransitionIn = value;
}
public float _ColorTransitionOut = 0.25f;
public float ColorTransitionOut
{
get => Config.ColorTransitionOutOverride ?? _ColorTransitionOut;
init => _ColorTransitionOut = value;
}
// Easing function for color transitions.
public Easing _ColorTransitionEasing = Easing.OutExpo;
public Easing ColorTransitionEasing
{
get => Config.ColorTransitionEasingOverride != null
? Easing.FindByName(Config.ColorTransitionEasingOverride)
: _ColorTransitionEasing;
init => _ColorTransitionEasing = value;
}
public float[] _FlickerLightsTimeSeries = []; public float[] _FlickerLightsTimeSeries = [];
public float[] FlickerLightsTimeSeries public float[] FlickerLightsTimeSeries
{ {
get => Config.FlickerLightsTimeSeriesOverride ?? _FlickerLightsTimeSeries; get => _FlickerLightsTimeSeries;
init init
{ {
Array.Sort(value); Array.Sort(value);
@ -1008,12 +994,7 @@ namespace MuzikaGromche
} }
} }
public float[] _LyricsTimeSeries = []; public float[] LyricsTimeSeries { get; private set; } = [];
public float[] LyricsTimeSeries
{
get => Config.LyricsTimeSeriesOverride ?? _LyricsTimeSeries;
private set => _LyricsTimeSeries = value;
}
// Lyrics line may contain multiple tab-separated alternatives. // Lyrics line may contain multiple tab-separated alternatives.
// In such case, a random number chosen and updated once per loop // In such case, a random number chosen and updated once per loop
@ -1034,12 +1015,7 @@ namespace MuzikaGromche
} }
} }
public Palette _Palette = Palette.DEFAULT; public Palette Palette { get; set; } = Palette.DEFAULT;
public Palette Palette
{
get => Config.PaletteOverride ?? _Palette;
set => _Palette = value;
}
} }
// Standalone, top-level, selectable audio track // Standalone, top-level, selectable audio track
@ -1853,9 +1829,6 @@ namespace MuzikaGromche
: SyncedConfig2<Config> : SyncedConfig2<Config>
#endif #endif
{ {
// Latest set track, used for loading palette and timings.
public static IAudioTrack? CurrentTrack { get; internal set; } = null;
public static ConfigEntry<bool> DisplayLyrics { get; private set; } = null!; public static ConfigEntry<bool> DisplayLyrics { get; private set; } = null!;
public static ConfigEntry<float> AudioOffset { get; private set; } = null!; public static ConfigEntry<float> AudioOffset { get; private set; } = null!;
@ -1867,16 +1840,45 @@ namespace MuzikaGromche
public static bool ExtrapolateTime { get; private set; } = true; public static bool ExtrapolateTime { get; private set; } = true;
public static bool ShouldSkipWindingPhase { get; private set; } = false; public static bool ShouldSkipWindingPhase { get; private set; } = false;
public static Palette? PaletteOverride { get; private set; } = null; #if DEBUG
// Latest set track, used for loading palette and timings.
private static IAudioTrack? CurrentTrack = null;
// All per-track values that can be overridden
private static float? BeatsOffsetOverride = null;
private static float? FadeOutBeatOverride = null;
private static float? FadeOutDurationOverride = null;
private static float? ColorTransitionInOverride = null;
private static float? ColorTransitionOutOverride = null;
private static string? ColorTransitionEasingOverride = null;
private static float[]? FlickerLightsTimeSeriesOverride = null;
private static float[]? LyricsTimeSeriesOverride = null;
private static Palette? PaletteOverride = null;
public static float? FadeOutBeatOverride { get; private set; } = null; private class AudioTrackWithConfigOverride(IAudioTrack track) : ProxyAudioTrack(track), IAudioTrack
public static float? FadeOutDurationOverride { get; private set; } = null; {
public static float[]? FlickerLightsTimeSeriesOverride { get; private set; } = null; float IAudioTrack.BeatsOffset => BeatsOffsetOverride ?? Track.BeatsOffset;
public static float[]? LyricsTimeSeriesOverride { get; private set; } = null;
public static float? BeatsOffsetOverride { get; private set; } = null; float IAudioTrack.FadeOutBeat => FadeOutBeatOverride ?? Track.FadeOutBeat;
public static float? ColorTransitionInOverride { get; private set; } = null;
public static float? ColorTransitionOutOverride { get; private set; } = null; float IAudioTrack.FadeOutDuration => FadeOutDurationOverride ?? Track.FadeOutDuration;
public static string? ColorTransitionEasingOverride { get; private set; } = null;
float IAudioTrack.ColorTransitionIn => ColorTransitionInOverride ?? Track.ColorTransitionIn;
float IAudioTrack.ColorTransitionOut => ColorTransitionOutOverride ?? Track.ColorTransitionOut;
Easing IAudioTrack.ColorTransitionEasing =>
ColorTransitionEasingOverride != null
? Easing.FindByName(ColorTransitionEasingOverride)
: Track.ColorTransitionEasing;
float[] IAudioTrack.FlickerLightsTimeSeries =>
FlickerLightsTimeSeriesOverride ?? Track.FlickerLightsTimeSeries;
float[] IAudioTrack.LyricsTimeSeries => LyricsTimeSeriesOverride ?? Track.LyricsTimeSeries;
Palette IAudioTrack.Palette => PaletteOverride ?? Track.Palette;
}
#endif
internal Config(ConfigFile configFile) internal Config(ConfigFile configFile)
#if DEBUG #if DEBUG
@ -1955,6 +1957,16 @@ namespace MuzikaGromche
#endif #endif
} }
internal static IAudioTrack OverrideCurrentTrack(IAudioTrack track)
{
#if DEBUG
CurrentTrack = track;
return new AudioTrackWithConfigOverride(track);
#else
return track;
#endif
}
#if DEBUG #if DEBUG
// HACK because CSync doesn't provide an API to register a list of config entries // HACK because CSync doesn't provide an API to register a list of config entries
// See https://github.com/lc-sigurd/CSync/issues/11 // See https://github.com/lc-sigurd/CSync/issues/11
@ -2068,7 +2080,7 @@ namespace MuzikaGromche
void load() void load()
{ {
var palette = (CurrentTrack as CoreAudioTrack)?._Palette ?? Palette.DEFAULT; var palette = CurrentTrack?.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);
@ -2101,7 +2113,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<CoreAudioTrack?> Load, Action Apply)> entries = []; List<(Action<IAudioTrack?> 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!;
@ -2151,21 +2163,21 @@ namespace MuzikaGromche
LethalConfigManager.AddConfigItem(new FloatSliderConfigItem(colorTransitionOutSyncedEntry.Entry, floatSliderOptions)); LethalConfigManager.AddConfigItem(new FloatSliderConfigItem(colorTransitionOutSyncedEntry.Entry, floatSliderOptions));
LethalConfigManager.AddConfigItem(new TextDropDownConfigItem(colorTransitionEasingSyncedEntry.Entry, Default(new TextDropDownOptions()))); LethalConfigManager.AddConfigItem(new TextDropDownConfigItem(colorTransitionEasingSyncedEntry.Entry, Default(new TextDropDownOptions())));
registerStruct(fadeOutBeatSyncedEntry, t => t._FadeOutBeat, x => FadeOutBeatOverride = x); registerStruct(fadeOutBeatSyncedEntry, t => t.FadeOutBeat, x => FadeOutBeatOverride = x);
registerStruct(fadeOutDurationSyncedEntry, t => t._FadeOutDuration, x => FadeOutDurationOverride = x); registerStruct(fadeOutDurationSyncedEntry, t => t.FadeOutDuration, x => FadeOutDurationOverride = x);
registerArray(flickerLightsTimeSeriesSyncedEntry, t => t._FlickerLightsTimeSeries, xs => FlickerLightsTimeSeriesOverride = xs, float.Parse, sort: true); registerArray(flickerLightsTimeSeriesSyncedEntry, t => t.FlickerLightsTimeSeries, xs => FlickerLightsTimeSeriesOverride = xs, float.Parse, sort: true);
registerArray(lyricsTimeSeriesSyncedEntry, t => t._LyricsTimeSeries, xs => LyricsTimeSeriesOverride = xs, float.Parse, sort: true); registerArray(lyricsTimeSeriesSyncedEntry, t => t.LyricsTimeSeries, xs => LyricsTimeSeriesOverride = xs, float.Parse, sort: true);
registerStruct(beatsOffsetSyncedEntry, t => t._BeatsOffset, x => BeatsOffsetOverride = x); registerStruct(beatsOffsetSyncedEntry, t => t.BeatsOffset, x => BeatsOffsetOverride = x);
registerStruct(colorTransitionInSyncedEntry, t => t._ColorTransitionIn, x => ColorTransitionInOverride = x); registerStruct(colorTransitionInSyncedEntry, t => t.ColorTransitionIn, x => ColorTransitionInOverride = x);
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<CoreAudioTrack, T> getter, Action applier) void register<T>(SyncedEntry<T> syncedEntry, Func<IAudioTrack, T> getter, Action applier)
{ {
CSyncHackAddSyncedEntry(syncedEntry); CSyncHackAddSyncedEntry(syncedEntry);
syncedEntry.SyncHostToLocal(); syncedEntry.SyncHostToLocal();
syncedEntry.Changed += (sender, args) => applier(); syncedEntry.Changed += (sender, args) => applier();
void loader(CoreAudioTrack? track) void loader(IAudioTrack? 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);
@ -2173,11 +2185,11 @@ namespace MuzikaGromche
entries.Add((loader, applier)); entries.Add((loader, applier));
} }
void registerStruct<T>(SyncedEntry<T> syncedEntry, Func<CoreAudioTrack, T> getter, Action<T?> setter) where T : struct => void registerStruct<T>(SyncedEntry<T> syncedEntry, Func<IAudioTrack, 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<CoreAudioTrack, T> getter, Action<T?> setter) where T : class => void registerClass<T>(SyncedEntry<T> syncedEntry, Func<IAudioTrack, 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<CoreAudioTrack, T[]> getter, Action<T[]?> setter, Func<string, T> parser, bool sort = false) where T : struct => void registerArray<T>(SyncedEntry<string> syncedEntry, Func<IAudioTrack, 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)),
() => () =>
@ -2208,10 +2220,9 @@ namespace MuzikaGromche
void load() void load()
{ {
var track = CurrentTrack;
foreach (var entry in entries) foreach (var entry in entries)
{ {
entry.Load(track as CoreAudioTrack); entry.Load(CurrentTrack);
} }
} }
@ -2354,7 +2365,10 @@ namespace MuzikaGromche
public void SetTrackClientRpc(string name) public void SetTrackClientRpc(string name)
{ {
Debug.Log($"{nameof(MuzikaGromche)} SetTrackClientRpc {name}"); Debug.Log($"{nameof(MuzikaGromche)} SetTrackClientRpc {name}");
Config.CurrentTrack = CurrentTrack = Plugin.FindTrackNamed(name); if (Plugin.FindTrackNamed(name) is { } track)
{
CurrentTrack = Config.OverrideCurrentTrack(track);
}
} }
[ServerRpc] [ServerRpc]