Recursive AI

Join audio files in C#

All oneaudio recipes

Load each file at one sample rate and channel count with LoadOptions, then write them one after another into a single WavEncoder. Files of different formats and rates join cleanly because each is converted as it loads. The inputs are short synthetic test files from the oneaudio repository, an MP3, a FLAC and an Opus file at three different rates.

Terminal
dotnet new console -o JoinAudio && cd JoinAudio
dotnet add package RecursiveAI.OneAudio --version 1.4.0
t=https://raw.githubusercontent.com/recursive-ai-oss/oneaudio-csharp/v1.4.0/testdata
curl -L -o one.mp3 $t/mp3/small_44100_2_joint.mp3
curl -L -o two.flac $t/flac/stereo16_seektable.flac
curl -L -o three.opus $t/opus/celt-stereo.opus

Put this in Program.cs and run dotnet run:

C#
using OneAudio;
 
string[] inputs = ["one.mp3", "two.flac", "three.opus"];
var common = new LoadOptions { SampleRate = 44100, Channels = 2 };
float[] gap = new float[44100 / 4 * 2];         // a quarter second of silence
 
var format = new AudioFormat(44100, 2);
var pcm16 = new WavEncoderSettings { SampleType = NativeSampleType.PcmSigned16 };
using (var wav = new WavEncoder(File.Create("joined.wav"), format, pcm16,
                                leaveOpen: false))
{
    for (int i = 0; i < inputs.Length; i++)
    {
        int rate;
        using (IAudioDecoder d = Audio.Open(inputs[i]))
            rate = d.Format.SampleRate;
        AudioClip clip = Audio.Load(inputs[i], common);
        Console.WriteLine($"{inputs[i],-11}{rate,6} Hz -> " +
                          $"{clip.Frames,6} frames at 44.1 kHz");
        wav.Write(clip.Samples.Span);
        if (i < inputs.Length - 1)
            wav.Write(gap);
    }
}
 
using IAudioDecoder joined = Audio.Open("joined.wav");
Console.WriteLine($"joined.wav: {joined.LengthFrames} frames, " +
                  $"{joined.LengthFrames / 44100.0:F3} s");
Text
one.mp3     44100 Hz ->  26460 frames at 44.1 kHz
two.flac    22050 Hz -> 132000 frames at 44.1 kHz
three.opus  48000 Hz ->  44100 frames at 44.1 kHz
joined.wav: 224610 frames, 5.093 s

Notes

  • Pick the rate and channels of the output once; LoadOptions resamples (polyphase filter) and up- or down-mixes each input to match. Mono becomes stereo as a copy.
  • Audio.Load holds each input in memory, which is fine for clips and tracks. For hours of audio, read each input with Audio.Open and Read in blocks, and resample with AudioResampler (see Resample audio).
  • Opus files, M4A files and MP3 files with a LAME/Xing header record their encoder padding, and oneaudio trims it, so they add no extra silence at the joins. Raw AAC and header-less MP3 keep theirs.
  • For a crossfade instead of a gap, or to layer files on top of each other, use the mixer: see Mix audio files.

Limits

  • The output is WAV here; convert it to FLAC or Opus with Audio.Transcode afterwards. oneaudio doesn’t write MP3.
  • Two files at different loudness stay different. To even them out, normalize each one first: see Normalize loudness.