Skip to content

Commit

Permalink
Filter step addition
Browse files Browse the repository at this point in the history
  • Loading branch information
VoidXH committed Jun 8, 2024
1 parent 057c7d4 commit 5a6868a
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 43 deletions.
74 changes: 53 additions & 21 deletions Cavern.QuickEQ.Format/ConfigurationFile/ConfigurationFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,23 +52,41 @@ protected ConfigurationFile(string name, string[] inputs) {
}

/// <summary>
/// Removes one of the <see cref="SplitPoints"/> by <paramref name="name"/> and clears all the filters it contains.
/// Add a new split with a custom <paramref name="name"/> at a specific <paramref name="index"/> of <see cref="SplitPoints"/>.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">The <paramref name="name"/> was not found
/// among the <see cref="SplitPoints"/></exception>
/// <exception cref="IndexOutOfRangeException">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.</exception>
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;
}
}

/// <summary>
/// Clears all filters in one of the <see cref="SplitPoints"/> by <paramref name="index"/>.
/// </summary>
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);
Expand All @@ -81,26 +99,30 @@ public void ClearSplitPoint(string name) {
}

/// <summary>
/// Removes one of the <see cref="SplitPoints"/> by <paramref name="name"/> and clears all the filters it contains.
/// Clears all filters in one of the <see cref="SplitPoints"/> by <paramref name="name"/>.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">The <paramref name="name"/> was not found
/// among the <see cref="SplitPoints"/></exception>
public void ClearSplitPoint(string name) => ClearSplitPoint(GetSplitPointIndexByName(name));

/// <summary>
/// Removes one of the <see cref="SplitPoints"/> by <paramref name="index"/> and clears all the filters it contains.
/// </summary>
/// <exception cref="IndexOutOfRangeException">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.</exception>
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);
Expand All @@ -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);
}

/// <summary>
/// Removes one of the <see cref="SplitPoints"/> by <paramref name="name"/> and clears all the filters it contains.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">The <paramref name="name"/> was not found
/// among the <see cref="SplitPoints"/></exception>
/// <exception cref="IndexOutOfRangeException">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.</exception>
public void RemoveSplitPoint(string name) => RemoveSplitPoint(GetSplitPointIndexByName(name));

/// <summary>
/// Adds an entry to the <see cref="SplitPoints"/> with the current state of the configuration, creating new
/// <see cref="InputChannel"/>s after each existing <see cref="OutputChannel"/>.
Expand All @@ -123,7 +154,8 @@ public void RemoveSplitPoint(string name) {
/// because new input nodes are created in this function.</remarks>
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));
}
Expand Down
1 change: 1 addition & 0 deletions Cavern.QuickEQ/Equalization/Equalizer.Transform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public void AddSlope(double slope, double startFreq, double endFreq) {

/// <summary>
/// Set this equalizer so if the <paramref name="other"/> is linear, this will be the difference from it.
/// This is calculated by LHS (this instance) - RHS (<paramref name="other"/>) for each value.
/// </summary>
/// <remarks>Matching frequencies have to be guaranteed before calling this function with
/// <see cref="HasTheSameFrequenciesAs(Equalizer)"/>.</remarks>
Expand Down
2 changes: 1 addition & 1 deletion Cavern/Filters/Utilities/FilterGraphNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public class FilterGraphNode {
/// Place a <see cref="FilterGraphNode"/> between this and the <see cref="parents"/>.
/// </summary>
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);
Expand Down
11 changes: 7 additions & 4 deletions CavernSamples/FilterStudio/Consts/Language.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using System.Globalization;
using System.Windows;

using FilterStudio.Windows;

namespace FilterStudio.Consts {
/// <summary>
/// Handle fetching of language strings and translations.
Expand All @@ -13,9 +15,10 @@ static class Language {
public static ResourceDictionary GetMainWindowStrings() => mainWindowCache ??= GetFor("MainWindowStrings");

/// <summary>
/// Get the translations of dialogs.
/// Get the <see cref="ConvolutionLengthDialog"/>'s translation.
/// </summary>
public static ResourceDictionary GetDialogStrings() => dialogCache ??= GetFor("DialogStrings");
public static ResourceDictionary GetConvolutionLengthDialogStrings() =>
convolutionLengthDialogCache ??= GetFor("ConvolutionLengthDialogStrings");

/// <summary>
/// Get the translation of a resource file in the user's language, or in English if a translation couldn't be found.
Expand All @@ -40,8 +43,8 @@ static ResourceDictionary GetFor(string resource) {
static ResourceDictionary mainWindowCache;

/// <summary>
/// The loaded translation of the dialogs for reuse.
/// The loaded translation of the <see cref="ConvolutionLengthDialog"/> for reuse.
/// </summary>
static ResourceDictionary dialogCache;
static ResourceDictionary convolutionLengthDialogCache;
}
}
5 changes: 5 additions & 0 deletions CavernSamples/FilterStudio/Graphs/ManipulatableGraph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
24 changes: 17 additions & 7 deletions CavernSamples/FilterStudio/Graphs/PipelineEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand All @@ -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;
}

Expand All @@ -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);
}
}

