54 lines
2.2 KiB
C#
54 lines
2.2 KiB
C#
using System.Runtime.InteropServices;
|
|||
|
|
using Microsoft.Win32.SafeHandles;
|
||
|
|
|
||
|
|
namespace VoiceCat.Dsp;
|
||
|
|
|
||
|
|
public sealed unsafe partial class RnnoiseProcessor : IDisposable
|
||
|
|
{
|
||
|
|
public const int SampleRate = 48000;
|
||
|
|
public const int FrameSamples = 480;
|
||
|
|
private readonly RnnoiseHandle handle;
|
||
|
|
private readonly float[] input = new float[FrameSamples];
|
||
|
|
private readonly float[] output = new float[FrameSamples];
|
||
|
|
|
||
|
|
public RnnoiseProcessor()
|
||
|
|
{
|
||
|
|
handle = new(Create());
|
||
|
|
if (handle.IsInvalid) { handle.Dispose(); throw new OutOfMemoryException(); }
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Process(Span<short> pcm, int sampleRate = SampleRate)
|
||
|
|
{
|
||
|
|
ObjectDisposedException.ThrowIf(handle.IsClosed, this);
|
||
|
|
if (sampleRate != SampleRate) return;
|
||
|
|
if (pcm.Length % FrameSamples != 0) throw new ArgumentException("RNNoise requires complete 480-sample mono chunks.", nameof(pcm));
|
||
|
|
fixed (float* source = input)
|
||
|
|
fixed (float* destination = output)
|
||
|
|
{
|
||
|
|
for (int offset = 0; offset < pcm.Length; offset += FrameSamples)
|
||
|
|
{
|
||
|
|
for (int i = 0; i < FrameSamples; i++) input[i] = pcm[offset + i];
|
||
|
|
ProcessFrame(handle, destination, source);
|
||
|
|
for (int i = 0; i < FrameSamples; i++)
|
||
|
|
pcm[offset + i] = (short)Math.Clamp(MathF.Round(output[i], MidpointRounding.AwayFromZero), short.MinValue, short.MaxValue);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Dispose() => handle.Dispose();
|
||
|
|
|
||
|
|
[LibraryImport("voicecat_media", EntryPoint = "vcm_rnnoise_create")]
|
||
|
|
private static partial nint Create();
|
||
|
|
[LibraryImport("voicecat_media", EntryPoint = "vcm_rnnoise_destroy")]
|
||
|
|
private static partial void Destroy(nint state);
|
||
|
|
[LibraryImport("voicecat_media", EntryPoint = "vcm_rnnoise_process")]
|
||
|
|
private static partial float ProcessFrame(RnnoiseHandle state, float* output, float* input);
|
||
|
|
|
||
|
|
private sealed class RnnoiseHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||
|
|
{
|
||
|
|
public RnnoiseHandle() : base(true) { }
|
||
|
|
internal RnnoiseHandle(nint value) : this() => SetHandle(value);
|
||
|
|
protected override bool ReleaseHandle() { Destroy(handle); return true; }
|
||
|
|
}
|
||
|
|
}
|