using Xunit; using ytLive.Services.Audio; namespace ytLive.Tests; /// /// TASK 22: the global audio-sync delay line. It is a pure fixed positive /// delay on the interleaved stereo mix — the OBS fix for audio running ahead /// of video. Positive-only: advancing audio needs a video-side delay and is /// out of the audio layer's scope. /// public class AudioSyncDelayTests { [Fact] public void ZeroDelay_PassesThroughUnchanged() { var delay = new AudioSyncDelay(48000); delay.Configure(0); var input = new float[16]; for (var i = 0; i < input.Length; i++) input[i] = i - 8; var output = new float[16]; delay.Process(input, output); Assert.Equal(input, output); } [Fact] public void ConfigureClampsNegativeToZero_AndBeyond500msTo500ms() { var delay = new AudioSyncDelay(48000); delay.Configure(-50); Assert.Equal(0, MarkerShift(delay, 1000)); // 500 ms at 48 kHz = 24000 frames = 48000 interleaved samples. delay.Configure(6000); Assert.Equal(48000, MarkerShift(delay, 48000)); } [Fact] public void TenMsDelay_EmitsTheSignal960SamplesLater() { // 10 ms at 48 kHz = 480 frames; interleaved stereo = 960 samples. var delay = new AudioSyncDelay(48000); Assert.Equal(960, MarkerShift(delay, 480 * 2, configureMs: 10)); } private static int MarkerShift(AudioSyncDelay delay, int chunkLen, int configureMs = -1) { if (configureMs >= 0) delay.Configure(configureMs); var chunk = new float[chunkLen]; var output = new float[chunkLen]; chunk[0] = 1f; // sole non-zero: left of frame 0 var foundAt = -1; var total = 0; for (var c = 0; c < 4 && foundAt < 0; c++) { delay.Process(chunk, output); for (var i = 0; i < chunkLen; i++) { if (output[i] != 0f) { foundAt = total + i; break; } } total += chunkLen; } Assert.True(foundAt >= 0, "marker never appeared in the delayed output"); return foundAt; } }