naudio / NAudio

Audio and MIDI library for .NET
MIT License
5.58k stars 1.1k forks source link

AutoGain #1157

Open alenet444 opened 6 months ago

alenet444 commented 6 months ago

I have developed a class in VB.NET that dynamically adjusts the gain of an audio signal to maintain a consistent volume level. It has proven to be very effective for my needs, so I decided to convert it to C#. My knowledge of C# is limited, so any improvements or feedback are welcome. I believe this class could be a valuable addition to the NAudio library.

using System;
using NAudio.Wave;

public class AutoGainSampleProvider : ISampleProvider
{
    private readonly ISampleProvider source;
    private readonly float gainFactor;
    private readonly float targetLevel;
    private readonly float maxGain;
    private readonly float adjustmentSpeed;
    private readonly float gateThreshold;
    private readonly float freezeThreshold;
    private readonly float attack;
    private readonly float release;
    private readonly float ratio;
    private float currentGain;
    private float lastRms;
    private bool _isEnabled;

    public AutoGainSampleProvider(ISampleProvider source, float gainFactor, float targetLevel, float maxGain, float adjustmentSpeed, float gateThreshold, float freezeThreshold, float attack, float release, float ratio, bool isEnabled = true)
    {
        this.source = source;
        this.gainFactor = gainFactor;
        this.targetLevel = targetLevel;
        this.maxGain = maxGain;
        this.adjustmentSpeed = adjustmentSpeed;
        this.gateThreshold = gateThreshold;
        this.freezeThreshold = freezeThreshold;
        this.attack = attack;
        this.release = release;
        this.ratio = ratio;
        this.currentGain = 1.0f;
        this.lastRms = 0.0f;
        this._isEnabled = isEnabled;
    }

    public WaveFormat WaveFormat => source.WaveFormat;

    public bool IsEnabled
    {
        get => _isEnabled;
        set => _isEnabled = value;
    }

    public int Read(float[] buffer, int offset, int count)
    {
        int samplesRead = source.Read(buffer, offset, count);
        AdjustGain(buffer, offset, samplesRead);
        if (_isEnabled)
        {
            ApplyGain(buffer, offset, samplesRead);
        }
        return samplesRead;
    }

    private void AdjustGain(float[] buffer, int offset, int count)
    {
        float rms = 0.0f;

        for (int i = 0; i < count; i++)
        {
            rms += buffer[offset + i] * buffer[offset + i];
        }

        rms = (float)Math.Sqrt(rms / count);

        // Apply gate threshold
        if (rms < gateThreshold)
        {
            currentGain = 1.0f;
            return;
        }

        // Freeze threshold
        if (rms < freezeThreshold && lastRms < freezeThreshold)
        {
            return;
        }

        lastRms = rms;

        // Compression ratio
        float desiredGain = targetLevel / (rms + 1.0E-10f);
        desiredGain = Math.Min(desiredGain, maxGain);
        desiredGain = 1.0f + ((desiredGain - 1.0f) / ratio);

        // Attack and release
        float gainDifference = desiredGain - currentGain;
        if (gainDifference > 0)
        {
            currentGain += gainDifference * attack;
        }
        else
        {
            currentGain += gainDifference * release;
        }
    }

    private void ApplyGain(float[] buffer, int offset, int count)
    {
        for (int i = 0; i < count; i++)
        {
            buffer[offset + i] *= currentGain * gainFactor;
        }
    }
}

A code example (generated by IA)

using System;
using NAudio.Wave;

namespace AudioPlaybackExample
{
    class Program
    {
        static void Main(string[] args)
        {
            // File path to audio
            string audioFilePath = "path_to_your_audio_file.wav";

            using (var audioFileReader = new AudioFileReader(audioFilePath))
            {
                // Convert audio to 32 bits (SampleChannel)
                var sampleChannel = new SampleChannel(audioFileReader, true);

                // Create instance for AutoGainSampleProvider with parameters
                var autoGain = new AutoGainSampleProvider(
                    sampleChannel,
                    gainFactor: 1.0f,
                    targetLevel: 0.1995f,
                    maxGain: 2.0f,
                    adjustmentSpeed: 0.00001f,
                    gateThreshold: 0.01f,
                    freezeThreshold: 0.05f,
                    attack: 0.001f,
                    release: 0.0005f,
                    ratio: 1.0f,
                    isEnabled: true
                );

                // Use WaveOutEvent for play
                using (var waveOut = new WaveOutEvent())
                {
                    waveOut.Init(autoGain);
                    waveOut.Play();

                    // Wait for end
                    while (waveOut.PlaybackState == PlaybackState.Playing)
                    {
                        System.Threading.Thread.Sleep(100);
                    }
                }
            }
        }
    }
}

I apologize if there are any errors in the code. I used AI to convert it from VB.NET to C#. If needed, I have the original code written in VB.NET.

Feel free to reach out if you have any questions or suggestions for improvement.