using System;
using System.IO;
using System.Text.RegularExpressions;
using T3.Core.Settings;
#nullable enable
namespace T3.Core.Audio;
///
/// Resolves file-system paths and session indices for live-session recordings.
///
///
///
/// Recording sessions share a single incrementing index across audio and data files; the
/// prefix distinguishes the kind so a clip's name reads as its type at a glance:
///
///
/// - AudioRec-007-mic1.wav — single audio source, session 7
/// - DataRec-007.data — IO data capture for the same session
///
///
/// scans for both prefixes (and the legacy rec- name,
/// so indices don't collide with recordings made before the rename) so an audio-only
/// session still bumps the counter that a later data-only session sees.
///
///
/// Recorders write to during capture; on stop the finalised file is
/// imported into the project's Assets/ (the canonical location the clip references) and the staging
/// copy is removed.
///
///
public static class RecordingPaths
{
///
/// Temp staging directory for in-progress recordings: %APPDATA%\TiXL<version>\Tmp\Recordings\.
/// Transient — the finalised file is imported into the project's Assets/ on stop and the staging
/// copy deleted, so nothing here is meant to persist.
///
public static string TempRecordingsDirectory => Path.Combine(FileLocations.TempFolder, "Recordings");
/// Filename prefix for IO-data recordings (DataRec-NNN.data).
public const string DataRecordingPrefix = "DataRec";
/// Filename prefix for audio recordings (AudioRec-NNN.wav).
public const string AudioRecordingPrefix = "AudioRec";
///
/// Returns N+1 where N is the highest session index found across any
/// combination of the supplied directories, recognising both current prefixes and the
/// legacy rec- name. Returns 1 if nothing matches. Non-existent directories are
/// skipped silently.
///
public static int NextSessionIndex(params string[] directories)
{
var highest = 0;
foreach (var directory in directories)
{
if (string.IsNullOrEmpty(directory) || !Directory.Exists(directory))
continue;
foreach (var path in Directory.EnumerateFiles(directory))
{
var match = _sessionIndexRegex.Match(Path.GetFileName(path));
if (!match.Success)
continue;
if (int.TryParse(match.Groups[1].Value, out var index) && index > highest)
highest = index;
}
}
return highest + 1;
}
///
/// Builds a recording filename of the form {prefix}-NNN[-suffix].extension.
/// Pads the index to three digits; the suffix is omitted when null or empty.
///
/// Recording-kind prefix — or .
/// Session index from .
/// File extension including the leading dot (e.g. .wav).
/// Optional source identifier (e.g. mic1, loopback).
public static string BuildFileName(string prefix, int sessionIndex, string extension, string? suffix = null)
{
var sanitisedSuffix = SanitiseSuffix(suffix);
var indexPart = sessionIndex.ToString("D3");
return string.IsNullOrEmpty(sanitisedSuffix)
? $"{prefix}-{indexPart}{extension}"
: $"{prefix}-{indexPart}-{sanitisedSuffix}{extension}";
}
private static string SanitiseSuffix(string? suffix)
{
if (string.IsNullOrWhiteSpace(suffix))
return string.Empty;
// Keep the suffix terse and filesystem-safe. Replace anything that isn't a letter,
// digit, dash or underscore with a dash. Avoid LINQ to stay allocation-light here
// (called once per recording, not hot path, but matches surrounding style).
Span buffer = stackalloc char[suffix.Length];
var write = 0;
var lastWasDash = false;
for (var i = 0; i < suffix.Length; i++)
{
var c = suffix[i];
var isSafe = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|| (c >= '0' && c <= '9') || c == '_';
if (isSafe)
{
buffer[write++] = c;
lastWasDash = false;
}
else if (!lastWasDash && write > 0)
{
buffer[write++] = '-';
lastWasDash = true;
}
}
// Trim trailing dash if any.
if (write > 0 && buffer[write - 1] == '-')
write--;
return new string(buffer.Slice(0, write));
}
// Matches the index in current (DataRec-/AudioRec-) and legacy (rec-) recording names,
// so a session counter computed after the rename still clears pre-rename files.
private static readonly Regex _sessionIndexRegex = new(@"^(?:DataRec|AudioRec|rec)-(\d{3,})(?:-|\.)", RegexOptions.Compiled);
}