Skip to content

Commit

Permalink
Support Tonewinner AT-series
Browse files Browse the repository at this point in the history
  • Loading branch information
VoidXH committed Aug 10, 2024
1 parent 230fb9d commit 4c23a8b
Show file tree
Hide file tree
Showing 20 changed files with 258 additions and 35 deletions.
2 changes: 2 additions & 0 deletions Cavern.QuickEQ.Format/FilterSet/BaseClasses/FilterSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public static FilterSet Create(FilterSetTarget device, int channels, int sampleR
FilterSetTarget.Emotiva => new EmotivaFilterSet(channels, sampleRate),
FilterSetTarget.MonolithHTP1 => new MonolithHTP1FilterSet(channels, sampleRate),
FilterSetTarget.StormAudio => new StormAudioFilterSet(channels, sampleRate),
FilterSetTarget.TonewinnerAT => new TonewinnerATFilterSet(channels, sampleRate),
FilterSetTarget.BehringerNX => new BehringerNXFilterSet(channels, sampleRate),
FilterSetTarget.DiracLive => new DiracLiveFilterSet(channels, sampleRate),
FilterSetTarget.DiracLiveBassControl => new DiracLiveBassControlFilterSet(channels, sampleRate),
Expand Down Expand Up @@ -130,6 +131,7 @@ public static FilterSet Create(FilterSetTarget device, ReferenceChannel[] channe
FilterSetTarget.Emotiva => new EmotivaFilterSet(channels, sampleRate),
FilterSetTarget.MonolithHTP1 => new MonolithHTP1FilterSet(channels, sampleRate),
FilterSetTarget.StormAudio => new StormAudioFilterSet(channels, sampleRate),
FilterSetTarget.TonewinnerAT => new TonewinnerATFilterSet(channels, sampleRate),
FilterSetTarget.BehringerNX => new BehringerNXFilterSet(channels, sampleRate),
FilterSetTarget.DiracLive => new DiracLiveFilterSet(channels, sampleRate),
FilterSetTarget.DiracLiveBassControl => new DiracLiveBassControlFilterSet(channels, sampleRate),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ public enum FilterSetTarget {
/// StormAudio ISP processors.
/// </summary>
StormAudio,
/// <summary>
/// Tonewinner AT-series processors.
/// </summary>
TonewinnerAT,

// -------------------------------------------------------------------------
// Amplifiers --------------------------------------------------------------
Expand Down Expand Up @@ -176,6 +180,7 @@ public static class FilterSetTargetExtensions {
FilterSetTarget.Emotiva => "Emotiva",
FilterSetTarget.MonolithHTP1 => "Monoprice Monolith HTP-1",
FilterSetTarget.StormAudio => "StormAudio",
FilterSetTarget.TonewinnerAT => "Tonewinner AT series",
FilterSetTarget.BehringerNX => "Behringer NX series",
FilterSetTarget.DiracLive => null,
FilterSetTarget.DiracLiveBassControl => null,
Expand Down
10 changes: 10 additions & 0 deletions Cavern.QuickEQ.Format/FilterSet/BaseClasses/IIRFilterSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ public bool Equals(IIRChannelData other) => filters.Equals(other.filters) && gai
/// </summary>
public virtual int Bands => 20;

/// <summary>
/// Limit the number of bands exported for the LFE channel.
/// </summary>
public virtual int LFEBands => Bands;

/// <summary>
/// Minimum gain of a single peaking EQ band in decibels.
/// </summary>
Expand Down Expand Up @@ -72,6 +77,11 @@ public bool Equals(IIRChannelData other) => filters.Equals(other.filters) && gai
/// </summary>
public IIRFilterSet(ReferenceChannel[] channels, int sampleRate) : base(sampleRate) => Initialize<IIRChannelData>(channels);

/// <summary>
/// If the filter set's band count is dependent on which channel is selected, use this function instead of <see cref="Bands"/>.
/// </summary>
public virtual int GetBands(ReferenceChannel channel) => channel == ReferenceChannel.ScreenLFE ? LFEBands : Bands;

/// <summary>
/// Convert the filter set to convolution impulse responses to be used with e.g. a <see cref="MultichannelConvolver"/>.
/// </summary>
Expand Down
42 changes: 42 additions & 0 deletions Cavern.QuickEQ.Format/FilterSet/BaseClasses/LimitedIIRFilterSet.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Cavern.Channels;
using Cavern.Filters;
using Cavern.Utilities;

namespace Cavern.Format.FilterSet.BaseClasses {
/// <summary>
/// An <see cref="IIRFilterSet"/> where the selection of frequencies and Q factors are limited to a predetermined set.
/// </summary>
public abstract class LimitedIIRFilterSet : IIRFilterSet {
/// <summary>
/// The allowed frequency values in ascending order.
/// </summary>
protected abstract float[] Frequencies { get; }

/// <summary>
/// The allowed Q factor values in ascending order.
/// </summary>
protected abstract float[] QFactors { get; }

/// <summary>
/// An <see cref="IIRFilterSet"/> where the selection of frequencies and Q factors are limited to a predetermined set.
/// </summary>
protected LimitedIIRFilterSet(int channels, int sampleRate) : base(channels, sampleRate) { }

/// <summary>
/// An <see cref="IIRFilterSet"/> where the selection of frequencies and Q factors are limited to a predetermined set.
/// </summary>
protected LimitedIIRFilterSet(ReferenceChannel[] channels, int sampleRate) : base(channels, sampleRate) { }

/// <inheritdoc/>
protected override string Export(bool gainOnly) {
for (int i = 0; i < Channels.Length; i++) {
BiquadFilter[] filters = ((IIRChannelData)Channels[i]).filters;
for (int j = 0; j < filters.Length; j++) {
filters[j].Reset(Frequencies.Nearest((float)filters[j].CenterFreq), QFactors.Nearest((float)filters[j].Q),
filters[j].Gain);
}
}
return base.Export(gainOnly);
}
}
}
2 changes: 1 addition & 1 deletion Cavern.QuickEQ.Format/FilterSet/DiracLiveFilterSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public override void Export(string path) {
fileNameBase = Path.GetFileName(path);
fileNameBase = fileNameBase[..fileNameBase.LastIndexOf('.')];
for (int i = 0; i < Channels.Length; i++) {
var channelRef = (EqualizerChannelData)Channels[i];
EqualizerChannelData channelRef = (EqualizerChannelData)Channels[i];
channelRef.curve.ExportToDirac(Path.Combine(folder, $"{fileNameBase} {channelRef.name}.txt"), 0, optionalHeader);
}
}
Expand Down
4 changes: 3 additions & 1 deletion Cavern.QuickEQ.Format/FilterSet/Multiband31FilterSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ namespace Cavern.Format.FilterSet {
/// Traditional 31-band graphic equalizer.
/// </summary>
public class Multiband31FilterSet : MultibandPEQFilterSet {
/// <inheritdoc/>
public override int LFEBands => 9;

/// <inheritdoc/>
public Multiband31FilterSet(string path, int sampleRate) : base(path, sampleRate) => Prepare();

Expand Down Expand Up @@ -39,7 +42,6 @@ void Prepare() {
(3200, 3150),
(13000, 12500)
};
LFEBands = 9;
}
}
}
7 changes: 0 additions & 7 deletions Cavern.QuickEQ.Format/FilterSet/MultibandPEQFilterSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,6 @@ public class MultibandPEQFilterSet : IIRFilterSet {
/// </summary>
public (double oldFreq, double newFreq)[] FreqOverrides { get; set; }

/// <summary>
/// Limit the number of bands exported for the LFE channel.
/// </summary>
protected int LFEBands { get; set; }

/// <summary>
/// Frequency of the first exported band.
/// </summary>
Expand Down Expand Up @@ -67,7 +62,6 @@ public MultibandPEQFilterSet(int channels, int sampleRate, double firstBand, dou
this.firstBand = firstBand;
this.bandsPerOctave = bandsPerOctave;
this.bandCount = bandCount;
LFEBands = bandCount;
}

/// <summary>
Expand Down Expand Up @@ -98,7 +92,6 @@ public MultibandPEQFilterSet(ReferenceChannel[] channels, int sampleRate, double
this.firstBand = firstBand;
this.bandsPerOctave = bandsPerOctave;
this.bandCount = bandCount;
LFEBands = bandCount;
}

/// <summary>
Expand Down
93 changes: 93 additions & 0 deletions Cavern.QuickEQ.Format/FilterSet/TonewinnerATFilterSet.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using System.IO;

using Cavern.Channels;
using Cavern.Format.FilterSet.BaseClasses;

namespace Cavern.Format.FilterSet {
/// <summary>
/// IIR filter set for Tonewinner AT-series processors.
/// </summary>
public class TonewinnerATFilterSet : LimitedIIRFilterSet {
/// <inheritdoc/>
public override int Bands => throw new ChannelDependentBandCountException();

/// <inheritdoc/>
public override int LFEBands => throw new ChannelDependentBandCountException();

/// <inheritdoc/>
public override double MinGain => -12;

/// <inheritdoc/>
public override double MaxGain => 12;

/// <inheritdoc/>
public override double GainPrecision => .5;

/// <inheritdoc/>
protected override float[] Frequencies => frequencies;

/// <inheritdoc/>
protected override float[] QFactors => qFactors;

/// <summary>
/// IIR filter set for Tonewinner AT-series processors.
/// </summary>
public TonewinnerATFilterSet(int channels, int sampleRate) : base(channels, sampleRate) { }

/// <summary>
/// IIR filter set for Tonewinner AT-series processors.
/// </summary>
public TonewinnerATFilterSet(ReferenceChannel[] channels, int sampleRate) : base(channels, sampleRate) { }

/// <inheritdoc/>
public override int GetBands(ReferenceChannel channel) {
switch (channel) {
case ReferenceChannel.FrontLeft:
case ReferenceChannel.FrontRight:
case ReferenceChannel.FrontCenter:
case ReferenceChannel.FrontLeftCenter:
case ReferenceChannel.FrontRightCenter:
return 11;
case ReferenceChannel.ScreenLFE:
return 5;
case ReferenceChannel.TopFrontLeft:
case ReferenceChannel.TopFrontRight:
case ReferenceChannel.TopSideLeft:
case ReferenceChannel.TopSideRight:
case ReferenceChannel.TopFrontCenter:
case ReferenceChannel.TopRearLeft:
case ReferenceChannel.TopRearRight:
case ReferenceChannel.TopRearCenter:
return 7;
default:
return 6;
}
}

/// <inheritdoc/>
public override void Export(string path) => File.WriteAllText(path, Export(false));

/// <summary>
/// All the possible bands that can be selected for this device.
/// </summary>
static readonly float[] frequencies = {
20.2f, 20.8f, 21.3f, 21.9f, 22.5f, 23.2f, 23.8f, 24.4f, 25.1f, 25.8f, 26.5f, 27.2f, 28, 28.7f, 29.5f, 30.3f, 31.2f, 32, 32.9f,
33.8f, 34.7f, 35.7f, 36.7f, 37.7f, 38.7f, 39.8f, 40.8f, 42, 43.1f, 44.3f, 45.5f, 46.7f, 48, 49.3f, 50.7f, 52.1f, 53.5f, 55,
56.5f, 58, 59.6f, 61.2f, 62.9f, 64.6f, 66.4f, 68.2f, 70.1f, 72, 74, 76f, 78.1f, 80.2f, 82.4f, 84.7f, 87, 89.4f, 91.8f, 94.3f,
96.9f, 99.6f, 102, 105, 108, 111, 114, 117, 120, 124, 127, 130, 134, 138, 141, 145, 149, 153, 158, 162, 166, 171, 176, 180,
185, 190, 196, 201, 206, 212, 218, 224, 230, 236, 243, 249, 256, 263, 270, 278, 285, 293, 301, 309, 318, 327, 335, 345, 354,
364, 374, 384, 394, 405, 416, 428, 439, 451, 464, 476, 489, 503, 517, 531, 545, 560, 576, 591, 607, 624, 641, 659, 677, 695,
714, 734, 754, 774, 796, 817, 840, 863, 886, 910, 935, 961, 987, 1010, 1040, 1070, 1090, 1120, 1160, 1190, 1220, 1250, 1290,
1320, 1360, 1400, 1440, 1470, 1520, 1560, 1600, 1640, 1690, 1730, 1780, 1830, 1880, 1930, 1990, 2040, 2100, 2150, 2210, 2270,
2340, 2400, 2470, 2530, 2600, 2670, 2750, 2820, 2900, 2980, 3060, 3150, 3230, 3320, 3410, 3500, 3600, 3700, 3800, 3900, 4010,
4120, 4230, 4350, 4470, 4590, 4720, 4850, 4980, 5120, 5260, 5400, 5550, 5700, 5850, 6010, 6180, 6350, 6520, 6700, 6880, 7070,
7270, 7470, 7670, 7880, 8100, 8320, 8540, 8780, 9020, 9270, 9520, 9780, 10050, 10320, 10600, 10890, 11190, 11500, 11810, 12140,
12470, 12810, 13160, 13520, 13890, 14270, 14660, 15060, 15470, 15900, 16330, 16780, 17240, 17710, 18190, 18690, 19200, 19730
};

/// <summary>
/// All the possible Q-factors that can be selected for this device.
/// </summary>
static readonly float[] qFactors = { 0.5f, 0.63f, 0.794f, 1f, 1.26f, 1.587f, 2f, 2.520f, 3.175f, 4f, 5.04f, 6.350f, 8f, 10.08f };
}
}
83 changes: 67 additions & 16 deletions Cavern.QuickEQ.Format/FilterSet/YPAOFilterSet.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,43 @@
using System.IO;
using System;
using System.IO;
using System.Linq;

using Cavern.Channels;
using Cavern.Filters;
using Cavern.Utilities;
using Cavern.Format.FilterSet.BaseClasses;
using Cavern.QuickEQ.Equalization;
using Cavern.QuickEQ.Utilities;

namespace Cavern.Format.FilterSet {
/// <summary>
/// Filter set limited to 1/3 octave band choices for some versions of YPAO.
/// </summary>
public class YPAOFilterSet : IIRFilterSet {
public class YPAOFilterSet : LimitedIIRFilterSet {
/// <inheritdoc/>
public override int Bands => 7;
public override int LFEBands => lfeBands;
const int lfeBands = 4;

/// <inheritdoc/>
public override double MaxGain => 6;
public override int Bands => bands;
const int bands = 7;

/// <inheritdoc/>
public override double MinGain => -6;
public override double MaxGain => maxGain;
const double maxGain = 6;

/// <inheritdoc/>
public override double GainPrecision => .5f;
public override double MinGain => minGain;
const double minGain = -6;

/// <inheritdoc/>
public override double GainPrecision => gainPrecision;
const double gainPrecision = .5;

/// <inheritdoc/>
protected override float[] Frequencies => frequencies;

/// <inheritdoc/>
protected override float[] QFactors => qFactors;

/// <summary>
/// Filter set limited to 1/3 octave band choices for some versions of YPAO.
Expand All @@ -31,16 +49,44 @@ public YPAOFilterSet(int channels, int sampleRate) : base(channels, sampleRate)
/// </summary>
public YPAOFilterSet(ReferenceChannel[] channels, int sampleRate) : base(channels, sampleRate) { }

/// <inheritdoc/>
protected override string Export(bool gainOnly) {
for (int i = 0; i < Channels.Length; i++) {
BiquadFilter[] filters = ((IIRChannelData)Channels[i]).filters;
for (int j = 0; j < filters.Length; j++) {
filters[j].Reset(frequencies.Nearest((float)filters[j].CenterFreq), qFactors.Nearest((float)filters[j].Q),
filters[j].Gain);
}
/// <summary>
/// For certain Yamaha RX models between 2015 and 2018, the last 3 filters only start from 500 Hz.
/// This function calculates the filters in such a way.
/// </summary>
/// <param name="target">Curve to approximate with the resulting <see cref="PeakingEQ"/> set</param>
/// <param name="lfe">The operation is performed for the LFE channel</param>
/// <param name="sampleRate">Sample rate of the created filters</param>
/// <remarks>Might return less bands when no better solution can be found in the iteration limit.</remarks>
public static PeakingEQ[] GetFilters(Equalizer target, bool lfe, int sampleRate) {
if (lfe) {
return new PeakingEqualizer(target) {
MinGain = minGain,
MaxGain = maxGain,
GainPrecision = gainPrecision,
}.GetPeakingEQ(sampleRate, lfeBands);
}

const int limitedBands = 3,
unlimitedBands = bands - limitedBands;
PeakingEqualizer peqGenerator = new PeakingEqualizer(target) {
MaxGain = maxGain,
MinGain = minGain,
GainPrecision = gainPrecision
};
PeakingEQ[] result = peqGenerator.GetPeakingEQ(sampleRate, unlimitedBands);
ComplexFilter simulator = new ComplexFilter(result.Select(x => x.GetInverse()));
FilterAnalyzer analyzer = new FilterAnalyzer(simulator, sampleRate);
Equalizer approximation = analyzer.ToEqualizer(10, 20000, 1 / 24f);
target.Merge(approximation);

peqGenerator.MinFrequency = frequencies[lastBandFreqsFrom];
PeakingEQ[] lastBands = peqGenerator.GetPeakingEQ(sampleRate, limitedBands);
int firstBands = result.Length;
Array.Resize(ref result, firstBands + lastBands.Length);
for (int i = 0; i < lastBands.Length; i++) {
result[i + firstBands] = lastBands[i];
}
return base.Export(gainOnly);
return result;
}

/// <inheritdoc/>
Expand All @@ -54,6 +100,11 @@ protected override string Export(bool gainOnly) {
1000, 1260, 1590, 2000, 2520, 3170, 4000, 5040, 6350, 8000, 10100, 12700, 16000
};

/// <summary>
/// The frequency which is the first option in the last few bands.
/// </summary>
const int lastBandFreqsFrom = 15;

/// <summary>
/// All the possible Q-factors that can be selected for YPAO.
/// </summary>
Expand Down
7 changes: 5 additions & 2 deletions Cavern.QuickEQ.Format/FilterSet/YPAOLiteFilterSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ namespace Cavern.Format.FilterSet {
/// Filter set limited to 4/3 octave band choices for some versions of YPAO.
/// </summary>
public class YPAOLiteFilterSet : MultibandPEQFilterSet {
/// <inheritdoc/>
public override int LFEBands => 2;

/// <inheritdoc/>
public override double MinGain => -6;

Expand All @@ -17,11 +20,11 @@ public class YPAOLiteFilterSet : MultibandPEQFilterSet {
/// <summary>
/// Filter set limited to 4/3 octave band choices for some versions of YPAO.
/// </summary>
public YPAOLiteFilterSet(int channels, int sampleRate) : base(channels, sampleRate, 62.5, .75, 7) => LFEBands = 2;
public YPAOLiteFilterSet(int channels, int sampleRate) : base(channels, sampleRate, 62.5, .75, 7) { }

/// <summary>
/// Filter set limited to 4/3 octave band choices for some versions of YPAO.
/// </summary>
public YPAOLiteFilterSet(ReferenceChannel[] channels, int sampleRate) : base(channels, sampleRate, 62.5, .75, 7) => LFEBands = 2;
public YPAOLiteFilterSet(ReferenceChannel[] channels, int sampleRate) : base(channels, sampleRate, 62.5, .75, 7) { }
}
}
Loading

0 comments on commit 4c23a8b

Please sign in to comment.