using System; using System.IO; using Robust.Shared.Utility; namespace Robust.Shared.Audio.AudioLoading; /// /// Implements functionality for loading wav audio files. /// /// internal static class AudioLoaderWav { /// /// Load metadata for a wav audio file. /// /// Audio file stream to load. public static AudioMetadata LoadAudioMetadata(Stream stream) { // TODO: Don't load entire WAV file just to extract metadata. var data = LoadAudioData(stream); var length = TimeSpan.FromSeconds(data.Data.Length / (double) data.BlockAlign / data.SampleRate); return new AudioMetadata(length, data.NumChannels); } /// /// Load a wav file into raw samples and metadata. /// /// Audio file stream to load. public static WavData LoadAudioData(Stream stream) { var reader = new BinaryReader(stream, EncodingHelpers.UTF8, true); // Read outer most chunks. Span fourCc = stackalloc byte[4]; while (true) { ReadFourCC(reader, fourCc); if (!fourCc.SequenceEqual("RIFF"u8)) { SkipChunk(reader); continue; } return ReadRiffChunk(reader); } } private static void SkipChunk(BinaryReader reader) { var length = reader.ReadUInt32(); reader.BaseStream.Position += length; } private static void ReadFourCC(BinaryReader reader, Span fourCc) { fourCc[0] = reader.ReadByte(); fourCc[1] = reader.ReadByte(); fourCc[2] = reader.ReadByte(); fourCc[3] = reader.ReadByte(); } private static WavData ReadRiffChunk(BinaryReader reader) { Span format = stackalloc byte[4]; reader.ReadUInt32(); ReadFourCC(reader, format); if (!format.SequenceEqual("WAVE"u8)) { throw new InvalidDataException("File is not a WAVE file."); } ReadFourCC(reader, format); if (!format.SequenceEqual("fmt "u8)) { throw new InvalidDataException("Expected fmt chunk."); } // Read fmt chunk. var size = reader.ReadInt32(); var afterFmtPos = reader.BaseStream.Position + size; var audioType = (WavAudioFormatType)reader.ReadInt16(); var channels = reader.ReadInt16(); var sampleRate = reader.ReadInt32(); var byteRate = reader.ReadInt32(); var blockAlign = reader.ReadInt16(); var bitsPerSample = reader.ReadInt16(); if (audioType != WavAudioFormatType.PCM) { throw new NotImplementedException("Unable to support audio types other than PCM."); } DebugTools.Assert(byteRate == sampleRate * channels * bitsPerSample / 8); // Fmt is not of guaranteed size, so use the size header to skip to the end. reader.BaseStream.Position = afterFmtPos; while (true) { ReadFourCC(reader, format); if (!format.SequenceEqual("data"u8)) { SkipChunk(reader); continue; } break; } // We are in the data chunk. size = reader.ReadInt32(); var data = reader.ReadBytes(size); return new WavData(audioType, channels, sampleRate, byteRate, blockAlign, bitsPerSample, data); } /// /// See http://soundfile.sapp.org/doc/WaveFormat/ for reference. /// internal readonly struct WavData { public readonly WavAudioFormatType AudioType; public readonly short NumChannels; public readonly int SampleRate; public readonly int ByteRate; public readonly short BlockAlign; public readonly short BitsPerSample; public readonly ReadOnlyMemory Data; public WavData(WavAudioFormatType audioType, short numChannels, int sampleRate, int byteRate, short blockAlign, short bitsPerSample, ReadOnlyMemory data) { AudioType = audioType; NumChannels = numChannels; SampleRate = sampleRate; ByteRate = byteRate; BlockAlign = blockAlign; BitsPerSample = bitsPerSample; Data = data; } } internal enum WavAudioFormatType : short { Unknown = 0, PCM = 1, // There's a bunch of other types, those are all unsupported. } }