diff --git a/Directory.Packages.props b/Directory.Packages.props
index a3750c7fd..560487eb0 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -44,24 +44,24 @@
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
+
@@ -90,6 +90,10 @@
+
+
diff --git a/ErsatzTV.Infrastructure.Sqlite/ErsatzTV.Infrastructure.Sqlite.csproj b/ErsatzTV.Infrastructure.Sqlite/ErsatzTV.Infrastructure.Sqlite.csproj
index d1cabae48..c42756d17 100644
--- a/ErsatzTV.Infrastructure.Sqlite/ErsatzTV.Infrastructure.Sqlite.csproj
+++ b/ErsatzTV.Infrastructure.Sqlite/ErsatzTV.Infrastructure.Sqlite.csproj
@@ -16,6 +16,9 @@
+
+
diff --git a/ErsatzTV.Infrastructure.Tests/Streaming/Graphics/OpacityExpressionHelperTests.cs b/ErsatzTV.Infrastructure.Tests/Streaming/Graphics/OpacityExpressionHelperTests.cs
new file mode 100644
index 000000000..f98357fba
--- /dev/null
+++ b/ErsatzTV.Infrastructure.Tests/Streaming/Graphics/OpacityExpressionHelperTests.cs
@@ -0,0 +1,52 @@
+using ErsatzTV.Infrastructure.Streaming.Graphics;
+using NCalc;
+using NUnit.Framework;
+using Shouldly;
+
+namespace ErsatzTV.Infrastructure.Tests.Streaming.Graphics;
+
+[TestFixture]
+public class OpacityExpressionHelperTests
+{
+ // Exercises the NCalc custom-function wiring (FunctionEventArgs / FunctionData under NCalc 6.x)
+ // exactly the way the graphics elements do: build an Expression, hook EvaluateFunction, evaluate.
+ private static float Evaluate(string expressionString, TimeSpan timeOfDay)
+ {
+ var expression = new Expression(expressionString);
+ expression.EvaluateFunction += OpacityExpressionHelper.EvaluateFunction;
+ return OpacityExpressionHelper.GetOpacity(
+ expression,
+ timeOfDay,
+ TimeSpan.Zero,
+ TimeSpan.Zero,
+ TimeSpan.Zero);
+ }
+
+ // LinearFadePoints(time, start=0, peakStart=10, peakEnd=20, end=30)
+ [TestCase(5, 0.5)] // fade in: (5-0)/(10-0)
+ [TestCase(15, 1.0)] // solid
+ [TestCase(25, 0.5)] // fade out: (30-25)/(30-20)
+ [TestCase(35, 0.0)] // past end
+ public void LinearFadePoints_ReturnsExpectedOpacity(int timeOfDaySeconds, double expected)
+ {
+ float opacity = Evaluate(
+ "LinearFadePoints(time_of_day_seconds, 0, 10, 20, 30)",
+ TimeSpan.FromSeconds(timeOfDaySeconds));
+
+ opacity.ShouldBe((float)expected, 0.0001);
+ }
+
+ // LinearFadeDuration(time, start=0, fadeSeconds=10, peakSeconds=10)
+ // -> peakStart=10, peakEnd=20, end=30 (same curve as the LinearFadePoints case above)
+ [TestCase(5, 0.5)] // mid fade in
+ [TestCase(15, 1.0)] // solid
+ [TestCase(25, 0.5)] // mid fade out
+ public void LinearFadeDuration_ReturnsExpectedOpacity(int timeOfDaySeconds, double expected)
+ {
+ float opacity = Evaluate(
+ "LinearFadeDuration(time_of_day_seconds, 0, 10, 10)",
+ TimeSpan.FromSeconds(timeOfDaySeconds));
+
+ opacity.ShouldBe((float)expected, 0.0001);
+ }
+}
diff --git a/ErsatzTV.Infrastructure/Streaming/Graphics/OpacityExpressionHelper.cs b/ErsatzTV.Infrastructure/Streaming/Graphics/OpacityExpressionHelper.cs
index 93a82ea3f..3b9f07c53 100644
--- a/ErsatzTV.Infrastructure/Streaming/Graphics/OpacityExpressionHelper.cs
+++ b/ErsatzTV.Infrastructure/Streaming/Graphics/OpacityExpressionHelper.cs
@@ -6,37 +6,37 @@ namespace ErsatzTV.Infrastructure.Streaming.Graphics;
public static class OpacityExpressionHelper
{
- public static void EvaluateFunction(string name, FunctionArgs args)
+ public static void EvaluateFunction(string name, FunctionEventArgs args)
{
switch (name)
{
case "LinearFadePoints":
{
- if (args.Parameters.Length != 5)
+ if (args.Parameters.Count != 5)
{
throw new ArgumentException("LinearFadePoints() requires 5 arguments.");
}
- var time = Convert.ToDouble(args.Parameters[0].Evaluate(), CultureInfo.CurrentCulture);
- var start = Convert.ToDouble(args.Parameters[1].Evaluate(), CultureInfo.CurrentCulture);
- var peakStart = Convert.ToDouble(args.Parameters[2].Evaluate(), CultureInfo.CurrentCulture);
- var peakEnd = Convert.ToDouble(args.Parameters[3].Evaluate(), CultureInfo.CurrentCulture);
- var end = Convert.ToDouble(args.Parameters[4].Evaluate(), CultureInfo.CurrentCulture);
+ var time = Convert.ToDouble(args.Parameters.Evaluate(0), CultureInfo.CurrentCulture);
+ var start = Convert.ToDouble(args.Parameters.Evaluate(1), CultureInfo.CurrentCulture);
+ var peakStart = Convert.ToDouble(args.Parameters.Evaluate(2), CultureInfo.CurrentCulture);
+ var peakEnd = Convert.ToDouble(args.Parameters.Evaluate(3), CultureInfo.CurrentCulture);
+ var end = Convert.ToDouble(args.Parameters.Evaluate(4), CultureInfo.CurrentCulture);
args.Result = LinearFadePoints(time, start, peakStart, peakEnd, end);
break;
}
case "LinearFadeDuration":
{
- if (args.Parameters.Length != 4)
+ if (args.Parameters.Count != 4)
{
throw new ArgumentException("LinearFadeDuration() requires 4 arguments.");
}
- var time = Convert.ToDouble(args.Parameters[0].Evaluate(), CultureInfo.CurrentCulture);
- var start = Convert.ToDouble(args.Parameters[1].Evaluate(), CultureInfo.CurrentCulture);
- var fadeSeconds = Convert.ToDouble(args.Parameters[2].Evaluate(), CultureInfo.CurrentCulture);
- var peakSeconds = Convert.ToDouble(args.Parameters[3].Evaluate(), CultureInfo.CurrentCulture);
+ var time = Convert.ToDouble(args.Parameters.Evaluate(0), CultureInfo.CurrentCulture);
+ var start = Convert.ToDouble(args.Parameters.Evaluate(1), CultureInfo.CurrentCulture);
+ var fadeSeconds = Convert.ToDouble(args.Parameters.Evaluate(2), CultureInfo.CurrentCulture);
+ var peakSeconds = Convert.ToDouble(args.Parameters.Evaluate(3), CultureInfo.CurrentCulture);
args.Result = LinearFadeDuration(time, start, fadeSeconds, peakSeconds);
break;