Forked from Ahmed-Abdelhameed/DelayFadeOutSampleProvider.cs
Last active
September 1, 2020 11:33
-
-
Save ransagy/5e3180e42501e243108c249b2da92734 to your computer and use it in GitHub Desktop.
NAudio basic example of how to begin a fade in or fade out after a certain number of milliseconds have elapsed, as a stop gap to crossfading.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using NAudio.Wave; | |
namespace WaveCombineHelper | |
{ | |
/// <summary> | |
/// Sample Provider to allow fading in and out with delays. Based on https://gist.github.com/Ahmed-Abdelhameed/b867a8cfe739cd7a128b73a33d317402 | |
/// </summary> | |
public class DelayFadeSampleProvider : ISampleProvider | |
{ | |
private enum FadeState | |
{ | |
Silence, | |
FadingIn, | |
FullVolume, | |
FadingOut, | |
} | |
private readonly object lockObject = new object(); | |
private readonly ISampleProvider source; | |
private int fadeSamplePosition; | |
private int fadeSampleCount; | |
private int fadeOutDelaySamples; | |
private int fadeOutDelayPosition; | |
private int fadeInDelaySamples; | |
private int fadeInDelayPosition; | |
private FadeState fadeState; | |
/// <summary> | |
/// Creates a new FadeInOutSampleProvider | |
/// </summary> | |
/// <param name="source">The source stream with the audio to be faded in or out</param> | |
/// <param name="initiallySilent">If true, we start faded out</param> | |
public DelayFadeSampleProvider(ISampleProvider source, bool initiallySilent = false) | |
{ | |
this.source = source; | |
fadeState = initiallySilent ? FadeState.Silence : FadeState.FullVolume; | |
} | |
/// <summary> | |
/// Requests that a fade-in begins (will start on the next call to Read) | |
/// </summary> | |
/// <param name="fadeDurationInMilliseconds">Duration of fade in milliseconds</param> | |
public void BeginFadeIn(double fadeAfterMilliseconds, double fadeDurationInMilliseconds) | |
{ | |
lock (lockObject) | |
{ | |
fadeSamplePosition = 0; | |
fadeSampleCount = (int)((fadeDurationInMilliseconds * source.WaveFormat.SampleRate) / 1000); | |
fadeInDelaySamples = (int)((fadeAfterMilliseconds * source.WaveFormat.SampleRate) / 1000); | |
fadeInDelayPosition = 0; | |
} | |
} | |
/// <summary> | |
/// Requests that a fade-out begins (will start on the next call to Read) | |
/// </summary> | |
/// <param name="fadeDurationInMilliseconds">Duration of fade in milliseconds</param> | |
public void BeginFadeOut(double fadeAfterMilliseconds, double fadeDurationInMilliseconds) | |
{ | |
lock (lockObject) | |
{ | |
fadeSamplePosition = 0; | |
fadeSampleCount = (int)((fadeDurationInMilliseconds * source.WaveFormat.SampleRate) / 1000); | |
fadeOutDelaySamples = (int)((fadeAfterMilliseconds * source.WaveFormat.SampleRate) / 1000); | |
fadeOutDelayPosition = 0; | |
} | |
} | |
/// <summary> | |
/// Reads samples from this sample provider | |
/// </summary> | |
/// <param name="buffer">Buffer to read into</param> | |
/// <param name="offset">Offset within buffer to write to</param> | |
/// <param name="count">Number of samples desired</param> | |
/// <returns>Number of samples read</returns> | |
public int Read(float[] buffer, int offset, int count) | |
{ | |
int sourceSamplesRead = source.Read(buffer, offset, count); | |
lock (lockObject) | |
{ | |
if (fadeOutDelaySamples > 0) | |
{ | |
int oldFadeOutDelayPos = fadeOutDelayPosition; | |
fadeOutDelayPosition += sourceSamplesRead / WaveFormat.Channels; | |
if (fadeOutDelayPosition > fadeOutDelaySamples) | |
{ | |
int normalSamples = (fadeOutDelaySamples - oldFadeOutDelayPos) * WaveFormat.Channels; | |
int fadeOutSamples = (fadeOutDelayPosition - fadeOutDelaySamples) * WaveFormat.Channels; | |
// apply the fade-out only to the samples after fadeOutDelayPosition | |
FadeOut(buffer, offset + normalSamples, fadeOutSamples); | |
fadeOutDelaySamples = 0; | |
fadeState = FadeState.FadingOut; | |
return sourceSamplesRead; | |
} | |
} | |
if (fadeInDelaySamples > 0) | |
{ | |
int oldFadeInDelayPos = fadeInDelayPosition; | |
fadeInDelayPosition += sourceSamplesRead / WaveFormat.Channels; | |
if (fadeInDelayPosition > fadeInDelaySamples) | |
{ | |
int normalSamples = (fadeInDelaySamples - oldFadeInDelayPos) * WaveFormat.Channels; | |
int fadeInSamples = (fadeInDelayPosition - fadeInDelaySamples) * WaveFormat.Channels; | |
// apply the fade-in only to the samples after fadeInDelayPosition | |
FadeIn(buffer, offset + normalSamples, fadeInSamples); | |
fadeInDelaySamples = 0; | |
fadeState = FadeState.FadingIn; | |
return sourceSamplesRead; | |
} | |
} | |
if (fadeState == FadeState.FadingIn) | |
{ | |
FadeIn(buffer, offset, sourceSamplesRead); | |
} | |
else if (fadeState == FadeState.FadingOut) | |
{ | |
FadeOut(buffer, offset, sourceSamplesRead); | |
} | |
else if (fadeState == FadeState.Silence) | |
{ | |
ClearBuffer(buffer, offset, count); | |
} | |
} | |
return sourceSamplesRead; | |
} | |
private static void ClearBuffer(float[] buffer, int offset, int count) | |
{ | |
for (int n = 0; n < count; n++) | |
{ | |
buffer[n + offset] = 0; | |
} | |
} | |
private void FadeOut(float[] buffer, int offset, int sourceSamplesRead) | |
{ | |
int sample = 0; | |
while (sample < sourceSamplesRead) | |
{ | |
float multiplier = 1.0f - (fadeSamplePosition / (float)fadeSampleCount); | |
for (int ch = 0; ch < source.WaveFormat.Channels; ch++) | |
{ | |
buffer[offset + sample++] *= multiplier; | |
} | |
fadeSamplePosition++; | |
if (fadeSamplePosition > fadeSampleCount) | |
{ | |
fadeState = FadeState.Silence; | |
// clear out the end | |
ClearBuffer(buffer, sample + offset, sourceSamplesRead - sample); | |
break; | |
} | |
} | |
} | |
private void FadeIn(float[] buffer, int offset, int sourceSamplesRead) | |
{ | |
int sample = 0; | |
while (sample < sourceSamplesRead) | |
{ | |
float multiplier = (fadeSamplePosition / (float)fadeSampleCount); | |
for (int ch = 0; ch < source.WaveFormat.Channels; ch++) | |
{ | |
buffer[offset + sample++] *= multiplier; | |
} | |
fadeSamplePosition++; | |
if (fadeSamplePosition > fadeSampleCount) | |
{ | |
fadeState = FadeState.FullVolume; | |
// no need to multiply any more | |
break; | |
} | |
} | |
} | |
/// <summary> | |
/// WaveFormat of this SampleProvider | |
/// </summary> | |
public WaveFormat WaveFormat => source.WaveFormat; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void Main() | |
{ | |
using(var reader = new AudioFileReader(@"D:\Audio\Music\Example.mp3")) | |
{ | |
var fadeOut = new DelayFadeOutSampleProvider(reader); | |
fadeOut.BeginFadeOut(10000, 2000); | |
using(var player = new WaveOutEvent()) | |
{ | |
player.Init(fadeOut); | |
player.Play(); | |
while(player.PlaybackState == PlaybackState.Playing) | |
{ | |
Thread.Sleep(500); | |
} | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment