Overlap two audio clips while one fades out and the other fades in.
dotnet add package RecursiveAI.OneAudioRequires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.
first.wav and second.wav come in the download. Any two mono 48 kHz WAVs work; use clips at least two seconds long to fill this example’s transition.
using OneAudio; var mixer = new AudioMixer(new MixerOptions { SampleRate = 48000, Channels = 1 }); var first = mixer.Play(new PlayParams(Audio.Load("first.wav"))); float[] samples = new float[144000]; mixer.Render(samples.AsSpan(0, 48000)); var next = mixer.Crossfade(first, new PlayParams(Audio.Load("second.wav")), 48000); if (!next.IsValid) throw new Exception("Crossfade was not queued"); mixer.Render(samples.AsSpan(48000)); using (var wav = new WavEncoder(File.Create("crossfade.wav"), new AudioFormat(48000, 1), new WavEncoderSettings(), leaveOpen: false)) wav.Write(samples); Console.WriteLine("Wrote crossfade.wav");
Wrote crossfade.wavThe download’s original two-second tones are 440 Hz and 660 Hz. Both samples write a three-second crossfade.wav.
Unzip the example, then dotnet run.
The full program
The download writes crossfade.wav: one second of the first tone, one second of overlap, then one second of the second tone. The tones were generated for this example and may be freely reused.
using OneAudio; // first.wav and second.wav are the generated tones in the download. var mixer = new AudioMixer(new MixerOptions { SampleRate = 48000, Channels = 1 }); var first = mixer.Play(new PlayParams(Audio.Load("first.wav"))); float[] samples = new float[144000]; mixer.Render(samples.AsSpan(0, 48000)); var second = mixer.Crossfade(first, new PlayParams(Audio.Load("second.wav")), 48000); if (!second.IsValid) throw new InvalidOperationException("Crossfade was not queued."); mixer.Render(samples.AsSpan(48000)); using (var wav = new WavEncoder(File.Create("crossfade.wav"), new AudioFormat(48000, 1), new WavEncoderSettings(), leaveOpen: false)) wav.Write(samples); using var result = Audio.Open("crossfade.wav"); Console.WriteLine($"crossfade.wav: {result.LengthFrames} frames at {result.Format.SampleRate} Hz");
dotnet run prints:
crossfade.wav: 144000 frames at 48000 HzIf Crossfade returns an invalid handle, the command was not queued; the program stops rather than writing a transition that never started.
Variations
A half-second overlap: replace the crossfade call in the full program. The output frame count remains unchanged because the render buffer still covers three seconds.
var second = mixer.Crossfade(first, new PlayParams(Audio.Load("second.wav")), 24000);
Related recipes
The short answer
Use AudioMixer.Crossfade with the outgoing voice handle, the incoming source and the fade length in mixer frames. Render before the call to establish the handoff point, then render afterward to produce the overlap.
What is audio crossfade?
A crossfade overlaps two voices: the outgoing voice fades to silence while the incoming voice fades up. A hard join instead puts the second clip immediately after the first. With two two-second clips and a one-second overlap, the result is three seconds, not four. Those are this example’s chosen durations, not library defaults.
In oneaudio, Crossfade starts the incoming voice and stops the outgoing one over the same frame count in the same render pass. The library queues both operations together or neither. It replaces the incoming PlayParams.FadeInFrames with the crossfade length. The outgoing voice’s stale handle is ignored, so retain the handle from the voice you actually want to replace. See the mixer API.
The call does not render an output file or play through a sound device. Render consumes the queued commands and fills the supplied sample buffer. The full program then passes that buffer to WavEncoder. This makes the example an offline file operation; no speaker, microphone or device package is needed.
What is the perfect crossfade time?
There is no single duration selected by this API. You supply a positive frame count. Choose a short overlap for a brief handoff, or a longer overlap when you want both recordings audible together. Listen to the result with your actual material: the example’s steady tones make overlap easy to hear, but do not establish a suitable duration for music or speech.
A frame contains one sample per channel. At this example’s 48,000 Hz mixer rate, 48,000 frames means one second. Stereo still uses 48,000 frames for that second, but needs 96,000 float values. The mixer derives the number of frames from the buffer length divided by the output channel count. Passing a stereo-sized buffer to a mono mixer would therefore render twice the intended time.
The fade length must be at least one frame. Both clips also need material available throughout the intended overlap. An ended source cannot contribute more sound just because its fade is still scheduled. For long recordings, use the documented streaming sources instead of loading whole files into clips.
How do I crossfade audio in C#?
Render the outgoing voice up to the handoff point, queue Crossfade, then render the overlap and the remaining tail. That order is important for offline work: queuing the handoff before any render would begin the fade at the start, rather than after the first second.
The mixer sums its inputs rather than clipping them. Account for levels when replacing the low-amplitude tones with recordings. Both samples use a plain AudioMixer without a master limiter.
A busy control queue can reject commands. Check the returned handle before committing the file, as both examples do. Keep source sample rates and output rate distinct when calculating durations: this crossfade’s count is in mixer frames, not a byte count or a count of samples read from either source file.