Apply AudioSource Playback Control To Voices

This commit is contained in:
copecone committed 2026-10-13 11:57:07 +09:00
1 parent cb0fd1bd96
commit 5055e4dfbb
3 files changed
+141 -6

No files matched your search

+7
View File
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading;
using ADOFAIRenderer.Util.Audio.Clip;
namespace ADOFAIRenderer.Util.Audio.Mixer;
@@ -64,6 +65,12 @@ internal sealed class Voice
internal bool Loop;
internal bool Finished;
/// <summary>
/// Playback has ended and the voice was released by the render thread. Readable from any thread;
/// the value can lag behind the render thread.
/// </summary>
public bool IsFinished => Volatile.Read(ref Finished);
/// <summary>Loop length in frames at the output sample rate (source duration * output rate).</summary>
internal double Period;
+32 -4
View File
@@ -32,6 +32,9 @@ internal sealed class OfflineAudioMixer : IDisposable
/// <summary>Additional gain of effect sounds (registered with <see cref="RegisterSoundEvent"/>).</summary>
private const double EffectVolume = 2.0 / 3.0;
/// <summary>Number of tracked effect voices at which finished ones are removed from the list used by <see cref="StopAllEffects"/>.</summary>
private const int EffectVoicePruneCount = 1024;
/// <summary>Longest wait for a commit before checking for cancellation again, in milliseconds.</summary>
private const int CommitWaitMs = 50;
@@ -45,6 +48,7 @@ internal sealed class OfflineAudioMixer : IDisposable
// Main thread.
private ConditionalWeakTable<AudioSource, Voice> _sourceVoices = new();
private readonly List<Voice> _effectVoices = new();
private AudioClipSource? _bgmSource;
private string _bgmKey = "";
private Voice? _bgmVoice;
@@ -182,16 +186,26 @@ internal sealed class OfflineAudioMixer : IDisposable
/// Starts an effect sound at <paramref name="startTime"/>. An <paramref name="endTime"/> of 0 or more makes it loop until then.
/// <paramref name="source"/> (when given) is mapped to the voice for later changes through the AudioSource.
/// </summary>
public void RegisterSoundEvent(AudioSource? source, string clipName, double startTime, double endTime, float volume, float pitch, double now)
public Voice RegisterSoundEvent(AudioSource? source, string clipName, double startTime, double endTime, float volume, float pitch, double now)
{
var clipSource = GetOrAddSource(clipName);
var loop = endTime >= 0;
var voice = _mixer.Play(clipSource, clipName, startTime, volume * ClipVolume * EffectVolume, pitch, loop, now);
if (loop) voice.SetEndTime(endTime, now);
if (!source) return;
_sourceVoices.Remove(source!);
_sourceVoices.Add(source!, voice);
if (_effectVoices.Count >= EffectVoicePruneCount)
_effectVoices.RemoveAll(v => v.IsFinished);
_effectVoices.Add(voice);
if (source) MapSource(source!, voice);
return voice;
}
/// <summary>Maps <paramref name="source"/> to <paramref name="voice"/> for later changes through the AudioSource (replaces an earlier mapping).</summary>
public void MapSource(AudioSource source, Voice voice)
{
_sourceVoices.Remove(source);
_sourceVoices.Add(source, voice);
}
/// <summary>Voice started for <paramref name="source"/> by <see cref="RegisterSoundEvent"/>.</summary>
@@ -203,6 +217,19 @@ internal sealed class OfflineAudioMixer : IDisposable
public bool RemoveSourceMapping(AudioSource source) => _sourceVoices.Remove(source);
/// <summary>
/// Stops every effect sound at <paramref name="now"/>, including ones scheduled to start later
/// (AudioManager.StopAllSounds destroys every AudioSource in liveSources, which holds the sources of
/// AudioManager._Play and scrConductor.PlayWithEndTime). The song voice keeps playing.
/// </summary>
public void StopAllEffects(double now)
{
foreach (var voice in _effectVoices)
voice.Stop(now);
_effectVoices.Clear();
_sourceVoices = new ConditionalWeakTable<AudioSource, Voice>();
}
/// <summary>
/// Marks every command with a time before <paramref name="time"/> as queued
/// (called after each frame with the time of the next frame).
@@ -312,5 +339,6 @@ internal sealed class OfflineAudioMixer : IDisposable
filter.Reset();
}
_sourceVoices = new ConditionalWeakTable<AudioSource, Voice>();
_effectVoices.Clear();
}
}