/ src / Ryujinx.Audio / Backends / CompatLayer / Downmixing.cs
Downmixing.cs
  1  using System;
  2  using System.Runtime.CompilerServices;
  3  using System.Runtime.InteropServices;
  4  
  5  namespace Ryujinx.Audio.Backends.CompatLayer
  6  {
  7      public static class Downmixing
  8      {
  9          [StructLayout(LayoutKind.Sequential, Pack = 1)]
 10          private struct Channel51FormatPCM16
 11          {
 12              public short FrontLeft;
 13              public short FrontRight;
 14              public short FrontCenter;
 15              public short LowFrequency;
 16              public short BackLeft;
 17              public short BackRight;
 18          }
 19  
 20          [StructLayout(LayoutKind.Sequential, Pack = 1)]
 21          private struct ChannelStereoFormatPCM16
 22          {
 23              public short Left;
 24              public short Right;
 25          }
 26  
 27          private const int Q15Bits = 16;
 28          private const int RawQ15One = 1 << Q15Bits;
 29          private const int RawQ15HalfOne = (int)(0.5f * RawQ15One);
 30          private const int Minus3dBInQ15 = (int)(0.707f * RawQ15One);
 31          private const int Minus6dBInQ15 = (int)(0.501f * RawQ15One);
 32          private const int Minus12dBInQ15 = (int)(0.251f * RawQ15One);
 33  
 34          private static readonly long[] _defaultSurroundToStereoCoefficients = new long[4]
 35          {
 36              RawQ15One,
 37              Minus3dBInQ15,
 38              Minus12dBInQ15,
 39              Minus3dBInQ15,
 40          };
 41  
 42          private static readonly long[] _defaultStereoToMonoCoefficients = new long[2]
 43          {
 44              Minus6dBInQ15,
 45              Minus6dBInQ15,
 46          };
 47  
 48          private const int SurroundChannelCount = 6;
 49          private const int StereoChannelCount = 2;
 50          private const int MonoChannelCount = 1;
 51  
 52          [MethodImpl(MethodImplOptions.AggressiveInlining)]
 53          private static ReadOnlySpan<Channel51FormatPCM16> GetSurroundBuffer(ReadOnlySpan<short> data)
 54          {
 55              return MemoryMarshal.Cast<short, Channel51FormatPCM16>(data);
 56          }
 57  
 58          [MethodImpl(MethodImplOptions.AggressiveInlining)]
 59          private static ReadOnlySpan<ChannelStereoFormatPCM16> GetStereoBuffer(ReadOnlySpan<short> data)
 60          {
 61              return MemoryMarshal.Cast<short, ChannelStereoFormatPCM16>(data);
 62          }
 63  
 64          [MethodImpl(MethodImplOptions.AggressiveInlining)]
 65          private static short DownMixStereoToMono(ReadOnlySpan<long> coefficients, short left, short right)
 66          {
 67              return (short)Math.Clamp((left * coefficients[0] + right * coefficients[1]) >> Q15Bits, short.MinValue, short.MaxValue);
 68          }
 69  
 70          [MethodImpl(MethodImplOptions.AggressiveInlining)]
 71          private static short DownMixSurroundToStereo(ReadOnlySpan<long> coefficients, short back, short lfe, short center, short front)
 72          {
 73              return (short)Math.Clamp(
 74                  (coefficients[3] * back +
 75                  coefficients[2] * lfe +
 76                  coefficients[1] * center +
 77                  coefficients[0] * front + RawQ15HalfOne) >> Q15Bits, short.MinValue, short.MaxValue);
 78          }
 79  
 80          [MethodImpl(MethodImplOptions.AggressiveInlining)]
 81          private static short[] DownMixSurroundToStereo(ReadOnlySpan<long> coefficients, ReadOnlySpan<short> data)
 82          {
 83              int samplePerChannelCount = data.Length / SurroundChannelCount;
 84  
 85              short[] downmixedBuffer = new short[samplePerChannelCount * StereoChannelCount];
 86  
 87              ReadOnlySpan<Channel51FormatPCM16> channels = GetSurroundBuffer(data);
 88  
 89              for (int i = 0; i < samplePerChannelCount; i++)
 90              {
 91                  Channel51FormatPCM16 channel = channels[i];
 92  
 93                  downmixedBuffer[i * 2] = DownMixSurroundToStereo(coefficients, channel.BackLeft, channel.LowFrequency, channel.FrontCenter, channel.FrontLeft);
 94                  downmixedBuffer[i * 2 + 1] = DownMixSurroundToStereo(coefficients, channel.BackRight, channel.LowFrequency, channel.FrontCenter, channel.FrontRight);
 95              }
 96  
 97              return downmixedBuffer;
 98          }
 99  
100          [MethodImpl(MethodImplOptions.AggressiveInlining)]
101          private static short[] DownMixStereoToMono(ReadOnlySpan<long> coefficients, ReadOnlySpan<short> data)
102          {
103              int samplePerChannelCount = data.Length / StereoChannelCount;
104  
105              short[] downmixedBuffer = new short[samplePerChannelCount * MonoChannelCount];
106  
107              ReadOnlySpan<ChannelStereoFormatPCM16> channels = GetStereoBuffer(data);
108  
109              for (int i = 0; i < samplePerChannelCount; i++)
110              {
111                  ChannelStereoFormatPCM16 channel = channels[i];
112  
113                  downmixedBuffer[i] = DownMixStereoToMono(coefficients, channel.Left, channel.Right);
114              }
115  
116              return downmixedBuffer;
117          }
118  
119          public static short[] DownMixStereoToMono(ReadOnlySpan<short> data)
120          {
121              return DownMixStereoToMono(_defaultStereoToMonoCoefficients, data);
122          }
123  
124          public static short[] DownMixSurroundToStereo(ReadOnlySpan<short> data)
125          {
126              return DownMixSurroundToStereo(_defaultSurroundToStereoCoefficients, data);
127          }
128      }
129  }