using System; using System.IO; using MelonLoader; using UnityEngine; namespace ADOFAIRenderer.Util.Audio.Clip; /// /// Opens the for a clip the game plays, keyed by the game's audioLib key (also AudioClip.name): /// - Key recorded in and the file exists: decoded from the file with /// (mono MP3 through ) /// (song files and PlaySound files of custom levels). /// - Otherwise, or when FFmpeg cannot open the file: the Unity clip is copied with /// (built-in hit sounds from Resources, official/DLC level songs from Addressables). /// Main thread only (the Unity copy calls AudioClip.GetData). /// internal static class AudioClipResolver { /// /// Returns the source registered under in , opening it on first use /// (file first, then the Unity clip; see the class summary). A key that could not be opened is remembered as null. /// /// Store of the current render. /// audioLib key of the clip (for example "song.ogg*external" or "sndKick"). /// Loaded Unity clip for the key, used when the clip has no file; may be null when the key has a file. /// The source, or null when neither the file nor the clip can be read. public static AudioClipSource? GetOrAdd(AudioClipStore store, string key, AudioClip? clip) { if (store.TryGet(key, out var existing)) return existing; if (AudioClipPaths.TryGetPath(key, out var path) && File.Exists(path)) { IPCMReader? fileReader = null; try { fileReader = OpenFile(path); if (fileReader is MP3MonoLayoutReader) Melon.Logger.Warning($"[AudioClipResolver] Mono MP3 '{path}' is rendered like the game: half speed, one octave lower."); } catch (Exception e) { Melon.Logger.Warning($"[AudioClipResolver] FFmpeg could not open '{path}' for '{key}', using the Unity clip: {e.Message}"); } // The reader opened here becomes the first decode stream; additional streams reopen the file with OpenFile. // When no source results (store disposed, or the source could not be created), the reader is disposed here // (readers allow Dispose more than once). if (fileReader != null) { var source = store.GetOrAdd(key, () => fileReader, () => OpenFile(path)); if (source == null) fileReader.Dispose(); return source; } } if (!clip) return store.GetOrAdd(key, () => null); return store.GetOrAdd(key, () => UnityClipLoader.Load(clip!, store)); } /// Opens with FFmpeg; mono MP3 files are wrapped in (game layout). private static IPCMReader OpenFile(string path) { var reader = new FFmpegPCMReader(path); if (!reader.IsMP3 || reader.Channels != 1) return reader; return new MP3MonoLayoutReader(reader); } }