Add support for fading out, and debug-only config for flickering lights
This commit is contained in:
parent
b8824dbbfb
commit
118eecbb59
|
@ -76,6 +76,8 @@ namespace MuzikaGromche
|
||||||
Bars = 8,
|
Bars = 8,
|
||||||
LoopOffset = 32,
|
LoopOffset = 32,
|
||||||
BeatsOffset = 0.0f,
|
BeatsOffset = 0.0f,
|
||||||
|
FadeOutBeat = -35,
|
||||||
|
FadeOutDuration = 3.3f,
|
||||||
ColorTransitionIn = 0.25f,
|
ColorTransitionIn = 0.25f,
|
||||||
ColorTransitionOut = 0.25f,
|
ColorTransitionOut = 0.25f,
|
||||||
ColorTransitionEasing = Easing.OutExpo,
|
ColorTransitionEasing = Easing.OutExpo,
|
||||||
|
@ -479,6 +481,20 @@ namespace MuzikaGromche
|
||||||
// Offset of beats, in seconds. Bigger offset => colors will change later.
|
// Offset of beats, in seconds. Bigger offset => colors will change later.
|
||||||
public float BeatsOffsetInSeconds => BeatsOffset / Beats * LoadedLoop.length;
|
public float BeatsOffsetInSeconds => BeatsOffset / Beats * LoadedLoop.length;
|
||||||
|
|
||||||
|
public float _FadeOutBeat = float.NaN;
|
||||||
|
public float FadeOutBeat
|
||||||
|
{
|
||||||
|
get => Config.FadeOutBeatOverride ?? _FadeOutBeat;
|
||||||
|
set => _FadeOutBeat = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float _FadeOutDuration = 2f;
|
||||||
|
public float FadeOutDuration
|
||||||
|
{
|
||||||
|
get => Config.FadeOutDurationOverride ?? _FadeOutDuration;
|
||||||
|
set => _FadeOutDuration = value;
|
||||||
|
}
|
||||||
|
|
||||||
// Duration of color transition, measured in beats.
|
// Duration of color transition, measured in beats.
|
||||||
public float _ColorTransitionIn = 0.25f;
|
public float _ColorTransitionIn = 0.25f;
|
||||||
public float ColorTransitionIn
|
public float ColorTransitionIn
|
||||||
|
@ -504,7 +520,16 @@ namespace MuzikaGromche
|
||||||
set => _ColorTransitionEasing = value;
|
set => _ColorTransitionEasing = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float[] FlickerLightsTimeSeries = [];
|
public float[] _FlickerLightsTimeSeries = [];
|
||||||
|
public float[] FlickerLightsTimeSeries
|
||||||
|
{
|
||||||
|
get => Config.FlickerLightsTimeSeriesOverride ?? _FlickerLightsTimeSeries;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Array.Sort(value);
|
||||||
|
_FlickerLightsTimeSeries = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public float[] LyricsTimeSeries { get; private set; }
|
public float[] LyricsTimeSeries { get; private set; }
|
||||||
public string[] LyricsLines { get; private set; }
|
public string[] LyricsLines { get; private set; }
|
||||||
|
@ -846,7 +871,7 @@ namespace MuzikaGromche
|
||||||
List<BaseEvent> events = [];
|
List<BaseEvent> events = [];
|
||||||
|
|
||||||
{
|
{
|
||||||
var colorEvent = GetColorEvent(windUpOffsetTimestamp);
|
var colorEvent = GetColorEvent(loopOffsetSpan, windUpOffsetTimestamp);
|
||||||
if (colorEvent != null)
|
if (colorEvent != null)
|
||||||
{
|
{
|
||||||
events.Add(colorEvent);
|
events.Add(colorEvent);
|
||||||
|
@ -873,22 +898,46 @@ namespace MuzikaGromche
|
||||||
return events;
|
return events;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SetLightsColorEvent GetColorEvent(BeatTimestamp windUpOffsetTimestamp)
|
private SetLightsColorEvent GetColorEvent(BeatTimeSpan loopOffsetSpan, BeatTimestamp windUpOffsetTimestamp)
|
||||||
{
|
{
|
||||||
if (windUpOffsetTimestamp.Beat < -track.ColorTransitionIn)
|
if (FadeOut(loopOffsetSpan) is Color c1)
|
||||||
{
|
{
|
||||||
// TODO: Maybe fade out?
|
return new SetLightsColorEvent(c1);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if (ColorFromPaletteAtTimestamp(windUpOffsetTimestamp) is Color c2)
|
||||||
{
|
{
|
||||||
var color = ColorFromPaletteAtTimestamp(windUpOffsetTimestamp);
|
return new SetLightsColorEvent(c2);
|
||||||
return new SetLightsColorEvent(color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Color ColorFromPaletteAtTimestamp(BeatTimestamp timestamp)
|
private Color? FadeOut(BeatTimeSpan loopOffsetSpan)
|
||||||
{
|
{
|
||||||
|
var beat = loopOffsetSpan.BeatToInclusive;
|
||||||
|
var fadeOutStart = track.FadeOutBeat;
|
||||||
|
var fadeOutEnd = fadeOutStart + track.FadeOutDuration;
|
||||||
|
|
||||||
|
if (track.FadeOutBeat < beat && beat <= fadeOutEnd)
|
||||||
|
{
|
||||||
|
var x = (beat - track.FadeOutBeat) / track.FadeOutDuration;
|
||||||
|
var t = Mathf.Clamp(Easing.Linear.Eval(x), 0f, 1f);
|
||||||
|
return Color.Lerp(Color.white, Color.black, t);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color? ColorFromPaletteAtTimestamp(BeatTimestamp timestamp)
|
||||||
|
{
|
||||||
|
if (timestamp.Beat <= -track.ColorTransitionIn)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Imagine the timeline as a sequence of clips without gaps where each clip is a whole beat long.
|
// Imagine the timeline as a sequence of clips without gaps where each clip is a whole beat long.
|
||||||
// Transition is when two adjacent clips need to be combined with some blend function(t)
|
// Transition is when two adjacent clips need to be combined with some blend function(t)
|
||||||
// where t is a factor in range 0..1 expressed as (time - Transition.Start) / Transition.Length;
|
// where t is a factor in range 0..1 expressed as (time - Transition.Start) / Transition.Length;
|
||||||
|
@ -952,7 +1001,7 @@ namespace MuzikaGromche
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return Color.white;
|
return float.IsNaN(track.FadeOutBeat) ? Color.black : Color.white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1117,6 +1166,9 @@ namespace MuzikaGromche
|
||||||
|
|
||||||
public static Palette PaletteOverride { get; private set; } = null;
|
public static Palette PaletteOverride { get; private set; } = null;
|
||||||
|
|
||||||
|
public static float? FadeOutBeatOverride { get; private set; } = null;
|
||||||
|
public static float? FadeOutDurationOverride { get; private set; } = null;
|
||||||
|
public static float[] FlickerLightsTimeSeriesOverride { get; private set; } = null;
|
||||||
public static float? BeatsOffsetOverride { get; private set; } = null;
|
public static float? BeatsOffsetOverride { get; private set; } = null;
|
||||||
public static float? ColorTransitionInOverride { get; private set; } = null;
|
public static float? ColorTransitionInOverride { get; private set; } = null;
|
||||||
public static float? ColorTransitionOutOverride { get; private set; } = null;
|
public static float? ColorTransitionOutOverride { get; private set; } = null;
|
||||||
|
@ -1319,6 +1371,9 @@ namespace MuzikaGromche
|
||||||
// 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<Track> Load, Action Apply)> entries = [];
|
||||||
SyncedEntry<bool> overrideTimingsSyncedEntry = null;
|
SyncedEntry<bool> overrideTimingsSyncedEntry = null;
|
||||||
|
SyncedEntry<float> fadeOutBeatSyncedEntry = null;
|
||||||
|
SyncedEntry<float> fadeOutDurationSyncedEntry = null;
|
||||||
|
SyncedEntry<string> flickerLightsTimeSeriesSyncedEntry = null;
|
||||||
SyncedEntry<float> beatsOffsetSyncedEntry = null;
|
SyncedEntry<float> beatsOffsetSyncedEntry = null;
|
||||||
SyncedEntry<float> colorTransitionInSyncedEntry = null;
|
SyncedEntry<float> colorTransitionInSyncedEntry = null;
|
||||||
SyncedEntry<float> colorTransitionOutSyncedEntry = null;
|
SyncedEntry<float> colorTransitionOutSyncedEntry = null;
|
||||||
|
@ -1336,6 +1391,12 @@ namespace MuzikaGromche
|
||||||
overrideTimingsSyncedEntry.Changed += (sender, args) => apply();
|
overrideTimingsSyncedEntry.Changed += (sender, args) => apply();
|
||||||
overrideTimingsSyncedEntry.SyncHostToLocal();
|
overrideTimingsSyncedEntry.SyncHostToLocal();
|
||||||
|
|
||||||
|
fadeOutBeatSyncedEntry = configFile.BindSyncedEntry(section, "Fade Out Beat", 0f,
|
||||||
|
new ConfigDescription("The beat at which to start fading out", new AcceptableValueRange<float>(-1000f, 0)));
|
||||||
|
fadeOutDurationSyncedEntry = configFile.BindSyncedEntry(section, "Fade Out Duration", 0f,
|
||||||
|
new ConfigDescription("Duration of fading out", new AcceptableValueRange<float>(0, 100)));
|
||||||
|
flickerLightsTimeSeriesSyncedEntry = configFile.BindSyncedEntry(section, "Flicker Lights", "",
|
||||||
|
new ConfigDescription("Time series of beat offsets when to flicker the lights"));
|
||||||
beatsOffsetSyncedEntry = configFile.BindSyncedEntry(section, "Beats Offset", 0f,
|
beatsOffsetSyncedEntry = configFile.BindSyncedEntry(section, "Beats Offset", 0f,
|
||||||
new ConfigDescription("How much to offset the whole beat. More is later", new AcceptableValueRange<float>(-0.5f, 0.5f)));
|
new ConfigDescription("How much to offset the whole beat. More is later", new AcceptableValueRange<float>(-0.5f, 0.5f)));
|
||||||
colorTransitionInSyncedEntry = configFile.BindSyncedEntry(section, "Color Transition In", 0.25f,
|
colorTransitionInSyncedEntry = configFile.BindSyncedEntry(section, "Color Transition In", 0.25f,
|
||||||
|
@ -1346,11 +1407,17 @@ namespace MuzikaGromche
|
||||||
new ConfigDescription("Interpolation/easing method to use for color transitions", new AcceptableValueList<string>(Easing.AllNames)));
|
new ConfigDescription("Interpolation/easing method to use for color transitions", new AcceptableValueList<string>(Easing.AllNames)));
|
||||||
|
|
||||||
var floatSliderOptions = Default(new FloatSliderOptions());
|
var floatSliderOptions = Default(new FloatSliderOptions());
|
||||||
|
LethalConfigManager.AddConfigItem(new FloatSliderConfigItem(fadeOutBeatSyncedEntry.Entry, floatSliderOptions));
|
||||||
|
LethalConfigManager.AddConfigItem(new FloatSliderConfigItem(fadeOutDurationSyncedEntry.Entry, floatSliderOptions));
|
||||||
|
LethalConfigManager.AddConfigItem(new TextInputFieldConfigItem(flickerLightsTimeSeriesSyncedEntry.Entry, Default(new TextInputFieldOptions())));
|
||||||
LethalConfigManager.AddConfigItem(new FloatSliderConfigItem(beatsOffsetSyncedEntry.Entry, floatSliderOptions));
|
LethalConfigManager.AddConfigItem(new FloatSliderConfigItem(beatsOffsetSyncedEntry.Entry, floatSliderOptions));
|
||||||
LethalConfigManager.AddConfigItem(new FloatSliderConfigItem(colorTransitionInSyncedEntry.Entry, floatSliderOptions));
|
LethalConfigManager.AddConfigItem(new FloatSliderConfigItem(colorTransitionInSyncedEntry.Entry, floatSliderOptions));
|
||||||
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(fadeOutDurationSyncedEntry, t => t._FadeOutDuration, x => FadeOutDurationOverride = x);
|
||||||
|
registerArray(flickerLightsTimeSeriesSyncedEntry, t => t._FlickerLightsTimeSeries, xs => FlickerLightsTimeSeriesOverride = 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);
|
||||||
|
@ -1373,6 +1440,34 @@ namespace MuzikaGromche
|
||||||
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<Track, 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 =>
|
||||||
|
register(syncedEntry,
|
||||||
|
(track) => string.Join(", ", getter(track)),
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
var values = parseStringArray(syncedEntry.Value, parser, sort);
|
||||||
|
if (values != null)
|
||||||
|
{
|
||||||
|
// ensure the entry is sorted and formatted
|
||||||
|
syncedEntry.LocalValue = string.Join(", ", values);
|
||||||
|
}
|
||||||
|
setter.Invoke(overrideTimingsSyncedEntry.Value ? values : null);
|
||||||
|
});
|
||||||
|
|
||||||
|
T[] parseStringArray<T>(string str, Func<string, T> parser, bool sort = false) where T: struct
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
T[] xs = str.Replace(" ", "").Split(",").Select(parser).ToArray();
|
||||||
|
Array.Sort(xs);
|
||||||
|
return xs;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Debug.Log($"{nameof(MuzikaGromche)} Unable to parse array: {e}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void load()
|
void load()
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue