Open the file, Seek to the start of the part you want, Read until its end and write what you read with a WavEncoder. Seeking is sample-exact, so the cut lands where you ask. The file here is a three-second synthetic test file from the oneaudio repository; the program keeps 0.5 s to 2.0 s.
dotnet new console -o TrimAudio && cd TrimAudio 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 song.flac $t/flac/stereo16_seektable.flac
Put this in Program.cs and run dotnet run:
using OneAudio; TimeSpan from = TimeSpan.FromSeconds(0.5), to = TimeSpan.FromSeconds(2.0); using IAudioDecoder decoder = Audio.Open("song.flac"); AudioFormat f = decoder.Format; long start = (long)(from.TotalSeconds * f.SampleRate); long end = Math.Min((long)(to.TotalSeconds * f.SampleRate), decoder.LengthFrames); long fade = f.SampleRate / 100; // 10 ms at each end decoder.Seek(start); // A lossless source keeps its bit depth; a lossy one is written as 16-bit. using (var wav = new WavEncoder(File.Create("cut.wav"), f, new WavEncoderSettings(), leaveOpen: false)) { float[] block = new float[4096 * f.Channels]; long position = start; while (position < end) { int want = (int)Math.Min(4096, end - position); int read = decoder.Read(block.AsSpan(0, want * f.Channels)); if (read == 0) break; for (int i = 0; i < read; i++) // fade in and out { long fromStart = position + i - start, fromEnd = end - (position + i) - 1; float gain = Math.Min(1f, Math.Min(fromStart, fromEnd) / (float)fade); for (int c = 0; c < f.Channels; c++) block[i * f.Channels + c] *= gain; } wav.Write(block.AsSpan(0, read * f.Channels)); position += read; } } using IAudioDecoder cut = Audio.Open("cut.wav"); Console.WriteLine($"song.flac: {decoder.LengthFrames / (double)f.SampleRate:F3} s"); Console.WriteLine($"cut.wav: {cut.LengthFrames} frames, " + $"{cut.LengthFrames / (double)f.SampleRate:F3} s, " + $"{cut.Format.NativeType}");
song.flac: 2.993 s cut.wav: 33075 frames, 1.500 s, PcmSigned16
The 10 ms fades stop the cut from clicking where it lands mid-waveform. Leave the loop out (and use ReadInteger with WriteInteger) for a bit-exact cut of a lossless file.
Limits
- Works the same for MP3, Ogg Vorbis, Opus, FLAC, AAC, AMR and WAV inputs: only the input name changes. MP3 and AAC seeks are exact because the decoder rebuilds its state from frames before the target.
- The output is WAV here. To keep it compressed, write the WAV to FLAC or Opus with
Audio.Transcode(see Convert audio files). A lossy file can’t be cut without decoding, so the result is re-encoded, never a byte-for-byte copy of the original frames. - A stream that can’t seek (a pipe or an HTTP body) throws
NotSupportedExceptionfromSeek; read and discard frames up to the start instead.
To join clips end to end, see Join audio files.