Skip to content

Commit

Permalink
Spatial remapping + updated tests
Browse files Browse the repository at this point in the history
  • Loading branch information
VoidXH committed Nov 18, 2023
1 parent dd06d0e commit 5b99d85
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 7 deletions.
28 changes: 26 additions & 2 deletions Cavern/Channels/ChannelPrototype.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,24 @@ public static ChannelPrototype[] Get(ReferenceChannel[] source) {
return result;
}

/// <summary>
/// Convert a mapping of <see cref="ReferenceChannel"/>s to <see cref="ChannelPrototype"/>s with <see cref="AlternativePositions"/>.
/// </summary>
public static ChannelPrototype[] GetAlternative(ReferenceChannel[] source) {
ChannelPrototype[] result = new ChannelPrototype[source.Length];
for (int i = 0; i < source.Length; ++i) {
int index = (int)source[i];
ChannelPrototype old = Mapping[index];
Channel moved = new Channel(AlternativePositions[index], old.LFE);
if (old.X == 0) {
result[i] = new ChannelPrototype(moved.Y, old.Name, old.LFE, old.Muted);
} else {
result[i] = new ChannelPrototype(moved.Y, moved.X, old.Name);
}
}
return result;
}

/// <summary>
/// Convert a mapping of <see cref="ReferenceChannel"/>s to the names of the channels.
/// </summary>
Expand Down Expand Up @@ -191,15 +209,21 @@ public static Channel[] ToLayout(ChannelPrototype[] source) {
/// </summary>
public static Channel[] ToLayout(ReferenceChannel[] source) => ToLayout(Get(source));

/// <summary>
/// Convert a reference array to a <see cref="Channel"/> array that can be set in <see cref="Listener.Channels"/>,
/// using the <see cref="AlternativePositions"/>.
/// </summary>
public static Channel[] ToLayoutAlternative(ReferenceChannel[] source) => ToLayout(GetAlternative(source));

/// <summary>
/// Check if two channel prototypes are the same.
/// </summary>
public bool Equals(ChannelPrototype other) => X == other.X && Y == other.Y && LFE == other.LFE;
public readonly bool Equals(ChannelPrototype other) => X == other.X && Y == other.Y && LFE == other.LFE;

/// <summary>
/// Human-readable channel prototype data.
/// </summary>
public override string ToString() {
public override readonly string ToString() {
string basic = $"{(LFE ? Name + "(LFE)" : Name)} ({X}; {Y})";
if (Muted) {
return basic + " (muted)";
Expand Down
61 changes: 61 additions & 0 deletions Cavern/Channels/SpatialRemapping.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;

using Cavern.Utilities;

namespace Cavern.Channels {
/// <summary>
/// Multiple ways of getting a mixing matrix to simulate one channel layout on a different one. While Cavern can play any standard
/// content on any layout, getting the matrix is useful for applying this feature in calibrations.
/// </summary>
public static class SpatialRemapping {
/// <summary>
/// Get a mixing matrix that maps the <paramref name="playedContent"/> to a <paramref name="usedLayout"/>. The result is a set of
/// multipliers for each output (playback) channel, with which the input (content) channels should be multiplied and mixed to that
/// specific channel. The dimensions are [output channels][input channels].
/// </summary>
public static float[][] GetMatrix(Channel[] playedContent, Channel[] usedLayout) {
Channel[] oldChannels = Listener.Channels;
Listener.ReplaceChannels(usedLayout);
int inputs = playedContent.Length,
outputs = usedLayout.Length;

// Create simulation
Listener simulator = new Listener() {
UpdateRate = Math.Max(inputs, 16),
LFESeparation = true,
DirectLFE = true
};
for (int i = 0; i < inputs; i++) {
simulator.AttachSource(new Source() {
Clip = GetClipForChannel(i, inputs, simulator.SampleRate),
Position = playedContent[i].SpatialPos * Listener.EnvironmentSize,
LFE = playedContent[i].LFE,
VolumeRolloff = Rolloffs.Disabled
});
}

// Simulate and format
float[] result = simulator.Render();
Listener.ReplaceChannels(oldChannels);
int expectedLength = inputs * outputs;
if (result.Length > expectedLength) {
Array.Resize(ref result, expectedLength);
}
float[][] output = new float[outputs][];
for (int i = 0; i < outputs; i++) {
output[i] = new float[inputs];
}
WaveformUtils.InterlacedToMultichannel(result, output);
return output;
}

/// <summary>
/// Create a <see cref="Clip"/> that is 1 at the channel's index and 0 everywhere else.
/// </summary>
static Clip GetClipForChannel(int channel, int channels, int sampleRate) {
float[] data = new float[channels];
data[channel] = 1;
return new Clip(data, 1, sampleRate);
}
}
}
2 changes: 1 addition & 1 deletion Cavern/Listener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ public float[] Render(int frames = 1) {
for (int source = 0; source < sourceDistances.Length; ++source) {
sourceDistances[source] = Range;
}
pulseDelta = (frames * UpdateRate) / (float)SampleRate;
pulseDelta = frames * UpdateRate / (float)SampleRate;

// Choose the sources to play
LinkedListNode<Source> node = activeSources.First;
Expand Down
2 changes: 1 addition & 1 deletion Tests/Test.Cavern.Format/Test.Cavern.Format.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>

Expand Down
2 changes: 1 addition & 1 deletion Tests/Test.Cavern.QuickEQ/Test.Cavern.QuickEQ.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Cavern.Channels;

namespace Test.Cavern {
namespace Test.Cavern.Channels {
/// <summary>
/// Tests the <see cref="ChannelPrototype"/> struct.
/// </summary>
Expand Down
29 changes: 29 additions & 0 deletions Tests/Test.Cavern/Channels/SpatialRemapping_Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Cavern;
using Cavern.Channels;

namespace Test.Cavern.Channels {
/// <summary>
/// Tests the <see cref="SpatialRemapping"/> functions.
/// </summary>
[TestClass]
public class SpatialRemapping_Tests {
/// <summary>
/// Tests if remapping the alternative 5.1 is done correctly to average 5.1 placement.
/// </summary>
[TestMethod, Timeout(1000)]
public void Remap5Point1() {
Channel[] content = ChannelPrototype.ToLayoutAlternative(ChannelPrototype.GetStandardMatrix(6)),
playback = ChannelPrototype.ToLayout(ChannelPrototype.GetStandardMatrix(6));
float[][] matrix = SpatialRemapping.GetMatrix(content, playback);
Assert.AreEqual(1, matrix[0][0]); // FL
Assert.AreEqual(1, matrix[1][1]); // FR
Assert.AreEqual(1, matrix[2][2]); // C
Assert.AreEqual(1, matrix[3][3]); // LFE
Assert.AreEqual(.570968032f, matrix[0][4]); // SL front mix
Assert.AreEqual(.570968032f, matrix[1][5]); // SR front mix
Assert.AreEqual(.820972264f, matrix[4][4]); // SL side mix
Assert.AreEqual(.820972264f, matrix[5][5]); // SR side mix
TestUtils.AssertNumberOfZeros(matrix, 28);
}
}
}
22 changes: 22 additions & 0 deletions Tests/Test.Cavern/Consts/TestUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Test.Cavern {
/// <summary>
/// Common utilities used in testing like assertions.
/// </summary>
internal static class TestUtils {
/// <summary>
/// Test if the number of zeros in a jagged <paramref name="array"/> match an expected <paramref name="count"/>.
/// </summary>
public static void AssertNumberOfZeros(float[][] array, int count) {
int zeros = 0;
for (int i = 0; i < array.Length; i++) {
float[] subarray = array[i];
for (int j = 0; j < subarray.Length; j++) {
if (subarray[j] == 0) {
zeros++;
}
}
}
Assert.AreEqual(count, zeros);
}
}
}
2 changes: 1 addition & 1 deletion Tests/Test.Cavern/Test.Cavern.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>

Expand Down

0 comments on commit 5b99d85

Please sign in to comment.