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

+39
View File
@@ -21,15 +21,33 @@ namespace ADOFAIRenderer.Util.Audio;
/// </summary>
internal sealed class SourceState
{
/// <summary>Start of playback, seconds relative to the render start.</summary>
private readonly double _start;
/// <summary>Render time of the last position or pitch change (at least <see cref="_start"/>), seconds.</summary>
private double _anchorTime;
/// <summary>Unwrapped clip position at <see cref="_anchorTime"/>, seconds into the clip.</summary>
private double _anchorPosition;
/// <summary>Last finite pitch set (reported as is; negative values play as 0).</summary>
private double _pitch;
private readonly bool _loop;
/// <summary>Scheduled end time, seconds relative to the render start; infinity when not set.</summary>
private double _endTime = double.PositiveInfinity;
/// <summary>True after <see cref="Stop"/>; playback does not resume.</summary>
private bool _stopped;
/// <summary>timeSamples reported while not playing: the last value set through time/timeSamples, 0 after Stop.</summary>
private int _offsetSamples;
/// <summary>Clip length in seconds.</summary>
private double _length;
/// <summary>Clip sample rate in Hz (at least 1), used to convert between time and timeSamples.</summary>
private int _frequency;
/// <param name="voice">Voice that plays the sound.</param>
@@ -51,6 +69,7 @@ internal sealed class SourceState
_frequency = Math.Max(1, frequency);
}
/// <summary>Voice of the <see cref="VoiceMixer"/> that plays the sound; changes made while playing are queued on it.</summary>
public Voice Voice { get; }
/// <summary>Takes the clip length and sample rate from the clip of the game AudioSource.</summary>
@@ -69,26 +88,39 @@ internal sealed class SourceState
Voice.SetEndTime(endTime, now);
}
/// <summary>AudioSource.isPlaying at <paramref name="now"/>: not stopped and before both the scheduled end and the natural end.</summary>
public bool IsPlaying(double now) => !_stopped && now < Math.Min(_endTime, NaturalEnd());
/// <summary>AudioSource.time at <paramref name="now"/>: clip position in seconds while playing, otherwise 0.</summary>
public float GetTime(double now) => IsPlaying(now) ? (float)PositionAt(now) : 0f;
/// <summary>AudioSource.timeSamples at <paramref name="now"/>: floor(position * frequency) while playing, otherwise the stored offset.</summary>
public int GetTimeSamples(double now) => IsPlaying(now) ? (int)Math.Floor(PositionAt(now) * _frequency) : _offsetSamples;
/// <summary>AudioSource.pitch: the last finite value set.</summary>
public float GetPitch() => (float)_pitch;
/// <summary>
/// AudioSource.time setter: stores floor(seconds * frequency) as the offset and, while playing, moves the position to
/// <paramref name="seconds"/> (negative values become 0) from <paramref name="now"/> (or from the start when not started yet).
/// </summary>
public void SetTime(float seconds, double now)
{
_offsetSamples = (int)Math.Floor((double)seconds * _frequency);
Seek(seconds, now);
}
/// <summary>AudioSource.timeSamples setter: as <see cref="SetTime"/> with samples / frequency seconds.</summary>
public void SetTimeSamples(int samples, double now)
{
_offsetSamples = samples;
Seek((double)samples / _frequency, now);
}
/// <summary>
/// AudioSource.pitch setter: ignores NaN and infinity. While playing, the position up to <paramref name="now"/> is kept
/// (new anchor) and the voice changes rate at <paramref name="now"/>; otherwise only the reported value changes.
/// </summary>
public void SetPitch(float pitch, double now)
{
if (float.IsNaN(pitch) || float.IsInfinity(pitch)) return;
@@ -98,6 +130,7 @@ internal sealed class SourceState
if (playing) Voice.SetPitch(pitch, now);
}
/// <summary>AudioSource.Stop(): stops the voice at <paramref name="now"/> when playing, marks the state stopped and clears the offset.</summary>
public void Stop(double now)
{
if (IsPlaying(now)) Voice.Stop(now);
@@ -105,8 +138,13 @@ internal sealed class SourceState
_offsetSamples = 0;
}
/// <summary>Rate used for playback: <see cref="_pitch"/> with negative values treated as 0 (as in <see cref="Voice.SetPitch"/>).</summary>
private double PlaybackPitch => Math.Max(0.0, _pitch);
/// <summary>
/// While playing, sets the clip position to <paramref name="seconds"/> (at least 0) from max(now, start) and queues the same
/// position change on the voice.
/// </summary>
private void Seek(double seconds, double now)
{
if (!IsPlaying(now)) return;
@@ -116,6 +154,7 @@ internal sealed class SourceState
Voice.SetPosition(position, now);
}
/// <summary>Moves the anchor to max(now, start), keeping the current position, before a pitch change.</summary>
private void Reanchor(double now)
{
var time = Math.Max(now, _start);