Skip to content

Commit

Permalink
Partial CBF implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
VoidXH committed Jun 30, 2024
1 parent df8a579 commit f8f7314
Show file tree
Hide file tree
Showing 14 changed files with 531 additions and 64 deletions.
2 changes: 1 addition & 1 deletion Cavern.Format/Utilities/StreamExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace Cavern.Format.Utilities {
/// <summary>
/// Stream reading extension functions. Provides functionality similar to <see cref="BinaryReader"/> with better performance.
/// </summary>
static class StreamExtensions {
public static class StreamExtensions {
/// <summary>
/// Read more than 2 GB into a buffer.
/// </summary>
Expand Down
45 changes: 31 additions & 14 deletions Cavern.QuickEQ.Format/ConfigurationFile/ConfigurationFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@ public abstract class ConfigurationFile : IExportable {
/// Named points where the configuration file can be separated to new sections. Split points only consist of input nodes after the
/// previous split point's output nodes.
/// </summary>
public IReadOnlyList<(string name, FilterGraphNode[] roots)> SplitPoints { get; }
public IReadOnlyList<(string name, FilterGraphNode[] roots)> SplitPoints => splitPoints;

/// <summary>
/// Named points where the configuration file can be separated to new sections. Split points only consist of input nodes after the
/// previous split point's output nodes.
/// </summary>
readonly List<(string name, FilterGraphNode[] roots)> splitPoints;

/// <inheritdoc/>
public abstract string FileExtension { get; }
Expand All @@ -33,7 +39,19 @@ public abstract class ConfigurationFile : IExportable {
protected ConfigurationFile(ConfigurationFile other) {
Dictionary<FilterGraphNode, FilterGraphNode> mapping = other.InputChannels.GetItem2s().DeepCopyWithMapping().mapping;
InputChannels = other.InputChannels.Select(x => (x.name, mapping[x.root])).ToArray();
SplitPoints = other.SplitPoints.Select(x => (x.name, x.roots.Select(x => mapping[x]).ToArray())).ToList();
splitPoints = other.SplitPoints.Select(x => (x.name, x.roots.Select(x => mapping[x]).ToArray())).ToList();
}

/// <summary>
/// Construct a configuration file from a complete filter graph, with references to its <paramref name="inputChannels"/>.
/// </summary>
/// <remarks>It's mandatory to have the corresponding output channels to close the split point. Refer to the constructors of
/// <see cref="CavernFilterStudioConfigurationFile"/> for how to add closing <see cref="OutputChannel"/>s.</remarks>
protected ConfigurationFile(string name, (string name, FilterGraphNode root)[] inputChannels) {
InputChannels = inputChannels;
splitPoints = new List<(string, FilterGraphNode[])> {
(name, InputChannels.GetItem2s())
};
}

/// <summary>
Expand All @@ -48,7 +66,7 @@ protected ConfigurationFile(string name, ReferenceChannel[] inputs) {
InputChannels[i] = (inputs[i].GetShortName(), new FilterGraphNode(new InputChannel(inputs[i])));
}

SplitPoints = new List<(string, FilterGraphNode[])> {
splitPoints = new List<(string, FilterGraphNode[])> {
(name, InputChannels.GetItem2s())
};
}
Expand All @@ -65,7 +83,7 @@ protected ConfigurationFile(string name, string[] inputs) {
InputChannels[i] = (inputs[i], new FilterGraphNode(new InputChannel(inputs[i])));
}

SplitPoints = new List<(string, FilterGraphNode[])> {
splitPoints = new List<(string, FilterGraphNode[])> {
(name, InputChannels.GetItem2s())
};
}
Expand All @@ -78,13 +96,12 @@ protected ConfigurationFile(string name, string[] inputs) {
/// </summary>
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();
FilterGraphNode[] start = (FilterGraphNode[])splitPoints[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));
splitPoints.Insert(index, (name, start));
} else {
CreateNewSplitPoint(name);
FilterGraphNode[] end = SplitPoints[^1].roots;
Expand Down Expand Up @@ -142,7 +159,7 @@ public void MergeSplitPoints() {
roots[j].DetachFromGraph(); // Input of the current split
}
}
((List<(string, FilterGraphNode[])>)SplitPoints).RemoveRange(1, SplitPoints.Count - 1);
splitPoints.RemoveRange(1, SplitPoints.Count - 1);
}

/// <summary>
Expand Down Expand Up @@ -197,9 +214,9 @@ public void RemoveSplitPoint(int index) {
equivalent.DetachChildren();
roots[i].AddChildren(oldChildren);
}
((List<(string, FilterGraphNode[])>)SplitPoints)[index + 1] = (SplitPoints[index + 1].name, roots);
splitPoints[index + 1] = (SplitPoints[index + 1].name, roots);
}
((List<(string, FilterGraphNode[])>)SplitPoints).RemoveAt(index);
splitPoints.RemoveAt(index);
}

/// <summary>
Expand All @@ -223,7 +240,7 @@ protected void CreateNewSplitPoint(string name) {
for (int i = 0; i < nodes.Length; i++) {
nodes[i] = nodes[i].AddChild(new InputChannel(((OutputChannel)nodes[i].Filter).Channel));
}
((List<(string, FilterGraphNode[])>)SplitPoints).Add((name, nodes));
splitPoints.Add((name, nodes));
}

/// <summary>
Expand Down Expand Up @@ -262,6 +279,7 @@ void VisitNode(FilterGraphNode node) {

(FilterGraphNode node, int channel)[] result = new (FilterGraphNode, int)[orderedNodes.Count];
Dictionary<int, FilterGraphNode> lastNodes = new Dictionary<int, FilterGraphNode>(); // For channel indices
int lowestChannel = 0;
for (int i = 0, c = result.Length - 1; i <= c; i++) {
FilterGraphNode source = orderedNodes[c - i];
int channelIndex = 0;
Expand All @@ -277,14 +295,13 @@ void VisitNode(FilterGraphNode node) {
}
}
} else { // Either merge node or exit from a split: new virtual channel
channelIndex = lastNodes.Keys.Min() - 1;
channelIndex = --lowestChannel;
}
lastNodes[channelIndex] = source;
result[i] = (source, channelIndex);
}

