AutoGain
alenet444 opened this issue · comments
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.