From f508237dcd80add2478b7c0075623707e8d1efb1 Mon Sep 17 00:00:00 2001 From: VoidX Date: Tue, 26 Dec 2023 20:55:06 +0100 Subject: [PATCH] RMS averaging for EQ curves --- .../Equalization/EQGenerator.Averaging.cs | 84 +++++++++++++++++++ Cavern.QuickEQ/Equalization/EQGenerator.cs | 50 +---------- Cavern/Utilities/QMath.cs | 12 +++ .../EQGenerator.Averaging_Tests.cs | 20 +++++ 4 files changed, 117 insertions(+), 49 deletions(-) create mode 100644 Cavern.QuickEQ/Equalization/EQGenerator.Averaging.cs create mode 100644 Tests/Test.Cavern.QuickEQ/Equalization/EQGenerator.Averaging_Tests.cs diff --git a/Cavern.QuickEQ/Equalization/EQGenerator.Averaging.cs b/Cavern.QuickEQ/Equalization/EQGenerator.Averaging.cs new file mode 100644 index 00000000..2caa2999 --- /dev/null +++ b/Cavern.QuickEQ/Equalization/EQGenerator.Averaging.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using Cavern.Utilities; + +namespace Cavern.QuickEQ.Equalization { + public static partial class EQGenerator { + /// + /// Get the average gains of multiple equalizers. The averaging happens in linear space. + /// + /// All must have an equal number of bands at the same frequencies. + public static Equalizer Average(params Equalizer[] sources) { + double mul = 1.0 / sources[0].Bands.Count; + return Average(QMath.DbToGain, x => QMath.GainToDb(x * mul), sources); + } + + /// + /// Get the average gains of multiple equalizers. The averaging happens in linear space and an RMS value is taken. + /// + /// All must have an equal number of bands at the same frequencies. + public static Equalizer AverageRMS(params Equalizer[] sources) { + double mul = 1.0 / sources.Length; + return Average(RMSAddition, x => QMath.GainToDb(Math.Sqrt(x) * mul), sources); + } + + /// + /// Get the average gains of multiple equalizers, regardless of how many bands they have. The averaging happens in linear space. + /// + public static Equalizer AverageSafe(params Equalizer[] sources) { + List bands = new List(); + double div = 1.0 / sources.Length; + for (int i = 0; i < sources.Length; i++) { + IReadOnlyList source = sources[i].Bands; + for (int j = 0, c = source.Count; j < c; j++) { + double freq = source[j].Frequency, + gain = 0; + for (int other = 0; other < sources.Length; other++) { + gain += Math.Pow(10, sources[other][freq] * .05f); + } + bands.Add(new Band(freq, 20 * Math.Log10(gain * div))); + } + } + + bands.Sort(); + return new Equalizer(bands.Distinct().ToList(), true); + } + + /// + /// Get the average gains of multiple equalizers by a custom averaging function. + /// + /// Transformation when a value is added to the average calculation + /// Transformation when the average from the accumulator is taken + /// Curves to get the average of + /// All must have an equal number of bands at the same frequencies. + static Equalizer Average(Func addition, Func division, params Equalizer[] sources) { + double[] bands = new double[sources[0].Bands.Count]; + IReadOnlyList source = sources[0].Bands; + for (int i = 0; i < bands.Length; i++) { + bands[i] = addition(source[i].Gain); + } + for (int i = 1; i < sources.Length; i++) { + source = sources[i].Bands; + for (int j = 0; j < bands.Length; j++) { + bands[j] += addition(source[j].Gain); + } + } + + List result = new List(bands.Length); + for (int i = 0; i < bands.Length; i++) { + result.Add(new Band(source[i].Frequency, division(bands[i]))); + } + return new Equalizer(result, true); + } + + /// + /// Helper function for , adds a decibel value to the accumulator as a squared voltage. + /// + static double RMSAddition(double x) { + x = QMath.DbToGain(x); + return x * x; + } + } +} \ No newline at end of file diff --git a/Cavern.QuickEQ/Equalization/EQGenerator.cs b/Cavern.QuickEQ/Equalization/EQGenerator.cs index 8b885256..1abbbfce 100644 --- a/Cavern.QuickEQ/Equalization/EQGenerator.cs +++ b/Cavern.QuickEQ/Equalization/EQGenerator.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Linq; using Cavern.Filters; using Cavern.Filters.Utilities; @@ -14,7 +13,7 @@ namespace Cavern.QuickEQ.Equalization { /// /// Equalizer generation functions. /// - public static class EQGenerator { + public static partial class EQGenerator { /// /// Generate an equalizer setting to flatten the processed response of /// . The maximum gain on any band @@ -127,53 +126,6 @@ public static Equalizer AutoCorrectGraph(float[] graph, double startFreq, double return new Equalizer(bands, true); } - /// - /// Get the average gains of multiple equalizers. The averaging happens in linear space. - /// - /// All must have an equal number of bands at the same frequencies. - public static Equalizer Average(params Equalizer[] sources) { - double[] bands = new double[sources[0].Bands.Count]; - IReadOnlyList source = sources[0].Bands; - for (int i = 0; i < bands.Length; i++) { - bands[i] = Math.Pow(10, source[i].Gain * .05f); - } - for (int i = 1; i < sources.Length; i++) { - source = sources[i].Bands; - for (int j = 0; j < bands.Length; j++) { - bands[j] += Math.Pow(10, source[j].Gain * .05f); - } - } - - double div = 1.0 / sources.Length; - List result = new List(bands.Length); - for (int i = 0; i < bands.Length; i++) { - result.Add(new Band(source[i].Frequency, 20 * Math.Log10(bands[i] * div))); - } - return new Equalizer(result, true); - } - - /// - /// Get the average gains of multiple equalizers, regardless of how many bands they have. The averaging happens in linear space. - /// - public static Equalizer AverageSafe(params Equalizer[] sources) { - List bands = new List(); - double div = 1.0 / sources.Length; - for (int i = 0; i < sources.Length; i++) { - IReadOnlyList source = sources[i].Bands; - for (int j = 0, c = source.Count; j < c; j++) { - double freq = source[j].Frequency, - gain = 0; - for (int other = 0; other < sources.Length; other++) { - gain += Math.Pow(10, sources[other][freq] * .05f); - } - bands.Add(new Band(freq, 20 * Math.Log10(gain * div))); - } - } - - bands.Sort(); - return new Equalizer(bands.Distinct().ToList(), true); - } - /// /// Create an EQ that completely linearizes the . /// diff --git a/Cavern/Utilities/QMath.cs b/Cavern/Utilities/QMath.cs index 78d4c181..944b9aea 100644 --- a/Cavern/Utilities/QMath.cs +++ b/Cavern/Utilities/QMath.cs @@ -140,12 +140,24 @@ public static float Clamp01(float x) { return x; } + /// + /// Convert decibels to voltage gain. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double DbToGain(double gain) => Math.Pow(10, gain * .05); + /// /// Convert decibels to voltage gain. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float DbToGain(float gain) => MathF.Pow(10, gain * .05f); + /// + /// Convert voltage gain to decibels. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double GainToDb(double gain) => 20 * Math.Log10(gain); + /// /// Convert voltage gain to decibels. /// diff --git a/Tests/Test.Cavern.QuickEQ/Equalization/EQGenerator.Averaging_Tests.cs b/Tests/Test.Cavern.QuickEQ/Equalization/EQGenerator.Averaging_Tests.cs new file mode 100644 index 00000000..ee8a6420 --- /dev/null +++ b/Tests/Test.Cavern.QuickEQ/Equalization/EQGenerator.Averaging_Tests.cs @@ -0,0 +1,20 @@ +using Cavern.QuickEQ.Equalization; + +namespace Test.Cavern.QuickEQ.Equalization { + /// + /// Tests the class's averaging functions. + /// + [TestClass] + public class EQGeneratorAveraging_Tests { + /// + /// Tests if works as intended. + /// + [TestMethod, Timeout(1000)] + public void AverageRMS() { + Equalizer a = new Equalizer([new Band(20, 1)], true), + b = new Equalizer([new Band(20, 10)], true), + avg = EQGenerator.AverageRMS(a, b); + Assert.AreEqual(4.494369506972679, avg.Bands[0].Gain); + } + } +} \ No newline at end of file