// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- using System; using System.IO; using UnityEngine; namespace UltimateXR.Extensions.Unity.Audio { public static partial class AudioClipExt { #region Private Types & Data /// /// Describes a PCM audio data header. /// private readonly struct PcmHeader { #region Public Types & Data /// /// Gets the bits per audio sample. /// public int BitDepth { get; } /// /// Gets the audio total sample size in bytes. /// public int AudioSampleSize { get; } /// /// Gets the number of audio samples. /// public int AudioSampleCount { get; } /// /// Gets the number of audio channels. /// public ushort Channels { get; } /// /// Gets the sample rate in audio samples per second. /// public int SampleRate { get; } /// /// Gets the data index where the audio data starts. /// public int AudioStartIndex { get; } /// /// Gets the audio data bytes per second. /// public int ByteRate { get; } /// /// Gets the data block alignment. /// public ushort BlockAlign { get; } #endregion #region Constructors & Finalizer /// /// Constructor. /// /// Sample bit size /// Total audio data size in bytes /// Index where the audio sample data starts /// The number of audio channels /// The number of samples per second /// The number of bytes per second /// The block alignment private PcmHeader(int bitDepth, int audioSize, int audioStartIndex, ushort channels, int sampleRate, int byteRate, ushort blockAlign) { BitDepth = bitDepth; _negativeDepth = Mathf.Pow(2f, BitDepth - 1f); _positiveDepth = _negativeDepth - 1f; AudioSampleSize = bitDepth / 8; AudioSampleCount = Mathf.FloorToInt(audioSize / (float)AudioSampleSize); AudioStartIndex = audioStartIndex; Channels = channels; SampleRate = sampleRate; ByteRate = byteRate; BlockAlign = blockAlign; } #endregion #region Public Methods /// /// Creates a object reading from a byte array. /// /// Source byte array /// object public static PcmHeader FromBytes(byte[] pcmBytes) { using var memoryStream = new MemoryStream(pcmBytes); return FromStream(memoryStream); } /// /// Creates a object reading from a data stream. /// /// Source data /// object public static PcmHeader FromStream(Stream pcmStream) { pcmStream.Position = SizeIndex; using BinaryReader reader = new BinaryReader(pcmStream); int headerSize = reader.ReadInt32(); // 16 ushort audioFormatCode = reader.ReadUInt16(); // 20 string audioFormat = GetAudioFormatFromCode(audioFormatCode); if (audioFormatCode != 1 && audioFormatCode == 65534) { // Only uncompressed PCM wav files are supported. throw new ArgumentOutOfRangeException(nameof(pcmStream), $"Detected format code '{audioFormatCode}' {audioFormat}, but only PCM and WaveFormatExtensible uncompressed formats are currently supported."); } ushort channelCount = reader.ReadUInt16(); // 22 int sampleRate = reader.ReadInt32(); // 24 int byteRate = reader.ReadInt32(); // 28 ushort blockAlign = reader.ReadUInt16(); // 32 ushort bitDepth = reader.ReadUInt16(); //34 pcmStream.Position = SizeIndex + headerSize + 2 * sizeof(int); // Header end index int audioSize = reader.ReadInt32(); // Audio size index return new PcmHeader(bitDepth, audioSize, (int)pcmStream.Position, channelCount, sampleRate, byteRate, blockAlign); // audio start index } /// /// Normalizes a raw audio sample. /// /// Audio sample to normalize /// Normalized audio sample public float NormalizeSample(float rawSample) { float sampleDepth = rawSample < 0 ? _negativeDepth : _positiveDepth; return rawSample / sampleDepth; } #endregion #region Private Methods /// /// Gets the audio format string from the numerical code. /// /// Numerical audio format code /// Audio format string /// The code is not valid private static string GetAudioFormatFromCode(ushort code) { switch (code) { case 1: return "PCM"; case 2: return "ADPCM"; case 3: return "IEEE"; case 7: return "?-law"; case 65534: return "WaveFormatExtensible"; default: throw new ArgumentOutOfRangeException(nameof(code), code, "Unknown wav code format."); } } #endregion #region Private Types & Data private const int SizeIndex = 16; private readonly float _positiveDepth; private readonly float _negativeDepth; #endregion } #endregion } }