This commit is contained in:
2026-04-17 14:55:32 -04:00
commit bc3ac1d4c9
18017 changed files with 4371742 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
namespace DTS.Common.Classes.DSP
{
/// <summary>
/// this is a restriction on who can use this filter
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Acronym")]
public class DASRestriction
{
/// <summary>
/// the Hardware_Type string value for the das (e.g. SLICE6_AIR, etc)
/// empty string means all das
/// </summary>
public string DASType { get; set; }
/// <summary>
/// the protocol version for that das (<=0 means all protocol versions will work)
/// </summary>
public int ProtocolVersion { get; set; }
public DASRestriction()
{
DASType = string.Empty;
ProtocolVersion = -1;
}
public DASRestriction(string dasType, int protocolVersion)
{
DASType = dasType;
ProtocolVersion = protocolVersion;
}
}
}

View File

@@ -0,0 +1,115 @@
using DTS.Common.Enums.Hardware;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Serialization;
namespace DTS.Common.Classes.DSP
{
/// <summary>
/// collection for all dsp filters
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Acronym")]
public class DSPFilterCollection : Collection<DSPFilterType>
{
private static readonly object MyLock = new object();
private static DSPFilterCollection _instance = null;
public static DSPFilterCollection GetDSPFilterCollection()
{
lock (MyLock)
{
if (null != _instance) { return _instance; }
WriteDefaultFileIfMissing(DSP_FILTER_XML_FILE);
_instance = ReadFile(DSP_FILTER_XML_FILE);
}
return _instance;
}
private const string DSP_FILTER_XML_FILE = "DSPFilters.xml";
private DSPFilterCollection(DSPFilterType[] filters)
{
foreach (var filter in filters) { Add(filter); }
}
public DSPFilterCollection() { }
public const int BUTTERWORTH = 13;
public const int FIR45TAP = 14;
public const int NONE = 0;
public enum DSPFilterDefaults
{
[Display(Name ="None", Description = "Default and legacy setting for streaming")]
[Scaler(double.NaN)]
None = NONE,
[Display(Name = "6th IIR Butterworth", Description = "6 - pole IIR Butterworth Low Pass")]
[Scaler(double.NaN)]
Butterworth = BUTTERWORTH,
[Display(Name = "45-Tap FIR", Description = "finite impulse response filter with 45 taps")]
[Scaler(double.NaN)]
FIR = FIR45TAP
}
private static DSPFilterCollection CreateDefaultCollection()
{
var list = new List<DSPFilterType>
{
new DSPFilterType(DSPFilterDefaults.None, new DASRestriction[] { new DASRestriction() }),
new DSPFilterType(DSPFilterDefaults.Butterworth, new DASRestriction[]
{
new DASRestriction(HardwareTypes.SLICE6_AIR.ToString(), 28),
new DASRestriction(HardwareTypes.SLICE6_AIR_BR.ToString(), -1),
new DASRestriction(HardwareTypes.SLICE6_AIR_TC.ToString(), -1)
}),
new DSPFilterType(DSPFilterDefaults.FIR, new DASRestriction[]
{
new DASRestriction(HardwareTypes.SLICE6_AIR.ToString(), 28),
new DASRestriction(HardwareTypes.SLICE6_AIR_BR.ToString(), -1),
new DASRestriction(HardwareTypes.SLICE6_AIR_TC.ToString(), -1)
})
};
var collection = new DSPFilterCollection(list.ToArray());
return collection;
}
private static void WriteDefaultFileIfMissing(string filePath)
{
if (File.Exists(filePath)) { return; }
var collection = CreateDefaultCollection();
var serializer = new XmlSerializer(typeof(DSPFilterCollection));
var settings = new XmlWriterSettings() { Indent = true };
using (var writer = XmlWriter.Create(filePath, settings))
{
serializer.Serialize(writer, collection);
}
}
private static DSPFilterCollection ReadFile(string filePath)
{
var deserializer = new XmlSerializer(typeof(DSPFilterCollection));
using (var fs = new FileStream(filePath, FileMode.Open))
{
var cs = (DSPFilterCollection)deserializer.Deserialize(fs);
return cs;
}
}
/// <summary>
/// returns the matching filter, or None if not found, or if none isn't found then the first entry
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
public DSPFilterType GetFilter(string s)
{
var match = Items.FirstOrDefault(x => x.DisplayString == s);
if (null != match) { return match; }
match = Items.FirstOrDefault(x => x.EnumValue == 0);
if (null != match) { return match; }
return Items[0];
}
}
}

