SHA256
59 lines
2.9 KiB
C#
59 lines
2.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
namespace ADOFAIRenderer.Util.Audio.Clip;
|
|
|
|
/// <summary>
|
|
/// Copies a Unity <see cref="AudioClip"/> into memory for clips without a file on disk
|
|
/// (built-in hit sounds from Resources, official/DLC level audio).
|
|
/// <see cref="AudioClip.GetData(float[], int)"/> runs on the main Unity thread only, so the whole clip is copied once on the main Unity thread
|
|
/// and the decoder thread reads the copy through a <see cref="MemoryPCMReader"/>.
|
|
/// Streaming clips (<see cref="AudioClipLoadType.Streaming"/>) are rejected because GetData does not read them.
|
|
/// Reference: https://docs.unity3d.com/ScriptReference/AudioClip.GetData.html
|
|
/// </summary>
|
|
internal static class UnityClipLoader
|
|
{
|
|
/// <summary>
|
|
/// Copies <paramref name="clip"/> in blocks of <see cref="AudioClipSource.ChunkFloats"/> floats or less
|
|
/// (FramesPerBlock = ChunkFloats / channels frames per GetData call). Main thread only.
|
|
/// Block memory is counted in <paramref name="store"/> and released block by block as the decoder thread reads it.
|
|
/// </summary>
|
|
public static MemoryPCMReader Load(AudioClip clip, AudioClipStore store)
|
|
{
|
|
if (!clip) throw new ArgumentNullException(nameof(clip));
|
|
if (clip.loadType == AudioClipLoadType.Streaming)
|
|
throw new NotSupportedException($"AudioClip '{clip.name}' is a streaming clip; GetData cannot read it.");
|
|
|
|
// Clips with preloadAudioData off start unloaded; LoadAudioData loads the data synchronously, or asynchronously for
|
|
// clips with loadInBackground (GetData below then fails while the data is still loading).
|
|
// Reference: https://docs.unity3d.com/ScriptReference/AudioClip.LoadAudioData.html
|
|
if (clip.loadState == AudioDataLoadState.Unloaded)
|
|
clip.LoadAudioData();
|
|
|
|
var channels = clip.channels;
|
|
var totalFrames = (long)clip.samples;
|
|
var framesPerBlock = AudioClipSource.ChunkFloats / channels;
|
|
var blocks = new List<float[]>();
|
|
try
|
|
{
|
|
for (long start = 0; start < totalFrames; start += framesPerBlock)
|
|
{
|
|
var frames = (int)Math.Min(framesPerBlock, totalFrames - start);
|
|
var block = store.AllocateExactArray(frames * channels);
|
|
blocks.Add(block);
|
|
// GetData fills the whole array; the offset is in frames (samples per channel).
|
|
if (!clip.GetData(block, (int)start))
|
|
throw new InvalidOperationException($"AudioClip.GetData failed for '{clip.name}' at frame {start} (load state {clip.loadState}).");
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
foreach (var block in blocks) store.FreeExactArray(block);
|
|
throw;
|
|
}
|
|
|
|
return new MemoryPCMReader(blocks, framesPerBlock, channels, clip.frequency, totalFrames, store.FreeExactArray);
|
|
}
|
|
}
|