diff --git a/Cavern.QuickEQ.Format/ConfigurationFile/ConfigurationFile.cs b/Cavern.QuickEQ.Format/ConfigurationFile/ConfigurationFile.cs
index b3998e5c..4c4cf331 100644
--- a/Cavern.QuickEQ.Format/ConfigurationFile/ConfigurationFile.cs
+++ b/Cavern.QuickEQ.Format/ConfigurationFile/ConfigurationFile.cs
@@ -52,23 +52,41 @@ protected ConfigurationFile(string name, string[] inputs) {
}
///
- /// Removes one of the by and clears all the filters it contains.
+ /// Add a new split with a custom at a specific of .
///
- /// The was not found
- /// among the
- /// The last split point can't be removed. To bypass this restriction,
- /// you could add an empty split point and remove the previous last one.
- public void ClearSplitPoint(string name) {
- int pos = GetSplitPointIndexByName(name);
- if (pos == SplitPoints.Count - 1) { // Last split can be cleared and replaced with new outputs.
- FilterGraphNode[] roots = SplitPoints[pos].roots;
+ public void AddSplitPoint(int index, string name) {
+ if (index != SplitPoints.Count) {
+ List<(string name, FilterGraphNode[] roots)> splits = (List<(string, FilterGraphNode[])>)SplitPoints;
+ FilterGraphNode[] start = (FilterGraphNode[])splits[index].roots.Clone();
+ for (int i = 0; i < start.Length; i++) {
+ ReferenceChannel channel = ((InputChannel)start[i].Filter).Channel;
+ start[i] = start[i].AddAfterParents(new OutputChannel(channel)).AddAfterParents(new InputChannel(channel));
+ }
+ splits.Insert(index, (name, start));
+ } else {
+ CreateNewSplitPoint(name);
+ FilterGraphNode[] end = SplitPoints[^1].roots;
+ for (int i = 0; i < end.Length; i++) {
+ ReferenceChannel channel = ((InputChannel)end[i].Filter).Channel;
+ end[i].AddChild(new OutputChannel(channel));
+ }
+ return;
+ }
+ }
+
+ ///
+ /// Clears all filters in one of the by .
+ ///
+ public void ClearSplitPoint(int index) {
+ if (index == SplitPoints.Count - 1) { // Last split can be cleared and replaced with new outputs
+ FilterGraphNode[] roots = SplitPoints[index].roots;
for (int i = 0; i < roots.Length; i++) {
roots[i].DetachChildren();
roots[i].AddChild(new OutputChannel(((InputChannel)roots[i].Filter).Channel));
}
} else { // General case: clear the children and use the next split to fetch the outputs
- FilterGraphNode[] roots = SplitPoints[pos].roots,
- next = SplitPoints[pos + 1].roots;
+ FilterGraphNode[] roots = SplitPoints[index].roots,
+ next = SplitPoints[index + 1].roots;
for (int i = 0; i < roots.Length; i++) {
ReferenceChannel channel = ((InputChannel)roots[i].Filter).Channel;
FilterGraphNode equivalent = next.First(x => ((InputChannel)x.Filter).Channel == channel);
@@ -81,26 +99,30 @@ public void ClearSplitPoint(string name) {
}
///
- /// Removes one of the by and clears all the filters it contains.
+ /// Clears all filters in one of the by .
///
/// The was not found
/// among the
+ public void ClearSplitPoint(string name) => ClearSplitPoint(GetSplitPointIndexByName(name));
+
+ ///
+ /// Removes one of the by and clears all the filters it contains.
+ ///
/// The last split point can't be removed. To bypass this restriction,
/// you could add an empty split point and remove the previous last one.
- public void RemoveSplitPoint(string name) {
+ public void RemoveSplitPoint(int index) {
if (SplitPoints.Count == 1) {
throw new IndexOutOfRangeException();
}
- int pos = GetSplitPointIndexByName(name);
- if (pos == SplitPoints.Count - 1) { // Last split can be just removed
- FilterGraphNode[] roots = SplitPoints[pos].roots;
+ if (index == SplitPoints.Count - 1) { // Last split can be just removed
+ FilterGraphNode[] roots = SplitPoints[index].roots;
for (int i = 0; i < roots.Length; i++) {
roots[i].DetachFromGraph(false);
}
} else { // General case: transfer children from the next set of roots, then swap roots
- FilterGraphNode[] roots = SplitPoints[pos].roots,
- next = SplitPoints[pos + 1].roots;
+ FilterGraphNode[] roots = SplitPoints[index].roots,
+ next = SplitPoints[index + 1].roots;
for (int i = 0; i < roots.Length; i++) {
ReferenceChannel channel = ((InputChannel)roots[i].Filter).Channel;
FilterGraphNode equivalent = next.First(x => ((InputChannel)x.Filter).Channel == channel);
@@ -110,11 +132,20 @@ public void RemoveSplitPoint(string name) {
equivalent.DetachChildren();
roots[i].AddChildren(oldChildren);
}
- ((List<(string, FilterGraphNode[])>)SplitPoints)[pos + 1] = (SplitPoints[pos + 1].name, roots);
+ ((List<(string, FilterGraphNode[])>)SplitPoints)[index + 1] = (SplitPoints[index + 1].name, roots);
}
- ((List<(string, FilterGraphNode[])>)SplitPoints).RemoveAt(pos);
+ ((List<(string, FilterGraphNode[])>)SplitPoints).RemoveAt(index);
}
+ ///
+ /// Removes one of the by and clears all the filters it contains.
+ ///
+ /// The was not found
+ /// among the
+ /// The last split point can't be removed. To bypass this restriction,
+ /// you could add an empty split point and remove the previous last one.
+ public void RemoveSplitPoint(string name) => RemoveSplitPoint(GetSplitPointIndexByName(name));
+
///
/// Adds an entry to the with the current state of the configuration, creating new
/// s after each existing .
@@ -123,7 +154,8 @@ public void RemoveSplitPoint(string name) {
/// because new input nodes are created in this function.
protected void CreateNewSplitPoint(string name) {
FilterGraphNode[] nodes =
- FilterGraphNodeUtils.MapGraph(InputChannels.Select(x => x.root)).Where(x => x.Filter is OutputChannel).ToArray();
+ FilterGraphNodeUtils.MapGraph(InputChannels.Select(x => x.root))
+ .Where(x => x.Filter is OutputChannel && x.Children.Count == 0).ToArray();
for (int i = 0; i < nodes.Length; i++) {
nodes[i] = nodes[i].AddChild(new InputChannel(((OutputChannel)nodes[i].Filter).Channel));
}
diff --git a/Cavern.QuickEQ/Equalization/Equalizer.Transform.cs b/Cavern.QuickEQ/Equalization/Equalizer.Transform.cs
index 0c95db07..7d5023be 100644
--- a/Cavern.QuickEQ/Equalization/Equalizer.Transform.cs
+++ b/Cavern.QuickEQ/Equalization/Equalizer.Transform.cs
@@ -39,6 +39,7 @@ public void AddSlope(double slope, double startFreq, double endFreq) {
///
/// Set this equalizer so if the is linear, this will be the difference from it.
+ /// This is calculated by LHS (this instance) - RHS () for each value.
///
/// Matching frequencies have to be guaranteed before calling this function with
/// .
diff --git a/Cavern/Filters/Utilities/FilterGraphNode.cs b/Cavern/Filters/Utilities/FilterGraphNode.cs
index 88b47090..c21e602d 100644
--- a/Cavern/Filters/Utilities/FilterGraphNode.cs
+++ b/Cavern/Filters/Utilities/FilterGraphNode.cs
@@ -43,7 +43,7 @@ public class FilterGraphNode {
/// Place a between this and the .
///
public void AddAfterParents(FilterGraphNode newParent) {
- newParent.parents.AddRange(children);
+ newParent.parents.AddRange(parents);
for (int i = 0, c = parents.Count; i < c; i++) {
parents[i].children.Clear();
parents[i].children.Add(newParent);
diff --git a/CavernSamples/FilterStudio/Consts/Language.cs b/CavernSamples/FilterStudio/Consts/Language.cs
index 135f85e0..b409ca23 100644
--- a/CavernSamples/FilterStudio/Consts/Language.cs
+++ b/CavernSamples/FilterStudio/Consts/Language.cs
@@ -2,6 +2,8 @@
using System.Globalization;
using System.Windows;
+using FilterStudio.Windows;
+
namespace FilterStudio.Consts {
///
/// Handle fetching of language strings and translations.
@@ -13,9 +15,10 @@ static class Language {
public static ResourceDictionary GetMainWindowStrings() => mainWindowCache ??= GetFor("MainWindowStrings");
///
- /// Get the translations of dialogs.
+ /// Get the 's translation.
///
- public static ResourceDictionary GetDialogStrings() => dialogCache ??= GetFor("DialogStrings");
+ public static ResourceDictionary GetConvolutionLengthDialogStrings() =>
+ convolutionLengthDialogCache ??= GetFor("ConvolutionLengthDialogStrings");
///
/// Get the translation of a resource file in the user's language, or in English if a translation couldn't be found.
@@ -40,8 +43,8 @@ static ResourceDictionary GetFor(string resource) {
static ResourceDictionary mainWindowCache;
///
- /// The loaded translation of the dialogs for reuse.
+ /// The loaded translation of the for reuse.
///
- static ResourceDictionary dialogCache;
+ static ResourceDictionary convolutionLengthDialogCache;
}
}
\ No newline at end of file
diff --git a/CavernSamples/FilterStudio/Graphs/ManipulatableGraph.cs b/CavernSamples/FilterStudio/Graphs/ManipulatableGraph.cs
index d6f45f00..66e43029 100644
--- a/CavernSamples/FilterStudio/Graphs/ManipulatableGraph.cs
+++ b/CavernSamples/FilterStudio/Graphs/ManipulatableGraph.cs
@@ -93,6 +93,11 @@ public void SelectNode(string uid) {
return;
}
+ foreach (Node other in Graph.Nodes) {
+ if (other.Attr.LineWidth != 1) {
+ other.Attr.LineWidth = 1;
+ }
+ }
node.Attr.LineWidth = 2;
Dispatcher.BeginInvoke(() => { // Call after the graph was redrawn
OnLeftClick?.Invoke(node);
diff --git a/CavernSamples/FilterStudio/Graphs/PipelineEditor.cs b/CavernSamples/FilterStudio/Graphs/PipelineEditor.cs
index 71c21789..a516b50b 100644
--- a/CavernSamples/FilterStudio/Graphs/PipelineEditor.cs
+++ b/CavernSamples/FilterStudio/Graphs/PipelineEditor.cs
@@ -40,7 +40,7 @@ public ConfigurationFile Source {
set {
source = value;
RecreateGraph();
- SelectNode(source.SplitPoints[0].roots.GetHashCode().ToString());
+ SelectNode("0");
OnSplitChanged?.Invoke(source.SplitPoints[0].roots);
}
}
@@ -62,16 +62,16 @@ void RecreateGraph() {
graph.Attr.BackgroundColor = background;
graph.Attr.LayerDirection = LayerDirection.LR;
- string lastUid = "a";
+ string lastUid = inNodeUid;
graph.AddNode(new StyledNode(lastUid, (string)language["NInpu"]));
for (int i = 0, c = splits.Count; i < c; i++) {
- string newUid = splits[i].roots.GetHashCode().ToString();
+ string newUid = i.ToString();
graph.AddNode(new StyledNode(newUid, splits[i].name));
new StyledEdge(graph, lastUid, newUid);
lastUid = newUid;
}
- graph.AddNode(new StyledNode("b", (string)language["NOutp"]));
- new StyledEdge(graph, lastUid, "b");
+ graph.AddNode(new StyledNode(outNodeUid, (string)language["NOutp"]));
+ new StyledEdge(graph, lastUid, outNodeUid);
Graph = graph;
}
@@ -83,10 +83,20 @@ void LeftClick(object element) {
return;
}
- if (int.TryParse(node.Id, out int rootCode)) {
- (string _, FilterGraphNode[] roots) = source.SplitPoints.FirstOrDefault(x => x.roots.GetHashCode() == rootCode);
+ if (int.TryParse(node.Id, out int root)) {
+ (string _, FilterGraphNode[] roots) = source.SplitPoints[root];
OnSplitChanged?.Invoke(roots);
}
}
+
+ ///
+ /// UID of the node that represents the filter set input.
+ ///
+ internal const string inNodeUid = "in";
+
+ ///
+ /// UID of the node that represents the filter set output.
+ ///
+ internal const string outNodeUid = "out";
}
}
\ No newline at end of file
diff --git a/CavernSamples/FilterStudio/MainWindow.Graph.cs b/CavernSamples/FilterStudio/MainWindow.Graph.cs
index 7ad503ad..ec733f5a 100644
--- a/CavernSamples/FilterStudio/MainWindow.Graph.cs
+++ b/CavernSamples/FilterStudio/MainWindow.Graph.cs
@@ -61,9 +61,14 @@ void SetDirection(LayerDirection direction) {
/// Converts all filters to convolutions and merges them downwards if they only have a single child.
///
void ConvertToConvolution(object _, RoutedEventArgs e) {
+ if (pipeline.Source == null) {
+ Error((string)language["NoCon"]);
+ return;
+ }
+
ConvolutionLengthDialog length = new();
if (length.ShowDialog().Value) {
- FilterGraphNodeUtils.ConvertToConvolution(rootNodes, length.Size);
+ FilterGraphNodeUtils.ConvertToConvolution(pipeline.Source.SplitPoints[0].roots, length.Size);
ReloadGraph();
}
}
diff --git a/CavernSamples/FilterStudio/MainWindow.Pipeline.cs b/CavernSamples/FilterStudio/MainWindow.Pipeline.cs
index c419b387..cccdb9c3 100644
--- a/CavernSamples/FilterStudio/MainWindow.Pipeline.cs
+++ b/CavernSamples/FilterStudio/MainWindow.Pipeline.cs
@@ -10,6 +10,30 @@
namespace FilterStudio {
// Handlers of the pipeline graph control
partial class MainWindow {
+ ///
+ /// Add a new empty pipeline step after the selected node.
+ ///
+ void AddStep(object sender, RoutedEventArgs e) {
+ StyledNode node = pipeline.GetSelectedNode(sender);
+ if (node == null) {
+ Error((string)language["NPNod"]);
+ return;
+ }
+
+ if (!int.TryParse(node.Id, out int uid)) {
+ if (node.Id == PipelineEditor.inNodeUid) {
+ Error((string)language["NPNod"]);
+ return;
+ } else {
+ uid = pipeline.Source.SplitPoints.Count;
+ }
+ }
+
+ pipeline.Source.AddSplitPoint(uid, (string)language["NSNew"]);
+ pipeline.Source = pipeline.Source; // Force a reload of the pipeline graph
+ pipeline.SelectNode(uid.ToString()); // Force a reload of the filter graph on the new step
+ }
+
///
/// Clear the currently selected pipeline step (remove all its filters).
///
@@ -20,9 +44,9 @@ void ClearStep(object sender, RoutedEventArgs e) {
return;
}
- try {
- pipeline.Source.ClearSplitPoint(node.LabelText);
- } catch (ArgumentOutOfRangeException) {
+ if (int.TryParse(node.Id, out int uid)) {
+ pipeline.Source.ClearSplitPoint(uid);
+ } else {
Error((string)language["NPiSi"]);
return;
}
@@ -57,17 +81,16 @@ void DeleteStep(object sender, RoutedEventArgs e) {
/// Handle right-clicking on a pipeline .
///
void PipelineRightClick(object element) {
- if (element is not Node && element is not Edge) {
+ if (element is not Node) {
return;
}
List<(string, Action