Skip to content

Commit

Permalink
RMS averaging for EQ curves
Browse files Browse the repository at this point in the history
  • Loading branch information
VoidXH committed Dec 26, 2023
1 parent b05157a commit f508237
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 49 deletions.
84 changes: 84 additions & 0 deletions Cavern.QuickEQ/Equalization/EQGenerator.Averaging.cs
Original file line number Diff line number Diff line change
@@ -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 {
/// <summary>
/// Get the average gains of multiple equalizers. The averaging happens in linear space.
/// </summary>
/// <remarks>All <paramref name="sources"/> must have an equal number of bands at the same frequencies.</remarks>
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);
}

/// <summary>
/// Get the average gains of multiple equalizers. The averaging happens in linear space and an RMS value is taken.
/// </summary>
/// <remarks>All <paramref name="sources"/> must have an equal number of bands at the same frequencies.</remarks>
public static Equalizer AverageRMS(params Equalizer[] sources) {
double mul = 1.0 / sources.Length;
return Average(RMSAddition, x => QMath.GainToDb(Math.Sqrt(x) * mul), sources);
}

/// <summary>
/// Get the average gains of multiple equalizers, regardless of how many bands they have. The averaging happens in linear space.
/// </summary>
public static Equalizer AverageSafe(params Equalizer[] sources) {
List<Band> bands = new List<Band>();
double div = 1.0 / sources.Length;
for (int i = 0; i < sources.Length; i++) {
IReadOnlyList<Band> 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);
}

/// <summary>
/// Get the average gains of multiple equalizers by a custom averaging function.
/// </summary>
/// <param name="addition">Transformation when a value is added to the average calculation</param>
/// <param name="division">Transformation when the average from the accumulator is taken</param>
/// <param name="sources">Curves to get the average of</param>
/// <remarks>All <paramref name="sources"/> must have an equal number of bands at the same frequencies.</remarks>
static Equalizer Average(Func<double, double> addition, Func<double, double> division, params Equalizer[] sources) {
double[] bands = new double[sources[0].Bands.Count];
IReadOnlyList<Band> 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<Band> result = new List<Band>(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);
}

/// <summary>
/// Helper function for <see cref="AverageRMS(Equalizer[])"/>, adds a decibel value to the accumulator as a squared voltage.
/// </summary>
static double RMSAddition(double x) {
x = QMath.DbToGain(x);
return x * x;
}
}
}
50 changes: 1 addition & 49 deletions Cavern.QuickEQ/Equalization/EQGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;

using Cavern.Filters;
using Cavern.Filters.Utilities;
Expand All @@ -14,7 +13,7 @@ namespace Cavern.QuickEQ.Equalization {
/// <summary>
/// Equalizer generation functions.
/// </summary>
public static class EQGenerator {
public static partial class EQGenerator {
/// <summary>
/// Generate an equalizer setting to flatten the processed response of
/// <see cref="GraphUtils.SmoothGraph(float[], float, float, float)"/>. The maximum gain on any band
Expand Down Expand Up @@ -127,53 +126,6 @@ public static Equalizer AutoCorrectGraph(float[] graph, double startFreq, double
return new Equalizer(bands, true);
}

/// <summary>
/// Get the average gains of multiple equalizers. The averaging happens in linear space.
/// </summary>
/// <remarks>All <paramref name="sources"/> must have an equal number of bands at the same frequencies.</remarks>
public static Equalizer Average(params Equalizer[] sources) {
double[] bands = new double[sources[0].Bands.Count];
IReadOnlyList<Band> 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<Band> result = new List<Band>(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);
}

/// <summary>
/// Get the average gains of multiple equalizers, regardless of how many bands they have. The averaging happens in linear space.
/// </summary>
public static Equalizer AverageSafe(params Equalizer[] sources) {
List<Band> bands = new List<Band>();
double div = 1.0 / sources.Length;
for (int i = 0; i < sources.Length; i++) {
IReadOnlyList<Band> 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);
}

/// <summary>
/// Create an EQ that completely linearizes the <paramref name="spectrum"/>.
/// </summary>
Expand Down
12 changes: 12 additions & 0 deletions Cavern/Utilities/QMath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,24 @@ public static float Clamp01(float x) {
return x;
}

/// <summary>
/// Convert decibels to voltage gain.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double DbToGain(double gain) => Math.Pow(10, gain * .05);

/// <summary>
/// Convert decibels to voltage gain.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DbToGain(float gain) => MathF.Pow(10, gain * .05f);

/// <summary>
/// Convert voltage gain to decibels.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double GainToDb(double gain) => 20 * Math.Log10(gain);

/// <summary>
/// Convert voltage gain to decibels.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Cavern.QuickEQ.Equalization;

namespace Test.Cavern.QuickEQ.Equalization {
/// <summary>
/// Tests the <see cref="EQGenerator"/> class's averaging functions.
/// </summary>
[TestClass]
public class EQGeneratorAveraging_Tests {
/// <summary>
/// Tests if <see cref="EQGenerator.AverageRMS(Equalizer[])"/> works as intended.
/// </summary>
[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);
}
}
}

0 comments on commit f508237

Please sign in to comment.