Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fading sound in/out

Tags:

c#

naudio

I have a background sound playing in an endless loop. I want it to fade out when the user does press a button.

I tried the following:

  • A DirectSoundOut is initiated with the WaveStream
  • A Timer changes the volume of the WaveChannel32.

The Problem:

  • Changing the volume while the sound is playing produces noises.

Does anyone knows a better solution?

like image 331
Christian Kirst Avatar asked Feb 27 '12 16:02

Christian Kirst


1 Answers

To perform a smooth fade-in or fade-out, you need to do so at the sample level. You then multiply each sample by a gradually increasing or decreasing number. You are using WaveChannel32, so your audio has already been converted to 32 bit float. I would then create another IWaveProvider implementer that was responsible for doing the fade in and fade out. Normally it would pass through samples unchanged, but in the Read method, if you are in a fade in or fade out, it would multiply each sample (or pair if it is stereo).

The ISampleProvider interface in NAudio 1.5 was designed to make this type of thing much easier, as it allows you to deal samples already as 32 bit floats, rather than implementing IWaveProvider which requires you to convert from a byte[] to float[]. Here's a SampleProvider for fade-in and fade-out I just made which I will include in the next NAudio, and hopefully blog about it soon. Just call BeginFadeIn or BeginFadeOut with the appropriate fade duration.

public class FadeInOutSampleProvider : ISampleProvider
{
    enum FadeState
    {
        Silence,
        FadingIn,
        FullVolume,
        FadingOut,
    }

    private readonly object lockObject = new object();
    private readonly ISampleProvider source;
    private int fadeSamplePosition;
    private int fadeSampleCount;
    private FadeState fadeState;

    public FadeInOutSampleProvider(ISampleProvider source)
    {
        this.source = source;
        this.fadeState = FadeState.FullVolume;
    }

    public void BeginFadeIn(double fadeDurationInMilliseconds)
    {
        lock (lockObject)
        { 
            fadeSamplePosition = 0;
            fadeSampleCount = (int)((fadeDurationInMilliseconds * source.WaveFormat.SampleRate) / 1000);
            fadeState = FadeState.FadingIn;
        }
    }

    public void BeginFadeOut(double fadeDurationInMilliseconds)
    {
        lock (lockObject)
        {
            fadeSamplePosition = 0;
            fadeSampleCount = (int)((fadeDurationInMilliseconds * source.WaveFormat.SampleRate) / 1000);
            fadeState = FadeState.FadingOut;
        }
    }

    public int Read(float[] buffer, int offset, int count)
    {
        int sourceSamplesRead = source.Read(buffer, offset, count);
        lock (lockObject)
        {
            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;
            }
        }
    }

    public WaveFormat WaveFormat
    {
        get { return source.WaveFormat; }
    }
}
like image 138
Mark Heath Avatar answered Sep 28 '22 07:09

Mark Heath