diff --git a/Cavern/Channels/ChannelPrototype.Consts.cs b/Cavern/Channels/ChannelPrototype.Consts.cs index 0f6cdf0b..1c9fc865 100644 --- a/Cavern/Channels/ChannelPrototype.Consts.cs +++ b/Cavern/Channels/ChannelPrototype.Consts.cs @@ -114,6 +114,11 @@ public static readonly ChannelPrototype /// public static readonly ReferenceChannel[] ref510 = { ReferenceChannel.FrontLeft, ReferenceChannel.FrontRight, ReferenceChannel.FrontCenter, ReferenceChannel.ScreenLFE, ReferenceChannel.SideLeft, ReferenceChannel.SideRight }; + /// + /// Standard 5.1.2 setup (L, R, C, LFE, SL, SR, TSL, TSR). + /// + public static readonly ReferenceChannel[] ref512 = { ReferenceChannel.FrontLeft, ReferenceChannel.FrontRight, ReferenceChannel.FrontCenter, ReferenceChannel.ScreenLFE, ReferenceChannel.SideLeft, ReferenceChannel.SideRight, ReferenceChannel.TopSideLeft, ReferenceChannel.TopSideRight }; + /// /// Standard 5.1.4 setup (L, R, C, LFE, SL, SR, TFL, TFR, TRL, TRR). /// diff --git a/Cavern/Channels/SpatialRemapping.cs b/Cavern/Channels/SpatialRemapping.cs index 6a00aac9..8edd04f6 100644 --- a/Cavern/Channels/SpatialRemapping.cs +++ b/Cavern/Channels/SpatialRemapping.cs @@ -1,4 +1,7 @@ using System; +using System.Globalization; +using System.Runtime.CompilerServices; +using System.Text; using Cavern.Utilities; @@ -14,17 +17,17 @@ public static class SpatialRemapping { /// specific channel. The dimensions are [output channels][input channels]. /// 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), @@ -49,6 +52,51 @@ public static float[][] GetMatrix(Channel[] playedContent, Channel[] usedLayout) return output; } + /// + /// Get a mixing matrix that maps the to the user-set 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]. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float[][] GetMatrix(Channel[] playedContent) => GetMatrix(playedContent, Listener.Channels); + + /// + /// Convert a spatial remapping matrix to an Equalizer APO Copy filter. + /// + 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(); + } + + /// + /// Create an Equalizer APO Copy filter that matrix mixes the to the user-set + /// of the current system. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ToEqualizerAPO(Channel[] playedContent) => ToEqualizerAPO(GetMatrix(playedContent)); + /// /// Create a that is 1 at the channel's index and 0 everywhere else. /// diff --git a/CavernSamples/CavernSamples.sln b/CavernSamples/CavernSamples.sln index 28b348ec..ee31efdf 100644 --- a/CavernSamples/CavernSamples.sln +++ b/CavernSamples/CavernSamples.sln @@ -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 @@ -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 diff --git a/CavernSamples/CavernizeGUI/Elements/RenderTarget.cs b/CavernSamples/CavernizeGUI/Elements/RenderTarget.cs index e178a44f..b36a059f 100644 --- a/CavernSamples/CavernizeGUI/Elements/RenderTarget.cs +++ b/CavernSamples/CavernizeGUI/Elements/RenderTarget.cs @@ -110,63 +110,63 @@ public ReferenceChannel[] GetNameMappedChannels() { /// /// Top rears are used instead of sides for smooth height transitions and WAVEFORMATEXTENSIBLE support. 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), diff --git a/Tests/Test.Cavern/Channels/SpatialRemapping_Tests.cs b/Tests/Test.Cavern/Channels/SpatialRemapping_Tests.cs index f99e854d..9fffde4f 100644 --- a/Tests/Test.Cavern/Channels/SpatialRemapping_Tests.cs +++ b/Tests/Test.Cavern/Channels/SpatialRemapping_Tests.cs @@ -12,8 +12,8 @@ public class SpatialRemapping_Tests { /// [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 @@ -25,5 +25,16 @@ public void Remap5Point1() { Assert.AreEqual(.820972264f, matrix[5][5]); // SR side mix TestUtils.AssertNumberOfZeros(matrix, 28); } + + /// + /// Tests if remapping 7.1 is done correctly to 5.1.2 and converted to the valid Equalizer APO line. + /// + [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); + } } } \ No newline at end of file