Recursive AI

Read and write WAV files in C#

All oneaudio recipes

Read WAV and RF64 files in C# with oneaudio as exact integers or floats, and write 16-bit, 24-bit or 32-bit float WAV, with dither when you reduce the bit depth.

C#
using OneAudio;
 
// Read a WAV file: the format says how the samples are stored.
using (IAudioDecoder wav = Audio.Open("music.wav"))
{
    AudioFormat f = wav.Format;
    Console.WriteLine($"music.wav: {f.SampleRate} Hz, {f.Channels} ch, {f.NativeType}, " +
                      $"{f.BitsPerSample} bits, {wav.LengthFrames} frames");
 
    int[] ints = new int[8 * f.Channels];
    wav.ReadInteger(ints);              // the exact 16-bit values
    float[] floats = new float[8 * f.Channels];
    wav.Seek(0);
    wav.Read(floats);                   // the same samples scaled to -1..1
    Console.WriteLine($"sample 4: {ints[4]} as an integer, {floats[4]:F6} as a float " +
                      $"({ints[4] / 32768.0:F6})");
}
 
// Write floats as 24-bit PCM.
AudioClip clip = Audio.Load("music.wav");
AudioFormat src = clip.Format;
var format24 = new AudioFormat(src.SampleRate, src.Channels, src.ChannelMask,
                               NativeSampleType.PcmSigned24, 24);
using (var encoder = new WavEncoder(File.Create("music-24.wav"), format24,
                                    new WavEncoderSettings(), leaveOpen: false))
    encoder.Write(clip.Samples.Span);   // Dispose finishes the file
 
using (IAudioDecoder check = Audio.Open("music-24.wav"))
    Console.WriteLine($"music-24.wav: {check.Format.NativeType}, {check.LengthFrames} frames, " +
                      $"{new FileInfo("music-24.wav").Length:N0} bytes");
 
// Back to 16-bit with triangular dither, or to 32-bit float.
Audio.Transcode("music-24.wav", "music-16-dither.wav", new TranscodeOptions
{
    Wav = new WavEncoderSettings { SampleType = NativeSampleType.PcmSigned16, Dither = true },
});
Audio.Transcode("music.wav", "music-float.wav", new TranscodeOptions
{
    Wav = new WavEncoderSettings { SampleType = NativeSampleType.IeeeFloat32 },
});
foreach (string name in new[] { "music-16-dither.wav", "music-float.wav" })
{
    using IAudioDecoder d = Audio.Open(name);
    Console.WriteLine($"{name}: {d.Format.NativeType}, {new FileInfo(name).Length:N0} bytes");
}
Text
music.wav: 44100 Hz, 2 ch, PcmSigned16, 16 bits, 1411200 frames
sample 4: 803 as an integer, 0.024506 as a float (0.024506)
music-24.wav: PcmSigned24, 1411200 frames, 8,467,304 bytes
music-16-dither.wav: PcmSigned16, 5,644,880 bytes
music-float.wav: IeeeFloat32, 11,289,716 bytes

Reading

  • PCM at 8, 16, 24 and 32 bits, IEEE float at 32 and 64 bits, WAVE_FORMAT_EXTENSIBLE and RF64 (WAV over 4 GiB) all open with Audio.Open.
  • Read gives floats with full scale at ±1. ReadInteger gives the stored integers exactly. 8-bit samples are re-centered to −128..127.
  • A WAV header whose sizes say “unknown” (0xFFFFFFFF), as written to a pipe, is read to the end of the stream.

Writing

  • WavEncoderSettings.SampleType picks the stored type. By default it’s the format’s own type, or 16-bit PCM when the source is lossy.
  • Floats are scaled, rounded to nearest and clipped to the integer range. NaN becomes 0. Dither = true adds one LSB of triangular dither first, from a fixed seed (DitherSeed), so the same input gives the same file.
  • WAVE_FORMAT_EXTENSIBLE with a channel mask is used for more than two channels or more than 16 bits. Float files get a fact chunk.
  • On a seekable stream, Finish writes the final sizes, and a file that outgrows 4 GiB is turned into RF64. On a stream that can’t seek, give TotalFrames for exact sizes. ForceRf64 writes RF64 from the start.
  • WavEncoder.Encode(decoder, stream) copies a whole decoder, and Audio.Transcode(input, "out.wav") does the same from a path.

16-bit, 24-bit, float and RF64 files: Read and write WAV files in C#.