2 * Copyright (C) 2018-2019 Dridi Boukelmoune
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * Alternatively, you can also redistribute this library and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of either License along with this
20 * program. If not, see <http://www.gnu.org/licenses/>.
25 using System.Reflection;
27 using static System.Diagnostics.Debug;
29 [assembly: AssemblyVersionAttribute("0.0")]
34 internal static class LittleEndian
36 private static uint Read(byte[] buf, int off, int len)
41 Assert(off + len / 8 <= buf.Length);
47 res += (uint)buf[off] << shl;
56 internal static short ReadShort(byte[] buf, int off)
58 return ((short)ReadUShort(buf, off));
61 internal static ushort ReadUShort(byte[] buf, int off)
63 return ((ushort)Read(buf, off, 16));
66 internal static uint ReadUInt(byte[] buf, int off)
68 return (Read(buf, off, 32));
71 internal static void Write(byte[] buf, int off, long val,
74 Write(buf, off, (ulong)val, len);
77 internal static void Write(byte[] buf, int off, ulong val,
83 Assert(off + len / 8 <= buf.Length);
97 public const int HEADER_SIZE_XA = 32;
98 public const int HEADER_SIZE_RIFF = 44;
100 internal const uint HEADER_MAGIC = 0x3144574b;
101 internal const uint BLOCK_SAMPLES = 32;
102 internal const uint WAVE_HEADER_LEN = 16;
103 internal const ushort WAVE_FORMAT_PCM = 1;
105 public long DataLengthPcm;
107 public uint BlockSizePcm;
108 public uint BlockSizeXa;
109 public ushort SamplesRate;
110 public uint SampleBits;
111 public uint Channels;
113 public uint BlockSamples
115 get { return (BLOCK_SAMPLES * Channels); }
118 public long RiffHeaderLength
120 get { return (HEADER_SIZE_RIFF - 8 + DataLengthPcm); }
123 public uint WaveHeaderLength
125 get { return (WAVE_HEADER_LEN); }
128 public uint WaveFormatPcm
130 get { return (WAVE_FORMAT_PCM); }
133 public uint WaveByteRate
137 return (SamplesRate * BlockSizePcm /
142 public uint WaveBlockAlign
144 get { return (Channels * SampleBits / 8); }
147 internal Format() { }
149 public byte[] WritePcm(short[] pcm, long len)
152 throw new ArgumentNullException(
153 "bjxa.Format.WritePcm: pcm");
154 if (len % sizeof(short) != 0)
155 throw new ArgumentException(
156 "Invalid length: {len}");
157 int pcmLen = (int)len / sizeof(short);
158 if (pcmLen < 0 || pcmLen > pcm.Length)
159 throw new ArgumentException(
160 "Invalid length: {len}");
162 byte[] dst = new byte[pcmLen * sizeof(short)];
164 int dstOff = 0, pcmOff = 0;
165 while (pcmOff < pcmLen)
167 ushort sample = (ushort)pcm[pcmOff];
170 dst[dstOff] = (byte)(sample & 0xff);
172 dst[dstOff] = (byte)(sample >> 8);
179 public int WritePcm(Stream wav, short[] pcm, long len)
182 throw new ArgumentNullException(
183 "bjxa.Format.WritePcm: wav");
185 throw new ArgumentNullException(
186 "bjxa.Format.WritePcm: pcm");
188 byte[] buf = WritePcm(pcm, len);
189 wav.Write(buf, 0, buf.Length);
194 public sealed class FormatError : Exception
196 internal FormatError(String message) : base(message) { }
199 internal delegate byte Inflate(DecoderState dec, short[] dst, int off,
202 internal class ChannelState
204 internal short Prev0;
205 internal short Prev1;
208 internal class DecoderState
210 internal uint DataLength;
211 internal uint Samples;
212 internal ushort SamplesRate;
213 internal uint BlockSize;
214 internal uint Channels;
215 internal ChannelState[] LR;
216 internal Inflate InflateFunc;
218 internal DecoderState()
220 LR = new ChannelState[2] {
226 internal bool IsValid()
228 long blocks, maxSamples;
230 if (DataLength == 0 || Samples == 0 ||
231 SamplesRate == 0 || BlockSize == 0)
234 if (Channels != 1 && Channels != 2)
237 blocks = DataLength / BlockSize;
238 maxSamples = (Format.BLOCK_SAMPLES * DataLength) /
239 (BlockSize * Channels);
241 if (blocks * BlockSize != DataLength)
244 if (Samples > maxSamples)
247 if (maxSamples - Samples >= Format.BLOCK_SAMPLES)
250 if (InflateFunc == null)
256 internal Format ToFormat()
260 Format fmt = new Format();
261 fmt.DataLengthPcm = Samples * Channels *
263 fmt.SamplesRate = SamplesRate;
265 fmt.Channels = Channels;
266 fmt.BlockSizeXa = BlockSize * Channels;
267 fmt.BlockSizePcm = Format.BLOCK_SAMPLES * Channels *
269 fmt.Blocks = DataLength / fmt.BlockSizeXa;
271 Assert(fmt.Blocks * fmt.BlockSizeXa == DataLength);
276 internal byte Inflate(short[] dst, int off, byte[] src)
278 return (InflateFunc(this, dst, off, src));
287 static Inflate BlockInflater(uint bits)
290 return ((dec, dst, off, src) => {
291 Assert(off == 0 || off == 1);
292 Assert(Format.BLOCK_SAMPLES *
293 dec.Channels == dst.Length);
295 byte profile = src[0];
298 for (uint n = Format.BLOCK_SAMPLES;
301 ushort s = src[srcOff];
305 (short)((s & 0xf0) << 8);
306 off += (int)dec.Channels;
308 (short)((s & 0x0f) << 12);
309 off += (int)dec.Channels;
316 return ((dec, dst, off, src) => {
317 Assert(off == 0 || off == 1);
318 Assert(Format.BLOCK_SAMPLES *
319 dec.Channels == dst.Length);
321 byte profile = src[0];
324 for (uint n = Format.BLOCK_SAMPLES;
327 int s = (src[srcOff] << 16) |
328 (src[srcOff + 1] << 8) |
333 ((s & 0x00fc0000) >> 8);
334 off += (int)dec.Channels;
336 ((s & 0x0003f000) >> 2);
337 off += (int)dec.Channels;
339 ((s & 0x00000fc0) << 4);
340 off += (int)dec.Channels;
342 ((s & 0x0000003f) << 10);
343 off += (int)dec.Channels;
350 return ((dec, dst, off, src) => {
351 Assert(off == 0 || off == 1);
352 Assert(Format.BLOCK_SAMPLES *
353 dec.Channels == dst.Length);
355 byte profile = src[0];
358 for (uint n = Format.BLOCK_SAMPLES;
362 (short)(src[srcOff] << 8);
363 off += (int)dec.Channels;
373 public Format ReadHeader(byte[] xa)
376 throw new ArgumentNullException(
377 "bjxa.Decoder.ReadHeader: xa");
378 if (xa.Length < Format.HEADER_SIZE_XA)
379 throw new ArgumentException(
382 DecoderState tmp = new DecoderState();
383 uint magic = LittleEndian.ReadUInt(xa, 0);
384 tmp.DataLength = LittleEndian.ReadUInt(xa, 4);
385 tmp.Samples = LittleEndian.ReadUInt(xa, 8);
386 tmp.SamplesRate = LittleEndian.ReadUShort(xa, 12);
388 tmp.Channels = xa[15];
389 /* XXX: skipping loop ptr field for now */
390 tmp.LR[0].Prev0 = LittleEndian.ReadShort(xa, 20);
391 tmp.LR[0].Prev1 = LittleEndian.ReadShort(xa, 22);
392 tmp.LR[1].Prev0 = LittleEndian.ReadShort(xa, 24);
393 tmp.LR[1].Prev1 = LittleEndian.ReadShort(xa, 26);
394 /* XXX: ignoring padding for now */
396 tmp.InflateFunc = BlockInflater(bits);
397 tmp.BlockSize = bits * 4 + 1;
399 if (magic != Format.HEADER_MAGIC || !tmp.IsValid())
400 throw new FormatError("Invalid XA header");
403 fmt = state.ToFormat();
405 return (state.ToFormat());
408 public Format ReadHeader(Stream xa)
411 throw new ArgumentNullException(
412 "bjxa.Decoder.ReadHeader: xa");
414 byte[] hdr = new byte[Format.HEADER_SIZE_XA];
415 if (xa.Read(hdr, 0, hdr.Length) != hdr.Length)
416 throw new EndOfStreamException("XA header");
418 return ReadHeader(hdr);
421 public byte[] WriteRiffHeader()
424 throw new InvalidOperationException(
425 "Decoder not ready");
426 Format fmt = state.ToFormat();
427 byte[] hdr = new byte[Format.HEADER_SIZE_RIFF];
428 LittleEndian.Write(hdr, 0, 0x46464952, 32); /* RIFF */
429 LittleEndian.Write(hdr, 4, fmt.RiffHeaderLength, 32);
430 LittleEndian.Write(hdr, 8, 0x45564157, 32); /* WAVE */
431 LittleEndian.Write(hdr, 12, 0x20746d66, 32); /* fmt */
432 LittleEndian.Write(hdr, 16, fmt.WaveHeaderLength, 32);
433 LittleEndian.Write(hdr, 20, fmt.WaveFormatPcm, 16);
434 LittleEndian.Write(hdr, 22, fmt.Channels, 16);
435 LittleEndian.Write(hdr, 24, fmt.SamplesRate, 32);
436 LittleEndian.Write(hdr, 28, fmt.WaveByteRate, 32);
437 LittleEndian.Write(hdr, 32, fmt.WaveBlockAlign, 16);
438 LittleEndian.Write(hdr, 34, fmt.SampleBits, 16);
439 LittleEndian.Write(hdr, 36, 0x61746164, 32); /* data */
440 LittleEndian.Write(hdr, 40, fmt.DataLengthPcm, 32);
444 public int WriteRiffHeader(Stream wav)
447 throw new InvalidOperationException(
448 "Decoder not ready");
450 throw new ArgumentNullException(
451 "bjxa.Decoder.WriteRiffHeader: wav");
453 byte[] hdr = WriteRiffHeader();
454 wav.Write(hdr, 0, hdr.Length);
458 private readonly short[,] GainFactor = {
466 private void DecodeInflated(short[] pcm, int off, byte prof)
468 Assert(off == 0 || off == 1);
469 Assert(Format.BLOCK_SAMPLES * state.Channels ==
472 int factor = prof >> 4;
473 int range = prof & 0x0f;
475 if (factor >= GainFactor.Length)
476 throw new FormatError(
477 $"Invalid factor: {factor}");
479 ChannelState chan = state.LR[off];
480 short k0 = GainFactor[factor, 0];
481 short k1 = GainFactor[factor, 1];
483 for (uint i = Format.BLOCK_SAMPLES; i > 0; i--)
485 int ranged = pcm[off] >> range;
486 int gain = (chan.Prev0 * k0) +
488 int sample = ranged + gain / 256;
490 sample = Math.Max(sample, Int16.MinValue);
491 sample = Math.Min(sample, Int16.MaxValue);
493 pcm[off] = (short)sample;
494 chan.Prev1 = chan.Prev0;
495 chan.Prev0 = (short)sample;
497 off += (int)state.Channels;
501 public int Decode(byte[] xa, short[] pcm, out long pcm_data)
504 throw new InvalidOperationException(
505 "Decoder not ready");
507 throw new ArgumentNullException(
508 "bjxa.Decoder.Decode: xa");
510 throw new ArgumentNullException(
511 "bjxa.Decoder.Decode: pcm");
514 throw new InvalidOperationException(
515 "XA stream fully decoded");
517 int xaLen = xa.Length * sizeof(byte);
519 int pcmLen = pcm.Length * sizeof(short);
521 byte[] xaBuf = new byte[state.BlockSize];
522 short[] pcmBuf = new short[fmt.BlockSamples];
524 long pcm_block = Math.Min(fmt.BlockSizePcm,
528 while (fmt.Blocks > 0 && xaLen >= fmt.BlockSizeXa &&
532 Array.Copy(xa, xaOff, xaBuf, 0, xaBuf.Length);
533 byte prof = state.Inflate(pcmBuf, 0, xaBuf);
534 DecodeInflated(pcmBuf, 0, prof);
536 xaOff += xaBuf.Length;
537 xaLen -= xaBuf.Length;
539 if (state.Channels == 2)
541 Array.Copy(xa, xaOff, xaBuf, 0,
543 prof = state.Inflate(pcmBuf, 1, xaBuf);
544 DecodeInflated(pcmBuf, 1, prof);
546 xaOff += xaBuf.Length;
547 xaLen -= xaBuf.Length;
550 Array.Copy(pcmBuf, 0, pcm, pcmOff,
551 pcm_block / sizeof(short));
552 pcmOff += pcmBuf.Length;
553 pcmLen -= (int)pcm_block;
554 pcm_data += pcm_block;
555 fmt.DataLengthPcm -= pcm_block;
557 pcm_block = Math.Min(fmt.BlockSizePcm,