diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt
new file mode 100644
index 0000000..2b5cc1f
--- /dev/null
+++ b/LICENSES/MIT.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Alex Barney
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/REUSE.toml b/REUSE.toml
index c1b2aac..09a59a9 100644
--- a/REUSE.toml
+++ b/REUSE.toml
@@ -15,3 +15,9 @@ path = [
precedence = "aggregate"
SPDX-FileCopyrightText = "SharpEmu Emulator Project"
SPDX-License-Identifier = "GPL-2.0-or-later"
+
+[[annotations]]
+path = "src/SharpEmu.GUI/Atrac9/**"
+precedence = "aggregate"
+SPDX-FileCopyrightText = "2018 Alex Barney"
+SPDX-License-Identifier = "MIT"
diff --git a/src/SharpEmu.CLI/Program.cs b/src/SharpEmu.CLI/Program.cs
index 14229b3..43ae5e5 100644
--- a/src/SharpEmu.CLI/Program.cs
+++ b/src/SharpEmu.CLI/Program.cs
@@ -60,6 +60,7 @@ internal static partial class Program
// The executable uses the GUI subsystem, so CLI mode has to connect
// itself to a console before the first write.
EnsureCliConsole();
+ UseUtf8ConsoleOutput();
Console.Error.WriteLine($"[DEBUG] SharpEmu starting with {args.Length} args");
@@ -162,6 +163,48 @@ internal static partial class Program
RebindStdHandleToConsole(STD_ERROR_HANDLE);
}
+ ///
+ /// Makes console writes UTF-8 so the GUI's pipe reader (and any modern
+ /// terminal) decodes non-ASCII text correctly. Without this, redirected
+ /// output falls back to the OS ANSI code page and characters like "—"
+ /// arrive mangled.
+ ///
+ private static void UseUtf8ConsoleOutput()
+ {
+ try
+ {
+ // Also recreates the redirected Console.Out/Error writers with
+ // the new encoding.
+ Console.OutputEncoding = Encoding.UTF8;
+ return;
+ }
+ catch (Exception)
+ {
+ // No attached console (GUI-subsystem child with piped output):
+ // wrap the raw handles instead.
+ }
+
+ if (Console.IsOutputRedirected)
+ {
+ Console.SetOut(new StreamWriter(
+ Console.OpenStandardOutput(),
+ new UTF8Encoding(encoderShouldEmitUTF8Identifier: false))
+ {
+ AutoFlush = true,
+ });
+ }
+
+ if (Console.IsErrorRedirected)
+ {
+ Console.SetError(new StreamWriter(
+ Console.OpenStandardError(),
+ new UTF8Encoding(encoderShouldEmitUTF8Identifier: false))
+ {
+ AutoFlush = true,
+ });
+ }
+ }
+
private static void RebindStdHandleToConsole(int stdHandle)
{
if (IsHandleValid(GetStdHandle(stdHandle)) || GetConsoleWindow() == 0)
diff --git a/src/SharpEmu.GUI/Atrac9/Atrac9Config.cs b/src/SharpEmu.GUI/Atrac9/Atrac9Config.cs
new file mode 100644
index 0000000..8498fd8
--- /dev/null
+++ b/src/SharpEmu.GUI/Atrac9/Atrac9Config.cs
@@ -0,0 +1,120 @@
+#nullable disable
+using System.IO;
+using LibAtrac9.Utilities;
+
+namespace LibAtrac9
+{
+ ///
+ /// Stores the configuration data needed to decode or encode an ATRAC9 stream.
+ ///
+ public class Atrac9Config
+ {
+ ///
+ /// The 4-byte ATRAC9 configuration data.
+ ///
+ public byte[] ConfigData { get; }
+
+ ///
+ /// A 4-bit value specifying one of 16 sample rates.
+ ///
+ public int SampleRateIndex { get; }
+ ///
+ /// A 3-bit value specifying one of 6 substream channel mappings.
+ ///
+ public int ChannelConfigIndex { get; }
+ ///
+ /// An 11-bit value containing the average size of a single frame.
+ ///
+ public int FrameBytes { get; }
+ ///
+ /// A 2-bit value indicating how many frames are in each superframe.
+ ///
+ public int SuperframeIndex { get; }
+
+ ///
+ /// The channel mapping used by the ATRAC9 stream.
+ ///
+ public ChannelConfig ChannelConfig { get; }
+ ///
+ /// The total number of channels in the ATRAC9 stream.
+ ///
+ public int ChannelCount { get; }
+ ///
+ /// The sample rate of the ATRAC9 stream.
+ ///
+ public int SampleRate { get; }
+ ///
+ /// Indicates whether the ATRAC9 stream has a of 8 or above.
+ ///
+ public bool HighSampleRate { get; }
+
+ ///
+ /// The number of frames in each superframe.
+ ///
+ public int FramesPerSuperframe { get; }
+ ///
+ /// The number of samples in one frame as an exponent of 2.
+ /// = 2^.
+ ///
+ public int FrameSamplesPower { get; }
+ ///
+ /// The number of samples in one frame.
+ ///
+ public int FrameSamples { get; }
+ ///
+ /// The number of bytes in one superframe.
+ ///
+ public int SuperframeBytes { get; }
+ ///
+ /// The number of samples in one superframe.
+ ///
+ public int SuperframeSamples { get; }
+
+ ///
+ /// Reads ATRAC9 configuration data and calculates the stream parameters from it.
+ ///
+ /// The processed ATRAC9 configuration.
+ public Atrac9Config(byte[] configData)
+ {
+ if (configData == null || configData.Length != 4)
+ {
+ throw new InvalidDataException("Config data must be 4 bytes long");
+ }
+
+ ReadConfigData(configData, out int a, out int b, out int c, out int d);
+ SampleRateIndex = a;
+ ChannelConfigIndex = b;
+ FrameBytes = c;
+ SuperframeIndex = d;
+ ConfigData = configData;
+
+ FramesPerSuperframe = 1 << SuperframeIndex;
+ SuperframeBytes = FrameBytes << SuperframeIndex;
+ ChannelConfig = Tables.ChannelConfig[ChannelConfigIndex];
+
+ ChannelCount = ChannelConfig.ChannelCount;
+ SampleRate = Tables.SampleRates[SampleRateIndex];
+ HighSampleRate = SampleRateIndex > 7;
+ FrameSamplesPower = Tables.SamplingRateIndexToFrameSamplesPower[SampleRateIndex];
+ FrameSamples = 1 << FrameSamplesPower;
+ SuperframeSamples = FrameSamples * FramesPerSuperframe;
+ }
+
+ private static void ReadConfigData(byte[] configData, out int sampleRateIndex, out int channelConfigIndex, out int frameBytes, out int superframeIndex)
+ {
+ var reader = new BitReader(configData);
+
+ int header = reader.ReadInt(8);
+ sampleRateIndex = reader.ReadInt(4);
+ channelConfigIndex = reader.ReadInt(3);
+ int validationBit = reader.ReadInt(1);
+ frameBytes = reader.ReadInt(11) + 1;
+ superframeIndex = reader.ReadInt(2);
+
+ if (header != 0xFE || validationBit != 0)
+ {
+ throw new InvalidDataException("ATRAC9 Config Data is invalid");
+ }
+ }
+ }
+}
diff --git a/src/SharpEmu.GUI/Atrac9/Atrac9Decoder.cs b/src/SharpEmu.GUI/Atrac9/Atrac9Decoder.cs
new file mode 100644
index 0000000..e99d451
--- /dev/null
+++ b/src/SharpEmu.GUI/Atrac9/Atrac9Decoder.cs
@@ -0,0 +1,128 @@
+#nullable disable
+using System;
+using LibAtrac9.Utilities;
+
+namespace LibAtrac9
+{
+ ///
+ /// Decodes an ATRAC9 stream into 16-bit PCM.
+ ///
+ public class Atrac9Decoder
+ {
+ ///
+ /// The config data for the current ATRAC9 stream.
+ ///
+ public Atrac9Config Config { get; private set; }
+
+ private Frame Frame { get; set; }
+ private BitReader Reader { get; set; }
+ private bool _initialized;
+
+ ///
+ /// Sets up the decoder to decode an ATRAC9 stream based on the information in .
+ ///
+ /// A 4-byte value containing information about the ATRAC9 stream.
+ public void Initialize(byte[] configData)
+ {
+ Config = new Atrac9Config(configData);
+ Frame = new Frame(Config);
+ Reader = new BitReader(null);
+ _initialized = true;
+ }
+
+ ///
+ /// Decodes one superframe of ATRAC9 data.
+ ///
+ /// The ATRAC9 data to decode. The array must be at least
+ /// . bytes long.
+ /// A buffer that the decoded PCM data will be placed in.
+ /// The array must have dimensions of at least [.]
+ /// [.].
+ public void Decode(byte[] atrac9Data, short[][] pcmOut)
+ {
+ if (!_initialized) throw new InvalidOperationException("Decoder must be initialized before decoding.");
+
+ ValidateDecodeBuffers(atrac9Data, pcmOut);
+ Reader.SetBuffer(atrac9Data);
+ DecodeSuperFrame(pcmOut);
+ }
+
+ private void ValidateDecodeBuffers(byte[] atrac9Buffer, short[][] pcmBuffer)
+ {
+ if (atrac9Buffer == null) throw new ArgumentNullException(nameof(atrac9Buffer));
+ if (pcmBuffer == null) throw new ArgumentNullException(nameof(pcmBuffer));
+
+ if (atrac9Buffer.Length < Config.SuperframeBytes)
+ {
+ throw new ArgumentException("ATRAC9 buffer is too small");
+ }
+
+ if (pcmBuffer.Length < Config.ChannelCount)
+ {
+ throw new ArgumentException("PCM buffer is too small");
+ }
+
+ for (int i = 0; i < Config.ChannelCount; i++)
+ {
+ if (pcmBuffer[i]?.Length < Config.SuperframeSamples)
+ {
+ throw new ArgumentException("PCM buffer is too small");
+ }
+ }
+ }
+
+ private void DecodeSuperFrame(short[][] pcmOut)
+ {
+ for (int i = 0; i < Config.FramesPerSuperframe; i++)
+ {
+ Frame.FrameIndex = i;
+ DecodeFrame(Reader, Frame);
+ PcmFloatToShort(pcmOut, i * Config.FrameSamples);
+ Reader.AlignPosition(8);
+ }
+ }
+
+ private void PcmFloatToShort(short[][] pcmOut, int start)
+ {
+ int endSample = start + Config.FrameSamples;
+ int channelNum = 0;
+ foreach (Block block in Frame.Blocks)
+ {
+ foreach (Channel channel in block.Channels)
+ {
+ double[] pcmSrc = channel.Pcm;
+ short[] pcmDest = pcmOut[channelNum++];
+ for (int d = 0, s = start; s < endSample; d++, s++)
+ {
+ double sample = pcmSrc[d];
+ // Not using Math.Round because it's ~20x slower on 64-bit
+ int roundedSample = (int)Math.Floor(sample + 0.5);
+ pcmDest[s] = Helpers.Clamp16(roundedSample);
+ }
+ }
+ }
+ }
+
+ private static void DecodeFrame(BitReader reader, Frame frame)
+ {
+ Unpack.UnpackFrame(reader, frame);
+
+ foreach (Block block in frame.Blocks)
+ {
+ Quantization.DequantizeSpectra(block);
+ Stereo.ApplyIntensityStereo(block);
+ Quantization.ScaleSpectrum(block);
+ BandExtension.ApplyBandExtension(block);
+ ImdctBlock(block);
+ }
+ }
+
+ private static void ImdctBlock(Block block)
+ {
+ foreach (Channel channel in block.Channels)
+ {
+ channel.Mdct.RunImdct(channel.Spectra, channel.Pcm);
+ }
+ }
+ }
+}
diff --git a/src/SharpEmu.GUI/Atrac9/Atrac9Rng.cs b/src/SharpEmu.GUI/Atrac9/Atrac9Rng.cs
new file mode 100644
index 0000000..90b13c9
--- /dev/null
+++ b/src/SharpEmu.GUI/Atrac9/Atrac9Rng.cs
@@ -0,0 +1,34 @@
+#nullable disable
+namespace LibAtrac9
+{
+ ///
+ /// An Xorshift RNG used by the ATRAC9 codec
+ ///
+ internal class Atrac9Rng
+ {
+ private ushort _stateA;
+ private ushort _stateB;
+ private ushort _stateC;
+ private ushort _stateD;
+
+ public Atrac9Rng(ushort seed)
+ {
+ int startValue = 0x4D93 * (seed ^ (seed >> 14));
+
+ _stateA = (ushort)(3 - startValue);
+ _stateB = (ushort)(2 - startValue);
+ _stateC = (ushort)(1 - startValue);
+ _stateD = (ushort)(0 - startValue);
+ }
+
+ public ushort Next()
+ {
+ ushort t = (ushort)(_stateD ^ (_stateD << 5));
+ _stateD = _stateC;
+ _stateC = _stateB;
+ _stateB = _stateA;
+ _stateA = (ushort)(t ^ _stateA ^ ((t ^ (_stateA >> 5)) >> 4));
+ return _stateA;
+ }
+ }
+}
diff --git a/src/SharpEmu.GUI/Atrac9/BandExtension.cs b/src/SharpEmu.GUI/Atrac9/BandExtension.cs
new file mode 100644
index 0000000..57cc5b4
--- /dev/null
+++ b/src/SharpEmu.GUI/Atrac9/BandExtension.cs
@@ -0,0 +1,373 @@
+#nullable disable
+using System;
+
+namespace LibAtrac9
+{
+ internal static class BandExtension
+ {
+ public static void ApplyBandExtension(Block block)
+ {
+ if (!block.BandExtensionEnabled || !block.HasExtensionData) return;
+
+ foreach (Channel channel in block.Channels)
+ {
+ ApplyBandExtensionChannel(channel);
+ }
+ }
+
+ private static void ApplyBandExtensionChannel(Channel channel)
+ {
+ int groupAUnit = channel.Block.QuantizationUnitCount;
+ int[] scaleFactors = channel.ScaleFactors;
+ double[] spectra = channel.Spectra;
+ double[] scales = channel.BexScales;
+ int[] values = channel.BexValues;
+
+ GetBexBandInfo(out int bandCount, out int groupBUnit, out int groupCUnit, groupAUnit);
+ int totalUnits = Math.Max(groupCUnit, 22);
+
+ int groupABin = Tables.QuantUnitToCoeffIndex[groupAUnit];
+ int groupBBin = Tables.QuantUnitToCoeffIndex[groupBUnit];
+ int groupCBin = Tables.QuantUnitToCoeffIndex[groupCUnit];
+ int totalBins = Tables.QuantUnitToCoeffIndex[totalUnits];
+
+ FillHighFrequencies(spectra, groupABin, groupBBin, groupCBin, totalBins);
+
+ switch (channel.BexMode)
+ {
+ case 0:
+ int bexQuantUnits = totalUnits - groupAUnit;
+
+ switch (bandCount)
+ {
+ case 3:
+ scales[0] = BexMode0Bands3[0][values[0]];
+ scales[1] = BexMode0Bands3[1][values[0]];
+ scales[2] = BexMode0Bands3[2][values[1]];
+ scales[3] = BexMode0Bands3[3][values[2]];
+ scales[4] = BexMode0Bands3[4][values[3]];
+ break;
+ case 4:
+ scales[0] = BexMode0Bands4[0][values[0]];
+ scales[1] = BexMode0Bands4[1][values[0]];
+ scales[2] = BexMode0Bands4[2][values[1]];
+ scales[3] = BexMode0Bands4[3][values[2]];
+ scales[4] = BexMode0Bands4[4][values[3]];
+ break;
+ case 5:
+ scales[0] = BexMode0Bands5[0][values[0]];
+ scales[1] = BexMode0Bands5[1][values[1]];
+ scales[2] = BexMode0Bands5[2][values[1]];
+ break;
+ }
+
+ scales[bexQuantUnits - 1] = Tables.SpectrumScale[scaleFactors[groupAUnit]];
+
+ AddNoiseToSpectrum(channel, Tables.QuantUnitToCoeffIndex[totalUnits - 1],
+ Tables.QuantUnitToCoeffCount[totalUnits - 1]);
+ ScaleBexQuantUnits(spectra, scales, groupAUnit, totalUnits);
+ break;
+ case 1:
+ for (int i = groupAUnit; i < totalUnits; i++)
+ {
+ scales[i - groupAUnit] = Tables.SpectrumScale[scaleFactors[i]];
+ }
+
+ AddNoiseToSpectrum(channel, groupABin, totalBins - groupABin);
+ ScaleBexQuantUnits(spectra, scales, groupAUnit, totalUnits);
+ break;
+ case 2:
+ double groupAScale2 = BexMode2Scale[values[0]];
+ double groupBScale2 = BexMode2Scale[values[1]];
+
+ for (int i = groupABin; i < groupBBin; i++)
+ {
+ spectra[i] *= groupAScale2;
+ }
+
+ for (int i = groupBBin; i < groupCBin; i++)
+ {
+ spectra[i] *= groupBScale2;
+ }
+ return;
+ case 3:
+ double rate = Math.Pow(2, BexMode3Rate[values[1]]);
+ double scale = BexMode3Initial[values[0]];
+ for (int i = groupABin; i < totalBins; i++)
+ {
+ scale *= rate;
+ spectra[i] *= scale;
+ }
+ return;
+ case 4:
+ double mult = BexMode4Multiplier[values[0]];
+ double groupAScale4 = 0.7079468 * mult;
+ double groupBScale4 = 0.5011902 * mult;
+ double groupCScale4 = 0.3548279 * mult;
+
+ for (int i = groupABin; i < groupBBin; i++)
+ {
+ spectra[i] *= groupAScale4;
+ }
+
+ for (int i = groupBBin; i < groupCBin; i++)
+ {
+ spectra[i] *= groupBScale4;
+ }
+
+ for (int i = groupCBin; i < totalBins; i++)
+ {
+ spectra[i] *= groupCScale4;
+ }
+ return;
+ }
+ }
+
+ private static void ScaleBexQuantUnits(double[] spectra, double[] scales, int startUnit, int totalUnits)
+ {
+ for (int i = startUnit; i < totalUnits; i++)
+ {
+ for (int k = Tables.QuantUnitToCoeffIndex[i]; k < Tables.QuantUnitToCoeffIndex[i + 1]; k++)
+ {
+ spectra[k] *= scales[i - startUnit];
+ }
+ }
+ }
+
+ private static void FillHighFrequencies(double[] spectra, int groupABin, int groupBBin, int groupCBin, int totalBins)
+ {
+ for (int i = 0; i < groupBBin - groupABin; i++)
+ {
+ spectra[groupABin + i] = spectra[groupABin - i - 1];
+ }
+
+ for (int i = 0; i < groupCBin - groupBBin; i++)
+ {
+ spectra[groupBBin + i] = spectra[groupBBin - i - 1];
+ }
+
+ for (int i = 0; i < totalBins - groupCBin; i++)
+ {
+ spectra[groupCBin + i] = spectra[groupCBin - i - 1];
+ }
+ }
+
+ private static void AddNoiseToSpectrum(Channel channel, int index, int count)
+ {
+ if (channel.Rng == null)
+ {
+ int[] sf = channel.ScaleFactors;
+ ushort seed = (ushort)(543 * (sf[8] + sf[12] + sf[15] + 1));
+ channel.Rng = new Atrac9Rng(seed);
+ }
+ for (int i = 0; i < count; i++)
+ {
+ channel.Spectra[i + index] = channel.Rng.Next() / 65535.0 * 2.0 - 1.0;
+ }
+ }
+
+ public static void GetBexBandInfo(out int bandCount, out int groupAUnit, out int groupBUnit, int quantUnits)
+ {
+ groupAUnit = BexGroupInfo[quantUnits - 13][0];
+ groupBUnit = BexGroupInfo[quantUnits - 13][1];
+ bandCount = BexGroupInfo[quantUnits - 13][2];
+ }
+
+ public static readonly byte[][] BexGroupInfo =
+ {
+ new byte[] {16, 21, 0},
+ new byte[] {18, 22, 1},
+ new byte[] {20, 22, 2},
+ new byte[] {21, 22, 3},
+ new byte[] {21, 22, 3},
+ new byte[] {23, 24, 4},
+ new byte[] {23, 24, 4},
+ new byte[] {24, 24, 5}
+ };
+
+ // [mode][bands]
+
+ public static readonly byte[][] BexEncodedValueCounts =
+ {
+ new byte[] {0, 0, 0, 4, 4, 2},
+ new byte[] {0, 0, 0, 0, 0, 0},
+ new byte[] {0, 0, 0, 2, 2, 1},
+ new byte[] {0, 0, 0, 2, 2, 2},
+ new byte[] {1, 1, 1, 0, 0, 0}
+ };
+
+ // [mode][bands][valueIndex]
+
+ public static readonly byte[][][] BexDataLengths =
+ {
+ new[] {
+ new byte[] {0, 0, 0, 0},
+ new byte[] {0, 0, 0, 0},
+ new byte[] {0, 0, 0, 0},
+ new byte[] {5, 4, 3, 3},
+ new byte[] {4, 4, 3, 4},
+ new byte[] {4, 5, 0, 0}
+ }, new[] {
+ new byte[] {0, 0, 0, 0},
+ new byte[] {0, 0, 0, 0},
+ new byte[] {0, 0, 0, 0},
+ new byte[] {0, 0, 0, 0},
+ new byte[] {0, 0, 0, 0},
+ new byte[] {0, 0, 0, 0}
+ }, new[] {
+ new byte[] {0, 0, 0, 0},
+ new byte[] {0, 0, 0, 0},
+ new byte[] {0, 0, 0, 0},
+ new byte[] {6, 6, 0, 0},
+ new byte[] {6, 6, 0, 0},
+ new byte[] {6, 0, 0, 0}
+ }, new[] {
+ new byte[] {0, 0, 0, 0},
+ new byte[] {0, 0, 0, 0},
+ new byte[] {0, 0, 0, 0},
+ new byte[] {4, 4, 0, 0},
+ new byte[] {4, 4, 0, 0},
+ new byte[] {4, 4, 0, 0}
+ }, new[] {
+ new byte[] {3, 0, 0, 0},
+ new byte[] {3, 0, 0, 0},
+ new byte[] {3, 0, 0, 0},
+ new byte[] {0, 0, 0, 0},
+ new byte[] {0, 0, 0, 0},
+ new byte[] {0, 0, 0, 0}
+ }
+ };
+
+ public static readonly double[][] BexMode0Bands3 =
+ {
+ new[] {
+ 0.000000e+0, 1.988220e-1, 2.514343e-1, 2.960510e-1,
+ 3.263550e-1, 3.771362e-1, 3.786926e-1, 4.540405e-1,
+ 4.877625e-1, 5.262451e-1, 5.447083e-1, 5.737000e-1,
+ 6.212158e-1, 6.222839e-1, 6.560974e-1, 6.896667e-1,
+ 7.555542e-1, 7.677917e-1, 7.918091e-1, 7.971497e-1,
+ 8.188171e-1, 8.446045e-1, 9.790649e-1, 9.822083e-1,
+ 9.846191e-1, 9.859314e-1, 9.863586e-1, 9.863892e-1,
+ 9.873352e-1, 9.881287e-1, 9.898682e-1, 9.913330e-1
+ }, new[] {
+ 0.000000e+0, 9.982910e-1, 7.592773e-2, 7.179565e-1,
+ 9.851379e-1, 5.340271e-1, 9.013672e-1, 6.349182e-1,
+ 7.226257e-1, 1.948547e-1, 7.628174e-1, 9.873657e-1,
+ 8.112183e-1, 2.715454e-1, 9.734192e-1, 1.443787e-1,
+ 4.640198e-1, 3.249207e-1, 3.790894e-1, 8.276367e-2,
+ 5.954590e-1, 2.864380e-1, 9.806824e-1, 7.929077e-1,
+ 6.292114e-1, 4.887085e-1, 2.905273e-1, 1.301880e-1,
+ 3.140869e-1, 5.482483e-1, 4.210815e-1, 1.182861e-1
+ }, new[] {
+ 0.000000e+0, 3.155518e-2, 8.581543e-2, 1.364746e-1,
+ 1.858826e-1, 2.368469e-1, 2.888184e-1, 3.432617e-1,
+ 4.012451e-1, 4.623108e-1, 5.271301e-1, 5.954895e-1,
+ 6.681213e-1, 7.448425e-1, 8.245239e-1, 9.097290e-1
+ }, new[] {
+ 0.000000e+0, 4.418945e-2, 1.303711e-1, 2.273560e-1,
+ 3.395996e-1, 4.735718e-1, 6.267090e-1, 8.003845e-1
+ }, new[] {
+ 0.000000e+0, 2.804565e-2, 9.683228e-2, 1.849976e-1,
+ 3.005981e-1, 4.470520e-1, 6.168518e-1, 8.007813e-1
+ }
+ };
+
+ public static readonly double[][] BexMode0Bands4 =
+ {
+ new[] {
+ 0.000000e+0, 2.708740e-1, 3.479614e-1, 3.578186e-1,
+ 5.083618e-1, 5.299072e-1, 5.819092e-1, 6.381836e-1,
+ 7.276917e-1, 7.595520e-1, 7.878723e-1, 9.707336e-1,
+ 9.713135e-1, 9.736023e-1, 9.759827e-1, 9.832458e-1
+ }, new[] {
+ 0.000000e+0, 2.330627e-1, 5.891418e-1, 7.170410e-1,
+ 2.036438e-1, 1.613464e-1, 6.668701e-1, 9.481201e-1,
+ 9.769897e-1, 5.111694e-1, 3.522644e-1, 8.209534e-1,
+ 2.933960e-1, 9.757690e-1, 5.289917e-1, 4.372253e-1
+ }, new[] {
+ 0.000000e+0, 4.360962e-2, 1.056519e-1, 1.590576e-1,
+ 2.078857e-1, 2.572937e-1, 3.082581e-1, 3.616028e-1,
+ 4.191589e-1, 4.792175e-1, 5.438538e-1, 6.125183e-1,
+ 6.841125e-1, 7.589417e-1, 8.365173e-1, 9.148254e-1
+ }, new[] {
+ 0.000000e+0, 4.074097e-2, 1.164551e-1, 2.077026e-1,
+ 3.184509e-1, 4.532166e-1, 6.124268e-1, 7.932129e-1
+ }, new[] {
+ 0.000000e+0, 8.880615e-3, 2.932739e-2, 5.593872e-2,
+ 8.825684e-2, 1.259155e-1, 1.721497e-1, 2.270813e-1,
+ 2.901611e-1, 3.579712e-1, 4.334106e-1, 5.147095e-1,
+ 6.023254e-1, 6.956177e-1, 7.952881e-1, 8.977356e-1
+ }
+ };
+
+ public static readonly double[][] BexMode0Bands5 =
+ {
+ new[] {
+ 0.000000e+0, 7.379150e-2, 1.806335e-1, 2.687073e-1,
+ 3.407898e-1, 4.047546e-1, 4.621887e-1, 5.168762e-1,
+ 5.703125e-1, 6.237488e-1, 6.763611e-1, 7.288208e-1,
+ 7.808533e-1, 8.337708e-1, 8.874512e-1, 9.418030e-1
+ }, new[] {
+ 0.000000e+0, 7.980347e-2, 1.615295e-1, 1.665649e-1,
+ 1.822205e-1, 2.185669e-1, 2.292175e-1, 2.456665e-1,
+ 2.666321e-1, 3.306580e-1, 3.330688e-1, 3.765259e-1,
+ 4.085083e-1, 4.400024e-1, 4.407654e-1, 4.817505e-1,
+ 4.924011e-1, 5.320740e-1, 5.893860e-1, 6.131287e-1,
+ 6.212463e-1, 6.278076e-1, 6.308899e-1, 7.660828e-1,
+ 7.850647e-1, 7.910461e-1, 7.929382e-1, 8.038330e-1,
+ 9.834900e-1, 9.846191e-1, 9.852295e-1, 9.862671e-1
+ }, new[] {
+ 0.000000e+0, 6.084290e-1, 3.672791e-1, 3.151855e-1,
+ 1.488953e-1, 2.571716e-1, 5.103455e-1, 3.311157e-1,
+ 5.426025e-2, 4.254456e-1, 7.998352e-1, 7.873230e-1,
+ 5.418701e-1, 2.925110e-1, 8.468628e-2, 1.410522e-1,
+ 9.819641e-1, 9.609070e-1, 3.530884e-2, 9.729004e-2,
+ 5.758362e-1, 9.941711e-1, 7.215576e-1, 7.183228e-1,
+ 2.028809e-1, 9.588623e-2, 2.032166e-1, 1.338806e-1,
+ 5.003357e-1, 1.874390e-1, 9.804993e-1, 1.107788e-1
+ }
+ };
+
+ public static readonly double[] BexMode2Scale =
+ {
+ 4.272461e-4, 1.312256e-3, 2.441406e-3, 3.692627e-3,
+ 4.913330e-3, 6.134033e-3, 7.507324e-3, 8.972168e-3,
+ 1.049805e-2, 1.223755e-2, 1.406860e-2, 1.599121e-2,
+ 1.800537e-2, 2.026367e-2, 2.264404e-2, 2.517700e-2,
+ 2.792358e-2, 3.073120e-2, 3.344727e-2, 3.631592e-2,
+ 3.952026e-2, 4.275513e-2, 4.608154e-2, 4.968262e-2,
+ 5.355835e-2, 5.783081e-2, 6.195068e-2, 6.677246e-2,
+ 7.196045e-2, 7.745361e-2, 8.319092e-2, 8.993530e-2,
+ 9.759521e-2, 1.056213e-1, 1.138916e-1, 1.236267e-1,
+ 1.348267e-1, 1.470337e-1, 1.603394e-1, 1.755676e-1,
+ 1.905823e-1, 2.071228e-1, 2.245178e-1, 2.444153e-1,
+ 2.658997e-1, 2.897644e-1, 3.146057e-1, 3.450012e-1,
+ 3.766174e-1, 4.122620e-1, 4.505615e-1, 4.893799e-1,
+ 5.305481e-1, 5.731201e-1, 6.157837e-1, 6.580811e-1,
+ 6.985168e-1, 7.435303e-1, 7.865906e-1, 8.302612e-1,
+ 8.718567e-1, 9.125671e-1, 9.575806e-1, 9.996643e-1
+ };
+
+ public static readonly double[] BexMode3Initial =
+ {
+ 3.491211e-1, 5.371094e-1, 6.782227e-1, 7.910156e-1,
+ 9.057617e-1, 1.024902e+0, 1.156250e+0, 1.290527e+0,
+ 1.458984e+0, 1.664551e+0, 1.929688e+0, 2.278320e+0,
+ 2.831543e+0, 3.659180e+0, 5.257813e+0, 8.373047e+0
+ };
+
+ public static readonly double[] BexMode3Rate =
+ {
+ -2.913818e-1, -2.541504e-1, -1.664429e-1, -1.476440e-1,
+ -1.342163e-1, -1.220703e-1, -1.117554e-1, -1.026611e-1,
+ -9.436035e-2, -8.483887e-2, -7.476807e-2, -6.304932e-2,
+ -4.492188e-2, -2.447510e-2, +1.831055e-4, +4.174805e-2
+ };
+
+ public static readonly double[] BexMode4Multiplier =
+ {
+ 3.610229e-2, 1.260681e-1, 2.227478e-1, 3.338318e-1,
+ 4.662170e-1, 6.221313e-1, 7.989197e-1, 9.939575e-1
+ };
+ }
+}
diff --git a/src/SharpEmu.GUI/Atrac9/BitAllocation.cs b/src/SharpEmu.GUI/Atrac9/BitAllocation.cs
new file mode 100644
index 0000000..f178d8c
--- /dev/null
+++ b/src/SharpEmu.GUI/Atrac9/BitAllocation.cs
@@ -0,0 +1,141 @@
+#nullable disable
+using System;
+
+namespace LibAtrac9
+{
+ internal static class BitAllocation
+ {
+ public static void CreateGradient(Block block)
+ {
+ int valueCount = block.GradientEndValue - block.GradientStartValue;
+ int unitCount = block.GradientEndUnit - block.GradientStartUnit;
+
+ for (int i = 0; i < block.GradientEndUnit; i++)
+ {
+ block.Gradient[i] = block.GradientStartValue;
+ }
+
+ for (int i = block.GradientEndUnit; i <= block.QuantizationUnitCount; i++)
+ {
+ block.Gradient[i] = block.GradientEndValue;
+ }
+ if (unitCount <= 0) return;
+ if (valueCount == 0) return;
+
+ byte[] curve = Tables.GradientCurves[unitCount - 1];
+ if (valueCount <= 0)
+ {
+ double scale = (-valueCount - 1) / 31.0;
+ int baseVal = block.GradientStartValue - 1;
+ for (int i = block.GradientStartUnit; i < block.GradientEndUnit; i++)
+ {
+ block.Gradient[i] = baseVal - (int)(curve[i - block.GradientStartUnit] * scale);
+ }
+ }
+ else
+ {
+ double scale = (valueCount - 1) / 31.0;
+ int baseVal = block.GradientStartValue + 1;
+ for (int i = block.GradientStartUnit; i < block.GradientEndUnit; i++)
+ {
+ block.Gradient[i] = baseVal + (int)(curve[i - block.GradientStartUnit] * scale);
+ }
+ }
+ }
+
+ public static void CalculateMask(Channel channel)
+ {
+ Array.Clear(channel.PrecisionMask, 0, channel.PrecisionMask.Length);
+ for (int i = 1; i < channel.Block.QuantizationUnitCount; i++)
+ {
+ int delta = channel.ScaleFactors[i] - channel.ScaleFactors[i - 1];
+ if (delta > 1)
+ {
+ channel.PrecisionMask[i] += Math.Min(delta - 1, 5);
+ }
+ else if (delta < -1)
+ {
+ channel.PrecisionMask[i - 1] += Math.Min(delta * -1 - 1, 5);
+ }
+ }
+ }
+
+ public static void CalculatePrecisions(Channel channel)
+ {
+ Block block = channel.Block;
+
+ if (block.GradientMode != 0)
+ {
+ for (int i = 0; i < block.QuantizationUnitCount; i++)
+ {
+ channel.Precisions[i] = channel.ScaleFactors[i] + channel.PrecisionMask[i] - block.Gradient[i];
+ if (channel.Precisions[i] > 0)
+ {
+ switch (block.GradientMode)
+ {
+ case 1:
+ channel.Precisions[i] /= 2;
+ break;
+ case 2:
+ channel.Precisions[i] = 3 * channel.Precisions[i] / 8;
+ break;
+ case 3:
+ channel.Precisions[i] /= 4;
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ for (int i = 0; i < block.QuantizationUnitCount; i++)
+ {
+ channel.Precisions[i] = channel.ScaleFactors[i] - block.Gradient[i];
+ }
+ }
+
+ for (int i = 0; i < block.QuantizationUnitCount; i++)
+ {
+ if (channel.Precisions[i] < 1)
+ {
+ channel.Precisions[i] = 1;
+ }
+ }
+
+ for (int i = 0; i < block.GradientBoundary; i++)
+ {
+ channel.Precisions[i]++;
+ }
+
+ for (int i = 0; i < block.QuantizationUnitCount; i++)
+ {
+ channel.PrecisionsFine[i] = 0;
+ if (channel.Precisions[i] > 15)
+ {
+ channel.PrecisionsFine[i] = channel.Precisions[i] - 15;
+ channel.Precisions[i] = 15;
+ }
+ }
+ }
+
+ public static byte[][] GenerateGradientCurves()
+ {
+ byte[] main =
+ {
+ 01, 01, 01, 01, 02, 02, 02, 02, 03, 03, 03, 04, 04, 05, 05, 06, 07, 08, 09, 10, 11, 12, 13, 15,
+ 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, 26, 27, 27, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30
+ };
+ var curves = new byte[main.Length][];
+
+ for (int length = 1; length <= main.Length; length++)
+ {
+ curves[length - 1] = new byte[length];
+ for (int i = 0; i < length; i++)
+ {
+ curves[length - 1][i] = main[i * main.Length / length];
+ }
+ }
+ return curves;
+ }
+ }
+}
diff --git a/src/SharpEmu.GUI/Atrac9/Block.cs b/src/SharpEmu.GUI/Atrac9/Block.cs
new file mode 100644
index 0000000..e267578
--- /dev/null
+++ b/src/SharpEmu.GUI/Atrac9/Block.cs
@@ -0,0 +1,92 @@
+#nullable disable
+namespace LibAtrac9
+{
+ internal class Block
+ {
+ public Atrac9Config Config { get; }
+ public BlockType BlockType { get; }
+ public int BlockIndex { get; }
+ public Frame Frame { get; }
+
+ public Channel[] Channels { get; }
+ public int ChannelCount { get; }
+
+ public bool FirstInSuperframe { get; set; }
+ public bool ReuseBandParams { get; set; }
+
+ public int BandCount { get; set; }
+ public int StereoBand { get; set; }
+ public int ExtensionBand { get; set; }
+ public int QuantizationUnitCount { get; set; }
+ public int StereoQuantizationUnit { get; set; }
+ public int ExtensionUnit { get; set; }
+ public int QuantizationUnitsPrev { get; set; }
+
+ public int[] Gradient { get; } = new int[31];
+ public int GradientMode { get; set; }
+ public int GradientStartUnit { get; set; }
+ public int GradientStartValue { get; set; }
+ public int GradientEndUnit { get; set; }
+ public int GradientEndValue { get; set; }
+ public int GradientBoundary { get; set; }
+
+ public int PrimaryChannelIndex { get; set; }
+ public int[] JointStereoSigns { get; } = new int[30];
+ public bool HasJointStereoSigns { get; set; }
+ public Channel PrimaryChannel => Channels[PrimaryChannelIndex == 0 ? 0 : 1];
+ public Channel SecondaryChannel => Channels[PrimaryChannelIndex == 0 ? 1 : 0];
+
+ public bool BandExtensionEnabled { get; set; }
+ public bool HasExtensionData { get; set; }
+ public int BexDataLength { get; set; }
+ public int BexMode { get; set; }
+
+ public Block(Frame parentFrame, int blockIndex)
+ {
+ Frame = parentFrame;
+ BlockIndex = blockIndex;
+ Config = parentFrame.Config;
+ BlockType = Config.ChannelConfig.BlockTypes[blockIndex];
+ ChannelCount = BlockTypeToChannelCount(BlockType);
+ Channels = new Channel[ChannelCount];
+ for (int i = 0; i < ChannelCount; i++)
+ {
+ Channels[i] = new Channel(this, i);
+ }
+ }
+
+ public static int BlockTypeToChannelCount(BlockType blockType)
+ {
+ switch (blockType)
+ {
+ case BlockType.Mono:
+ return 1;
+ case BlockType.Stereo:
+ return 2;
+ case BlockType.LFE:
+ return 1;
+ default:
+ return 0;
+ }
+ }
+ }
+
+ ///
+ /// An ATRAC9 block (substream) type
+ ///
+ public enum BlockType
+ {
+ ///
+ /// Mono ATRAC9 block
+ ///
+ Mono = 0,
+ ///
+ /// Stereo ATRAC9 block
+ ///
+ Stereo = 1,
+ ///
+ /// Low-frequency effects ATRAC9 block
+ ///
+ LFE = 2
+ }
+}
diff --git a/src/SharpEmu.GUI/Atrac9/Channel.cs b/src/SharpEmu.GUI/Atrac9/Channel.cs
new file mode 100644
index 0000000..80d80b1
--- /dev/null
+++ b/src/SharpEmu.GUI/Atrac9/Channel.cs
@@ -0,0 +1,49 @@
+#nullable disable
+using LibAtrac9.Utilities;
+
+namespace LibAtrac9
+{
+ internal class Channel
+ {
+ public Atrac9Config Config { get; }
+ public int ChannelIndex { get; }
+ public bool IsPrimary => Block.PrimaryChannelIndex == ChannelIndex;
+ public Block Block { get; }
+ public Mdct Mdct { get; }
+
+ public double[] Pcm { get; } = new double[256];
+ public double[] Spectra { get; } = new double[256];
+
+ public int CodedQuantUnits { get; set; }
+ public int ScaleFactorCodingMode { get; set; }
+ public int[] ScaleFactors { get; } = new int[31];
+ public int[] ScaleFactorsPrev { get; } = new int[31];
+
+ public int[] Precisions { get; } = new int[30];
+ public int[] PrecisionsFine { get; } = new int[30];
+ public int[] PrecisionMask { get; } = new int[30];
+
+ public int[] SpectraValuesBuffer { get; } = new int[16];
+ public int[] CodebookSet { get; } = new int[30];
+
+ public int[] QuantizedSpectra { get; } = new int[256];
+ public int[] QuantizedSpectraFine { get; } = new int[256];
+
+ public int BexMode { get; set; }
+ public int BexValueCount { get; set; }
+ public int[] BexValues { get; } = new int[4];
+ public double[] BexScales { get; } = new double[6];
+ public Atrac9Rng Rng { get; set; }
+
+ public Channel(Block parentBlock, int channelIndex)
+ {
+ Block = parentBlock;
+ ChannelIndex = channelIndex;
+ Config = parentBlock.Config;
+ Mdct = new Mdct(Config.FrameSamplesPower, Tables.ImdctWindow[Config.FrameSamplesPower - 6]);
+ }
+
+ public void UpdateCodedUnits() =>
+ CodedQuantUnits = IsPrimary ? Block.QuantizationUnitCount : Block.StereoQuantizationUnit;
+ }
+}
diff --git a/src/SharpEmu.GUI/Atrac9/ChannelConfig.cs b/src/SharpEmu.GUI/Atrac9/ChannelConfig.cs
new file mode 100644
index 0000000..b3de145
--- /dev/null
+++ b/src/SharpEmu.GUI/Atrac9/ChannelConfig.cs
@@ -0,0 +1,34 @@
+#nullable disable
+namespace LibAtrac9
+{
+ ///
+ /// Describes the channel mapping for an ATRAC9 stream
+ ///
+ public class ChannelConfig
+ {
+ internal ChannelConfig(params BlockType[] blockTypes)
+ {
+ BlockCount = blockTypes.Length;
+ BlockTypes = blockTypes;
+ foreach (BlockType type in blockTypes)
+ {
+ ChannelCount += Block.BlockTypeToChannelCount(type);
+ }
+ }
+
+ ///
+ /// The number of blocks or substreams in the ATRAC9 stream
+ ///
+ public int BlockCount { get; }
+
+ ///
+ /// The type of each block or substream in the ATRAC9 stream
+ ///
+ public BlockType[] BlockTypes { get; }
+
+ ///
+ /// The number of channels in the ATRAC9 stream
+ ///
+ public int ChannelCount { get; }
+ }
+}
diff --git a/src/SharpEmu.GUI/Atrac9/Frame.cs b/src/SharpEmu.GUI/Atrac9/Frame.cs
new file mode 100644
index 0000000..73e0b77
--- /dev/null
+++ b/src/SharpEmu.GUI/Atrac9/Frame.cs
@@ -0,0 +1,21 @@
+#nullable disable
+namespace LibAtrac9
+{
+ internal class Frame
+ {
+ public Atrac9Config Config { get; }
+ public int FrameIndex { get; set; }
+ public Block[] Blocks { get; }
+
+ public Frame(Atrac9Config config)
+ {
+ Config = config;
+ Blocks = new Block[config.ChannelConfig.BlockCount];
+
+ for (int i = 0; i < config.ChannelConfig.BlockCount; i++)
+ {
+ Blocks[i] = new Block(this, i);
+ }
+ }
+ }
+}
diff --git a/src/SharpEmu.GUI/Atrac9/HuffmanCodebook.cs b/src/SharpEmu.GUI/Atrac9/HuffmanCodebook.cs
new file mode 100644
index 0000000..dcd8a40
--- /dev/null
+++ b/src/SharpEmu.GUI/Atrac9/HuffmanCodebook.cs
@@ -0,0 +1,63 @@
+#nullable disable
+using System;
+using LibAtrac9.Utilities;
+
+namespace LibAtrac9
+{
+ internal class HuffmanCodebook
+ {
+ public HuffmanCodebook(short[] codes, byte[] bits, byte valueCountPower)
+ {
+ Codes = codes;
+ Bits = bits;
+ if (Codes == null || Bits == null) return;
+
+ ValueCount = 1 << valueCountPower;
+ ValueCountPower = valueCountPower;
+ ValueBits = Helpers.Log2(codes.Length) >> valueCountPower;
+ ValueMax = 1 << ValueBits;
+
+ int max = 0;
+ foreach (byte bitSize in bits)
+ {
+ max = Math.Max(max, bitSize);
+ }
+
+ MaxBitSize = max;
+ Lookup = CreateLookupTable();
+ }
+
+ private byte[] CreateLookupTable()
+ {
+ if (Codes == null || Bits == null) return null;
+
+ int tableSize = 1 << MaxBitSize;
+ var dest = new byte[tableSize];
+
+ for (int i = 0; i < Bits.Length; i++)
+ {
+ if (Bits[i] == 0) continue;
+ int unusedBits = MaxBitSize - Bits[i];
+
+ int start = Codes[i] << unusedBits;
+ int length = 1 << unusedBits;
+ int end = start + length;
+
+ for (int j = start; j < end; j++)
+ {
+ dest[j] = (byte)i;
+ }
+ }
+ return dest;
+ }
+
+ public short[] Codes { get; }
+ public byte[] Bits { get; }
+ public byte[] Lookup { get; }
+ public int ValueCount { get; }
+ public int ValueCountPower { get; }
+ public int ValueBits { get; }
+ public int ValueMax { get; }
+ public int MaxBitSize { get; }
+ }
+}
diff --git a/src/SharpEmu.GUI/Atrac9/HuffmanCodebooks.cs b/src/SharpEmu.GUI/Atrac9/HuffmanCodebooks.cs
new file mode 100644
index 0000000..80c78a1
--- /dev/null
+++ b/src/SharpEmu.GUI/Atrac9/HuffmanCodebooks.cs
@@ -0,0 +1,1353 @@
+#nullable disable
+namespace LibAtrac9
+{
+ internal static class HuffmanCodebooks
+ {
+ public static HuffmanCodebook[][] GenerateHuffmanCodebooks(short[][][] codes, byte[][][] bits, byte[][] groupCounts)
+ {
+ var tables = new HuffmanCodebook[bits.Length][];
+ for (int i = 0; i < tables.Length; i++)
+ {
+ if (codes[i] != null)
+ {
+ tables[i] = GenerateHuffmanCodebooks(codes[i], bits[i], groupCounts[i]);
+ }
+ }
+ return tables;
+ }
+
+ public static HuffmanCodebook[] GenerateHuffmanCodebooks(short[][] codes, byte[][] bits, byte[] groupCounts)
+ {
+ var tables = new HuffmanCodebook[bits.Length];
+ for (int i = 0; i < tables.Length; i++)
+ {
+ if (codes[i] != null)
+ {
+ tables[i] = new HuffmanCodebook(codes[i], bits[i], groupCounts[i]);
+ }
+ }
+ return tables;
+ }
+
+ // For scale factor table names, {letter}{number} correspond to the signedness and word length
+ private static readonly byte[] ScaleFactorsA1Bits =
+ {
+ 1, 1
+ };
+
+ private static readonly short[] ScaleFactorsA1Codes =
+ {
+ 0x00, 0x01
+ };
+
+ private static readonly byte[] ScaleFactorsA2Bits =
+ {
+ 1, 3, 3, 2
+ };
+
+ private static readonly short[] ScaleFactorsA2Codes =
+ {
+ 0x00, 0x06, 0x07, 0x02
+ };
+
+ private static readonly byte[] ScaleFactorsA3Bits =
+ {
+ 2, 2, 4, 6, 6, 5, 3, 2
+ };
+
+ private static readonly short[] ScaleFactorsA3Codes =
+ {
+ 0x00, 0x01, 0x0E, 0x3E, 0x3F, 0x1E, 0x06, 0x02
+ };
+
+ private static readonly byte[] ScaleFactorsA4Bits =
+ {
+ 2, 2, 4, 5, 6, 7, 8, 8, 8, 8, 8, 8, 6, 5, 4, 2
+ };
+
+ private static readonly short[] ScaleFactorsA4Codes =
+ {
+ 0x01, 0x02, 0x00, 0x06, 0x0F, 0x13, 0x23, 0x24, 0x25, 0x22, 0x21, 0x20, 0x0E, 0x05, 0x01, 0x03
+ };
+
+ private static readonly byte[] ScaleFactorsA5Bits =
+ {
+ 2, 3, 3, 4, 5, 5, 6, 7, 7, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 7, 6, 5, 5, 4, 3
+ };
+
+ private static readonly short[] ScaleFactorsA5Codes =
+ {
+ 0x02, 0x01, 0x07, 0x0D, 0x0C, 0x18, 0x1B, 0x21, 0x3F, 0x6A, 0x6B, 0x68, 0x73, 0x79, 0x7C, 0x7D,
+ 0x7A, 0x7B, 0x78, 0x72, 0x44, 0x45, 0x47, 0x46, 0x69, 0x38, 0x20, 0x1D, 0x19, 0x09, 0x05, 0x00
+ };
+
+ private static readonly byte[] ScaleFactorsA6Bits =
+ {
+ 3, 3, 4, 4, 5, 5, 6, 6, 6, 7, 7, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 7, 7, 7, 6, 6, 5, 5, 5, 4, 4, 4
+ };
+
+ private static readonly short[] ScaleFactorsA6Codes =
+ {
+ 0x00, 0x01, 0x04, 0x05, 0x12, 0x13, 0x2E, 0x2F, 0x30, 0x66, 0x67, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA,
+ 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA,
+ 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA,
+ 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x68, 0x69, 0x6A, 0x31, 0x32, 0x14, 0x15, 0x16, 0x06, 0x07, 0x08
+ };
+
+ private static readonly byte[] ScaleFactorsB2Bits =
+ {
+ 1, 2, 0, 2
+ };
+
+ private static readonly short[] ScaleFactorsB2Codes =
+ {
+ 0x00, 0x03, 0x00, 0x02
+ };
+
+ private static readonly byte[] ScaleFactorsB3Bits =
+ {
+ 1, 3, 5, 6, 0, 6, 4, 2
+ };
+
+ private static readonly short[] ScaleFactorsB3Codes =
+ {
+ 0x01, 0x00, 0x04, 0x0B, 0x00, 0x0A, 0x03, 0x01
+ };
+
+ private static readonly byte[] ScaleFactorsB4Bits =
+ {
+ 1, 3, 4, 5, 5, 7, 8, 8, 0, 8, 8, 7, 6, 6, 4, 3
+ };
+
+ private static readonly short[] ScaleFactorsB4Codes =
+ {
+ 0x01, 0x01, 0x04, 0x0E, 0x0F, 0x2C, 0x5A, 0x5D, 0x00, 0x5C, 0x5B, 0x2F, 0x15, 0x14, 0x06, 0x00
+ };
+
+ private static readonly byte[] ScaleFactorsB5Bits =
+ {
+ 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 6, 7, 7, 7, 8, 8,
+ 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 6, 3
+ };
+
+ private static readonly short[] ScaleFactorsB5Codes =
+ {
+ 0x00, 0x05, 0x07, 0x0C, 0x04, 0x02, 0x03, 0x05, 0x09, 0x10, 0x23, 0x33, 0x36, 0x6E, 0x60, 0x65,
+ 0x62, 0x61, 0x63, 0x64, 0x6F, 0x6D, 0x6C, 0x6B, 0x6A, 0x68, 0x69, 0x45, 0x44, 0x37, 0x1A, 0x07
+ };
+
+ // For spectrum table names, {letter}{number}{number} correspond to the
+ // codebook set, word length, and band group
+ private static readonly byte[] SpectrumA21Bits =
+ {
+ 0, 3, 0, 3, 3, 3, 0, 3, 0, 0, 0, 0, 3, 3, 0, 3
+ };
+
+ private static readonly short[] SpectrumA21Codes =
+ {
+ 0x00, 0x00, 0x00, 0x01, 0x03, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x05, 0x00, 0x06
+ };
+
+ private static readonly byte[] SpectrumA22Bits =
+ {
+ 0, 4, 0, 4, 5, 6, 0, 6, 0, 0, 0, 0, 5, 6, 0, 6,
+ 5, 6, 0, 6, 6, 7, 0, 7, 0, 0, 0, 0, 6, 7, 0, 7,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 5, 6, 0, 6, 6, 7, 0, 7, 0, 0, 0, 0, 6, 7, 0, 7,
+ 5, 6, 0, 6, 7, 7, 0, 7, 0, 0, 0, 0, 6, 7, 0, 7,
+ 6, 7, 0, 7, 7, 8, 0, 8, 0, 0, 0, 0, 7, 8, 0, 7,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 6, 7, 0, 7, 7, 8, 0, 8, 0, 0, 0, 0, 7, 7, 0, 8,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 5, 6, 0, 6, 6, 7, 0, 7, 0, 0, 0, 0, 7, 7, 0, 7,
+ 6, 7, 0, 7, 7, 8, 0, 7, 0, 0, 0, 0, 7, 8, 0, 8,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 6, 7, 0, 7, 7, 7, 0, 8, 0, 0, 0, 0, 7, 8, 0, 8
+ };
+
+ private static readonly short[] SpectrumA22Codes =
+ {
+ 0x00, 0x02, 0x00, 0x03, 0x10, 0x3C, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x11, 0x3E, 0x00, 0x3D,
+ 0x0E, 0x00, 0x00, 0x39, 0x18, 0x26, 0x00, 0x75, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x24, 0x00, 0x6D,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0F, 0x38, 0x00, 0x01, 0x1A, 0x6C, 0x00, 0x25, 0x00, 0x00, 0x00, 0x00, 0x19, 0x74, 0x00, 0x27,
+ 0x16, 0x14, 0x00, 0x17, 0x76, 0x06, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x35, 0x64, 0x00, 0x6F,
+ 0x26, 0x04, 0x00, 0x63, 0x22, 0xA2, 0x00, 0x97, 0x00, 0x00, 0x00, 0x00, 0x67, 0xA0, 0x00, 0x0D,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x2B, 0x52, 0x00, 0x0B, 0x20, 0x92, 0x00, 0x91, 0x00, 0x00, 0x00, 0x00, 0x61, 0x0E, 0x00, 0x95,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x17, 0x16, 0x00, 0x15, 0x34, 0x6E, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x77, 0x08, 0x00, 0x07,
+ 0x2A, 0x0A, 0x00, 0x53, 0x60, 0x94, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x21, 0x90, 0x00, 0x93,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x27, 0x62, 0x00, 0x05, 0x66, 0x0C, 0x00, 0xA1, 0x00, 0x00, 0x00, 0x00, 0x23, 0x96, 0x00, 0xA3
+ };
+
+ private static readonly byte[] SpectrumA23Bits =
+ {
+ 3, 4, 0, 4, 5, 6, 0, 6, 0, 0, 0, 0, 5, 6, 0, 6,
+ 5, 7, 0, 6, 6, 8, 0, 7, 0, 0, 0, 0, 6, 8, 0, 7,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 5, 6, 0, 7, 6, 7, 0, 8, 0, 0, 0, 0, 6, 7, 0, 8,
+ 5, 6, 0, 6, 7, 8, 0, 8, 0, 0, 0, 0, 6, 7, 0, 7,
+ 6, 8, 0, 7, 8, 9, 0, 9, 0, 0, 0, 0, 7, 9, 0, 8,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 6, 8, 0, 8, 8, 9, 0, 9, 0, 0, 0, 0, 7, 8, 0, 9,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 5, 6, 0, 6, 6, 7, 0, 7, 0, 0, 0, 0, 7, 8, 0, 8,
+ 6, 8, 0, 8, 7, 9, 0, 8, 0, 0, 0, 0, 8, 9, 0, 9,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 6, 7, 0, 8, 7, 8, 0, 9, 0, 0, 0, 0, 8, 9, 0, 9
+ };
+
+ private static readonly short[] SpectrumA23Codes =
+ {
+ 0x006, 0x002, 0x000, 0x003, 0x016, 0x01E, 0x000, 0x021, 0x000, 0x000, 0x000, 0x000,
+ 0x017, 0x020, 0x000, 0x01F, 0x01C, 0x054, 0x000, 0x027, 0x010, 0x0A6, 0x000, 0x027,
+ 0x000, 0x000, 0x000, 0x000, 0x015, 0x0A4, 0x000, 0x02D, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x01D, 0x026, 0x000, 0x055, 0x014, 0x02C, 0x000, 0x0A5, 0x000, 0x000, 0x000, 0x000,
+ 0x011, 0x026, 0x000, 0x0A7, 0x01E, 0x000, 0x000, 0x003, 0x04A, 0x074, 0x000, 0x071,
+ 0x000, 0x000, 0x000, 0x000, 0x023, 0x00A, 0x000, 0x009, 0x018, 0x072, 0x000, 0x00D,
+ 0x0A2, 0x15A, 0x000, 0x123, 0x000, 0x000, 0x000, 0x000, 0x00F, 0x158, 0x000, 0x05D,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x01B, 0x0AE, 0x000, 0x077, 0x092, 0x140, 0x000, 0x121,
+ 0x000, 0x000, 0x000, 0x000, 0x025, 0x05E, 0x000, 0x143, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x01F, 0x002, 0x000, 0x001, 0x022, 0x008, 0x000, 0x00B, 0x000, 0x000, 0x000, 0x000,
+ 0x04B, 0x070, 0x000, 0x075, 0x01A, 0x076, 0x000, 0x0AF, 0x024, 0x142, 0x000, 0x05F,
+ 0x000, 0x000, 0x000, 0x000, 0x093, 0x120, 0x000, 0x141, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x019, 0x00C, 0x000, 0x073, 0x00E, 0x05C, 0x000, 0x159, 0x000, 0x000, 0x000, 0x000,
+ 0x0A3, 0x122, 0x000, 0x15B
+ };
+
+ private static readonly byte[] SpectrumA24Bits =
+ {
+ 02, 04, 00, 04, 05, 06, 00, 06, 00, 00, 00, 00, 05, 06, 00, 06,
+ 05, 07, 00, 06, 06, 08, 00, 08, 00, 00, 00, 00, 06, 08, 00, 08,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 05, 06, 00, 07, 06, 08, 00, 08, 00, 00, 00, 00, 06, 08, 00, 08,
+ 05, 07, 00, 07, 07, 09, 00, 09, 00, 00, 00, 00, 06, 08, 00, 08,
+ 06, 09, 00, 08, 08, 10, 00, 10, 00, 00, 00, 00, 08, 10, 00, 09,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 06, 08, 00, 09, 09, 10, 00, 10, 00, 00, 00, 00, 08, 09, 00, 10,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 05, 07, 00, 07, 06, 08, 00, 08, 00, 00, 00, 00, 07, 09, 00, 09,
+ 06, 09, 00, 08, 08, 10, 00, 09, 00, 00, 00, 00, 09, 10, 00, 10,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 06, 08, 00, 09, 08, 09, 00, 10, 00, 00, 00, 00, 08, 10, 00, 10
+ };
+
+ private static readonly short[] SpectrumA24Codes =
+ {
+ 0x002, 0x002, 0x000, 0x003, 0x01E, 0x010, 0x000, 0x013, 0x000, 0x000, 0x000, 0x000,
+ 0x01F, 0x012, 0x000, 0x011, 0x01A, 0x030, 0x000, 0x01B, 0x000, 0x064, 0x000, 0x0C1,
+ 0x000, 0x000, 0x000, 0x000, 0x003, 0x052, 0x000, 0x07D, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x01B, 0x01A, 0x000, 0x031, 0x002, 0x07C, 0x000, 0x053, 0x000, 0x000, 0x000, 0x000,
+ 0x001, 0x0C0, 0x000, 0x065, 0x01C, 0x062, 0x000, 0x065, 0x02A, 0x198, 0x000, 0x19B,
+ 0x000, 0x000, 0x000, 0x000, 0x017, 0x078, 0x000, 0x07B, 0x004, 0x0FE, 0x000, 0x077,
+ 0x050, 0x33A, 0x000, 0x1F9, 0x000, 0x000, 0x000, 0x000, 0x073, 0x338, 0x000, 0x0E1,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x007, 0x066, 0x000, 0x187, 0x19E, 0x308, 0x000, 0x30B,
+ 0x000, 0x000, 0x000, 0x000, 0x075, 0x0E2, 0x000, 0x1FB, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x01D, 0x064, 0x000, 0x063, 0x016, 0x07A, 0x000, 0x079, 0x000, 0x000, 0x000, 0x000,
+ 0x02B, 0x19A, 0x000, 0x199, 0x006, 0x186, 0x000, 0x067, 0x074, 0x1FA, 0x000, 0x0E3,
+ 0x000, 0x000, 0x000, 0x000, 0x19F, 0x30A, 0x000, 0x309, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x005, 0x076, 0x000, 0x0FF, 0x072, 0x0E0, 0x000, 0x339, 0x000, 0x000, 0x000, 0x000,
+ 0x051, 0x1F8, 0x000, 0x33B
+ };
+
+ private static readonly byte[] SpectrumA31Bits =
+ {
+ 0, 0, 4, 5, 0, 5, 4, 0, 0, 0, 5, 5, 0, 5, 5, 0,
+ 5, 5, 6, 6, 0, 6, 5, 5, 5, 6, 6, 7, 0, 7, 6, 6,
+ 0, 0, 0, 0, 0, 0, 0, 0, 5, 6, 6, 7, 0, 7, 6, 6,
+ 5, 5, 5, 6, 0, 6, 6, 5, 0, 0, 5, 5, 0, 5, 5, 0
+ };
+
+ private static readonly short[] SpectrumA31Codes =
+ {
+ 0x00, 0x00, 0x02, 0x18, 0x00, 0x19, 0x03, 0x00, 0x00, 0x00, 0x12, 0x02, 0x00, 0x09, 0x15, 0x00,
+ 0x1A, 0x0A, 0x3E, 0x2C, 0x00, 0x2F, 0x01, 0x0D, 0x0E, 0x38, 0x20, 0x78, 0x00, 0x7B, 0x23, 0x3B,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x3A, 0x22, 0x7A, 0x00, 0x79, 0x21, 0x39,
+ 0x1B, 0x0C, 0x00, 0x2E, 0x00, 0x2D, 0x3F, 0x0B, 0x00, 0x00, 0x14, 0x08, 0x00, 0x03, 0x13, 0x00
+ };
+
+ private static readonly byte[] SpectrumA32Bits =
+ {
+ 4, 5, 5, 6, 0, 6, 5, 5, 5, 6, 5, 6, 0, 6, 5, 5,
+ 5, 5, 6, 7, 0, 7, 6, 5, 6, 6, 7, 7, 0, 7, 7, 6,
+ 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 7, 7, 0, 7, 7, 6,
+ 5, 5, 6, 7, 0, 7, 6, 5, 5, 5, 5, 6, 0, 6, 5, 6
+ };
+
+ private static readonly short[] SpectrumA32Codes =
+ {
+ 0x0D, 0x18, 0x16, 0x3A, 0x00, 0x3B, 0x17, 0x19, 0x12, 0x3E, 0x08, 0x1C, 0x00, 0x1B, 0x07, 0x01,
+ 0x10, 0x02, 0x28, 0x78, 0x00, 0x7B, 0x1F, 0x05, 0x2A, 0x16, 0x72, 0x2A, 0x00, 0x29, 0x71, 0x19,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x18, 0x70, 0x28, 0x00, 0x2B, 0x73, 0x17,
+ 0x11, 0x04, 0x1E, 0x7A, 0x00, 0x79, 0x29, 0x03, 0x13, 0x00, 0x06, 0x1A, 0x00, 0x1D, 0x09, 0x3F
+ };
+
+ private static readonly byte[] SpectrumA33Bits =
+ {
+ 3, 4, 5, 6, 0, 6, 5, 4, 4, 5, 6, 7, 0, 7, 6, 5,
+ 5, 6, 6, 7, 0, 7, 6, 6, 6, 7, 8, 8, 0, 8, 8, 7,
+ 0, 0, 0, 0, 0, 0, 0, 0, 6, 7, 8, 8, 0, 8, 8, 7,
+ 5, 6, 6, 7, 0, 7, 6, 6, 4, 5, 6, 7, 0, 7, 6, 5
+ };
+
+ private static readonly short[] SpectrumA33Codes =
+ {
+ 0x05, 0x06, 0x10, 0x08, 0x00, 0x09, 0x11, 0x07, 0x04, 0x12, 0x3E, 0x6A, 0x00, 0x6D, 0x3D, 0x19,
+ 0x06, 0x3A, 0x06, 0x02, 0x00, 0x01, 0x05, 0x39, 0x02, 0x16, 0xDC, 0x2A, 0x00, 0x29, 0xDF, 0x69,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x68, 0xDE, 0x28, 0x00, 0x2B, 0xDD, 0x17,
+ 0x07, 0x38, 0x04, 0x00, 0x00, 0x03, 0x07, 0x3B, 0x05, 0x18, 0x3C, 0x6C, 0x00, 0x6B, 0x3F, 0x13
+ };
+
+ private static readonly byte[] SpectrumA34Bits =
+ {
+ 02, 04, 05, 07, 00, 07, 05, 04, 04, 05, 06, 08, 00, 08, 06, 05,
+ 05, 06, 07, 08, 00, 08, 07, 06, 07, 08, 08, 10, 00, 10, 09, 08,
+ 00, 00, 00, 00, 00, 00, 00, 00, 07, 08, 09, 10, 00, 10, 08, 08,
+ 05, 06, 07, 08, 00, 08, 07, 06, 04, 05, 06, 08, 00, 08, 06, 05
+ };
+
+ private static readonly short[] SpectrumA34Codes =
+ {
+ 0x000, 0x00A, 0x00A, 0x034, 0x000, 0x035, 0x00B, 0x00B, 0x008, 0x01C, 0x032, 0x0DA,
+ 0x000, 0x0DD, 0x035, 0x01F, 0x008, 0x01E, 0x03A, 0x06C, 0x000, 0x063, 0x039, 0x031,
+ 0x032, 0x06E, 0x060, 0x37A, 0x000, 0x379, 0x1BF, 0x0D9, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x033, 0x0D8, 0x1BE, 0x378, 0x000, 0x37B, 0x061, 0x06F,
+ 0x009, 0x030, 0x038, 0x062, 0x000, 0x06D, 0x03B, 0x01F, 0x009, 0x01E, 0x034, 0x0DC,
+ 0x000, 0x0DB, 0x033, 0x01D
+ };
+
+ private static readonly byte[] SpectrumA41Bits =
+ {
+ 0, 0, 0, 0, 6, 6, 7, 7, 0, 7, 7, 6, 6, 0, 0, 0,
+ 0, 0, 0, 0, 7, 7, 7, 7, 0, 7, 7, 7, 6, 0, 0, 0,
+ 0, 0, 0, 0, 7, 7, 7, 8, 0, 8, 7, 7, 7, 0, 0, 0,
+ 0, 0, 0, 0, 7, 7, 8, 8, 0, 8, 8, 7, 7, 0, 0, 0,
+ 7, 7, 7, 8, 7, 8, 8, 8, 0, 8, 8, 8, 7, 8, 7, 7,
+ 7, 7, 7, 7, 8, 8, 8, 9, 0, 8, 8, 8, 8, 7, 7, 7,
+ 7, 7, 8, 8, 8, 8, 9, 9, 0, 9, 8, 8, 8, 8, 8, 7,
+ 8, 8, 8, 8, 8, 9, 9, 9, 0, 9, 9, 9, 8, 8, 8, 8,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 8, 8, 8, 8, 8, 9, 9, 9, 0, 9, 9, 9, 8, 8, 8, 8,
+ 7, 7, 8, 8, 8, 8, 8, 9, 0, 9, 9, 8, 8, 8, 8, 7,
+ 7, 7, 7, 7, 8, 8, 8, 8, 0, 9, 8, 8, 8, 7, 7, 7,
+ 7, 7, 7, 8, 7, 8, 8, 8, 0, 8, 8, 8, 7, 8, 7, 7,
+ 0, 0, 0, 0, 7, 7, 8, 8, 0, 8, 8, 7, 7, 0, 0, 0,
+ 0, 0, 0, 0, 7, 7, 7, 8, 0, 8, 7, 7, 7, 0, 0, 0,
+ 0, 0, 0, 0, 6, 7, 7, 7, 0, 7, 7, 7, 7, 0, 0, 0
+ };
+
+ private static readonly short[] SpectrumA41Codes =
+ {
+ 0x000, 0x000, 0x000, 0x000, 0x018, 0x00E, 0x05E, 0x028, 0x000, 0x029, 0x05F, 0x00F,
+ 0x019, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x076, 0x06E, 0x03E, 0x004,
+ 0x000, 0x017, 0x045, 0x07B, 0x013, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x04A, 0x048, 0x010, 0x0CE, 0x000, 0x0E1, 0x023, 0x055, 0x053, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x008, 0x018, 0x0D6, 0x09E, 0x000, 0x09D, 0x0E5, 0x02B,
+ 0x01B, 0x000, 0x000, 0x000, 0x07C, 0x05C, 0x038, 0x0FC, 0x002, 0x0D2, 0x09A, 0x05C,
+ 0x000, 0x06B, 0x0A3, 0x0D9, 0x00F, 0x0FF, 0x03D, 0x061, 0x074, 0x056, 0x036, 0x000,
+ 0x0CC, 0x08C, 0x058, 0x1E2, 0x000, 0x00F, 0x05F, 0x0A1, 0x0D5, 0x00D, 0x03B, 0x059,
+ 0x040, 0x014, 0x0DA, 0x0B6, 0x084, 0x040, 0x1E0, 0x196, 0x000, 0x1A1, 0x00D, 0x043,
+ 0x087, 0x0C7, 0x0E3, 0x00B, 0x0F2, 0x0C4, 0x08E, 0x05A, 0x024, 0x1CC, 0x194, 0x168,
+ 0x000, 0x16B, 0x1A3, 0x1CF, 0x027, 0x069, 0x099, 0x0C9, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x0F3, 0x0C8, 0x098, 0x068, 0x026, 0x1CE, 0x1A2, 0x16A, 0x000, 0x169, 0x195, 0x1CD,
+ 0x025, 0x05B, 0x08F, 0x0C5, 0x041, 0x00A, 0x0E2, 0x0C6, 0x086, 0x042, 0x00C, 0x1A0,
+ 0x000, 0x197, 0x1E1, 0x041, 0x085, 0x0B7, 0x0DB, 0x015, 0x075, 0x058, 0x03A, 0x00C,
+ 0x0D4, 0x0A0, 0x05E, 0x00E, 0x000, 0x1E3, 0x059, 0x08D, 0x0CD, 0x001, 0x037, 0x057,
+ 0x07D, 0x060, 0x03C, 0x0FE, 0x00E, 0x0D8, 0x0A2, 0x06A, 0x000, 0x05D, 0x09B, 0x0D3,
+ 0x003, 0x0FD, 0x039, 0x05D, 0x000, 0x000, 0x000, 0x000, 0x01A, 0x02A, 0x0E4, 0x09C,
+ 0x000, 0x09F, 0x0D7, 0x019, 0x009, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x052, 0x054, 0x022, 0x0E0, 0x000, 0x0CF, 0x011, 0x049, 0x04B, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x012, 0x07A, 0x044, 0x016, 0x000, 0x005, 0x03F, 0x06F,
+ 0x077, 0x000, 0x000, 0x000
+ };
+
+ private static readonly byte[] SpectrumA42Bits =
+ {
+ 05, 06, 07, 07, 07, 07, 08, 08, 00, 08, 08, 07, 07, 07, 07, 06,
+ 06, 07, 07, 08, 07, 07, 08, 08, 00, 08, 08, 07, 07, 08, 07, 07,
+ 07, 07, 08, 08, 07, 08, 08, 09, 00, 09, 08, 08, 07, 08, 08, 07,
+ 08, 08, 08, 08, 08, 08, 08, 09, 00, 09, 08, 08, 08, 08, 08, 08,
+ 07, 07, 07, 08, 08, 08, 09, 09, 00, 09, 09, 08, 08, 08, 07, 07,
+ 07, 07, 08, 08, 08, 09, 09, 09, 00, 09, 09, 09, 08, 08, 08, 07,
+ 08, 08, 08, 08, 09, 09, 09, 10, 00, 10, 09, 09, 09, 08, 08, 08,
+ 08, 08, 09, 09, 09, 09, 10, 10, 00, 10, 10, 09, 09, 09, 09, 09,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 08, 09, 09, 09, 09, 09, 10, 10, 00, 10, 10, 09, 09, 09, 09, 08,
+ 08, 08, 08, 08, 09, 09, 09, 10, 00, 10, 09, 09, 09, 08, 08, 08,
+ 07, 07, 08, 08, 08, 09, 09, 09, 00, 09, 09, 09, 08, 08, 08, 07,
+ 07, 07, 07, 08, 08, 08, 09, 09, 00, 09, 09, 08, 08, 08, 07, 07,
+ 08, 08, 08, 08, 08, 08, 08, 09, 00, 09, 08, 08, 08, 08, 08, 08,
+ 07, 07, 08, 08, 07, 08, 08, 09, 00, 09, 08, 08, 07, 08, 08, 07,
+ 06, 07, 07, 08, 07, 07, 08, 08, 00, 08, 08, 07, 07, 08, 07, 07
+ };
+
+ private static readonly short[] SpectrumA42Codes =
+ {
+ 0x003, 0x018, 0x058, 0x000, 0x066, 0x03C, 0x0D6, 0x07C, 0x000, 0x07D, 0x0D7, 0x03D,
+ 0x067, 0x001, 0x059, 0x019, 0x002, 0x064, 0x036, 0x0DA, 0x04C, 0x01C, 0x0BE, 0x02C,
+ 0x000, 0x037, 0x0C5, 0x029, 0x04B, 0x0E7, 0x03B, 0x069, 0x044, 0x02E, 0x0FA, 0x092,
+ 0x020, 0x0F8, 0x086, 0x1FC, 0x000, 0x1E7, 0x07F, 0x0F5, 0x023, 0x0AD, 0x0FD, 0x02D,
+ 0x0F6, 0x0DC, 0x09C, 0x03E, 0x0F0, 0x0B6, 0x026, 0x186, 0x000, 0x18D, 0x02F, 0x0B5,
+ 0x0E1, 0x03D, 0x0AF, 0x0D9, 0x054, 0x040, 0x014, 0x0EC, 0x0BC, 0x054, 0x1C6, 0x108,
+ 0x000, 0x10B, 0x1C5, 0x069, 0x0B9, 0x0DF, 0x019, 0x047, 0x026, 0x008, 0x0E4, 0x0A2,
+ 0x056, 0x1DC, 0x142, 0x06A, 0x000, 0x091, 0x123, 0x1DF, 0x04B, 0x0A7, 0x0EB, 0x00B,
+ 0x0C0, 0x09E, 0x06A, 0x022, 0x1AA, 0x140, 0x092, 0x3CA, 0x000, 0x3A7, 0x04B, 0x121,
+ 0x18F, 0x007, 0x071, 0x0A5, 0x020, 0x004, 0x1A8, 0x174, 0x0E4, 0x068, 0x3A4, 0x2EE,
+ 0x000, 0x2ED, 0x3C9, 0x049, 0x0E7, 0x185, 0x1D1, 0x1FF, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x021, 0x1FE, 0x1D0, 0x184, 0x0E6, 0x048, 0x3C8, 0x2EC, 0x000, 0x2EF, 0x3A5, 0x069,
+ 0x0E5, 0x175, 0x1A9, 0x005, 0x0C1, 0x0A4, 0x070, 0x006, 0x18E, 0x120, 0x04A, 0x3A6,
+ 0x000, 0x3CB, 0x093, 0x141, 0x1AB, 0x023, 0x06B, 0x09F, 0x027, 0x00A, 0x0EA, 0x0A6,
+ 0x04A, 0x1DE, 0x122, 0x090, 0x000, 0x06B, 0x143, 0x1DD, 0x057, 0x0A3, 0x0E5, 0x009,
+ 0x055, 0x046, 0x018, 0x0DE, 0x0B8, 0x068, 0x1C4, 0x10A, 0x000, 0x109, 0x1C7, 0x055,
+ 0x0BD, 0x0ED, 0x015, 0x041, 0x0F7, 0x0D8, 0x0AE, 0x03C, 0x0E0, 0x0B4, 0x02E, 0x18C,
+ 0x000, 0x187, 0x027, 0x0B7, 0x0F1, 0x03F, 0x09D, 0x0DD, 0x045, 0x02C, 0x0FC, 0x0AC,
+ 0x022, 0x0F4, 0x07E, 0x1E6, 0x000, 0x1FD, 0x087, 0x0F9, 0x021, 0x093, 0x0FB, 0x02F,
+ 0x003, 0x068, 0x03A, 0x0E6, 0x04A, 0x028, 0x0C4, 0x036, 0x000, 0x02D, 0x0BF, 0x01D,
+ 0x04D, 0x0DB, 0x037, 0x065
+ };
+
+ private static readonly byte[] SpectrumA43Bits =
+ {
+ 04, 06, 06, 07, 07, 08, 08, 09, 00, 09, 08, 08, 07, 07, 06, 06,
+ 05, 06, 07, 07, 07, 08, 08, 09, 00, 09, 08, 08, 07, 07, 07, 06,
+ 06, 07, 07, 07, 08, 08, 09, 09, 00, 09, 09, 08, 08, 07, 07, 07,
+ 07, 07, 07, 08, 08, 08, 09, 10, 00, 10, 09, 09, 08, 08, 07, 07,
+ 07, 07, 08, 08, 08, 09, 10, 10, 00, 10, 10, 09, 08, 08, 08, 07,
+ 08, 08, 08, 09, 09, 09, 10, 10, 00, 10, 10, 09, 09, 09, 08, 08,
+ 08, 09, 09, 09, 10, 10, 10, 10, 00, 10, 10, 10, 10, 09, 09, 09,
+ 09, 09, 10, 10, 10, 10, 10, 10, 00, 10, 10, 10, 10, 10, 10, 09,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 09, 09, 10, 10, 10, 10, 10, 10, 00, 10, 10, 10, 10, 10, 10, 09,
+ 08, 09, 09, 09, 10, 10, 10, 10, 00, 10, 10, 10, 10, 09, 09, 09,
+ 08, 08, 08, 09, 09, 09, 10, 10, 00, 10, 10, 09, 09, 09, 08, 08,
+ 07, 07, 08, 08, 08, 09, 10, 10, 00, 10, 10, 09, 08, 08, 08, 07,
+ 07, 07, 07, 08, 08, 09, 09, 10, 00, 10, 09, 08, 08, 08, 07, 07,
+ 06, 07, 07, 07, 08, 08, 09, 09, 00, 09, 09, 08, 08, 07, 07, 07,
+ 05, 06, 07, 07, 07, 08, 08, 09, 00, 09, 08, 08, 07, 07, 07, 06
+ };
+
+ private static readonly short[] SpectrumA43Codes =
+ {
+ 0x002, 0x03E, 0x016, 0x060, 0x04E, 0x0DC, 0x04A, 0x130, 0x000, 0x131, 0x04B, 0x0DD,
+ 0x04F, 0x061, 0x017, 0x03F, 0x002, 0x02C, 0x076, 0x042, 0x034, 0x0CE, 0x002, 0x0E8,
+ 0x000, 0x0CF, 0x001, 0x0D1, 0x037, 0x045, 0x07B, 0x02F, 0x014, 0x072, 0x052, 0x01A,
+ 0x0E0, 0x080, 0x198, 0x01E, 0x000, 0x01D, 0x19B, 0x083, 0x0DF, 0x019, 0x055, 0x079,
+ 0x050, 0x03C, 0x004, 0x0C4, 0x096, 0x00C, 0x0EA, 0x34A, 0x000, 0x34F, 0x0ED, 0x1D7,
+ 0x095, 0x0AF, 0x003, 0x03F, 0x046, 0x026, 0x0D6, 0x092, 0x046, 0x15A, 0x3A8, 0x108,
+ 0x000, 0x10F, 0x3A3, 0x135, 0x039, 0x091, 0x0D9, 0x031, 0x0D4, 0x0CA, 0x072, 0x1C6,
+ 0x136, 0x090, 0x2B2, 0x104, 0x000, 0x103, 0x111, 0x08B, 0x133, 0x1D3, 0x071, 0x0C9,
+ 0x03E, 0x1B4, 0x18C, 0x0CC, 0x38A, 0x2B0, 0x106, 0x0F2, 0x000, 0x0EF, 0x101, 0x113,
+ 0x3A1, 0x0CB, 0x18F, 0x1B7, 0x0EE, 0x092, 0x388, 0x348, 0x10A, 0x0F4, 0x0F0, 0x0EA,
+ 0x000, 0x0E9, 0x0ED, 0x0F7, 0x10D, 0x34D, 0x3AB, 0x0C9, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x0EF, 0x0C8, 0x3AA, 0x34C, 0x10C, 0x0F6, 0x0EC, 0x0E8, 0x000, 0x0EB, 0x0F1, 0x0F5,
+ 0x10B, 0x349, 0x389, 0x093, 0x03F, 0x1B6, 0x18E, 0x0CA, 0x3A0, 0x112, 0x100, 0x0EE,
+ 0x000, 0x0F3, 0x107, 0x2B1, 0x38B, 0x0CD, 0x18D, 0x1B5, 0x0D5, 0x0C8, 0x070, 0x1D2,
+ 0x132, 0x08A, 0x110, 0x102, 0x000, 0x105, 0x2B3, 0x091, 0x137, 0x1C7, 0x073, 0x0CB,
+ 0x047, 0x030, 0x0D8, 0x090, 0x038, 0x134, 0x3A2, 0x10E, 0x000, 0x109, 0x3A9, 0x15B,
+ 0x047, 0x093, 0x0D7, 0x027, 0x051, 0x03E, 0x002, 0x0AE, 0x094, 0x1D6, 0x0EC, 0x34E,
+ 0x000, 0x34B, 0x0EB, 0x00D, 0x097, 0x0C5, 0x005, 0x03D, 0x015, 0x078, 0x054, 0x018,
+ 0x0DE, 0x082, 0x19A, 0x01C, 0x000, 0x01F, 0x199, 0x081, 0x0E1, 0x01B, 0x053, 0x073,
+ 0x003, 0x02E, 0x07A, 0x044, 0x036, 0x0D0, 0x000, 0x0CE, 0x000, 0x0E9, 0x003, 0x0CF,
+ 0x035, 0x043, 0x077, 0x02D
+ };
+
+ private static readonly byte[] SpectrumA44Bits =
+ {
+ 04, 05, 06, 07, 07, 08, 09, 10, 00, 10, 09, 08, 07, 07, 06, 05,
+ 05, 06, 06, 07, 07, 08, 09, 10, 00, 10, 09, 08, 07, 07, 06, 06,
+ 06, 06, 07, 07, 08, 09, 10, 10, 00, 10, 10, 09, 08, 07, 07, 06,
+ 07, 07, 07, 08, 08, 09, 10, 10, 00, 10, 10, 09, 08, 08, 07, 07,
+ 07, 08, 08, 08, 09, 10, 10, 10, 00, 10, 10, 10, 09, 08, 08, 07,
+ 08, 08, 09, 09, 10, 10, 10, 10, 00, 10, 10, 10, 10, 09, 09, 08,
+ 09, 09, 10, 10, 10, 10, 10, 10, 00, 10, 10, 10, 10, 10, 10, 09,
+ 10, 10, 10, 10, 10, 10, 10, 10, 00, 10, 10, 10, 10, 10, 10, 10,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 10, 10, 10, 10, 10, 10, 10, 10, 00, 10, 10, 10, 10, 10, 10, 10,
+ 09, 09, 10, 10, 10, 10, 10, 10, 00, 10, 10, 10, 10, 10, 10, 09,
+ 08, 08, 09, 09, 10, 10, 10, 10, 00, 10, 10, 10, 10, 09, 09, 08,
+ 07, 07, 08, 08, 09, 10, 10, 10, 00, 10, 10, 10, 09, 08, 08, 08,
+ 07, 07, 07, 08, 08, 09, 10, 10, 00, 10, 10, 09, 08, 08, 07, 07,
+ 06, 06, 07, 07, 08, 09, 10, 10, 00, 10, 10, 09, 08, 07, 07, 06,
+ 05, 06, 06, 07, 07, 08, 09, 10, 00, 10, 09, 08, 07, 07, 06, 06
+ };
+
+ private static readonly short[] SpectrumA44Codes =
+ {
+ 0x00A, 0x012, 0x030, 0x06E, 0x024, 0x074, 0x0EC, 0x07E, 0x000, 0x07F, 0x0ED, 0x075,
+ 0x025, 0x06F, 0x031, 0x013, 0x010, 0x03C, 0x018, 0x05A, 0x002, 0x046, 0x09E, 0x07C,
+ 0x000, 0x079, 0x0E5, 0x04D, 0x007, 0x065, 0x01B, 0x03F, 0x02E, 0x016, 0x072, 0x01A,
+ 0x0D6, 0x1C6, 0x3B4, 0x066, 0x000, 0x06B, 0x3B7, 0x1D9, 0x0D5, 0x021, 0x075, 0x015,
+ 0x06C, 0x03E, 0x01E, 0x0CC, 0x044, 0x0F2, 0x082, 0x05C, 0x000, 0x05F, 0x087, 0x0F5,
+ 0x031, 0x0CF, 0x017, 0x059, 0x01C, 0x0EE, 0x0D0, 0x024, 0x1C0, 0x08E, 0x06E, 0x048,
+ 0x000, 0x04D, 0x06D, 0x089, 0x0F7, 0x033, 0x0D3, 0x001, 0x070, 0x028, 0x1C2, 0x0F0,
+ 0x08A, 0x074, 0x054, 0x040, 0x000, 0x043, 0x053, 0x073, 0x099, 0x0EF, 0x1C5, 0x02B,
+ 0x0E6, 0x04E, 0x08C, 0x080, 0x068, 0x058, 0x046, 0x02A, 0x000, 0x029, 0x045, 0x051,
+ 0x065, 0x085, 0x09B, 0x09D, 0x07A, 0x076, 0x060, 0x056, 0x04E, 0x02C, 0x024, 0x022,
+ 0x000, 0x021, 0x027, 0x02F, 0x04B, 0x05B, 0x063, 0x071, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x07B, 0x070, 0x062, 0x05A, 0x04A, 0x02E, 0x026, 0x020, 0x000, 0x023, 0x025, 0x02D,
+ 0x04F, 0x057, 0x061, 0x077, 0x0E7, 0x09C, 0x09A, 0x084, 0x064, 0x050, 0x044, 0x028,
+ 0x000, 0x02B, 0x047, 0x059, 0x069, 0x081, 0x08D, 0x04F, 0x071, 0x02A, 0x1C4, 0x0EE,
+ 0x098, 0x072, 0x052, 0x042, 0x000, 0x041, 0x055, 0x075, 0x08B, 0x0F1, 0x1C3, 0x029,
+ 0x01D, 0x000, 0x0D2, 0x032, 0x0F6, 0x088, 0x06C, 0x04C, 0x000, 0x049, 0x06F, 0x08F,
+ 0x1C1, 0x025, 0x0D1, 0x0EF, 0x06D, 0x058, 0x016, 0x0CE, 0x030, 0x0F4, 0x086, 0x05E,
+ 0x000, 0x05D, 0x083, 0x0F3, 0x045, 0x0CD, 0x01F, 0x03F, 0x02F, 0x014, 0x074, 0x020,
+ 0x0D4, 0x1D8, 0x3B6, 0x06A, 0x000, 0x067, 0x3B5, 0x1C7, 0x0D7, 0x01B, 0x073, 0x017,
+ 0x011, 0x03E, 0x01A, 0x064, 0x006, 0x04C, 0x0E4, 0x078, 0x000, 0x07D, 0x09F, 0x047,
+ 0x003, 0x05B, 0x019, 0x03D
+ };
+
+ private static readonly byte[] SpectrumA51Bits =
+ {
+ 5, 5, 5, 5, 5, 6, 6, 6, 4, 4, 5, 5, 5, 5, 5, 5,
+ 0, 5, 5, 5, 5, 5, 5, 4, 4, 6, 6, 6, 5, 5, 5, 5
+ };
+
+ private static readonly short[] SpectrumA51Codes =
+ {
+ 0x19, 0x16, 0x12, 0x0E, 0x06, 0x3A, 0x38, 0x30, 0x00, 0x04, 0x1E, 0x1A, 0x14, 0x10, 0x0C, 0x04,
+ 0x00, 0x05, 0x0D, 0x11, 0x15, 0x1B, 0x1F, 0x05, 0x01, 0x31, 0x39, 0x3B, 0x07, 0x0F, 0x13, 0x17
+ };
+
+ private static readonly byte[] SpectrumA52Bits =
+ {
+ 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6,
+ 0, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4
+ };
+
+ private static readonly short[] SpectrumA52Codes =
+ {
+ 0x09, 0x04, 0x00, 0x1E, 0x1A, 0x14, 0x0C, 0x06, 0x18, 0x16, 0x0E, 0x04, 0x3A, 0x38, 0x22, 0x20,
+ 0x00, 0x21, 0x23, 0x39, 0x3B, 0x05, 0x0F, 0x17, 0x19, 0x07, 0x0D, 0x15, 0x1B, 0x1F, 0x01, 0x05
+ };
+
+ private static readonly byte[] SpectrumA53Bits =
+ {
+ 3, 4, 4, 4, 5, 5, 5, 5, 5, 5, 6, 6, 7, 7, 7, 7,
+ 0, 7, 7, 7, 7, 6, 6, 5, 5, 5, 5, 5, 5, 4, 4, 4
+ };
+
+ private static readonly short[] SpectrumA53Codes =
+ {
+ 0x00, 0x0C, 0x08, 0x04, 0x1E, 0x16, 0x14, 0x06, 0x0C, 0x04, 0x38, 0x1E, 0x76, 0x74, 0x3A, 0x38,
+ 0x00, 0x39, 0x3B, 0x75, 0x77, 0x1F, 0x39, 0x05, 0x0D, 0x07, 0x15, 0x17, 0x1F, 0x05, 0x09, 0x0D
+ };
+
+ private static readonly byte[] SpectrumA54Bits =
+ {
+ 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 7, 7, 8, 8,
+ 0, 8, 8, 7, 7, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4
+ };
+
+ private static readonly short[] SpectrumA54Codes =
+ {
+ 0x02, 0x0E, 0x0A, 0x08, 0x02, 0x1A, 0x0E, 0x02, 0x00, 0x30, 0x18, 0x66, 0x36, 0x34, 0xCA, 0xC8,
+ 0x00, 0xC9, 0xCB, 0x35, 0x37, 0x67, 0x19, 0x31, 0x01, 0x03, 0x0F, 0x1B, 0x03, 0x09, 0x0B, 0x0F
+ };
+
+ private static readonly byte[] SpectrumA61Bits =
+ {
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7,
+ 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5,
+ 5, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6
+ };
+
+ private static readonly short[] SpectrumA61Codes =
+ {
+ 0x35, 0x30, 0x2A, 0x28, 0x24, 0x20, 0x18, 0x0E, 0x0C, 0x7E, 0x7C, 0x72, 0x70, 0x68, 0x5E, 0x5C,
+ 0x04, 0x0E, 0x08, 0x00, 0x3C, 0x3A, 0x36, 0x32, 0x2C, 0x26, 0x22, 0x1A, 0x16, 0x14, 0x06, 0x04,
+ 0x00, 0x05, 0x07, 0x15, 0x17, 0x1B, 0x23, 0x27, 0x2D, 0x33, 0x37, 0x3B, 0x3D, 0x01, 0x09, 0x0F,
+ 0x05, 0x5D, 0x5F, 0x69, 0x71, 0x73, 0x7D, 0x7F, 0x0D, 0x0F, 0x19, 0x21, 0x25, 0x29, 0x2B, 0x31
+ };
+
+ private static readonly byte[] SpectrumA62Bits =
+ {
+ 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7,
+ 0, 7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5
+ };
+
+ private static readonly short[] SpectrumA62Codes =
+ {
+ 0x14, 0x0E, 0x08, 0x04, 0x02, 0x3E, 0x3C, 0x38, 0x34, 0x30, 0x2A, 0x24, 0x1A, 0x18, 0x0E, 0x02,
+ 0x32, 0x36, 0x2C, 0x26, 0x20, 0x16, 0x0C, 0x00, 0x76, 0x74, 0x5E, 0x5C, 0x46, 0x44, 0x2A, 0x28,
+ 0x00, 0x29, 0x2B, 0x45, 0x47, 0x5D, 0x5F, 0x75, 0x77, 0x01, 0x0D, 0x17, 0x21, 0x27, 0x2D, 0x37,
+ 0x33, 0x03, 0x0F, 0x19, 0x1B, 0x25, 0x2B, 0x31, 0x35, 0x39, 0x3D, 0x3F, 0x03, 0x05, 0x09, 0x0F
+ };
+
+ private static readonly byte[] SpectrumA63Bits =
+ {
+ 4, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8,
+ 0, 8, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5
+ };
+
+ private static readonly short[] SpectrumA63Codes =
+ {
+ 0x00, 0x1C, 0x18, 0x14, 0x10, 0x0A, 0x08, 0x02, 0x3E, 0x36, 0x2E, 0x2C, 0x24, 0x1C, 0x0E, 0x08,
+ 0x1E, 0x1A, 0x0C, 0x7A, 0x6A, 0x68, 0x4C, 0x32, 0x16, 0x14, 0xF2, 0xF0, 0x9E, 0x9C, 0x62, 0x60,
+ 0x00, 0x61, 0x63, 0x9D, 0x9F, 0xF1, 0xF3, 0x15, 0x17, 0x33, 0x4D, 0x69, 0x6B, 0x7B, 0x0D, 0x1B,
+ 0x1F, 0x09, 0x0F, 0x1D, 0x25, 0x2D, 0x2F, 0x37, 0x3F, 0x03, 0x09, 0x0B, 0x11, 0x15, 0x19, 0x1D
+ };
+
+ private static readonly byte[] SpectrumA64Bits =
+ {
+ 4, 4, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7,
+ 6, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9,
+ 0, 9, 9, 9, 9, 8, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7,
+ 6, 7, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 4
+ };
+
+ private static readonly short[] SpectrumA64Codes =
+ {
+ 0x006, 0x002, 0x01C, 0x01A, 0x016, 0x012, 0x00E, 0x00A, 0x002, 0x03E, 0x032, 0x02A,
+ 0x022, 0x020, 0x010, 0x07A, 0x000, 0x078, 0x060, 0x050, 0x024, 0x006, 0x0C6, 0x0C4,
+ 0x0A4, 0x04E, 0x00A, 0x008, 0x14E, 0x14C, 0x09A, 0x098, 0x000, 0x099, 0x09B, 0x14D,
+ 0x14F, 0x009, 0x00B, 0x04F, 0x0A5, 0x0C5, 0x0C7, 0x007, 0x025, 0x051, 0x061, 0x079,
+ 0x001, 0x07B, 0x011, 0x021, 0x023, 0x02B, 0x033, 0x03F, 0x003, 0x00B, 0x00F, 0x013,
+ 0x017, 0x01B, 0x01D, 0x003
+ };
+
+ private static readonly byte[] SpectrumA71Bits =
+ {
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6,
+ 6, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7
+ };
+
+ private static readonly short[] SpectrumA71Codes =
+ {
+ 0x6C, 0x66, 0x62, 0x5C, 0x56, 0x50, 0x52, 0x4E, 0x48, 0x3E, 0x36, 0x34, 0x2A, 0x26, 0x1E, 0x16,
+ 0x0E, 0x08, 0x00, 0xF6, 0xF4, 0xEE, 0xEC, 0xE2, 0xE0, 0xDA, 0xD2, 0xD0, 0xBE, 0xBC, 0xB2, 0xB0,
+ 0x0C, 0x20, 0x1C, 0x16, 0x10, 0x08, 0x02, 0x7E, 0x7C, 0x78, 0x74, 0x72, 0x6E, 0x6A, 0x64, 0x60,
+ 0x5A, 0x54, 0x4C, 0x4A, 0x46, 0x44, 0x3C, 0x32, 0x30, 0x28, 0x24, 0x1C, 0x14, 0x0C, 0x0A, 0x02,
+ 0x00, 0x03, 0x0B, 0x0D, 0x15, 0x1D, 0x25, 0x29, 0x31, 0x33, 0x3D, 0x45, 0x47, 0x4B, 0x4D, 0x55,
+ 0x5B, 0x61, 0x65, 0x6B, 0x6F, 0x73, 0x75, 0x79, 0x7D, 0x7F, 0x03, 0x09, 0x11, 0x17, 0x1D, 0x21,
+ 0x0D, 0xB1, 0xB3, 0xBD, 0xBF, 0xD1, 0xD3, 0xDB, 0xE1, 0xE3, 0xED, 0xEF, 0xF5, 0xF7, 0x01, 0x09,
+ 0x0F, 0x17, 0x1F, 0x27, 0x2B, 0x35, 0x37, 0x3F, 0x49, 0x4F, 0x53, 0x51, 0x57, 0x5D, 0x63, 0x67
+ };
+
+ private static readonly byte[] SpectrumA72Bits =
+ {
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6
+ };
+
+ private static readonly short[] SpectrumA72Codes =
+ {
+ 0x2A, 0x24, 0x1C, 0x18, 0x12, 0x0E, 0x0A, 0x06, 0x02, 0x7E, 0x7C, 0x7A, 0x76, 0x72, 0x70, 0x6A,
+ 0x68, 0x62, 0x5C, 0x5A, 0x52, 0x4E, 0x46, 0x42, 0x3C, 0x34, 0x2A, 0x28, 0x20, 0x12, 0x10, 0x08,
+ 0x66, 0x74, 0x6C, 0x64, 0x5E, 0x58, 0x50, 0x44, 0x40, 0x36, 0x2C, 0x22, 0x1A, 0x0A, 0x02, 0x00,
+ 0xF2, 0xF0, 0xDE, 0xDC, 0xC2, 0xC0, 0xAE, 0xAC, 0x9A, 0x98, 0x7E, 0x7C, 0x5E, 0x5C, 0x32, 0x30,
+ 0x00, 0x31, 0x33, 0x5D, 0x5F, 0x7D, 0x7F, 0x99, 0x9B, 0xAD, 0xAF, 0xC1, 0xC3, 0xDD, 0xDF, 0xF1,
+ 0xF3, 0x01, 0x03, 0x0B, 0x1B, 0x23, 0x2D, 0x37, 0x41, 0x45, 0x51, 0x59, 0x5F, 0x65, 0x6D, 0x75,
+ 0x67, 0x09, 0x11, 0x13, 0x21, 0x29, 0x2B, 0x35, 0x3D, 0x43, 0x47, 0x4F, 0x53, 0x5B, 0x5D, 0x63,
+ 0x69, 0x6B, 0x71, 0x73, 0x77, 0x7B, 0x7D, 0x7F, 0x03, 0x07, 0x0B, 0x0F, 0x13, 0x19, 0x1D, 0x25
+ };
+
+ private static readonly byte[] SpectrumA73Bits =
+ {
+ 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6
+ };
+
+ private static readonly short[] SpectrumA73Codes =
+ {
+ 0x003, 0x03E, 0x038, 0x034, 0x030, 0x02C, 0x028, 0x024, 0x020, 0x01C, 0x016, 0x014,
+ 0x00E, 0x00A, 0x004, 0x000, 0x07A, 0x076, 0x06E, 0x06C, 0x064, 0x05E, 0x056, 0x04E,
+ 0x04C, 0x044, 0x036, 0x030, 0x022, 0x018, 0x012, 0x004, 0x03C, 0x03E, 0x032, 0x024,
+ 0x020, 0x010, 0x0F2, 0x0F0, 0x0E8, 0x0CE, 0x0BA, 0x0B8, 0x0A8, 0x08C, 0x06A, 0x04E,
+ 0x04C, 0x034, 0x00E, 0x00C, 0x1D6, 0x1D4, 0x19A, 0x198, 0x156, 0x154, 0x11E, 0x11C,
+ 0x0D2, 0x0D0, 0x06E, 0x06C, 0x000, 0x06D, 0x06F, 0x0D1, 0x0D3, 0x11D, 0x11F, 0x155,
+ 0x157, 0x199, 0x19B, 0x1D5, 0x1D7, 0x00D, 0x00F, 0x035, 0x04D, 0x04F, 0x06B, 0x08D,
+ 0x0A9, 0x0B9, 0x0BB, 0x0CF, 0x0E9, 0x0F1, 0x0F3, 0x011, 0x021, 0x025, 0x033, 0x03F,
+ 0x03D, 0x005, 0x013, 0x019, 0x023, 0x031, 0x037, 0x045, 0x04D, 0x04F, 0x057, 0x05F,
+ 0x065, 0x06D, 0x06F, 0x077, 0x07B, 0x001, 0x005, 0x00B, 0x00F, 0x015, 0x017, 0x01D,
+ 0x021, 0x025, 0x029, 0x02D, 0x031, 0x035, 0x039, 0x03F
+ };
+
+ private static readonly byte[] SpectrumA74Bits =
+ {
+ 05, 05, 05, 05, 06, 06, 06, 06, 06, 06, 06, 06, 06, 06, 06, 06,
+ 06, 07, 07, 07, 07, 07, 07, 07, 07, 07, 07, 07, 07, 07, 08, 08,
+ 07, 08, 08, 08, 08, 08, 08, 08, 08, 08, 08, 09, 09, 09, 09, 09,
+ 09, 09, 09, 09, 09, 09, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 00, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 09, 09, 09, 09, 09,
+ 09, 09, 09, 09, 09, 09, 08, 08, 08, 08, 08, 08, 08, 08, 08, 08,
+ 07, 08, 08, 07, 07, 07, 07, 07, 07, 07, 07, 07, 07, 07, 07, 07,
+ 06, 06, 06, 06, 06, 06, 06, 06, 06, 06, 06, 06, 06, 05, 05, 05
+ };
+
+ private static readonly short[] SpectrumA74Codes =
+ {
+ 0x00D, 0x00A, 0x004, 0x000, 0x03A, 0x036, 0x032, 0x030, 0x02C, 0x028, 0x026, 0x022,
+ 0x01E, 0x018, 0x012, 0x00E, 0x006, 0x07E, 0x07A, 0x070, 0x06A, 0x05E, 0x056, 0x054,
+ 0x048, 0x040, 0x038, 0x022, 0x01A, 0x00A, 0x0F8, 0x0E6, 0x008, 0x0FA, 0x0F0, 0x0D2,
+ 0x0BA, 0x0B8, 0x094, 0x084, 0x074, 0x042, 0x032, 0x1E6, 0x1CA, 0x1C8, 0x1A2, 0x12E,
+ 0x10E, 0x10C, 0x0EC, 0x082, 0x062, 0x060, 0x3CA, 0x3C8, 0x342, 0x340, 0x25A, 0x258,
+ 0x1DE, 0x1DC, 0x102, 0x100, 0x000, 0x101, 0x103, 0x1DD, 0x1DF, 0x259, 0x25B, 0x341,
+ 0x343, 0x3C9, 0x3CB, 0x061, 0x063, 0x083, 0x0ED, 0x10D, 0x10F, 0x12F, 0x1A3, 0x1C9,
+ 0x1CB, 0x1E7, 0x033, 0x043, 0x075, 0x085, 0x095, 0x0B9, 0x0BB, 0x0D3, 0x0F1, 0x0FB,
+ 0x009, 0x0E7, 0x0F9, 0x00B, 0x01B, 0x023, 0x039, 0x041, 0x049, 0x055, 0x057, 0x05F,
+ 0x06B, 0x071, 0x07B, 0x07F, 0x007, 0x00F, 0x013, 0x019, 0x01F, 0x023, 0x027, 0x029,
+ 0x02D, 0x031, 0x033, 0x037, 0x03B, 0x001, 0x005, 0x00B
+ };
+
+ private static readonly byte[] SpectrumB22Bits =
+ {
+ 00, 04, 00, 04, 04, 05, 00, 05, 00, 00, 00, 00, 04, 05, 00, 05,
+ 04, 07, 00, 06, 06, 09, 00, 07, 00, 00, 00, 00, 06, 09, 00, 07,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 04, 06, 00, 07, 06, 07, 00, 09, 00, 00, 00, 00, 06, 07, 00, 09,
+ 04, 08, 00, 08, 08, 10, 00, 10, 00, 00, 00, 00, 06, 09, 00, 09,
+ 05, 10, 00, 09, 09, 10, 00, 10, 00, 00, 00, 00, 07, 10, 00, 10,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 06, 09, 00, 10, 09, 10, 00, 10, 00, 00, 00, 00, 07, 10, 00, 10,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 04, 08, 00, 08, 06, 09, 00, 09, 00, 00, 00, 00, 08, 10, 00, 10,
+ 06, 10, 00, 09, 07, 10, 00, 10, 00, 00, 00, 00, 09, 10, 00, 10,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 05, 09, 00, 10, 07, 10, 00, 10, 00, 00, 00, 00, 09, 10, 00, 10
+ };
+
+ private static readonly short[] SpectrumB22Codes =
+ {
+ 0x000, 0x00E, 0x000, 0x00F, 0x008, 0x006, 0x000, 0x00B, 0x000, 0x000, 0x000, 0x000,
+ 0x009, 0x00A, 0x000, 0x007, 0x006, 0x00A, 0x000, 0x029, 0x006, 0x158, 0x000, 0x023,
+ 0x000, 0x000, 0x000, 0x000, 0x013, 0x174, 0x000, 0x021, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x007, 0x028, 0x000, 0x00B, 0x012, 0x020, 0x000, 0x175, 0x000, 0x000, 0x000, 0x000,
+ 0x007, 0x022, 0x000, 0x159, 0x00C, 0x0BC, 0x000, 0x0BF, 0x022, 0x2B8, 0x000, 0x2BB,
+ 0x000, 0x000, 0x000, 0x000, 0x00B, 0x170, 0x000, 0x15B, 0x000, 0x04E, 0x000, 0x15F,
+ 0x042, 0x04A, 0x000, 0x041, 0x000, 0x000, 0x000, 0x000, 0x055, 0x044, 0x000, 0x04D,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x02D, 0x172, 0x000, 0x2ED, 0x040, 0x042, 0x000, 0x047,
+ 0x000, 0x000, 0x000, 0x000, 0x013, 0x2EE, 0x000, 0x049, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x00D, 0x0BE, 0x000, 0x0BD, 0x00A, 0x15A, 0x000, 0x171, 0x000, 0x000, 0x000, 0x000,
+ 0x023, 0x2BA, 0x000, 0x2B9, 0x02C, 0x2EC, 0x000, 0x173, 0x012, 0x048, 0x000, 0x2EF,
+ 0x000, 0x000, 0x000, 0x000, 0x041, 0x046, 0x000, 0x043, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x001, 0x15E, 0x000, 0x04F, 0x054, 0x04C, 0x000, 0x045, 0x000, 0x000, 0x000, 0x000,
+ 0x043, 0x040, 0x000, 0x04B
+ };
+
+ private static readonly byte[] SpectrumB23Bits =
+ {
+ 02, 04, 00, 04, 04, 06, 00, 06, 00, 00, 00, 00, 04, 06, 00, 06,
+ 04, 09, 00, 07, 07, 09, 00, 08, 00, 00, 00, 00, 07, 09, 00, 08,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 04, 07, 00, 09, 07, 08, 00, 09, 00, 00, 00, 00, 07, 08, 00, 09,
+ 04, 08, 00, 08, 09, 10, 00, 10, 00, 00, 00, 00, 07, 10, 00, 10,
+ 07, 10, 00, 10, 10, 10, 00, 10, 00, 00, 00, 00, 09, 10, 00, 10,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 07, 10, 00, 10, 10, 10, 00, 10, 00, 00, 00, 00, 08, 10, 00, 10,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 04, 08, 00, 08, 07, 10, 00, 10, 00, 00, 00, 00, 09, 10, 00, 10,
+ 07, 10, 00, 10, 08, 10, 00, 10, 00, 00, 00, 00, 10, 10, 00, 10,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 07, 10, 00, 10, 09, 10, 00, 10, 00, 00, 00, 00, 10, 10, 00, 10
+ };
+
+ private static readonly short[] SpectrumB23Codes =
+ {
+ 0x003, 0x008, 0x000, 0x009, 0x002, 0x018, 0x000, 0x01B, 0x000, 0x000, 0x000, 0x000,
+ 0x003, 0x01A, 0x000, 0x019, 0x000, 0x17C, 0x000, 0x055, 0x056, 0x0E8, 0x000, 0x07D,
+ 0x000, 0x000, 0x000, 0x000, 0x059, 0x0F6, 0x000, 0x07F, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x001, 0x054, 0x000, 0x17D, 0x058, 0x07E, 0x000, 0x0F7, 0x000, 0x000, 0x000, 0x000,
+ 0x057, 0x07C, 0x000, 0x0E9, 0x004, 0x0A2, 0x000, 0x0A1, 0x17A, 0x1DA, 0x000, 0x1D9,
+ 0x000, 0x000, 0x000, 0x000, 0x053, 0x1E8, 0x000, 0x2F3, 0x05C, 0x1D6, 0x000, 0x1E7,
+ 0x1EA, 0x1E2, 0x000, 0x1CF, 0x000, 0x000, 0x000, 0x000, 0x17F, 0x1CA, 0x000, 0x1DD,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x05B, 0x2F0, 0x000, 0x1DF, 0x1E4, 0x1CC, 0x000, 0x1D5,
+ 0x000, 0x000, 0x000, 0x000, 0x071, 0x1E0, 0x000, 0x1C9, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x005, 0x0A0, 0x000, 0x0A3, 0x052, 0x2F2, 0x000, 0x1E9, 0x000, 0x000, 0x000, 0x000,
+ 0x17B, 0x1D8, 0x000, 0x1DB, 0x05A, 0x1DE, 0x000, 0x2F1, 0x070, 0x1C8, 0x000, 0x1E1,
+ 0x000, 0x000, 0x000, 0x000, 0x1E5, 0x1D4, 0x000, 0x1CD, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x05D, 0x1E6, 0x000, 0x1D7, 0x17E, 0x1DC, 0x000, 0x1CB, 0x000, 0x000, 0x000, 0x000,
+ 0x1EB, 0x1CE, 0x000, 0x1E3
+ };
+
+ private static readonly byte[] SpectrumB24Bits =
+ {
+ 01, 04, 00, 04, 05, 07, 00, 07, 00, 00, 00, 00, 05, 07, 00, 07,
+ 05, 09, 00, 07, 08, 10, 00, 09, 00, 00, 00, 00, 07, 10, 00, 09,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 05, 07, 00, 09, 07, 09, 00, 10, 00, 00, 00, 00, 08, 09, 00, 10,
+ 05, 09, 00, 08, 09, 10, 00, 10, 00, 00, 00, 00, 07, 10, 00, 10,
+ 07, 10, 00, 10, 10, 10, 00, 10, 00, 00, 00, 00, 10, 10, 00, 10,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 07, 10, 00, 10, 10, 10, 00, 10, 00, 00, 00, 00, 10, 10, 00, 10,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 05, 08, 00, 09, 07, 10, 00, 10, 00, 00, 00, 00, 09, 10, 00, 10,
+ 07, 10, 00, 10, 10, 10, 00, 10, 00, 00, 00, 00, 10, 10, 00, 10,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 07, 10, 00, 10, 10, 10, 00, 10, 00, 00, 00, 00, 10, 10, 00, 10
+ };
+
+ private static readonly short[] SpectrumB24Codes =
+ {
+ 0x001, 0x000, 0x000, 0x001, 0x00A, 0x01C, 0x000, 0x033, 0x000, 0x000, 0x000, 0x000,
+ 0x00B, 0x032, 0x000, 0x01D, 0x008, 0x0D8, 0x000, 0x031, 0x06E, 0x0FA, 0x000, 0x0D7,
+ 0x000, 0x000, 0x000, 0x000, 0x011, 0x0F4, 0x000, 0x0D5, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x009, 0x030, 0x000, 0x0D9, 0x010, 0x0D4, 0x000, 0x0F5, 0x000, 0x000, 0x000, 0x000,
+ 0x06F, 0x0D6, 0x000, 0x0FB, 0x00E, 0x0DA, 0x000, 0x025, 0x0D2, 0x0D4, 0x000, 0x0DB,
+ 0x000, 0x000, 0x000, 0x000, 0x017, 0x0FE, 0x000, 0x0FD, 0x014, 0x0DC, 0x000, 0x0F9,
+ 0x0F2, 0x0D6, 0x000, 0x09B, 0x000, 0x000, 0x000, 0x000, 0x1A3, 0x09C, 0x000, 0x0D3,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x019, 0x0F6, 0x000, 0x0D9, 0x0F0, 0x09E, 0x000, 0x0D1,
+ 0x000, 0x000, 0x000, 0x000, 0x1A1, 0x0DE, 0x000, 0x099, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x00F, 0x024, 0x000, 0x0DB, 0x016, 0x0FC, 0x000, 0x0FF, 0x000, 0x000, 0x000, 0x000,
+ 0x0D3, 0x0DA, 0x000, 0x0D5, 0x018, 0x0D8, 0x000, 0x0F7, 0x1A0, 0x098, 0x000, 0x0DF,
+ 0x000, 0x000, 0x000, 0x000, 0x0F1, 0x0D0, 0x000, 0x09F, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x015, 0x0F8, 0x000, 0x0DD, 0x1A2, 0x0D2, 0x000, 0x09D, 0x000, 0x000, 0x000, 0x000,
+ 0x0F3, 0x09A, 0x000, 0x0D7
+ };
+
+ private static readonly byte[] SpectrumB32Bits =
+ {
+ 2, 4, 5, 6, 0, 6, 5, 4, 5, 6, 6, 7, 0, 6, 5, 6,
+ 5, 6, 7, 7, 0, 8, 7, 6, 6, 7, 8, 9, 0, 9, 8, 7,
+ 0, 0, 0, 0, 0, 0, 0, 0, 6, 7, 8, 9, 0, 9, 8, 7,
+ 5, 6, 7, 8, 0, 7, 7, 6, 5, 6, 5, 6, 0, 7, 6, 6
+ };
+
+ private static readonly short[] SpectrumB32Codes =
+ {
+ 0x001, 0x002, 0x01E, 0x02A, 0x000, 0x02B, 0x01F, 0x003, 0x016, 0x020, 0x03A, 0x064,
+ 0x000, 0x005, 0x001, 0x023, 0x01A, 0x026, 0x070, 0x00C, 0x000, 0x0CF, 0x073, 0x031,
+ 0x024, 0x00E, 0x0CC, 0x146, 0x000, 0x145, 0x0A1, 0x053, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x025, 0x052, 0x0A0, 0x144, 0x000, 0x147, 0x0CD, 0x00F,
+ 0x01B, 0x030, 0x072, 0x0CE, 0x000, 0x00D, 0x071, 0x027, 0x017, 0x022, 0x000, 0x004,
+ 0x000, 0x065, 0x03B, 0x021
+ };
+
+ private static readonly byte[] SpectrumB33Bits =
+ {
+ 02, 04, 05, 07, 00, 07, 05, 04, 04, 05, 06, 08, 00, 07, 06, 05,
+ 05, 06, 07, 09, 00, 08, 07, 06, 07, 08, 09, 10, 00, 10, 09, 08,
+ 00, 00, 00, 00, 00, 00, 00, 00, 07, 08, 09, 10, 00, 10, 09, 08,
+ 05, 06, 07, 08, 00, 09, 07, 06, 04, 05, 06, 07, 00, 08, 06, 05
+ };
+
+ private static readonly short[] SpectrumB33Codes =
+ {
+ 0x003, 0x008, 0x014, 0x05E, 0x000, 0x05F, 0x015, 0x009, 0x004, 0x002, 0x01C, 0x0BA,
+ 0x000, 0x011, 0x01F, 0x001, 0x00C, 0x00C, 0x014, 0x166, 0x000, 0x02D, 0x013, 0x00F,
+ 0x05A, 0x0B0, 0x05E, 0x0B8, 0x000, 0x0BB, 0x165, 0x0B9, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x05B, 0x0B8, 0x164, 0x0BA, 0x000, 0x0B9, 0x05F, 0x0B1,
+ 0x00D, 0x00E, 0x012, 0x02C, 0x000, 0x167, 0x015, 0x00D, 0x005, 0x000, 0x01E, 0x010,
+ 0x000, 0x0BB, 0x01D, 0x003
+ };
+
+ private static readonly byte[] SpectrumB34Bits =
+ {
+ 01, 04, 06, 08, 00, 08, 06, 04, 04, 06, 07, 09, 00, 08, 07, 06,
+ 06, 07, 08, 10, 00, 10, 08, 07, 08, 09, 10, 10, 00, 10, 10, 09,
+ 00, 00, 00, 00, 00, 00, 00, 00, 08, 09, 10, 10, 00, 10, 10, 09,
+ 06, 07, 08, 10, 00, 10, 08, 07, 04, 06, 07, 08, 00, 09, 07, 06
+ };
+
+ private static readonly short[] SpectrumB34Codes =
+ {
+ 0x000, 0x00A, 0x038, 0x0EE, 0x000, 0x0EF, 0x039, 0x00B, 0x008, 0x03C, 0x06E, 0x1D8,
+ 0x000, 0x0C1, 0x075, 0x03F, 0x032, 0x068, 0x0C4, 0x358, 0x000, 0x30F, 0x0C7, 0x06D,
+ 0x0D4, 0x1AE, 0x30C, 0x308, 0x000, 0x30B, 0x35B, 0x1DB, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x0D5, 0x1DA, 0x35A, 0x30A, 0x000, 0x309, 0x30D, 0x1AF,
+ 0x033, 0x06C, 0x0C6, 0x30E, 0x000, 0x359, 0x0C5, 0x069, 0x009, 0x03E, 0x074, 0x0C0,
+ 0x000, 0x1D9, 0x06F, 0x03D
+ };
+
+ private static readonly byte[] SpectrumB42Bits =
+ {
+ 04, 05, 06, 08, 06, 07, 08, 08, 00, 08, 08, 07, 06, 08, 06, 05,
+ 05, 06, 07, 08, 07, 07, 08, 09, 00, 08, 08, 07, 07, 08, 07, 06,
+ 07, 07, 08, 09, 07, 08, 09, 09, 00, 09, 09, 08, 07, 09, 08, 07,
+ 08, 09, 09, 10, 08, 08, 09, 10, 00, 10, 09, 08, 08, 10, 09, 08,
+ 06, 07, 08, 08, 09, 09, 10, 10, 00, 10, 10, 09, 09, 08, 08, 07,
+ 07, 07, 08, 09, 09, 10, 10, 10, 00, 10, 10, 10, 09, 09, 08, 07,
+ 08, 08, 09, 09, 10, 10, 10, 10, 00, 10, 10, 10, 10, 09, 09, 08,
+ 08, 09, 09, 10, 10, 10, 10, 10, 00, 10, 10, 10, 10, 10, 09, 09,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 08, 09, 09, 10, 10, 10, 10, 10, 00, 10, 10, 10, 10, 10, 09, 09,
+ 08, 08, 09, 09, 10, 10, 10, 10, 00, 10, 10, 10, 10, 09, 09, 08,
+ 07, 07, 08, 09, 09, 10, 10, 10, 00, 10, 10, 10, 09, 09, 08, 07,
+ 06, 07, 08, 08, 09, 09, 10, 10, 00, 10, 10, 09, 09, 08, 08, 07,
+ 08, 08, 09, 10, 08, 08, 09, 10, 00, 10, 09, 08, 08, 10, 09, 09,
+ 07, 07, 08, 09, 07, 08, 09, 09, 00, 09, 09, 08, 07, 09, 08, 07,
+ 05, 06, 07, 08, 07, 07, 08, 08, 00, 09, 08, 07, 07, 08, 07, 06
+ };
+
+ private static readonly short[] SpectrumB42Codes =
+ {
+ 0x00E, 0x018, 0x010, 0x0F0, 0x024, 0x05A, 0x0F6, 0x078, 0x000, 0x079, 0x0F7, 0x05B,
+ 0x025, 0x0F1, 0x011, 0x019, 0x00C, 0x014, 0x01C, 0x036, 0x05C, 0x012, 0x09E, 0x1E4,
+ 0x000, 0x00B, 0x0A9, 0x03B, 0x05F, 0x071, 0x019, 0x017, 0x06E, 0x000, 0x03E, 0x114,
+ 0x002, 0x0B0, 0x1AA, 0x07A, 0x000, 0x099, 0x1E7, 0x0B3, 0x00B, 0x131, 0x07F, 0x00D,
+ 0x0D8, 0x1FE, 0x112, 0x22E, 0x086, 0x010, 0x134, 0x35C, 0x000, 0x35F, 0x133, 0x013,
+ 0x081, 0x22D, 0x119, 0x07B, 0x00A, 0x050, 0x0F8, 0x04E, 0x1B4, 0x154, 0x3EC, 0x0D2,
+ 0x000, 0x0D7, 0x3D7, 0x137, 0x1FD, 0x073, 0x0FD, 0x057, 0x052, 0x010, 0x08E, 0x1E8,
+ 0x11A, 0x3EE, 0x0F2, 0x03C, 0x000, 0x03F, 0x0F1, 0x3D5, 0x111, 0x1F5, 0x09D, 0x025,
+ 0x0D2, 0x082, 0x1A0, 0x0F8, 0x36E, 0x0D4, 0x072, 0x03A, 0x000, 0x027, 0x071, 0x07D,
+ 0x36D, 0x0FB, 0x1AD, 0x085, 0x00C, 0x1A8, 0x03C, 0x346, 0x0D0, 0x076, 0x024, 0x020,
+ 0x000, 0x023, 0x039, 0x075, 0x07F, 0x345, 0x09B, 0x157, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x00D, 0x156, 0x09A, 0x344, 0x07E, 0x074, 0x038, 0x022, 0x000, 0x021, 0x025, 0x077,
+ 0x0D1, 0x347, 0x03D, 0x1A9, 0x0D3, 0x084, 0x1AC, 0x0FA, 0x36C, 0x07C, 0x070, 0x026,
+ 0x000, 0x03B, 0x073, 0x0D5, 0x36F, 0x0F9, 0x1A1, 0x083, 0x053, 0x024, 0x09C, 0x1F4,
+ 0x110, 0x3D4, 0x0F0, 0x03E, 0x000, 0x03D, 0x0F3, 0x3EF, 0x11B, 0x1E9, 0x08F, 0x011,
+ 0x00B, 0x056, 0x0FC, 0x072, 0x1FC, 0x136, 0x3D6, 0x0D6, 0x000, 0x0D3, 0x3ED, 0x155,
+ 0x1B5, 0x04F, 0x0F9, 0x051, 0x0D9, 0x07A, 0x118, 0x22C, 0x080, 0x012, 0x132, 0x35E,
+ 0x000, 0x35D, 0x135, 0x011, 0x087, 0x22F, 0x113, 0x1FF, 0x06F, 0x00C, 0x07E, 0x130,
+ 0x00A, 0x0B2, 0x1E6, 0x098, 0x000, 0x07B, 0x1AB, 0x0B1, 0x003, 0x115, 0x03F, 0x001,
+ 0x00D, 0x016, 0x018, 0x070, 0x05E, 0x03A, 0x0A8, 0x00A, 0x000, 0x1E5, 0x09F, 0x013,
+ 0x05D, 0x037, 0x01D, 0x015
+ };
+
+ private static readonly byte[] SpectrumB43Bits =
+ {
+ 02, 05, 06, 07, 07, 08, 08, 09, 00, 09, 08, 08, 07, 07, 06, 05,
+ 05, 06, 07, 08, 07, 08, 09, 10, 00, 10, 09, 08, 07, 08, 07, 06,
+ 06, 07, 08, 09, 08, 09, 10, 10, 00, 10, 10, 09, 08, 09, 08, 07,
+ 07, 08, 09, 10, 09, 09, 10, 10, 00, 10, 10, 10, 09, 10, 09, 08,
+ 07, 08, 08, 09, 10, 10, 10, 10, 00, 10, 10, 10, 10, 09, 08, 07,
+ 08, 08, 09, 10, 10, 10, 10, 10, 00, 10, 10, 10, 10, 10, 09, 08,
+ 09, 09, 10, 10, 10, 10, 10, 10, 00, 10, 10, 10, 10, 10, 10, 09,
+ 10, 10, 10, 10, 10, 10, 10, 10, 00, 10, 10, 10, 10, 10, 10, 10,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 10, 10, 10, 10, 10, 10, 10, 10, 00, 10, 10, 10, 10, 10, 10, 10,
+ 09, 09, 10, 10, 10, 10, 10, 10, 00, 10, 10, 10, 10, 10, 10, 09,
+ 08, 08, 09, 10, 10, 10, 10, 10, 00, 10, 10, 10, 10, 10, 09, 08,
+ 07, 07, 08, 09, 10, 10, 10, 10, 00, 10, 10, 10, 10, 09, 08, 08,
+ 07, 08, 09, 10, 09, 10, 10, 10, 00, 10, 10, 09, 09, 10, 09, 08,
+ 06, 07, 08, 09, 08, 09, 10, 10, 00, 10, 10, 09, 08, 09, 08, 07,
+ 05, 06, 07, 08, 07, 08, 09, 10, 00, 10, 09, 08, 07, 08, 07, 06
+ };
+
+ private static readonly short[] SpectrumB43Codes =
+ {
+ 0x001, 0x01E, 0x022, 0x018, 0x064, 0x0EC, 0x008, 0x100, 0x000, 0x101, 0x009, 0x0ED,
+ 0x065, 0x019, 0x023, 0x01F, 0x01A, 0x030, 0x056, 0x09A, 0x00A, 0x090, 0x12C, 0x0A6,
+ 0x000, 0x0A9, 0x12F, 0x093, 0x00F, 0x09F, 0x059, 0x039, 0x00E, 0x054, 0x0BC, 0x19E,
+ 0x082, 0x176, 0x0AC, 0x088, 0x000, 0x08B, 0x0AF, 0x19D, 0x095, 0x1D1, 0x0BF, 0x051,
+ 0x002, 0x098, 0x1D4, 0x0B8, 0x170, 0x046, 0x090, 0x060, 0x000, 0x067, 0x095, 0x0BD,
+ 0x173, 0x0B5, 0x1D3, 0x09D, 0x052, 0x0EE, 0x034, 0x174, 0x0BA, 0x09C, 0x080, 0x044,
+ 0x000, 0x047, 0x06D, 0x099, 0x0BF, 0x16F, 0x085, 0x001, 0x0CC, 0x036, 0x16C, 0x0B0,
+ 0x09A, 0x084, 0x04E, 0x03E, 0x000, 0x037, 0x04B, 0x06B, 0x0A1, 0x0B3, 0x16B, 0x087,
+ 0x1D6, 0x102, 0x0A4, 0x092, 0x068, 0x04C, 0x034, 0x030, 0x000, 0x02D, 0x03D, 0x049,
+ 0x083, 0x097, 0x0AB, 0x169, 0x0B6, 0x09E, 0x06E, 0x064, 0x040, 0x038, 0x02E, 0x02A,
+ 0x000, 0x029, 0x033, 0x03B, 0x043, 0x063, 0x087, 0x0A3, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x0B7, 0x0A2, 0x086, 0x062, 0x042, 0x03A, 0x032, 0x028, 0x000, 0x02B, 0x02F, 0x039,
+ 0x041, 0x065, 0x06F, 0x09F, 0x1D7, 0x168, 0x0AA, 0x096, 0x082, 0x048, 0x03C, 0x02C,
+ 0x000, 0x031, 0x035, 0x04D, 0x069, 0x093, 0x0A5, 0x103, 0x0CD, 0x086, 0x16A, 0x0B2,
+ 0x0A0, 0x06A, 0x04A, 0x036, 0x000, 0x03F, 0x04F, 0x085, 0x09B, 0x0B1, 0x16D, 0x037,
+ 0x053, 0x000, 0x084, 0x16E, 0x0BE, 0x098, 0x06C, 0x046, 0x000, 0x045, 0x081, 0x09D,
+ 0x0BB, 0x175, 0x035, 0x0EF, 0x003, 0x09C, 0x1D2, 0x0B4, 0x172, 0x0BC, 0x094, 0x066,
+ 0x000, 0x061, 0x091, 0x047, 0x171, 0x0B9, 0x1D5, 0x099, 0x00F, 0x050, 0x0BE, 0x1D0,
+ 0x094, 0x19C, 0x0AE, 0x08A, 0x000, 0x089, 0x0AD, 0x177, 0x083, 0x19F, 0x0BD, 0x055,
+ 0x01B, 0x038, 0x058, 0x09E, 0x00E, 0x092, 0x12E, 0x0A8, 0x000, 0x0A7, 0x12D, 0x091,
+ 0x00B, 0x09B, 0x057, 0x031
+ };
+
+ private static readonly byte[] SpectrumB44Bits =
+ {
+ 02, 04, 06, 07, 07, 08, 10, 10, 00, 10, 10, 08, 07, 07, 06, 04,
+ 05, 05, 07, 08, 08, 10, 10, 10, 00, 10, 10, 10, 08, 08, 07, 05,
+ 06, 07, 08, 09, 09, 10, 10, 10, 00, 10, 10, 10, 10, 09, 08, 07,
+ 08, 08, 09, 10, 10, 10, 10, 10, 00, 10, 10, 10, 10, 10, 10, 08,
+ 08, 08, 10, 10, 10, 10, 10, 10, 00, 10, 10, 10, 10, 10, 10, 08,
+ 09, 10, 10, 10, 10, 10, 10, 10, 00, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 00, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 00, 10, 10, 10, 10, 10, 10, 10,
+ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
+ 10, 10, 10, 10, 10, 10, 10, 10, 00, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 00, 10, 10, 10, 10, 10, 10, 10,
+ 09, 10, 10, 10, 10, 10, 10, 10, 00, 10, 10, 10, 10, 10, 10, 10,
+ 08, 08, 10, 10, 10, 10, 10, 10, 00, 10, 10, 10, 10, 10, 10, 08,
+ 08, 08, 10, 10, 10, 10, 10, 10, 00, 10, 10, 10, 10, 10, 09, 08,
+ 06, 07, 08, 09, 10, 10, 10, 10, 00, 10, 10, 10, 09, 09, 08, 07,
+ 05, 05, 07, 08, 08, 10, 10, 10, 00, 10, 10, 10, 08, 08, 07, 05
+ };
+
+ private static readonly short[] SpectrumB44Codes =
+ {
+ 0x002, 0x002, 0x030, 0x000, 0x002, 0x00C, 0x1D2, 0x1AE, 0x000, 0x1AF, 0x1D3, 0x00D,
+ 0x003, 0x001, 0x031, 0x003, 0x01E, 0x002, 0x070, 0x0C8, 0x07E, 0x1E8, 0x1C0, 0x176,
+ 0x000, 0x17F, 0x1C3, 0x1EB, 0x0CF, 0x0D3, 0x073, 0x009, 0x018, 0x06A, 0x0EC, 0x1DE,
+ 0x1A2, 0x1CA, 0x1AA, 0x164, 0x000, 0x16D, 0x1AD, 0x1D1, 0x1EF, 0x1DD, 0x0EB, 0x06D,
+ 0x0E8, 0x0CA, 0x1BE, 0x1CE, 0x1DA, 0x1B6, 0x170, 0x154, 0x000, 0x153, 0x173, 0x1B1,
+ 0x1D7, 0x1D5, 0x343, 0x0CD, 0x0DC, 0x078, 0x340, 0x1CC, 0x1BA, 0x1A8, 0x156, 0x148,
+ 0x000, 0x145, 0x15F, 0x1A1, 0x1BD, 0x1D9, 0x1ED, 0x07D, 0x1BC, 0x1DC, 0x1C4, 0x1B2,
+ 0x17C, 0x15A, 0x14A, 0x03A, 0x000, 0x039, 0x147, 0x16B, 0x17B, 0x1B5, 0x1C9, 0x1DF,
+ 0x1C6, 0x1B8, 0x1A2, 0x168, 0x160, 0x14C, 0x02E, 0x024, 0x000, 0x027, 0x03D, 0x151,
+ 0x15D, 0x16F, 0x1A7, 0x1BF, 0x1A4, 0x174, 0x162, 0x14E, 0x140, 0x02C, 0x02A, 0x022,
+ 0x000, 0x021, 0x029, 0x03F, 0x143, 0x159, 0x167, 0x179, 0x000, 0x000, 0x000, 0x000,
+ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
+ 0x1A5, 0x178, 0x166, 0x158, 0x142, 0x03E, 0x028, 0x020, 0x000, 0x023, 0x02B, 0x02D,
+ 0x141, 0x14F, 0x163, 0x175, 0x1C7, 0x1BE, 0x1A6, 0x16E, 0x15C, 0x150, 0x03C, 0x026,
+ 0x000, 0x025, 0x02F, 0x14D, 0x161, 0x169, 0x1A3, 0x1B9, 0x1BD, 0x1DE, 0x1C8, 0x1B4,
+ 0x17A, 0x16A, 0x146, 0x038, 0x000, 0x03B, 0x14B, 0x15B, 0x17D, 0x1B3, 0x1C5, 0x1DD,
+ 0x0DD, 0x07C, 0x1EC, 0x1D8, 0x1BC, 0x1A0, 0x15E, 0x144, 0x000, 0x149, 0x157, 0x1A9,
+ 0x1BB, 0x1CD, 0x341, 0x079, 0x0E9, 0x0CC, 0x342, 0x1D4, 0x1D6, 0x1B0, 0x172, 0x152,
+ 0x000, 0x155, 0x171, 0x1B7, 0x1DB, 0x1CF, 0x1BF, 0x0CB, 0x019, 0x06C, 0x0EA, 0x1DC,
+ 0x1EE, 0x1D0, 0x1AC, 0x16C, 0x000, 0x165, 0x1AB, 0x1CB, 0x1A3, 0x1DF, 0x0ED, 0x06B,
+ 0x01F, 0x008, 0x072, 0x0D2, 0x0CE, 0x1EA, 0x1C2, 0x17E, 0x000, 0x177, 0x1C1, 0x1E9,
+ 0x07F, 0x0C9, 0x071, 0x003
+ };
+
+ private static readonly byte[] SpectrumB52Bits =
+ {
+ 3, 4, 4, 4, 5, 5, 6, 6, 5, 5, 5, 6, 6, 6, 7, 7,
+ 0, 7, 7, 6, 6, 6, 5, 5, 5, 6, 6, 5, 5, 4, 4, 4
+ };
+
+ private static readonly short[] SpectrumB52Codes =
+ {
+ 0x06, 0x0E, 0x06, 0x00, 0x0A, 0x04, 0x2C, 0x12, 0x14, 0x10, 0x06, 0x2E, 0x24, 0x10, 0x4E, 0x4C,
+ 0x00, 0x4D, 0x4F, 0x11, 0x25, 0x2F, 0x07, 0x11, 0x15, 0x13, 0x2D, 0x05, 0x0B, 0x01, 0x07, 0x0F
+ };
+
+ private static readonly byte[] SpectrumB53Bits =
+ {
+ 2, 3, 4, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 8, 8,
+ 0, 8, 8, 7, 7, 7, 6, 6, 6, 6, 6, 6, 5, 5, 4, 3
+ };
+
+ private static readonly short[] SpectrumB53Codes =
+ {
+ 0x02, 0x00, 0x06, 0x1C, 0x18, 0x3E, 0x16, 0x10, 0x3C, 0x36, 0x14, 0x6A, 0x26, 0x24, 0xD2, 0xD0,
+ 0x00, 0xD1, 0xD3, 0x25, 0x27, 0x6B, 0x15, 0x37, 0x3D, 0x11, 0x17, 0x3F, 0x19, 0x1D, 0x07, 0x01
+ };
+
+ private static readonly byte[] SpectrumB54Bits =
+ {
+ 2, 3, 4, 4, 5, 6, 6, 7, 6, 6, 7, 8, 8, 8, 9, 9,
+ 0, 9, 9, 8, 8, 8, 7, 6, 6, 7, 6, 6, 5, 4, 4, 3
+ };
+
+ private static readonly short[] SpectrumB54Codes =
+ {
+ 0x003, 0x002, 0x008, 0x000, 0x014, 0x02E, 0x00E, 0x05A, 0x00A, 0x008, 0x01A, 0x0B2,
+ 0x032, 0x030, 0x162, 0x160, 0x000, 0x161, 0x163, 0x031, 0x033, 0x0B3, 0x01B, 0x009,
+ 0x00B, 0x05B, 0x00F, 0x02F, 0x015, 0x001, 0x009, 0x003
+ };
+
+ private static readonly byte[] SpectrumB62Bits =
+ {
+ 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7,
+ 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8,
+ 0, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6,
+ 6, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 4
+ };
+
+ private static readonly short[] SpectrumB62Codes =
+ {
+ 0x0D, 0x06, 0x1C, 0x14, 0x0A, 0x04, 0x3E, 0x2E, 0x22, 0x0E, 0x06, 0x00, 0x5A, 0x4E, 0x40, 0x20,
+ 0x30, 0x32, 0x24, 0x12, 0x0C, 0x02, 0x78, 0x58, 0x42, 0x22, 0x0A, 0x08, 0xF6, 0xF4, 0x9A, 0x98,
+ 0x00, 0x99, 0x9B, 0xF5, 0xF7, 0x09, 0x0B, 0x23, 0x43, 0x59, 0x79, 0x03, 0x0D, 0x13, 0x25, 0x33,
+ 0x31, 0x21, 0x41, 0x4F, 0x5B, 0x01, 0x07, 0x0F, 0x23, 0x2F, 0x3F, 0x05, 0x0B, 0x15, 0x1D, 0x07
+ };
+
+ private static readonly byte[] SpectrumB63Bits =
+ {
+ 3, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8,
+ 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9,
+ 0, 9, 9, 9, 9, 9, 9, 8, 8, 8, 8, 7, 7, 7, 7, 6,
+ 6, 8, 7, 7, 7, 7, 6, 6, 6, 6, 5, 5, 5, 5, 4, 4
+ };
+
+ private static readonly short[] SpectrumB63Codes =
+ {
+ 0x006, 0x00E, 0x004, 0x014, 0x010, 0x006, 0x000, 0x026, 0x01C, 0x018, 0x004, 0x05C,
+ 0x04A, 0x03C, 0x016, 0x0BC, 0x006, 0x008, 0x058, 0x03E, 0x036, 0x014, 0x0B6, 0x0B4,
+ 0x090, 0x068, 0x17E, 0x17C, 0x126, 0x124, 0x0D6, 0x0D4, 0x000, 0x0D5, 0x0D7, 0x125,
+ 0x127, 0x17D, 0x17F, 0x069, 0x091, 0x0B5, 0x0B7, 0x015, 0x037, 0x03F, 0x059, 0x009,
+ 0x007, 0x0BD, 0x017, 0x03D, 0x04B, 0x05D, 0x005, 0x019, 0x01D, 0x027, 0x001, 0x007,
+ 0x011, 0x015, 0x005, 0x00F
+ };
+
+ private static readonly byte[] SpectrumB64Bits =
+ {
+ 03, 03, 04, 05, 05, 05, 06, 06, 06, 06, 07, 07, 07, 07, 07, 08,
+ 07, 07, 07, 08, 08, 08, 09, 09, 09, 09, 09, 09, 10, 10, 10, 10,
+ 00, 10, 10, 10, 10, 09, 09, 09, 09, 09, 09, 08, 08, 08, 07, 07,
+ 07, 08, 07, 07, 07, 07, 07, 06, 06, 06, 06, 05, 05, 05, 04, 03
+ };
+
+ private static readonly short[] SpectrumB64Codes =
+ {
+ 0x007, 0x000, 0x008, 0x01A, 0x014, 0x00C, 0x032, 0x02E, 0x01E, 0x014, 0x062, 0x05A,
+ 0x03A, 0x026, 0x020, 0x0B2, 0x038, 0x02C, 0x022, 0x0C0, 0x05E, 0x04A, 0x186, 0x184,
+ 0x160, 0x0BA, 0x092, 0x090, 0x2C6, 0x2C4, 0x172, 0x170, 0x000, 0x171, 0x173, 0x2C5,
+ 0x2C7, 0x091, 0x093, 0x0BB, 0x161, 0x185, 0x187, 0x04B, 0x05F, 0x0C1, 0x023, 0x02D,
+ 0x039, 0x0B3, 0x021, 0x027, 0x03B, 0x05B, 0x063, 0x015, 0x01F, 0x02F, 0x033, 0x00D,
+ 0x015, 0x01B, 0x009, 0x001
+ };
+
+ private static readonly byte[] SpectrumB72Bits =
+ {
+ 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5
+ };
+
+ private static readonly short[] SpectrumB72Codes =
+ {
+ 0x01E, 0x016, 0x00C, 0x000, 0x038, 0x032, 0x028, 0x022, 0x01C, 0x012, 0x00E, 0x006,
+ 0x076, 0x06C, 0x060, 0x04E, 0x03E, 0x02A, 0x022, 0x01A, 0x012, 0x00A, 0x0FC, 0x0DC,
+ 0x0C6, 0x0A8, 0x094, 0x086, 0x058, 0x042, 0x040, 0x02A, 0x068, 0x07C, 0x06A, 0x056,
+ 0x048, 0x040, 0x02E, 0x028, 0x016, 0x010, 0x008, 0x0EA, 0x0DE, 0x0AA, 0x09A, 0x096,
+ 0x07A, 0x078, 0x05A, 0x032, 0x030, 0x028, 0x1FE, 0x1FC, 0x1D2, 0x1D0, 0x18A, 0x188,
+ 0x132, 0x130, 0x10A, 0x108, 0x000, 0x109, 0x10B, 0x131, 0x133, 0x189, 0x18B, 0x1D1,
+ 0x1D3, 0x1FD, 0x1FF, 0x029, 0x031, 0x033, 0x05B, 0x079, 0x07B, 0x097, 0x09B, 0x0AB,
+ 0x0DF, 0x0EB, 0x009, 0x011, 0x017, 0x029, 0x02F, 0x041, 0x049, 0x057, 0x06B, 0x07D,
+ 0x069, 0x02B, 0x041, 0x043, 0x059, 0x087, 0x095, 0x0A9, 0x0C7, 0x0DD, 0x0FD, 0x00B,
+ 0x013, 0x01B, 0x023, 0x02B, 0x03F, 0x04F, 0x061, 0x06D, 0x077, 0x007, 0x00F, 0x013,
+ 0x01D, 0x023, 0x029, 0x033, 0x039, 0x001, 0x00D, 0x017
+ };
+
+ private static readonly byte[] SpectrumB73Bits =
+ {
+ 03, 04, 05, 05, 05, 06, 06, 06, 06, 06, 06, 07, 07, 07, 07, 07,
+ 07, 07, 07, 07, 08, 08, 08, 08, 08, 08, 08, 08, 08, 08, 09, 09,
+ 08, 07, 08, 08, 08, 08, 08, 08, 08, 08, 09, 09, 09, 09, 09, 09,
+ 09, 09, 09, 09, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 00, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 09, 09, 09,
+ 09, 09, 09, 09, 09, 09, 09, 08, 08, 08, 08, 08, 08, 08, 08, 07,
+ 08, 09, 09, 08, 08, 08, 08, 08, 08, 08, 08, 08, 08, 07, 07, 07,
+ 07, 07, 07, 07, 07, 07, 06, 06, 06, 06, 06, 06, 05, 05, 05, 04
+ };
+
+ private static readonly short[] SpectrumB73Codes =
+ {
+ 0x000, 0x006, 0x018, 0x010, 0x004, 0x03A, 0x034, 0x02A, 0x026, 0x014, 0x010, 0x07E,
+ 0x072, 0x06E, 0x05C, 0x052, 0x04A, 0x02C, 0x024, 0x018, 0x0F4, 0x0E0, 0x0DA, 0x0B6,
+ 0x0B2, 0x0A0, 0x05E, 0x04E, 0x038, 0x034, 0x1E6, 0x1B2, 0x0FA, 0x01E, 0x0F8, 0x0F0,
+ 0x0BE, 0x0B4, 0x0A2, 0x090, 0x04C, 0x03A, 0x1EE, 0x1E4, 0x1C6, 0x1B0, 0x178, 0x162,
+ 0x126, 0x124, 0x0B8, 0x06C, 0x3DA, 0x3D8, 0x38A, 0x388, 0x2F6, 0x2F4, 0x2C2, 0x2C0,
+ 0x176, 0x174, 0x0DC, 0x0DE, 0x000, 0x0DF, 0x0DD, 0x175, 0x177, 0x2C1, 0x2C3, 0x2F5,
+ 0x2F7, 0x389, 0x38B, 0x3D9, 0x3DB, 0x06D, 0x0B9, 0x125, 0x127, 0x163, 0x179, 0x1B1,
+ 0x1C7, 0x1E5, 0x1EF, 0x03B, 0x04D, 0x091, 0x0A3, 0x0B5, 0x0BF, 0x0F1, 0x0F9, 0x01F,
+ 0x0FB, 0x1B3, 0x1E7, 0x035, 0x039, 0x04F, 0x05F, 0x0A1, 0x0B3, 0x0B7, 0x0DB, 0x0E1,
+ 0x0F5, 0x019, 0x025, 0x02D, 0x04B, 0x053, 0x05D, 0x06F, 0x073, 0x07F, 0x011, 0x015,
+ 0x027, 0x02B, 0x035, 0x03B, 0x005, 0x011, 0x019, 0x007
+ };
+
+ private static readonly byte[] SpectrumB74Bits =
+ {
+ 03, 04, 05, 05, 05, 05, 06, 06, 06, 06, 06, 06, 07, 07, 07, 07,
+ 07, 07, 07, 07, 08, 08, 08, 08, 08, 08, 08, 08, 08, 09, 09, 09,
+ 08, 08, 08, 08, 08, 09, 09, 09, 09, 09, 09, 09, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 00, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 09, 09, 09, 09, 09, 09, 09, 08, 08, 08, 08,
+ 08, 09, 09, 09, 08, 08, 08, 08, 08, 08, 08, 08, 08, 07, 07, 07,
+ 07, 07, 07, 07, 07, 06, 06, 06, 06, 06, 06, 05, 05, 05, 05, 04
+ };
+
+ private static readonly short[] SpectrumB74Codes =
+ {
+ 0x001, 0x008, 0x01E, 0x018, 0x00C, 0x002, 0x03A, 0x034, 0x02C, 0x01E, 0x016, 0x012,
+ 0x072, 0x06E, 0x05E, 0x056, 0x050, 0x038, 0x022, 0x004, 0x0E2, 0x0DA, 0x0BA, 0x0A8,
+ 0x076, 0x054, 0x050, 0x002, 0x000, 0x1C0, 0x1B0, 0x156, 0x0A4, 0x0A6, 0x074, 0x052,
+ 0x004, 0x1C2, 0x1B2, 0x170, 0x154, 0x0AE, 0x0AC, 0x086, 0x2E6, 0x2E4, 0x10A, 0x108,
+ 0x106, 0x104, 0x102, 0x100, 0x03E, 0x03A, 0x03C, 0x038, 0x036, 0x034, 0x032, 0x030,
+ 0x01E, 0x01A, 0x01C, 0x018, 0x000, 0x019, 0x01D, 0x01B, 0x01F, 0x031, 0x033, 0x035,
+ 0x037, 0x039, 0x03D, 0x03B, 0x03F, 0x101, 0x103, 0x105, 0x107, 0x109, 0x10B, 0x2E5,
+ 0x2E7, 0x087, 0x0AD, 0x0AF, 0x155, 0x171, 0x1B3, 0x1C3, 0x005, 0x053, 0x075, 0x0A7,
+ 0x0A5, 0x157, 0x1B1, 0x1C1, 0x001, 0x003, 0x051, 0x055, 0x077, 0x0A9, 0x0BB, 0x0DB,
+ 0x0E3, 0x005, 0x023, 0x039, 0x051, 0x057, 0x05F, 0x06F, 0x073, 0x013, 0x017, 0x01F,
+ 0x02D, 0x035, 0x03B, 0x003, 0x00D, 0x019, 0x01F, 0x009
+ };
+
+ public static readonly byte[][] HuffmanScaleFactorsABits =
+ {
+ null,
+ ScaleFactorsA1Bits, ScaleFactorsA2Bits, ScaleFactorsA3Bits,
+ ScaleFactorsA4Bits, ScaleFactorsA5Bits, ScaleFactorsA6Bits
+ };
+
+ public static readonly short[][] HuffmanScaleFactorsACodes =
+ {
+ null,
+ ScaleFactorsA1Codes, ScaleFactorsA2Codes, ScaleFactorsA3Codes,
+ ScaleFactorsA4Codes, ScaleFactorsA5Codes, ScaleFactorsA6Codes
+ };
+
+ public static readonly byte[][] HuffmanScaleFactorsBBits =
+ {
+ null, null,
+ ScaleFactorsB2Bits, ScaleFactorsB3Bits, ScaleFactorsB4Bits, ScaleFactorsB5Bits
+ };
+
+ public static readonly short[][] HuffmanScaleFactorsBCodes =
+ {
+ null, null,
+ ScaleFactorsB2Codes, ScaleFactorsB3Codes, ScaleFactorsB4Codes, ScaleFactorsB5Codes
+ };
+
+ public static readonly byte[] HuffmanScaleFactorsGroupSizes = { 0, 0, 0, 0, 0, 0, 0 };
+
+ public static readonly byte[][][] HuffmanSpectrumABits =
+ {
+ null,
+ null,
+ new[] {SpectrumA21Bits, SpectrumA22Bits, SpectrumA23Bits, SpectrumA24Bits},
+ new[] {SpectrumA31Bits, SpectrumA32Bits, SpectrumA33Bits, SpectrumA34Bits},
+ new[] {SpectrumA41Bits, SpectrumA42Bits, SpectrumA43Bits, SpectrumA44Bits},
+ new[] {SpectrumA51Bits, SpectrumA52Bits, SpectrumA53Bits, SpectrumA54Bits},
+ new[] {SpectrumA61Bits, SpectrumA62Bits, SpectrumA63Bits, SpectrumA64Bits},
+ new[] {SpectrumA71Bits, SpectrumA72Bits, SpectrumA73Bits, SpectrumA74Bits}
+ };
+
+ public static readonly short[][][] HuffmanSpectrumACodes =
+ {
+ null,
+ null,
+ new[] {SpectrumA21Codes, SpectrumA22Codes, SpectrumA23Codes, SpectrumA24Codes},
+ new[] {SpectrumA31Codes, SpectrumA32Codes, SpectrumA33Codes, SpectrumA34Codes},
+ new[] {SpectrumA41Codes, SpectrumA42Codes, SpectrumA43Codes, SpectrumA44Codes},
+ new[] {SpectrumA51Codes, SpectrumA52Codes, SpectrumA53Codes, SpectrumA54Codes},
+ new[] {SpectrumA61Codes, SpectrumA62Codes, SpectrumA63Codes, SpectrumA64Codes},
+ new[] {SpectrumA71Codes, SpectrumA72Codes, SpectrumA73Codes, SpectrumA74Codes}
+ };
+
+ public static readonly byte[][] HuffmanSpectrumAGroupSizes =
+ {
+ new byte[] {0, 0, 0, 0},
+ new byte[] {0, 0, 0, 0},
+ new byte[] {1, 2, 2, 2},
+ new byte[] {1, 1, 1, 1},
+ new byte[] {1, 1, 1, 1},
+ new byte[] {0, 0, 0, 0},
+ new byte[] {0, 0, 0, 0},
+ new byte[] {0, 0, 0, 0}
+ };
+
+ public static readonly byte[][][] HuffmanSpectrumBBits =
+ {
+ null,
+ null,
+ new[] {null, SpectrumB22Bits, SpectrumB23Bits, SpectrumB24Bits},
+ new[] {null, SpectrumB32Bits, SpectrumB33Bits, SpectrumB34Bits},
+ new[] {null, SpectrumB42Bits, SpectrumB43Bits, SpectrumB44Bits},
+ new[] {null, SpectrumB52Bits, SpectrumB53Bits, SpectrumB54Bits},
+ new[] {null, SpectrumB62Bits, SpectrumB63Bits, SpectrumB64Bits},
+ new[] {null, SpectrumB72Bits, SpectrumB73Bits, SpectrumB74Bits}
+ };
+
+ public static readonly short[][][] HuffmanSpectrumBCodes =
+ {
+ null,
+ null,
+ new[] {null, SpectrumB22Codes, SpectrumB23Codes, SpectrumB24Codes},
+ new[] {null, SpectrumB32Codes, SpectrumB33Codes, SpectrumB34Codes},
+ new[] {null, SpectrumB42Codes, SpectrumB43Codes, SpectrumB44Codes},
+ new[] {null, SpectrumB52Codes, SpectrumB53Codes, SpectrumB54Codes},
+ new[] {null, SpectrumB62Codes, SpectrumB63Codes, SpectrumB64Codes},
+ new[] {null, SpectrumB72Codes, SpectrumB73Codes, SpectrumB74Codes}
+ };
+
+ public static readonly byte[][] HuffmanSpectrumBGroupSizes =
+ {
+ new byte[] {0, 0, 0, 0},
+ new byte[] {0, 0, 0, 0},
+ new byte[] {0, 2, 2, 2},
+ new byte[] {0, 1, 1, 1},
+ new byte[] {0, 1, 1, 1},
+ new byte[] {0, 0, 0, 0},
+ new byte[] {0, 0, 0, 0},
+ new byte[] {0, 0, 0, 0}
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/SharpEmu.GUI/Atrac9/LICENSE.txt b/src/SharpEmu.GUI/Atrac9/LICENSE.txt
new file mode 100644
index 0000000..2b5cc1f
--- /dev/null
+++ b/src/SharpEmu.GUI/Atrac9/LICENSE.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Alex Barney
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/src/SharpEmu.GUI/Atrac9/Quantization.cs b/src/SharpEmu.GUI/Atrac9/Quantization.cs
new file mode 100644
index 0000000..4e10dbf
--- /dev/null
+++ b/src/SharpEmu.GUI/Atrac9/Quantization.cs
@@ -0,0 +1,58 @@
+#nullable disable
+using System;
+
+namespace LibAtrac9
+{
+ internal static class Quantization
+ {
+ public static void DequantizeSpectra(Block block)
+ {
+ foreach (Channel channel in block.Channels)
+ {
+ Array.Clear(channel.Spectra, 0, channel.Spectra.Length);
+
+ for (int i = 0; i < channel.CodedQuantUnits; i++)
+ {
+ DequantizeQuantUnit(channel, i);
+ }
+ }
+ }
+
+ private static void DequantizeQuantUnit(Channel channel, int band)
+ {
+ int subBandIndex = Tables.QuantUnitToCoeffIndex[band];
+ int subBandCount = Tables.QuantUnitToCoeffCount[band];
+ double stepSize = Tables.QuantizerStepSize[channel.Precisions[band]];
+ double stepSizeFine = Tables.QuantizerFineStepSize[channel.PrecisionsFine[band]];
+
+ for (int sb = 0; sb < subBandCount; sb++)
+ {
+ double coarse = channel.QuantizedSpectra[subBandIndex + sb] * stepSize;
+ double fine = channel.QuantizedSpectraFine[subBandIndex + sb] * stepSizeFine;
+ channel.Spectra[subBandIndex + sb] = coarse + fine;
+ }
+ }
+
+ public static void ScaleSpectrum(Block block)
+ {
+ foreach (Channel channel in block.Channels)
+ {
+ ScaleSpectrum(channel);
+ }
+ }
+
+ private static void ScaleSpectrum(Channel channel)
+ {
+ int quantUnitCount = channel.Block.QuantizationUnitCount;
+ double[] spectra = channel.Spectra;
+
+ for (int i = 0; i < quantUnitCount; i++)
+ {
+ for (int sb = Tables.QuantUnitToCoeffIndex[i]; sb < Tables.QuantUnitToCoeffIndex[i + 1]; sb++)
+ {
+ spectra[sb] *= Tables.SpectrumScale[channel.ScaleFactors[i]];
+ }
+ }
+ }
+ }
+}
diff --git a/src/SharpEmu.GUI/Atrac9/ScaleFactors.cs b/src/SharpEmu.GUI/Atrac9/ScaleFactors.cs
new file mode 100644
index 0000000..ace9245
--- /dev/null
+++ b/src/SharpEmu.GUI/Atrac9/ScaleFactors.cs
@@ -0,0 +1,172 @@
+#nullable disable
+using System;
+using System.IO;
+using LibAtrac9.Utilities;
+
+namespace LibAtrac9
+{
+ internal static class ScaleFactors
+ {
+ public static void Read(BitReader reader, Channel channel)
+ {
+ Array.Clear(channel.ScaleFactors, 0, channel.ScaleFactors.Length);
+
+ channel.ScaleFactorCodingMode = reader.ReadInt(2);
+ if (channel.ChannelIndex == 0)
+ {
+ switch (channel.ScaleFactorCodingMode)
+ {
+ case 0:
+ ReadVlcDeltaOffset(reader, channel);
+ break;
+ case 1:
+ ReadClcOffset(reader, channel);
+ break;
+ case 2:
+ if (channel.Block.FirstInSuperframe) throw new InvalidDataException();
+ ReadVlcDistanceToBaseline(reader, channel, channel.ScaleFactorsPrev, channel.Block.QuantizationUnitsPrev);
+ break;
+ case 3:
+ if (channel.Block.FirstInSuperframe) throw new InvalidDataException();
+ ReadVlcDeltaOffsetWithBaseline(reader, channel, channel.ScaleFactorsPrev, channel.Block.QuantizationUnitsPrev);
+ break;
+ }
+ }
+ else
+ {
+ switch (channel.ScaleFactorCodingMode)
+ {
+ case 0:
+ ReadVlcDeltaOffset(reader, channel);
+ break;
+ case 1:
+ ReadVlcDistanceToBaseline(reader, channel, channel.Block.Channels[0].ScaleFactors, channel.Block.ExtensionUnit);
+ break;
+ case 2:
+ ReadVlcDeltaOffsetWithBaseline(reader, channel, channel.Block.Channels[0].ScaleFactors, channel.Block.ExtensionUnit);
+ break;
+ case 3:
+ if (channel.Block.FirstInSuperframe) throw new InvalidDataException();
+ ReadVlcDistanceToBaseline(reader, channel, channel.ScaleFactorsPrev, channel.Block.QuantizationUnitsPrev);
+ break;
+ }
+ }
+
+ for (int i = 0; i < channel.Block.ExtensionUnit; i++)
+ {
+ if (channel.ScaleFactors[i] < 0 || channel.ScaleFactors[i] > 31)
+ {
+ throw new InvalidDataException("Scale factor values are out of range.");
+ }
+ }
+
+ Array.Copy(channel.ScaleFactors, channel.ScaleFactorsPrev, channel.ScaleFactors.Length);
+ }
+
+ private static void ReadClcOffset(BitReader reader, Channel channel)
+ {
+ const int maxBits = 5;
+ int[] sf = channel.ScaleFactors;
+ int bitLength = reader.ReadInt(2) + 2;
+ int baseValue = bitLength < maxBits ? reader.ReadInt(maxBits) : 0;
+
+ for (int i = 0; i < channel.Block.ExtensionUnit; i++)
+ {
+ sf[i] = reader.ReadInt(bitLength) + baseValue;
+ }
+ }
+
+ private static void ReadVlcDeltaOffset(BitReader reader, Channel channel)
+ {
+ int weightIndex = reader.ReadInt(3);
+ byte[] weights = ScaleFactorWeights[weightIndex];
+
+ int[] sf = channel.ScaleFactors;
+ int baseValue = reader.ReadInt(5);
+ int bitLength = reader.ReadInt(2) + 3;
+ HuffmanCodebook codebook = Tables.HuffmanScaleFactorsUnsigned[bitLength];
+
+ sf[0] = reader.ReadInt(bitLength);
+
+ for (int i = 1; i < channel.Block.ExtensionUnit; i++)
+ {
+ int delta = Unpack.ReadHuffmanValue(codebook, reader);
+ sf[i] = (sf[i - 1] + delta) & (codebook.ValueMax - 1);
+ }
+
+ for (int i = 0; i < channel.Block.ExtensionUnit; i++)
+ {
+ sf[i] += baseValue - weights[i];
+ }
+ }
+
+ private static void ReadVlcDistanceToBaseline(BitReader reader, Channel channel, int[] baseline, int baselineLength)
+ {
+ int[] sf = channel.ScaleFactors;
+ int bitLength = reader.ReadInt(2) + 2;
+ HuffmanCodebook codebook = Tables.HuffmanScaleFactorsSigned[bitLength];
+ int unitCount = Math.Min(channel.Block.ExtensionUnit, baselineLength);
+
+ for (int i = 0; i < unitCount; i++)
+ {
+ int distance = Unpack.ReadHuffmanValue(codebook, reader, true);
+ sf[i] = (baseline[i] + distance) & 31;
+ }
+
+ for (int i = unitCount; i < channel.Block.ExtensionUnit; i++)
+ {
+ sf[i] = reader.ReadInt(5);
+ }
+ }
+
+ private static void ReadVlcDeltaOffsetWithBaseline(BitReader reader, Channel channel, int[] baseline, int baselineLength)
+ {
+ int[] sf = channel.ScaleFactors;
+ int baseValue = reader.ReadOffsetBinary(5, BitReader.OffsetBias.Negative);
+ int bitLength = reader.ReadInt(2) + 1;
+ HuffmanCodebook codebook = Tables.HuffmanScaleFactorsUnsigned[bitLength];
+ int unitCount = Math.Min(channel.Block.ExtensionUnit, baselineLength);
+
+ sf[0] = reader.ReadInt(bitLength);
+
+ for (int i = 1; i < unitCount; i++)
+ {
+ int delta = Unpack.ReadHuffmanValue(codebook, reader);
+ sf[i] = (sf[i - 1] + delta) & (codebook.ValueMax - 1);
+ }
+
+ for (int i = 0; i < unitCount; i++)
+ {
+ sf[i] += baseValue + baseline[i];
+ }
+
+ for (int i = unitCount; i < channel.Block.ExtensionUnit; i++)
+ {
+ sf[i] = reader.ReadInt(5);
+ }
+ }
+
+ public static readonly byte[][] ScaleFactorWeights =
+ {
+ new byte[] {
+ 0, 0, 0, 1, 1, 2, 2, 2, 2, 2, 2, 3, 2, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5, 6, 6, 7, 7, 8, 10, 12, 12, 12
+ }, new byte[] {
+ 3, 2, 2, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 2, 3, 3, 4, 5, 7, 10, 10, 10
+ }, new byte[] {
+ 0, 2, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 8, 9, 12, 12, 12
+ }, new byte[] {
+ 0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 6, 7, 8, 8, 10, 11, 11, 12, 13, 13, 13, 13
+ }, new byte[] {
+ 0, 2, 2, 3, 3, 4, 4, 5, 4, 5, 5, 5, 5, 6, 7, 8, 8, 8, 8, 9, 9, 9, 10, 10, 11, 12, 12, 13, 13, 14, 14, 14
+ }, new byte[] {
+ 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 5, 6, 7, 7, 9, 11, 11, 11
+ }, new byte[] {
+ 0, 5, 8, 10, 11, 11, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 12, 12, 12,
+ 12, 13, 15, 15, 15
+ }, new byte[] {
+ 0, 2, 3, 4, 5, 6, 6, 7, 7, 8, 8, 8, 9, 9, 10, 10, 10, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13,
+ 15, 15, 15
+ }
+ };
+ }
+}
diff --git a/src/SharpEmu.GUI/Atrac9/Stereo.cs b/src/SharpEmu.GUI/Atrac9/Stereo.cs
new file mode 100644
index 0000000..ab158a4
--- /dev/null
+++ b/src/SharpEmu.GUI/Atrac9/Stereo.cs
@@ -0,0 +1,34 @@
+#nullable disable
+namespace LibAtrac9
+{
+ internal static class Stereo
+ {
+ public static void ApplyIntensityStereo(Block block)
+ {
+ if (block.BlockType != BlockType.Stereo) return;
+
+ int totalUnits = block.QuantizationUnitCount;
+ int stereoUnits = block.StereoQuantizationUnit;
+ if (stereoUnits >= totalUnits) return;
+
+ Channel source = block.PrimaryChannel;
+ Channel dest = block.SecondaryChannel;
+
+ for (int i = stereoUnits; i < totalUnits; i++)
+ {
+ int sign = block.JointStereoSigns[i];
+ for (int sb = Tables.QuantUnitToCoeffIndex[i]; sb < Tables.QuantUnitToCoeffIndex[i + 1]; sb++)
+ {
+ if (sign > 0)
+ {
+ dest.Spectra[sb] = -source.Spectra[sb];
+ }
+ else
+ {
+ dest.Spectra[sb] = source.Spectra[sb];
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/SharpEmu.GUI/Atrac9/Tables.cs b/src/SharpEmu.GUI/Atrac9/Tables.cs
new file mode 100644
index 0000000..72b4e74
--- /dev/null
+++ b/src/SharpEmu.GUI/Atrac9/Tables.cs
@@ -0,0 +1,116 @@
+#nullable disable
+using System;
+using static LibAtrac9.HuffmanCodebooks;
+
+namespace LibAtrac9
+{
+ internal static class Tables
+ {
+ public static int MaxHuffPrecision(bool highSampleRate) => highSampleRate ? 1 : 7;
+ public static int MinBandCount(bool highSampleRate) => highSampleRate ? 1 : 3;
+ public static int MaxExtensionBand(bool highSampleRate) => highSampleRate ? 16 : 18;
+
+ public static readonly int[] SampleRates =
+ {
+ 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000,
+ 44100, 48000, 64000, 88200, 96000, 128000, 176400, 192000
+ };
+
+ public static readonly byte[] SamplingRateIndexToFrameSamplesPower = { 6, 6, 7, 7, 7, 8, 8, 8, 6, 6, 7, 7, 7, 8, 8, 8 };
+
+ // From sampling rate index
+ public static readonly byte[] MaxBandCount = { 8, 8, 12, 12, 12, 18, 18, 18, 8, 8, 12, 12, 12, 16, 16, 16 };
+ public static readonly byte[] BandToQuantUnitCount = { 0, 4, 8, 10, 12, 13, 14, 15, 16, 18, 20, 21, 22, 23, 24, 25, 26, 28, 30 };
+
+ public static readonly byte[] QuantUnitToCoeffCount =
+ {
+ 02, 02, 02, 02, 02, 02, 02, 02, 04, 04, 04, 04, 08, 08, 08,
+ 08, 08, 08, 08, 08, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16
+ };
+
+ public static readonly short[] QuantUnitToCoeffIndex =
+ {
+ 0, 2, 4, 6, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56,
+ 64, 72, 80, 88, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256
+ };
+
+ public static readonly byte[] QuantUnitToCodebookIndex =
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2,
+ 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3
+ };
+
+ public static readonly ChannelConfig[] ChannelConfig =
+ {
+ new ChannelConfig(BlockType.Mono),
+ new ChannelConfig(BlockType.Mono, BlockType.Mono),
+ new ChannelConfig(BlockType.Stereo),
+ new ChannelConfig(BlockType.Stereo, BlockType.Mono, BlockType.LFE, BlockType.Stereo),
+ new ChannelConfig(BlockType.Stereo, BlockType.Mono, BlockType.LFE, BlockType.Stereo, BlockType.Stereo),
+ new ChannelConfig(BlockType.Stereo, BlockType.Stereo)
+ };
+
+ public static readonly HuffmanCodebook[] HuffmanScaleFactorsUnsigned =
+ GenerateHuffmanCodebooks(HuffmanScaleFactorsACodes, HuffmanScaleFactorsABits, HuffmanScaleFactorsGroupSizes);
+
+ public static readonly HuffmanCodebook[] HuffmanScaleFactorsSigned =
+ GenerateHuffmanCodebooks(HuffmanScaleFactorsBCodes, HuffmanScaleFactorsBBits, HuffmanScaleFactorsGroupSizes);
+
+ public static readonly HuffmanCodebook[][][] HuffmanSpectrum =
+ {
+ GenerateHuffmanCodebooks(HuffmanSpectrumACodes, HuffmanSpectrumABits, HuffmanSpectrumAGroupSizes),
+ GenerateHuffmanCodebooks(HuffmanSpectrumBCodes, HuffmanSpectrumBBits, HuffmanSpectrumBGroupSizes)
+ };
+
+ public static readonly double[][] ImdctWindow = { GenerateImdctWindow(6), GenerateImdctWindow(7), GenerateImdctWindow(8) };
+
+ public static readonly double[] SpectrumScale = Generate(32, SpectrumScaleFunction);
+ public static readonly double[] QuantizerStepSize = Generate(16, QuantizerStepSizeFunction);
+ public static readonly double[] QuantizerFineStepSize = Generate(16, QuantizerFineStepSizeFunction);
+
+ public static readonly byte[][] GradientCurves = BitAllocation.GenerateGradientCurves();
+
+ private static double QuantizerStepSizeFunction(int x) => 2.0 / ((1 << (x + 1)) - 1);
+ private static double QuantizerFineStepSizeFunction(int x) => QuantizerStepSizeFunction(x) / ushort.MaxValue;
+ private static double SpectrumScaleFunction(int x) => Math.Pow(2, x - 15);
+
+ private static double[] GenerateImdctWindow(int frameSizePower)
+ {
+ int frameSize = 1 << frameSizePower;
+ var output = new double[frameSize];
+
+ double[] a1 = GenerateMdctWindow(frameSizePower);
+
+ for (int i = 0; i < frameSize; i++)
+ {
+ output[i] = a1[i] / (a1[frameSize - 1 - i] * a1[frameSize - 1 - i] + a1[i] * a1[i]);
+ }
+ return output;
+ }
+
+ private static double[] GenerateMdctWindow(int frameSizePower)
+ {
+ int frameSize = 1 << frameSizePower;
+ var output = new double[frameSize];
+
+ for (int i = 0; i < frameSize; i++)
+ {
+ output[i] = (Math.Sin(((i + 0.5) / frameSize - 0.5) * Math.PI) + 1.0) * 0.5;
+ }
+
+ return output;
+ }
+
+ private static T[] Generate(int count, Func elementGenerator)
+ {
+ var table = new T[count];
+ for (int i = 0; i < count; i++)
+ {
+ table[i] = elementGenerator(i);
+ }
+ return table;
+ }
+
+
+ }
+}
diff --git a/src/SharpEmu.GUI/Atrac9/Unpack.cs b/src/SharpEmu.GUI/Atrac9/Unpack.cs
new file mode 100644
index 0000000..2ddbc45
--- /dev/null
+++ b/src/SharpEmu.GUI/Atrac9/Unpack.cs
@@ -0,0 +1,426 @@
+#nullable disable
+using System;
+using System.IO;
+using LibAtrac9.Utilities;
+
+namespace LibAtrac9
+{
+ internal static class Unpack
+ {
+ public static void UnpackFrame(BitReader reader, Frame frame)
+ {
+ foreach (Block block in frame.Blocks)
+ {
+ UnpackBlock(reader, block);
+ }
+ }
+
+ private static void UnpackBlock(BitReader reader, Block block)
+ {
+ ReadBlockHeader(reader, block);
+
+ if (block.BlockType == BlockType.LFE)
+ {
+ UnpackLfeBlock(reader, block);
+ }
+ else
+ {
+ UnpackStandardBlock(reader, block);
+ }
+
+ reader.AlignPosition(8);
+ }
+
+ private static void ReadBlockHeader(BitReader reader, Block block)
+ {
+ bool firstInSuperframe = block.Frame.FrameIndex == 0;
+ block.FirstInSuperframe = !reader.ReadBool();
+ block.ReuseBandParams = reader.ReadBool();
+
+ if (block.FirstInSuperframe != firstInSuperframe)
+ {
+ throw new InvalidDataException();
+ }
+
+ if (firstInSuperframe && block.ReuseBandParams && block.BlockType != BlockType.LFE)
+ {
+ throw new InvalidDataException();
+ }
+ }
+
+ private static void UnpackStandardBlock(BitReader reader, Block block)
+ {
+ Channel[] channels = block.Channels;
+
+ if (!block.ReuseBandParams)
+ {
+ ReadBandParams(reader, block);
+ }
+
+ ReadGradientParams(reader, block);
+ BitAllocation.CreateGradient(block);
+ ReadStereoParams(reader, block);
+ ReadExtensionParams(reader, block);
+
+ foreach (Channel channel in channels)
+ {
+ channel.UpdateCodedUnits();
+
+ ScaleFactors.Read(reader, channel);
+ BitAllocation.CalculateMask(channel);
+ BitAllocation.CalculatePrecisions(channel);
+ CalculateSpectrumCodebookIndex(channel);
+
+ ReadSpectra(reader, channel);
+ ReadSpectraFine(reader, channel);
+ }
+
+ block.QuantizationUnitsPrev = block.BandExtensionEnabled ? block.ExtensionUnit : block.QuantizationUnitCount;
+ }
+
+ private static void ReadBandParams(BitReader reader, Block block)
+ {
+ int minBandCount = Tables.MinBandCount(block.Config.HighSampleRate);
+ int maxExtensionBand = Tables.MaxExtensionBand(block.Config.HighSampleRate);
+ block.BandCount = reader.ReadInt(4);
+ block.BandCount += minBandCount;
+ block.QuantizationUnitCount = Tables.BandToQuantUnitCount[block.BandCount];
+ if (block.BandCount < minBandCount || block.BandCount >
+ Tables.MaxBandCount[block.Config.SampleRateIndex])
+ {
+ return;
+ }
+
+ if (block.BlockType == BlockType.Stereo)
+ {
+ block.StereoBand = reader.ReadInt(4);
+ block.StereoBand += minBandCount;
+ block.StereoQuantizationUnit = Tables.BandToQuantUnitCount[block.StereoBand];
+ }
+ else
+ {
+ block.StereoBand = block.BandCount;
+ }
+
+ block.BandExtensionEnabled = reader.ReadBool();
+ if (block.BandExtensionEnabled)
+ {
+ block.ExtensionBand = reader.ReadInt(4);
+ block.ExtensionBand += minBandCount;
+
+ if (block.ExtensionBand < block.BandCount || block.ExtensionBand > maxExtensionBand)
+ {
+ throw new InvalidDataException();
+ }
+
+ block.ExtensionUnit = Tables.BandToQuantUnitCount[block.ExtensionBand];
+ }
+ else
+ {
+ block.ExtensionBand = block.BandCount;
+ block.ExtensionUnit = block.QuantizationUnitCount;
+ }
+ }
+
+ private static void ReadGradientParams(BitReader reader, Block block)
+ {
+ block.GradientMode = reader.ReadInt(2);
+ if (block.GradientMode > 0)
+ {
+ block.GradientEndUnit = 31;
+ block.GradientEndValue = 31;
+ block.GradientStartUnit = reader.ReadInt(5);
+ block.GradientStartValue = reader.ReadInt(5);
+ }
+ else
+ {
+ block.GradientStartUnit = reader.ReadInt(6);
+ block.GradientEndUnit = reader.ReadInt(6) + 1;
+ block.GradientStartValue = reader.ReadInt(5);
+ block.GradientEndValue = reader.ReadInt(5);
+ }
+ block.GradientBoundary = reader.ReadInt(4);
+
+ if (block.GradientBoundary > block.QuantizationUnitCount)
+ {
+ throw new InvalidDataException();
+ }
+ if (block.GradientStartUnit < 1 || block.GradientStartUnit >= 48)
+ {
+ throw new InvalidDataException();
+ }
+ if (block.GradientEndUnit < 1 || block.GradientEndUnit >= 48)
+ {
+ throw new InvalidDataException();
+ }
+ if (block.GradientStartUnit > block.GradientEndUnit)
+ {
+ throw new InvalidDataException();
+ }
+ if (block.GradientStartValue < 0 || block.GradientStartValue >= 32)
+ {
+ throw new InvalidDataException();
+ }
+ if (block.GradientEndValue < 0 || block.GradientEndValue >= 32)
+ {
+ throw new InvalidDataException();
+ }
+ }
+
+ private static void ReadStereoParams(BitReader reader, Block block)
+ {
+ if (block.BlockType != BlockType.Stereo) return;
+
+ block.PrimaryChannelIndex = reader.ReadInt(1);
+ block.HasJointStereoSigns = reader.ReadBool();
+ if (block.HasJointStereoSigns)
+ {
+ for (int i = block.StereoQuantizationUnit; i < block.QuantizationUnitCount; i++)
+ {
+ block.JointStereoSigns[i] = reader.ReadInt(1);
+ }
+ }
+ else
+ {
+ Array.Clear(block.JointStereoSigns, 0, block.JointStereoSigns.Length);
+ }
+ }
+
+ private static void ReadExtensionParams(BitReader reader, Block block)
+ {
+ // ReSharper disable once RedundantAssignment
+ int bexBand = 0;
+ if (block.BandExtensionEnabled)
+ {
+ BandExtension.GetBexBandInfo(out bexBand, out _, out _, block.QuantizationUnitCount);
+ if (block.BlockType == BlockType.Stereo)
+ {
+ ReadHeader(block.Channels[1]);
+ }
+ else
+ {
+ reader.Position += 1;
+ }
+ }
+ block.HasExtensionData = reader.ReadBool();
+
+ if (!block.HasExtensionData) return;
+ if (!block.BandExtensionEnabled)
+ {
+ block.BexMode = reader.ReadInt(2);
+ block.BexDataLength = reader.ReadInt(5);
+ reader.Position += block.BexDataLength;
+ return;
+ }
+
+ ReadHeader(block.Channels[0]);
+
+ block.BexDataLength = reader.ReadInt(5);
+ if (block.BexDataLength <= 0) return;
+ int bexDataEnd = reader.Position + block.BexDataLength;
+
+ ReadData(block.Channels[0]);
+
+ if (block.BlockType == BlockType.Stereo)
+ {
+ ReadData(block.Channels[1]);
+ }
+
+ // Make sure we didn't read too many bits
+ if (reader.Position > bexDataEnd)
+ {
+ throw new InvalidDataException();
+ }
+
+ void ReadHeader(Channel channel)
+ {
+ int bexMode = reader.ReadInt(2);
+ channel.BexMode = bexBand > 2 ? bexMode : 4;
+ channel.BexValueCount = BandExtension.BexEncodedValueCounts[channel.BexMode][bexBand];
+ }
+
+ void ReadData(Channel channel)
+ {
+ for (int i = 0; i < channel.BexValueCount; i++)
+ {
+ int dataLength = BandExtension.BexDataLengths[channel.BexMode][bexBand][i];
+ channel.BexValues[i] = reader.ReadInt(dataLength);
+ }
+ }
+ }
+
+ private static void CalculateSpectrumCodebookIndex(Channel channel)
+ {
+ Array.Clear(channel.CodebookSet, 0, channel.CodebookSet.Length);
+ int quantUnits = channel.CodedQuantUnits;
+ int[] sf = channel.ScaleFactors;
+
+ if (quantUnits <= 1) return;
+ if (channel.Config.HighSampleRate) return;
+
+ // Temporarily setting this value allows for simpler code by
+ // making the last value a non-special case.
+ int originalScaleTmp = sf[quantUnits];
+ sf[quantUnits] = sf[quantUnits - 1];
+
+ int avg = 0;
+ if (quantUnits > 12)
+ {
+ for (int i = 0; i < 12; i++)
+ {
+ avg += sf[i];
+ }
+ avg = (avg + 6) / 12;
+ }
+
+ for (int i = 8; i < quantUnits; i++)
+ {
+ int prevSf = sf[i - 1];
+ int nextSf = sf[i + 1];
+ int minSf = Math.Min(prevSf, nextSf);
+ if (sf[i] - minSf >= 3 || sf[i] - prevSf + sf[i] - nextSf >= 3)
+ {
+ channel.CodebookSet[i] = 1;
+ }
+ }
+
+ for (int i = 12; i < quantUnits; i++)
+ {
+ if (channel.CodebookSet[i] == 0)
+ {
+ int minSf = Math.Min(sf[i - 1], sf[i + 1]);
+ if (sf[i] - minSf >= 2 && sf[i] >= avg - (Tables.QuantUnitToCoeffCount[i] == 16 ? 1 : 0))
+ {
+ channel.CodebookSet[i] = 1;
+ }
+ }
+ }
+
+ sf[quantUnits] = originalScaleTmp;
+ }
+
+ private static void ReadSpectra(BitReader reader, Channel channel)
+ {
+ int[] values = channel.SpectraValuesBuffer;
+ Array.Clear(channel.QuantizedSpectra, 0, channel.QuantizedSpectra.Length);
+ int maxHuffPrecision = Tables.MaxHuffPrecision(channel.Config.HighSampleRate);
+
+ for (int i = 0; i < channel.CodedQuantUnits; i++)
+ {
+ int subbandCount = Tables.QuantUnitToCoeffCount[i];
+ int precision = channel.Precisions[i] + 1;
+ if (precision <= maxHuffPrecision)
+ {
+ HuffmanCodebook huff = Tables.HuffmanSpectrum[channel.CodebookSet[i]][precision][Tables.QuantUnitToCodebookIndex[i]];
+ int groupCount = subbandCount >> huff.ValueCountPower;
+ for (int j = 0; j < groupCount; j++)
+ {
+ values[j] = ReadHuffmanValue(huff, reader);
+ }
+
+ DecodeHuffmanValues(channel.QuantizedSpectra, Tables.QuantUnitToCoeffIndex[i], subbandCount, huff, values);
+ }
+ else
+ {
+ int subbandIndex = Tables.QuantUnitToCoeffIndex[i];
+ for (int j = subbandIndex; j < Tables.QuantUnitToCoeffIndex[i + 1]; j++)
+ {
+ channel.QuantizedSpectra[j] = reader.ReadSignedInt(precision);
+ }
+ }
+ }
+ }
+
+ private static void ReadSpectraFine(BitReader reader, Channel channel)
+ {
+ Array.Clear(channel.QuantizedSpectraFine, 0, channel.QuantizedSpectraFine.Length);
+
+ for (int i = 0; i < channel.CodedQuantUnits; i++)
+ {
+ if (channel.PrecisionsFine[i] > 0)
+ {
+ int overflowBits = channel.PrecisionsFine[i] + 1;
+ int startSubband = Tables.QuantUnitToCoeffIndex[i];
+ int endSubband = Tables.QuantUnitToCoeffIndex[i + 1];
+
+ for (int j = startSubband; j < endSubband; j++)
+ {
+ channel.QuantizedSpectraFine[j] = reader.ReadSignedInt(overflowBits);
+ }
+ }
+ }
+ }
+
+ private static void DecodeHuffmanValues(int[] spectrum, int index, int bandCount, HuffmanCodebook huff, int[] values)
+ {
+ int valueCount = bandCount >> huff.ValueCountPower;
+ int mask = (1 << huff.ValueBits) - 1;
+
+ for (int i = 0; i < valueCount; i++)
+ {
+ int value = values[i];
+ for (int j = 0; j < huff.ValueCount; j++)
+ {
+ spectrum[index++] = Bit.SignExtend32(value & mask, huff.ValueBits);
+ value >>= huff.ValueBits;
+ }
+ }
+ }
+
+ public static int ReadHuffmanValue(HuffmanCodebook huff, BitReader reader, bool signed = false)
+ {
+ int code = reader.PeekInt(huff.MaxBitSize);
+ byte value = huff.Lookup[code];
+ int bits = huff.Bits[value];
+ reader.Position += bits;
+ return signed ? Bit.SignExtend32(value, huff.ValueBits) : value;
+ }
+
+ private static void UnpackLfeBlock(BitReader reader, Block block)
+ {
+ Channel channel = block.Channels[0];
+ block.QuantizationUnitCount = 2;
+
+ DecodeLfeScaleFactors(reader, channel);
+ CalculateLfePrecision(channel);
+ channel.CodedQuantUnits = block.QuantizationUnitCount;
+ ReadLfeSpectra(reader, channel);
+ }
+
+ private static void DecodeLfeScaleFactors(BitReader reader, Channel channel)
+ {
+ Array.Clear(channel.ScaleFactors, 0, channel.ScaleFactors.Length);
+ for (int i = 0; i < channel.Block.QuantizationUnitCount; i++)
+ {
+ channel.ScaleFactors[i] = reader.ReadInt(5);
+ }
+ }
+
+ private static void CalculateLfePrecision(Channel channel)
+ {
+ Block block = channel.Block;
+ int precision = block.ReuseBandParams ? 8 : 4;
+ for (int i = 0; i < block.QuantizationUnitCount; i++)
+ {
+ channel.Precisions[i] = precision;
+ channel.PrecisionsFine[i] = 0;
+ }
+ }
+
+ private static void ReadLfeSpectra(BitReader reader, Channel channel)
+ {
+ Array.Clear(channel.QuantizedSpectra, 0, channel.QuantizedSpectra.Length);
+
+ for (int i = 0; i < channel.CodedQuantUnits; i++)
+ {
+ if (channel.Precisions[i] <= 0) continue;
+
+ int precision = channel.Precisions[i] + 1;
+ for (int j = Tables.QuantUnitToCoeffIndex[i]; j < Tables.QuantUnitToCoeffIndex[i + 1]; j++)
+ {
+ channel.QuantizedSpectra[j] = reader.ReadSignedInt(precision);
+ }
+ }
+ }
+ }
+}
diff --git a/src/SharpEmu.GUI/Atrac9/Utilities/Bit.cs b/src/SharpEmu.GUI/Atrac9/Utilities/Bit.cs
new file mode 100644
index 0000000..a3349be
--- /dev/null
+++ b/src/SharpEmu.GUI/Atrac9/Utilities/Bit.cs
@@ -0,0 +1,23 @@
+#nullable disable
+namespace LibAtrac9.Utilities
+{
+ internal static class Bit
+ {
+ private static uint BitReverse32(uint value)
+ {
+ value = ((value & 0xaaaaaaaa) >> 1) | ((value & 0x55555555) << 1);
+ value = ((value & 0xcccccccc) >> 2) | ((value & 0x33333333) << 2);
+ value = ((value & 0xf0f0f0f0) >> 4) | ((value & 0x0f0f0f0f) << 4);
+ value = ((value & 0xff00ff00) >> 8) | ((value & 0x00ff00ff) << 8);
+ return (value >> 16) | (value << 16);
+ }
+ private static uint BitReverse32(uint value, int bitCount) => BitReverse32(value) >> (32 - bitCount);
+ public static int BitReverse32(int value, int bitCount) => (int) BitReverse32((uint) value, bitCount);
+
+ public static int SignExtend32(int value, int bits)
+ {
+ int shift = 8 * sizeof(int) - bits;
+ return (value << shift) >> shift;
+ }
+ }
+}
diff --git a/src/SharpEmu.GUI/Atrac9/Utilities/BitReader.cs b/src/SharpEmu.GUI/Atrac9/Utilities/BitReader.cs
new file mode 100644
index 0000000..4622560
--- /dev/null
+++ b/src/SharpEmu.GUI/Atrac9/Utilities/BitReader.cs
@@ -0,0 +1,133 @@
+#nullable disable
+using System;
+using System.Diagnostics;
+
+namespace LibAtrac9.Utilities
+{
+ internal class BitReader
+ {
+ private byte[] Buffer { get; set; }
+ private int LengthBits { get; set; }
+ public int Position { get; set; }
+ private int Remaining => LengthBits - Position;
+
+ public BitReader(byte[] buffer) => SetBuffer(buffer);
+
+ public void SetBuffer(byte[] buffer)
+ {
+ Buffer = buffer;
+ LengthBits = Buffer?.Length * 8 ?? 0;
+ Position = 0;
+ }
+
+ public int ReadInt(int bitCount)
+ {
+ int value = PeekInt(bitCount);
+ Position += bitCount;
+ return value;
+ }
+
+ public int ReadSignedInt(int bitCount)
+ {
+ int value = PeekInt(bitCount);
+ Position += bitCount;
+ return Bit.SignExtend32(value, bitCount);
+ }
+
+ public bool ReadBool() => ReadInt(1) == 1;
+
+ public int ReadOffsetBinary(int bitCount, OffsetBias bias)
+ {
+ int offset = (1 << (bitCount - 1)) - (int)bias;
+ int value = PeekInt(bitCount) - offset;
+ Position += bitCount;
+ return value;
+ }
+
+ public void AlignPosition(int multiple)
+ {
+ Position = Helpers.GetNextMultiple(Position, multiple);
+ }
+
+ public int PeekInt(int bitCount)
+ {
+ Debug.Assert(bitCount >= 0 && bitCount <= 32);
+
+ if (bitCount > Remaining)
+ {
+ if (Position >= LengthBits) return 0;
+
+ int extraBits = bitCount - Remaining;
+ return PeekIntFallback(Remaining) << extraBits;
+ }
+
+ int byteIndex = Position / 8;
+ int bitIndex = Position % 8;
+
+ if (bitCount <= 9 && Remaining >= 16)
+ {
+ int value = Buffer[byteIndex] << 8 | Buffer[byteIndex + 1];
+ value &= 0xFFFF >> bitIndex;
+ value >>= 16 - bitCount - bitIndex;
+ return value;
+ }
+
+ if (bitCount <= 17 && Remaining >= 24)
+ {
+ int value = Buffer[byteIndex] << 16 | Buffer[byteIndex + 1] << 8 | Buffer[byteIndex + 2];
+ value &= 0xFFFFFF >> bitIndex;
+ value >>= 24 - bitCount - bitIndex;
+ return value;
+ }
+
+ if (bitCount <= 25 && Remaining >= 32)
+ {
+ int value = Buffer[byteIndex] << 24 | Buffer[byteIndex + 1] << 16 | Buffer[byteIndex + 2] << 8 | Buffer[byteIndex + 3];
+ value &= (int)(0xFFFFFFFF >> bitIndex);
+ value >>= 32 - bitCount - bitIndex;
+ return value;
+ }
+ return PeekIntFallback(bitCount);
+ }
+
+ private int PeekIntFallback(int bitCount)
+ {
+ int value = 0;
+ int byteIndex = Position / 8;
+ int bitIndex = Position % 8;
+
+ while (bitCount > 0)
+ {
+ if (bitIndex >= 8)
+ {
+ bitIndex = 0;
+ byteIndex++;
+ }
+
+ int bitsToRead = Math.Min(bitCount, 8 - bitIndex);
+ int mask = 0xFF >> bitIndex;
+ int currentByte = (mask & Buffer[byteIndex]) >> (8 - bitIndex - bitsToRead);
+
+ value = (value << bitsToRead) | currentByte;
+ bitIndex += bitsToRead;
+ bitCount -= bitsToRead;
+ }
+ return value;
+ }
+
+ ///
+ /// Specifies the bias of an offset binary value. A positive bias can represent one more
+ /// positive value than negative value, and a negative bias can represent one more
+ /// negative value than positive value.
+ ///
+ /// Example:
+ /// A 4-bit offset binary value with a positive bias can store
+ /// the values 8 through -7 inclusive.
+ /// A 4-bit offset binary value with a positive bias can store
+ /// the values 7 through -8 inclusive.
+ public enum OffsetBias
+ {
+ Negative = 0
+ }
+ }
+}
diff --git a/src/SharpEmu.GUI/Atrac9/Utilities/Helpers.cs b/src/SharpEmu.GUI/Atrac9/Utilities/Helpers.cs
new file mode 100644
index 0000000..bd5d940
--- /dev/null
+++ b/src/SharpEmu.GUI/Atrac9/Utilities/Helpers.cs
@@ -0,0 +1,51 @@
+#nullable disable
+using System.Runtime.CompilerServices;
+
+namespace LibAtrac9.Utilities
+{
+ internal static class Helpers
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static short Clamp16(int value)
+ {
+ if (value > short.MaxValue)
+ return short.MaxValue;
+ if (value < short.MinValue)
+ return short.MinValue;
+ return (short)value;
+ }
+
+ public static int GetNextMultiple(int value, int multiple)
+ {
+ if (multiple <= 0)
+ return value;
+
+ if (value % multiple == 0)
+ return value;
+
+ return value + multiple - value % multiple;
+ }
+
+ ///
+ /// Returns the floor of the base 2 logarithm of a specified number.
+ ///
+ /// The number whose logarithm is to be found.
+ /// The floor of the base 2 logarithm of .
+ public static int Log2(int value)
+ {
+ value |= value >> 1;
+ value |= value >> 2;
+ value |= value >> 4;
+ value |= value >> 8;
+ value |= value >> 16;
+
+ return MultiplyDeBruijnBitPosition[(uint)(value * 0x07C4ACDDU) >> 27];
+ }
+
+ private static readonly int[] MultiplyDeBruijnBitPosition =
+ {
+ 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30,
+ 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31
+ };
+ }
+}
diff --git a/src/SharpEmu.GUI/Atrac9/Utilities/Mdct.cs b/src/SharpEmu.GUI/Atrac9/Utilities/Mdct.cs
new file mode 100644
index 0000000..0413b61
--- /dev/null
+++ b/src/SharpEmu.GUI/Atrac9/Utilities/Mdct.cs
@@ -0,0 +1,178 @@
+#nullable disable
+using System;
+using System.Collections.Generic;
+
+namespace LibAtrac9.Utilities
+{
+ internal class Mdct
+ {
+ private int MdctBits { get; }
+ private int MdctSize { get; }
+ private double Scale { get; }
+
+ private static readonly object TableLock = new object();
+ private static int _tableBits = -1;
+ private static readonly List SinTables = new List();
+ private static readonly List CosTables = new List();
+ private static readonly List ShuffleTables = new List();
+
+ private readonly double[] _imdctPrevious;
+ private readonly double[] _imdctWindow;
+
+ private readonly double[] _scratchMdct;
+ private readonly double[] _scratchDct;
+
+ public Mdct(int mdctBits, double[] window, double scale = 1)
+ {
+ SetTables(mdctBits);
+
+ MdctBits = mdctBits;
+ MdctSize = 1 << mdctBits;
+ Scale = scale;
+
+ if (window.Length < MdctSize)
+ {
+ throw new ArgumentException("Window must be as long as the MDCT size.", nameof(window));
+ }
+
+ _imdctPrevious = new double[MdctSize];
+ _scratchMdct = new double[MdctSize];
+ _scratchDct = new double[MdctSize];
+ _imdctWindow = window;
+ }
+
+ private static void SetTables(int maxBits)
+ {
+ lock (TableLock)
+ {
+ if (maxBits > _tableBits)
+ {
+ for (int i = _tableBits + 1; i <= maxBits; i++)
+ {
+ GenerateTrigTables(i, out double[] sin, out double[] cos);
+ SinTables.Add(sin);
+ CosTables.Add(cos);
+ ShuffleTables.Add(GenerateShuffleTable(i));
+ }
+ _tableBits = maxBits;
+ }
+ }
+ }
+
+ public void RunImdct(double[] input, double[] output)
+ {
+ if (input.Length < MdctSize)
+ {
+ throw new ArgumentException("Input must be as long as the MDCT size.", nameof(input));
+ }
+
+ if (output.Length < MdctSize)
+ {
+ throw new ArgumentException("Output must be as long as the MDCT size.", nameof(output));
+ }
+
+ int size = MdctSize;
+ int half = size / 2;
+ double[] dctOut = _scratchMdct;
+
+ Dct4(input, dctOut);
+
+ for (int i = 0; i < half; i++)
+ {
+ output[i] = _imdctWindow[i] * dctOut[i + half] + _imdctPrevious[i];
+ output[i + half] = _imdctWindow[i + half] * -dctOut[size - 1 - i] - _imdctPrevious[i + half];
+ _imdctPrevious[i] = _imdctWindow[size - 1 - i] * -dctOut[half - i - 1];
+ _imdctPrevious[i + half] = _imdctWindow[half - i - 1] * dctOut[i];
+ }
+ }
+
+ ///
+ /// Does a Type-4 DCT.
+ ///
+ /// The input array containing the time or frequency-domain samples
+ /// The output array that will contain the transformed time or frequency-domain samples
+ private void Dct4(double[] input, double[] output)
+ {
+ int[] shuffleTable = ShuffleTables[MdctBits];
+ double[] sinTable = SinTables[MdctBits];
+ double[] cosTable = CosTables[MdctBits];
+ double[] dctTemp = _scratchDct;
+
+ int size = MdctSize;
+ int lastIndex = size - 1;
+ int halfSize = size / 2;
+
+ for (int i = 0; i < halfSize; i++)
+ {
+ int i2 = i * 2;
+ double a = input[i2];
+ double b = input[lastIndex - i2];
+ double sin = sinTable[i];
+ double cos = cosTable[i];
+ dctTemp[i2] = a * cos + b * sin;
+ dctTemp[i2 + 1] = a * sin - b * cos;
+ }
+ int stageCount = MdctBits - 1;
+
+ for (int stage = 0; stage < stageCount; stage++)
+ {
+ int blockCount = 1 << stage;
+ int blockSizeBits = stageCount - stage;
+ int blockHalfSizeBits = blockSizeBits - 1;
+ int blockSize = 1 << blockSizeBits;
+ int blockHalfSize = 1 << blockHalfSizeBits;
+ sinTable = SinTables[blockHalfSizeBits];
+ cosTable = CosTables[blockHalfSizeBits];
+
+ for (int block = 0; block < blockCount; block++)
+ {
+ for (int i = 0; i < blockHalfSize; i++)
+ {
+ int frontPos = (block * blockSize + i) * 2;
+ int backPos = frontPos + blockSize;
+ double a = dctTemp[frontPos] - dctTemp[backPos];
+ double b = dctTemp[frontPos + 1] - dctTemp[backPos + 1];
+ double sin = sinTable[i];
+ double cos = cosTable[i];
+ dctTemp[frontPos] += dctTemp[backPos];
+ dctTemp[frontPos + 1] += dctTemp[backPos + 1];
+ dctTemp[backPos] = a * cos + b * sin;
+ dctTemp[backPos + 1] = a * sin - b * cos;
+ }
+ }
+ }
+
+ for (int i = 0; i < MdctSize; i++)
+ {
+ output[i] = dctTemp[shuffleTable[i]] * Scale;
+ }
+ }
+
+ internal static void GenerateTrigTables(int sizeBits, out double[] sin, out double[] cos)
+ {
+ int size = 1 << sizeBits;
+ sin = new double[size];
+ cos = new double[size];
+
+ for (int i = 0; i < size; i++)
+ {
+ double value = Math.PI * (4 * i + 1) / (4 * size);
+ sin[i] = Math.Sin(value);
+ cos[i] = Math.Cos(value);
+ }
+ }
+
+ internal static int[] GenerateShuffleTable(int sizeBits)
+ {
+ int size = 1 << sizeBits;
+ var table = new int[size];
+
+ for (int i = 0; i < size; i++)
+ {
+ table[i] = Bit.BitReverse32(i ^ (i / 2), sizeBits);
+ }
+
+ return table;
+ }
+ }
+}
diff --git a/src/SharpEmu.GUI/GuiSettings.cs b/src/SharpEmu.GUI/GuiSettings.cs
index 5ffaf3b..85c6c9b 100644
--- a/src/SharpEmu.GUI/GuiSettings.cs
+++ b/src/SharpEmu.GUI/GuiSettings.cs
@@ -23,6 +23,12 @@ public sealed class GuiSettings
public bool StrictDynlibResolution { get; set; }
+ /// Mirror emulator output to user/logs/<titleId>-<timestamp>.log.
+ public bool LogToFile { get; set; }
+
+ /// Loop the selected game's sce_sys/snd0.at9 preview music.
+ public bool PlayTitleMusic { get; set; } = true;
+
public string? EmulatorPath { get; set; }
// The emulator is portable and keeps its data next to the executable;
diff --git a/src/SharpEmu.GUI/MainWindow.axaml b/src/SharpEmu.GUI/MainWindow.axaml
index aacdf79..80bc6b7 100644
--- a/src/SharpEmu.GUI/MainWindow.axaml
+++ b/src/SharpEmu.GUI/MainWindow.axaml
@@ -244,10 +244,18 @@ SPDX-License-Identifier: GPL-2.0-or-later
-
+
+
+
+
+
+
+
+
+
diff --git a/src/SharpEmu.GUI/MainWindow.axaml.cs b/src/SharpEmu.GUI/MainWindow.axaml.cs
index f0010f6..fd3e612 100644
--- a/src/SharpEmu.GUI/MainWindow.axaml.cs
+++ b/src/SharpEmu.GUI/MainWindow.axaml.cs
@@ -16,6 +16,7 @@ using Avalonia.Platform.Storage;
using Avalonia.Threading;
using Avalonia.VisualTree;
using SharpEmu.Libs.Pad;
+using SharpEmu.Logging;
namespace SharpEmu.GUI;
@@ -38,6 +39,8 @@ public partial class MainWindow : Window
private GuiSettings _settings = new();
private EmulatorProcess? _emulator;
+ private StreamWriter? _fileLog;
+ private readonly SndPreviewPlayer _sndPreview = new();
private string? _emulatorExePath;
private bool _isRunning;
private int _autoScrollTicks;
@@ -84,6 +87,7 @@ public partial class MainWindow : Window
CopyLogButton.Click += async (_, _) => await CopyConsoleAsync();
OptionsToggle.IsCheckedChanged += (_, _) => OptionsPanel.IsVisible = OptionsToggle.IsChecked == true;
ConsoleToggle.IsCheckedChanged += (_, _) => ConsolePanel.IsVisible = ConsoleToggle.IsChecked == true;
+ TitleMusicToggle.IsCheckedChanged += (_, _) => OnTitleMusicToggled();
GameList.AddHandler(ContextRequestedEvent, OnGameContextRequested, RoutingStrategies.Tunnel);
CtxLaunch.Click += (_, _) => LaunchSelected();
@@ -98,6 +102,7 @@ public partial class MainWindow : Window
Closing += (_, _) => OnWindowClosing();
DualSenseReader.EnsureStarted();
+ XInputReader.EnsureStarted();
_gamepadTimer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(50),
@@ -110,7 +115,8 @@ public partial class MainWindow : Window
private void PollGamepad()
{
- if (!DualSenseReader.TryGetState(out var pad))
+ // DualSense wins when both are connected; XInput covers Xbox pads.
+ if (!DualSenseReader.TryGetState(out var pad) && !XInputReader.TryGetState(out pad))
{
_previousPadButtons = 0;
return;
@@ -216,10 +222,15 @@ public partial class MainWindow : Window
private async Task OnOpenedAsync()
{
var version = Assembly.GetExecutingAssembly().GetName().Version;
- if (version is not null)
- {
- VersionText.Text = $"v{version.ToString(3)}";
- }
+ var display = version is not null ? $"v{version.ToString(3)}" : "v0.0.1";
+ display += BuildInfo.CommitSha is null
+ ? " · dev"
+ : BuildInfo.IsOfficialRelease
+ ? $" · {BuildInfo.CommitSha}"
+ : $" · UNOFFICIAL {BuildInfo.CommitSha}";
+ VersionText.Text = display;
+ Title = $"SharpEmu {display}";
+ ToolTip.SetTip(VersionText, BuildInfo.Banner);
_settings = GuiSettings.Load();
ApplySettingsToControls();
@@ -233,7 +244,9 @@ public partial class MainWindow : Window
_settings.Save();
_consoleFlushTimer.Stop();
_gamepadTimer.Stop();
+ _sndPreview.Stop();
_emulator?.Dispose();
+ DropFileLog();
}
private void OnTitleBarPointerPressed(object? sender, PointerPressedEventArgs e)
@@ -260,6 +273,8 @@ public partial class MainWindow : Window
};
TraceImportsBox.Value = Math.Clamp(_settings.ImportTraceLimit, 0, 4096);
StrictToggle.IsChecked = _settings.StrictDynlibResolution;
+ LogToFileToggle.IsChecked = _settings.LogToFile;
+ TitleMusicToggle.IsChecked = _settings.PlayTitleMusic;
}
private void ReadControlsIntoSettings()
@@ -267,6 +282,8 @@ public partial class MainWindow : Window
_settings.LogLevel = SelectedLogLevel();
_settings.ImportTraceLimit = (int)(TraceImportsBox.Value ?? 0);
_settings.StrictDynlibResolution = StrictToggle.IsChecked == true;
+ _settings.LogToFile = LogToFileToggle.IsChecked == true;
+ _settings.PlayTitleMusic = TitleMusicToggle.IsChecked == true;
}
private string SelectedLogLevel()
@@ -778,6 +795,7 @@ public partial class MainWindow : Window
SelectedGamePath.Text = game.Path;
SelectedCoverPanel.DataContext = game;
_ = UpdateBackdropAsync(game);
+ PlaySelectedGamePreview(game);
}
else
{
@@ -785,11 +803,66 @@ public partial class MainWindow : Window
SelectedGamePath.Text = "Pick a game from the library, or open an eboot.bin directly.";
SelectedCoverPanel.DataContext = null;
_ = UpdateBackdropAsync(null);
+ _sndPreview.Stop();
}
UpdateRunButtons();
}
+ ///
+ /// Loops the selected game's sce_sys/snd0.at9 preview music, console
+ /// home screen style. Silent while a game is running or when disabled
+ /// in the options.
+ ///
+ private void PlaySelectedGamePreview(GameEntry game)
+ {
+ if (_isRunning || !_settings.PlayTitleMusic)
+ {
+ return;
+ }
+
+ var directory = Path.GetDirectoryName(game.Path);
+ var sndPath = directory is null ? null : Path.Combine(directory, "sce_sys", "snd0.at9");
+ if (sndPath is not null && File.Exists(sndPath))
+ {
+ _sndPreview.Play(sndPath);
+ }
+ else
+ {
+ _sndPreview.Stop();
+ }
+ }
+
+ private void OnTitleMusicToggled()
+ {
+ _settings.PlayTitleMusic = TitleMusicToggle.IsChecked == true;
+ if (!_settings.PlayTitleMusic)
+ {
+ _sndPreview.Stop();
+ }
+ else if (GameList.SelectedItem is GameEntry game)
+ {
+ PlaySelectedGamePreview(game);
+ }
+ }
+
+ /// Pauses the preview music while the window is minimized.
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+ if (change.Property == WindowStateProperty)
+ {
+ if (WindowState == WindowState.Minimized)
+ {
+ _sndPreview.Pause();
+ }
+ else
+ {
+ _sndPreview.Resume();
+ }
+ }
+ }
+
///
/// Fades the window backdrop to the selected game's key art. The image
/// decodes off the UI thread and is cached on the entry; a newer
@@ -855,11 +928,11 @@ public partial class MainWindow : Window
{
if (GameList.SelectedItem is GameEntry game)
{
- Launch(game.Path, game.Name);
+ Launch(game.Path, game.Name, game.TitleId);
}
}
- private void Launch(string ebootPath, string displayName)
+ private void Launch(string ebootPath, string displayName, string? titleId = null)
{
if (_isRunning)
{
@@ -876,6 +949,7 @@ public partial class MainWindow : Window
}
}
+ _sndPreview.Stop();
ReadControlsIntoSettings();
_settings.Save();
@@ -898,6 +972,23 @@ public partial class MainWindow : Window
_consoleLines.Clear();
ConsoleToggle.IsChecked = true;
+
+ // Mirror everything the console pane shows into a log file for the
+ // duration of the run, regardless of the emulator's log level.
+ DropFileLog();
+ if (_settings.LogToFile && BuildLogFilePath(titleId) is { } logFilePath)
+ {
+ try
+ {
+ _fileLog = new StreamWriter(logFilePath, append: false);
+ AppendConsoleLine($"Log file: {logFilePath}", DimLineBrush);
+ }
+ catch (Exception ex)
+ {
+ AppendConsoleLine($"Could not open the log file: {ex.Message}", WarningLineBrush);
+ }
+ }
+
AppendConsoleLine($"$ SharpEmu {string.Join(' ', arguments)}", DimLineBrush);
var emulator = new EmulatorProcess();
@@ -912,6 +1003,7 @@ public partial class MainWindow : Window
{
emulator.Dispose();
AppendConsoleLine($"Failed to start the emulator: {ex.Message}", ErrorLineBrush);
+ DropFileLog();
return;
}
@@ -923,6 +1015,37 @@ public partial class MainWindow : Window
UpdateRunButtons();
}
+ ///
+ /// Builds "user/logs/<titleId>-<timestamp>.log" next to the emulator
+ /// executable, following the same portable-data convention as savedata.
+ ///
+ private string? BuildLogFilePath(string? titleId)
+ {
+ try
+ {
+ var exeDirectory = Path.GetDirectoryName(_emulatorExePath);
+ if (string.IsNullOrEmpty(exeDirectory))
+ {
+ return null;
+ }
+
+ var logsDirectory = Path.Combine(exeDirectory, "user", "logs");
+ Directory.CreateDirectory(logsDirectory);
+
+ var id = string.IsNullOrWhiteSpace(titleId) ? "UNKNOWN" : titleId;
+ foreach (var invalid in Path.GetInvalidFileNameChars())
+ {
+ id = id.Replace(invalid, '_');
+ }
+
+ return Path.Combine(logsDirectory, $"{id}-{DateTime.Now:yyyyMMdd-HHmmss}.log");
+ }
+ catch (Exception)
+ {
+ return null; // unwritable location: launch continues without a log file
+ }
+ }
+
private void OnEmulatorExited(int exitCode)
{
FlushPendingConsoleLines();
@@ -941,6 +1064,7 @@ public partial class MainWindow : Window
};
var brush = exitCode == 0 ? SuccessLineBrush : ErrorLineBrush;
AppendConsoleLine($"Process exited with code {exitCode} ({meaning}).", brush);
+ CloseFileLogSoon();
StatusDot.Fill = exitCode == 0 ? (IBrush)SuccessLineBrush : ErrorLineBrush;
StatusText.Text = $"Exited with code {exitCode} ({meaning})";
@@ -967,9 +1091,12 @@ public partial class MainWindow : Window
var incoming = new List();
while (_pendingLines.TryDequeue(out var pending))
{
+ WriteFileLog(pending.Line);
incoming.Add(new LogLine(pending.Line, BrushForLine(pending.Line)));
}
+ FlushFileLog();
+
if (incoming.Count >= MaxConsoleLines)
{
// A burst larger than the cap: keep only the newest lines.
@@ -998,11 +1125,81 @@ public partial class MainWindow : Window
private void AppendConsoleLine(string text, IBrush brush)
{
+ WriteFileLog(text);
+ FlushFileLog();
_consoleLines.Add(new LogLine(text, brush));
_autoScrollTicks = 3;
MaybeAutoScroll();
}
+ // ---- Console-to-file mirroring ----
+
+ private void WriteFileLog(string text)
+ {
+ if (_fileLog is not { } writer)
+ {
+ return;
+ }
+
+ try
+ {
+ writer.Write('[');
+ writer.Write(DateTime.Now.ToString("HH:mm:ss.fff"));
+ writer.Write("] ");
+ writer.WriteLine(text);
+ }
+ catch (Exception)
+ {
+ DropFileLog(); // unwritable (disk full, etc.): stop mirroring
+ }
+ }
+
+ private void FlushFileLog()
+ {
+ try
+ {
+ _fileLog?.Flush();
+ }
+ catch (Exception)
+ {
+ DropFileLog();
+ }
+ }
+
+ private void DropFileLog()
+ {
+ var writer = _fileLog;
+ _fileLog = null;
+ try
+ {
+ writer?.Dispose();
+ }
+ catch (Exception)
+ {
+ }
+ }
+
+ ///
+ /// The pipe reader threads can deliver a final burst after the exit
+ /// event, so the file stays open for one more flush cycle.
+ ///
+ private void CloseFileLogSoon()
+ {
+ if (_fileLog is not { } writer)
+ {
+ return;
+ }
+
+ DispatcherTimer.RunOnce(() =>
+ {
+ if (ReferenceEquals(_fileLog, writer))
+ {
+ FlushPendingConsoleLines();
+ DropFileLog();
+ }
+ }, TimeSpan.FromMilliseconds(400));
+ }
+
private void MaybeAutoScroll()
{
// ScrollToEnd is applied over a few flush-timer ticks because the
diff --git a/src/SharpEmu.GUI/SharpEmu.GUI.csproj b/src/SharpEmu.GUI/SharpEmu.GUI.csproj
index 132338a..ffeaa28 100644
--- a/src/SharpEmu.GUI/SharpEmu.GUI.csproj
+++ b/src/SharpEmu.GUI/SharpEmu.GUI.csproj
@@ -12,6 +12,12 @@ SPDX-License-Identifier: GPL-2.0-or-later
0.0.1
+
+
+
+
+
@@ -24,12 +30,15 @@ SPDX-License-Identifier: GPL-2.0-or-later
-
+
+
+
diff --git a/src/SharpEmu.GUI/SndPreviewPlayer.cs b/src/SharpEmu.GUI/SndPreviewPlayer.cs
new file mode 100644
index 0000000..09c413b
--- /dev/null
+++ b/src/SharpEmu.GUI/SndPreviewPlayer.cs
@@ -0,0 +1,301 @@
+// Copyright (C) 2026 SharpEmu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+using System.Buffers.Binary;
+using System.Runtime.InteropServices;
+using System.Text;
+using LibAtrac9;
+
+namespace SharpEmu.GUI;
+
+///
+/// Loops a game's sce_sys/snd0.at9 preview music while the game is selected
+/// in the library, like the console home screen. The ATRAC9 stream is decoded
+/// to WAV on a background task (vendored LibAtrac9); playback uses winmm's
+/// PlaySound, so this is Windows-only and a no-op elsewhere.
+///
+internal sealed class SndPreviewPlayer
+{
+ private const uint SND_ASYNC = 0x0001;
+ private const uint SND_NODEFAULT = 0x0002;
+ private const uint SND_MEMORY = 0x0004;
+ private const uint SND_LOOP = 0x0008;
+
+ private readonly object _sync = new();
+ private int _generation;
+ private GCHandle _pinnedWav;
+ private bool _playing;
+ private bool _paused;
+ private string? _cachedPath;
+ private byte[]? _cachedWav;
+
+ /// Starts looping the given snd0.at9 after a short debounce.
+ public void Play(string at9Path)
+ {
+ if (!OperatingSystem.IsWindows())
+ {
+ return;
+ }
+
+ int generation;
+ lock (_sync)
+ {
+ generation = ++_generation;
+ }
+
+ _ = Task.Run(async () =>
+ {
+ // Debounce so skimming through the library does not decode (or
+ // start) a preview per tile.
+ await Task.Delay(300).ConfigureAwait(false);
+
+ byte[]? wav;
+ lock (_sync)
+ {
+ if (generation != _generation)
+ {
+ return;
+ }
+
+ wav = string.Equals(_cachedPath, at9Path, StringComparison.OrdinalIgnoreCase)
+ ? _cachedWav
+ : null;
+ }
+
+ if (wav is null)
+ {
+ try
+ {
+ wav = DecodeAt9ToWav(File.ReadAllBytes(at9Path));
+ }
+ catch (Exception)
+ {
+ return; // corrupt or unsupported preview: stay silent
+ }
+ }
+
+ lock (_sync)
+ {
+ if (generation != _generation)
+ {
+ return;
+ }
+
+ _cachedPath = at9Path;
+ _cachedWav = wav;
+ StopLocked();
+
+ // The WAV image must stay pinned while winmm plays from it.
+ _pinnedWav = GCHandle.Alloc(wav, GCHandleType.Pinned);
+ _playing = PlaySound(_pinnedWav.AddrOfPinnedObject(), 0, SND_MEMORY | SND_ASYNC | SND_LOOP | SND_NODEFAULT);
+ if (!_playing)
+ {
+ _pinnedWav.Free();
+ }
+ }
+ });
+ }
+
+ public void Stop()
+ {
+ if (!OperatingSystem.IsWindows())
+ {
+ return;
+ }
+
+ lock (_sync)
+ {
+ _generation++;
+ StopLocked();
+ }
+ }
+
+ ///
+ /// Silences playback but keeps the decoded track ready, so
+ /// can restart it (winmm cannot truly pause).
+ ///
+ public void Pause()
+ {
+ if (!OperatingSystem.IsWindows())
+ {
+ return;
+ }
+
+ lock (_sync)
+ {
+ if (!_playing)
+ {
+ return;
+ }
+
+ _ = PlaySound(0, 0, 0);
+ _playing = false;
+ _paused = true;
+ }
+ }
+
+ /// Restarts the track silenced by .
+ public void Resume()
+ {
+ if (!OperatingSystem.IsWindows())
+ {
+ return;
+ }
+
+ lock (_sync)
+ {
+ if (!_paused || !_pinnedWav.IsAllocated)
+ {
+ return;
+ }
+
+ _paused = false;
+ _playing = PlaySound(_pinnedWav.AddrOfPinnedObject(), 0, SND_MEMORY | SND_ASYNC | SND_LOOP | SND_NODEFAULT);
+ }
+ }
+
+ private void StopLocked()
+ {
+ _ = PlaySound(0, 0, 0);
+ _playing = false;
+ _paused = false;
+ if (_pinnedWav.IsAllocated)
+ {
+ _pinnedWav.Free();
+ }
+ }
+
+ private static readonly Guid Atrac9SubFormat = new("47E142D2-36BA-4D8D-88FC-61654F8C836C");
+
+ ///
+ /// Decodes a Sony AT9 (RIFF-wrapped ATRAC9) file to a PCM16 WAV image.
+ /// Layout per Sony's container: an extensible fmt chunk whose extension
+ /// carries the 4-byte codec config, a fact chunk with the sample count
+ /// and encoder delay, and superframes in the data chunk.
+ ///
+ private static byte[] DecodeAt9ToWav(byte[] file)
+ {
+ using var reader = new BinaryReader(new MemoryStream(file), Encoding.ASCII);
+ if (Encoding.ASCII.GetString(reader.ReadBytes(4)) != "RIFF")
+ {
+ throw new InvalidDataException("Not a RIFF file.");
+ }
+
+ reader.BaseStream.Position += 4; // riff size
+ if (Encoding.ASCII.GetString(reader.ReadBytes(4)) != "WAVE")
+ {
+ throw new InvalidDataException("Not a WAVE file.");
+ }
+
+ byte[]? configData = null;
+ var sampleCount = 0;
+ var encoderDelay = 0;
+ var dataOffset = -1;
+ var dataSize = 0;
+
+ while (reader.BaseStream.Position + 8 <= reader.BaseStream.Length)
+ {
+ var chunkId = Encoding.ASCII.GetString(reader.ReadBytes(4));
+ var chunkSize = reader.ReadInt32();
+ var chunkStart = reader.BaseStream.Position;
+ switch (chunkId)
+ {
+ case "fmt ":
+ var formatTag = reader.ReadUInt16();
+ reader.BaseStream.Position = chunkStart + 24; // to SubFormat GUID
+ var subFormat = new Guid(reader.ReadBytes(16));
+ if (formatTag != 0xFFFE || subFormat != Atrac9SubFormat)
+ {
+ throw new InvalidDataException("Not an ATRAC9 stream.");
+ }
+
+ reader.BaseStream.Position += 4; // version info
+ configData = reader.ReadBytes(4);
+ break;
+ case "fact":
+ sampleCount = reader.ReadInt32();
+ reader.BaseStream.Position += 4; // input overlap delay
+ encoderDelay = reader.ReadInt32();
+ break;
+ case "data":
+ dataOffset = (int)chunkStart;
+ dataSize = chunkSize;
+ break;
+ }
+
+ reader.BaseStream.Position = chunkStart + chunkSize + (chunkSize & 1);
+ }
+
+ if (configData is null || sampleCount <= 0 || dataOffset < 0)
+ {
+ throw new InvalidDataException("Missing fmt, fact, or data chunk.");
+ }
+
+ var decoder = new Atrac9Decoder();
+ decoder.Initialize(configData);
+ var config = decoder.Config;
+
+ var superframeCount = (sampleCount + encoderDelay + config.SuperframeSamples - 1) / config.SuperframeSamples;
+ superframeCount = Math.Min(superframeCount, dataSize / config.SuperframeBytes);
+
+ var channels = config.ChannelCount;
+ var pcmBuffer = new short[channels][];
+ for (var i = 0; i < channels; i++)
+ {
+ pcmBuffer[i] = new short[config.SuperframeSamples];
+ }
+
+ var wav = new byte[44 + (sampleCount * channels * 2)];
+ WriteWavHeader(wav, channels, config.SampleRate, sampleCount);
+
+ var superframe = new byte[config.SuperframeBytes];
+ var decodedIndex = 0L; // per-channel, includes the encoder delay
+ var written = 0;
+ for (var f = 0; f < superframeCount && written < sampleCount; f++)
+ {
+ Buffer.BlockCopy(file, dataOffset + (f * config.SuperframeBytes), superframe, 0, config.SuperframeBytes);
+ decoder.Decode(superframe, pcmBuffer);
+ for (var s = 0; s < config.SuperframeSamples && written < sampleCount; s++)
+ {
+ if (decodedIndex++ < encoderDelay)
+ {
+ continue;
+ }
+
+ var sampleOffset = 44 + ((long)written * channels * 2);
+ for (var ch = 0; ch < channels; ch++)
+ {
+ BinaryPrimitives.WriteInt16LittleEndian(
+ wav.AsSpan((int)(sampleOffset + (ch * 2))),
+ pcmBuffer[ch][s]);
+ }
+
+ written++;
+ }
+ }
+
+ return wav;
+ }
+
+ private static void WriteWavHeader(byte[] wav, int channels, int sampleRate, int sampleCount)
+ {
+ var span = wav.AsSpan();
+ Encoding.ASCII.GetBytes("RIFF").CopyTo(span);
+ BinaryPrimitives.WriteInt32LittleEndian(span[4..], wav.Length - 8);
+ Encoding.ASCII.GetBytes("WAVE").CopyTo(span[8..]);
+ Encoding.ASCII.GetBytes("fmt ").CopyTo(span[12..]);
+ BinaryPrimitives.WriteInt32LittleEndian(span[16..], 16);
+ BinaryPrimitives.WriteInt16LittleEndian(span[20..], 1); // PCM
+ BinaryPrimitives.WriteInt16LittleEndian(span[22..], (short)channels);
+ BinaryPrimitives.WriteInt32LittleEndian(span[24..], sampleRate);
+ BinaryPrimitives.WriteInt32LittleEndian(span[28..], sampleRate * channels * 2);
+ BinaryPrimitives.WriteInt16LittleEndian(span[32..], (short)(channels * 2));
+ BinaryPrimitives.WriteInt16LittleEndian(span[34..], 16); // bits per sample
+ Encoding.ASCII.GetBytes("data").CopyTo(span[36..]);
+ BinaryPrimitives.WriteInt32LittleEndian(span[40..], sampleCount * channels * 2);
+ }
+
+ [DllImport("winmm.dll")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static extern bool PlaySound(nint sound, nint module, uint flags);
+}
diff --git a/src/SharpEmu.Libs/Pad/DualSenseReader.cs b/src/SharpEmu.Libs/Pad/DualSenseReader.cs
index 92ad3ee..7094cdc 100644
--- a/src/SharpEmu.Libs/Pad/DualSenseReader.cs
+++ b/src/SharpEmu.Libs/Pad/DualSenseReader.cs
@@ -5,21 +5,6 @@ using Microsoft.Win32.SafeHandles;
namespace SharpEmu.Libs.Pad;
-///
-/// Snapshot of the DualSense state, already translated to ORBIS pad
-/// conventions (SCE_PAD_BUTTON bits; sticks 0..255 with 128 centered;
-/// triggers 0..255).
-///
-internal readonly record struct DualSenseState(
- bool Connected,
- uint Buttons,
- byte LeftX,
- byte LeftY,
- byte RightX,
- byte RightY,
- byte L2,
- byte R2);
-
///
/// Reads a DualSense controller over raw HID on a background thread.
/// Supports USB (input report 0x01) and Bluetooth (extended report 0x31,
@@ -31,26 +16,8 @@ internal static class DualSenseReader
private const ushort DualSenseProductId = 0x0CE6;
private const ushort DualSenseEdgeProductId = 0x0DF2;
- // SCE_PAD_BUTTON bit values.
- private const uint ButtonL3 = 0x0002;
- private const uint ButtonR3 = 0x0004;
- private const uint ButtonOptions = 0x0008;
- private const uint ButtonUp = 0x0010;
- private const uint ButtonRight = 0x0020;
- private const uint ButtonDown = 0x0040;
- private const uint ButtonLeft = 0x0080;
- private const uint ButtonL2 = 0x0100;
- private const uint ButtonR2 = 0x0200;
- private const uint ButtonL1 = 0x0400;
- private const uint ButtonR1 = 0x0800;
- private const uint ButtonTriangle = 0x1000;
- private const uint ButtonCircle = 0x2000;
- private const uint ButtonCross = 0x4000;
- private const uint ButtonSquare = 0x8000;
- private const uint ButtonTouchPad = 0x100000;
-
private static readonly object Gate = new();
- private static DualSenseState _state;
+ private static PadState _state;
private static bool _started;
// Output (rumble/lightbar) state, all guarded by Gate.
@@ -92,7 +59,7 @@ internal static class DualSenseReader
}
}
- internal static bool TryGetState(out DualSenseState state)
+ internal static bool TryGetState(out PadState state)
{
lock (Gate)
{
@@ -102,7 +69,7 @@ internal static class DualSenseReader
return state.Connected;
}
- private static void SetState(in DualSenseState state)
+ private static void SetState(in PadState state)
{
lock (Gate)
{
@@ -399,7 +366,7 @@ internal static class DualSenseReader
return null;
}
- private static bool TryParseReport(ReadOnlySpan report, out DualSenseState state)
+ private static bool TryParseReport(ReadOnlySpan report, out PadState state)
{
// USB: report id 0x01, payload starts at [1].
// Bluetooth extended: report id 0x31, sequence byte at [1], payload at [2].
@@ -429,21 +396,21 @@ internal static class DualSenseReader
var buttons2 = report[offset + 9];
uint buttons = 0;
- buttons |= (buttons0 & 0x10) != 0 ? ButtonSquare : 0;
- buttons |= (buttons0 & 0x20) != 0 ? ButtonCross : 0;
- buttons |= (buttons0 & 0x40) != 0 ? ButtonCircle : 0;
- buttons |= (buttons0 & 0x80) != 0 ? ButtonTriangle : 0;
+ buttons |= (buttons0 & 0x10) != 0 ? OrbisPadButton.Square : 0;
+ buttons |= (buttons0 & 0x20) != 0 ? OrbisPadButton.Cross : 0;
+ buttons |= (buttons0 & 0x40) != 0 ? OrbisPadButton.Circle : 0;
+ buttons |= (buttons0 & 0x80) != 0 ? OrbisPadButton.Triangle : 0;
buttons |= HatToButtons(buttons0 & 0x0F);
- buttons |= (buttons1 & 0x01) != 0 ? ButtonL1 : 0;
- buttons |= (buttons1 & 0x02) != 0 ? ButtonR1 : 0;
- buttons |= (buttons1 & 0x04) != 0 ? ButtonL2 : 0;
- buttons |= (buttons1 & 0x08) != 0 ? ButtonR2 : 0;
- buttons |= (buttons1 & 0x20) != 0 ? ButtonOptions : 0;
- buttons |= (buttons1 & 0x40) != 0 ? ButtonL3 : 0;
- buttons |= (buttons1 & 0x80) != 0 ? ButtonR3 : 0;
- buttons |= (buttons2 & 0x02) != 0 ? ButtonTouchPad : 0;
+ buttons |= (buttons1 & 0x01) != 0 ? OrbisPadButton.L1 : 0;
+ buttons |= (buttons1 & 0x02) != 0 ? OrbisPadButton.R1 : 0;
+ buttons |= (buttons1 & 0x04) != 0 ? OrbisPadButton.L2 : 0;
+ buttons |= (buttons1 & 0x08) != 0 ? OrbisPadButton.R2 : 0;
+ buttons |= (buttons1 & 0x20) != 0 ? OrbisPadButton.Options : 0;
+ buttons |= (buttons1 & 0x40) != 0 ? OrbisPadButton.L3 : 0;
+ buttons |= (buttons1 & 0x80) != 0 ? OrbisPadButton.R3 : 0;
+ buttons |= (buttons2 & 0x02) != 0 ? OrbisPadButton.TouchPad : 0;
- state = new DualSenseState(
+ state = new PadState(
Connected: true,
Buttons: buttons,
LeftX: leftX,
@@ -457,14 +424,14 @@ internal static class DualSenseReader
private static uint HatToButtons(int hat) => hat switch
{
- 0 => ButtonUp,
- 1 => ButtonUp | ButtonRight,
- 2 => ButtonRight,
- 3 => ButtonRight | ButtonDown,
- 4 => ButtonDown,
- 5 => ButtonDown | ButtonLeft,
- 6 => ButtonLeft,
- 7 => ButtonLeft | ButtonUp,
+ 0 => OrbisPadButton.Up,
+ 1 => OrbisPadButton.Up | OrbisPadButton.Right,
+ 2 => OrbisPadButton.Right,
+ 3 => OrbisPadButton.Right | OrbisPadButton.Down,
+ 4 => OrbisPadButton.Down,
+ 5 => OrbisPadButton.Down | OrbisPadButton.Left,
+ 6 => OrbisPadButton.Left,
+ 7 => OrbisPadButton.Left | OrbisPadButton.Up,
_ => 0,
};
}
diff --git a/src/SharpEmu.Libs/Pad/PadExports.cs b/src/SharpEmu.Libs/Pad/PadExports.cs
index dc9f3f8..459de27 100644
--- a/src/SharpEmu.Libs/Pad/PadExports.cs
+++ b/src/SharpEmu.Libs/Pad/PadExports.cs
@@ -31,6 +31,7 @@ public static class PadExports
{
_initialized = true;
DualSenseReader.EnsureStarted();
+ XInputReader.EnsureStarted();
return ctx.SetReturn(0);
}
@@ -61,9 +62,12 @@ public static class PadExports
}
DualSenseReader.EnsureStarted();
+ XInputReader.EnsureStarted();
Console.Error.WriteLine(DualSenseReader.TryGetState(out _)
? "[LOADER][INFO] Controls: DualSense connected (keyboard fallback also active)."
- : "[LOADER][INFO] Keyboard controls: Arrow keys = D-pad, WASD = left stick, IJKL = right stick, Z/Enter = Cross, X/Esc = Circle, C = Square, V = Triangle, Q = L1, E = R1, R = L2, F = R2, Tab/Backspace = Options. A DualSense will be used automatically when plugged in.");
+ : XInputReader.TryGetState(out _)
+ ? "[LOADER][INFO] Controls: Xbox controller connected (keyboard fallback also active)."
+ : "[LOADER][INFO] Keyboard controls: Arrow keys = D-pad, WASD = left stick, IJKL = right stick, Z/Enter = Cross, X/Esc = Circle, C = Square, V = Triangle, Q = L1, E = R1, R = L2, F = R2, Tab/Backspace = Options. A DualSense or Xbox controller will be used automatically when plugged in.");
return ctx.SetReturn(PrimaryPadHandle);
}
@@ -201,6 +205,7 @@ public static class PadExports
}
DualSenseReader.SetRumble(parameter[0], parameter[1]);
+ XInputReader.SetRumble(parameter[0], parameter[1]);
return ctx.SetReturn(0);
}
@@ -277,6 +282,17 @@ public static class PadExports
r2 = Math.Max(r2, pad.R2);
}
+ if (XInputReader.TryGetState(out var xpad))
+ {
+ buttons |= xpad.Buttons;
+ leftX = MergeAxis(xpad.LeftX, leftX);
+ leftY = MergeAxis(xpad.LeftY, leftY);
+ rightX = MergeAxis(xpad.RightX, rightX);
+ rightY = MergeAxis(xpad.RightY, rightY);
+ l2 = Math.Max(l2, xpad.L2);
+ r2 = Math.Max(r2, xpad.R2);
+ }
+
BinaryPrimitives.WriteUInt32LittleEndian(data[0x00..], buttons);
data[0x04] = leftX;
data[0x05] = leftY;
diff --git a/src/SharpEmu.Libs/Pad/PadState.cs b/src/SharpEmu.Libs/Pad/PadState.cs
new file mode 100644
index 0000000..c934255
--- /dev/null
+++ b/src/SharpEmu.Libs/Pad/PadState.cs
@@ -0,0 +1,40 @@
+// Copyright (C) 2026 SharpEmu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+namespace SharpEmu.Libs.Pad;
+
+///
+/// Snapshot of a physical controller's state, already translated to ORBIS
+/// pad conventions (SCE_PAD_BUTTON bits; sticks 0..255 with 128 centered
+/// and Y growing downward; triggers 0..255).
+///
+internal readonly record struct PadState(
+ bool Connected,
+ uint Buttons,
+ byte LeftX,
+ byte LeftY,
+ byte RightX,
+ byte RightY,
+ byte L2,
+ byte R2);
+
+/// SCE_PAD_BUTTON bit values.
+internal static class OrbisPadButton
+{
+ internal const uint L3 = 0x0002;
+ internal const uint R3 = 0x0004;
+ internal const uint Options = 0x0008;
+ internal const uint Up = 0x0010;
+ internal const uint Right = 0x0020;
+ internal const uint Down = 0x0040;
+ internal const uint Left = 0x0080;
+ internal const uint L2 = 0x0100;
+ internal const uint R2 = 0x0200;
+ internal const uint L1 = 0x0400;
+ internal const uint R1 = 0x0800;
+ internal const uint Triangle = 0x1000;
+ internal const uint Circle = 0x2000;
+ internal const uint Cross = 0x4000;
+ internal const uint Square = 0x8000;
+ internal const uint TouchPad = 0x100000;
+}
diff --git a/src/SharpEmu.Libs/Pad/XInputReader.cs b/src/SharpEmu.Libs/Pad/XInputReader.cs
new file mode 100644
index 0000000..9336120
--- /dev/null
+++ b/src/SharpEmu.Libs/Pad/XInputReader.cs
@@ -0,0 +1,246 @@
+// Copyright (C) 2026 SharpEmu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+using System.Runtime.InteropServices;
+
+namespace SharpEmu.Libs.Pad;
+
+///
+/// Reads Xbox 360 / Xbox One (and other XInput-compatible) controllers via
+/// the Windows XInput API on a background thread, translated to the same
+/// ORBIS pad conventions as . Supports rumble
+/// and hot-plug retry; the first connected slot (of four) is used.
+///
+internal static class XInputReader
+{
+ private const uint ErrorSuccess = 0;
+ private const int SlotCount = 4;
+ private const byte TriggerThreshold = 30; // XINPUT_GAMEPAD_TRIGGER_THRESHOLD
+
+ // XINPUT_GAMEPAD wButtons bit values.
+ private const ushort XinputDpadUp = 0x0001;
+ private const ushort XinputDpadDown = 0x0002;
+ private const ushort XinputDpadLeft = 0x0004;
+ private const ushort XinputDpadRight = 0x0008;
+ private const ushort XinputStart = 0x0010;
+ private const ushort XinputBack = 0x0020;
+ private const ushort XinputLeftThumb = 0x0040;
+ private const ushort XinputRightThumb = 0x0080;
+ private const ushort XinputLeftShoulder = 0x0100;
+ private const ushort XinputRightShoulder = 0x0200;
+ private const ushort XinputA = 0x1000;
+ private const ushort XinputB = 0x2000;
+ private const ushort XinputX = 0x4000;
+ private const ushort XinputY = 0x8000;
+
+ private static readonly object Gate = new();
+ private static PadState _state;
+ private static bool _started;
+ private static int _slot = -1; // connected XInput user index, -1 when none
+ private static byte _motorLeft;
+ private static byte _motorRight;
+
+ /// Starts the background reader once; safe to call repeatedly.
+ internal static void EnsureStarted()
+ {
+ if (!OperatingSystem.IsWindows())
+ {
+ return;
+ }
+
+ lock (Gate)
+ {
+ if (_started)
+ {
+ return;
+ }
+
+ _started = true;
+ var thread = new Thread(ReadLoop)
+ {
+ IsBackground = true,
+ Name = "XInputReader",
+ };
+ thread.Start();
+ }
+ }
+
+ internal static bool TryGetState(out PadState state)
+ {
+ lock (Gate)
+ {
+ state = _state;
+ }
+
+ return state.Connected;
+ }
+
+ private static void SetState(in PadState state)
+ {
+ lock (Gate)
+ {
+ _state = state;
+ }
+ }
+
+ /// Sets rumble; large = left/strong motor, small = right/weak.
+ internal static void SetRumble(byte largeMotor, byte smallMotor)
+ {
+ lock (Gate)
+ {
+ if (_motorLeft == largeMotor && _motorRight == smallMotor)
+ {
+ return;
+ }
+
+ _motorLeft = largeMotor;
+ _motorRight = smallMotor;
+ SendRumbleLocked();
+ }
+ }
+
+ private static void SendRumbleLocked()
+ {
+ if (_slot < 0)
+ {
+ return; // resent on connect
+ }
+
+ var vibration = new XInputVibration
+ {
+ LeftMotorSpeed = (ushort)(_motorLeft * 257), // 0..255 -> 0..65535
+ RightMotorSpeed = (ushort)(_motorRight * 257),
+ };
+ _ = XInputSetState((uint)_slot, ref vibration);
+ }
+
+ private static void ReadLoop()
+ {
+ try
+ {
+ while (true)
+ {
+ var slot = FindConnectedSlot();
+ if (slot < 0)
+ {
+ SetState(default);
+ Thread.Sleep(1000);
+ continue;
+ }
+
+ lock (Gate)
+ {
+ _slot = slot;
+ SendRumbleLocked();
+ }
+
+ Console.Error.WriteLine("[LOADER][INFO] XInput (Xbox) controller connected.");
+ while (XInputGetState((uint)slot, out var state) == ErrorSuccess)
+ {
+ SetState(Translate(state.Gamepad));
+ Thread.Sleep(8);
+ }
+
+ Console.Error.WriteLine("[LOADER][INFO] XInput (Xbox) controller disconnected.");
+ lock (Gate)
+ {
+ _slot = -1;
+ _motorLeft = 0;
+ _motorRight = 0;
+ _state = default;
+ }
+
+ Thread.Sleep(1000);
+ }
+ }
+ catch (DllNotFoundException)
+ {
+ // XInput unavailable on this system; leave the reader disconnected.
+ }
+ catch (EntryPointNotFoundException)
+ {
+ }
+ }
+
+ private static int FindConnectedSlot()
+ {
+ for (var index = 0; index < SlotCount; index++)
+ {
+ if (XInputGetState((uint)index, out _) == ErrorSuccess)
+ {
+ return index;
+ }
+ }
+
+ return -1;
+ }
+
+ private static PadState Translate(in XInputGamepad pad)
+ {
+ uint buttons = 0;
+ buttons |= (pad.Buttons & XinputDpadUp) != 0 ? OrbisPadButton.Up : 0;
+ buttons |= (pad.Buttons & XinputDpadDown) != 0 ? OrbisPadButton.Down : 0;
+ buttons |= (pad.Buttons & XinputDpadLeft) != 0 ? OrbisPadButton.Left : 0;
+ buttons |= (pad.Buttons & XinputDpadRight) != 0 ? OrbisPadButton.Right : 0;
+ buttons |= (pad.Buttons & XinputStart) != 0 ? OrbisPadButton.Options : 0;
+ buttons |= (pad.Buttons & XinputBack) != 0 ? OrbisPadButton.TouchPad : 0;
+ buttons |= (pad.Buttons & XinputLeftThumb) != 0 ? OrbisPadButton.L3 : 0;
+ buttons |= (pad.Buttons & XinputRightThumb) != 0 ? OrbisPadButton.R3 : 0;
+ buttons |= (pad.Buttons & XinputLeftShoulder) != 0 ? OrbisPadButton.L1 : 0;
+ buttons |= (pad.Buttons & XinputRightShoulder) != 0 ? OrbisPadButton.R1 : 0;
+ buttons |= (pad.Buttons & XinputA) != 0 ? OrbisPadButton.Cross : 0;
+ buttons |= (pad.Buttons & XinputB) != 0 ? OrbisPadButton.Circle : 0;
+ buttons |= (pad.Buttons & XinputX) != 0 ? OrbisPadButton.Square : 0;
+ buttons |= (pad.Buttons & XinputY) != 0 ? OrbisPadButton.Triangle : 0;
+ buttons |= pad.LeftTrigger > TriggerThreshold ? OrbisPadButton.L2 : 0;
+ buttons |= pad.RightTrigger > TriggerThreshold ? OrbisPadButton.R2 : 0;
+
+ return new PadState(
+ Connected: true,
+ Buttons: buttons,
+ LeftX: AxisToByte(pad.ThumbLX),
+ LeftY: AxisToByteInverted(pad.ThumbLY),
+ RightX: AxisToByte(pad.ThumbRX),
+ RightY: AxisToByteInverted(pad.ThumbRY),
+ L2: pad.LeftTrigger,
+ R2: pad.RightTrigger);
+ }
+
+ private static byte AxisToByte(short value) => (byte)((value + 32768) >> 8);
+
+ // XInput Y grows upward, ORBIS pads report Y growing downward.
+ private static byte AxisToByteInverted(short value) => (byte)(255 - ((value + 32768) >> 8));
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct XInputGamepad
+ {
+ public ushort Buttons;
+ public byte LeftTrigger;
+ public byte RightTrigger;
+ public short ThumbLX;
+ public short ThumbLY;
+ public short ThumbRX;
+ public short ThumbRY;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct XInputState
+ {
+ public uint PacketNumber;
+ public XInputGamepad Gamepad;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct XInputVibration
+ {
+ public ushort LeftMotorSpeed;
+ public ushort RightMotorSpeed;
+ }
+
+ // xinput1_4.dll ships with Windows 8 and later.
+ [DllImport("xinput1_4.dll")]
+ private static extern uint XInputGetState(uint userIndex, out XInputState state);
+
+ [DllImport("xinput1_4.dll")]
+ private static extern uint XInputSetState(uint userIndex, ref XInputVibration vibration);
+}
diff --git a/src/SharpEmu.Logging/SharpEmuLog.cs b/src/SharpEmu.Logging/SharpEmuLog.cs
index e3dcac9..f778bb6 100644
--- a/src/SharpEmu.Logging/SharpEmuLog.cs
+++ b/src/SharpEmu.Logging/SharpEmuLog.cs
@@ -12,8 +12,14 @@ public static class SharpEmuLog
new(StringComparer.Ordinal);
private static readonly object ConfigurationSync = new();
private static volatile LogLevel _minimumLevel = ResolveMinimumLevelFromEnvironment();
+ private static bool _fileCapturesAllLevels;
private static ISharpEmuLogSink _sink = ResolveSinkFromEnvironment();
+ ///
+ /// Entries below this level are dropped. When a SHARPEMU_LOG_FILE sink is
+ /// active it only limits the console — the file receives every level.
+ /// disables logging entirely, file included.
+ ///
public static LogLevel MinimumLevel
{
get => _minimumLevel;
@@ -40,6 +46,11 @@ public static class SharpEmuLog
return;
}
+ // A replacement sink is not the environment-configured
+ // console+file pair, so the minimum level applies globally
+ // again.
+ _fileCapturesAllLevels = false;
+
if (_sink is IDisposable disposable)
{
try
@@ -122,7 +133,14 @@ public static class SharpEmuLog
internal static bool IsEnabled(LogLevel level)
{
var minimum = _minimumLevel;
- return minimum != LogLevel.None && level >= minimum;
+ if (minimum == LogLevel.None)
+ {
+ return false;
+ }
+
+ // With a file sink capturing all levels, the console filter is
+ // applied per-sink instead of here.
+ return _fileCapturesAllLevels || level >= minimum;
}
internal static void Write(
@@ -187,7 +205,10 @@ public static class SharpEmuLog
try
{
var fileSink = new FileLogSink(logFilePath, append: true, includeTimestamp: true);
- return new CompositeLogSink(consoleSink, fileSink);
+ // The file gets every level; the configured minimum only
+ // limits what reaches the console.
+ _fileCapturesAllLevels = true;
+ return new CompositeLogSink(new MinimumLevelFilterSink(consoleSink), fileSink);
}
catch (Exception ex)
{
@@ -199,6 +220,25 @@ public static class SharpEmuLog
return consoleSink;
}
+ ///
+ /// Forwards only entries at or above . Wraps
+ /// the console sink when a file sink captures all levels.
+ ///
+ private sealed class MinimumLevelFilterSink : ISharpEmuLogSink
+ {
+ private readonly ISharpEmuLogSink _inner;
+
+ internal MinimumLevelFilterSink(ISharpEmuLogSink inner) => _inner = inner;
+
+ public void Write(in LogEntry entry)
+ {
+ if (entry.Level >= _minimumLevel)
+ {
+ _inner.Write(in entry);
+ }
+ }
+ }
+
private static bool IsTrueLike(string? text)
{
if (string.IsNullOrWhiteSpace(text))