Recursive AI

Convert WAV to FLAC in C#

All oneaudio recipes

Audio.Transcode("in.wav", "out.flac") compresses a WAV file to FLAC in one call, and FlacEncoder adds the compression level and tags. FLAC is lossless: the samples decode to exactly what went in. Pass your own WAV to the program; without one, it first writes a three-second test WAV so that it runs as is.

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

Put this in Program.cs and run dotnet run (or dotnet run -- yourfile.wav):

C#
using OneAudio;
using OneAudio.Flac;
 
string input = args.Length > 0 ? args[0] : "test.wav";
if (!File.Exists(input))
    WriteTestWav(input);
 
// One call, at the default level (5).
Audio.Transcode(input, "out.flac");
 
// Or with a level and tags: 0 is fastest, 8 smallest.
using (IAudioDecoder source = Audio.Open(input))
using (FileStream output = File.Create("out-level8.flac"))
{
    FlacEncoder.Encode(source, output, new FlacEncoderSettings
    {
        CompressionLevel = 8,
        Comments = ["TITLE=Test chord", "ARTIST=Recursive AI"],
    });
}
 
foreach (string f in new[] { input, "out.flac", "out-level8.flac" })
    Console.WriteLine($"{f,-16}{new FileInfo(f).Length,12:N0} bytes");
 
// Lossless: the FLAC decodes to the WAV's exact integers.
using IAudioDecoder a = Audio.Open(input), b = Audio.Open("out-level8.flac");
int[] x = new int[8192], y = new int[8192];
bool same = true;
int n;
while ((n = a.ReadInteger(x)) > 0)
    same &= b.ReadInteger(y) == n && x.AsSpan(0, n).SequenceEqual(y.AsSpan(0, n));
Console.WriteLine($"bit-exact: {same}");
 
// A test WAV: 3 s of a fading C major chord with a little noise, 44.1 kHz stereo.
static void WriteTestWav(string path)
{
    var random = new Random(1);
    float[] s = new float[44100 * 3 * 2];
    for (int i = 0; i < s.Length; i++)
    {
        double t = i / 2 / 44100.0;
        double chord = Math.Sin(2 * Math.PI * 261.63 * t)
                     + Math.Sin(2 * Math.PI * 329.63 * t)
                     + Math.Sin(2 * Math.PI * 392.00 * t);
        double noise = 0.01 * (random.NextDouble() - 0.5);
        s[i] = (float)(0.25 * chord * Math.Exp(-t) + noise);
    }
    var format = new AudioFormat(44100, 2);
    var pcm16 = new WavEncoderSettings { SampleType = NativeSampleType.PcmSigned16 };
    using var wav = new WavEncoder(File.Create(path), format, pcm16, leaveOpen: false);
    wav.Write(s);
}
Text
test.wav             529,280 bytes
out.flac             298,139 bytes
out-level8.flac      297,551 bytes
bit-exact: True

How much smaller the FLAC is depends on the audio. The test chord, with its layer of noise, comes out at 56 percent of the WAV; level 8 saves a little more than the default, at the cost of encoding time.

Settings

  • CompressionLevel runs from 0 (fastest) to 8 (smallest); the default is 5. Output stays within FLAC’s streamable subset unless you set Subset = false.
  • Comments are Vorbis comment tags, NAME=value.
  • Any depth from 4 to 32 bits, 1 to 8 channels. 16 and 24-bit WAV files are copied bit-exact; a float WAV is written as 16-bit unless you ask for 24 (TranscodeOptions.Flac).
  • On a seekable stream, the encoder writes the length, the MD5 signature and a seek table when it finishes. On a stream that can’t seek, it can’t go back: there is no MD5 or seek table.
  • Audio.Transcode reads any format oneaudio decodes, so MP3, Ogg or M4A to FLAC is the same call.

Limits

  • Ogg FLAC isn’t written, only native .flac.
  • Embedded pictures (cover art) aren’t written.
  • Files decoded by flac -d are bit-exact, and sizes are within 0.4% of the flac tool’s at levels 0 to 8, per the project’s published results.

To read FLAC files, see Read a FLAC file.