using System;
namespace T3.Core.Audio
{
///
/// Utility methods for operator audio sources to ensure correct buffer filling and resampling.
///
internal static class OperatorAudioUtils
{
///
/// 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.
///
/// A function that fills a temp buffer at the operator's native sample rate and channel count.
/// Start time in seconds.
/// Duration in seconds.
/// The output buffer to fill (interleaved, targetChannels, targetSampleRate).
/// The operator's native sample rate.
/// The operator's native channel count.
/// The target (mixer) sample rate.
/// The target (mixer) channel count.
public static void FillAndResample(
Func 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);
}
///
/// Simple linear resampler and up/down-mixer for float[] audio (interleaved).
///
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
}
}
}