// Channel slot optimization: two non-parallel virtual channels should only occupy one virtual channel, but at different times
// TODO: easy, find ranges, and it's the standard scheduling problem from uni - make use of the channel's path if it's unused
result.OptimizeChannelUse();
return result;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
using System.IO;

namespace Cavern.Format.ConfigurationFile.ConvolutionBoxFormat {
/// <summary>
/// A single filter to be written into a <see cref="ConvolutionBoxFormatConfigurationFile"/>.
/// </summary>
abstract class CBFEntry {
/// <summary>
/// Export this filter.
/// </summary>
public abstract void Write(Stream target);

/// <summary>
/// Get the next entry from a <see cref="ConvolutionBoxFormatConfigurationFile"/> <paramref name="stream"/>.
/// </summary>
public static CBFEntry Read(Stream stream) => stream.ReadByte() switch {
0 => new MatrixEntry(stream),
1 => new ConvolutionEntry(stream),
_ => throw new NotImplementedException(),
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.IO;

using Cavern.Format.Utilities;

namespace Cavern.Format.ConfigurationFile.ConvolutionBoxFormat {
/// <summary>
/// Convolution filter in a <see cref="ConvolutionBoxFormatConfigurationFile"/>.
/// </summary>
class ConvolutionEntry : CBFEntry {
/// <summary>
/// Channel affected by this filter.
/// </summary>
public int Channel { get; }

/// <summary>
/// Convolution filter samples.
/// </summary>
public float[] Filter { get; }

/// <summary>
/// Convolution filter in a <see cref="ConvolutionBoxFormatConfigurationFile"/>.
/// </summary>
/// <param name="channel">Channel affected by this filter</param>
/// <param name="filter">Convolution filter samples</param>
public ConvolutionEntry(int channel, float[] filter) {
Channel = channel;
Filter = filter;
}

/// <summary>
/// Convolution filter from a <see cref="ConvolutionBoxFormatConfigurationFile"/> <paramref name="stream"/>.
/// </summary>
public ConvolutionEntry(Stream stream) {
Channel = stream.ReadInt32();
Filter = new float[stream.ReadInt32()];
for (int i = 0; i < Filter.Length; i++) {
Filter[i] = stream.ReadSingle();
}
}

/// <inheritdoc/>
public override void Write(Stream target) {
target.WriteByte(1);
target.WriteAny(Channel);
target.WriteAny(Filter.Length);
for (int i = 0; i < Filter.Length; i++) {
target.WriteAny(Filter[i]);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

using Cavern.Format.Utilities;

namespace Cavern.Format.ConfigurationFile.ConvolutionBoxFormat {
/// <summary>
/// A mixing filter in a <see cref="ConvolutionBoxFormatConfigurationFile"/>.
/// </summary>
class MatrixEntry : CBFEntry {
/// <summary>
/// Which channels to mix to which other. When multiple sources mix to the same target, the signals shall be merged.
/// </summary>
public IReadOnlyList<(int source, int[] targets)> Mixes => mixes;

/// <summary>
/// Which channels to mix to which other.
/// </summary>
readonly List<(int source, int[] targets)> mixes;

/// <summary>
/// A mixing filter in a <see cref="ConvolutionBoxFormatConfigurationFile"/>.
/// </summary>
public MatrixEntry() => mixes = new List<(int, int[])>();

/// <summary>
/// A mixing filter from a <see cref="ConvolutionBoxFormatConfigurationFile"/> <paramref name="stream"/>.
/// </summary>
public MatrixEntry(Stream stream) : this() {
int count = stream.ReadInt32();
for (int i = 0; i < count; i++) {
int source = stream.ReadInt32();
int[] targets = new int[stream.ReadInt32()];
for (int target = 0; target < targets.Length; target++) {
targets[target] = stream.ReadInt32();
}
mixes.Add((source, targets));
}
}

/// <summary>
/// Add a new entry to this mixing table of 1 channel being mixed to any number of channels.
/// </summary>
public void Expand(int sourceChannel, params int[] targetChannels) => mixes.Add((sourceChannel, targetChannels));

/// <summary>
/// Add a new entry to this mixing table of many channels all being mixed to any number of channels.
/// </summary>
public void Expand(int[] sourceChannels, params int[] targetChannels) {
for (int i = 0; i < sourceChannels.Length; i++) {
mixes.Add((sourceChannels[i], targetChannels));
}
}

/// <inheritdoc/>
public override void Write(Stream target) {
Cleanup();
target.WriteByte(0);
int count = mixes.Count;
target.WriteAny(count);
for (int i = 0; i < count; i++) {
target.WriteAny(mixes[i].source);
int[] targets = mixes[i].targets;
target.WriteAny(targets.Length);
for (int j = 0; j < targets.Length; j++) {
target.WriteAny(targets[j]);
}
}
}

/// <inheritdoc/>
public override string ToString() => string.Join(", ", mixes.Select(x => $"{x.source} -> [{string.Join(", ", x.targets)}]"));

/// <summary>
/// Remove duplications of the same operation if present.
/// </summary>
void Cleanup() {
List<int> toRemove = new List<int>();
for (int i = 0, c = mixes.Count; i < c; i++) {
int source = mixes[i].source;
int[] targets = mixes[i].targets;
for (int target = 0; target < targets.Length; target++) {
for (int j = i + 1; j < c; j++) {
if (toRemove.Contains(j)) {
continue;
}
int source2 = mixes[j].source;
int[] targets2 = mixes[j].targets;
for (int target2 = 0; target2 < targets2.Length; target2++) {
if (source == source2 && targets[target] == targets2[target2]) {
if (targets2.Length == 1) {
toRemove.Add(j);
} else {
targets2[target2] = targets2[^1];
Array.Resize(ref targets2, targets2.Length - 1);
}
}
}
}
}
}
toRemove.Sort();
for (int i = toRemove.Count - 1; i >= 0; i--) {
mixes.RemoveAt(toRemove[i]);
}
}
}
}
Loading

0 comments on commit f8f7314

Please sign in to comment.