SHA256
Apply AudioSource Playback Control To Voices
This commit is contained in:
1 parent
cb0fd1bd96
commit
5055e4dfbb
3 files changed
+141
-6
No files matched your search
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using ADOFAIRenderer.Core;
|
||||
using ADOFAIRenderer.Util.Audio.Clip;
|
||||
using ADOFAIRenderer.Util.Audio.Mixer;
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Audio;
|
||||
@@ -55,9 +56,19 @@ 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"/>).
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(scrConductor), "PlayWithEndTime")]
|
||||
public static class PlayWithEndTimePatch
|
||||
{
|
||||
/// <summary>Voice registered by the running PlayWithEndTime call, until its MakeSource returns. Main thread only.</summary>
|
||||
internal static Voice? PendingVoice;
|
||||
|
||||
[HarmonyFinalizer]
|
||||
public static void Finalizer() => PendingVoice = null;
|
||||
|
||||
[HarmonyPrefix]
|
||||
public static bool Prefix(string snd, double time, double endTime, float volume, int priority)
|
||||
{
|
||||
@@ -74,13 +85,51 @@ public static class PlayWithEndTimePatch
|
||||
double relativeEndTime = endTime - RenderManager.Instance.dspTimeBase;
|
||||
|
||||
// Register looping sound event to the offline mixer (default pitch = 1.0f)
|
||||
mixer.RegisterSoundEvent(null, snd, relativeStartTime, relativeEndTime, volume, 1.0f, RenderManager.Instance.AudioTime);
|
||||
PendingVoice = mixer.RegisterSoundEvent(null, snd, relativeStartTime, relativeEndTime, volume, 1.0f, RenderManager.Instance.AudioTime);
|
||||
}
|
||||
}
|
||||
return true; // Let the original method execute
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Maps the AudioSource created inside scrConductor.PlayWithEndTime to its voice (<see cref="PlayWithEndTimePatch.PendingVoice"/>).</summary>
|
||||
[HarmonyPatch(typeof(AudioManager), nameof(AudioManager.MakeSource))]
|
||||
public static class AudioManagerMakeSourcePatch
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static void Postfix(AudioSource __result)
|
||||
{
|
||||
var voice = PlayWithEndTimePatch.PendingVoice;
|
||||
if (voice == null || !__result) return;
|
||||
PlayWithEndTimePatch.PendingVoice = null;
|
||||
RenderManager.Instance.AudioMixer?.MapSource(__result, voice);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AudioSource.isPlaying getter: a source mapped to a voice reports true until the voice has finished, including before its
|
||||
/// scheduled start, as a source started with PlayScheduled does. During rendering PlayScheduled is blocked, so without this
|
||||
/// the game sees every effect source as stopped and AudioManager.Update returns it to the reusable pool on the next frame.
|
||||
/// Voice completion is set by the render thread, which mixes behind the main thread, so the value turns false at or after
|
||||
/// the render time the voice ends.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(AudioSource), "isPlaying", MethodType.Getter)]
|
||||
public static class AudioSourceIsPlayingPatch
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static void Postfix(AudioSource __instance, ref bool __result)
|
||||
{
|
||||
if (__result || !RenderManager.IsCapturingActive) return;
|
||||
var mixer = RenderManager.Instance.AudioMixer;
|
||||
if (mixer != null && mixer.TryGetVoice(__instance, out var voice))
|
||||
{
|
||||
__result = !voice.IsFinished;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(AudioSource), "pitch", MethodType.Setter)]
|
||||
public static class AudioSourcePitchPatch
|
||||
{
|
||||
@@ -117,6 +166,32 @@ public static class AudioSourceSetScheduledEndTimePatch
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AudioSource.time setter: moves the playback position of the mapped voice (clip seconds) from the current render time.
|
||||
/// Set before the scheduled start (ffxPlaySound sets it right after AudioManager.Play), it becomes the start position.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(AudioSource), "time", MethodType.Setter)]
|
||||
public static class AudioSourceTimePatch
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static void Postfix(AudioSource __instance, float value)
|
||||
{
|
||||
if (RenderManager.IsCapturingActive && RenderManager.Instance.Configuration.AudioConfig.RenderAudio)
|
||||
{
|
||||
var mixer = RenderManager.Instance.AudioMixer;
|
||||
if (mixer != null && mixer.TryGetVoice(__instance, out var voice))
|
||||
{
|
||||
voice.SetPosition(value, RenderManager.Instance.AudioTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AudioSource.Stop(): stops the mapped voice at the current render time and removes the mapping.
|
||||
/// AudioManager.Update calls Stop() on sources it recycles, which are finished (<see cref="AudioSourceIsPlayingPatch"/>).
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(AudioSource), "Stop", new Type[0])]
|
||||
public static class AudioSourceStopPatch
|
||||
{
|
||||
@@ -126,7 +201,32 @@ public static class AudioSourceStopPatch
|
||||
{
|
||||
if (RenderManager.IsCapturingActive && RenderManager.Instance.Configuration.AudioConfig.RenderAudio)
|
||||
{
|
||||
RenderManager.Instance.AudioMixer?.RemoveSourceMapping(__instance);
|
||||
var mixer = RenderManager.Instance.AudioMixer;
|
||||
if (mixer == null) return;
|
||||
if (mixer.TryGetVoice(__instance, out var voice))
|
||||
{
|
||||
voice.Stop(RenderManager.Instance.AudioTime);
|
||||
}
|
||||
mixer.RemoveSourceMapping(__instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(AudioManager), nameof(AudioManager.StopAllSounds))]
|
||||
public static class AudioManagerStopAllSoundsPatch
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
public static void Prefix()
|
||||
{
|
||||
if (RenderManager.IsCapturingActive && RenderManager.Instance.Configuration.AudioConfig.RenderAudio)
|
||||
{
|
||||
RenderManager.Instance.AudioMixer?.StopAllEffects(RenderManager.Instance.AudioTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user