Search code examples
c#audionaudio

C# Verify Code for playing 2 audio tracks at specific SNR


I have two WAV audio tracks which I want to play at SNR of +10, +8,+6,+4 and 0db respectively

Formula of SNR (db) = 20 log(rms of signal / rms of noise)

For this purpose, I need to calculate and set the SNR of my audio tracks using the language C#. I finally came up with this solution thanks to people and want to verify it.

Here is what I did:

    public void play(int required_snr)
    {
        WaveFileReader signal = new WaveFileReader(@"E:\signal.wav"),
                            noise = new WaveFileReader(@"E:\noise.wav");

        int signal_length = (int)signal.Length, noise_length = (int)noise.Length, i;

        byte[] signal_sample = new byte[signal_length], noise_sample = new byte[noise_length];

        int signal_read = signal.Read(signal_sample, 0, signal_length) / 2, noise_read = noise.Read(noise_sample, 0, noise_length) / 2;

        float[] sample_arr = new float[signal_read], noise_arr = new float[noise_read];
        float sum = 0;

        for (i = 0; i < sample_arr.Length; i++)
        {
            sample_arr[i] = (float)BitConverter.ToInt16(signal_sample, i * 2) / 32768f;
            sum += (sample_arr[i] * sample_arr[i]);
        }

        float rms_signal = (float)Math.Sqrt((sum / sample_arr.Length));

        sum = 0;

        for (i = 0; i < noise_arr.Length; i++)
        {
            noise_arr[i] = (float)BitConverter.ToInt16(noise_sample, i * 2) / 32768f;
            sum += (noise_arr[i] * noise_arr[i]);
        }

        float rms_noise = (float)Math.Sqrt((sum / noise_arr.Length));

        float snr_db = (float)Math.Round(20 * Math.Log10(rms_signal / rms_noise), 1);

        float factor = (float)Math.Pow(10, (required_snr - snr_db) / 20);

        rms_noise = 0;

        for (i = 0; i < noise_arr.Length; i++)
        {
            noise_arr[i] = noise_arr[i] / factor;

            rms_noise += (noise_arr[i] * noise_arr[i]);
        }

        rms_noise = (float)Math.Sqrt((rms_noise / noise_arr.Length));
        snr_db = (float)Math.Round(20 * Math.Log10(rms_signal / rms_noise), 1);

        using (WaveFileWriter writer = new WaveFileWriter(@"E:\aw4.wav", noise.WaveFormat))
        {
            for (i = 0; i < noise_arr.Length; i++)
                writer.WriteSample(noise_arr[i]);
        }

        WaveFileReader reader = new WaveFileReader(@"E:\aw4.wav");
        WaveOut waveOut = new WaveOut();
        waveOut.Init(reader);
        waveOut.Play();

        WaveFileReader si = new WaveFileReader(@"E:\signal.wav");
        WaveOut o = new WaveOut();
        o.Init(si);
        o.Play();
    }

Is this code correct? I am a noob in DSP so I dont know. But I do hear and feel the changes in the noise volume level which increases as I decrease my required SNR from 10 to 0.


Solution

  • You want to produce an audio signal with a defined signal-to-noise ratio (SNR). This implies that you have two audio signals, one is signal the other is noise.

    All the following can be done by direct sample (int) manipulation, multiplying a sample with a factor or adding two samples.

    First you have to measure the RMS (root mean square) Level of both signals. This is (as the name implies) Thre root of the mean of all squared samples, expressed as a decibel value:

     db = 20*Log10(amplitude)
    

    Assuming you have a signal with 30 dB and a noise with 20 dB you will arrive at a SNR of 30-20 = 10dB.

    If you need a better SNR you have to either amplify the signal or weaken the noise.

    You amplify by multiplicating all samples with a constant factor. (Be careful to not overload, i.e. produce samples too large for the allowed integer range)

    factor = exp10(db/20);
    

    Finally you add both signals to arrive at the combined signal with the wanted SNR.


    Edit

    The signal to noise ratio can be calculated in two ways:

    snr1 = 20 * log10(rms_signal / rms_noise)
    

    or using log (decibel) values:

    snr2 = db_signal - db_noise
    

    These two are equivalent.

    Here the more detailed steps to produce a mixed signal with a defined SNR from two audio tracks.

    Method1 linear calculation

    Calculate the linear rms of both signals:

    rms := sqrt ( sum(x*x) / num(x) )
    

    With x are all the samples (usually int16) of an audio track.

    A SNR of 6 dB is equivalent to a linear ratio of exp10(6/20) = 2.0

    So if you found an average amplitude (rms) of 160 for both of your signal, you'll have either to multiply signal by two or divide noise by two. This will result in two rms values of 160 and 80 which gives you the expected SNR of 2.0 (linear) or 6.0 dB.

    Method2 logarithmic calculation

    First calculate linear rms as above (results in signal = 160, noise = 160)

    Second calc decibel of this rms values, results in signal=40 dB noise = 40dB

    So the current SNR is 40-40 = 0dB, both parts have the same power.

    To arrive at an SNR of 6dB you'll have to lower the noise component by 6dB.

    The factor needed to do so is exp10(6.0/20.0) = 2.0

    So you have to divide your noise signal by 2.0.

    This will give you a linear rms of 80 for the noise signal.

    So finally you have a SNR = 20.0 * log10(160/80) = 6.0


    Edit

    After modifying the noise signal, you have to mix signal and noise:

    for(...)  mix[i] = signal[i] + noise[i];
    

    This will result in a noisy signal with the required SNR.