Document Audio Pipeline

This commit is contained in:
copecone committed 2026-10-15 16:17:12 +09:00
1 parent 7bb47e4974
commit 19d0e258ac
22 files changed
+634 -12

No files matched your search

+48 -3
View File
@@ -18,6 +18,7 @@ namespace ADOFAIRenderer.Patch.Engine;
[HarmonyPatch(typeof(AudioManager), nameof(AudioManager.FindOrLoadAudioClipExternal))]
public static class AudioManagerFindOrLoadAudioClipExternalPatch
{
/// <summary>Records <paramref name="path"/> in <see cref="AudioClipPaths"/> when its audioLib key is not loaded yet. Runs the original always.</summary>
[HarmonyPrefix]
// ReSharper disable once InconsistentNaming
public static void Prefix(AudioManager __instance, string path)
@@ -28,9 +29,18 @@ public static class AudioManagerFindOrLoadAudioClipExternalPatch
}
}
/// <summary>
/// AudioManager._Play (private; AudioManager.Play forwards to it): registers each scheduled effect sound (hit sounds, PlaySound
/// events, hold sounds without an end time) as an effect voice of the offline mixer and maps the returned AudioSource to it.
/// Active while capturing (RenderManager.IsCapturingActive) with RenderAudio on. The original always runs: it creates the
/// AudioSource the game keeps using (its PlayScheduled call is blocked during rendering by another patch), and the game sets
/// time/pitch/SetScheduledEndTime on it afterwards, which the <see cref="EffectSource"/> patches forward to the mapped state.
/// The game volume is passed through; the start pitch is 1 because _Play sets pitch = 1 before PlayScheduled.
/// </summary>
[HarmonyPatch(typeof(AudioManager), "_Play")]
public static class AudioManagerPlayPatch
{
/// <summary>Opens the clip's source so its first chunk starts loading. Returns true (the original runs).</summary>
[HarmonyPrefix]
public static bool Prefix(string snd, double time, AudioMixerGroup group, float volume, int priority)
{
@@ -42,6 +52,10 @@ public static class AudioManagerPlayPatch
return true; // Let the original method execute to prevent NullReferenceException
}
/// <summary>
/// Registers the sound at <paramref name="time"/> (absolute DSP time, converted to render time) as a non-looping effect voice,
/// maps <paramref name="__result"/> to it and queues the source's game object for deactivation at the end of the frame.
/// </summary>
[HarmonyPostfix]
// ReSharper disable once InconsistentNaming
public static void Postfix(AudioSource __result, string snd, double time, AudioMixerGroup group, float volume, int priority)
@@ -63,6 +77,10 @@ public static class AudioManagerPlayPatch
/// <summary>
/// scrConductor.PlayWithEndTime: registers a looping effect voice until the end time. The AudioSource it creates with
/// AudioManager.MakeSource is mapped to that voice (<see cref="AudioManagerMakeSourcePatch"/>).
/// Active while capturing (RenderManager.IsCapturingActive) with RenderAudio on; the original always runs. The game calls it for
/// hold sounds with an end time above 0 (scrConductor hold sound scheduling); it sets loop = true, pitch = 1, PlayScheduled(time) and
/// SetScheduledEndTime(endTime) on the new source. pitch and SetScheduledEndTime reach the mapped state through the
/// <see cref="EffectSource"/> patches; PlayScheduled is blocked during rendering by another patch.
/// </summary>
[HarmonyPatch(typeof(scrConductor), "PlayWithEndTime")]
public static class PlayWithEndTimePatch
@@ -70,9 +88,14 @@ public static class PlayWithEndTimePatch
/// <summary>State registered by the running PlayWithEndTime call, until its MakeSource returns. Main thread only.</summary>
internal static SourceState? PendingState;
/// <summary>Clears <see cref="PendingState"/> when PlayWithEndTime returns or throws, so it cannot map a later MakeSource call.</summary>
[HarmonyFinalizer]
public static void Finalizer() => PendingState = null;
/// <summary>
/// Opens the clip's source and registers a looping voice from <paramref name="time"/> to <paramref name="endTime"/>
/// (absolute DSP times, converted to render time); the state waits in <see cref="PendingState"/>. Returns true (the original runs).
/// </summary>
[HarmonyPrefix]
public static bool Prefix(string snd, double time, double endTime, float volume, int priority)
{
@@ -96,10 +119,18 @@ public static class PlayWithEndTimePatch
}
}
/// <summary>Maps the AudioSource created inside scrConductor.PlayWithEndTime to its state (<see cref="PlayWithEndTimePatch.PendingState"/>).</summary>
/// <summary>
/// Maps the AudioSource created inside scrConductor.PlayWithEndTime to its state (<see cref="PlayWithEndTimePatch.PendingState"/>).
/// Active only while a PlayWithEndTime call registered a state (capturing with RenderAudio on); every other MakeSource call
/// (including the one inside AudioManager._Play) is left unchanged.
/// </summary>
[HarmonyPatch(typeof(AudioManager), nameof(AudioManager.MakeSource))]
public static class AudioManagerMakeSourcePatch
{
/// <summary>
/// Takes the pending state, maps <paramref name="__result"/> to it and queues the source's game object for deactivation at
/// the end of the frame.
/// </summary>
[HarmonyPostfix]
// ReSharper disable once InconsistentNaming
public static void Postfix(AudioSource __result)
@@ -173,6 +204,7 @@ internal static class EffectSource
[HarmonyPatch(typeof(AudioSource), "isPlaying", MethodType.Getter)]
public static class AudioSourceIsPlayingPatch
{
/// <summary>When <see cref="EffectSource.TryGet"/> finds a mapped state, uses it and returns false (Unity is skipped); otherwise returns true.</summary>
[HarmonyPrefix]
// ReSharper disable once InconsistentNaming
public static bool Prefix(AudioSource __instance, ref bool __result)
@@ -187,6 +219,7 @@ public static class AudioSourceIsPlayingPatch
[HarmonyPatch(typeof(AudioSource), "time", MethodType.Getter)]
public static class AudioSourceTimeGetterPatch
{
/// <summary>When <see cref="EffectSource.TryGet"/> finds a mapped state, uses it and returns false (Unity is skipped); otherwise returns true.</summary>
[HarmonyPrefix]
// ReSharper disable once InconsistentNaming
public static bool Prefix(AudioSource __instance, ref float __result)
@@ -204,6 +237,7 @@ public static class AudioSourceTimeGetterPatch
[HarmonyPatch(typeof(AudioSource), "time", MethodType.Setter)]
public static class AudioSourceTimePatch
{
/// <summary>When <see cref="EffectSource.TryGet"/> finds a mapped state, uses it and returns false (Unity is skipped); otherwise returns true.</summary>
[HarmonyPrefix]
// ReSharper disable once InconsistentNaming
public static bool Prefix(AudioSource __instance, float value)
@@ -214,10 +248,14 @@ public static class AudioSourceTimePatch
}
}
/// <summary>AudioSource.timeSamples getter (<see cref="EffectSource"/>): clip position in samples of the clip.</summary>
/// <summary>
/// AudioSource.timeSamples getter (<see cref="EffectSource"/>): clip position in samples of the clip while playing; while not playing,
/// the last value set through time or timeSamples (0 after Stop()).
/// </summary>
[HarmonyPatch(typeof(AudioSource), "timeSamples", MethodType.Getter)]
public static class AudioSourceTimeSamplesGetterPatch
{
/// <summary>When <see cref="EffectSource.TryGet"/> finds a mapped state, uses it and returns false (Unity is skipped); otherwise returns true.</summary>
[HarmonyPrefix]
// ReSharper disable once InconsistentNaming
public static bool Prefix(AudioSource __instance, ref int __result)
@@ -232,6 +270,7 @@ public static class AudioSourceTimeSamplesGetterPatch
[HarmonyPatch(typeof(AudioSource), "timeSamples", MethodType.Setter)]
public static class AudioSourceTimeSamplesPatch
{
/// <summary>When <see cref="EffectSource.TryGet"/> finds a mapped state, uses it and returns false (Unity is skipped); otherwise returns true.</summary>
[HarmonyPrefix]
// ReSharper disable once InconsistentNaming
public static bool Prefix(AudioSource __instance, int value)
@@ -246,6 +285,7 @@ public static class AudioSourceTimeSamplesPatch
[HarmonyPatch(typeof(AudioSource), "pitch", MethodType.Getter)]
public static class AudioSourcePitchGetterPatch
{
/// <summary>When <see cref="EffectSource.TryGet"/> finds a mapped state, uses it and returns false (Unity is skipped); otherwise returns true.</summary>
[HarmonyPrefix]
// ReSharper disable once InconsistentNaming
public static bool Prefix(AudioSource __instance, ref float __result)
@@ -260,6 +300,7 @@ public static class AudioSourcePitchGetterPatch
[HarmonyPatch(typeof(AudioSource), "pitch", MethodType.Setter)]
public static class AudioSourcePitchPatch
{
/// <summary>When <see cref="EffectSource.TryGet"/> finds a mapped state, uses it and returns false (Unity is skipped); otherwise returns true.</summary>
[HarmonyPrefix]
// ReSharper disable once InconsistentNaming
public static bool Prefix(AudioSource __instance, float value)
@@ -274,6 +315,7 @@ public static class AudioSourcePitchPatch
[HarmonyPatch(typeof(AudioSource), "SetScheduledEndTime", typeof(double))]
public static class AudioSourceSetScheduledEndTimePatch
{
/// <summary>When <see cref="EffectSource.TryGet"/> finds a mapped state, uses it and returns false (Unity is skipped); otherwise returns true.</summary>
[HarmonyPrefix]
// ReSharper disable once InconsistentNaming
public static bool Prefix(AudioSource __instance, double time)
@@ -292,6 +334,7 @@ public static class AudioSourceSetScheduledEndTimePatch
[HarmonyPatch(typeof(AudioSource), "Stop", new Type[0])]
public static class AudioSourceStopPatch
{
/// <summary>When <see cref="EffectSource.TryGet"/> finds a mapped state, uses it and returns false (Unity is skipped); otherwise returns true.</summary>
[HarmonyPrefix]
// ReSharper disable once InconsistentNaming
public static bool Prefix(AudioSource __instance)
@@ -306,11 +349,13 @@ public static class AudioSourceStopPatch
/// AudioManager.StopAllSounds destroys every source in liveSources (all AudioManager._Play and scrConductor.PlayWithEndTime sources),
/// so every effect voice stops at the current render time, including ones scheduled to start later.
/// Callers include scrConductor.PlayHitTimes (from its second call), ScrubMusicToTile/Time, KillAllSoundsCoroutine,
/// scrController.FailAction and scnGame.Play.
/// scrController.FailAction, scrController.DebugUpdate, scnGame.Play and PauseMenu.Choose.
/// Active while capturing (RenderManager.IsCapturingActive) with RenderAudio on; the original always runs (it destroys the sources).
/// </summary>
[HarmonyPatch(typeof(AudioManager), nameof(AudioManager.StopAllSounds))]
public static class AudioManagerStopAllSoundsPatch
{
/// <summary>Stops every effect voice while capturing with RenderAudio on. The original always runs (no return value).</summary>
[HarmonyPrefix]
public static void Prefix()
{