Resample audio in C# with oneaudio, from 44.1 kHz to 48 kHz and back or any ratio up to 64, in one call or as a stream, with three quality levels and no allocation while running.
using OneAudio; // A whole clip, 44.1 kHz to 48 kHz with the default Polyphase filter. AudioClip clip = Audio.Load("music.wav"); AudioClip at48 = Audio.Resample(clip, 48000); Console.WriteLine($"{clip.Frames} frames at 44.1 kHz -> {at48.Frames} at 48 kHz"); // And back. The length is always round(frames x ratio), and the filter // delay is removed. AudioClip back = Audio.Resample(at48, 44100); Console.WriteLine($"and back: {back.Frames} frames"); // Interleaved floats you already hold, at a chosen quality. float[] voice = Audio.Load("voice.wav").Samples.ToArray(); // 16 kHz mono ResamplerQuality[] qualities = [ResamplerQuality.Polyphase, ResamplerQuality.PolyphaseFast, ResamplerQuality.Linear]; foreach (ResamplerQuality q in qualities) { float[] up = Audio.Resample(voice, channels: 1, fromRate: 16000, toRate: 48000, q); Console.WriteLine($"{q,-13} {voice.Length} -> {up.Length} samples"); } // Streaming, for live audio: feed blocks of any size. The output doesn't // depend on how the input was split. IResampler resampler = AudioResampler.Create(inputRate: 44100, outputRate: 48000, channels: 2, ResamplerQuality.Polyphase); float[] input = clip.Samples.ToArray(); float[] output = new float[resampler.MaxOutputFrames(512) * 2]; // fits 512 in long produced = 0; for (int pos = 0; pos < clip.Frames;) { int frames = (int)Math.Min(512, clip.Frames - pos); resampler.Process(input.AsSpan(pos * 2, frames * 2), output, out int used, out int written); pos += used; // input that didn't fit is offered again produced += written; } int tail; while ((tail = resampler.Flush(output)) > 0) // what the filter still holds produced += tail; Console.WriteLine($"streamed: {produced} frames, latency {resampler.LatencyFrames:F1} frames");
1411200 frames at 44.1 kHz -> 1536000 at 48 kHz and back: 1411200 frames Polyphase 96400 -> 289200 samples PolyphaseFast 96400 -> 289200 samples Linear 96400 -> 289200 samples streamed: 1536001 frames, latency 78.4 frames
Choosing a quality
| Quality | Filter | Use it for |
|---|---|---|
Polyphase (default) | 144-tap Kaiser-windowed sinc, flat within 0.05 dB to 0.9 of the lower Nyquist frequency, more than 100 dB of alias rejection | Files and anything you keep |
PolyphaseFast | 44 taps, flat to 0.8 of the lower Nyquist frequency, about 75 dB of alias rejection | Real-time voices in a mixer |
Linear | Two taps | Pitch effects where speed matters more than quality |
When the rate goes down, the filter’s cutoff follows the new Nyquist frequency.
One call or a stream
Audio.Resamplereturns exactly round(frames × ratio) frames, with the filter delay removed so that output frame m is the input at time m / ratio. It accepts ratios from 1/64 to 64.AudioResampler.Createreturns anIResamplerfor audio that arrives in blocks. Everything is allocated when it’s created, andProcessallocates nothing. The output doesn’t depend on how the input is split. Input that doesn’t fit in the output buffer is left unused and must be offered again, which is whatpos += useddoes.Flushreturns the frames still held in the filter. Call it until it returns 0. The streamed total can be a frame longer thanAudio.Resample’s, which trims to round(frames × ratio), as in this run.LatencyFramesis the filter’s look-ahead, in output frames.SetRatiochanges the ratio while running, without a glitch. That’s how the mixer bends pitch. Keep each change to a few percent.
The mixer resamples each voice whose source rate differs from its own, so audio for AudioMixer needs no resampling first. Every resampler detail is in the API overview.
A longer guide: Resample audio in C#.