View File

@@ -0,0 +1,70 @@
using System;
using System.ComponentModel;
using System.Globalization;
namespace DTS.Common.Classes.DSP
{
/// <summary>
/// this converter is used to display DSPFilterCollection in a property grid
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Acronym")]
public class DSPFilterConverter : ArrayConverter
{
private DSPFilterCollection _collection = null;
private static readonly object MyLock = new object();
private DSPFilterType[] _values = null;
private DSPFilterCollection GetCollection()
{
lock (MyLock)
{
if (null == _collection)
{
_collection = DSPFilterCollection.GetDSPFilterCollection();
}
return _collection;
}
}
public DSPFilterType [] Values
{
get
{
lock (MyLock)
{
if (null == _values)
{
var collection = GetCollection();
var array = new DSPFilterType[collection.Count];
collection.CopyTo(array, 0);
_values = array;
}
}
return _values;
}
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string)) { return true; }
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
var result = Array.Find(Values, x => x.DisplayString == (string)value);
if (result != null) { return result; }
return base.ConvertFrom(context, culture, value);
}
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return true;
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
return true;
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
var collection = GetCollection();
return new StandardValuesCollection(collection);
}
}
}

View File

@@ -0,0 +1,31 @@
namespace DTS.Common.Classes.DSP
{
/// <summary>
/// this is a restriction on who can use this filter
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Acronym")]
public class DSPFilterRestriction
{
/// <summary>
/// the Hardware_Type string value for the das (e.g. SLICE6_AIR, etc)
/// empty string means all das
/// </summary>
public string DASType { get; set; }
/// <summary>
/// the protocol version for that das (<=0 means all protocol versions will work)
/// </summary>
public int ProtocolVersion { get; set; }
public DSPFilterRestriction()
{
DASType = string.Empty;
ProtocolVersion = -1;
}
public DSPFilterRestriction(string dasType, int protocolVersion)
{
DASType = dasType;
ProtocolVersion = protocolVersion;
}
}
}

View File

