Skip to content

Commit

Permalink
Parametric room curves and treble suppressing for everything except flat
Browse files Browse the repository at this point in the history
  • Loading branch information
VoidXH committed Dec 6, 2023
1 parent 2a69ba7 commit e84b417
Show file tree
Hide file tree
Showing 10 changed files with 268 additions and 199 deletions.
File renamed without changes.
90 changes: 90 additions & 0 deletions Cavern.QuickEQ/EQCurves/BaseClasses/RoomCurveLikeCurve.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System;

namespace Cavern.QuickEQ.EQCurves {
/// <summary>
/// A curve with a linear bass rise and linear treble suppression.
/// </summary>
public class RoomCurveLikeCurve : TrebleSuppressingCurve {
/// <summary>
/// The frequency from where the bass rise starts.
/// </summary>
readonly double kneeFrequency;

/// <summary>
/// Cached low knee position on logarithmic scale.
/// </summary>
readonly double kneePosition;

/// <summary>
/// Bass rise at 20 Hz in decibels.
/// </summary>
readonly float rise;

/// <summary>
/// The multiplier for getting the gain for a given frequency in the index operator.
/// </summary>
readonly double singleFreqHelper;

/// <summary>
/// A curve with a linear bass rise and linear treble suppression.
/// </summary>
/// <param name="kneeFrequency">The frequency from where the bass rise starts</param>
/// <param name="rise">Bass rise at 20 Hz in decibels</param>
public RoomCurveLikeCurve(double kneeFrequency, float rise) {
this.kneeFrequency = kneeFrequency;
kneePosition = Math.Log10(kneeFrequency);
this.rise = rise;
singleFreqHelper = rise / (Math.Log10(kneeFrequency) - log10_20);
}

/// <inheritdoc/>
public sealed override double this[double frequency] {
get {
if (frequency < kneeFrequency) {
return singleFreqHelper * (kneePosition - Math.Log10(frequency));
}
return base[frequency];
}
}

/// <inheritdoc/>
public sealed override float[] GenerateLinearCurve(int sampleRate, int length, float gain) {
float[] curve = base.GenerateLinearCurve(sampleRate, length, gain);
int knee = (int)(2 * kneeFrequency * curve.Length / sampleRate + .5f);
if (knee > curve.Length) {
knee = curve.Length;
}

float positioner = sampleRate * .5f / curve.Length,
kneeFreq = (float)kneeFrequency,
scale = -rise / (kneeFreq - 20);
for (int i = 0; i < knee; i++) {
curve[i] = scale * (i * positioner - kneeFreq) + gain;
}
return curve;
}

/// <inheritdoc/>
public sealed override float[] GenerateLogCurve(double startFreq, double endFreq, int length, float gain) {
float[] curve = base.GenerateLogCurve(startFreq, endFreq, length, gain);
double powerMin = Math.Log10(startFreq), powerRange = curve.Length / (Math.Log10(endFreq) - powerMin);
int knee = (int)((Math.Log10(kneeFrequency) - powerMin) * powerRange);
if (knee < 0) {
return curve;
} else if (knee > curve.Length) {
knee = curve.Length;
}

float positioner = (float)(1.0 / (knee - (log10_20 - powerMin) * powerRange));
for (int i = 0; i < knee; i++) {
curve[i] = rise * (knee - i) * positioner + gain;
}
return curve;
}

/// <summary>
/// Hardcoded log10(20) (low extension position on log scale).
/// </summary>
const double log10_20 = 1.30102999566f;
}
}
77 changes: 77 additions & 0 deletions Cavern.QuickEQ/EQCurves/BaseClasses/TrebleSuppressingCurve.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System;

