namespace RemSound.Core; /// /// Whether a capture source pulls audio via WASAPI loopback (rendering side of an output device, /// e.g. system audio / a soundcard's playback) or via direct WASAPI capture (microphones, /// line-ins, USB capture inputs). /// public enum CaptureKind { Loopback, Input, /// Per-application capture: loopback of ONE process (and its child process tree) via the /// Windows process-loopback API (Windows 10 build 19041+). The /// carries the target PID as "proc:<pid>" (see ). WASAPI-only /// — ASIO has no per-app concept. ProcessLoopback, } /// /// Identifies one source the sender should mix into the outgoing stream. is /// purely for diagnostic logging; is either a WASAPI MMDevice ID or a /// synthetic ASIO id of the form "asio:<channel-pair-index>". /// public sealed record CaptureSourceSpec(string DeviceId, CaptureKind Kind, string Name); /// /// Helpers for the synthetic ASIO device-id format used by both sender and receiver backends. /// Each ASIO channel pair (stereo) is identified by its zero-based pair index — pair 0 is ASIO /// channels 0+1, pair 1 is 2+3, etc. The driver itself isn't encoded in the id; only one ASIO /// driver is active per session and it's configured separately. /// public static class AsioDeviceId { public static string Format(int channelPair) => $"asio:{channelPair}"; public static bool TryParse(string deviceId, out int channelPair) { channelPair = -1; if (string.IsNullOrEmpty(deviceId)) return false; if (!deviceId.StartsWith("asio:", StringComparison.OrdinalIgnoreCase)) return false; return int.TryParse(deviceId.AsSpan("asio:".Length), out channelPair) && channelPair >= 0; } } /// Synthetic device-id for a per-application (process-loopback) capture: "proc:<pid>". /// The PID is resolved fresh each time the app list reconciles, so the id is transient (an app that /// restarts gets a new PID and a new spec) — selection is tracked by process NAME elsewhere. public static class ProcessLoopbackId { public static string Format(int processId) => $"proc:{processId}"; public static bool TryParse(string deviceId, out int processId) { processId = -1; if (string.IsNullOrEmpty(deviceId)) return false; if (!deviceId.StartsWith("proc:", StringComparison.OrdinalIgnoreCase)) return false; return int.TryParse(deviceId.AsSpan("proc:".Length), out processId) && processId > 0; } }