@@ -0,0 +1,121 @@
using DTS.Common.Enums.Hardware;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace DTS.Common.Classes.DSP
{
/// <summary>
/// see IStreamingFilterProfile for more information
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Acronym")]
public class DSPFilterType : IStreamingFilterProfile
{
public double Ratio { get; set; }
public int EnumValue { get; set; }
public string DisplayString { get; set; }
public string DescriptionString { get; set; }
private readonly List<DASRestriction> _restrictions = new List<DASRestriction>();
public DASRestriction[] Restrictions { get => _restrictions.ToArray(); set { _restrictions.Clear(); _restrictions.AddRange(value); } }
public DSPFilterType() { }
public DSPFilterType(DSPFilterCollection.DSPFilterDefaults filter, DASRestriction[] restrictions)
{
GetProfileValues(filter, out var displayName, out var description, out var enumValue, out var ratio);
Initialize(enumValue, displayName, description, ratio, restrictions);
}
public DSPFilterType(int enumValue, string displayString, string descriptionString, double ratio, DASRestriction[] restrictions)
{
Initialize(enumValue, displayString, descriptionString, ratio, restrictions);
}
private void Initialize(int enumValue, string displayString, string descriptionString, double ratio, DASRestriction[] restrictions)
{
EnumValue = enumValue;
DisplayString = displayString;
DescriptionString = descriptionString;
Restrictions = restrictions;
Ratio = ratio;
}
public override string ToString()
{
return DisplayString;
}
public static void GetProfileValues(DSPFilterCollection.DSPFilterDefaults filter, out string displayName, out string description, out int enumValue,
out double ratio)
{
displayName = string.Empty;
description = string.Empty;
enumValue = -1;
ratio = -1;
var enumType = typeof(DSPFilterCollection.DSPFilterDefaults);
var memberInfos = enumType.GetMember(filter.ToString());
var enumValueMemberInfo = Array.Find(memberInfos, m => m.DeclaringType == enumType);
var valueAttributes = enumValueMemberInfo.GetCustomAttributes(typeof(DisplayAttribute), false);
enumValue = (int)filter;
description = ((DisplayAttribute)valueAttributes[0]).GetDescription();
displayName = ((DisplayAttribute)valueAttributes[0]).GetName();
valueAttributes = enumValueMemberInfo.GetCustomAttributes(typeof(ScalerAttribute), false);
ratio = ((ScalerAttribute)valueAttributes[0]).Scaler;
}
/// <summary>
/// this is the table of breakpoints and corresponding fC for S6A/br
/// </summary>
private static readonly List<Tuple<int, int>> _6PButterWorthTable = new List<Tuple<int, int>>
(
new[]
{
new Tuple<int,int>(8000, 1280),
new Tuple<int,int>(5000, 610),
new Tuple<int,int>(2000, 366),
new Tuple<int,int>(1000, 120),
new Tuple<int,int>(500,120),
new Tuple<int,int>(200,50),
new Tuple<int,int>(80,15)
}
);
/// <summary>
/// returns an array of breakpoints and fC for S6A and S6A-BR legacy butterworth digital filter
/// </summary>
/// <returns></returns>
public static Tuple<int, int>[] Get6PButterWorthLegacyTable() { return _6PButterWorthTable.ToArray(); }
/// <summary>
/// this is the cap of SPS where S6A will fall back on AAF
/// </summary>
public const int S6A_CAP = 20480;
/// <summary>
/// returns the fC given a hardware type, and the legacy 6P butterworth filter
/// double.NaN means the filter is not supported
/// </summary>
/// <param name="sps"></param>
/// <param name="hwType"></param>
/// <returns></returns>
public static double GetLegacytDSPFilterRate(double sps, string hwType)
{
//if we have legacy then we need the sample rate, if it's >8k s6abr then Fc
if (hwType == HardwareTypes.SLICE6_AIR.ToString() && sps >= S6A_CAP) { return double.NaN; }
var array = Get6PButterWorthLegacyTable();
Array.Sort(array, (x, y) => { return y.Item1.CompareTo(x.Item1); });
foreach (var entry in array)
{
if (sps >= entry.Item1) { return entry.Item2; }
}
return double.NaN;
}
/// <summary>
/// returns a filter rate given the current digital filter profile and sps and hardware type
/// </summary>
/// <param name="sps"></param>
/// <param name="hwType"></param>
/// <returns></returns>
public double GetDSPFilterRate(double sps, string hwType)
{
if (EnumValue != DSPFilterCollection.BUTTERWORTH) { return double.NaN; }
if (!Array.Exists(Restrictions, x => x.DASType == hwType || string.IsNullOrEmpty(x.DASType))) { return double.NaN; }
return GetLegacytDSPFilterRate(sps, hwType);
}
}
}

View File

@@ -0,0 +1,34 @@
namespace DTS.Common.Classes.DSP
{
public interface IStreamingFilterProfile
{
/// <summary>
/// the byte/enum value to send to the firmware for this filter type
/// </summary>
int EnumValue { get; set; }
/// <summary>
/// the display string for this filter
/// </summary>
string DisplayString { get; set; }
/// <summary>
/// description string for this filter
/// </summary>
string DescriptionString { get; set; }
/// <summary>
/// any restrictions on what das can use this filter
/// </summary>
DASRestriction[] Restrictions { get; set; }
/// <summary>
/// if this profile uses a ratio, then the ratio of sps to fC
/// </summary>
double Ratio { get; set; }
/// <summary>
/// returns the fC given the hardware type and the sample rate
/// returns null if digital filtering isn't used in this combination
/// </summary>
/// <param name="sps"></param>
/// <param name="hwType"></param>
/// <returns></returns>
double GetDSPFilterRate(double sps, string hwType);
}
}

View File

@@ -0,0 +1,18 @@
using System;
namespace DTS.Common.Classes.DSP
{
/// <summary>
/// generic attribute to store doubles, so for say scalers or ratios
/// </summary>
[AttributeUsage(AttributeTargets.Enum | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field, AllowMultiple = false)]
public class ScalerAttribute : Attribute
{
public double Scaler { get; set; }
public ScalerAttribute(double d)
{
Scaler = d;
}
}
}

View File