namespace Cavern.QuickEQ.EQCurves {
/// <summary>
/// Applies the top part of the <see cref="RoomCurve"/>, to be reused in other curves not to let them be flat on the top end.
/// The exact curve is linearly decreasing from 1 kHz to 20 kHz by a set value.
/// </summary>
public class TrebleSuppressingCurve : EQCurve {
/// <summary>
/// Linear treble decrease from 1 kHz to 20 kHz in decibels.
/// </summary>
public float trebleSuppression = 3;

/// <inheritdoc/>
public override double this[double frequency] {
get {
if (frequency < 1000) {
return 0;
}
return -3 * (Math.Log10(frequency) - log10_1000) * highMul;
}
}

/// <inheritdoc/>
public override float[] GenerateLinearCurve(int sampleRate, int length, float gain) {
float[] curve = new float[length];
if (trebleSuppression == 0 || sampleRate < 2000) { // Knee would start from 1 kHz
return curve;
}
int knee = 2 * 1000 * length / sampleRate;
Array.Fill(curve, gain, 0, knee);

float positioner = sampleRate * .5f / length,
scale = -trebleSuppression / 19000;
for (int i = knee; i < length; i++) {
curve[i] = scale * (i * positioner - 1000) + gain;
}
return curve;
}

/// <inheritdoc/>
public override float[] GenerateLogCurve(double startFreq, double endFreq, int length, float gain) {
float[] curve = new float[length];
if (trebleSuppression == 0) {
return curve;
}
double powerMin = Math.Log10(startFreq), powerRange = length / (Math.Log10(endFreq) - powerMin);
int knee = (int)((log10_1000 - powerMin) * powerRange);
if (knee > length) {
knee = length;
}
Array.Fill(curve, gain, 0, knee);

float positioner = (float)(1.0 / ((log10_20000 - powerMin) * powerRange - knee)),
scale = -trebleSuppression;
for (int pos = knee; pos < length; pos++) {
curve[pos] = scale * (pos - knee) * positioner + gain;
}
return curve;
}

/// <summary>
/// Hardcoded log10(1000) (high knee position on log scale).
/// </summary>
const double log10_1000 = 3;

/// <summary>
/// Hardcoded log10(20000) (high extension position on log scale).
/// </summary>
const double log10_20000 = 4.30102999566f;

/// <summary>
/// Hardcoded 1 / (log10(20000) - log10(1000)) for high slope division.
/// </summary>
const double highMul = 0.76862178684;
}
}
69 changes: 7 additions & 62 deletions Cavern.QuickEQ/EQCurves/Depth.cs
Original file line number Diff line number Diff line change
@@ -1,68 +1,13 @@
using System;

