Files
tooll3--t3/Core/Audio/OperatorAudioUtils.cs
T
2026-07-13 13:13:17 +08:00

121 lines
5.5 KiB
C#

using System;
namespace T3.Core.Audio
{
/// <summary>
/// Utility methods for operator audio sources to ensure correct buffer filling and resampling.
/// </summary>
internal static class OperatorAudioUtils
{
/// <summary>
/// Fills the output buffer for the requested duration, sample rate, and channel count.
/// If the operator's native sample rate or channel count differs, resampling and up/down-mixing is performed.
/// </summary>
/// <param name="renderFunc">A function that fills a temp buffer at the operator's native sample rate and channel count.</param>
/// <param name="startTime">Start time in seconds.</param>
/// <param name="duration">Duration in seconds.</param>
/// <param name="outputBuffer">The output buffer to fill (interleaved, targetChannels, targetSampleRate).</param>
/// <param name="operatorSampleRate">The operator's native sample rate.</param>
/// <param name="operatorChannels">The operator's native channel count.</param>
/// <param name="targetSampleRate">The target (mixer) sample rate.</param>
/// <param name="targetChannels">The target (mixer) channel count.</param>
public static void FillAndResample(
Func<double, double, float[], int> renderFunc,
double startTime,
double duration,
float[] outputBuffer,
int operatorSampleRate,
int operatorChannels,
int targetSampleRate,
int targetChannels)
{
int targetSamples = outputBuffer.Length / targetChannels;
if (operatorSampleRate == targetSampleRate && operatorChannels == targetChannels)
{
// Direct fill
renderFunc(startTime, duration, outputBuffer);
return;
}
// Render at native rate/channels
int opSamples = (int)Math.Round(duration * operatorSampleRate);
float[] temp = new float[opSamples * operatorChannels];
int written = renderFunc(startTime, duration, temp);
if (written < opSamples * operatorChannels)
{
// Zero pad if not enough samples
Array.Clear(temp, written, temp.Length - written);
}
// Resample and up/down-mix
LinearResample(temp, opSamples, operatorChannels, outputBuffer, targetSamples, targetChannels);
}
/// <summary>
/// Simple linear resampler and up/down-mixer for float[] audio (interleaved).
/// </summary>
private static void LinearResample(
float[] input, int inputSamples, int inputChannels,
float[] output, int outputSamples, int outputChannels)
{
// Special case: mono to stereo - duplicate mono signal to both channels
if (inputChannels == 1 && outputChannels == 2)
{
for (int i = 0; i < outputSamples; i++)
{
float t = (float)i / Math.Max(outputSamples - 1, 1);
float srcPos = t * (inputSamples - 1);
int srcIndex = (int)srcPos;
float frac = srcPos - srcIndex;
int srcNext = Math.Min(srcIndex + 1, inputSamples - 1);
float sampleA = input[srcIndex];
float sampleB = input[srcNext];
float sample = sampleA + (sampleB - sampleA) * frac;
// Duplicate mono to both left and right channels
output[i * 2] = sample;
output[i * 2 + 1] = sample;
}
return;
}
// General case: resample each channel
for (int ch = 0; ch < Math.Min(inputChannels, outputChannels); ch++)
{
for (int i = 0; i < outputSamples; i++)
{
float t = (float)i / Math.Max(outputSamples - 1, 1);
float srcPos = t * (inputSamples - 1);
int srcIndex = (int)srcPos;
float frac = srcPos - srcIndex;
int srcBase = srcIndex * inputChannels + ch;
int srcNext = Math.Min(srcIndex + 1, inputSamples - 1) * inputChannels + ch;
float sampleA = input[srcBase];
float sampleB = input[srcNext];
output[i * outputChannels + ch] = sampleA + (sampleB - sampleA) * frac;
}
}
// Upmix: fill extra channels with copy of last valid channel (or zeros if no input)
if (outputChannels > inputChannels && inputChannels > 0)
{
// Copy the last input channel to fill remaining output channels
for (int ch = inputChannels; ch < outputChannels; ch++)
{
int srcCh = inputChannels - 1; // Use last input channel
for (int i = 0; i < outputSamples; i++)
{
output[i * outputChannels + ch] = output[i * outputChannels + srcCh];
}
}
}
else if (outputChannels > inputChannels)
{
// No input channels, fill with zeros
for (int ch = inputChannels; ch < outputChannels; ch++)
for (int i = 0; i < outputSamples; i++)
output[i * outputChannels + ch] = 0f;
}
// Downmix: ignore extra input channels
}
}
}