Skip to content

Commit

Permalink
Full parsing of Equalizer APO files
Browse files Browse the repository at this point in the history
  • Loading branch information
VoidXH committed Jun 17, 2024
1 parent 4e43a5b commit 109c9a7
Show file tree
Hide file tree
Showing 23 changed files with 212 additions and 172 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ void AddConfigFile(string path, Dictionary<string, FilterGraphNode> lastNodes, L
}

switch (split[0].ToLower(CultureInfo.InvariantCulture)) {
// Control
case "include":
string included = Path.Combine(Path.GetDirectoryName(path), string.Join(' ', split, 1, split.Length - 1));
CreateSplit(Path.GetFileNameWithoutExtension(included), lastNodes);
Expand All @@ -62,6 +63,7 @@ void AddConfigFile(string path, Dictionary<string, FilterGraphNode> lastNodes, L
}
}
break;
// Basic filters
case "preamp":
double gain = double.Parse(split[1].Replace(',', '.'), CultureInfo.InvariantCulture);
AddFilter(lastNodes, activeChannels, new Gain(gain));
Expand Down Expand Up @@ -94,9 +96,18 @@ void AddConfigFile(string path, Dictionary<string, FilterGraphNode> lastNodes, L
lastNodes[copy[0]] = target;
}
break;
// Parametric filters
case "filter":
AddFilter(lastNodes, activeChannels, BiquadFilter.FromEqualizerAPO(split, sampleRate));
break;
// Graphic equalizers
case "graphiceq":
AddFilter(lastNodes, activeChannels, GraphicEQ.FromEqualizerAPO(split, sampleRate));
break;
case "convolution":
string convolution = Path.Combine(Path.GetDirectoryName(path), line[(line.IndexOf(' ') + 1)..]);
AddFilter(lastNodes, activeChannels, new FastConvolver(AudioReader.Open(convolution).Read()));
break;
}
}
}
Expand Down
8 changes: 3 additions & 5 deletions Cavern.QuickEQ/Equalization/EQGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,7 @@ public static Equalizer FromCalibration(string[] lines) {
List<Band> bands = new List<Band>();
for (int line = 0; line < lines.Length; ++line) {
string[] nums = lines[line].Trim().Split(new[] { ' ', '\t' });
if (nums.Length > 1 && double.TryParse(nums[0].Replace(',', '.'), NumberStyles.Any,
CultureInfo.InvariantCulture, out double freq) && double.TryParse(nums[1].Replace(',', '.'), NumberStyles.Any,
CultureInfo.InvariantCulture, out double gain)) {
if (nums.Length > 1 && QMath.TryParseDouble(nums[0], out double freq) && QMath.TryParseDouble(nums[1], out double gain)) {
bands.Add(new Band(freq, gain));
}
}
Expand Down Expand Up @@ -229,9 +227,9 @@ public static Equalizer FromEqualizerAPO(string[] splitLine) {
float[] toParse = new float[splitLine.Length - 1];
for (int i = 1; i < splitLine.Length; i++) {
if (splitLine[i][^1] == ';') {
toParse[i - 1] = float.Parse(splitLine[i][..^1].Replace(',', '.'), CultureInfo.InvariantCulture);
toParse[i - 1] = QMath.ParseFloat(splitLine[i][..^1]);
} else {
toParse[i - 1] = float.Parse(splitLine[i].Replace(',', '.'), CultureInfo.InvariantCulture);
toParse[i - 1] = QMath.ParseFloat(splitLine[i]);
}
}
return FromCalibration(toParse);
Expand Down
8 changes: 4 additions & 4 deletions Cavern.QuickEQ/Equalization/PeakingEqualizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,10 @@ public static PeakingEQ[] ParseEQFile(IEnumerable<string> lines) {
List<PeakingEQ> result = new List<PeakingEQ>();
foreach (string line in lines) {
string[] parts = line.Split(new[] { ':', ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length > 11 && parts[0] == "Filter" && parts[2] == "ON" && parts[3] == "PK" &&
double.TryParse(parts[5], NumberStyles.Any, CultureInfo.InvariantCulture, out double freq) &&
double.TryParse(parts[8], NumberStyles.Any, CultureInfo.InvariantCulture, out double gain) &&
double.TryParse(parts[11], NumberStyles.Any, CultureInfo.InvariantCulture, out double q)) {
if (parts.Length > 11 && parts[0].ToLower() == "filter" && parts[2].ToLower() == "on" && parts[3].ToLower() == "pk" &&
QMath.TryParseDouble(parts[5], out double freq) &&
QMath.TryParseDouble(parts[8], out double gain) &&
QMath.TryParseDouble(parts[11], out double q)) {
result.Add(new PeakingEQ(Listener.DefaultSampleRate, freq, q, gain));
}
}
Expand Down
13 changes: 13 additions & 0 deletions Cavern/Filters/Allpass.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;

using Cavern.Utilities;

namespace Cavern.Filters {
/// <summary>
/// Simple first-order allpass filter.
Expand Down Expand Up @@ -33,6 +35,17 @@ public Allpass(int sampleRate, double centerFreq, double q) : base(sampleRate, c
/// <param name="gain">Gain of the filter in decibels</param>
public Allpass(int sampleRate, double centerFreq, double q, double gain) : base(sampleRate, centerFreq, q, gain) { }

/// <summary>
/// Parse a Filter line of Equalizer APO which was split at spaces to a Cavern <see cref="Allpass"/> filter.<br />
/// Sample: ON AP Fc 100 Hz Q 10
/// </summary>
public static Allpass FromEqualizerAPO(string[] splitLine, int sampleRate) {
if (QMath.TryParseDouble(splitLine[4], out double freq) && QMath.TryParseDouble(splitLine[7], out double q)) {
return new Allpass(sampleRate, freq, q);
}
throw new FormatException(nameof(splitLine));
}

/// <inheritdoc/>
public override object Clone() => new Allpass(SampleRate, centerFreq, q, gain);

Expand Down
17 changes: 17 additions & 0 deletions Cavern/Filters/Bandpass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;

using Cavern.Filters.Utilities;
using Cavern.Utilities;

namespace Cavern.Filters {
/// <summary>
Expand Down Expand Up @@ -35,6 +36,22 @@ public Bandpass(int sampleRate, double centerFreq, double q) : base(sampleRate,
/// <param name="gain">Gain of the filter in decibels</param>
public Bandpass(int sampleRate, double centerFreq, double q, double gain) : base(sampleRate, centerFreq, q, gain) { }

/// <summary>
/// Parse a Filter line of Equalizer APO which was split at spaces to a Cavern <see cref="Bandpass"/> filter.<br />
/// Sample: Filter: ON BP Fc 100 Hz
/// Sample with Q-factor: Filter: ON BP Fc 100 Hz Q 10
/// </summary>
public static Bandpass FromEqualizerAPO(string[] splitLine, int sampleRate) {
if (QMath.TryParseDouble(splitLine[4], out double freq)) {
if (splitLine.Length < 7) {
return new Bandpass(sampleRate, freq);
} else if (QMath.TryParseDouble(splitLine[7], out double q)) {
return new Bandpass(sampleRate, freq, q);
}
}
throw new FormatException(nameof(splitLine));
}

/// <inheritdoc/>
public override object Clone() => new Bandpass(SampleRate, centerFreq, q, gain);

Expand Down
20 changes: 20 additions & 0 deletions Cavern/Filters/BiquadFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,26 @@ protected BiquadFilter(int sampleRate, double centerFreq, double q, double gain)
_ => throw new ArgumentOutOfRangeException(nameof(type))
};

/// <summary>
/// Parse a Filter line of Equalizer APO which was split at spaces to a Cavern <see cref="BiquadFilter"/> filter,
/// detecting its type.<br />
/// </summary>
public static BiquadFilter FromEqualizerAPO(string[] splitLine, int sampleRate) => splitLine[2].ToUpper() switch {
"PK" => PeakingEQ.FromEqualizerAPO(splitLine, sampleRate),
"LP" => Lowpass.FromEqualizerAPO(splitLine, sampleRate),
"LPQ" => Lowpass.FromEqualizerAPO(splitLine, sampleRate),
"HP" => Highpass.FromEqualizerAPO(splitLine, sampleRate),
"HPQ" => Highpass.FromEqualizerAPO(splitLine, sampleRate),
"BP" => Bandpass.FromEqualizerAPO(splitLine, sampleRate),
"LS" => LowShelf.FromEqualizerAPO(splitLine, sampleRate),
"LSC" => LowShelf.FromEqualizerAPO(splitLine, sampleRate),
"HS" => HighShelf.FromEqualizerAPO(splitLine, sampleRate),
"HSC" => HighShelf.FromEqualizerAPO(splitLine, sampleRate),
"NO" => Notch.FromEqualizerAPO(splitLine, sampleRate),
"AP" => Allpass.FromEqualizerAPO(splitLine, sampleRate),
_ => throw new ArgumentOutOfRangeException(splitLine[2])
};

/// <summary>
/// Wipe the history to prevent clipping when applying the same filter for a new signal.
/// </summary>
Expand Down
4 changes: 3 additions & 1 deletion Cavern/Filters/Delay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,9 @@ public static Delay FromEqualizerAPO(string line, int sampleRate) =>
/// Parse a Delay line of Equalizer APO which was split at spaces to a Cavern <see cref="Delay"/> filter.
/// </summary>
public static Delay FromEqualizerAPO(string[] splitLine, int sampleRate) {
double delay = double.Parse(splitLine[1].Replace(',', '.'), CultureInfo.InvariantCulture);
if (splitLine.Length < 3 || !QMath.TryParseDouble(splitLine[1], out double delay)) {
throw new FormatException(nameof(splitLine));
}
return splitLine[2].ToLower(CultureInfo.InvariantCulture) switch {
"ms" => new Delay(delay, sampleRate),
"samples" => new Delay((int)delay),
Expand Down
17 changes: 17 additions & 0 deletions Cavern/Filters/HighShelf.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;

using Cavern.Filters.Utilities;
using Cavern.Utilities;

namespace Cavern.Filters {
/// <summary>
Expand Down Expand Up @@ -35,6 +36,22 @@ public HighShelf(int sampleRate, double centerFreq, double q) : base(sampleRate,
/// <param name="gain">Gain of the filter in decibels</param>
public HighShelf(int sampleRate, double centerFreq, double q, double gain) : base(sampleRate, centerFreq, q, gain) { }

/// <summary>
/// Parse a Filter line of Equalizer APO which was split at spaces to a Cavern <see cref="HighShelf"/> filter.<br />
/// Sample with fixed shelf: Filter: ON HS Fc 100 Hz Gain 0 dB
/// Sample with custom shelf: Filter: ON HSC 12 dB Fc 100 Hz Gain 0 dB
/// </summary>
public static HighShelf FromEqualizerAPO(string[] splitLine, int sampleRate) {
string type = splitLine[2].ToLower();
if (type == "hs" && QMath.TryParseDouble(splitLine[4], out double freq) && QMath.TryParseDouble(splitLine[5], out double gain)) {
return new HighShelf(sampleRate, freq, QFactor.FromSlope(0.9, gain));
} else if (type == "hsc" && QMath.TryParseDouble(splitLine[3], out double slope) &&
QMath.TryParseDouble(splitLine[6], out freq) && QMath.TryParseDouble(splitLine[9], out gain)) {
return new HighShelf(sampleRate, freq, QFactor.FromSlopeDecibels(slope, gain), gain);
}
throw new FormatException(nameof(splitLine));
}

/// <inheritdoc/>
public override object Clone() => new HighShelf(SampleRate, centerFreq, q, gain);

Expand Down
19 changes: 18 additions & 1 deletion Cavern/Filters/Highpass.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;

using Cavern.Filters.Utilities;
using Cavern.Utilities;

namespace Cavern.Filters {
/// <summary>
Expand Down Expand Up @@ -34,6 +36,21 @@ public Highpass(int sampleRate, double centerFreq, double q) : base(sampleRate,
/// <param name="gain">Gain of the filter in decibels</param>
public Highpass(int sampleRate, double centerFreq, double q, double gain) : base(sampleRate, centerFreq, q, gain) { }

/// <summary>
/// Parse a Filter line of Equalizer APO which was split at spaces to a Cavern <see cref="Highpass"/> filter.<br />
/// Sample with fixed Q factor: Filter: ON HP Fc 100 Hz
/// Sample with custom Q factor: Filter: ON HPQ Fc 100 Hz Q 0.7071
/// </summary>
public static Highpass FromEqualizerAPO(string[] splitLine, int sampleRate) {
string type = splitLine[2].ToLower();
if (type == "hp" && QMath.TryParseDouble(splitLine[4], out double freq)) {
return new Highpass(sampleRate, freq);
} else if (type == "hpq" && QMath.TryParseDouble(splitLine[4], out freq) && QMath.TryParseDouble(splitLine[7], out double q)) {
return new Highpass(sampleRate, freq, q);
}
throw new FormatException(nameof(splitLine));
}

/// <inheritdoc/>
public override object Clone() => new Highpass(SampleRate, centerFreq, q, gain);

Expand Down
17 changes: 17 additions & 0 deletions Cavern/Filters/LowShelf.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;

using Cavern.Filters.Utilities;
using Cavern.Utilities;

namespace Cavern.Filters {
/// <summary>
Expand Down Expand Up @@ -35,6 +36,22 @@ public LowShelf(int sampleRate, double centerFreq, double q) : base(sampleRate,
/// <param name="gain">Gain of the filter in decibels</param>
public LowShelf(int sampleRate, double centerFreq, double q, double gain) : base(sampleRate, centerFreq, q, gain) { }

/// <summary>
/// Parse a Filter line of Equalizer APO which was split at spaces to a Cavern <see cref="LowShelf"/> filter.<br />
/// Sample with fixed shelf: Filter: ON LS Fc 100 Hz Gain 0 dB
/// Sample with custom shelf: Filter: ON LSC 12 dB Fc 100 Hz Gain 0 dB
/// </summary>
public static LowShelf FromEqualizerAPO(string[] splitLine, int sampleRate) {
string type = splitLine[2].ToLower();
if (type == "ls" && QMath.TryParseDouble(splitLine[4], out double freq) && QMath.TryParseDouble(splitLine[5], out double gain)) {
return new LowShelf(sampleRate, freq, QFactor.FromSlope(0.9, gain));
} else if (type == "lsc" && QMath.TryParseDouble(splitLine[3], out double slope) &&
QMath.TryParseDouble(splitLine[6], out freq) && QMath.TryParseDouble(splitLine[9], out gain)) {
return new LowShelf(sampleRate, freq, QFactor.FromSlopeDecibels(slope, gain), gain);
}
throw new FormatException(nameof(splitLine));
}

/// <inheritdoc/>
public override object Clone() => new LowShelf(SampleRate, centerFreq, q, gain);

Expand Down
19 changes: 18 additions & 1 deletion Cavern/Filters/Lowpass.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;

using Cavern.Filters.Utilities;
using Cavern.Utilities;

namespace Cavern.Filters {
/// <summary>
Expand Down Expand Up @@ -34,6 +36,21 @@ public Lowpass(int sampleRate, double centerFreq, double q) : base(sampleRate, c
/// <param name="gain">Gain of the filter in decibels</param>
public Lowpass(int sampleRate, double centerFreq, double q, double gain) : base(sampleRate, centerFreq, q, gain) { }

/// <summary>
/// Parse a Filter line of Equalizer APO which was split at spaces to a Cavern <see cref="Lowpass"/> filter.<br />
/// Sample with fixed Q factor: Filter: ON LP Fc 100 Hz
/// Sample with custom Q factor: Filter: ON LPQ Fc 100 Hz Q 0.7071
/// </summary>
public static Lowpass FromEqualizerAPO(string[] splitLine, int sampleRate) {
string type = splitLine[2].ToLower();
if (type == "lp" && QMath.TryParseDouble(splitLine[4], out double freq)) {
return new Lowpass(sampleRate, freq);
} else if (type == "lpq" && QMath.TryParseDouble(splitLine[4], out freq) && QMath.TryParseDouble(splitLine[7], out double q)) {
return new Lowpass(sampleRate, freq, q);
}
throw new FormatException(nameof(splitLine));
}

/// <inheritdoc/>
public override object Clone() => new Lowpass(SampleRate, centerFreq, q, gain);

Expand Down
19 changes: 19 additions & 0 deletions Cavern/Filters/Notch.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using System;
using System.Collections.Generic;

using Cavern.Filters.Utilities;
using Cavern.Utilities;

namespace Cavern.Filters {
/// <summary>
/// Simple first-order notch filter.
Expand Down Expand Up @@ -33,6 +36,22 @@ public Notch(int sampleRate, double centerFreq, double q) : base(sampleRate, cen
/// <param name="gain">Gain of the filter in decibels</param>
public Notch(int sampleRate, double centerFreq, double q, double gain) : base(sampleRate, centerFreq, q, gain) { }

/// <summary>
/// Parse a Filter line of Equalizer APO which was split at spaces to a Cavern <see cref="Notch"/> filter.<br />
/// Sample with default Q-factor: Filter: ON NO Fc 100 Hz
/// Sample with custom Q-factor: Filter: ON NO Fc 100 Hz Q 30
/// </summary>
public static Notch FromEqualizerAPO(string[] splitLine, int sampleRate) {
if (QMath.TryParseDouble(splitLine[4], out double freq)) {
if (splitLine.Length < 7) {
return new Notch(sampleRate, freq, 30);
} else if (QMath.TryParseDouble(splitLine[7], out double q)) {
return new Notch(sampleRate, freq, q);
}
}
throw new FormatException(nameof(splitLine));
}

/// <inheritdoc/>
public override object Clone() => new Notch(SampleRate, centerFreq, q, gain);

Expand Down
19 changes: 19 additions & 0 deletions Cavern/Filters/PeakingEQ.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using System;
using System.Collections.Generic;

using Cavern.Filters.Utilities;
using Cavern.Utilities;

namespace Cavern.Filters {
/// <summary>
/// Simple first-order peaking filter.
Expand Down Expand Up @@ -33,6 +36,22 @@ public PeakingEQ(int sampleRate, double centerFreq, double q) : base(sampleRate,
/// <param name="gain">Gain of the filter in decibels</param>
public PeakingEQ(int sampleRate, double centerFreq, double q, double gain) : base(sampleRate, centerFreq, q, gain) { }

/// <summary>
/// Parse a Filter line of Equalizer APO which was split at spaces to a Cavern <see cref="PeakingEQ"/> filter.<br />
/// Sample with Q factor: Filter: ON PK Fc 100 Hz Gain 0 dB Q 10<br />
/// Sample with bandwidth: Filter: ON PK Fc 100 Hz Gain 0 dB BW Oct 0.1442
/// </summary>
public static PeakingEQ FromEqualizerAPO(string[] splitLine, int sampleRate) {
if (QMath.TryParseDouble(splitLine[4], out double freq) && QMath.TryParseDouble(splitLine[7], out double gain)) {
if (splitLine[9].Equals("Q") && QMath.TryParseDouble(splitLine[10], out double q)) {
return new PeakingEQ(sampleRate, freq, q, gain);
} else if (splitLine[9].Equals("BW") && QMath.TryParseDouble(splitLine[11], out double bw)) {
return new PeakingEQ(sampleRate, freq, QFactor.FromBandwidth(bw), gain);
}
}
throw new FormatException(nameof(splitLine));
}

/// <inheritdoc/>
public override object Clone() => new PeakingEQ(SampleRate, centerFreq, q, gain);

Expand Down
6 changes: 6 additions & 0 deletions Cavern/Utilities/QMath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,12 @@ public static int TrailingZeros(int x) {
return zeros;
}

/// <summary>
/// Try to parse a double value regardless of the system's culture.
/// </summary>
public static bool TryParseDouble(string from, out double num) =>
double.TryParse(from.Replace(',', '.'), NumberStyles.Any, CultureInfo.InvariantCulture, out num);

/// <summary>
/// Conversion array for <see cref="BitsAfterMSB(int)"/>.
/// </summary>
Expand Down
Loading

0 comments on commit 109c9a7

Please sign in to comment.