Runtime and build - Move all projects to .NET 10 with SDK-style project files and a shared Directory.Build.props (WIN/LINUX, x86/x64 defines) - Load managed and unmanaged dependencies from libs/managed and libs/unmanaged through assembly and native library resolvers - Compile party modes with Roslyn instead of CodeDom - Update AppVeyor to the .NET 10 SDK and dotnet test - Download the FFmpeg and gstreamer runtimes from the dependencies repository during the build - Remove the VC++ 2015-2022 runtime check, no dependency needs it anymore Graphics and windowing - Replace the Direct3D 9 renderer with Direct3D 11 (Vortice) - Pace Direct3D 11 frames with a frame latency waitable swap chain - Move window, input and message boxes to SDL3 - Replace System.Drawing with SkiaSharp and WinForms with SDL3 and a software renderer Video and audio - Replace Acinerella with FFmpeg 8.1.3 (FFmpeg.AutoGen) - Add hardware accelerated video decoding (D3D11VA, DXVA2, VideoToolbox, VAAPI, VDPAU) with software fallback - Convert YUV video frames to RGB on the GPU in Direct3D 11 and OpenGL - Add the video options HardwareDecoding, GpuColorConversion, DecoderThreads and VideoDownscaleResolution - Update OpenAL to OpenTK 4 and NAudio recording to WasapiRecorder Fixes - Fix the song preview start and shorten the reporter buttons - Fix a crash when opening the highscore without a selected song - Use the options slide highlight in the party mode screens to avoid brightness flicker on VRR displays - Disable GameInput in the x86 build, SDL3 crashes in GameInputCreate on 32-bit systems with the GameInput runtime installed - Fix a truncation warning in PitchTracker
395 lines
14 KiB
C#
395 lines
14 KiB
C#
#region license
|
|
// This file is part of Vocaluxe.
|
|
//
|
|
// Vocaluxe is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Vocaluxe is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Vocaluxe. If not, see <http://www.gnu.org/licenses/>.
|
|
#endregion
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text.RegularExpressions;
|
|
using SkiaSharp;
|
|
using VocaluxeLib.Log;
|
|
|
|
namespace VocaluxeLib
|
|
{
|
|
public static class CHelper
|
|
{
|
|
private static readonly Regex _TextFileTypesRegex = new Regex("\\.txt|\\.txd", RegexOptions.IgnoreCase);
|
|
private static readonly Regex _SoundFileTypesRegex = new Regex("\\.mp3|\\.wma|\\.ogg|\\.wav", RegexOptions.IgnoreCase);
|
|
private static readonly Regex _ImageFileTypesRegex = new Regex("\\.jpg|\\.jpeg|\\.png|\\.gif", RegexOptions.IgnoreCase);
|
|
|
|
public static int CombinationCount(int n, int k)
|
|
{
|
|
if (k > n)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (k == 0 || k == n)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
if (k < 0 || n <= 0)
|
|
{
|
|
return 0; //is not defined
|
|
}
|
|
|
|
if (k * 2 > n)
|
|
{
|
|
k = n - k;
|
|
}
|
|
|
|
long result = n;
|
|
|
|
for (long i = 1; i <= k - 1; i++)
|
|
{
|
|
result = result * (n - i) / (i + 1);
|
|
}
|
|
|
|
return (int)result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Concat strings into one string with ", " as separator.
|
|
/// </summary>
|
|
public static string ListStrings(string[] str)
|
|
{
|
|
var result = string.Empty;
|
|
for (var i = 0; i < str.Length; i++)
|
|
{
|
|
result += str[i];
|
|
if (i < str.Length - 1)
|
|
{
|
|
result += ", ";
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Places a rect within the bounds maintaining the aspectRatio and using the given aspect
|
|
/// </summary>
|
|
/// <param name="bounds">Bounds to fit the rect in</param>
|
|
/// <param name="aspectRatio">The original aspectRatio of the rect/image/...</param>
|
|
/// <param name="aspect">
|
|
/// Crop: No empty space in bounds but has overhanging parts (same on both sides)<br />
|
|
/// LetterBox: Fit the long side possibly leaving some space on the other (rect will be centered)<br />
|
|
/// StretcH: Just fit the rect in the bounds (rect=bounds)
|
|
/// </param>
|
|
public static SRectF FitInBounds(SRectF bounds, float aspectRatio, EAspect aspect)
|
|
{
|
|
if (aspect == EAspect.Stretch)
|
|
{
|
|
return bounds;
|
|
}
|
|
|
|
var boundsAspectRatio = bounds.W / bounds.H;
|
|
|
|
float scaledWidth, scaledHeight;
|
|
|
|
switch (aspect)
|
|
{
|
|
case EAspect.Automatic:
|
|
case EAspect.Crop:
|
|
if (boundsAspectRatio >= aspectRatio)
|
|
{
|
|
scaledWidth = bounds.W;
|
|
scaledHeight = bounds.W / aspectRatio;
|
|
}
|
|
else
|
|
{
|
|
scaledHeight = bounds.H;
|
|
scaledWidth = bounds.H * aspectRatio;
|
|
}
|
|
|
|
break;
|
|
case EAspect.Zoom1:
|
|
if (boundsAspectRatio >= aspectRatio)
|
|
{
|
|
scaledWidth = bounds.W * 1.33f;
|
|
scaledHeight = bounds.W * 1.33f / aspectRatio;
|
|
}
|
|
else
|
|
{
|
|
scaledHeight = bounds.H / 1.33f;
|
|
scaledWidth = bounds.H / 1.33f * aspectRatio;
|
|
}
|
|
|
|
break;
|
|
case EAspect.Zoom2:
|
|
if (boundsAspectRatio >= aspectRatio)
|
|
{
|
|
scaledWidth = bounds.W * 1.17f;
|
|
scaledHeight = bounds.W * 1.17f / aspectRatio;
|
|
}
|
|
else
|
|
{
|
|
scaledHeight = bounds.H / 1.17f;
|
|
scaledWidth = bounds.H / 1.17f * aspectRatio;
|
|
}
|
|
|
|
break;
|
|
case EAspect.LetterBox:
|
|
if (boundsAspectRatio < aspectRatio)
|
|
{
|
|
scaledWidth = bounds.W;
|
|
scaledHeight = bounds.W / aspectRatio;
|
|
}
|
|
else
|
|
{
|
|
scaledHeight = bounds.H;
|
|
scaledWidth = bounds.H * aspectRatio;
|
|
}
|
|
|
|
break;
|
|
case EAspect.PillarBox:
|
|
scaledWidth = bounds.W * 0.77f;
|
|
scaledHeight = bounds.W / aspectRatio;
|
|
break;
|
|
default:
|
|
return bounds;
|
|
}
|
|
|
|
var left = (bounds.W - scaledWidth) / 2 + bounds.X;
|
|
var top = (bounds.H - scaledHeight) / 2 + bounds.Y;
|
|
|
|
return new SRectF(left, top, scaledWidth, scaledHeight, bounds.Z);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a list with all files in the given path that match a given pattern
|
|
/// </summary>
|
|
/// <param name="path">Path to search</param>
|
|
/// <param name="searchPattern">Pattern to match (e.g. "*.jpg")</param>
|
|
/// <param name="recursive">Search directories recursively</param>
|
|
/// <param name="fullpath">False for just file names, True for full path</param>
|
|
/// <returns>List of file names</returns>
|
|
public static IEnumerable<string> ListFiles(string path, string searchPattern, bool recursive = false, bool fullpath = false)
|
|
{
|
|
var files = Enumerable.Empty<string>();
|
|
var dir = new DirectoryInfo(path);
|
|
if (!dir.Exists)
|
|
{
|
|
return files;
|
|
}
|
|
|
|
if (recursive && !fullpath)
|
|
{
|
|
throw new NotSupportedException("recursive file listing with relative path names is not supported");
|
|
}
|
|
|
|
try
|
|
{
|
|
files = Directory.EnumerateFiles(dir.FullName, searchPattern, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
|
|
files = files.Select(fullpath ? (Func<string, string>)Path.GetFullPath : Path.GetFileName);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
CLog.Error(e, "Error listing files for path " + path + " with search pattern " + searchPattern);
|
|
return Enumerable.Empty<string>();
|
|
}
|
|
|
|
return files;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a list with all files in the given path that match a given extension pattern
|
|
/// </summary>
|
|
/// <param name="path">Path to search</param>
|
|
/// <param name="extPattern">File extension regex to match (e.g. "\\.jpg|\\.png")</param>
|
|
/// <param name="recursive">Search directories recursively</param>
|
|
/// <param name="fullpath">False for just file names, True for full path</param>
|
|
/// <returns>List of file names</returns>
|
|
public static IEnumerable<string> ListFilesForExtPattern(string path, Regex extPattern, bool recursive = false, bool fullpath = false)
|
|
{
|
|
var files = Enumerable.Empty<string>();
|
|
var dir = new DirectoryInfo(path);
|
|
if (!dir.Exists)
|
|
{
|
|
return files;
|
|
}
|
|
|
|
if (recursive && !fullpath)
|
|
{
|
|
throw new NotSupportedException("recursive file listing with relative path names is not supported");
|
|
}
|
|
|
|
try
|
|
{
|
|
files = Directory.EnumerateFiles(path, "*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)
|
|
.Where(file => extPattern.IsMatch(Path.GetExtension(file)));
|
|
files = files.Select(fullpath ? (Func<string, string>)Path.GetFullPath : Path.GetFileName);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
CLog.Error(e, "Error listing files for path " + path + " with regex " + extPattern.ToString());
|
|
return Enumerable.Empty<string>();
|
|
}
|
|
|
|
return files;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a list with all text files in the given path
|
|
/// </summary>
|
|
/// <param name="path">Path to search for</param>
|
|
/// <param name="recursive">Search directories recursively</param>
|
|
/// <param name="fullpath">False for just file names, True for full path</param>
|
|
/// <returns>List of text file names</returns>
|
|
public static IEnumerable<string> ListTextFiles(string path, bool recursive = false, bool fullpath = false)
|
|
{
|
|
return ListFilesForExtPattern(path, _TextFileTypesRegex, recursive, fullpath);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a list with all image files in the given path
|
|
/// </summary>
|
|
/// <param name="path">Path to search for</param>
|
|
/// <param name="recursive">Search directories recursively</param>
|
|
/// <param name="fullpath">False for just file names, True for full path</param>
|
|
/// <returns>List of image file names</returns>
|
|
public static IEnumerable<string> ListImageFiles(string path, bool recursive = false, bool fullpath = false)
|
|
{
|
|
return ListFilesForExtPattern(path, _ImageFileTypesRegex, recursive, fullpath);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a list with all image files in the given path
|
|
/// </summary>
|
|
/// <param name="path">Path to search for</param>
|
|
/// <param name="recursive">Search directories recursively</param>
|
|
/// <param name="fullpath">False for just file names, True for full path</param>
|
|
/// <returns>List of image file names</returns>
|
|
public static IEnumerable<string> ListSoundFiles(string path, bool recursive = false, bool fullpath = false)
|
|
{
|
|
return ListFilesForExtPattern(path, _SoundFileTypesRegex, recursive, fullpath);
|
|
}
|
|
|
|
public static bool TryParse<T>(string value, out T result, bool ignoreCase = false)
|
|
where T : struct
|
|
{
|
|
result = default(T);
|
|
try
|
|
{
|
|
result = (T)Enum.Parse(typeof(T), value, ignoreCase);
|
|
return true;
|
|
}
|
|
catch { }
|
|
|
|
return false;
|
|
}
|
|
|
|
public static bool TryParse(string value, out float result)
|
|
{
|
|
value = value.Replace(',', '.');
|
|
return Single.TryParse(value, NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign, NumberFormatInfo.InvariantInfo, out result);
|
|
}
|
|
|
|
public static bool IsInBounds(SRectF bounds, SMouseEvent mouseEvent)
|
|
{
|
|
return IsInBounds(bounds, mouseEvent.X, mouseEvent.Y);
|
|
}
|
|
|
|
public static bool IsInBounds(SRectF bounds, int x, int y)
|
|
{
|
|
return ((float)x).IsInRange(bounds.X, bounds.X + bounds.W) && ((float)y).IsInRange(bounds.Y, bounds.Y + bounds.H);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a filename that is unique in that path
|
|
/// </summary>
|
|
/// <param name="path">Path in which the file should be stored</param>
|
|
/// <param name="filename">filename (including extension)</param>
|
|
/// <param name="withPath">Whether the fullpath or only the filename should be returned</param>
|
|
/// <returns></returns>
|
|
public static string GetUniqueFileName(string path, string filename, bool withPath = true)
|
|
{
|
|
var ext = Path.GetExtension(filename);
|
|
filename = Path.GetFileNameWithoutExtension(filename) ?? "1";
|
|
if (File.Exists(Path.Combine(path, filename + ext)))
|
|
{
|
|
var i = 1;
|
|
while (File.Exists(Path.Combine(path, filename + "_" + i + ext)))
|
|
{
|
|
i++;
|
|
}
|
|
|
|
filename += "_" + i;
|
|
}
|
|
|
|
if (withPath)
|
|
{
|
|
filename = Path.Combine(path, filename);
|
|
}
|
|
|
|
return filename + ext;
|
|
}
|
|
|
|
public static int Sum(int n)
|
|
{
|
|
return (n * n + n) / 2;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads a bitmap from a file logging errors
|
|
/// </summary>
|
|
/// <param name="filePath">Full path to image file</param>
|
|
/// <returns>Bitmap or null on error</returns>
|
|
public static SKBitmap LoadBitmap(string filePath)
|
|
{
|
|
if (!File.Exists(filePath))
|
|
{
|
|
CLog.Error("Can't find File: " + filePath);
|
|
return null;
|
|
}
|
|
|
|
SKBitmap bmp;
|
|
try
|
|
{
|
|
bmp = SKBitmap.Decode(filePath);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
bmp = null;
|
|
}
|
|
|
|
if (bmp == null)
|
|
{
|
|
CLog.Error("Error loading bitmap: " + filePath);
|
|
return null;
|
|
}
|
|
|
|
return bmp;
|
|
}
|
|
|
|
public static float EaseOutBack(float t)
|
|
{
|
|
const float overshoot = 1.70158f;
|
|
t -= 1f;
|
|
return t * t * ((overshoot + 1f) * t + overshoot) + 1f;
|
|
}
|
|
|
|
public static float EaseOutCubic(float t)
|
|
{
|
|
t = 1f - t;
|
|
return 1f - t * t * t;
|
|
}
|
|
}
|
|
} |