diff --git a/src/tests/Dragon-ISO.Engine.Tests/Pipeline/AudioPeakComputerTests.cs b/src/tests/Dragon-ISO.Engine.Tests/Pipeline/AudioPeakComputerTests.cs index 382af71..52a43f0 100644 --- a/src/tests/Dragon-ISO.Engine.Tests/Pipeline/AudioPeakComputerTests.cs +++ b/src/tests/Dragon-ISO.Engine.Tests/Pipeline/AudioPeakComputerTests.cs @@ -22,7 +22,7 @@ public class AudioPeakComputerTests [Fact] public void UnknownFourCC_ReturnsZero_RatherThanThrow() { - // Receiver loop must never crash on an unrecognized format — better to + // Receiver loop must never crash on an unrecognized format — better to // show silence on the meter than to take down the pipeline. var floats = new[] { 0.5f, -0.5f }; var bytes = AsBytes(floats); @@ -77,7 +77,7 @@ public class AudioPeakComputerTests public void Fltp_TotalSamplesSmallerThanBuffer_OnlyConsumesReportedRange() { // The reported range covers only the first 3 floats. The 4th - // (largest) is past `totalSamples` and must be ignored — otherwise we'd + // (largest) is past `totalSamples` and must be ignored — otherwise we'd // be reading beyond what the source said it wrote. var floats = new[] { 0.1f, -0.2f, 0.3f, 0.99f }; var bytes = AsBytes(floats); @@ -128,10 +128,59 @@ public class AudioPeakComputerTests var samples = new[] { (short)0, (short)16384, (short)-16383 }; var bytes = AsBytes(samples); var peak = AudioPeakComputer.ComputePeak(bytes, AudioPeakComputer.FourCC_PCMs16, samples.Length); - // 16384 / 32767 ≈ 0.500015; tolerate small precision drift. + // 16384 / 32767 ≈ 0.500015; tolerate small precision drift. Assert.InRange(peak, 0.49, 0.51); } + // ============================================================ + // FourCC constant integrity — regression guard + // ============================================================ + // + // The NDI SDK packs FourCCs little-endian: the first ASCII character + // lands in the least-significant byte. These tests pin each public + // constant to the little-endian packing of its documented characters, + // so a transposed/typo'd literal (the PCMs16 0x73334d50 → 0x734D4350 + // fix) fails loudly instead of silently routing real audio to the + // "unknown format → 0.0" branch. + + [Fact] + public void FourCC_FLTP_MatchesAsciiPacking() + { + AudioPeakComputer.FourCC_FLTP.Should().Be(Pack('F', 'L', 'T', 'p')); + } + + [Fact] + public void FourCC_FLT_MatchesAsciiPacking() + { + AudioPeakComputer.FourCC_FLT.Should().Be(Pack('F', 'L', 'T', ' ')); + } + + [Fact] + public void FourCC_PCMs16_MatchesAsciiPacking() + { + // Regression: was 0x73334d50 ('P','M','3','s'), should be 'P','C','M','s'. + AudioPeakComputer.FourCC_PCMs16.Should().Be(Pack('P', 'C', 'M', 's')); + AudioPeakComputer.FourCC_PCMs16.Should().Be(0x734D4350u); + } + + [Fact] + public void Pcms16_DecodedViaLiteralFourCC_NotJustSymbol() + { + // Belt-and-braces: feed a PCM16 buffer tagged with the *literal* FourCC + // a real legacy Teams/NDI sender emits, not the symbol. If the constant + // ever drifts from the wire value again, this peak collapses to 0.0 + // because ComputePeak falls through to the unknown branch. + const uint wirePcms16 = 0x734D4350; // little-endian 'P','C','M','s' + var samples = new[] { (short)0, (short)16384, (short)-200 }; + var bytes = AsBytes(samples); + var peak = AudioPeakComputer.ComputePeak(bytes, wirePcms16, samples.Length); + peak.Should().BeInRange(0.49, 0.51); + } + + /// Little-endian ASCII FourCC packing: first char in the low byte. + private static uint Pack(char a, char b, char c, char d) => + (uint)a | ((uint)b << 8) | ((uint)c << 16) | ((uint)d << 24); + private static byte[] AsBytes(T[] arr) where T : struct { var span = MemoryMarshal.Cast(arr.AsSpan());