Files
sbox-public/engine/Sandbox.Engine/Resources/Sound/SoundData.cs
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

104 lines
3.1 KiB
C#

using System.IO;
using System.Text;
namespace Sandbox;
/// <summary>
/// Raw PCM sound data, kind of like a bitmap but for sounds
/// </summary>
internal class SoundData
{
public ushort Format { get; private set; }
public ushort Channels { get; private set; }
public uint SampleRate { get; private set; }
public ushort BitsPerSample { get; private set; }
public uint SampleCount { get; private set; }
public float Duration { get; private set; }
public byte[] PCMData { get; private set; }
public static SoundData FromWav( Span<byte> data )
{
using var stream = new MemoryStream( data.ToArray() );
using var reader = new BinaryReader( stream );
Span<byte> header = stackalloc byte[4];
if ( stream.Length < 12 ||
reader.Read( header ) != 4 || !header.SequenceEqual( RIFF ) ||
reader.ReadInt32() != (stream.Length - 8) ||
reader.Read( header ) != 4 || !header.SequenceEqual( WAVE ) )
{
throw new ArgumentException( "Invalid WAV file format" );
}
Span<byte> fmtChunk = default;
Span<byte> dataChunk = default;
uint fmtSize = 0;
uint dataSize = 0;
while ( stream.Position + 8 <= stream.Length )
{
reader.Read( header );
var chunkSize = reader.ReadUInt32();
var chunkStart = stream.Position;
if ( chunkStart + chunkSize > stream.Length )
throw new ArgumentException( "Invalid chunk size in WAV file" );
if ( header.SequenceEqual( FMT ) )
{
if ( chunkSize < 16 )
throw new ArgumentException( "Format chunk size too small" );
fmtChunk = data.Slice( (int)chunkStart, (int)chunkSize );
fmtSize = chunkSize;
}
else if ( header.SequenceEqual( DATA ) )
{
dataChunk = data.Slice( (int)chunkStart, (int)chunkSize );
dataSize = chunkSize;
}
stream.Position = chunkStart + chunkSize;
if ( chunkSize % 2 != 0 )
stream.Position++;
}
if ( fmtChunk.IsEmpty )
throw new ArgumentException( "Missing required FMT chunks" );
if ( dataChunk.IsEmpty )
throw new ArgumentException( "Missing required DATA chunks" );
var channels = BitConverter.ToUInt16( fmtChunk[2..] );
var bitsPerSample = BitConverter.ToUInt16( fmtChunk[14..] );
var bytesPerSample = (uint)bitsPerSample / 8;
var sampleSize = bytesPerSample * channels;
if ( dataSize % sampleSize != 0 )
throw new ArgumentException( "Data chunk size is not a multiple of sample size" );
var format = BitConverter.ToUInt16( fmtChunk );
var sampleRate = BitConverter.ToUInt32( fmtChunk[4..] );
var sampleCount = dataSize / sampleSize;
var duration = sampleRate > 0 ? (float)sampleCount / sampleRate : 0.0f;
var pcmData = dataChunk[..(int)dataSize].ToArray();
return new SoundData
{
Format = format,
Channels = channels,
SampleRate = sampleRate,
BitsPerSample = bitsPerSample,
SampleCount = sampleCount,
Duration = duration,
PCMData = pcmData
};
}
private static readonly byte[] RIFF = Encoding.ASCII.GetBytes( "RIFF" );
private static readonly byte[] WAVE = Encoding.ASCII.GetBytes( "WAVE" );
private static readonly byte[] FMT = Encoding.ASCII.GetBytes( "fmt " );
private static readonly byte[] DATA = Encoding.ASCII.GetBytes( "data" );
}