Recursive AI

Write a WAV file in C#

All oneaudio recipes

Create a WavEncoder over a file, Write interleaved float samples, and dispose it: the encoder converts to the bit depth you choose and fills in the header. The program needs no input: it generates two seconds of a 440 Hz tone and writes it three ways.

Terminal
dotnet new console -o WriteWav && cd WriteWav
dotnet add package RecursiveAI.OneAudio --version 1.4.0

Put this in Program.cs and run dotnet run:

C#
using OneAudio;
 
// Two seconds of a 440 Hz sine at half scale, 48 kHz stereo, interleaved:
// samples[frame * 2] is the left channel, samples[frame * 2 + 1] the right.
const int Rate = 48000, Channels = 2;
float[] samples = new float[Rate * 2 * Channels];
for (int frame = 0; frame < Rate * 2; frame++)
{
    float s = 0.5f * MathF.Sin(2 * MathF.PI * 440 * frame / Rate);
    samples[frame * 2] = s;
    samples[frame * 2 + 1] = s;
}
 
var format = new AudioFormat(Rate, Channels);
Write("tone-16.wav", NativeSampleType.PcmSigned16);
Write("tone-24.wav", NativeSampleType.PcmSigned24);
Write("tone-float.wav", NativeSampleType.IeeeFloat32);
 
void Write(string path, NativeSampleType type)
{
    var settings = new WavEncoderSettings { SampleType = type };
    using (var wav = new WavEncoder(File.Create(path), format, settings,
                                    leaveOpen: false))
    {
        wav.Write(samples);         // call as often as you like
    }                               // Dispose finishes the header
 
    using IAudioDecoder check = Audio.Open(path);
    long bytes = new FileInfo(path).Length;
    Console.WriteLine($"{path,-15}{check.Format.NativeType,-13}" +
                      $"{check.LengthFrames} frames, {bytes,9:N0} bytes");
}
Text
tone-16.wav    PcmSigned16  96000 frames,   384,080 bytes
tone-24.wav    PcmSigned24  96000 frames,   576,104 bytes
tone-float.wav IeeeFloat32  96000 frames,   768,116 bytes

Each file plays in any audio player and opens in any editor. To write audio you produce over time, a recording or a synthesizer, keep the encoder open and call Write with each block.

Options

  • SampleType picks the stored type: PcmUnsigned8, PcmSigned16, PcmSigned24, PcmSigned32, IeeeFloat32 or IeeeFloat64. Without it the encoder uses the format’s own type.
  • Floats are scaled, rounded to nearest and clipped to the integer range; NaN becomes 0. Dither = true adds one bit of triangular dither first, from a fixed seed (DitherSeed), so the same input gives the same file. Use it when you reduce 24-bit or float audio to 16 bits.
  • WriteInteger writes integers exactly, for audio that is already 16 or 24-bit.
  • 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.
  • To copy a whole file, Audio.Transcode("in.mp3", "out.wav") decodes and writes in one call; TranscodeOptions.Wav takes the same settings.

Limits

  • On a stream that can seek, disposing writes the final sizes, and a file that outgrows 4 GB becomes RF64 by itself. ForceRf64 writes RF64 from the start.
  • On a stream that can’t seek, such as a pipe or an HTTP response, the header can’t be patched afterwards. Give TotalFrames for exact sizes; otherwise the sizes are written as “unknown”, which oneaudio and most readers take to mean “to the end of the stream”.
  • The encoder isn’t thread-safe: write from one thread.

In the project’s published benchmarks, writing 60 seconds of 48 kHz stereo as 16-bit PCM took 2.8 ms. To read the files back, see Read a WAV file.