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.
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:
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");
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;
LoadOptionsresamples (polyphase filter) and up- or down-mixes each input to match. Mono becomes stereo as a copy. Audio.Loadholds each input in memory, which is fine for clips and tracks. For hours of audio, read each input withAudio.OpenandReadin blocks, and resample withAudioResampler(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.Transcodeafterwards. oneaudio doesn’t write MP3. - Two files at different loudness stay different. To even them out, normalize each one first: see Normalize loudness.