// --------------------------------------------------------------------------------------------------------------------
//
// 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
}
}