mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-19 15:51:48 +00:00
cache audio clips
This commit is contained in:
3 files changed
+141
-124
No files matched your search
@@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Utilities.Assets;
|
||||
|
||||
/// <summary>
|
||||
/// A utility class for various audio-related operations.
|
||||
/// </summary>
|
||||
public static class AudioTools
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads and returns an audio clip from a resource path using the specified <see cref="Assembly"/>.
|
||||
/// </summary>
|
||||
/// <param name="resourcePath">The path to the resource within the <see cref="Assembly"/>.</param>
|
||||
/// <param name="assembly">The <see cref="Assembly"/> from which to load the resource.</param>
|
||||
/// <returns>A <see cref="AudioClip"/> object loaded from the specified resource path.</returns>
|
||||
/// <exception cref="NotSupportedException">Attempted to load an Audio file in non WAV format.</exception>
|
||||
/// <exception cref="MissingManifestResourceException">Stream failed to load. Check if the name of your asset was correct.</exception>
|
||||
public static AudioClip LoadAudioFromResources(string resourcePath, Assembly assembly)
|
||||
{
|
||||
using var assetStream = assembly.GetManifestResourceStream(resourcePath)
|
||||
?? throw new MissingManifestResourceException($"Stream failed to load. Check if the asset name is correct: {resourcePath}");
|
||||
|
||||
using var reader = new BinaryReader(assetStream);
|
||||
|
||||
// Read RIFF header
|
||||
if (Encoding.ASCII.GetString(reader.ReadBytes(4)) != "RIFF")
|
||||
{
|
||||
throw new NotSupportedException("Invalid WAV file (missing RIFF header).");
|
||||
}
|
||||
|
||||
reader.ReadInt32(); // File size (ignore)
|
||||
|
||||
if (Encoding.ASCII.GetString(reader.ReadBytes(4)) != "WAVE")
|
||||
{
|
||||
throw new NotSupportedException("Invalid WAV file (missing WAVE header).");
|
||||
}
|
||||
|
||||
// Read chunks
|
||||
int channels = 0, sampleRate = 0, bitsPerSample = 0;
|
||||
byte[]? audioData = null;
|
||||
|
||||
while (reader.BaseStream.Position < reader.BaseStream.Length)
|
||||
{
|
||||
var chunkId = Encoding.ASCII.GetString(reader.ReadBytes(4));
|
||||
var chunkSize = reader.ReadInt32();
|
||||
|
||||
// Format chunk
|
||||
if (chunkId == "fmt ")
|
||||
{
|
||||
int audioFormat = reader.ReadInt16();
|
||||
channels = reader.ReadInt16();
|
||||
sampleRate = reader.ReadInt32();
|
||||
reader.ReadInt32(); // Byte rate (ignore)
|
||||
reader.ReadInt16(); // Block align (ignore)
|
||||
bitsPerSample = reader.ReadInt16();
|
||||
|
||||
if (audioFormat != 1) // Only PCM supported
|
||||
throw new NotSupportedException("Only PCM WAV files are supported.");
|
||||
}
|
||||
|
||||
// Data chunk
|
||||
else if (chunkId == "data")
|
||||
{
|
||||
audioData = reader.ReadBytes(chunkSize);
|
||||
break; // No need to read further
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.BaseStream.Seek(chunkSize, SeekOrigin.Current); // Skip unknown chunks
|
||||
}
|
||||
}
|
||||
|
||||
if (audioData == null)
|
||||
{
|
||||
throw new InvalidOperationException("WAV file does not contain audio data.");
|
||||
}
|
||||
|
||||
if (channels == 0 || sampleRate == 0 || bitsPerSample == 0)
|
||||
{
|
||||
throw new InvalidOperationException("WAV file does not contain valid format information.");
|
||||
}
|
||||
|
||||
// Convert PCM data to float array
|
||||
var samples = ConvertPcmToFloat(audioData, bitsPerSample);
|
||||
|
||||
// Create the AudioClip
|
||||
var audioClip = AudioClip.Create(resourcePath, samples.Length / channels, channels, sampleRate, false);
|
||||
audioClip.SetData(samples, 0);
|
||||
|
||||
return audioClip;
|
||||
}
|
||||
|
||||
// This is somewhat magic to me, I just copied it from elsewhere.
|
||||
private static float[] ConvertPcmToFloat(byte[] pcmData, int bitsPerSample)
|
||||
{
|
||||
var sampleCount = pcmData.Length / (bitsPerSample / 8);
|
||||
var floatData = new float[sampleCount];
|
||||
|
||||
for (var i = 0; i < sampleCount; i++)
|
||||
{
|
||||
var byteIndex = i * (bitsPerSample / 8);
|
||||
|
||||
switch (bitsPerSample)
|
||||
{
|
||||
case 8:
|
||||
floatData[i] = (pcmData[byteIndex] - 128) / 128f; // 8-bit PCM is unsigned
|
||||
break;
|
||||
case 16:
|
||||
floatData[i] = BitConverter.ToInt16(pcmData, byteIndex) / 32768f;
|
||||
break;
|
||||
case 24:
|
||||
var sample24 = pcmData[byteIndex] | (pcmData[byteIndex + 1] << 8) | (pcmData[byteIndex + 2] << 16);
|
||||
if (sample24 > 0x7FFFFF) sample24 -= 0x1000000; // Convert from unsigned to signed
|
||||
floatData[i] = sample24 / 8388608f;
|
||||
break;
|
||||
case 32:
|
||||
floatData[i] = BitConverter.ToInt32(pcmData, byteIndex) / 2147483648f;
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException($"Unsupported bit depth: {bitsPerSample}");
|
||||
}
|
||||
}
|
||||
|
||||
return floatData;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Utilities.Assets;
|
||||
@@ -19,114 +15,14 @@ public class LoadableAudioResourceAsset(string path) : LoadableAsset<AudioClip>
|
||||
/// Loads the asset from embedded resources.
|
||||
/// </summary>
|
||||
/// <returns>The asset to load.</returns>
|
||||
/// <exception cref="NotSupportedException">Attempted to load an Audio file in non WAV format.</exception>
|
||||
/// <exception cref="MissingManifestResourceException">Stream failed to load. Check if the name of your asset was correct.</exception>
|
||||
/// <inheritdoc />
|
||||
public override AudioClip LoadAsset()
|
||||
{
|
||||
using var assetStream = _assembly.GetManifestResourceStream(path)
|
||||
?? throw new MissingManifestResourceException($"Stream failed to load. Check if the asset name is correct: {path}");
|
||||
|
||||
using var reader = new BinaryReader(assetStream);
|
||||
|
||||
// Read RIFF header
|
||||
if (Encoding.ASCII.GetString(reader.ReadBytes(4)) != "RIFF")
|
||||
if (!LoadedAsset)
|
||||
{
|
||||
throw new NotSupportedException("Invalid WAV file (missing RIFF header).");
|
||||
LoadedAsset = AudioTools.LoadAudioFromResources(path, _assembly);
|
||||
}
|
||||
|
||||
reader.ReadInt32(); // File size (ignore)
|
||||
|
||||
if (Encoding.ASCII.GetString(reader.ReadBytes(4)) != "WAVE")
|
||||
{
|
||||
throw new NotSupportedException("Invalid WAV file (missing WAVE header).");
|
||||
}
|
||||
|
||||
// Read chunks
|
||||
int channels = 0, sampleRate = 0, bitsPerSample = 0;
|
||||
byte[]? audioData = null;
|
||||
|
||||
while (reader.BaseStream.Position < reader.BaseStream.Length)
|
||||
{
|
||||
var chunkId = Encoding.ASCII.GetString(reader.ReadBytes(4));
|
||||
var chunkSize = reader.ReadInt32();
|
||||
|
||||
// Format chunk
|
||||
if (chunkId == "fmt ")
|
||||
{
|
||||
int audioFormat = reader.ReadInt16();
|
||||
channels = reader.ReadInt16();
|
||||
sampleRate = reader.ReadInt32();
|
||||
reader.ReadInt32(); // Byte rate (ignore)
|
||||
reader.ReadInt16(); // Block align (ignore)
|
||||
bitsPerSample = reader.ReadInt16();
|
||||
|
||||
if (audioFormat != 1) // Only PCM supported
|
||||
throw new NotSupportedException("Only PCM WAV files are supported.");
|
||||
}
|
||||
|
||||
// Data chunk
|
||||
else if (chunkId == "data")
|
||||
{
|
||||
audioData = reader.ReadBytes(chunkSize);
|
||||
break; // No need to read further
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.BaseStream.Seek(chunkSize, SeekOrigin.Current); // Skip unknown chunks
|
||||
}
|
||||
}
|
||||
|
||||
if (audioData == null)
|
||||
{
|
||||
throw new InvalidOperationException("WAV file does not contain audio data.");
|
||||
}
|
||||
|
||||
if (channels == 0 || sampleRate == 0 || bitsPerSample == 0)
|
||||
{
|
||||
throw new InvalidOperationException("WAV file does not contain valid format information.");
|
||||
}
|
||||
|
||||
// Convert PCM data to float array
|
||||
var samples = ConvertPcmToFloat(audioData, bitsPerSample);
|
||||
|
||||
// Create the AudioClip
|
||||
var audioClip = AudioClip.Create(path, samples.Length / channels, channels, sampleRate, false);
|
||||
audioClip.SetData(samples, 0);
|
||||
|
||||
return audioClip;
|
||||
}
|
||||
|
||||
// This is somewhat magic to me, I just copied it from elsewhere.
|
||||
private static float[] ConvertPcmToFloat(byte[] pcmData, int bitsPerSample)
|
||||
{
|
||||
var sampleCount = pcmData.Length / (bitsPerSample / 8);
|
||||
var floatData = new float[sampleCount];
|
||||
|
||||
for (var i = 0; i < sampleCount; i++)
|
||||
{
|
||||
var byteIndex = i * (bitsPerSample / 8);
|
||||
|
||||
switch (bitsPerSample)
|
||||
{
|
||||
case 8:
|
||||
floatData[i] = (pcmData[byteIndex] - 128) / 128f; // 8-bit PCM is unsigned
|
||||
break;
|
||||
case 16:
|
||||
floatData[i] = BitConverter.ToInt16(pcmData, byteIndex) / 32768f;
|
||||
break;
|
||||
case 24:
|
||||
var sample24 = pcmData[byteIndex] | (pcmData[byteIndex + 1] << 8) | (pcmData[byteIndex + 2] << 16);
|
||||
if (sample24 > 0x7FFFFF) sample24 -= 0x1000000; // Convert from unsigned to signed
|
||||
floatData[i] = sample24 / 8388608f;
|
||||
break;
|
||||
case 32:
|
||||
floatData[i] = BitConverter.ToInt32(pcmData, byteIndex) / 2147483648f;
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException($"Unsupported bit depth: {bitsPerSample}");
|
||||
}
|
||||
}
|
||||
|
||||
return floatData;
|
||||
return LoadedAsset!;
|
||||
}
|
||||
}
|
||||
@@ -9,36 +9,26 @@ namespace MiraAPI.Utilities.Assets;
|
||||
/// <summary>
|
||||
/// A utility class for loading multiple assets from an <see cref="AssetBundle"/>.
|
||||
/// </summary>
|
||||
public class LoadableBundleSubAssetHolder
|
||||
/// <param name="names">The name of the assets to pull from.</param>
|
||||
/// <param name="bundle">The <see cref="AssetBundle"/> that contains the assets.</param>
|
||||
public class LoadableBundleSubAssetHolder(string[] names, AssetBundle bundle)
|
||||
{
|
||||
private readonly string[] spriteNames;
|
||||
private readonly AssetBundle bundle;
|
||||
private readonly string[] spriteNames = names;
|
||||
private readonly AssetBundle bundle = bundle;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprites contained within the asset.
|
||||
/// </summary>
|
||||
public Sprite[] SubSprites { get; private set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LoadableBundleSubAssetHolder"/> class.
|
||||
/// </summary>
|
||||
/// <param name="names">The name of the assets to pull from.</param>
|
||||
/// <param name="bundle">The <see cref="AssetBundle"/> that contains the assets.</param>
|
||||
public LoadableBundleSubAssetHolder(string[] names, AssetBundle bundle)
|
||||
{
|
||||
this.bundle = bundle;
|
||||
spriteNames = names;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LoadableBundleSubAssetHolder"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the asset.</param>
|
||||
/// <param name="bundle">The <see cref="AssetBundle"/> that contains the assets.</param>
|
||||
public LoadableBundleSubAssetHolder(string name, AssetBundle bundle)
|
||||
: this([name], bundle)
|
||||
{
|
||||
this.bundle = bundle;
|
||||
spriteNames = [name];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in new issue
Block a user