AdpcmHelper.cs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. using Ryujinx.Audio.Renderer.Dsp.State;
  2. using Ryujinx.Common.Logging;
  3. using System;
  4. using System.Diagnostics;
  5. using System.Runtime.CompilerServices;
  6. namespace Ryujinx.Audio.Renderer.Dsp
  7. {
  8. public static class AdpcmHelper
  9. {
  10. private const int FixedPointPrecision = 11;
  11. private const int SamplesPerFrame = 14;
  12. private const int NibblesPerFrame = SamplesPerFrame + 2;
  13. private const int BytesPerFrame = 8;
  14. private const int BitsPerFrame = BytesPerFrame * 8;
  15. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  16. public static uint GetAdpcmDataSize(int sampleCount)
  17. {
  18. Debug.Assert(sampleCount >= 0);
  19. int frames = sampleCount / SamplesPerFrame;
  20. int extraSize = 0;
  21. if ((sampleCount % SamplesPerFrame) != 0)
  22. {
  23. extraSize = (sampleCount % SamplesPerFrame) / 2 + 1 + (sampleCount % 2);
  24. }
  25. return (uint)(BytesPerFrame * frames + extraSize);
  26. }
  27. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  28. public static int GetAdpcmOffsetFromSampleOffset(int sampleOffset)
  29. {
  30. Debug.Assert(sampleOffset >= 0);
  31. return GetNibblesFromSampleCount(sampleOffset) / 2;
  32. }
  33. public static int NibbleToSample(int nibble)
  34. {
  35. int frames = nibble / NibblesPerFrame;
  36. int extraNibbles = nibble % NibblesPerFrame;
  37. int samples = SamplesPerFrame * frames;
  38. return samples + extraNibbles - 2;
  39. }
  40. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  41. public static int GetNibblesFromSampleCount(int sampleCount)
  42. {
  43. byte headerSize = 0;
  44. if ((sampleCount % SamplesPerFrame) != 0)
  45. {
  46. headerSize = 2;
  47. }
  48. return sampleCount % SamplesPerFrame + NibblesPerFrame * (sampleCount / SamplesPerFrame) + headerSize;
  49. }
  50. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  51. private static short Saturate(int value)
  52. {
  53. if (value > short.MaxValue)
  54. value = short.MaxValue;
  55. if (value < short.MinValue)
  56. value = short.MinValue;
  57. return (short)value;
  58. }
  59. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  60. private static short GetCoefficientAtIndex(ReadOnlySpan<short> coefficients, int index)
  61. {
  62. if ((uint)index > (uint)coefficients.Length)
  63. {
  64. Logger.Error?.Print(LogClass.AudioRenderer, $"Out of bound read for coefficient at index {index}");
  65. return 0;
  66. }
  67. return coefficients[index];
  68. }
  69. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  70. public static int Decode(Span<short> output, ReadOnlySpan<byte> input, int startSampleOffset, int endSampleOffset, int offset, int count, ReadOnlySpan<short> coefficients, ref AdpcmLoopContext loopContext)
  71. {
  72. if (input.IsEmpty || endSampleOffset < startSampleOffset)
  73. {
  74. return 0;
  75. }
  76. byte predScale = (byte)loopContext.PredScale;
  77. byte scale = (byte)(predScale & 0xF);
  78. byte coefficientIndex = (byte)((predScale >> 4) & 0xF);
  79. short history0 = loopContext.History0;
  80. short history1 = loopContext.History1;
  81. short coefficient0 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 0);
  82. short coefficient1 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 1);
  83. int decodedCount = Math.Min(count, endSampleOffset - startSampleOffset - offset);
  84. int nibbles = GetNibblesFromSampleCount(offset + startSampleOffset);
  85. int remaining = decodedCount;
  86. int outputBufferIndex = 0;
  87. int inputIndex = 0;
  88. ReadOnlySpan<byte> targetInput;
  89. targetInput = input.Slice(nibbles / 2);
  90. while (remaining > 0)
  91. {
  92. int samplesCount;
  93. if (((uint)nibbles % NibblesPerFrame) == 0)
  94. {
  95. predScale = targetInput[inputIndex++];
  96. scale = (byte)(predScale & 0xF);
  97. coefficientIndex = (byte)((predScale >> 4) & 0xF);
  98. coefficient0 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2);
  99. coefficient1 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 1);
  100. nibbles += 2;
  101. samplesCount = Math.Min(remaining, SamplesPerFrame);
  102. }
  103. else
  104. {
  105. samplesCount = 1;
  106. }
  107. int scaleFixedPoint = FixedPointHelper.ToFixed(1.0f, FixedPointPrecision) << scale;
  108. if (samplesCount < SamplesPerFrame)
  109. {
  110. for (int i = 0; i < samplesCount; i++)
  111. {
  112. int value = targetInput[inputIndex];
  113. int sample;
  114. if ((nibbles & 1) != 0)
  115. {
  116. sample = (value << 28) >> 28;
  117. inputIndex++;
  118. }
  119. else
  120. {
  121. sample = (value << 24) >> 28;
  122. }
  123. nibbles++;
  124. int prediction = coefficient0 * history0 + coefficient1 * history1;
  125. sample = FixedPointHelper.RoundUpAndToInt(sample * scaleFixedPoint + prediction, FixedPointPrecision);
  126. short saturatedSample = Saturate(sample);
  127. history1 = history0;
  128. history0 = saturatedSample;
  129. output[outputBufferIndex++] = saturatedSample;
  130. remaining--;
  131. }
  132. }
  133. else
  134. {
  135. for (int i = 0; i < SamplesPerFrame / 2; i++)
  136. {
  137. int value = targetInput[inputIndex];
  138. int sample0;
  139. int sample1;
  140. sample0 = (value << 24) >> 28;
  141. sample1 = (value << 28) >> 28;
  142. inputIndex++;
  143. int prediction0 = coefficient0 * history0 + coefficient1 * history1;
  144. sample0 = FixedPointHelper.RoundUpAndToInt(sample0 * scaleFixedPoint + prediction0, FixedPointPrecision);
  145. short saturatedSample0 = Saturate(sample0);
  146. int prediction1 = coefficient0 * saturatedSample0 + coefficient1 * history0;
  147. sample1 = FixedPointHelper.RoundUpAndToInt(sample1 * scaleFixedPoint + prediction1, FixedPointPrecision);
  148. short saturatedSample1 = Saturate(sample1);
  149. history1 = saturatedSample0;
  150. history0 = saturatedSample1;
  151. output[outputBufferIndex++] = saturatedSample0;
  152. output[outputBufferIndex++] = saturatedSample1;
  153. }
  154. nibbles += SamplesPerFrame;
  155. remaining -= SamplesPerFrame;
  156. }
  157. }
  158. loopContext.PredScale = predScale;
  159. loopContext.History0 = history0;
  160. loopContext.History1 = history1;
  161. return decodedCount;
  162. }
  163. }
  164. }