diff --git a/Cavern/Channels/ChannelPrototype.cs b/Cavern/Channels/ChannelPrototype.cs
index 75aeabf5..3e31131c 100644
--- a/Cavern/Channels/ChannelPrototype.cs
+++ b/Cavern/Channels/ChannelPrototype.cs
@@ -124,6 +124,24 @@ public static ChannelPrototype[] Get(ReferenceChannel[] source) {
return result;
}
+ ///
+ /// Convert a mapping of s to s with .
+ ///
+ 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;
+ }
+
///
/// Convert a mapping of s to the names of the channels.
///
@@ -191,15 +209,21 @@ public static Channel[] ToLayout(ChannelPrototype[] source) {
///
public static Channel[] ToLayout(ReferenceChannel[] source) => ToLayout(Get(source));
+ ///
+ /// Convert a reference array to a array that can be set in ,
+ /// using the .
+ ///
+ public static Channel[] ToLayoutAlternative(ReferenceChannel[] source) => ToLayout(GetAlternative(source));
+
///
/// Check if two channel prototypes are the same.
///
- 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;
///
/// Human-readable channel prototype data.
///
- public override string ToString() {
+ public override readonly string ToString() {
string basic = $"{(LFE ? Name + "(LFE)" : Name)} ({X}; {Y})";
if (Muted) {
return basic + " (muted)";
diff --git a/Cavern/Channels/SpatialRemapping.cs b/Cavern/Channels/SpatialRemapping.cs
new file mode 100644
index 00000000..6a00aac9
--- /dev/null
+++ b/Cavern/Channels/SpatialRemapping.cs
@@ -0,0 +1,61 @@
+using System;
+
+using Cavern.Utilities;
+
+namespace Cavern.Channels {
+ ///
+ /// 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.
+ ///
+ public static class SpatialRemapping {
+ ///
+ /// Get a mixing matrix that maps the to a . 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].
+ ///
+ 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;
+ }
+
+ ///
+ /// Create a that is 1 at the channel's index and 0 everywhere else.
+ ///
+ static Clip GetClipForChannel(int channel, int channels, int sampleRate) {
+ float[] data = new float[channels];
+ data[channel] = 1;
+ return new Clip(data, 1, sampleRate);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Cavern/Listener.cs b/Cavern/Listener.cs
index 5bef5661..5f32f7f5 100644
--- a/Cavern/Listener.cs
+++ b/Cavern/Listener.cs
@@ -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