Search code examples
c#winformssoundplayer

How do I fade out the audio of a .wav file using SoundPlayer instead of stopping it immediately?


I'm trying to stop sound as soon as I release the button, but what happens is that file is played fully. I don't want to stop the sound abruptly as it happened with sp.Stop(). Is there a way to do this?

private void button1_MouseDown(object sender, MouseEventArgs e)
{
    if(sender == mk)
    {
        if(e.Button == MouseButtons.Left)
        {
            SoundPlayer sp = new SoundPlayer();

            sp.SoundLocation = (@"C:\my path\sound.wav");
            sp.Play();
        }
    }
}

private void button1_MouseUp(object sender, MouseEventArgs e)
{
    if(sender == mk)
    {
        if(e.Button == MouseButtons.Left)
        { /*some code here (without sp.Stop())*/ }
    }
}

Solution

  • I'm interpreting your question as:

    How do I fade out the audio of a .wav file using SoundPlayer instead of stopping it immediately?

    The short answer is that you can't do it with SoundPlayer alone. You'll need the use of a couple apis from the winmm.dll library.

    If you are comfortable with WPF, then using a MediaElement with a DoubleAnimation on it's volume property is probably a better way to do this. See this question for information on that.

    However if you really want to use WinForms, the following is an implementation that should work.

    Notes about Form1:

    • Two buttons, named startButton and stopButton
    • A timer named timer1 that I use to fade out the sound

    Here is the code to get this working:

    using System;
    using System.Media;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    
    namespace WindowsFormsApplication1
    {
    
        public partial class Form1 : Form
        {
    
            [DllImport("winmm.dll", EntryPoint = "waveOutGetVolume")]
            private static extern int WaveOutGetVolume(IntPtr hwo, out uint dwVolume);
    
            [DllImport("winmm.dll", EntryPoint="waveOutSetVolume")]
            private static extern int WaveOutSetVolume(IntPtr hwo, uint dwVolume);
    
            private SoundPlayer player = new SoundPlayer();
    
            // a crude delta time field
            private float totalElapsedTime;
    
            // tweak this value to determine how quickly you want the fade to happen
            private const float Velocity = 0.001f;
    
            public Form1()
            {
                this.InitializeComponent();
    
                // i was using 100 milliseconds as my "frame rate"
                this.timer1.Interval = 100;
                this.stopButton.Enabled = false;
            }
    
            private void startButton_Click(object sender, EventArgs e)
            {
                // sets the audio device volume to the max.
                // this is not the computer's volume so it won't
                // blast out your ear drums by doing this unless you
                // have the computer volume super high - which is not this
                // code's fault
                WaveOutSetVolume(IntPtr.Zero, uint.MaxValue);
    
                this.startButton.Enabled = false;
                this.stopButton.Enabled = true;
    
                this.totalElapsedTime = 0f;
                this.player.SoundLocation = @"Music File.wav";
                this.player.Load();
                this.player.Play();
            }
    
            private void stopButton_Click(object sender, EventArgs e)
            {
                // being the stop
                this.timer1.Start();
                this.stopButton.Enabled = false;
            }
    
            private void timer1_Tick(object sender, EventArgs e)
            {
                // amount to interpolate (value between 0 and 1 inclusive)
                float amount = Math.Min(1f, this.totalElapsedTime * Velocity);
    
                // the new channel volume after a lerp
                float lerped = Lerp(ushort.MaxValue, 0, amount);
    
                // each channel's volume is actually represented as a ushort
                ushort channelVolume = (ushort)lerped;
    
                // the new volume for all the channels
                uint volume = (uint)channelVolume | ((uint)channelVolume << 16);
    
                // sets the volume 
                WaveOutSetVolume(IntPtr.Zero, volume);
    
                // checks if the interpolation is finished
                if (amount >= 1f)
                {
                    // stop the timer 
                    this.timer1.Stop();
    
                    // stop the player
                    this.player.Stop();
    
                    // stop is complete so let user start again
                    this.startButton.Enabled = true;
                }
    
                // add the elapsed milliseconds (very crude delta time)
                this.totalElapsedTime += this.timer1.Interval;
            }
    
            public static float Lerp(float value1, float value2, float amount)
            {
                // does a linear interpolation
                return (value1 + ((value2 - value1) * amount));
            }
    
        }
    
    }
    

    Hopefully this should be pretty self-explanatory. I think most people will recognize this as a crude "game-loop-esqe" approach, but it seems to work well. Of note here is that to tweak the speed at which the fade happens is the Velocity constant.

    The code is setup to fade out over 1 second. It's easy to figure that out by looking at the timer1.Interval and the Velocity. After 1000 milliseconds (10 timer ticks) then 1000 * 0.001 = 1 which causes the stop code to complete.

    The only reason to change the timer1.Interval is to make more "fades" occur. Current setup does 10 volume fades before stopping.