From 358551900737081eaaf4716e92dacd4a38f7a8d5 Mon Sep 17 00:00:00 2001 From: jimmyjumbo <37009523+anes66@users.noreply.github.com> Date: Fri, 17 Jul 2026 02:13:06 +0200 Subject: [PATCH] [Audio] Correct float PCM endpoint conversion (#291) --- src/SharpEmu.Libs/Audio/AudioPcmConversion.cs | 15 ++++- .../Audio/AudioPcmConversionTests.cs | 57 +++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 tests/SharpEmu.Libs.Tests/Audio/AudioPcmConversionTests.cs diff --git a/src/SharpEmu.Libs/Audio/AudioPcmConversion.cs b/src/SharpEmu.Libs/Audio/AudioPcmConversion.cs index 6e65b4c8..b8e2c110 100644 --- a/src/SharpEmu.Libs/Audio/AudioPcmConversion.cs +++ b/src/SharpEmu.Libs/Audio/AudioPcmConversion.cs @@ -52,8 +52,19 @@ internal static class AudioPcmConversion } var bits = BinaryPrimitives.ReadInt32LittleEndian(sample); - var value = Math.Clamp(BitConverter.Int32BitsToSingle(bits), -1.0f, 1.0f); - return checked((short)MathF.Round(value * short.MaxValue)); + return ConvertFloatSample(BitConverter.Int32BitsToSingle(bits)); + } + + private static short ConvertFloatSample(float value) + { + if (float.IsNaN(value)) + { + return 0; + } + + value = Math.Clamp(value, -1.0f, 1.0f); + var scale = value < 0.0f ? 32768.0f : short.MaxValue; + return checked((short)MathF.Round(value * scale)); } private static short ApplyVolume(short sample, float volume) diff --git a/tests/SharpEmu.Libs.Tests/Audio/AudioPcmConversionTests.cs b/tests/SharpEmu.Libs.Tests/Audio/AudioPcmConversionTests.cs new file mode 100644 index 00000000..35d76a4b --- /dev/null +++ b/tests/SharpEmu.Libs.Tests/Audio/AudioPcmConversionTests.cs @@ -0,0 +1,57 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using System.Buffers.Binary; +using SharpEmu.Libs.Audio; +using Xunit; + +namespace SharpEmu.Libs.Tests.Audio; + +public sealed class AudioPcmConversionTests +{ + [Fact] + public void FloatFullScaleMapsToSignedPcmEndpoints() + { + Span source = stackalloc byte[sizeof(float) * 2]; + WriteFloat(source, 0, -1.0f); + WriteFloat(source, 1, 1.0f); + Span destination = stackalloc byte[AudioPcmConversion.OutputFrameSize]; + + AudioPcmConversion.ConvertToStereoPcm16( + source, + destination, + frames: 1, + channels: 2, + bytesPerSample: sizeof(float), + isFloat: true, + volume: 1.0f); + + Assert.Equal(short.MinValue, BinaryPrimitives.ReadInt16LittleEndian(destination)); + Assert.Equal(short.MaxValue, BinaryPrimitives.ReadInt16LittleEndian(destination[2..])); + } + + [Fact] + public void FloatNaNMapsToSilence() + { + Span source = stackalloc byte[sizeof(float)]; + WriteFloat(source, 0, float.NaN); + Span destination = stackalloc byte[AudioPcmConversion.OutputFrameSize]; + + AudioPcmConversion.ConvertToStereoPcm16( + source, + destination, + frames: 1, + channels: 1, + bytesPerSample: sizeof(float), + isFloat: true, + volume: 1.0f); + + Assert.Equal(0, BinaryPrimitives.ReadInt16LittleEndian(destination)); + Assert.Equal(0, BinaryPrimitives.ReadInt16LittleEndian(destination[2..])); + } + + private static void WriteFloat(Span destination, int sample, float value) => + BinaryPrimitives.WriteInt32LittleEndian( + destination[(sample * sizeof(float))..], + BitConverter.SingleToInt32Bits(value)); +}