From 109c9a721090a84474a5645f5544dce05fd5b8a3 Mon Sep 17 00:00:00 2001 From: VoidX Date: Mon, 17 Jun 2024 23:30:05 +0200 Subject: [PATCH] Full parsing of Equalizer APO files --- .../EqualizerAPOConfigurationFile.cs | 11 ++ Cavern.QuickEQ/Equalization/EQGenerator.cs | 8 +- .../Equalization/PeakingEqualizer.cs | 8 +- Cavern/Filters/Allpass.cs | 13 ++ Cavern/Filters/Bandpass.cs | 17 ++ Cavern/Filters/BiquadFilter.cs | 20 +++ Cavern/Filters/Delay.cs | 4 +- Cavern/Filters/HighShelf.cs | 17 ++ Cavern/Filters/Highpass.cs | 19 ++- Cavern/Filters/LowShelf.cs | 17 ++ Cavern/Filters/Lowpass.cs | 19 ++- Cavern/Filters/Notch.cs | 19 +++ Cavern/Filters/PeakingEQ.cs | 19 +++ Cavern/Utilities/QMath.cs | 6 + CavernSamples/Cavern.WPF/BiquadEditor.xaml.cs | 5 +- CavernSamples/EQAPOtoFIR/LineParser.cs | 154 +----------------- .../FilterStudio/Graphs/ManipulatableGraph.cs | 11 +- .../FilterStudio/Graphs/PipelineEditor.cs | 3 + .../FilterStudio/MainWindow.Graph.cs | 2 +- CavernSamples/FilterStudio/MainWindow.xaml | 2 +- CavernSamples/FilterStudio/MainWindow.xaml.cs | 8 +- .../Resources/MainWindowStrings.hu-HU.xaml | 1 + .../Resources/MainWindowStrings.xaml | 1 + 23 files changed, 212 insertions(+), 172 deletions(-) diff --git a/Cavern.QuickEQ.Format/ConfigurationFile/EqualizerAPOConfigurationFile.cs b/Cavern.QuickEQ.Format/ConfigurationFile/EqualizerAPOConfigurationFile.cs index d1986be7..54ad203a 100644 --- a/Cavern.QuickEQ.Format/ConfigurationFile/EqualizerAPOConfigurationFile.cs +++ b/Cavern.QuickEQ.Format/ConfigurationFile/EqualizerAPOConfigurationFile.cs @@ -42,6 +42,7 @@ void AddConfigFile(string path, Dictionary 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); @@ -62,6 +63,7 @@ void AddConfigFile(string path, Dictionary lastNodes, L } } break; + // Basic filters case "preamp": double gain = double.Parse(split[1].Replace(',', '.'), CultureInfo.InvariantCulture); AddFilter(lastNodes, activeChannels, new Gain(gain)); @@ -94,9 +96,18 @@ void AddConfigFile(string path, Dictionary 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; } } } diff --git a/Cavern.QuickEQ/Equalization/EQGenerator.cs b/Cavern.QuickEQ/Equalization/EQGenerator.cs index b6c2386c..27ddec2a 100644 --- a/Cavern.QuickEQ/Equalization/EQGenerator.cs +++ b/Cavern.QuickEQ/Equalization/EQGenerator.cs @@ -199,9 +199,7 @@ public static Equalizer FromCalibration(string[] lines) { List bands = new List(); 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)); } } @@ -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); diff --git a/Cavern.QuickEQ/Equalization/PeakingEqualizer.cs b/Cavern.QuickEQ/Equalization/PeakingEqualizer.cs index e4cc8442..7b3ebc18 100644 --- a/Cavern.QuickEQ/Equalization/PeakingEqualizer.cs +++ b/Cavern.QuickEQ/Equalization/PeakingEqualizer.cs @@ -101,10 +101,10 @@ public static PeakingEQ[] ParseEQFile(IEnumerable lines) { List result = new List(); 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)); } } diff --git a/Cavern/Filters/Allpass.cs b/Cavern/Filters/Allpass.cs index 4679398e..a03e07f9 100644 --- a/Cavern/Filters/Allpass.cs +++ b/Cavern/Filters/Allpass.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using Cavern.Utilities; + namespace Cavern.Filters { /// /// Simple first-order allpass filter. @@ -33,6 +35,17 @@ public Allpass(int sampleRate, double centerFreq, double q) : base(sampleRate, c /// Gain of the filter in decibels public Allpass(int sampleRate, double centerFreq, double q, double gain) : base(sampleRate, centerFreq, q, gain) { } + /// + /// Parse a Filter line of Equalizer APO which was split at spaces to a Cavern filter.
+ /// Sample: ON AP Fc 100 Hz Q 10 + ///
+ 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)); + } + /// public override object Clone() => new Allpass(SampleRate, centerFreq, q, gain); diff --git a/Cavern/Filters/Bandpass.cs b/Cavern/Filters/Bandpass.cs index 1c5d1580..73c36688 100644 --- a/Cavern/Filters/Bandpass.cs +++ b/Cavern/Filters/Bandpass.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Cavern.Filters.Utilities; +using Cavern.Utilities; namespace Cavern.Filters { /// @@ -35,6 +36,22 @@ public Bandpass(int sampleRate, double centerFreq, double q) : base(sampleRate, /// Gain of the filter in decibels public Bandpass(int sampleRate, double centerFreq, double q, double gain) : base(sampleRate, centerFreq, q, gain) { } + /// + /// Parse a Filter line of Equalizer APO which was split at spaces to a Cavern filter.
+ /// Sample: Filter: ON BP Fc 100 Hz + /// Sample with Q-factor: Filter: ON BP Fc 100 Hz Q 10 + ///
+ 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)); + } + /// public override object Clone() => new Bandpass(SampleRate, centerFreq, q, gain); diff --git a/Cavern/Filters/BiquadFilter.cs b/Cavern/Filters/BiquadFilter.cs index 178986d6..24ca85c3 100644 --- a/Cavern/Filters/BiquadFilter.cs +++ b/Cavern/Filters/BiquadFilter.cs @@ -144,6 +144,26 @@ protected BiquadFilter(int sampleRate, double centerFreq, double q, double gain) _ => throw new ArgumentOutOfRangeException(nameof(type)) }; + /// + /// Parse a Filter line of Equalizer APO which was split at spaces to a Cavern filter, + /// detecting its type.
+ ///
+ 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]) + }; + /// /// Wipe the history to prevent clipping when applying the same filter for a new signal. /// diff --git a/Cavern/Filters/Delay.cs b/Cavern/Filters/Delay.cs index 9d6cc6e7..0b2e5afd 100644 --- a/Cavern/Filters/Delay.cs +++ b/Cavern/Filters/Delay.cs @@ -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 filter. ///
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), diff --git a/Cavern/Filters/HighShelf.cs b/Cavern/Filters/HighShelf.cs index ba39539d..28ea66f9 100644 --- a/Cavern/Filters/HighShelf.cs +++ b/Cavern/Filters/HighShelf.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Cavern.Filters.Utilities; +using Cavern.Utilities; namespace Cavern.Filters { /// @@ -35,6 +36,22 @@ public HighShelf(int sampleRate, double centerFreq, double q) : base(sampleRate, /// Gain of the filter in decibels public HighShelf(int sampleRate, double centerFreq, double q, double gain) : base(sampleRate, centerFreq, q, gain) { } + /// + /// Parse a Filter line of Equalizer APO which was split at spaces to a Cavern filter.
+ /// 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 + ///
+ 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)); + } + /// public override object Clone() => new HighShelf(SampleRate, centerFreq, q, gain); diff --git a/Cavern/Filters/Highpass.cs b/Cavern/Filters/Highpass.cs index 14130419..ec349e58 100644 --- a/Cavern/Filters/Highpass.cs +++ b/Cavern/Filters/Highpass.cs @@ -1,6 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Cavern.Filters.Utilities; +using Cavern.Utilities; namespace Cavern.Filters { /// @@ -34,6 +36,21 @@ public Highpass(int sampleRate, double centerFreq, double q) : base(sampleRate, /// Gain of the filter in decibels public Highpass(int sampleRate, double centerFreq, double q, double gain) : base(sampleRate, centerFreq, q, gain) { } + /// + /// Parse a Filter line of Equalizer APO which was split at spaces to a Cavern filter.
+ /// 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 + ///
+ 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)); + } + /// public override object Clone() => new Highpass(SampleRate, centerFreq, q, gain); diff --git a/Cavern/Filters/LowShelf.cs b/Cavern/Filters/LowShelf.cs index 2b3358fe..eeb67105 100644 --- a/Cavern/Filters/LowShelf.cs +++ b/Cavern/Filters/LowShelf.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Cavern.Filters.Utilities; +using Cavern.Utilities; namespace Cavern.Filters { /// @@ -35,6 +36,22 @@ public LowShelf(int sampleRate, double centerFreq, double q) : base(sampleRate, /// Gain of the filter in decibels public LowShelf(int sampleRate, double centerFreq, double q, double gain) : base(sampleRate, centerFreq, q, gain) { } + /// + /// Parse a Filter line of Equalizer APO which was split at spaces to a Cavern filter.
+ /// 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 + ///
+ 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)); + } + /// public override object Clone() => new LowShelf(SampleRate, centerFreq, q, gain); diff --git a/Cavern/Filters/Lowpass.cs b/Cavern/Filters/Lowpass.cs index 8ab6e1d6..4e170ba0 100644 --- a/Cavern/Filters/Lowpass.cs +++ b/Cavern/Filters/Lowpass.cs @@ -1,6 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Cavern.Filters.Utilities; +using Cavern.Utilities; namespace Cavern.Filters { /// @@ -34,6 +36,21 @@ public Lowpass(int sampleRate, double centerFreq, double q) : base(sampleRate, c /// Gain of the filter in decibels public Lowpass(int sampleRate, double centerFreq, double q, double gain) : base(sampleRate, centerFreq, q, gain) { } + /// + /// Parse a Filter line of Equalizer APO which was split at spaces to a Cavern filter.
+ /// 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 + ///
+ 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)); + } + /// public override object Clone() => new Lowpass(SampleRate, centerFreq, q, gain); diff --git a/Cavern/Filters/Notch.cs b/Cavern/Filters/Notch.cs index fc1b1284..aa357275 100644 --- a/Cavern/Filters/Notch.cs +++ b/Cavern/Filters/Notch.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; +using Cavern.Filters.Utilities; +using Cavern.Utilities; + namespace Cavern.Filters { /// /// Simple first-order notch filter. @@ -33,6 +36,22 @@ public Notch(int sampleRate, double centerFreq, double q) : base(sampleRate, cen /// Gain of the filter in decibels public Notch(int sampleRate, double centerFreq, double q, double gain) : base(sampleRate, centerFreq, q, gain) { } + /// + /// Parse a Filter line of Equalizer APO which was split at spaces to a Cavern filter.
+ /// Sample with default Q-factor: Filter: ON NO Fc 100 Hz + /// Sample with custom Q-factor: Filter: ON NO Fc 100 Hz Q 30 + ///
+ 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)); + } + /// public override object Clone() => new Notch(SampleRate, centerFreq, q, gain); diff --git a/Cavern/Filters/PeakingEQ.cs b/Cavern/Filters/PeakingEQ.cs index b46c1bd9..5a214381 100644 --- a/Cavern/Filters/PeakingEQ.cs +++ b/Cavern/Filters/PeakingEQ.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; +using Cavern.Filters.Utilities; +using Cavern.Utilities; + namespace Cavern.Filters { /// /// Simple first-order peaking filter. @@ -33,6 +36,22 @@ public PeakingEQ(int sampleRate, double centerFreq, double q) : base(sampleRate, /// Gain of the filter in decibels public PeakingEQ(int sampleRate, double centerFreq, double q, double gain) : base(sampleRate, centerFreq, q, gain) { } + /// + /// Parse a Filter line of Equalizer APO which was split at spaces to a Cavern filter.
+ /// Sample with Q factor: Filter: ON PK Fc 100 Hz Gain 0 dB Q 10
+ /// Sample with bandwidth: Filter: ON PK Fc 100 Hz Gain 0 dB BW Oct 0.1442 + ///
+ 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)); + } + /// public override object Clone() => new PeakingEQ(SampleRate, centerFreq, q, gain); diff --git a/Cavern/Utilities/QMath.cs b/Cavern/Utilities/QMath.cs index 944b9aea..c2fbb2e6 100644 --- a/Cavern/Utilities/QMath.cs +++ b/Cavern/Utilities/QMath.cs @@ -423,6 +423,12 @@ public static int TrailingZeros(int x) { return zeros; } + /// + /// Try to parse a double value regardless of the system's culture. + /// + public static bool TryParseDouble(string from, out double num) => + double.TryParse(from.Replace(',', '.'), NumberStyles.Any, CultureInfo.InvariantCulture, out num); + /// /// Conversion array for . /// diff --git a/CavernSamples/Cavern.WPF/BiquadEditor.xaml.cs b/CavernSamples/Cavern.WPF/BiquadEditor.xaml.cs index 2660760e..6018eef7 100644 --- a/CavernSamples/Cavern.WPF/BiquadEditor.xaml.cs +++ b/CavernSamples/Cavern.WPF/BiquadEditor.xaml.cs @@ -95,9 +95,8 @@ bool RecreateFilter() { return true; // Initializing, no need for checks } - if (double.TryParse(this.centerFreq.Text.Replace(',', '.'), CultureInfo.InvariantCulture, out double centerFreq) && - double.TryParse(this.q.Text.Replace(',', '.'), CultureInfo.InvariantCulture, out double q) && - double.TryParse(this.gain.Text.Replace(',', '.'), CultureInfo.InvariantCulture, out double gain)) { + if (QMath.TryParseDouble(this.centerFreq.Text, out double centerFreq) && QMath.TryParseDouble(this.q.Text, out double q) && + QMath.TryParseDouble(this.gain.Text, out double gain)) { Filter = BiquadFilter.Create((BiquadFilterType)filterTypes.SelectedItem, 48000, centerFreq, q, gain); if (Filter is PhaseSwappableBiquadFilter swappable) { swappable.PhaseSwapped = swapPhase.IsChecked.Value; diff --git a/CavernSamples/EQAPOtoFIR/LineParser.cs b/CavernSamples/EQAPOtoFIR/LineParser.cs index babc3ef2..e64f0c60 100644 --- a/CavernSamples/EQAPOtoFIR/LineParser.cs +++ b/CavernSamples/EQAPOtoFIR/LineParser.cs @@ -1,7 +1,6 @@ using System.Globalization; using Cavern.Filters; -using Cavern.Filters.Utilities; using Cavern.QuickEQ.Equalization; namespace EQAPOtoFIR { @@ -13,7 +12,7 @@ public static class LineParser { /// Parse a line of Equalizer APO configuration and apply the changes on a channel. ///
public static void Parse(string line, EqualizedChannel target) { - if (string.IsNullOrEmpty(line) || line.StartsWith("#")) { + if (string.IsNullOrEmpty(line) || line.StartsWith('#')) { return; } string[] split = line.Split(':'); @@ -25,7 +24,7 @@ public static void Parse(string line, EqualizedChannel target) { Delay(split[1], target); break; case "Filter": - target.Modify(Filter(split[1])); + target.Modify(BiquadFilter.FromEqualizerAPO(line.Split(' '), analyzerSampleRate)); break; case "GraphicEQ": target.Modify(GraphicEQ(split[1])); @@ -66,155 +65,6 @@ static void Delay(string source, EqualizedChannel channel) { } } - /// - /// Parse a peaking filter line. - /// Sample with Q factor: ON PK Fc 100 Hz Gain 0 dB Q 10 - /// Sample with bandwidth: ON PK Fc 100 Hz Gain 0 dB BW Oct 0.1442 - /// - static PeakingEQ ParsePeakingEQ(string[] split) { - if (ParseGain(split[3], out double freq) && ParseGain(split[6], out double gain)) { - if (split[8].Equals("Q") && ParseGain(split[9], out double q)) { - return new PeakingEQ(analyzerSampleRate, freq, q, gain); - } else if (split[8].Equals("BW") && ParseGain(split[10], out double bw)) { - return new PeakingEQ(analyzerSampleRate, freq, QFactor.FromBandwidth(bw), gain); - } - } - return null; - } - - /// - /// Parse a low-pass filter line. - /// Sample: ON LP Fc 100 Hz - /// - static Lowpass ParseLowpass(string[] split) => - ParseGain(split[3], out double freq) ? new Lowpass(analyzerSampleRate, freq) : null; - - /// - /// Parse a low-pass filter line with Q factor. - /// Sample: ON LPQ Fc 100 Hz Q 0.7071 - /// - static Lowpass ParseLowpassWithQ(string[] split) => - ParseGain(split[3], out double freq) && ParseGain(split[6], out double q) ? new Lowpass(analyzerSampleRate, freq, q) : null; - - /// - /// Parse a high-pass filter line. - /// Sample: ON HP Fc 100 Hz - /// - static Highpass ParseHighpass(string[] split) => - ParseGain(split[3], out double freq) ? new Highpass(analyzerSampleRate, freq) : null; - - /// - /// Parse a high-pass filter line with Q factor. - /// Sample: ON HPQ Fc 100 Hz Q 0.7071 - /// - static Highpass ParseHighpassWithQ(string[] split) => - ParseGain(split[3], out double freq) && ParseGain(split[6], out double q) ? new Highpass(analyzerSampleRate, freq, q) : null; - - /// - /// Parse a band-pass filter line. - /// Sample: ON BP Fc 100 Hz - /// Sample with Q-factor: ON BP Fc 100 Hz Q 10 - /// - static Bandpass ParseBandpass(string[] split) { - if (ParseGain(split[3], out double freq)) { - if (split.Length < 6) { - return new Bandpass(analyzerSampleRate, freq); - } else if (ParseGain(split[6], out double q)) { - return new Bandpass(analyzerSampleRate, freq, q); - } - } - return null; - } - - /// - /// Parse a low-shelf filter line. - /// Sample: ON LS Fc 100 Hz Gain 0 dB - /// - static LowShelf ParseLowShelf(string[] split) => - ParseGain(split[3], out double freq) && ParseGain(split[6], out double gain) - ? new LowShelf(analyzerSampleRate, freq, QFactor.FromSlope(0.9, gain), gain) - : null; - - /// - /// Parse a low-shelf filter line with slope. - /// Sample: ON LSC 12 dB Fc 100 Hz Gain 0 dB - /// - static LowShelf ParseLowShelfWithSlope(string[] split) => - ParseGain(split[2], out double slope) && ParseGain(split[5], out double freq) && ParseGain(split[8], out double gain) - ? new LowShelf(analyzerSampleRate, freq, QFactor.FromSlopeDecibels(slope, gain), gain) - : null; - - /// - /// Parse a high-shelf filter line. - /// Sample: ON HS Fc 100 Hz Gain 0 dB - /// - static HighShelf ParseHighShelf(string[] split) => - ParseGain(split[3], out double freq) && ParseGain(split[6], out double gain) - ? new HighShelf(analyzerSampleRate, freq, QFactor.FromSlope(0.9, gain), gain) - : null; - - /// - /// Parse a high-shelf filter line with slope. - /// Sample: ON HSC 12 dB Fc 100 Hz Gain 0 dB - /// - static HighShelf ParseHighShelfWithSlope(string[] split) => - ParseGain(split[2], out double slope) && ParseGain(split[5], out double freq) && ParseGain(split[8], out double gain) - ? new HighShelf(analyzerSampleRate, freq, QFactor.FromSlopeDecibels(slope, gain), gain) - : null; - - /// - /// Parse a notch filter line. - /// Sample: ON NO Fc 100 Hz - /// Sample with Q-factor: ON NO Fc 100 Hz Q 30 - /// - static Notch ParseNotch(string[] split) { - if (ParseGain(split[3], out double freq)) { - if (split.Length < 6) { - return new Notch(analyzerSampleRate, freq, 30); - } else if (ParseGain(split[6], out double q)) { - return new Notch(analyzerSampleRate, freq, q); - } - } - return null; - } - - /// - /// Parse an all-pass filter line. - /// Sample: ON AP Fc 100 Hz Q 10 - /// - static Allpass ParseAllpass(string[] split) { - if (ParseGain(split[3], out double freq) && ParseGain(split[6], out double q)) { - return new Allpass(analyzerSampleRate, freq, q); - } - return null; - } - - /// - /// Parse a biquad filter and generate an Equalizer that simulates it. - /// - static BiquadFilter Filter(string source) { - string[] split = source.Trim().Split(' '); - BiquadFilter filter = split[1] switch { - "PK" => ParsePeakingEQ(split), - "LP" => ParseLowpass(split), - "LPQ" => ParseLowpassWithQ(split), - "HP" => ParseHighpass(split), - "HPQ" => ParseHighpassWithQ(split), - "BP" => ParseBandpass(split), - "LS" => ParseLowShelf(split), - "LSC" => ParseLowShelfWithSlope(split), - "HS" => ParseHighShelf(split), - "HSC" => ParseHighShelfWithSlope(split), - "NO" => ParseNotch(split), - "AP" => ParseAllpass(split), - _ => null - }; - if (filter == null) { - return null; - } - return filter; - } - /// /// Parse a graphic EQ. /// diff --git a/CavernSamples/FilterStudio/Graphs/ManipulatableGraph.cs b/CavernSamples/FilterStudio/Graphs/ManipulatableGraph.cs index 66e43029..bce722d9 100644 --- a/CavernSamples/FilterStudio/Graphs/ManipulatableGraph.cs +++ b/CavernSamples/FilterStudio/Graphs/ManipulatableGraph.cs @@ -18,6 +18,11 @@ public class ManipulatableGraph : ScrollViewer { ///
public bool AllowConnection { get; set; } = true; + /// + /// Allow the user to drag the graph or nodes to move them around the screen. + /// + public bool AllowMovement { get; set; } = true; + /// /// Called when the user left-clicks anywhere in the graph control bounds, passes the clicked edge or node. /// If the click doesn't hit any graph element, the will be null. @@ -156,11 +161,15 @@ protected override void OnPreviewMouseUp(MouseButtonEventArgs e) { /// Hack to disable drag and drop as positions can't be preserved between graph updates. /// protected override void OnPreviewMouseMove(MouseEventArgs e) { - if (e.LeftButton == MouseButtonState.Pressed) { + if (!AllowMovement) { e.Handled = true; } if (connection != null) { + if (e.LeftButton == MouseButtonState.Pressed) { + e.Handled = true; + } + Point clickPos = e.GetPosition(this); connection.X2 = clickPos.X; connection.Y2 = clickPos.Y; diff --git a/CavernSamples/FilterStudio/Graphs/PipelineEditor.cs b/CavernSamples/FilterStudio/Graphs/PipelineEditor.cs index a516b50b..37c0e3c2 100644 --- a/CavernSamples/FilterStudio/Graphs/PipelineEditor.cs +++ b/CavernSamples/FilterStudio/Graphs/PipelineEditor.cs @@ -38,6 +38,9 @@ public class PipelineEditor : ManipulatableGraph { public ConfigurationFile Source { get => source; set { + if (value == null) { + return; + } source = value; RecreateGraph(); SelectNode("0"); diff --git a/CavernSamples/FilterStudio/MainWindow.Graph.cs b/CavernSamples/FilterStudio/MainWindow.Graph.cs index ec733f5a..b2db2db0 100644 --- a/CavernSamples/FilterStudio/MainWindow.Graph.cs +++ b/CavernSamples/FilterStudio/MainWindow.Graph.cs @@ -55,7 +55,7 @@ void SetDirection(LayerDirection direction) { /// /// When the user lost the graph because it was moved outside the screen, this function redisplays it in the center of the frame. /// - void Recenter(object _, RoutedEventArgs e) => ReloadGraph(); + void Recenter(object _, RoutedEventArgs e) => pipeline.Source = pipeline.Source; /// /// Converts all filters to convolutions and merges them downwards if they only have a single child. diff --git a/CavernSamples/FilterStudio/MainWindow.xaml b/CavernSamples/FilterStudio/MainWindow.xaml index da757192..aa84587b 100644 --- a/CavernSamples/FilterStudio/MainWindow.xaml +++ b/CavernSamples/FilterStudio/MainWindow.xaml @@ -62,7 +62,7 @@ - + diff --git a/CavernSamples/FilterStudio/MainWindow.xaml.cs b/CavernSamples/FilterStudio/MainWindow.xaml.cs index 79d19ac6..aaa30466 100644 --- a/CavernSamples/FilterStudio/MainWindow.xaml.cs +++ b/CavernSamples/FilterStudio/MainWindow.xaml.cs @@ -94,9 +94,13 @@ void LoadConfiguration(object _, RoutedEventArgs e) { Filter = (string)language["OpFil"] }; if (dialog.ShowDialog().Value) { - ConfigurationFile file = new EqualizerAPOConfigurationFile(dialog.FileName, Listener.DefaultSampleRate); + try { + ConfigurationFile file = new EqualizerAPOConfigurationFile(dialog.FileName, Listener.DefaultSampleRate); - pipeline.Source = file; + pipeline.Source = file; + } catch { + Error((string)language["NLoad"]); + } } } diff --git a/CavernSamples/FilterStudio/Resources/MainWindowStrings.hu-HU.xaml b/CavernSamples/FilterStudio/Resources/MainWindowStrings.hu-HU.xaml index 7d937cd7..f258bd21 100644 --- a/CavernSamples/FilterStudio/Resources/MainWindowStrings.hu-HU.xaml +++ b/CavernSamples/FilterStudio/Resources/MainWindowStrings.hu-HU.xaml @@ -44,6 +44,7 @@ Hiba Egy konfigurációs fájl szerkesztés alatt van. A csatornakonfiguráció csak új konfigurációs fájl létrehozásakor lesz alkalmazva. + A konfigurációs fájl betöltése nem sikerült: {0} Nem lettek kiválasztva csatornák, a csatornakonfiguráció nem változott. Nincs kiválasztva csúcspont. Kérlek, előbb hozz létre vagy tölts be egy konfigurációs fájlt. diff --git a/CavernSamples/FilterStudio/Resources/MainWindowStrings.xaml b/CavernSamples/FilterStudio/Resources/MainWindowStrings.xaml index befc4598..d6324a24 100644 --- a/CavernSamples/FilterStudio/Resources/MainWindowStrings.xaml +++ b/CavernSamples/FilterStudio/Resources/MainWindowStrings.xaml @@ -44,6 +44,7 @@ Error A configuration file is already being edited. The channel configuration will only be applied when you create a new configuration file. + Loading the configuration file has failed: {0} No channels were selected, the channel configuration wasn't changed. No node selected. Please create or load a configuration file first.