OverviewFilesCommitsIssuesDocsReleasesPeers

ifsrenderer-linux / Shared / AudioClipSource.cs

66 lines · csharp ref:
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);
    }
}