@@ -0,0 +1,70 @@
using System;
using System.ComponentModel;
using System.Globalization;
namespace DTS.Common.Classes.DSP
{
/// <summary>
/// this converter is used for displaying StreamingFilterCollection/array in property grids
/// </summary>
public class StreamingFilterConverter : ArrayConverter
{
private StreamingFilterProfileCollection _collection = null;
private static readonly object MyLock = new object();
private StreamingFilterProfile[] _values = null;
private StreamingFilterProfileCollection GetCollection()
{
lock (MyLock)
{
if (null == _collection)
{
_collection = StreamingFilterProfileCollection.GetCollection();
}
return _collection;
}
}
public StreamingFilterProfile[] Values
{
get
{
lock (MyLock)
{
if (null == _values)
{
var collection = GetCollection();
var array = new StreamingFilterProfile[collection.Count];
collection.CopyTo(array, 0);
_values = array;
}
}
return _values;
}
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string)) { return true; }
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
var result = Array.Find(Values, x => x.DisplayString == (string)value);
if (result != null) { return result; }
return base.ConvertFrom(context, culture, value);
}
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return true;
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
return true;
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
var collection = GetCollection();
return new StandardValuesCollection(collection);
}
}
}

View File

@@ -0,0 +1,82 @@
using DTS.Common.Enums.Hardware;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace DTS.Common.Classes.DSP
{
public class StreamingFilterProfile : IStreamingFilterProfile
{
public double Ratio { get; set; }
public string DisplayString { get; set; }
public string DescriptionString { get; set; }
public int EnumValue { get; set; }
private readonly List<DASRestriction> _restrictions = new List<DASRestriction>();
public DASRestriction[] Restrictions { get => _restrictions.ToArray(); set { _restrictions.Clear(); _restrictions.AddRange(value); } }
public StreamingFilterProfile() { }
public StreamingFilterProfile(StreamingFilterProfileCollection.DefaultProfiles profile, DASRestriction[] restrictions)
{
GetProfileValues(profile, out var displayName, out var description, out var enumValue, out var ratio);
Initialize(displayName, description, enumValue, ratio, restrictions);
}
public StreamingFilterProfile(string display, string description, int enumValue, double ratio, DASRestriction[] restrictions)
{
Initialize(display, description, enumValue, ratio, restrictions);
}
private void Initialize(string display, string description, int enumValue, double ratio, DASRestriction[] restrictions)
{
DisplayString = display;
DescriptionString = description;
EnumValue = enumValue;
Restrictions = restrictions;
Ratio = ratio;
}
public override string ToString()
{
return DisplayString;
}
public static void GetProfileValues(StreamingFilterProfileCollection.DefaultProfiles profile, out string displayName, out string description, out int enumValue,
out double ratio)
{
displayName = string.Empty;
description = string.Empty;
enumValue = -1;
ratio = -1;
var enumType = typeof(StreamingFilterProfileCollection.DefaultProfiles);
var memberInfos = enumType.GetMember(profile.ToString());
var enumValueMemberInfo = Array.Find(memberInfos, m => m.DeclaringType == enumType);
var valueAttributes = enumValueMemberInfo.GetCustomAttributes(typeof(DisplayAttribute), false);
enumValue = (int)profile;
description = ((DisplayAttribute)valueAttributes[0]).GetDescription();
displayName = ((DisplayAttribute)valueAttributes[0]).GetName();
valueAttributes = enumValueMemberInfo.GetCustomAttributes(typeof(ScalerAttribute), false);
ratio = ((ScalerAttribute)valueAttributes[0]).Scaler;
}
public double GetDSPFilterRate(double sps, string hwType)
{
//if we have legacy then we need the sample rate, if it's >8k s6abr then Fc
if (hwType == HardwareTypes.SLICE6_AIR.ToString() && sps >= DSPFilterType.S6A_CAP) { return double.NaN; }
if (!Array.Exists(Restrictions, x => x.DASType == hwType || string.IsNullOrEmpty(x.DASType))) { return double.NaN; }
if (EnumValue == StreamingFilterProfileCollection.DEFAULT_VALUE)
{
return DSPFilterType.GetLegacytDSPFilterRate(sps, hwType);
}
else if (double.IsNaN(Ratio)) { return double.NaN; }
var array = DSPFilterType.Get6PButterWorthLegacyTable();
Array.Sort(array, (x, y) => { return y.Item1.CompareTo(x.Item1); });
foreach (var entry in array)
{
if (sps >= entry.Item1) { return sps / Ratio; }
}
return double.NaN;
}
}
}

View File

