55 lines
1.9 KiB
C#
55 lines
1.9 KiB
C#
namespace ytLive.Services.Audio;
|
|
|
|
/// <summary>
|
|
/// A tiny stateful linear-interpolation sample-rate converter (TASK 9). NAudio's
|
|
/// WASAPI capture rate follows the device mix format; the stream needs a fixed
|
|
/// 48 kHz, so each source normalizes through one of these. Purely a fallback —
|
|
/// the mic requests 48 kHz float up front, and most loopback mix formats are
|
|
/// already 48 kHz, in which case <see cref="NeedsResampling"/> is false and the
|
|
/// chunk passes through untouched. Pure — unit-tested.
|
|
/// </summary>
|
|
public sealed class TinyResampler
|
|
{
|
|
private readonly double _ratio;
|
|
private double _pos;
|
|
|
|
/// <param name="inputRate">The source sample rate.</param>
|
|
/// <param name="outputRate">The target sample rate (48 kHz for the stream).</param>
|
|
public TinyResampler(int inputRate, int outputRate)
|
|
{
|
|
if (inputRate <= 0 || outputRate <= 0)
|
|
throw new ArgumentOutOfRangeException();
|
|
_ratio = (double)inputRate / outputRate;
|
|
}
|
|
|
|
public bool NeedsResampling => Math.Abs(_ratio - 1.0) > 1e-9;
|
|
|
|
/// <summary>Converts one interleaved chunk, keeping the phase across calls.</summary>
|
|
public float[] Process(float[] input)
|
|
{
|
|
if (!NeedsResampling)
|
|
return input;
|
|
|
|
var outLen = (int)((_pos + input.Length) / _ratio);
|
|
var output = new float[outLen];
|
|
for (var i = 0; i < outLen; i++)
|
|
{
|
|
var inPos = _pos + i * _ratio;
|
|
var idx = (int)inPos;
|
|
if (idx >= input.Length)
|
|
{
|
|
output[i] = input[input.Length - 1];
|
|
continue;
|
|
}
|
|
var frac = (float)(inPos - idx);
|
|
var a = input[idx];
|
|
var b = idx + 1 < input.Length ? input[idx + 1] : a;
|
|
output[i] = a + (b - a) * frac;
|
|
}
|
|
_pos += outLen * _ratio - input.Length;
|
|
return output;
|
|
}
|
|
|
|
public void Reset() => _pos = 0;
|
|
}
|