using System;
namespace ADOFAIRenderer.Util.Audio;
///
/// Splits the audio sample stream into per-video-frame sample counts so that audio and video stay aligned without drift.
///
/// The frame rate is converted to an exact fraction Numerator / Denominator ().
/// Frame k receives floor((k + 1) * sampleRate * Denominator / Numerator) - floor(k * sampleRate * Denominator / Numerator) samples:
/// the remainder of each division is carried into the next frame, so the sum over any number of frames equals the exact
/// sample count of that duration rounded down (for example 44100 Hz at 60 FPS gives 735 every frame; at 144 FPS three frames of 306
/// are followed by one of 307, 306.25 on average).
/// Used by the render thread only (RenderManager.WriteAudioForFrame, once per frame sent to the encoder).
///
internal class AudioSyncCalculator
{
/// Audio sample rate in Hz (samples per channel per second).
private readonly long _sampleRate;
/// Video frame rate as a fraction (frames per second = Numerator / Denominator).
private readonly (long Numerator, long Denominator) _fpsRational;
/// Total samples handed out since construction or (kept for debugging, not read).
// ReSharper disable once NotAccessedField.Local
private long _currentSample;
/// Remainder of the last division, in units of 1 / Numerator samples (0 ~ Numerator - 1).
private long _remainder;
/// Audio sample rate in Hz.
/// Video frame rate in frames per second (the encoded frame rate).
public AudioSyncCalculator(long sampleRate, double fps)
{
_sampleRate = sampleRate;
_fpsRational = RationalUtil.Double2MaxPrecisionInt(fps);
}
/// Restarts the sequence at frame 0 (the remainder is cleared).
public void Reset()
{
_currentSample = 0;
_remainder = 0;
}
/// Returns the sample count (frames per channel) of the next video frame and advances to the following frame.
public int GetNextFrameSamples()
{
// samplesNeeded = floor((sampleRate * Denominator + remainder) / Numerator); the new remainder carries the fraction.
var numerator = _sampleRate * _fpsRational.Denominator + _remainder;
var samplesNeeded = numerator / _fpsRational.Numerator;
_remainder = numerator % _fpsRational.Numerator;
_currentSample += samplesNeeded;
return (int)samplesNeeded;
}
}