namespace RemSound.Core;
///
/// Audio format announcement carried in every Format packet. was added
/// 2026-05-11 alongside the BothIndependent audio mode — see for
/// the semantics. The field is wire-backward-compatible: old receivers parse the first 32
/// bytes of the format payload and ignore the extra; new receivers reading a 32-byte
/// payload from an old sender default Lane to .
///
/// 2026-05-23 — FrameDurationMilliseconds renamed to FrameSamplesPerChannel
/// (v3.0 wire-format change). Previously held an int millisecond value; now holds the exact
/// sample-count per channel at the announced . Conversion is
/// ms = FrameSamplesPerChannel * 1000 / SampleRate for display. The change exists
/// because Opus's 2.5 ms RESTRICTED_LOWDELAY frame (= 120 samples at 48 kHz) can't be
/// expressed cleanly in integer milliseconds; using sample-count also removes a lossy
/// conversion that the encoder/decoder pipeline previously did at every announcement.
/// v2.x receivers reading this field will misinterpret the value as milliseconds and
/// over-size their internal buffers; the actual decode still works because the Opus
/// decoder is self-describing from the packet TOC byte. Within-major-version (v3.x ↔ v3.x)
/// the field is unambiguous.
///
public sealed record AudioFormatInfo(
int SampleRate,
int Channels,
int BitsPerSample,
int Encoding,
int BlockAlign,
int AverageBytesPerSecond,
int Codec = (int)AudioTransportCodec.Pcm,
int FrameSamplesPerChannel = 480,
RenderRoute Lane = RenderRoute.Mixed)
{
/// Human-friendly frame duration in milliseconds, derived from
/// and . May be a fraction
/// (2.5 ms at 48 kHz / 120 samples). For string formatting only — the encoder/decoder
/// hot path uses directly.
public double FrameDurationMs => SampleRate > 0 ? FrameSamplesPerChannel * 1000.0 / SampleRate : 0;
public override string ToString()
{
var encodingName = Encoding switch
{
1 => "PCM",
3 => "IEEE float",
_ => $"encoding {Encoding}"
};
var codecName = (AudioTransportCodec)Codec switch
{
AudioTransportCodec.Opus => $" over Opus ({FrameDurationMs:0.##} ms)",
_ => ""
};
var laneName = Lane == RenderRoute.Mixed ? "" : $" [{Lane}]";
return $"{SampleRate} Hz, {Channels} channel(s), {BitsPerSample}-bit {encodingName}{codecName}{laneName}";
}
}