NReplayGain by David Robinson, Glen Sawyer, Frank Klemm, Marcel Müller, Ivailo Karamanolev, Kyle McClellan

<PackageReference Include="NReplayGain" Version="1.0.2" />

 TrackGain

public class TrackGain : IDisposable
Contains ReplayGain data for a track.
using System; namespace NReplayGain { public class TrackGain : IDisposable { private int sampleSize; internal GainData gainData; private double[] lInPreBuf; private CPtr<double> lInPre; private double[] lStepBuf; private CPtr<double> lStep; private double[] lOutBuf; private CPtr<double> lOut; private double[] rInPreBuf; private CPtr<double> rInPre; private double[] rStepBuf; private CPtr<double> rStep; private double[] rOutBuf; private CPtr<double> rOut; private long sampleWindow; private long totSamp; private double lSum; private double rSum; private int freqIndex; public TrackGain(int sampleRate, int sampleSize) { if (!ReplayGain.IsSupportedFormat(sampleRate, sampleSize)) throw new NotSupportedException("Unsupported format. Supported sample sizes are 16, 24."); freqIndex = ReplayGain.FreqInfos.IndexOf((FrequencyInfo i) => i.SampleRate == sampleRate); this.sampleSize = sampleSize; gainData = new GainData(); lInPreBuf = new double[20]; lStepBuf = new double[2411]; lOutBuf = new double[2411]; rInPreBuf = new double[20]; rStepBuf = new double[2411]; rOutBuf = new double[2411]; sampleWindow = (int)Math.Ceiling((double)sampleRate * 0.05); lInPre = new CPtr<double>(lInPreBuf, 10); lStep = new CPtr<double>(lStepBuf, 10); lOut = new CPtr<double>(lOutBuf, 10); rInPre = new CPtr<double>(rInPreBuf, 10); rStep = new CPtr<double>(rStepBuf, 10); rOut = new CPtr<double>(rOutBuf, 10); } public void AnalyzeSamples(int[] leftSamples, int[] rightSamples) { if (leftSamples.Length != rightSamples.Length) throw new ArgumentException("leftSamples must be as big as rightSamples"); int num = leftSamples.Length; double[] array = new double[num]; double[] array2 = new double[num]; if (sampleSize == 16) { for (int i = 0; i < num; i++) { array[i] = (double)leftSamples[i]; array2[i] = (double)rightSamples[i]; } } else { if (sampleSize != 24) throw new InvalidOperationException(); for (int j = 0; j < num; j++) { array[j] = (double)leftSamples[j] * ReplayGain.FACTOR_24BIT; array2[j] = (double)rightSamples[j] * ReplayGain.FACTOR_24BIT; } } for (int k = 0; k < num; k++) { double num2 = (array[k] >= 0) ? array[k] : (0 - array[k]); if (num2 > gainData.PeakSample) gainData.PeakSample = num2; num2 = ((array2[k] >= 0) ? array2[k] : (0 - array2[k])); if (num2 > gainData.PeakSample) gainData.PeakSample = num2; } AnalyzeSamples(new CPtr<double>(array, 0), new CPtr<double>(array2, 0)); } private static double Sqr(double d) { return d * d; } private void FilterYule(CPtr<double> input, CPtr<double> output, long nSamples, double[] aKernel, double[] bKernel) { while (true) { long num = nSamples; nSamples = num - 1; if (num == 0) break; output[0] = 1E-10 + input[0] * bKernel[0] - output[-1] * aKernel[1] + input[-1] * bKernel[1] - output[-2] * aKernel[2] + input[-2] * bKernel[2] - output[-3] * aKernel[3] + input[-3] * bKernel[3] - output[-4] * aKernel[4] + input[-4] * bKernel[4] - output[-5] * aKernel[5] + input[-5] * bKernel[5] - output[-6] * aKernel[6] + input[-6] * bKernel[6] - output[-7] * aKernel[7] + input[-7] * bKernel[7] - output[-8] * aKernel[8] + input[-8] * bKernel[8] - output[-9] * aKernel[9] + input[-9] * bKernel[9] - output[-10] * aKernel[10] + input[-10] * bKernel[10]; output = ++output; input = ++input; } } private void FilterButter(CPtr<double> input, CPtr<double> output, long nSamples, double[] aKernel, double[] bKernel) { while (true) { long num = nSamples; nSamples = num - 1; if (num == 0) break; output[0] = input[0] * bKernel[0] - output[-1] * aKernel[1] + input[-1] * bKernel[1] - output[-2] * aKernel[2] + input[-2] * bKernel[2]; output = ++output; input = ++input; } } private void AnalyzeSamples(CPtr<double> leftSamples, CPtr<double> rightSamples) { int length = leftSamples.Length; long num = length; long num2 = 0; if (length < 10) { Array.Copy(leftSamples.Array, 0, lInPreBuf, 10, length); Array.Copy(rightSamples.Array, 0, rInPreBuf, 10, length); } else { Array.Copy(leftSamples.Array, 0, lInPreBuf, 10, 10); Array.Copy(rightSamples.Array, 0, rInPreBuf, 10, 10); } while (num > 0) { long num3 = (num > sampleWindow - totSamp) ? (sampleWindow - totSamp) : num; CPtr<double> cPtr; CPtr<double> cPtr2; if (num2 < 10) { cPtr = lInPre + num2; cPtr2 = rInPre + num2; if (num3 > 10 - num2) num3 = 10 - num2; } else { cPtr = leftSamples + num2; cPtr2 = rightSamples + num2; } FilterYule(cPtr, lStep + totSamp, num3, ReplayGain.FreqInfos[freqIndex].AYule, ReplayGain.FreqInfos[freqIndex].BYule); FilterYule(cPtr2, rStep + totSamp, num3, ReplayGain.FreqInfos[freqIndex].AYule, ReplayGain.FreqInfos[freqIndex].BYule); FilterButter(lStep + totSamp, lOut + totSamp, num3, ReplayGain.FreqInfos[freqIndex].AButter, ReplayGain.FreqInfos[freqIndex].BButter); FilterButter(rStep + totSamp, rOut + totSamp, num3, ReplayGain.FreqInfos[freqIndex].AButter, ReplayGain.FreqInfos[freqIndex].BButter); cPtr = lOut + totSamp; cPtr2 = rOut + totSamp; long num4 = num3 % 16; while (true) { long num5 = num4; num4 = num5 - 1; if (num5 == 0) break; lSum += Sqr(cPtr[0]); cPtr = ++cPtr; rSum += Sqr(cPtr2[0]); cPtr2 = ++cPtr2; } long num6 = num3 / 16; while (true) { long num7 = num6; num6 = num7 - 1; if (num7 == 0) break; lSum += Sqr(cPtr[0]) + Sqr(cPtr[1]) + Sqr(cPtr[2]) + Sqr(cPtr[3]) + Sqr(cPtr[4]) + Sqr(cPtr[5]) + Sqr(cPtr[6]) + Sqr(cPtr[7]) + Sqr(cPtr[8]) + Sqr(cPtr[9]) + Sqr(cPtr[10]) + Sqr(cPtr[11]) + Sqr(cPtr[12]) + Sqr(cPtr[13]) + Sqr(cPtr[14]) + Sqr(cPtr[15]); cPtr += 16; rSum += Sqr(cPtr2[0]) + Sqr(cPtr2[1]) + Sqr(cPtr2[2]) + Sqr(cPtr2[3]) + Sqr(cPtr2[4]) + Sqr(cPtr2[5]) + Sqr(cPtr2[6]) + Sqr(cPtr2[7]) + Sqr(cPtr2[8]) + Sqr(cPtr2[9]) + Sqr(cPtr2[10]) + Sqr(cPtr2[11]) + Sqr(cPtr2[12]) + Sqr(cPtr2[13]) + Sqr(cPtr2[14]) + Sqr(cPtr2[15]); cPtr2 += 16; } num -= num3; num2 += num3; totSamp += num3; if (totSamp == sampleWindow) { int num8 = (int)(1000 * Math.Log10((lSum + rSum) / (double)totSamp * 0.5 + 1E-37)); if (num8 < 0) num8 = 0; if (num8 >= gainData.Accum.Length) num8 = gainData.Accum.Length - 1; gainData.Accum[num8]++; lSum = (rSum = 0); if (totSamp > 2147483647) throw new OverflowException("Too many samples! Change to long and recompile!"); Array.Copy(lOutBuf, (int)totSamp, lOutBuf, 0, 10); Array.Copy(rOutBuf, (int)totSamp, rOutBuf, 0, 10); Array.Copy(lStepBuf, (int)totSamp, lStepBuf, 0, 10); Array.Copy(rStepBuf, (int)totSamp, rStepBuf, 0, 10); totSamp = 0; } if (totSamp > sampleWindow) throw new Exception("Gain analysis error!"); } if (length < 10) { Array.Copy(lInPreBuf, length, lInPreBuf, 0, 10 - length); Array.Copy(rInPreBuf, length, rInPreBuf, 0, 10 - length); Array.Copy(leftSamples.Array, leftSamples.Index, lInPreBuf, 10 - length, length); Array.Copy(rightSamples.Array, rightSamples.Index, rInPreBuf, 10 - length, length); } else { Array.Copy(leftSamples.Array, leftSamples.Index + length - 10, lInPreBuf, 0, 10); Array.Copy(rightSamples.Array, rightSamples.Index + length - 10, rInPreBuf, 0, 10); } } public double GetGain() { return ReplayGain.AnalyzeResult(gainData.Accum); } public double GetPeak() { return gainData.PeakSample / 32768; } public void Dispose() { lInPreBuf = null; lStepBuf = null; lOutBuf = null; rInPreBuf = null; rStepBuf = null; rOutBuf = null; lInPre = default(CPtr<double>); lStep = default(CPtr<double>); lOut = default(CPtr<double>); rInPre = default(CPtr<double>); rStep = default(CPtr<double>); rOut = default(CPtr<double>); } } }