namespace Cavern.QuickEQ.EQCurves {
namespace Cavern.QuickEQ.EQCurves {
/// <summary>
/// EQ curve with a sub-bass slope for depth emphasis.
/// EQ curve with a sub-bass slope for depth emphasis. Linearly rises by 12 dB down from 60 Hz to 20 Hz,
/// and optionally linearly decreases from 1 kHz to 20 kHz by 3 dB.
/// </summary>
public class Depth : EQCurve {
public class Depth : RoomCurveLikeCurve {
/// <summary>
/// Hardcoded log10(60), as C# compilers don't optimize this.
/// EQ curve with a sub-bass slope for depth emphasis. Linearly rises by 12 dB down from 60 Hz to 20 Hz,
/// and optionally linearly decreases from 1 kHz to 20 kHz by 3 dB.
/// </summary>
const float log10_60 = 1.77815125038f;

/// <summary>
/// Get the curve's gain in decibels at a given frequency.
/// </summary>
public override double this[double frequency] {
get {
if (frequency > 60) {
return 0;
}
return 12.0 / 60 * (60 - frequency);
}
}

/// <summary>
/// Generate a linear curve for correction generators.
/// </summary>
/// <param name="length">Curve length</param>
/// <param name="sampleRate">Sample rate of the measurement that the generated curve will be used for</param>
/// <param name="gain">Curve reference level</param>
public override float[] GenerateLinearCurve(int sampleRate, int length, float gain) {
float[] curve = new float[length];
float positioner = 12f / 60 * sampleRate * .5f / length;
int at60 = (int)(length * 120f / sampleRate);
if (at60 > length) {
at60 = length;
}
gain += 12;
for (int pos = 0; pos < at60; pos++) {
curve[pos] = gain - pos * positioner;
}
Array.Fill(curve, gain, at60, length - at60);
return curve;
}

/// <summary>
/// Generate a logarithmic curve for correction generators.
/// </summary>
/// <param name="length">Curve length</param>
/// <param name="startFreq">Frequency at the beginning of the curve</param>
/// <param name="endFreq">Frequency at the end of the curve</param>
/// <param name="gain">Curve reference level</param>
public override float[] GenerateLogCurve(double startFreq, double endFreq, int length, float gain) {
float[] curve = new float[length];
double powerMin = Math.Log10(startFreq), powerRange = (Math.Log10(endFreq) - powerMin) / length;
int at60 = (int)((log10_60 - powerMin) / powerRange);
if (at60 > length) {
at60 = length;
}
double startGain = this[startFreq], positioner = startGain / at60;
for (int pos = 0; pos < at60; pos++) {
curve[pos] = (float)(startGain - pos * positioner + gain);
}
Array.Fill(curve, gain, at60, length - at60);
return curve;
}
public Depth() : base(60, 12) { }
}
}
37 changes: 9 additions & 28 deletions Cavern.QuickEQ/EQCurves/Punch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,11 @@ namespace Cavern.QuickEQ.EQCurves {
/// <summary>
/// EQ curve with a bass bump for punch emphasis.
/// </summary>
public class Punch : EQCurve {
/// <summary>
/// Hardcoded log10(120), as C# compilers don't optimize this.
/// </summary>
const float log10_120 = 2.07918124605f;

public class Punch : TrebleSuppressingCurve {
/// <summary>
/// Filter gain in decibels.
/// </summary>
public double Gain { get; private set; }
public double Gain { get; private set; } = 3;

/// <summary>
/// EQ curve with a 6 dB bass bump for punch emphasis.
Expand All @@ -25,26 +20,19 @@ public class Punch : EQCurve {
/// </summary>
public Punch(double gain) => Gain = gain / 2;

/// <summary>
/// Get the curve's gain in decibels at a given frequency.
/// </summary>
/// <inheritdoc/>
public override double this[double frequency] {
get {
if (frequency > 120) {
return 0;
return base[frequency];
}
return (1 - Math.Cos(2 * Math.PI / 120 * frequency)) * Gain;
}
}

/// <summary>
/// Generate a linear curve for correction generators.
/// </summary>
/// <param name="length">Curve length</param>
/// <param name="sampleRate">Sample rate of the measurement that the generated curve will be used for</param>
/// <param name="gain">Curve reference level</param>
/// <inheritdoc/>
public override float[] GenerateLinearCurve(int sampleRate, int length, float gain) {
float[] curve = new float[length];
float[] curve = base.GenerateLinearCurve(sampleRate, length, gain);
float positioner = sampleRate * .5f / length;
int at120 = (int)(length * 240f / sampleRate);
if (at120 > length) {
Expand All @@ -53,21 +41,15 @@ public override float[] GenerateLinearCurve(int sampleRate, int length, float ga
for (int pos = 0; pos < at120; pos++) {
curve[pos] = gain + (float)((1 - Math.Cos(2 * Math.PI / 120 * pos * positioner)) * Gain);
}
Array.Fill(curve, gain, at120, length - at120);
return curve;
}

/// <summary>
/// Generate a logarithmic curve for correction generators.
/// </summary>
/// <param name="length">Curve length</param>
/// <param name="startFreq">Frequency at the beginning of the curve</param>
/// <param name="endFreq">Frequency at the end of the curve</param>
/// <param name="gain">Curve reference level</param>
/// <inheritdoc/>
public override float[] GenerateLogCurve(double startFreq, double endFreq, int length, float gain) {
float[] curve = new float[length];
float[] curve = base.GenerateLogCurve(startFreq, endFreq, length, gain);
float freqHere = (float)startFreq, multiplier = (float)Math.Pow(endFreq / startFreq, 1f / length),
powerMin = (float)Math.Log10(startFreq);
const float log10_120 = 2.07918124605f;
int at120 = (int)((log10_120 - powerMin) / (Math.Log10(endFreq) - powerMin) * length);
if (at120 > length) {
at120 = length;
Expand All @@ -76,7 +58,6 @@ public override float[] GenerateLogCurve(double startFreq, double endFreq, int l
curve[pos] = (float)((1 - Math.Cos(2 * Math.PI / 120 * freqHere)) * Gain) + gain;
freqHere *= multiplier;
}
Array.Fill(curve, gain, at120, length - at120);
return curve;
}
}
Expand Down
Loading

0 comments on commit e84b417

Please sign in to comment.