Skip to content

Commit

Permalink
Spatial Remapping for Equalzier APO
Browse files Browse the repository at this point in the history
  • Loading branch information
VoidXH committed Nov 19, 2023
1 parent 5b99d85 commit f652557
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 26 deletions.
5 changes: 5 additions & 0 deletions Cavern/Channels/ChannelPrototype.Consts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ public static readonly ChannelPrototype
/// </summary>
public static readonly ReferenceChannel[] ref510 = { ReferenceChannel.FrontLeft, ReferenceChannel.FrontRight, ReferenceChannel.FrontCenter, ReferenceChannel.ScreenLFE, ReferenceChannel.SideLeft, ReferenceChannel.SideRight };

/// <summary>
/// Standard 5.1.2 setup (L, R, C, LFE, SL, SR, TSL, TSR).
/// </summary>
public static readonly ReferenceChannel[] ref512 = { ReferenceChannel.FrontLeft, ReferenceChannel.FrontRight, ReferenceChannel.FrontCenter, ReferenceChannel.ScreenLFE, ReferenceChannel.SideLeft, ReferenceChannel.SideRight, ReferenceChannel.TopSideLeft, ReferenceChannel.TopSideRight };

/// <summary>
/// Standard 5.1.4 setup (L, R, C, LFE, SL, SR, TFL, TFR, TRL, TRR).
/// </summary>
Expand Down
54 changes: 51 additions & 3 deletions Cavern/Channels/SpatialRemapping.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text;

using Cavern.Utilities;

