Skip to content
This repository has been archived by the owner on Dec 30, 2024. It is now read-only.

Commit

Permalink
πŸ§‘β€πŸ’» Add audio format auto-detection
Browse files Browse the repository at this point in the history
  • Loading branch information
Chasmical committed Jul 30, 2023
1 parent cc7fbfd commit 8fd69b0
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 27 deletions.
16 changes: 15 additions & 1 deletion RogueLibsCore/RogueLibs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,23 @@ public static RogueSprite CreateCustomSprite(string name, SpriteScope scope, byt
/// <exception cref="UnauthorizedAccessException">The caller does not have the required permission.</exception>
/// <exception cref="NotSupportedException">The specified <paramref name="format"/> is not supported.</exception>
/// <exception cref="System.Security.SecurityException">The caller does not have the required permission.</exception>
[Obsolete("Use the overload without the AudioType parameter. RogueLibs v4.0.0 now auto-detects the audio format.")]
public static AudioClip CreateCustomAudio(string name, byte[] rawData, AudioType format)
=> CreateCustomAudio(name, rawData);
/// <summary>
/// <para>Creates a custom <see cref="AudioClip"/> from <paramref name="rawData"/> and adds it to the game under the specified <paramref name="name"/>. Automatically detects the audio format.</para>
/// <para>Supported audio formats: MP3 (.mp3), WAV (.wav, .wave) and Ogg (.ogg, .spx, .opus, .og_).</para>
/// </summary>
/// <param name="name">The name of the clip.</param>
/// <param name="rawData">The byte array containing a raw audio file.</param>
/// <returns>The created <see cref="AudioClip"/>.</returns>
/// <exception cref="IOException">An I/O error occurred while opening the file.</exception>
/// <exception cref="UnauthorizedAccessException">The caller does not have the required permission.</exception>
/// <exception cref="NotSupportedException">The audio format could not be identified.</exception>
/// <exception cref="System.Security.SecurityException">The caller does not have the required permission.</exception>
public static AudioClip CreateCustomAudio(string name, byte[] rawData)
{
AudioClip clip = RogueUtilities.ConvertToAudioClip(rawData, format);
AudioClip clip = RogueUtilities.ConvertToAudioClip(rawData);
clip.name = name;
if (GameController.gameController?.audioHandler != null)
{
Expand Down
85 changes: 59 additions & 26 deletions RogueLibsCore/Utilities/RogueUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,25 @@ public static Sprite ConvertToSprite(string filePath, Rect? region, float ppu =
}

/// <summary>
/// <para>Converts an audio file from the specified <paramref name="filePath"/> into an <see cref="AudioClip"/>. Detects the audio format by the file's extension.</para>
/// <para>Converts the specified <paramref name="rawData"/> into an <see cref="AudioClip"/> using the specified audio <paramref name="format"/>.</para>
/// <para>Supported audio formats: MP3 (.mp3), WAV (.wav, .wave) and Ogg (.ogg, .spx, .opus, .og_).</para>
/// </summary>
/// <param name="rawData">MP3-, WAV- or Ogg-encoded audio data.</param>
/// <param name="format">Format of the audio file.</param>
/// <returns>Created <see cref="AudioClip"/>.</returns>
/// <exception cref="IOException">An I/O error occurred while opening the file.</exception>
/// <exception cref="UnauthorizedAccessException">The caller does not have the required permission.</exception>
/// <exception cref="NotSupportedException">The specified <paramref name="format"/> is not supported.</exception>
/// <exception cref="System.Security.SecurityException">The caller does not have the required permission.</exception>
[Obsolete("Use the overload without the AudioType parameter. RogueLibs v4.0.0 now auto-detects the audio format.")]
public static AudioClip ConvertToAudioClip(byte[] rawData, AudioType format)
=> ConvertToAudioClip(rawData);
/// <summary>
/// <para>Converts an audio file from the specified <paramref name="filePath"/> into an <see cref="AudioClip"/> using the specified audio <paramref name="format"/>.</para>
/// <para>Supported audio formats: MP3 (.mp3), WAV (.wav, .wave) and Ogg (.ogg, .spx, .opus, .og_).</para>
/// </summary>
/// <param name="filePath">Path to the MP3-, WAV- or Ogg-encoded audio file.</param>
/// <param name="format">Format of the audio file.</param>
/// <returns>Created <see cref="AudioClip"/>.</returns>
/// <exception cref="ArgumentException"><paramref name="filePath"/> is an empty string or contains one or more invalid characters.</exception>
/// <exception cref="ArgumentNullException"><paramref name="filePath"/> is <see langword="null"/>.</exception>
Expand All @@ -145,34 +160,23 @@ public static Sprite ConvertToSprite(string filePath, Rect? region, float ppu =
/// <exception cref="IOException">An I/O error occurred while opening the file.</exception>
/// <exception cref="UnauthorizedAccessException"><paramref name="filePath"/> specifies a directory or the caller does not have the required permission.</exception>
/// <exception cref="FileNotFoundException">File specified in <paramref name="filePath"/> was not found.</exception>
/// <exception cref="NotSupportedException"><paramref name="filePath"/> is in invalid format or the extension's format is not supported.</exception>
/// <exception cref="NotSupportedException"><paramref name="filePath"/> is in invalid format or the specified <paramref name="format"/> is not supported.</exception>
/// <exception cref="System.Security.SecurityException">The caller does not have the required permission.</exception>
public static AudioClip ConvertToAudioClip(string filePath)
{
string extLow = Path.GetExtension(filePath).ToLowerInvariant();
AudioType type = extLow == ".mp3" ? AudioType.MPEG
: extLow is ".wav" or ".wave" ? AudioType.WAV
: extLow is ".ogg" or ".ogv" or ".oga" or ".ogx" or ".ogm" or ".spx" or ".opus" ? AudioType.OGGVORBIS
: throw new NotSupportedException($"File is in unknown format {extLow}.");

return ConvertToAudioClip(filePath, type);
}
[Obsolete("Use the overload without the AudioType parameter. RogueLibs v4.0.0 now auto-detects the audio format.")]
public static AudioClip ConvertToAudioClip(string filePath, AudioType format)
=> ConvertToAudioClip(filePath);
/// <summary>
/// <para>Converts the specified <paramref name="rawData"/> into an <see cref="AudioClip"/> using the specified audio <paramref name="format"/>.</para>
/// <para>Converts the specified <paramref name="rawData"/> into an <see cref="AudioClip"/>. Automatically detects the audio format.</para>
/// <para>Supported audio formats: MP3 (.mp3), WAV (.wav, .wave) and Ogg (.ogg, .spx, .opus, .og_).</para>
/// </summary>
/// <param name="rawData">MP3-, WAV- or Ogg-encoded audio data.</param>
/// <param name="format">Format of the audio file.</param>
/// <returns>Created <see cref="AudioClip"/>.</returns>
/// <exception cref="IOException">An I/O error occurred while opening the file.</exception>
/// <exception cref="UnauthorizedAccessException">The caller does not have the required permission.</exception>
/// <exception cref="NotSupportedException">The specified <paramref name="format"/> is not supported.</exception>
/// <exception cref="NotSupportedException">The audio format could not be identified.</exception>
/// <exception cref="System.Security.SecurityException">The caller does not have the required permission.</exception>
public static AudioClip ConvertToAudioClip(byte[] rawData, AudioType format)
public static AudioClip ConvertToAudioClip(byte[] rawData)
{
if (format is not AudioType.MPEG and not AudioType.WAV and not AudioType.OGGVORBIS)
throw new NotSupportedException($"{format} is not supported. Supported audio formats: {AudioType.MPEG}, {AudioType.WAV} and {AudioType.OGGVORBIS}.");

if (!audioCache.TryGetValue(rawData, out AudioClip? audioClip))
{
string name = ".audio-request." + Convert.ToString(new System.Random().Next(), 16).ToLowerInvariant();
Expand All @@ -181,7 +185,7 @@ public static AudioClip ConvertToAudioClip(byte[] rawData, AudioType format)
try
{
File.WriteAllBytes(filePath, rawData);
audioClip = ConvertToAudioClip(filePath, format);
audioClip = ConvertToAudioClip(filePath);
audioCache.Add(rawData, audioClip);
}
finally
Expand All @@ -192,11 +196,10 @@ public static AudioClip ConvertToAudioClip(byte[] rawData, AudioType format)
return audioClip;
}
/// <summary>
/// <para>Converts an audio file from the specified <paramref name="filePath"/> into an <see cref="AudioClip"/> using the specified audio <paramref name="format"/>.</para>
/// <para>Converts an audio file from the specified <paramref name="filePath"/> into an <see cref="AudioClip"/>. Automatically detects the audio format.</para>
/// <para>Supported audio formats: MP3 (.mp3), WAV (.wav, .wave) and Ogg (.ogg, .spx, .opus, .og_).</para>
/// </summary>
/// <param name="filePath">Path to the MP3-, WAV- or Ogg-encoded audio file.</param>
/// <param name="format">Format of the audio file.</param>
/// <returns>Created <see cref="AudioClip"/>.</returns>
/// <exception cref="ArgumentException"><paramref name="filePath"/> is an empty string or contains one or more invalid characters.</exception>
/// <exception cref="ArgumentNullException"><paramref name="filePath"/> is <see langword="null"/>.</exception>
Expand All @@ -205,18 +208,48 @@ public static AudioClip ConvertToAudioClip(byte[] rawData, AudioType format)
/// <exception cref="IOException">An I/O error occurred while opening the file.</exception>
/// <exception cref="UnauthorizedAccessException"><paramref name="filePath"/> specifies a directory or the caller does not have the required permission.</exception>
/// <exception cref="FileNotFoundException">File specified in <paramref name="filePath"/> was not found.</exception>
/// <exception cref="NotSupportedException"><paramref name="filePath"/> is in invalid format or the specified <paramref name="format"/> is not supported.</exception>
/// <exception cref="NotSupportedException">The audio format could not be identified.</exception>
/// <exception cref="NotSupportedException"><paramref name="filePath"/> is in invalid format or the audio format could not be identified.</exception>
/// <exception cref="System.Security.SecurityException">The caller does not have the required permission.</exception>
public static AudioClip ConvertToAudioClip(string filePath, AudioType format)
public static AudioClip ConvertToAudioClip(string filePath)
{
if (format is not AudioType.MPEG and not AudioType.WAV and not AudioType.OGGVORBIS)
throw new NotSupportedException($"{format} is not supported. Supported audio formats: {AudioType.MPEG}, {AudioType.WAV} and {AudioType.OGGVORBIS}.");
AudioType format = DetectAudioFormat(filePath);
if (format is AudioType.UNKNOWN)
{
const string supported = $"{nameof(AudioType.MPEG)}, {nameof(AudioType.WAV)} and {nameof(AudioType.OGGVORBIS)}";
throw new NotSupportedException($"This audio format is not supported. Supported audio formats: {supported}.");
}

UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip("file:///" + filePath, format);
request.SendWebRequest();
while (!request.isDone) Thread.Sleep(1);
return DownloadHandlerAudioClip.GetContent(request);
}

private static AudioType DetectAudioFormat(string filePath)
{
using (FileStream stream = File.OpenRead(filePath))
{
byte[] buffer = new byte[12];
_ = stream.Read(buffer, 0, 12);
return DetectAudioFormat(buffer);
}
}
private static AudioType DetectAudioFormat(byte[] rawData)
{
static bool IsMP3(byte[] b) // https://github.com/hemanth/is-mp3
=> b[0] == 73 && b[1] == 68 && b[2] == 51 || b[0] == 255 && (b[1] == 251 || b[1] == 250);
static bool IsOGG(byte[] b) // https://github.com/hemanth/is-ogg
=> b[0] == 79 && b[1] == 103 && b[2] == 103 && b[3] == 83;
static bool IsWAV(byte[] b) // https://github.com/hemanth/is-wav
=> b[0] == 82 && b[1] == 73 && b[2] == 70 && b[3] == 70 && b[8] == 87 && b[9] == 65 && b[10] == 86 && b[11] == 69;

if (IsMP3(rawData)) return AudioType.MPEG;
if (IsOGG(rawData)) return AudioType.OGGVORBIS;
if (IsWAV(rawData)) return AudioType.WAV;
return AudioType.UNKNOWN;
}

/// <summary>
/// <para>Returns the <paramref name="interfaceType"/>'s <paramref name="methodName"/> implemented by the specified <paramref name="type"/>.</para>
/// </summary>
Expand Down

0 comments on commit 8fd69b0

Please sign in to comment.