SHA256
Document Audio Pipeline
This commit is contained in:
1 parent
7bb47e4974
commit
19d0e258ac
22 files changed
+634
-12
No files matched your search
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user