Expand All @@ -14,17 +17,17 @@ public static class SpatialRemapping {
/// 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() {
Listener simulator = new Listener(false) {
UpdateRate = Math.Max(inputs, 16),
LFESeparation = true,
DirectLFE = true
};
Channel[] oldChannels = Listener.Channels;
Listener.ReplaceChannels(usedLayout);
for (int i = 0; i < inputs; i++) {
simulator.AttachSource(new Source() {
Clip = GetClipForChannel(i, inputs, simulator.SampleRate),
Expand All @@ -49,6 +52,51 @@ public static float[][] GetMatrix(Channel[] playedContent, Channel[] usedLayout)
return output;
}

/// <summary>
/// Get a mixing matrix that maps the <paramref name="playedContent"/> to the user-set <see cref="Listener.Channels"/> of the
/// current system. 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>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float[][] GetMatrix(Channel[] playedContent) => GetMatrix(playedContent, Listener.Channels);

/// <summary>
/// Convert a spatial remapping matrix to an Equalizer APO Copy filter.
/// </summary>
public static string ToEqualizerAPO(float[][] matrix) {
StringBuilder result = new StringBuilder("Copy:");
for (int i = 0; i < matrix.Length; i++) {
float[] input = matrix[i];
bool started = false;
string label = EqualizerAPOUtils.GetChannelLabel(i, matrix.Length);
for (int j = 0; j < input.Length; j++) {
if (input[j] != 0) {
if (!started) {
result.Append(' ').Append(label).Append('=');
started = true;
} else {
result.Append('+');
}
if (input[j] != 1) {
result.Append(input[j].ToString(CultureInfo.InvariantCulture)).Append('*');
}
result.Append(EqualizerAPOUtils.GetChannelLabel(j, input.Length));
}
}
if (!started) {
result.Append(' ').Append(label).Append("=0");
}
}
return result.ToString();
}

/// <summary>
/// Create an Equalizer APO Copy filter that matrix mixes the <paramref name="playedContent"/> to the user-set
/// <see cref="Listener.Channels"/> of the current system.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string ToEqualizerAPO(Channel[] playedContent) => ToEqualizerAPO(GetMatrix(playedContent));

/// <summary>
/// Create a <see cref="Clip"/> that is 1 at the channel's index and 0 everywhere else.
/// </summary>
Expand Down
8 changes: 7 additions & 1 deletion CavernSamples/CavernSamples.sln
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EnhancedAC3Merger", "Enhanc
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuickEQResultMerger", "QuickEQResultMerger\QuickEQResultMerger.csproj", "{AF9655E4-8DC7-4D30-8E35-59C81EFB3132}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EQAPOtoFIR", "EQAPOtoFIR\EQAPOtoFIR.csproj", "{9D7888F3-02C1-4B07-87C3-7272A254E477}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EQAPOtoFIR", "EQAPOtoFIR\EQAPOtoFIR.csproj", "{9D7888F3-02C1-4B07-87C3-7272A254E477}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cavern.QuickEQ.Format", "..\Cavern.QuickEQ.Format\Cavern.QuickEQ.Format.csproj", "{24E5737B-A035-4EC4-BA8E-21375BC81219}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CavernizeLive", "CavernizeLive\CavernizeLive.csproj", "{21AFB2EA-E96F-4C12-A526-064FA498B6EA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -107,6 +109,10 @@ Global
{24E5737B-A035-4EC4-BA8E-21375BC81219}.Debug|Any CPU.Build.0 = Debug|Any CPU
{24E5737B-A035-4EC4-BA8E-21375BC81219}.Release|Any CPU.ActiveCfg = Release|Any CPU
{24E5737B-A035-4EC4-BA8E-21375BC81219}.Release|Any CPU.Build.0 = Release|Any CPU
{21AFB2EA-E96F-4C12-A526-064FA498B6EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{21AFB2EA-E96F-4C12-A526-064FA498B6EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{21AFB2EA-E96F-4C12-A526-064FA498B6EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{21AFB2EA-E96F-4C12-A526-064FA498B6EA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
40 changes: 20 additions & 20 deletions CavernSamples/CavernizeGUI/Elements/RenderTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,63 +110,63 @@ public ReferenceChannel[] GetNameMappedChannels() {
/// </summary>
/// <remarks>Top rears are used instead of sides for smooth height transitions and WAVEFORMATEXTENSIBLE support.</remarks>
public static readonly RenderTarget[] Targets = {
new RenderTarget("4.0.4", new[] {
new RenderTarget("4.0.4", [
ReferenceChannel.FrontLeft, ReferenceChannel.FrontRight, ReferenceChannel.RearLeft, ReferenceChannel.RearRight,
ReferenceChannel.TopFrontLeft, ReferenceChannel.TopFrontRight, ReferenceChannel.TopRearLeft, ReferenceChannel.TopRearRight
}),
new RenderTarget("4.1.1", new[] {
]),
new RenderTarget("4.1.1", [
ReferenceChannel.FrontLeft, ReferenceChannel.FrontRight, ReferenceChannel.FrontCenter, ReferenceChannel.ScreenLFE,
ReferenceChannel.RearCenter, ReferenceChannel.TopFrontCenter
}),
]),
new RenderTarget("5.1 side", ChannelPrototype.ref510),
new RenderTarget("5.1 rear", new[] {
new RenderTarget("5.1 rear", [
ReferenceChannel.FrontLeft, ReferenceChannel.FrontRight, ReferenceChannel.FrontCenter, ReferenceChannel.ScreenLFE,
ReferenceChannel.RearLeft, ReferenceChannel.RearRight
}),
]),
new DownmixedRenderTarget("5.1.2 front", ChannelPrototype.ref514, (8, 4), (9, 5)),
new RenderTarget("5.1.2 side", new[] {
new RenderTarget("5.1.2 side", [
ReferenceChannel.FrontLeft, ReferenceChannel.FrontRight, ReferenceChannel.FrontCenter, ReferenceChannel.ScreenLFE,
ReferenceChannel.SideLeft, ReferenceChannel.SideRight, ReferenceChannel.TopRearLeft, ReferenceChannel.TopRearRight
}),
]),
new RenderTarget("5.1.4", ChannelPrototype.ref514),
new DownmixedRenderTarget("5.1.4 matrix", new[] {
new DownmixedRenderTarget("5.1.4 matrix", [
ReferenceChannel.FrontLeft, ReferenceChannel.FrontRight, ReferenceChannel.FrontCenter, ReferenceChannel.ScreenLFE,
ReferenceChannel.SideLeft, ReferenceChannel.SideRight, ReferenceChannel.TopFrontLeft, ReferenceChannel.TopFrontRight,
ReferenceChannel.TopRearLeft, ReferenceChannel.TopRearRight
}, (8, 0), (~8, 4), (9, 1), (~9, 5)),
], (8, 0), (~8, 4), (9, 1), (~9, 5)),
new RenderTarget("5.1.6 with top sides", ChannelPrototype.ref516),
new RenderTarget("5.1.6 for WAVE", ChannelPrototype.wav516),
new RenderTarget("7.1", ChannelPrototype.ref710),
new DownmixedRenderTarget("7.1.2 front", ChannelPrototype.ref714, (10, 4), (11, 5)),
new RenderTarget("7.1.2 side", new[] {
new RenderTarget("7.1.2 side", [
ReferenceChannel.FrontLeft, ReferenceChannel.FrontRight, ReferenceChannel.FrontCenter, ReferenceChannel.ScreenLFE,
ReferenceChannel.RearLeft, ReferenceChannel.RearRight, ReferenceChannel.SideLeft, ReferenceChannel.SideRight,
ReferenceChannel.TopRearLeft, ReferenceChannel.TopRearRight
}),
new DownmixedRenderTarget("7.1.2 matrix", new[] {
]),
new DownmixedRenderTarget("7.1.2 matrix", [
ReferenceChannel.FrontLeft, ReferenceChannel.FrontRight, ReferenceChannel.FrontCenter, ReferenceChannel.ScreenLFE,
ReferenceChannel.RearLeft, ReferenceChannel.RearRight, ReferenceChannel.SideLeft, ReferenceChannel.SideRight,
ReferenceChannel.TopRearLeft, ReferenceChannel.TopRearRight
}, (8, 6), (~8, 4), (9, 7), (~9, 5)),
new DownmixedRenderTarget("7.1.3 matrix", new[] {
], (8, 6), (~8, 4), (9, 7), (~9, 5)),
new DownmixedRenderTarget("7.1.3 matrix", [
ReferenceChannel.FrontLeft, ReferenceChannel.FrontRight, ReferenceChannel.FrontCenter, ReferenceChannel.ScreenLFE,
ReferenceChannel.RearLeft, ReferenceChannel.RearRight, ReferenceChannel.SideLeft, ReferenceChannel.SideRight,
ReferenceChannel.TopRearLeft, ReferenceChannel.TopRearRight, ReferenceChannel.TopFrontCenter
}, (8, 6), (~8, 4), (9, 7), (~9, 5), (10, 0), (~10, 1)),
], (8, 6), (~8, 4), (9, 7), (~9, 5), (10, 0), (~10, 1)),
new RenderTarget("7.1.4", ChannelPrototype.ref714),
new RenderTarget("7.1.6 with top sides", ChannelPrototype.ref716),
new RenderTarget("7.1.6 for WAVE", ChannelPrototype.wav716),
new RenderTarget("9.1", new[] {
new RenderTarget("9.1", [
ReferenceChannel.FrontLeft, ReferenceChannel.FrontRight, ReferenceChannel.FrontCenter, ReferenceChannel.ScreenLFE,
ReferenceChannel.RearLeft, ReferenceChannel.RearRight, ReferenceChannel.SideLeft, ReferenceChannel.SideRight,
ReferenceChannel.WideLeft, ReferenceChannel.WideRight
}),
]),
new DownmixedRenderTarget("9.1.2 front", ChannelPrototype.ref914, (12, 4), (13, 5)),
new RenderTarget("9.1.2 side", new[] {
new RenderTarget("9.1.2 side", [
ReferenceChannel.FrontLeft, ReferenceChannel.FrontRight, ReferenceChannel.FrontCenter, ReferenceChannel.ScreenLFE,
ReferenceChannel.RearLeft, ReferenceChannel.RearRight, ReferenceChannel.SideLeft, ReferenceChannel.SideRight,
ReferenceChannel.WideLeft, ReferenceChannel.WideRight, ReferenceChannel.TopRearLeft, ReferenceChannel.TopRearRight
}),
]),
new RenderTarget("9.1.4", ChannelPrototype.ref914),
new RenderTarget("9.1.6 with top sides", ChannelPrototype.ref916),
new RenderTarget("9.1.6 for WAVE", ChannelPrototype.wav916),
Expand Down
15 changes: 13 additions & 2 deletions Tests/Test.Cavern/Channels/SpatialRemapping_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ public class SpatialRemapping_Tests {
/// </summary>
[TestMethod, Timeout(1000)]
public void Remap5Point1() {
Channel[] content = ChannelPrototype.ToLayoutAlternative(ChannelPrototype.GetStandardMatrix(6)),
playback = ChannelPrototype.ToLayout(ChannelPrototype.GetStandardMatrix(6));
Channel[] content = ChannelPrototype.ToLayoutAlternative(ChannelPrototype.ref510),
playback = ChannelPrototype.ToLayout(ChannelPrototype.ref510);
float[][] matrix = SpatialRemapping.GetMatrix(content, playback);
Assert.AreEqual(1, matrix[0][0]); // FL
Assert.AreEqual(1, matrix[1][1]); // FR
Expand All @@ -25,5 +25,16 @@ public void Remap5Point1() {
Assert.AreEqual(.820972264f, matrix[5][5]); // SR side mix
TestUtils.AssertNumberOfZeros(matrix, 28);
}

/// <summary>
/// Tests if remapping 7.1 is done correctly to 5.1.2 and converted to the valid Equalizer APO line.
/// </summary>
[TestMethod, Timeout(1000)]
public void Remap7Point1To512APO() {
const string expected = "Copy: L=L R=R C=C SUB=SUB RL=0.92387956*RL+0.3826834*RR+SL RR=0.38268337*RL+0.92387956*RR+SR SL=0 SR=0";
string result = SpatialRemapping.ToEqualizerAPO(SpatialRemapping.GetMatrix(ChannelPrototype.ToLayout(ChannelPrototype.ref710),
ChannelPrototype.ToLayout(ChannelPrototype.ref512)));
Assert.AreEqual(expected, result);
}
}
}

0 comments on commit f652557

Please sign in to comment.