@@ -0,0 +1,116 @@
using DTS.Common.Enums.Hardware;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Serialization;
namespace DTS.Common.Classes.DSP
{
public class StreamingFilterProfileCollection : Collection<StreamingFilterProfile>
{
private static readonly object MyLock = new object();
private static StreamingFilterProfileCollection _instance = null;
public static StreamingFilterProfileCollection GetCollection()
{
lock (MyLock)
{
if (null != _instance) { return _instance; }
WriteDefaultFileIfMissing(FILTER_PROFILES_XML_FILE);
_instance = ReadFile(FILTER_PROFILES_XML_FILE);
}
return _instance;
}
private const string FILTER_PROFILES_XML_FILE = "StreamingFilterProfiles.xml";
private StreamingFilterProfileCollection(StreamingFilterProfile[] filters)
{
foreach (var filter in filters) { Add(filter); }
}
public StreamingFilterProfileCollection() { }
public enum DefaultProfiles
{
[Display(Name ="Default (Mixed)", Description = "6 pole butterworth legacy or default", Order = DEFAULT_VALUE)]
[Scaler(double.NaN)]
Default = DEFAULT_VALUE,
[Display(Name ="Sample rate / 4", Description = "based on sample rate")]
[Scaler(4)]
Profile7 = 7,
[Display(Name = "Sample rate / 6.4", Description = "based on sample rate")]
[Scaler(6.4)]
Profile8 = 8,
[Display(Name = "Sample rate / 8", Description = "based on sample rate")]
[Scaler(8)]
Profile9 = 9,
[Display(Name = "Sample rate / 10", Description = "based on sample rate")]
[Scaler(10)]
Profile10 = 10
}
private static StreamingFilterProfileCollection CreateDefaultCollection()
{
var list = new List<StreamingFilterProfile>
{
new StreamingFilterProfile(DefaultProfiles.Default, new[] { new DASRestriction(string.Empty, -1) }),
new StreamingFilterProfile(DefaultProfiles.Profile7, new[]
{
new DASRestriction(HardwareTypes.SLICE6_AIR_BR.ToString(), 50), new DASRestriction(HardwareTypes.SLICE6_AIR.ToString(), 51), new DASRestriction(HardwareTypes.SLICE6_AIR_TC.ToString(), 0)
}),
new StreamingFilterProfile(DefaultProfiles.Profile8, new[]
{
new DASRestriction(HardwareTypes.SLICE6_AIR_BR.ToString(), 50), new DASRestriction(HardwareTypes.SLICE6_AIR.ToString(), 51), new DASRestriction(HardwareTypes.SLICE6_AIR_TC.ToString(), 0)
}),
new StreamingFilterProfile(DefaultProfiles.Profile9, new[]
{
new DASRestriction(HardwareTypes.SLICE6_AIR_BR.ToString(), 50), new DASRestriction(HardwareTypes.SLICE6_AIR.ToString(), 51), new DASRestriction(HardwareTypes.SLICE6_AIR_TC.ToString(), 0)
}),
new StreamingFilterProfile(DefaultProfiles.Profile10, new[]
{
new DASRestriction(HardwareTypes.SLICE6_AIR_BR.ToString(), 50), new DASRestriction(HardwareTypes.SLICE6_AIR.ToString(), 51), new DASRestriction(HardwareTypes.SLICE6_AIR_TC.ToString(), 0)
})
};
var collection = new StreamingFilterProfileCollection(list.ToArray());
return collection;
}
private static void WriteDefaultFileIfMissing(string filePath)
{
if (File.Exists(filePath)) { return; }
var collection = CreateDefaultCollection();
var serializer = new XmlSerializer(typeof(StreamingFilterProfileCollection));
var settings = new XmlWriterSettings() { Indent = true };
using (var writer = XmlWriter.Create(filePath, settings))
{
serializer.Serialize(writer, collection);
}
}
private static StreamingFilterProfileCollection ReadFile(string filePath)
{
var deserializer = new XmlSerializer(typeof(StreamingFilterProfileCollection));
using (var fs = new FileStream(filePath, FileMode.Open))
{
var cs = (StreamingFilterProfileCollection)deserializer.Deserialize(fs);
return cs;
}
}
public const int DEFAULT_VALUE = 13;
public StreamingFilterProfile GetStreamingFilterProfile(string s)
{
var match = Items.FirstOrDefault(x => x.DisplayString == s);
if (null != match) { return match; }
match = Items.FirstOrDefault(x => x.EnumValue == DEFAULT_VALUE);
if (null != match) { return match; }
return Items[0];
}
}
}