using Cavern;
using Cavern.Format;
using Cavern.QuickEQ.Utilities;
using Cavern.Utilities;
namespace IFSRenderer.Shared;
/// <summary>
/// An audio clip that parameter channels can be driven from.
/// </summary>
/// <remarks>
/// Ported from the WPF app's CavernAudio/CavernHelper pair. Cavern targets
/// netstandard2.1, so the analysis works unchanged on Linux - only the WPF-side
/// plumbing needed replacing.
/// </remarks>
public sealed class AudioClipSource
{
/// <summary>FFT window. Must be a power of two.</summary>
public const int SamplingResolution = 2048;
private readonly Clip _clip;
private readonly FFTCache _cache = new(SamplingResolution);
public string Path { get; }
public float Length => _clip.Length;
public int SampleRate => _clip.SampleRate;
public int Channels => _clip.Channels;
public AudioClipSource(string path)
{
Path = path;
_clip = AudioReader.ReadClip(path);
}
/// <summary>Raw magnitude spectrum at time <paramref name="t"/> (seconds).</summary>
public float[] GetSpectrum(int channelId, double t)
{
t = Math.Clamp(t, 0, Length);
int samplePosition = (int)(t * SampleRate);
float[] samples = new float[SamplingResolution];
_clip.GetData(samples, Math.Clamp(channelId, 0, Channels - 1), samplePosition);
float[] spectrum = Measurements.GetSpectrum(
Measurements.FFT(samples.Select(r => new Complex(r, 0)).ToArray(), _cache));
WaveformUtils.Gain(spectrum, 1.0f / spectrum.Length);
return spectrum;
}
/// <summary>Loudness in a frequency band, 0..1 - the value a driven channel adds.</summary>
public float Sample(int channelId, double minFrequency, double maxFrequency, double t)
{
float[] spectrum = GetSpectrum(channelId, t);
int startBin = (int)Math.Clamp(2 * minFrequency / SampleRate * spectrum.Length, 0, spectrum.Length - 1);
int endBin = (int)Math.Clamp(2 * maxFrequency / SampleRate * spectrum.Length, 0, spectrum.Length);
endBin = Math.Clamp(endBin, startBin + 1, spectrum.Length);
return spectrum[startBin..endBin].Max();
}
/// <summary>Log-spaced spectrum, suitable for drawing a spectrogram column.</summary>
public float[] GetLogSpectrum(int channelId, double t, double startFrequency, double endFrequency)
{
float[] spectrum = GetSpectrum(channelId, t);
return GraphUtils.ConvertToGraph(spectrum, startFrequency, endFrequency, SampleRate, spectrum.Length);
}
}