/// <summary>
/// UID of the node that represents the filter set input.
/// </summary>
internal const string inNodeUid = "in";

/// <summary>
/// UID of the node that represents the filter set output.
/// </summary>
internal const string outNodeUid = "out";
}
}
7 changes: 6 additions & 1 deletion CavernSamples/FilterStudio/MainWindow.Graph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,14 @@ void SetDirection(LayerDirection direction) {
/// Converts all filters to convolutions and merges them downwards if they only have a single child.
/// </summary>
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();
}
}
Expand Down
41 changes: 32 additions & 9 deletions CavernSamples/FilterStudio/MainWindow.Pipeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,30 @@
namespace FilterStudio {
// Handlers of the pipeline graph control
partial class MainWindow {
/// <summary>
/// Add a new empty pipeline step after the selected node.
/// </summary>
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
}

/// <summary>
/// Clear the currently selected pipeline step (remove all its filters).
/// </summary>
Expand All @@ -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;
}
Expand Down Expand Up @@ -57,17 +81,16 @@ void DeleteStep(object sender, RoutedEventArgs e) {
/// Handle right-clicking on a pipeline <paramref name="element"/>.
/// </summary>
void PipelineRightClick(object element) {
if (element is not Node && element is not Edge) {
if (element is not Node) {
return;
}

List<(string, Action<object, RoutedEventArgs>)> menuItems = [
((string)language["OpAdP"], (_, e) => AddStep(element, e)),
(null, null), // Separator for deletion
((string)language["CoCle"], (_, e) => ClearStep(element, e)),
((string)language["CoDel"], (_, e) => DeleteStep(element, e))
];
if (element is Node) {
menuItems.Add((null, null)); // Separator for deletion
menuItems.Add(((string)language["CoCle"], (_, e) => ClearStep(element, e)));
menuItems.Add(((string)language["CoDel"], (_, e) => DeleteStep(element, e)));
}
QuickContextMenu.Show(menuItems);
}
}
Expand Down
1 change: 1 addition & 0 deletions CavernSamples/FilterStudio/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
<MenuItem Header="{StaticResource OpDel}" Click="DeleteNode"/>
</MenuItem>
<MenuItem Header="{StaticResource MPipe}" Style="{StaticResource RootMenuItem}">
<MenuItem Header="{StaticResource OpAdP}" Click="AddStep"/>
<Separator/>
<MenuItem Header="{StaticResource OpCle}" Click="ClearStep"/>
<MenuItem Header="{StaticResource OpDeP}" Click="DeleteStep"/>
Expand Down

0 comments on commit 5a6868a

Please sign in to comment.