using System; namespace Susurrus.XAudio2 { public static class OutputMatrix { private static float Magnitude(float toLeft, float toRight) { return (float)Math.Sqrt(toLeft * toLeft + toRight * toRight); } /// /// Fills with the levels that send a pan to a device, laid out the way XAudio2 reads /// them, with a row for every device channel and a column for every source channel. /// /// A device with two or more channels plays left and right on its first two, which is where every standard /// speaker layout puts its front pair. A mono device gets each side's power folded together. /// public static void Fill(float[] levels, in PanMatrix pan, int sourceChannels, int deviceChannels) { if (levels == null) { throw new ArgumentNullException(nameof(levels)); } if (sourceChannels < 1 || sourceChannels > 2) { throw new ArgumentOutOfRangeException(nameof(sourceChannels), sourceChannels, "Only mono and stereo sounds can be panned."); } if (deviceChannels < 1) { throw new ArgumentOutOfRangeException(nameof(deviceChannels), deviceChannels, "A device plays at least one channel."); } int count = sourceChannels * deviceChannels; if (levels.Length < count) { throw new ArgumentException("The levels are too short for the channels.", nameof(levels)); } for (int i = 0; i < count; ++i) { levels[i] = 0f; } if (deviceChannels == 1) { // A stereo source's two channels add, so each gives up half its power to keep the sum from clipping. float share = sourceChannels == 2 ? (float)Math.Sqrt(0.5) : 1f; levels[0] = share * Magnitude(pan.LeftToLeft, pan.LeftToRight); if (sourceChannels == 2) { levels[1] = share * Magnitude(pan.RightToLeft, pan.RightToRight); } return; } levels[0] = pan.LeftToLeft; levels[sourceChannels] = pan.LeftToRight; if (sourceChannels == 2) { levels[1] = pan.RightToLeft; levels[sourceChannels + 1] = pan.RightToRight; } } } }