diff --git a/CUETools.Codecs.FLAKE/BitReader.cs b/CUETools.Codecs.FLAKE/BitReader.cs new file mode 100644 index 0000000..1305464 --- /dev/null +++ b/CUETools.Codecs.FLAKE/BitReader.cs @@ -0,0 +1,245 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CUETools.Codecs.FLAKE +{ + class BitReader + { + byte[] buffer; + byte[] byte_to_unary_table; + int pos, len; + int _bitaccumulator; + + public int Position + { + get { return pos; } + } + + public byte[] Buffer + { + get + { + return buffer; + } + } + + public BitReader(byte[] _buffer, int _pos, int _len) + { + buffer = _buffer; + pos = _pos; + len = _len; + _bitaccumulator = 0; + + byte_to_unary_table = new byte[] { + 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 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, + 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 + }; + } + + /* supports reading 1 to 24 bits, in big endian format */ + public uint readbits_24(int bits) + { + uint result = (((uint)buffer[pos]) << 24) | (((uint)buffer[pos + 1]) << 16) | (((uint)buffer[pos + 2]) << 8) | ((uint)buffer[pos + 3]); + result <<= _bitaccumulator; + result >>= 32 - bits; + + int new_accumulator = (_bitaccumulator + bits); + pos += (new_accumulator >> 3); + _bitaccumulator = (new_accumulator & 7); + return result; + } + + public uint readbits_8(int bits) + { + uint result = (((uint)buffer[pos]) << 24) | (((uint)buffer[pos + 1]) << 16); + result <<= _bitaccumulator; + result >>= 32 - bits; + + int new_accumulator = (_bitaccumulator + bits); + pos += (new_accumulator >> 3); + _bitaccumulator = (new_accumulator & 7); + return result; + } + + public uint peekbits_24(int bits) + { + uint result = (((uint)buffer[pos]) << 24) | (((uint)buffer[pos + 1]) << 16) | (((uint)buffer[pos + 2]) << 8) | ((uint)buffer[pos + 3]); + result <<= _bitaccumulator; + result >>= 32 - bits; + return result; + } + + ///* supports reading 1 to 16 bits, in big endian format */ + //private unsafe uint peekbits_9(byte* buff, int pos) + //{ + // uint result = (((uint)buff[pos]) << 8) | (((uint)buff[pos + 1])); + // result <<= _bitaccumulator; + // result &= 0x0000ffff; + // result >>= 7; + // return result; + //} + + /* supports reading 1 to 16 bits, in big endian format */ + public void skipbits(int bits) + { + int new_accumulator = (_bitaccumulator + bits); + pos += (new_accumulator >> 3); + _bitaccumulator = (new_accumulator & 7); + } + + /* supports reading 1 to 32 bits, in big endian format */ + public uint readbits(int bits) + { + if (bits <= 24) + return readbits_24(bits); + + ulong result = (((ulong)buffer[pos]) << 32) | (((ulong)buffer[pos + 1]) << 24) | (((ulong)buffer[pos + 2]) << 16) | (((ulong)buffer[pos + 3]) << 8) | ((ulong)buffer[pos + 4]); + result <<= _bitaccumulator; + result &= 0x00ffffffffff; + result >>= 40 - bits; + int new_accumulator = (_bitaccumulator + bits); + pos += (new_accumulator >> 3); + _bitaccumulator = (new_accumulator & 7); + return (uint)result; + } + + /* reads a single bit */ + public uint readbit() + { + int new_accumulator; + uint result = buffer[pos]; + result <<= _bitaccumulator; + result = result >> 7 & 1; + new_accumulator = (_bitaccumulator + 1); + pos += (new_accumulator / 8); + _bitaccumulator = (new_accumulator % 8); + return result; + } + + public uint read_unary() + { + uint val = 0; + + int result = (buffer[pos] << _bitaccumulator) & 0xff; + if (result == 0) + { + val = 8 - (uint)_bitaccumulator; + _bitaccumulator = 0; + pos++; + return val + read_unary(); + // check eof + } + + val = byte_to_unary_table[result]; + + int new_accumulator = (_bitaccumulator + (int)val + 1); + pos += (new_accumulator / 8); + _bitaccumulator = (new_accumulator % 8); + return val; + } + + public void flush() + { + if (_bitaccumulator > 0) + readbits(8 - _bitaccumulator); + } + + public int readbits_signed(int bits) + { + int val = (int) readbits(bits); + val <<= (32 - bits); + val >>= (32 - bits); + return val; + } + + public uint read_utf8() + { + uint x = readbits(8); + uint v; + int i; + if (0 == (x & 0x80)) + { + v = x; + i = 0; + } + else if (0xC0 == (x & 0xE0)) /* 110xxxxx */ + { + v = x & 0x1F; + i = 1; + } + else if (0xE0 == (x & 0xF0)) /* 1110xxxx */ + { + v = x & 0x0F; + i = 2; + } + else if (0xF0 == (x & 0xF8)) /* 11110xxx */ + { + v = x & 0x07; + i = 3; + } + else if (0xF8 == (x & 0xFC)) /* 111110xx */ + { + v = x & 0x03; + i = 4; + } + else if (0xFC == (x & 0xFE)) /* 1111110x */ + { + v = x & 0x01; + i = 5; + } + else if (0xFE == x) /* 11111110 */ + { + v = 0; + i = 6; + } + else + throw new Exception("invalid utf8 encoding"); + for (; i > 0; i--) + { + x = readbits(8); + if (0x80 != (x & 0xC0)) /* 10xxxxxx */ + throw new Exception("invalid utf8 encoding"); + v <<= 6; + v |= (x & 0x3F); + } + return v; + } + + public int read_rice_signed(int k) + { + uint msbs = read_unary(); + uint lsbs = readbits_24(k); + uint uval = (msbs << k) | lsbs; + return (int)(uval >> 1 ^ -(int)(uval & 1)); + } + + public int read_rice_signed8(int k) + { + uint msbs = read_unary(); + uint lsbs = readbits_8(k); + uint uval = (msbs << k) | lsbs; + return (int)(uval >> 1 ^ -(int)(uval & 1)); + } + + public int read_unary_signed() + { + uint uval = read_unary(); + return (int)(uval >> 1 ^ -(int)(uval & 1)); + } + } +} diff --git a/CUETools.Codecs.FLAKE/BitWriter.cs b/CUETools.Codecs.FLAKE/BitWriter.cs new file mode 100644 index 0000000..0f727b7 --- /dev/null +++ b/CUETools.Codecs.FLAKE/BitWriter.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CUETools.Codecs.FLAKE +{ + class BitWriter + { + uint bit_buf; + int bit_left; + byte[] buffer; + int buf_start, buf_ptr, buf_end; + bool eof; + + public BitWriter(byte[] buf, int pos, int len) + { + buffer = buf; + buf_start = pos; + buf_ptr = pos; + buf_end = pos + len; + bit_left = 32; + bit_buf = 0; + eof = false; + } + + public void writebits_signed(int bits, int val) + { + writebits(bits, val & ((1 << bits) - 1)); + } + + public void writebits_signed(uint bits, int val) + { + writebits((int) bits, val & ((1 << (int) bits) - 1)); + } + + public void writebits(int bits, int val) + { + writebits(bits, (uint)val); + } + + public void writebits(int bits, uint val) + { + //assert(bits == 32 || val < (1U << bits)); + + if (bits == 0 || eof) return; + if ((buf_ptr + 3) >= buf_end) + { + eof = true; + return; + } + if (bits < bit_left) + { + bit_buf = (bit_buf << bits) | val; + bit_left -= bits; + } + else + { + uint bb = 0; + if (bit_left == 32) + { + //assert(bits == 32); + bb = val; + } + else + { + bb = (bit_buf << bit_left) | (val >> (bits - bit_left)); + bit_left += (32 - bits); + } + if (buffer != null) + { + buffer[buf_ptr + 3] = (byte)(bb & 0xFF); bb >>= 8; + buffer[buf_ptr + 2] = (byte)(bb & 0xFF); bb >>= 8; + buffer[buf_ptr + 1] = (byte)(bb & 0xFF); bb >>= 8; + buffer[buf_ptr + 0] = (byte)(bb & 0xFF); + } + buf_ptr += 4; + bit_buf = val; + } + } + + public void write_utf8(int val) + { + write_utf8((uint)val); + } + + public void write_utf8(uint val) + { + if (val < 0x80) + { + writebits(8, val); + return; + } + int bytes = (Flake.log2i(val) + 4) / 5; + int shift = (bytes - 1) * 6; + writebits(8, (256U - (256U >> bytes)) | (val >> shift)); + while (shift >= 6) + { + shift -= 6; + writebits(8, 0x80 | ((val >> shift) & 0x3F)); + } + } + + public void write_rice_signed(int k, int val) + { + int v, q; + + if (k < 0) return; + + // convert signed to unsigned + v = -2 * val - 1; + v ^= (v >> 31); + + // write quotient in unary + q = (v >> k) + 1; + while (q > 31) + { + writebits(31, 0); + q -= 31; + } + writebits(q, 1); + + // write write remainder in binary using 'k' bits + writebits(k, v & ((1 << k) - 1)); + } + + public void flush() + { + bit_buf <<= bit_left; + while (bit_left < 32 && !eof) + { + if (buf_ptr >= buf_end) + { + eof = true; + break; + } + if (buffer != null) + buffer[buf_ptr] = (byte)(bit_buf >> 24); + buf_ptr++; + bit_buf <<= 8; + bit_left += 8; + } + bit_left = 32; + bit_buf = 0; + } + + public int Length + { + get + { + return buf_ptr - buf_start; + } + } + } +} diff --git a/CUETools.Codecs.FLAKE/CUETools.Codecs.FLAKE.csproj b/CUETools.Codecs.FLAKE/CUETools.Codecs.FLAKE.csproj new file mode 100644 index 0000000..50608c2 --- /dev/null +++ b/CUETools.Codecs.FLAKE/CUETools.Codecs.FLAKE.csproj @@ -0,0 +1,60 @@ + + + Debug + AnyCPU + 8.0.50727 + 2.0 + {082D6B9E-326E-4D15-9798-EDAE9EDE70A6} + Library + Properties + CUETools.Codecs.FLAKE + CUETools.Codecs.FLAKE + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + + + + + + + + + + + + + + + + + + {6458A13A-30EF-45A9-9D58-E5031B17BEE2} + CUETools.Codecs + + + + + \ No newline at end of file diff --git a/CUETools.Codecs.FLAKE/Flake.cs b/CUETools.Codecs.FLAKE/Flake.cs new file mode 100644 index 0000000..794c4a4 --- /dev/null +++ b/CUETools.Codecs.FLAKE/Flake.cs @@ -0,0 +1,248 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CUETools.Codecs.FLAKE +{ + public class Flake + { + public const int MAX_BLOCKSIZE = 65535; + public const int MAX_RICE_PARAM = 14; + public const int MAX_PARTITION_ORDER = 8; + public const int MAX_PARTITIONS = 1 << MAX_PARTITION_ORDER; + public static int log2i(int v) + { + return log2i((uint)v); + } + public static int log2i(uint v) + { + int i; + int n = 0; + if (0 != (v & 0xffff0000)) { v >>= 16; n += 16; } + if (0 != (v & 0xff00)) { v >>= 8; n += 8; } + for (i = 2; i < 256; i <<= 1) + { + if (v >= i) n++; + else break; + } + return n; + } + + public static PredictionType LookupPredictionType(string name) + { + switch (name) + { + case "fixed": return PredictionType.Fixed; + case "levinson": return PredictionType.Levinson; + case "search" : return PredictionType.Search; + } + return (PredictionType)Int32.Parse(name); + } + + public static StereoMethod LookupStereoMethod(string name) + { + switch (name) + { + case "independent": return StereoMethod.Independent; + case "estimate": return StereoMethod.Estimate; + case "estimate2": return StereoMethod.Estimate2; + case "estimate3": return StereoMethod.Estimate3; + case "estimate4": return StereoMethod.Estimate4; + case "estimate5": return StereoMethod.Estimate5; + case "search": return StereoMethod.Search; + } + return (StereoMethod)Int32.Parse(name); + } + + public static OrderMethod LookupOrderMethod(string name) + { + switch (name) + { + case "estimate": return OrderMethod.Estimate; + case "logfast": return OrderMethod.LogFast; + case "logsearch": return OrderMethod.LogSearch; + case "estsearch": return OrderMethod.EstSearch; + case "search": return OrderMethod.Search; + } + return (OrderMethod)Int32.Parse(name); + } + + public static WindowFunction LookupWindowFunction(string name) + { + string[] parts = name.Split(','); + WindowFunction res = (WindowFunction)0; + foreach (string part in parts) + { + switch (part) + { + case "welch": res |= WindowFunction.Welch; break; + case "tukey": res |= WindowFunction.Tukey; break; + case "hann": res |= WindowFunction.Hann; break; + case "flattop": res |= WindowFunction.Flattop; break; + default: res |= (WindowFunction)Int32.Parse(name); break; + } + } + return res; + } + + unsafe public static bool memcmp(int* res, int* smp, int n) + { + for (int i = n; i > 0; i--) + if (*(res++) != *(smp++)) + return true; + return false; + } + unsafe public static void memcpy(int* res, int* smp, int n) + { + for (int i = n; i > 0; i--) + *(res++) = *(smp++); + } + unsafe public static void memset(int* res, int smp, int n) + { + for (int i = n; i > 0; i--) + *(res++) = smp; + } + } + + unsafe struct RiceContext + { + public int porder; /* partition order */ + public fixed uint rparams[Flake.MAX_PARTITIONS]; /* Rice parameters */ + public fixed int esc_bps[Flake.MAX_PARTITIONS]; /* bps if using escape code */ + }; + + unsafe struct FlacSubframe + { + public SubframeType type; + public int order; + public uint obits; + public uint wbits; + public int cbits; + public int shift; + public fixed int coefs[lpc.MAX_LPC_ORDER]; + public int* samples; + public int* residual; + public RiceContext rc; + public uint size; + public fixed uint done_lpcs[lpc.MAX_LPC_WINDOWS]; + public uint done_fixed; + public int window; + }; + + unsafe struct FlacFrame + { + public int blocksize; + public int bs_code0, bs_code1; + public ChannelMode ch_mode; + public int ch_order0, ch_order1; + public byte crc8; + public FlacSubframe* subframes; + public uint frame_count; + public FlacSubframe current; + } + + public enum OrderMethod + { + Max = 0, + Estimate = 1, + LogFast = 2, + LogSearch = 3, + EstSearch = 4, + Search = 5 + } + + /// + /// Type of linear prediction + /// + public enum PredictionType + { + /// + /// verbatim + /// + None = 0, + /// + /// Fixed only + /// + Fixed = 1, + /// + /// Levinson-Durbin recursion + /// + Levinson = 2, + /// + /// Exhaustive search + /// + Search = 3, + /// + /// Internal; Use prediction type from previous estimation + /// + Estimated = 4 + } + + public enum StereoMethod + { + Independent = 0, + Estimate = 1, + Estimate2 = 2, + Estimate3 = 3, + Estimate4 = 4, + Estimate5 = 5, + Search = 9 + } + + public enum SubframeType + { + Constant = 0, + Verbatim = 1, + Fixed = 8, + LPC = 32 + }; + + public enum ChannelMode + { + NotStereo = 0, + LeftRight = 1, + LeftSide = 8, + RightSide = 9, + MidSide = 10 + } + + public enum WindowFunction + { + Welch = 1, + Tukey = 2, + Hann = 4, + Flattop = 8 + } + + public enum MetadataType + { + + /// + /// STREAMINFO block + /// + FLAC__METADATA_TYPE_STREAMINFO = 0, + + /// + /// PADDING block + /// + FLAC__METADATA_TYPE_PADDING = 1, + + FLAC__METADATA_TYPE_APPLICATION = 2, + /**< APPLICATION block */ + + FLAC__METADATA_TYPE_SEEKTABLE = 3, + /**< SEEKTABLE block */ + + FLAC__METADATA_TYPE_VORBIS_COMMENT = 4, + /**< VORBISCOMMENT block (a.k.a. FLAC tags) */ + + FLAC__METADATA_TYPE_CUESHEET = 5, + /**< CUESHEET block */ + + FLAC__METADATA_TYPE_PICTURE = 6, + /**< PICTURE block */ + + FLAC__METADATA_TYPE_UNDEFINED = 7 + /**< marker to denote beginning of undefined type range; this number will increase as new metadata types are added */ + }; +} diff --git a/CUETools.Codecs.FLAKE/FlakeReader.cs b/CUETools.Codecs.FLAKE/FlakeReader.cs new file mode 100644 index 0000000..21ceb64 --- /dev/null +++ b/CUETools.Codecs.FLAKE/FlakeReader.cs @@ -0,0 +1,644 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; + +namespace CUETools.Codecs.FLAKE +{ + public class FlakeReader: IAudioSource + { + int[] flac_blocksizes; + int[] flac_bitdepths; + + int[] samplesBuffer; + int[] residualBuffer; + + byte[] _framesBuffer; + int _framesBufferLength = 0, _framesBufferOffset = 0; + + Crc8 crc8; + Crc16 crc16; + int channels; + uint bits_per_sample; + int sample_rate = 44100; + + uint min_block_size = 0; + uint max_block_size = 0; + uint min_frame_size = 0; + uint max_frame_size = 0; + + uint _samplesInBuffer, _samplesBufferOffset; + ulong _sampleCount = 0; + ulong _sampleOffset = 0; + + string _path; + Stream _IO; + + public int[] Samples + { + get + { + return samplesBuffer; + } + } + + public FlakeReader(string path, Stream IO) + { + _path = path; + _IO = IO != null ? IO : new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); + + flac_bitdepths = new int[8] { 0, 8, 12, 0, 16, 20, 24, 0 }; + flac_blocksizes = new int[15] { 0, 192, 576, 1152, 2304, 4608, 0, 0, 256, 512, 1024, 2048, 4096, 8192, 16384 }; + + crc8 = new Crc8(); + crc16 = new Crc16(); + + _framesBuffer = new byte[0x10000]; + decode_metadata(); + + //max_frame_size = 16 + ((Flake.MAX_BLOCKSIZE * (int)(bits_per_sample * channels + 1) + 7) >> 3); + if (max_frame_size * 2 > _framesBuffer.Length) + { + byte[] temp = _framesBuffer; + _framesBuffer = new byte[max_frame_size * 2]; + if (_framesBufferLength > 0) + Array.Copy(temp, _framesBufferOffset, _framesBuffer, 0, _framesBufferLength); + _framesBufferOffset = 0; + } + _samplesInBuffer = 0; + + if (bits_per_sample != 16 || channels != 2 || sample_rate != 44100) + throw new Exception("invalid flac file"); + + samplesBuffer = new int[Flake.MAX_BLOCKSIZE * channels]; + residualBuffer = new int[Flake.MAX_BLOCKSIZE * channels]; + } + + public FlakeReader(int _channels, uint _bits_per_sample) + { + flac_bitdepths = new int[8] { 0, 8, 12, 0, 16, 20, 24, 0 }; + flac_blocksizes = new int[15] { 0, 192, 576, 1152, 2304, 4608, 0, 0, 256, 512, 1024, 2048, 4096, 8192, 16384 }; + + crc8 = new Crc8(); + crc16 = new Crc16(); + + channels = _channels; + bits_per_sample = _bits_per_sample; + samplesBuffer = new int[Flake.MAX_BLOCKSIZE * channels]; + residualBuffer = new int[Flake.MAX_BLOCKSIZE * channels]; + } + + public void Close() + { + _IO.Close(); + } + + public int[,] Read(int[,] buff) + { + return AudioSamples.Read(this, buff); + } + + public ulong Length + { + get + { + return _sampleCount; + } + } + + public ulong Remaining + { + get + { + return _sampleCount - _sampleOffset + _samplesInBuffer; + } + } + + public ulong Position + { + get + { + return _sampleOffset - _samplesInBuffer; + } + set + { + throw new Exception("seeking not yet supported"); + } + } + + public int BitsPerSample + { + get + { + return (int)bits_per_sample; + } + } + + public int ChannelCount + { + get + { + return channels; + } + } + + public int SampleRate + { + get + { + return sample_rate; + } + } + + public string Path + { + get + { + return _path; + } + } + + void interlace(int [,] buff, int offset, int count) + { + for (int ch = 0; ch < channels ; ch++) + for (int i = 0; i < count; i++) + buff[offset + i, ch] = samplesBuffer[_samplesBufferOffset + i + ch * Flake.MAX_BLOCKSIZE]; + } + + public uint Read(int[,] buff, uint sampleCount) + { + uint offset = 0; + + while (_samplesInBuffer < sampleCount) + { + if (_samplesInBuffer > 0) + { + interlace(buff, (int)offset, (int)_samplesInBuffer); + sampleCount -= (uint)_samplesInBuffer; + offset += _samplesInBuffer; + _samplesInBuffer = 0; + _samplesBufferOffset = 0; + } + + fill_frames_buffer(); + + if (_framesBufferLength == 0) + return offset; + + int bytesDecoded = DecodeFrame(_framesBuffer, _framesBufferOffset, _framesBufferLength); + _framesBufferLength -= bytesDecoded; + _framesBufferOffset += bytesDecoded; + + _samplesInBuffer -= _samplesBufferOffset; // can be set by Seek, otherwise zero + _sampleOffset += _samplesInBuffer; + } + + interlace(buff, (int)offset, (int)sampleCount); + _samplesInBuffer -= sampleCount; + _samplesBufferOffset += sampleCount; + if (_samplesInBuffer == 0) + _samplesBufferOffset = 0; + return (uint)offset + sampleCount; + } + + void fill_frames_buffer() + { + if (_framesBufferLength == 0) + _framesBufferOffset = 0; + else if (_framesBufferLength < _framesBuffer.Length / 2 && _framesBufferOffset >= _framesBuffer.Length / 2) + { + Array.Copy(_framesBuffer, _framesBufferOffset, _framesBuffer, 0, _framesBufferLength); + _framesBufferOffset = 0; + } + while (_framesBufferLength < _framesBuffer.Length / 2) + { + int read = _IO.Read(_framesBuffer, _framesBufferOffset + _framesBufferLength, _framesBuffer.Length - _framesBufferOffset - _framesBufferLength); + _framesBufferLength += read; + if (read == 0) + break; + } + } + + unsafe void decode_frame_header(BitReader bitreader, FlacFrame* frame) + { + int header_start = bitreader.Position; + + if (bitreader.readbits(16) != 0xFFF8) + throw new Exception("invalid frame"); + + frame->bs_code0 = (int) bitreader.readbits(4); + uint sr_code0 = bitreader.readbits(4); + frame->ch_mode = (ChannelMode)bitreader.readbits(4); + uint bps_code = bitreader.readbits(3); + if (flac_bitdepths[bps_code] != bits_per_sample) + throw new Exception("unsupported bps coding"); + uint t1 = bitreader.readbit(); // == 0????? + if (t1 != 0) + throw new Exception("unsupported frame coding"); + frame->frame_count = bitreader.read_utf8(); + + // custom block size + if (frame->bs_code0 == 6) + { + frame->bs_code1 = (int)bitreader.readbits(8); + frame->blocksize = frame->bs_code1 + 1; + } + else if (frame->bs_code0 == 7) + { + frame->bs_code1 = (int)bitreader.readbits(16); + frame->blocksize = frame->bs_code1 + 1; + } + else + frame->blocksize = flac_blocksizes[frame->bs_code0]; + + // custom sample rate + if (sr_code0 < 4 || sr_code0 > 11) + { + // sr_code0 == 12 -> sr == bitreader.readbits(8) * 1000; + // sr_code0 == 13 -> sr == bitreader.readbits(16); + // sr_code0 == 14 -> sr == bitreader.readbits(16) * 10; + throw new Exception("invalid sample rate mode"); + } + + int frame_channels = (int)frame->ch_mode + 1; + if (frame_channels > 11) + throw new Exception("invalid channel mode"); + if (frame_channels == 2 || frame_channels > 8) // Mid/Left/Right Side Stereo + frame_channels = 2; + else + frame->ch_mode = ChannelMode.NotStereo; + if (frame_channels != channels) + throw new Exception("invalid channel mode"); + + // CRC-8 of frame header + byte crc = crc8.ComputeChecksum(bitreader.Buffer, header_start, bitreader.Position - header_start); + frame->crc8 = (byte)bitreader.readbits(8); + if (frame->crc8 != crc) + throw new Exception("header crc mismatch"); + } + + unsafe void decode_subframe_constant(BitReader bitreader, FlacFrame* frame, int ch) + { + int obits = (int)frame->subframes[ch].obits; + frame->subframes[ch].residual[0] = bitreader.readbits_signed(obits); + } + + unsafe void decode_subframe_verbatim(BitReader bitreader, FlacFrame* frame, int ch) + { + int obits = (int)frame->subframes[ch].obits; + for (int i = 0; i < frame->blocksize; i++) + frame->subframes[ch].residual[i] = bitreader.readbits_signed(obits); + } + + unsafe void decode_residual(BitReader bitreader, FlacFrame* frame, int ch) + { + // rice-encoded block + uint coding_method = bitreader.readbits(2); // ????? == 0 + if (coding_method != 0) // if 1, then parameter length == 5 bits instead of 4 + throw new Exception("unsupported residual coding"); + // partition order + frame->subframes[ch].rc.porder = (int)bitreader.readbits(4); + if (frame->subframes[ch].rc.porder > 8) + throw new Exception("invalid partition order"); + int psize = frame->blocksize >> frame->subframes[ch].rc.porder; + int res_cnt = psize - frame->subframes[ch].order; + + // residual + int j = frame->subframes[ch].order; + int* r = frame->subframes[ch].residual + j; + for (int p = 0; p < (1 << frame->subframes[ch].rc.porder); p++) + { + uint k = frame->subframes[ch].rc.rparams[p] = bitreader.readbits(4); + if (p == 1) res_cnt = psize; + int n = Math.Min(res_cnt, frame->blocksize - j); + if (k == 0) + for (int i = n; i > 0; i--) + *(r++) = bitreader.read_unary_signed(); + else if (k <= 8) + for (int i = n; i > 0; i--) + *(r++) = bitreader.read_rice_signed8((int)k); + else + for (int i = n; i > 0; i--) + *(r++) = bitreader.read_rice_signed((int)k); + j += n; + } + } + + unsafe void decode_subframe_fixed(BitReader bitreader, FlacFrame* frame, int ch) + { + // warm-up samples + int obits = (int)frame->subframes[ch].obits; + for (int i = 0; i < frame->subframes[ch].order; i++) + frame->subframes[ch].residual[i] = bitreader.readbits_signed(obits); + + // residual + decode_residual(bitreader, frame, ch); + } + + unsafe void decode_subframe_lpc(BitReader bitreader, FlacFrame* frame, int ch) + { + // warm-up samples + int obits = (int)frame->subframes[ch].obits; + for (int i = 0; i < frame->subframes[ch].order; i++) + frame->subframes[ch].residual[i] = bitreader.readbits_signed(obits); + + // LPC coefficients + frame->subframes[ch].cbits = (int)bitreader.readbits(4) + 1; // lpc_precision + frame->subframes[ch].shift = bitreader.readbits_signed(5); + for (int i = 0; i < frame->subframes[ch].order; i++) + frame->subframes[ch].coefs[i] = bitreader.readbits_signed(frame->subframes[ch].cbits); + + // residual + decode_residual(bitreader, frame, ch); + } + + unsafe void decode_subframes(BitReader bitreader, FlacFrame* frame) + { + fixed (int *r = residualBuffer, s = samplesBuffer) + for (int ch = 0; ch < channels; ch++) + { + // subframe header + uint t1 = bitreader.readbit(); // ?????? == 0 + if (t1 != 0) + throw new Exception("unsupported subframe coding"); + int type_code = (int)bitreader.readbits(6); + frame->subframes[ch].wbits = bitreader.readbit(); + if (frame->subframes[ch].wbits != 0) + frame->subframes[ch].wbits += bitreader.read_unary(); + + frame->subframes[ch].obits = bits_per_sample - frame->subframes[ch].wbits; + switch (frame->ch_mode) + { + case ChannelMode.MidSide: frame->subframes[ch].obits += (uint)ch; break; + case ChannelMode.LeftSide: frame->subframes[ch].obits += (uint)ch; break; + case ChannelMode.RightSide: frame->subframes[ch].obits += 1 - (uint)ch; break; + } + + frame->subframes[ch].type = (SubframeType)type_code; + frame->subframes[ch].order = 0; + + if ((type_code & (uint)SubframeType.LPC) != 0) + { + frame->subframes[ch].order = (type_code - (int)SubframeType.LPC) + 1; + frame->subframes[ch].type = SubframeType.LPC; + } + else if ((type_code & (uint)SubframeType.Fixed) != 0) + { + frame->subframes[ch].order = (type_code - (int)SubframeType.Fixed); + frame->subframes[ch].type = SubframeType.Fixed; + } + + frame->subframes[ch].residual = r + ch * Flake.MAX_BLOCKSIZE; + frame->subframes[ch].samples = s + ch * Flake.MAX_BLOCKSIZE; + + // subframe + switch (frame->subframes[ch].type) + { + case SubframeType.Constant: + decode_subframe_constant(bitreader, frame, ch); + break; + case SubframeType.Verbatim: + decode_subframe_verbatim(bitreader, frame, ch); + break; + case SubframeType.Fixed: + decode_subframe_fixed(bitreader, frame, ch); + break; + case SubframeType.LPC: + decode_subframe_lpc(bitreader, frame, ch); + break; + default: + throw new Exception("invalid subframe type"); + } + } + } + + unsafe void restore_samples_fixed(FlacFrame* frame, int ch) + { + FlacSubframe* sub = frame->subframes + ch; + + Flake.memcpy(sub->samples, sub->residual, sub->order); + int* data = sub->samples + sub->order; + int* residual = sub->residual + sub->order; + int data_len = frame->blocksize - sub->order; + switch (sub->order) + { + case 0: + Flake.memcpy(data, residual, data_len); + break; + case 1: + for (int i = 0; i < data_len; i++) + data[i] = residual[i] + data[i - 1]; + break; + case 2: + for (int i = 0; i < data_len; i++) + data[i] = residual[i] + (data[i - 1] << 1) - data[i - 2]; + break; + case 3: + for (int i = 0; i < data_len; i++) + data[i] = residual[i] + (((data[i - 1] - data[i - 2]) << 1) + (data[i - 1] - data[i - 2])) + data[i - 3]; + break; + case 4: + for (int i = 0; i < data_len; i++) + data[i] = residual[i] + ((data[i - 1] + data[i - 3]) << 2) - ((data[i - 2] << 2) + (data[i - 2] << 1)) - data[i - 4]; + break; + } + } + + unsafe void restore_samples_lpc(FlacFrame* frame, int ch) + { + FlacSubframe* sub = frame->subframes + ch; + + if ((ulong)sub->order * ((1UL << (int)sub->obits) - 1) * ((1U << sub->cbits) - 1) >= (1UL << 32)) + lpc.decode_residual_long(sub->residual, sub->samples, frame->blocksize, sub->order, sub->coefs, sub->shift); + else + lpc.decode_residual(sub->residual, sub->samples, frame->blocksize, sub->order, sub->coefs, sub->shift); + } + + unsafe void restore_samples(FlacFrame* frame) + { + for (int ch = 0; ch < channels; ch++) + { + switch (frame->subframes[ch].type) + { + case SubframeType.Constant: + Flake.memset(frame->subframes[ch].samples, frame->subframes[ch].residual[0], frame->blocksize); + break; + case SubframeType.Verbatim: + Flake.memcpy(frame->subframes[ch].samples, frame->subframes[ch].residual, frame->blocksize); + break; + case SubframeType.Fixed: + restore_samples_fixed(frame, ch); + break; + case SubframeType.LPC: + restore_samples_lpc(frame, ch); + break; + } + if (frame->subframes[ch].wbits != 0) + { + int x = (int) frame->subframes[ch].wbits; + for (int i = 0; i < frame->blocksize; i++) + frame->subframes[ch].samples[i] <<= x; + } + } + switch (frame->ch_mode) + { + case ChannelMode.NotStereo: + break; + case ChannelMode.LeftRight: + break; + case ChannelMode.MidSide: + for (int i = 0; i < frame->blocksize; i++) + { + int mid = frame->subframes[0].samples[i]; + int side = frame->subframes[1].samples[i]; + mid <<= 1; + mid |= (side & 1); /* i.e. if 'side' is odd... */ + frame->subframes[0].samples[i] = (mid + side) >> 1; + frame->subframes[1].samples[i] = (mid - side) >> 1; + } + break; + case ChannelMode.LeftSide: + for (int i = 0; i < frame->blocksize; i++) + frame->subframes[1].samples[i] = frame->subframes[0].samples[i] - frame->subframes[1].samples[i]; + break; + case ChannelMode.RightSide: + for (int i = 0; i < frame->blocksize; i++) + frame->subframes[0].samples[i] += frame->subframes[1].samples[i]; + break; + } + } + + public unsafe int DecodeFrame(byte[] buffer, int pos, int len) + { + BitReader bitreader = new BitReader(buffer, pos, len); + FlacFrame frame; + FlacSubframe* subframes = stackalloc FlacSubframe[channels]; + frame.subframes = subframes; + decode_frame_header(bitreader, &frame); + decode_subframes(bitreader, &frame); + bitreader.flush(); + ushort crc = crc16.ComputeChecksum(bitreader.Buffer, pos, bitreader.Position - pos); + if (crc != bitreader.readbits(16)) + throw new Exception("frame crc mismatch"); + restore_samples(&frame); + restore_samples(&frame); + _samplesInBuffer = (uint)frame.blocksize; + return bitreader.Position - pos; + } + + + bool skip_bytes(int bytes) + { + for (int j = 0; j < bytes; j++) + if (0 == _IO.Read(_framesBuffer, 0, 1)) + return false; + return true; + } + + void decode_metadata() + { + byte x; + int i, id; + bool first = true; + byte[] FLAC__STREAM_SYNC_STRING = new byte[] { (byte)'f', (byte)'L', (byte)'a', (byte)'C' }; + byte[] ID3V2_TAG_ = new byte[] { (byte)'I', (byte)'D', (byte)'3' }; + + for (i = id = 0; i < 4; ) + { + if (_IO.Read(_framesBuffer, 0, 1) == 0) + throw new Exception("FLAC stream not found"); + x = _framesBuffer[0]; + if (x == FLAC__STREAM_SYNC_STRING[i]) + { + first = true; + i++; + id = 0; + continue; + } + if (id < 3 && x == ID3V2_TAG_[id]) + { + id++; + i = 0; + if (id == 3) + { + if (!skip_bytes(3)) + throw new Exception("FLAC stream not found"); + int skip = 0; + for (int j = 0; j < 4; j++) + { + if (0 == _IO.Read(_framesBuffer, 0, 1)) + throw new Exception("FLAC stream not found"); + skip <<= 7; + skip |= ((int)_framesBuffer[0] & 0x7f); + } + if (!skip_bytes(skip)) + throw new Exception("FLAC stream not found"); + } + continue; + } + id = 0; + if (x == 0xff) /* MAGIC NUMBER for the first 8 frame sync bits */ + { + do + { + if (_IO.Read(_framesBuffer, 0, 1) == 0) + throw new Exception("FLAC stream not found"); + x = _framesBuffer[0]; + } while (x == 0xff); + if (x >> 2 == 0x3e) /* MAGIC NUMBER for the last 6 sync bits */ + { + //_IO.Position -= 2; + // state = frame + throw new Exception("headerless file unsupported"); + } + } + throw new Exception("FLAC stream not found"); + } + + do + { + long pos = _IO.Position; + fill_frames_buffer(); + BitReader bitreader = new BitReader(_framesBuffer, _framesBufferOffset, _framesBufferLength - _framesBufferOffset); + bool is_last = bitreader.readbit() != 0; + MetadataType type = (MetadataType)bitreader.readbits(7); + int len = (int)bitreader.readbits(24); + + if (type == MetadataType.FLAC__METADATA_TYPE_STREAMINFO) + { + const int FLAC__STREAM_METADATA_STREAMINFO_MIN_BLOCK_SIZE_LEN = 16; /* bits */ + const int FLAC__STREAM_METADATA_STREAMINFO_MAX_BLOCK_SIZE_LEN = 16; /* bits */ + const int FLAC__STREAM_METADATA_STREAMINFO_MIN_FRAME_SIZE_LEN = 24; /* bits */ + const int FLAC__STREAM_METADATA_STREAMINFO_MAX_FRAME_SIZE_LEN = 24; /* bits */ + const int FLAC__STREAM_METADATA_STREAMINFO_SAMPLE_RATE_LEN = 20; /* bits */ + const int FLAC__STREAM_METADATA_STREAMINFO_CHANNELS_LEN = 3; /* bits */ + const int FLAC__STREAM_METADATA_STREAMINFO_BITS_PER_SAMPLE_LEN = 5; /* bits */ + const int FLAC__STREAM_METADATA_STREAMINFO_TOTAL_SAMPLES_LEN = 36; /* bits */ + const int FLAC__STREAM_METADATA_STREAMINFO_MD5SUM_LEN = 128; /* bits */ + + min_block_size = bitreader.readbits(FLAC__STREAM_METADATA_STREAMINFO_MIN_BLOCK_SIZE_LEN); + max_block_size = bitreader.readbits(FLAC__STREAM_METADATA_STREAMINFO_MAX_BLOCK_SIZE_LEN); + min_frame_size = bitreader.readbits(FLAC__STREAM_METADATA_STREAMINFO_MIN_FRAME_SIZE_LEN); + max_frame_size = bitreader.readbits(FLAC__STREAM_METADATA_STREAMINFO_MAX_FRAME_SIZE_LEN); + sample_rate = (int) bitreader.readbits(FLAC__STREAM_METADATA_STREAMINFO_SAMPLE_RATE_LEN); + channels = 1 + (int) bitreader.readbits(FLAC__STREAM_METADATA_STREAMINFO_CHANNELS_LEN); + bits_per_sample = 1 + bitreader.readbits(FLAC__STREAM_METADATA_STREAMINFO_BITS_PER_SAMPLE_LEN); + _sampleCount = (((ulong)bitreader.readbits(4)) << 4) + (ulong)bitreader.readbits(32); + bitreader.skipbits(FLAC__STREAM_METADATA_STREAMINFO_MD5SUM_LEN); + } + if (_framesBufferLength < 4 + len) + { + _IO.Position = pos + 4 + len; + _framesBufferLength = 0; + } + else + { + _framesBufferLength -= 4 + len; + _framesBufferOffset += 4 + len; + } + if (is_last) + break; + } while (true); + } + } +} diff --git a/CUETools.Codecs.FLAKE/FlakeWriter.cs b/CUETools.Codecs.FLAKE/FlakeWriter.cs new file mode 100644 index 0000000..0974c1e --- /dev/null +++ b/CUETools.Codecs.FLAKE/FlakeWriter.cs @@ -0,0 +1,1656 @@ +using System; +using System.Text; +using System.IO; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Security.Cryptography; +using System.Runtime.InteropServices; +using CUETools.Codecs; + +namespace CUETools.Codecs.FLAKE +{ + public class FlakeWriter : IAudioDest + { + Stream _IO = null; + string _path; + long _position; + + // number of audio channels + // set by user prior to calling flake_encode_init + // valid values are 1 to 8 + int channels, ch_code; + + // audio sample rate in Hz + // set by user prior to calling flake_encode_init + int sample_rate, sr_code0, sr_code1; + + // sample size in bits + // set by user prior to calling flake_encode_init + // only 16-bit is currently supported + uint bits_per_sample; + int bps_code; + + // total stream samples + // set by user prior to calling flake_encode_init + // if 0, stream length is unknown + int sample_count; + + FlakeEncodeParams eparams; + + // maximum frame size in bytes + // set by flake_encode_init + // this can be used to allocate memory for output + int max_frame_size; + + byte[] frame_buffer = null; + + uint lpc_precision; + + int frame_count = 0; + + TimeSpan _userProcessorTime; + + // header bytes + // allocated by flake_encode_init and freed by flake_encode_close + byte[] header; + + int[] samplesBuffer; + int[] verifyBuffer; + int[] residualBuffer; + double[] windowBuffer; + int samplesInBuffer = 0; + + int _compressionLevel = 5; + int _blocksize = 0; + int _totalSize = 0; + int _windowsize = 0, _windowcount = 0; + + Crc8 crc8; + Crc16 crc16; + MD5 md5; + + FlakeReader verify; + + bool inited = false; + + public FlakeWriter(string path, int bitsPerSample, int channelCount, int sampleRate, Stream IO) + { + if (bitsPerSample != 16) + throw new Exception("Bits per sample must be 16."); + if (channelCount != 2) + throw new Exception("ChannelCount must be 2."); + + flac_samplerates = new int[16] { + 0, 0, 0, 0, + 8000, 16000, 22050, 24000, 32000, 44100, 48000, 96000, + 0, 0, 0, 0 + }; + flac_bitdepths = new int[8] { 0, 8, 12, 0, 16, 20, 24, 0 }; + flac_blocksizes = new int[15] { 0, 192, 576, 1152, 2304, 4608, 0, 0, 256, 512, 1024, 2048, 4096, 8192, 16384 }; + + channels = channelCount; + sample_rate = sampleRate; + bits_per_sample = (uint) bitsPerSample; + + // flake_validate_params + + _path = path; + _IO = IO; + + //verify = new FlakeReader(channels, bits_per_sample); + + samplesBuffer = new int[Flake.MAX_BLOCKSIZE * (channels == 2 ? 4 : channels)]; + residualBuffer = new int[Flake.MAX_BLOCKSIZE * (channels == 2 ? 4 : channels)]; + windowBuffer = new double[Flake.MAX_BLOCKSIZE * lpc.MAX_LPC_WINDOWS]; + if (verify != null) + verifyBuffer = new int[Flake.MAX_BLOCKSIZE * channels]; + + eparams.flake_set_defaults(_compressionLevel); + eparams.padding_size = 8192; + + crc8 = new Crc8(); + crc16 = new Crc16(); + md5 = new MD5CryptoServiceProvider(); + } + + public int TotalSize + { + get + { + return _totalSize; + } + } + + public int PaddingLength + { + get + { + return eparams.padding_size; + } + set + { + eparams.padding_size = value; + } + } + + public int CompressionLevel + { + get + { + return _compressionLevel; + } + set + { + _compressionLevel = value; + eparams.flake_set_defaults(_compressionLevel); + } + } + + [DllImport("kernel32.dll")] + static extern bool GetThreadTimes(IntPtr hThread, out long lpCreationTime, out long lpExitTime, out long lpKernelTime, out long lpUserTime); + [DllImport("kernel32.dll")] + static extern IntPtr GetCurrentThread(); + + void DoClose() + { + if (samplesInBuffer > 0) + { + eparams.block_size = samplesInBuffer; + output_frame(); + } + + long fake, KernelStart, UserStart; + GetThreadTimes(GetCurrentThread(), out fake, out fake, out KernelStart, out UserStart); + _userProcessorTime = new TimeSpan(UserStart); + + md5.TransformFinalBlock(frame_buffer, 0, 0); + if (_IO.CanSeek) + { + _IO.Position = 26; + _IO.Write(md5.Hash, 0, md5.Hash.Length); + } + _IO.Close(); + } + + public void Close() + { + DoClose(); + if (sample_count != 0 && _position != sample_count) + throw new Exception("Samples written differs from the expected sample count."); + } + + public void Delete() + { + DoClose(); + File.Delete(_path); + } + + public long Position + { + get + { + return _position; + } + } + + public long FinalSampleCount + { + set { sample_count = (int)value; } + } + + public long BlockSize + { + set { _blocksize = (int)value; } + } + + public OrderMethod OrderMethod + { + get { return eparams.order_method; } + set { eparams.order_method = value; } + } + + public PredictionType PredictionType + { + get { return eparams.prediction_type; } + set { eparams.prediction_type = value; } + } + + public StereoMethod StereoMethod + { + get { return eparams.stereo_method; } + set { eparams.stereo_method = value; } + } + + public WindowFunction WindowFunction + { + get { return eparams.window_function; } + set { eparams.window_function = value; } + } + + public int MinPredictionOrder + { + get + { + return eparams.prediction_type == PredictionType.Fixed ? + eparams.min_fixed_order : eparams.min_prediction_order; + } + set + { + if (eparams.prediction_type == PredictionType.Fixed) + { + if (value < 0 || value > eparams.max_fixed_order) + throw new Exception("invalid order"); + eparams.min_fixed_order = value; + return; + } + if (value < 0 || value > eparams.max_prediction_order) + throw new Exception("invalid order"); + eparams.min_prediction_order = value; + } + } + + public int MaxPredictionOrder + { + get + { + return eparams.prediction_type == PredictionType.Fixed ? + eparams.max_fixed_order : eparams.max_prediction_order; + } + set + { + if (eparams.prediction_type == PredictionType.Fixed) + { + if (value > 4 || value < eparams.min_fixed_order) + throw new Exception("invalid order"); + eparams.max_fixed_order = value; + return; + } + if (value > 32 || value < eparams.min_prediction_order) + throw new Exception("invalid order"); + eparams.max_prediction_order = value; + } + } + + public int MaxPartitionOrder + { + get { return eparams.max_partition_order; } + set + { + if (value > 8 || value < eparams.min_partition_order) + throw new Exception("invalid order"); + eparams.max_partition_order = value; + } + } + + public TimeSpan UserProcessorTime + { + get { return _userProcessorTime; } + } + + public int BitsPerSample + { + get { return 16; } + } + + int encode_frame_vbs() + { + throw new Exception("vbs not supported"); + } + + unsafe int get_wasted_bits(int* signal, int samples) + { + int i, shift; + int x = 0; + + for (i = 0; i < samples && 0 == (x & 1); i++) + x |= signal[i]; + + if (x == 0) + { + shift = 0; + } + else + { + for (shift = 0; 0 == (x & 1); shift++) + x >>= 1; + } + + if (shift > 0) + { + for (i = 0; i < samples; i++) + signal[i] >>= shift; + } + + return shift; + } + + unsafe void init_frame(FlacFrame * frame) + { + //if (channels == 2) + //max_frame_size = + + int i = 15; + if (eparams.variable_block_size == 0) + { + for (i = 0; i < 15; i++) + { + if (eparams.block_size == flac_blocksizes[i]) + { + frame->blocksize = flac_blocksizes[i]; + frame->bs_code0 = i; + frame->bs_code1 = -1; + break; + } + } + } + if (i == 15) + { + frame->blocksize = eparams.block_size; + if (frame->blocksize <= 256) + { + frame->bs_code0 = 6; + frame->bs_code1 = frame->blocksize - 1; + } + else + { + frame->bs_code0 = 7; + frame->bs_code1 = frame->blocksize - 1; + } + } + } + + /** + * Copy channel-interleaved input samples into separate subframes + */ + unsafe void copy_samples(int[,] samples, int pos, int block) + { + fixed (int* fsamples = samplesBuffer) + for (int ch = 0; ch < channels; ch++) + { + int* psamples = fsamples + ch * Flake.MAX_BLOCKSIZE + samplesInBuffer; + for (int i = 0; i < block; i++) + psamples[i] = samples[pos + i, ch]; + } + samplesInBuffer += block; + } + + static uint rice_encode_count(uint sum, uint n, uint k) + { + return n*(k+1) + ((sum-(n>>1))>>(int)k); + } + + //static unsafe uint find_optimal_rice_param(uint sum, uint n) + //{ + // uint* nbits = stackalloc uint[Flake.MAX_RICE_PARAM + 1]; + // int k_opt = 0; + + // nbits[0] = UINT32_MAX; + // for (int k = 0; k <= Flake.MAX_RICE_PARAM; k++) + // { + // nbits[k] = rice_encode_count(sum, n, (uint)k); + // if (nbits[k] < nbits[k_opt]) + // k_opt = k; + // } + // return (uint)k_opt; + //} + + static unsafe uint find_optimal_rice_param(uint sum, uint n, out uint nbits_best) + { + int k_opt = 0; + uint a = n; + uint b = sum - (n >> 1); + uint nbits = a + b; + for (int k = 1; k <= Flake.MAX_RICE_PARAM; k++) + { + a += n; + b >>= 1; + uint nbits_k = a + b; + if (nbits_k < nbits) + { + k_opt = k; + nbits = nbits_k; + } + } + nbits_best = nbits; + return (uint)k_opt; + } + + unsafe uint calc_decorr_score(FlacFrame* frame, int ch, FlacSubframe* sub) + { + int* s = sub->samples; + int n = frame->blocksize; + ulong sum = 0; + for (int i = 2; i < n; i++) + sum += (ulong)Math.Abs(s[i] - 2 * s[i - 1] + s[i - 2]); + uint nbits; + find_optimal_rice_param((uint)(2 * sum), (uint)n, out nbits); + return nbits; + } + + unsafe void initialize_subframe(FlacFrame* frame, int ch, int *s, int * r, uint bps) + { + int w = get_wasted_bits(s, frame->blocksize); + if (w > bps) + throw new Exception("internal error"); + frame->subframes[ch].samples = s; + frame->subframes[ch].residual = r; + frame->subframes[ch].obits = bps - (uint)w; + frame->subframes[ch].wbits = (uint)w; + frame->subframes[ch].type = SubframeType.Verbatim; + frame->subframes[ch].size = UINT32_MAX; + for (int iWindow = 0; iWindow < lpc.MAX_LPC_WINDOWS; iWindow++) + frame->subframes[ch].done_lpcs[iWindow] = 0; + frame->subframes[ch].done_fixed = 0; + } + + unsafe static void channel_decorrelation(int* leftS, int* rightS, int *leftM, int *rightM, int blocksize) + { + for (int i = 0; i < blocksize; i++) + { + leftM[i] = (leftS[i] + rightS[i]) >> 1; + rightM[i] = leftS[i] - rightS[i]; + } + } + + unsafe void encode_residual_verbatim(int* res, int* smp, uint n) + { + Flake.memcpy(res, smp, (int) n); + } + + unsafe void encode_residual_fixed(int* res, int* smp, int n, int order) + { + int i; + int s0, s1, s2; + switch (order) + { + case 0: + Flake.memcpy(res, smp, n); + return; + case 1: + *(res++) = s1 = *(smp++); + for (i = n - 1; i > 0; i--) + { + s0 = *(smp++); + *(res++) = s0 - s1; + s1 = s0; + } + return; + case 2: + *(res++) = s2 = *(smp++); + *(res++) = s1 = *(smp++); + for (i = n - 2; i > 0; i--) + { + s0 = *(smp++); + *(res++) = s0 - 2 * s1 + s2; + s2 = s1; + s1 = s0; + } + return; + case 3: + res[0] = smp[0]; + res[1] = smp[1]; + res[2] = smp[2]; + for (i = 3; i < n; i++) + { + res[i] = smp[i] - 3 * smp[i - 1] + 3 * smp[i - 2] - smp[i - 3]; + } + return; + case 4: + res[0] = smp[0]; + res[1] = smp[1]; + res[2] = smp[2]; + res[3] = smp[3]; + for (i = 4; i < n; i++) + { + res[i] = smp[i] - 4 * smp[i - 1] + 6 * smp[i - 2] - 4 * smp[i - 3] + smp[i - 4]; + } + return; + default: + return; + } + } + + public const uint UINT32_MAX = 0xffffffff; + + static unsafe uint calc_optimal_rice_params(RiceContext* rc, int porder, uint* sums, uint n, uint pred_order) + { + uint part = (1U << porder); + uint all_bits = 0; + rc->rparams[0] = find_optimal_rice_param(sums[0], (n >> porder) - pred_order, out all_bits); + uint cnt = (n >> porder); + for (uint i = 1; i < part; i++) + { + uint nbits; + rc->rparams[i] = find_optimal_rice_param(sums[i], cnt, out nbits); + all_bits += nbits; + } + all_bits += (4 * part); + rc->porder = porder; + return all_bits; + } + + static unsafe void calc_sums(int pmin, int pmax, uint* data, uint n, uint pred_order, uint* sums) + { + uint* res = &data[pred_order]; + + // sums for highest level + int parts = (1 << pmax); + uint cnt = (n >> pmax) - pred_order; + for (int i = 0; i < parts; i++) + { + if (i == 1) cnt = (n >> pmax); + if (i > 0) res = &data[i * cnt]; + sums[pmax * Flake.MAX_PARTITIONS + i] = 0; + for (int j = 0; j < cnt; j++) + { + sums[pmax * Flake.MAX_PARTITIONS + i] += res[j]; + } + } + // sums for lower levels + for (int i = pmax - 1; i >= pmin; i--) + { + parts = (1 << i); + for (int j = 0; j < parts; j++) + { + sums[i * Flake.MAX_PARTITIONS + j] = + sums[(i + 1) * Flake.MAX_PARTITIONS + 2 * j] + + sums[(i + 1) * Flake.MAX_PARTITIONS + 2 * j + 1]; + } + } + } + + static unsafe uint calc_rice_params(RiceContext* rc, int pmin, int pmax, int* data, uint n, uint pred_order) + { + RiceContext tmp_rc; + uint* udata = stackalloc uint[(int)n]; + uint* sums = stackalloc uint[(pmax + 1) * Flake.MAX_PARTITIONS]; + //uint* bits = stackalloc uint[Flake.MAX_PARTITION_ORDER]; + + //assert(pmin >= 0 && pmin <= Flake.MAX_PARTITION_ORDER); + //assert(pmax >= 0 && pmax <= Flake.MAX_PARTITION_ORDER); + //assert(pmin <= pmax); + + for (uint i = 0; i < n; i++) + udata[i] = (uint) ((2 * data[i]) ^ (data[i] >> 31)); + + calc_sums(pmin, pmax, udata, n, pred_order, sums); + + int opt_porder = pmin; + uint opt_bits = UINT32_MAX; + for (int i = pmin; i <= pmax; i++) + { + uint bits = calc_optimal_rice_params(&tmp_rc, i, sums + i * Flake.MAX_PARTITIONS, n, pred_order); + if (bits <= opt_bits) + { + opt_porder = i; + opt_bits = bits; + *rc = tmp_rc; + } + } + + return opt_bits; + } + + static int get_max_p_order(int max_porder, int n, int order) + { + int porder = Math.Min(max_porder, Flake.log2i(n ^ (n - 1))); + if (order > 0) + porder = Math.Min(porder, Flake.log2i(n / order)); + return porder; + } + + static unsafe uint calc_rice_params_fixed(RiceContext* rc, int pmin, int pmax, + int* data, int n, int pred_order, uint bps) + { + pmin = get_max_p_order(pmin, n, pred_order); + pmax = get_max_p_order(pmax, n, pred_order); + uint bits = (uint)pred_order * bps + 6; + bits += calc_rice_params(rc, pmin, pmax, data, (uint)n, (uint)pred_order); + return bits; + } + + static unsafe uint calc_rice_params_lpc(RiceContext* rc, int pmin, int pmax, + int* data, int n, int pred_order, uint bps, uint precision) + { + pmin = get_max_p_order(pmin, n, pred_order); + pmax = get_max_p_order(pmax, n, pred_order); + uint bits = (uint)pred_order * bps + 4 + 5 + (uint)pred_order * precision + 6; + bits += calc_rice_params(rc, pmin, pmax, data, (uint)n, (uint)pred_order); + return bits; + } + + unsafe void choose_best_subframe(FlacFrame* frame, int ch) + { + if (frame->current.size < frame->subframes[ch].size) + { + FlacSubframe tmp = frame->subframes[ch]; + frame->subframes[ch] = frame->current; + for (int iWindow = 0; iWindow < lpc.MAX_LPC_WINDOWS; iWindow++) + frame->subframes[ch].done_lpcs[iWindow] = tmp.done_lpcs[iWindow]; + frame->subframes[ch].done_fixed = tmp.done_fixed; + frame->current = tmp; + } + } + + unsafe void encode_residual_lpc_sub(FlacFrame* frame, double* lpcs, int iWindow, int order, int ch) + { + if ((frame->subframes[ch].done_lpcs[iWindow] & (1U << (order - 1))) != 0) + return; // already calculated; + + frame->subframes[ch].done_lpcs[iWindow] |= (1U << (order - 1)); + + frame->current.type = SubframeType.LPC; + frame->current.order = order; + frame->current.window = iWindow; + + lpc.quantize_lpc_coefs(lpcs + (frame->current.order - 1) * lpc.MAX_LPC_ORDER, + frame->current.order, lpc_precision, frame->current.coefs, out frame->current.shift); + + lpc.encode_residual(frame->current.residual, frame->current.samples, frame->blocksize, frame->current.order, frame->current.coefs, frame->current.shift); + + frame->current.size = calc_rice_params_lpc(&frame->current.rc, eparams.min_partition_order, eparams.max_partition_order, + frame->current.residual, frame->blocksize, frame->current.order, frame->current.obits, lpc_precision); + + choose_best_subframe(frame, ch); + } + + unsafe void encode_residual_fixed_sub(FlacFrame* frame, int order, int ch) + { + if ((frame->subframes[ch].done_fixed & (1U << order)) != 0) + return; // already calculated; + + frame->current.order = order; + frame->current.type = SubframeType.Fixed; + + encode_residual_fixed(frame->current.residual, frame->current.samples, frame->blocksize, frame->current.order); + + frame->current.size = calc_rice_params_fixed(&frame->current.rc, eparams.min_partition_order, eparams.max_partition_order, + frame->current.residual, frame->blocksize, frame->current.order, frame->current.obits); + + frame->subframes[ch].done_fixed |= (1U << order); + + choose_best_subframe(frame, ch); + } + + unsafe void encode_residual(FlacFrame* frame, int ch, PredictionType predict, OrderMethod omethod) + { + int i; + FlacSubframe* sub = frame->subframes + ch; + int* smp = sub->samples; + int n = frame->blocksize; + + frame->current.samples = sub->samples; + frame->current.obits = sub->obits; + frame->current.wbits = sub->wbits; + + // CONSTANT + for (i = 1; i < n; i++) + { + if (smp[i] != smp[0]) break; + } + if (i == n) + { + sub->type = SubframeType.Constant; + sub->residual[0] = smp[0]; + sub->size = sub->obits; + return; + } + + // VERBATIM + frame->current.type = SubframeType.Verbatim; + frame->current.size = frame->current.obits * (uint)frame->blocksize; + choose_best_subframe(frame, ch); + + if (n < 5 || predict == PredictionType.None) + return; + + // FIXED + if (predict == PredictionType.Fixed || + predict == PredictionType.Search || + (predict == PredictionType.Estimated && sub->type == SubframeType.Fixed) || + n <= eparams.max_prediction_order) + { + int max_fixed_order = Math.Min(eparams.max_fixed_order, 4); + int min_fixed_order = Math.Min(eparams.min_fixed_order, max_fixed_order); + + for (i = min_fixed_order; i <= max_fixed_order; i++) + encode_residual_fixed_sub(frame, i, ch); + } + + // LPC + if (n > eparams.max_prediction_order && + (predict == PredictionType.Levinson || + predict == PredictionType.Search || + (predict == PredictionType.Estimated && sub->type == SubframeType.LPC))) + { + double* lpcs = stackalloc double[lpc.MAX_LPC_ORDER * lpc.MAX_LPC_ORDER]; + int min_order = eparams.min_prediction_order; + int max_order = eparams.max_prediction_order; + + fixed (double* window = windowBuffer) + for (int iWindow = 0; iWindow < _windowcount; iWindow++) + { + if (predict == PredictionType.Estimated && sub->window != iWindow) + continue; + + int est_order = (int)lpc.calc_coefs(smp, (uint)n, (uint)max_order, omethod, lpcs, + window + iWindow * _windowsize); + + switch (omethod) + { + case OrderMethod.Max: + // always use maximum order + encode_residual_lpc_sub(frame, lpcs, iWindow, max_order, ch); + break; + case OrderMethod.Estimate: + // estimated order + encode_residual_lpc_sub(frame, lpcs, iWindow, est_order, ch); + break; + case OrderMethod.Search: + // brute-force optimal order search + for (i = max_order; i > 0; i--) + encode_residual_lpc_sub(frame, lpcs, iWindow, i, ch); + break; + case OrderMethod.EstSearch: + // brute-force search starting from estimate + for (i = est_order; i > 0; i--) + encode_residual_lpc_sub(frame, lpcs, iWindow, i, ch); + break; + case OrderMethod.LogFast: + // Try max, est, 32,16,8,4,2,1 + encode_residual_lpc_sub(frame, lpcs, iWindow, max_order, ch); + //encode_residual_lpc_sub(frame, lpcs, est_order, ch); + for (i = lpc.MAX_LPC_ORDER; i > 0; i >>= 1) + if (i < max_order) + encode_residual_lpc_sub(frame, lpcs, iWindow, i, ch); + break; + case OrderMethod.LogSearch: + // do LogFast first + encode_residual_lpc_sub(frame, lpcs, iWindow, max_order, ch); + //encode_residual_lpc_sub(frame, lpcs, est_order, ch); + for (i = lpc.MAX_LPC_ORDER; i > 0; i >>= 1) + if (i < max_order) + encode_residual_lpc_sub(frame, lpcs, iWindow, i, ch); + // if found a good order, try to search around it + if (frame->subframes[ch].type == SubframeType.LPC) + { + // log search (written by Michael Niedermayer for FFmpeg) + for (int step = lpc.MAX_LPC_ORDER; step > 0; step >>= 1) + { + int last = frame->subframes[ch].order; + if (step <= (last + 1) / 2) + for (i = last - step; i <= last + step; i += step) + if (i >= min_order && i <= max_order) + encode_residual_lpc_sub(frame, lpcs, iWindow, i, ch); + } + } + break; + default: + throw new Exception("unknown ordermethod"); + } + } + } + } + + unsafe void output_frame_header(FlacFrame* frame, BitWriter bitwriter) + { + bitwriter.writebits(16, 0xFFF8); + bitwriter.writebits(4, frame->bs_code0); + bitwriter.writebits(4, sr_code0); + if (frame->ch_mode == ChannelMode.NotStereo) + bitwriter.writebits(4, ch_code); + else + bitwriter.writebits(4, (int) frame->ch_mode); + bitwriter.writebits(3, bps_code); + bitwriter.writebits(1, 0); + bitwriter.write_utf8(frame_count); + + // custom block size + if (frame->bs_code1 >= 0) + { + if (frame->bs_code1 < 256) + bitwriter.writebits(8, frame->bs_code1); + else + bitwriter.writebits(16, frame->bs_code1); + } + + // custom sample rate + if (sr_code1 > 0) + { + if (sr_code1 < 256) + bitwriter.writebits(8, sr_code1); + else + bitwriter.writebits(16, sr_code1); + } + + // CRC-8 of frame header + bitwriter.flush(); + byte crc = crc8.ComputeChecksum(frame_buffer, 0, bitwriter.Length); + bitwriter.writebits(8, crc); + } + + unsafe void output_residual(FlacFrame* frame, BitWriter bitwriter, FlacSubframe* sub) + { + // rice-encoded block + bitwriter.writebits(2, 0); + + // partition order + int porder = sub->rc.porder; + int psize = frame->blocksize >> porder; + //assert(porder >= 0); + bitwriter.writebits(4, porder); + int res_cnt = psize - sub->order; + + // residual + int j = sub->order; + for (int p = 0; p < (1 << porder); p++) + { + int k = (int) sub->rc.rparams[p]; + bitwriter.writebits(4, k); + if (p == 1) res_cnt = psize; + for (int i = 0; i < res_cnt && j < frame->blocksize; i++, j++) + { + bitwriter.write_rice_signed(k, sub->residual[j]); + } + } + } + + unsafe void + output_subframe_constant(FlacFrame* frame, BitWriter bitwriter, FlacSubframe* sub) + { + bitwriter.writebits_signed(sub->obits, sub->residual[0]); + } + + unsafe void + output_subframe_verbatim(FlacFrame* frame, BitWriter bitwriter, FlacSubframe* sub) + { + int n = frame->blocksize; + for (int i = 0; i < n; i++) + bitwriter.writebits_signed(sub->obits, sub->samples[i]); + // Don't use residual here, because we don't copy samples to residual for verbatim frames. + } + + unsafe void + output_subframe_fixed(FlacFrame* frame, BitWriter bitwriter, FlacSubframe* sub) + { + // warm-up samples + for (int i = 0; i < sub->order; i++) + bitwriter.writebits_signed(sub->obits, sub->residual[i]); + + // residual + output_residual(frame, bitwriter, sub); + } + + unsafe void + output_subframe_lpc(FlacFrame* frame, BitWriter bitwriter, FlacSubframe* sub) + { + // warm-up samples + for (int i = 0; i < sub->order; i++) + bitwriter.writebits_signed(sub->obits, sub->residual[i]); + + // LPC coefficients + uint cbits = lpc_precision; + bitwriter.writebits(4, cbits - 1); + bitwriter.writebits_signed(5, sub->shift); + for (int i = 0; i < sub->order; i++) + bitwriter.writebits_signed(cbits, sub->coefs[i]); + + // residual + output_residual(frame, bitwriter, sub); + } + + unsafe void output_subframes(FlacFrame* frame, BitWriter bitwriter) + { + for (int ch = 0; ch < channels; ch++) + { + FlacSubframe* sub = frame->subframes + ch; + // subframe header + int type_code = (int) sub->type; + if (sub->type == SubframeType.Fixed) + type_code |= sub->order; + if (sub->type == SubframeType.LPC) + type_code |= sub->order - 1; + bitwriter.writebits(1, 0); + bitwriter.writebits(6, type_code); + bitwriter.writebits(1, sub->wbits != 0 ? 1 : 0); + if (sub->wbits > 0) + bitwriter.writebits((int)sub->wbits, 1); + + // subframe + switch (sub->type) + { + case SubframeType.Constant: + output_subframe_constant(frame, bitwriter, sub); + break; + case SubframeType.Verbatim: + output_subframe_verbatim(frame, bitwriter, sub); + break; + case SubframeType.Fixed: + output_subframe_fixed(frame, bitwriter, sub); + break; + case SubframeType.LPC: + output_subframe_lpc(frame, bitwriter, sub); + break; + } + } + } + + void output_frame_footer(BitWriter bitwriter) + { + bitwriter.flush(); + ushort crc = crc16.ComputeChecksum(frame_buffer, 0, bitwriter.Length); + bitwriter.writebits(16, crc); + bitwriter.flush(); + } + + unsafe void window_welch(double* window, int L) + { + int N = L - 1; + double N2 = (double)N / 2.0; + + for (int n = 0; n <= N; n++) + { + double k = 1 / N2 - 1.0 - Math.Min(n, N-n); + //double k = ((double)n - N2) / N2; + window[n] = 1.0 - k * k; + } + } + + unsafe void window_rectangle(double* window, int L) + { + for (int n = 0; n < L; n++) + window[n] = 1.0; + } + + unsafe void window_flattop(double* window, int L) + { + int N = L - 1; + for (int n = 0; n < L; n++) + window[n] = 1.0 - 1.93 * Math.Cos(2.0 * Math.PI * n / N) + 1.29 * Math.Cos(4.0 * Math.PI * n / N) - 0.388 * Math.Cos(6.0 * Math.PI * n / N) + 0.0322 * Math.Cos(8.0 * Math.PI * n / N); + } + + unsafe void window_tukey(double* window, int L) + { + window_rectangle(window, L); + double p = 0.5; + int Np = (int) (p / 2.0 * L) - 1; + if (Np > 0) + { + for (int n = 0; n <= Np; n++) + { + window[n] = 0.5 - 0.5 * Math.Cos(Math.PI * n / Np); + window[L - Np - 1 + n] = 0.5 - 0.5 * Math.Cos(Math.PI * (n + Np) / Np); + } + } + } + + unsafe void window_hann(double* window, int L) + { + int N = L - 1; + for (int n = 0; n < L; n++) + window[n] = 0.5 - 0.5 * Math.Cos(2.0 * Math.PI * n / N); + } + + unsafe int encode_frame() + { + FlacFrame* frame = stackalloc FlacFrame[1]; + FlacSubframe* sf = stackalloc FlacSubframe[channels * 3]; + + fixed (int* s = samplesBuffer, r = residualBuffer) + { + frame->subframes = sf; + init_frame(frame); + int* current_residual = stackalloc int[frame->blocksize * 3]; + bool alreadyEncoded = false; + if (frame->blocksize != _windowsize && frame->blocksize > 4) + fixed (double* window = windowBuffer) + { + _windowsize = frame->blocksize; + _windowcount = 0; + if ((eparams.window_function & WindowFunction.Welch) != 0) + window_welch(window + (_windowcount++)*_windowsize, _windowsize); + if ((eparams.window_function & WindowFunction.Tukey) != 0) + window_tukey(window + (_windowcount++) * _windowsize, _windowsize); + if ((eparams.window_function & WindowFunction.Hann) != 0) + window_hann(window + (_windowcount++) * _windowsize, _windowsize); + if ((eparams.window_function & WindowFunction.Flattop) != 0) + window_flattop(window + (_windowcount++) * _windowsize, _windowsize); + if (_windowcount == 0) + throw new Exception("invalid windowfunction"); + } + + if (channels != 2 || frame->blocksize <= 32 || eparams.stereo_method == StereoMethod.Independent) + { + frame->ch_mode = channels != 2 ? ChannelMode.NotStereo : ChannelMode.LeftRight; + frame->current.residual = current_residual; + for (int ch = 0; ch < channels; ch++) + initialize_subframe(frame, ch, s + ch * Flake.MAX_BLOCKSIZE, r + ch * Flake.MAX_BLOCKSIZE, bits_per_sample); + } + else + { + FlacFrame* frameM = stackalloc FlacFrame[1]; + FlacFrame* frameS = stackalloc FlacFrame[1]; + init_frame(frameS); + frameS->subframes = sf + channels; + frameS->ch_mode = ChannelMode.LeftRight; + frameS->current.residual = current_residual; + init_frame(frameM); + frameM->subframes = sf + channels * 2; + frameM->ch_mode = ChannelMode.MidSide; + frameM->current.residual = current_residual + frame->blocksize; + + channel_decorrelation(s, s + Flake.MAX_BLOCKSIZE, s + 2 * Flake.MAX_BLOCKSIZE, s + 3 * Flake.MAX_BLOCKSIZE, frame->blocksize); + + for (int ch = 0; ch < channels; ch++) + { + initialize_subframe(frameS, ch, s + ch * Flake.MAX_BLOCKSIZE, r + ch * Flake.MAX_BLOCKSIZE, bits_per_sample); + initialize_subframe(frameM, ch, s + (channels + ch) * Flake.MAX_BLOCKSIZE, r + (channels + ch) * Flake.MAX_BLOCKSIZE, bits_per_sample + (uint)ch); + } + + uint bitsBest = UINT32_MAX; + ChannelMode modeBest = ChannelMode.LeftRight; + + if (eparams.stereo_method == StereoMethod.Estimate) + { + frameM->subframes[0].size = (uint)frame->blocksize * 32 + calc_decorr_score(frameM, 0, frameM->subframes + 0); + frameM->subframes[1].size = (uint)frame->blocksize * 32 + calc_decorr_score(frameM, 1, frameM->subframes + 1); + frameS->subframes[0].size = (uint)frame->blocksize * 32 + calc_decorr_score(frameS, 0, frameS->subframes + 0); + frameS->subframes[1].size = (uint)frame->blocksize * 32 + calc_decorr_score(frameS, 1, frameS->subframes + 1); + } + else if (eparams.stereo_method == StereoMethod.Estimate2) + { + int max_prediction_order = eparams.max_prediction_order; + int max_window = _windowcount; + eparams.max_prediction_order /= 2; + _windowcount = 1; + encode_residual(frameM, 0, PredictionType.Levinson, OrderMethod.Estimate); + encode_residual(frameM, 1, PredictionType.Levinson, OrderMethod.Estimate); + encode_residual(frameS, 0, PredictionType.Levinson, OrderMethod.Estimate); + encode_residual(frameS, 1, PredictionType.Levinson, OrderMethod.Estimate); + _windowcount = max_window; + eparams.max_prediction_order = max_prediction_order; + } + else if (eparams.stereo_method == StereoMethod.Estimate3) + { + int max_fixed_order = eparams.max_fixed_order; + eparams.max_fixed_order = 2; + encode_residual(frameM, 0, PredictionType.Fixed, OrderMethod.Estimate); + encode_residual(frameM, 1, PredictionType.Fixed, OrderMethod.Estimate); + encode_residual(frameS, 0, PredictionType.Fixed, OrderMethod.Estimate); + encode_residual(frameS, 1, PredictionType.Fixed, OrderMethod.Estimate); + eparams.max_fixed_order = max_fixed_order; + } + else if (eparams.stereo_method == StereoMethod.Estimate4) + { + int max_prediction_order = eparams.max_prediction_order; + int max_fixed_order = eparams.max_fixed_order; + int min_fixed_order = eparams.min_fixed_order; + eparams.min_fixed_order = 2; + eparams.max_fixed_order = 2; + eparams.max_prediction_order = Math.Min(eparams.max_prediction_order, 8); + encode_residual(frameM, 0, eparams.prediction_type, OrderMethod.Estimate); + encode_residual(frameM, 1, eparams.prediction_type, OrderMethod.Estimate); + encode_residual(frameS, 0, eparams.prediction_type, OrderMethod.Estimate); + encode_residual(frameS, 1, eparams.prediction_type, OrderMethod.Estimate); + eparams.min_fixed_order = min_fixed_order; + eparams.max_fixed_order = max_fixed_order; + eparams.max_prediction_order = max_prediction_order; + } + else if (eparams.stereo_method == StereoMethod.Estimate5) + { + int max_prediction_order = eparams.max_prediction_order; + int max_fixed_order = eparams.max_fixed_order; + int min_fixed_order = eparams.min_fixed_order; + eparams.min_fixed_order = 2; + eparams.max_fixed_order = 2; + eparams.max_prediction_order = Math.Min(eparams.max_prediction_order, 12); + encode_residual(frameM, 0, eparams.prediction_type, OrderMethod.Estimate); + encode_residual(frameM, 1, eparams.prediction_type, OrderMethod.Estimate); + encode_residual(frameS, 0, eparams.prediction_type, OrderMethod.Estimate); + encode_residual(frameS, 1, eparams.prediction_type, OrderMethod.Estimate); + eparams.min_fixed_order = min_fixed_order; + eparams.max_fixed_order = max_fixed_order; + eparams.max_prediction_order = max_prediction_order; + } + else // StereoMethod.Search + { + encode_residual(frameM, 0, eparams.prediction_type, eparams.order_method); + encode_residual(frameM, 1, eparams.prediction_type, eparams.order_method); + encode_residual(frameS, 0, eparams.prediction_type, eparams.order_method); + encode_residual(frameS, 1, eparams.prediction_type, eparams.order_method); + alreadyEncoded = true; + } + + if (bitsBest > frameM->subframes[0].size + frameM->subframes[1].size) + { + bitsBest = frameM->subframes[0].size + frameM->subframes[1].size; + modeBest = ChannelMode.MidSide; + frame->subframes[0] = frameM->subframes[0]; + frame->subframes[1] = frameM->subframes[1]; + } + if (bitsBest > frameM->subframes[1].size + frameS->subframes[1].size) + { + bitsBest = frameM->subframes[1].size + frameS->subframes[1].size; + modeBest = ChannelMode.RightSide; + frame->subframes[0] = frameM->subframes[1]; + frame->subframes[1] = frameS->subframes[1]; + } + if (bitsBest > frameM->subframes[1].size + frameS->subframes[0].size) + { + bitsBest = frameM->subframes[1].size + frameS->subframes[0].size; + modeBest = ChannelMode.LeftSide; + frame->subframes[0] = frameS->subframes[0]; + frame->subframes[1] = frameM->subframes[1]; + } + if (bitsBest > frameS->subframes[0].size + frameS->subframes[1].size) + { + bitsBest = frameS->subframes[0].size + frameS->subframes[1].size; + modeBest = ChannelMode.LeftRight; + frame->subframes[0] = frameS->subframes[0]; + frame->subframes[1] = frameS->subframes[1]; + } + frame->ch_mode = modeBest; + frame->current.residual = current_residual + 2 * frame->blocksize; + if (eparams.stereo_method == StereoMethod.Estimate4 || eparams.stereo_method == StereoMethod.Estimate5) + { + encode_residual(frame, 0, PredictionType.Estimated, eparams.order_method); + encode_residual(frame, 1, PredictionType.Estimated, eparams.order_method); + alreadyEncoded = true; + } + } + + if (!alreadyEncoded) + for (int ch = 0; ch < channels; ch++) + encode_residual(frame, ch, eparams.prediction_type, eparams.order_method); + + BitWriter bitwriter = new BitWriter(frame_buffer, 0, max_frame_size); + + output_frame_header(frame, bitwriter); + output_subframes(frame, bitwriter); + output_frame_footer(bitwriter); + + if (frame_buffer != null) + { + if (eparams.variable_block_size > 0) + frame_count += eparams.block_size; + else + frame_count++; + } + return bitwriter.Length; + } + } + + unsafe void output_frame() + { + if (verify != null) + { + fixed (int* s = verifyBuffer, r = samplesBuffer) + for (int ch = 0; ch < channels; ch++) + Flake.memcpy(s + ch * Flake.MAX_BLOCKSIZE, r + ch * Flake.MAX_BLOCKSIZE, eparams.block_size); + } + + int fs; + if (0 != eparams.variable_block_size && 0 == (eparams.block_size & 7) && eparams.block_size >= 128) + fs = encode_frame_vbs(); + else + fs = encode_frame(); + + _position += eparams.block_size; + _IO.Write(frame_buffer, 0, fs); + _totalSize += fs; + + if (verify != null) + { + int decoded = verify.DecodeFrame(frame_buffer, 0, fs); + if (decoded != fs || verify.Remaining != (ulong)eparams.block_size) + throw new Exception("validation failed!"); + fixed (int* s = verifyBuffer, r = verify.Samples) + { + for (int ch = 0; ch < channels; ch++) + if (Flake.memcmp(s + ch * Flake.MAX_BLOCKSIZE, r +ch * Flake.MAX_BLOCKSIZE, decoded)) + throw new Exception("validation failed!"); + } + } + } + + public void Write(int[,] buff, int pos, int sampleCount) + { + if (!inited) + { + int header_size = flake_encode_init(); + if (_IO == null) + _IO = new FileStream(_path, FileMode.Create, FileAccess.Write, FileShare.Read); + _IO.Write(header, 0, header_size); + inited = true; + } + + int len = sampleCount; + while (len > 0) + { + int block = Math.Min(len, eparams.block_size - samplesInBuffer); + + copy_samples(buff, pos, block); + + AudioSamples.FLACSamplesToBytes(buff, pos, frame_buffer, 0, block, channels, (int)bits_per_sample); + md5.TransformBlock(frame_buffer, 0, block * channels * ((int)bits_per_sample >> 3), null, 0); + + if (samplesInBuffer < eparams.block_size) + return; + + output_frame(); + + samplesInBuffer = 0; + len -= block; + pos += block; + } + } + + public string Path { get { return _path; } } + + int[] flac_samplerates; + int[] flac_bitdepths; + int[] flac_blocksizes; + string vendor_string = "Flake#0.1"; + + int select_blocksize(int samplerate, int time_ms) + { + int blocksize = flac_blocksizes[1]; + int target = (samplerate * time_ms) / 1000; + for (int i = 0; i < flac_blocksizes.Length; i++) + if (target >= flac_blocksizes[i] && flac_blocksizes[i] > blocksize) + { + blocksize = flac_blocksizes[i]; + } + return blocksize; + } + + void write_streaminfo(byte[] header, int pos, int last) + { + Array.Clear(header, pos, 38); + BitWriter bitwriter = new BitWriter(header, pos, 38); + + // metadata header + bitwriter.writebits(1, last); + bitwriter.writebits(7, 0); + bitwriter.writebits(24, 34); + + if (eparams.variable_block_size > 0) + bitwriter.writebits(16, 0); + else + bitwriter.writebits(16, eparams.block_size); + + bitwriter.writebits(16, eparams.block_size); + bitwriter.writebits(24, 0); + bitwriter.writebits(24, max_frame_size); + bitwriter.writebits(20, sample_rate); + bitwriter.writebits(3, channels - 1); + bitwriter.writebits(5, bits_per_sample - 1); + + // total samples + if (sample_count > 0) + { + bitwriter.writebits(4, 0); + bitwriter.writebits(32, sample_count); + } + else + { + bitwriter.writebits(4, 0); + bitwriter.writebits(32, 0); + } + bitwriter.flush(); + } + + /** + * Write vorbis comment metadata block to byte array. + * Just writes the vendor string for now. + */ + int write_vorbis_comment(byte[] comment, int pos, int last) + { + BitWriter bitwriter = new BitWriter(comment, pos, 4); + Encoding enc = new ASCIIEncoding(); + int vendor_len = enc.GetBytes(vendor_string, 0, vendor_string.Length, comment, pos + 8); + + // metadata header + bitwriter.writebits(1, last); + bitwriter.writebits(7, 4); + bitwriter.writebits(24, vendor_len + 8); + + comment[pos + 4] = (byte)(vendor_len & 0xFF); + comment[pos + 5] = (byte)((vendor_len >> 8) & 0xFF); + comment[pos + 6] = (byte)((vendor_len >> 16) & 0xFF); + comment[pos + 7] = (byte)((vendor_len >> 24) & 0xFF); + comment[pos + 8 + vendor_len] = 0; + comment[pos + 9 + vendor_len] = 0; + comment[pos + 10 + vendor_len] = 0; + comment[pos + 11 + vendor_len] = 0; + bitwriter.flush(); + return vendor_len + 12; + } + + /** + * Write padding metadata block to byte array. + */ + int + write_padding(byte[] padding, int pos, int last, int padlen) + { + BitWriter bitwriter = new BitWriter(padding, pos, 4); + + // metadata header + bitwriter.writebits(1, last); + bitwriter.writebits(7, 1); + bitwriter.writebits(24, padlen); + + return padlen + 4; + } + + int write_headers() + { + int header_size = 0; + int last = 0; + + // stream marker + header[0] = 0x66; + header[1] = 0x4C; + header[2] = 0x61; + header[3] = 0x43; + header_size += 4; + + // streaminfo + write_streaminfo(header, header_size, last); + header_size += 38; + + // vorbis comment + if (eparams.padding_size == 0) last = 1; + header_size += write_vorbis_comment(header, header_size, last); + + // padding + if (eparams.padding_size > 0) + { + last = 1; + header_size += write_padding(header, header_size, last, eparams.padding_size); + } + + return header_size; + } + + int flake_encode_init() + { + int i, header_len; + + //if(flake_validate_params(s) < 0) + + ch_code = channels - 1; + + // find samplerate in table + for (i = 4; i < 12; i++) + { + if (sample_rate == flac_samplerates[i]) + { + sr_code0 = i; + break; + } + } + + // if not in table, samplerate is non-standard + if (i == 12) + throw new Exception("non-standard samplerate"); + + for (i = 1; i < 8; i++) + { + if (bits_per_sample == flac_bitdepths[i]) + { + bps_code = i; + break; + } + } + if (i == 8) + throw new Exception("non-standard bps"); + // FIXME: For now, only 16-bit encoding is supported + if (bits_per_sample != 16) + throw new Exception("non-standard bps"); + + if (eparams.block_size == 0) + if (_blocksize == 0) + eparams.block_size = select_blocksize(sample_rate, eparams.block_time_ms); + else + eparams.block_size = _blocksize; + + // select LPC precision based on block size + if (eparams.block_size <= 192) lpc_precision = 7U; + else if (eparams.block_size <= 384) lpc_precision = 8U; + else if (eparams.block_size <= 576) lpc_precision = 9U; + else if (eparams.block_size <= 1152) lpc_precision = 10U; + else if (eparams.block_size <= 2304) lpc_precision = 11U; + else if (eparams.block_size <= 4608) lpc_precision = 12U; + else if (eparams.block_size <= 8192) lpc_precision = 13U; + else if (eparams.block_size <= 16384) lpc_precision = 14U; + else lpc_precision = 15; + + // set maximum encoded frame size (if larger, re-encodes in verbatim mode) + if (channels == 2) + max_frame_size = 16 + ((eparams.block_size * (int)(bits_per_sample + bits_per_sample + 1) + 7) >> 3); + else + max_frame_size = 16 + ((eparams.block_size * channels * (int)bits_per_sample + 7) >> 3); + + // output header bytes + header = new byte[eparams.padding_size + 1024]; + header_len = write_headers(); + + // initialize CRC & MD5 + //crc_init(); + //md5_init(&ctx->md5ctx); + + frame_buffer = new byte[max_frame_size]; + + return header_len; + } + } + + struct FlakeEncodeParams + { + // compression quality + // set by user prior to calling flake_encode_init + // standard values are 0 to 8 + // 0 is lower compression, faster encoding + // 8 is higher compression, slower encoding + // extended values 9 to 12 are slower and/or use + // higher prediction orders + public int compression; + + // prediction order selection method + // set by user prior to calling flake_encode_init + // if set to less than 0, it is chosen based on compression. + // valid values are 0 to 5 + // 0 = use maximum order only + // 1 = use estimation + // 2 = 2-level + // 3 = 4-level + // 4 = 8-level + // 5 = full search + // 6 = log search + public OrderMethod order_method; + + + // stereo decorrelation method + // set by user prior to calling flake_encode_init + // if set to less than 0, it is chosen based on compression. + // valid values are 0 to 2 + // 0 = independent L+R channels + // 1 = mid-side encoding + public StereoMethod stereo_method; + + // block size in samples + // set by the user prior to calling flake_encode_init + // if set to 0, a block size is chosen based on block_time_ms + // can also be changed by user before encoding a frame + public int block_size; + + // block time in milliseconds + // set by the user prior to calling flake_encode_init + // used to calculate block_size based on sample rate + // can also be changed by user before encoding a frame + public int block_time_ms; + + // padding size in bytes + // set by the user prior to calling flake_encode_init + // if set to less than 0, defaults to 4096 + public int padding_size; + + // maximum encoded frame size + // this is set by flake_encode_init based on input audio format + // it can be used by the user to allocate an output buffer + public int max_frame_size; + + // minimum LPC order + // set by user prior to calling flake_encode_init + // if set to less than 0, it is chosen based on compression. + // valid values are 1 to 32 + public int min_prediction_order; + + // maximum LPC order + // set by user prior to calling flake_encode_init + // if set to less than 0, it is chosen based on compression. + // valid values are 1 to 32 + public int max_prediction_order; + + // minimum fixed prediction order + // set by user prior to calling flake_encode_init + // if set to less than 0, it is chosen based on compression. + // valid values are 0 to 4 + public int min_fixed_order; + + // maximum fixed prediction order + // set by user prior to calling flake_encode_init + // if set to less than 0, it is chosen based on compression. + // valid values are 0 to 4 + public int max_fixed_order; + + // type of linear prediction + // set by user prior to calling flake_encode_init + public PredictionType prediction_type; + + // minimum partition order + // set by user prior to calling flake_encode_init + // if set to less than 0, it is chosen based on compression. + // valid values are 0 to 8 + public int min_partition_order; + + // maximum partition order + // set by user prior to calling flake_encode_init + // if set to less than 0, it is chosen based on compression. + // valid values are 0 to 8 + public int max_partition_order; + + // whether to use variable block sizes + // set by user prior to calling flake_encode_init + // 0 = fixed block size + // 1 = variable block size + public int variable_block_size; + + + public WindowFunction window_function; + + public int flake_set_defaults(int lvl) + { + compression = lvl; + + if ((lvl < 0 || lvl > 12) && (lvl != 99)) + { + return -1; + } + + // default to level 5 params + window_function = WindowFunction.Flattop | WindowFunction.Tukey; + order_method = OrderMethod.Estimate; + stereo_method = StereoMethod.Estimate; + block_size = 0; + block_time_ms = 105; + prediction_type = PredictionType.Search; + min_prediction_order = 1; + max_prediction_order = 12; + min_fixed_order = 0; + max_fixed_order = 4; + min_partition_order = 0; + max_partition_order = 6; + variable_block_size = 0; + + // differences from level 5 + switch (lvl) + { + case 0: + stereo_method = StereoMethod.Independent; + block_time_ms = 27; + prediction_type = PredictionType.Fixed; + min_fixed_order = 2; + max_fixed_order = 2; + min_partition_order = 4; + max_partition_order = 4; + break; + case 1: + block_time_ms = 27; + prediction_type = PredictionType.Fixed; + min_fixed_order = 2; + max_fixed_order = 3; + min_partition_order = 2; + max_partition_order = 2; + break; + case 2: + block_time_ms = 27; + prediction_type = PredictionType.Fixed; + //prediction_type = PredictionType.Fixed; + min_fixed_order = 2; + max_fixed_order = 4; + //min_partition_order = 0; + //max_partition_order = 3; + break; + case 3: + prediction_type = PredictionType.Levinson; + window_function = WindowFunction.Welch; + max_prediction_order = 8; + break; + case 4: + prediction_type = PredictionType.Levinson; + window_function = WindowFunction.Welch; + break; + case 5: + prediction_type = PredictionType.Levinson; + break; + case 6: + stereo_method = StereoMethod.Estimate4; + break; + case 7: + order_method = OrderMethod.LogSearch; + stereo_method = StereoMethod.Estimate4; + break; + case 8: + order_method = OrderMethod.Search; + stereo_method = StereoMethod.Estimate4; + break; + case 9: + stereo_method = StereoMethod.Estimate4; + max_prediction_order = 32; + break; + case 10: + order_method = OrderMethod.LogFast; + stereo_method = StereoMethod.Estimate5; + max_prediction_order = 32; + break; + case 11: + order_method = OrderMethod.LogSearch; + stereo_method = StereoMethod.Estimate5; + max_prediction_order = 32; + max_partition_order = 8; + break; + case 99: + order_method = OrderMethod.Search; + block_time_ms = 186; + max_prediction_order = 32; + max_partition_order = 8; + variable_block_size = 2; + break; + } + + return 0; + } + } +} diff --git a/CUETools.Codecs.FLAKE/Properties/AssemblyInfo.cs b/CUETools.Codecs.FLAKE/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..ca83661 --- /dev/null +++ b/CUETools.Codecs.FLAKE/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("CUETools.Codecs.FLAKE")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("CUETools.Codecs.FLAKE")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2009")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("af385bb5-1d2e-409b-97db-b96d9ed491a0")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/CUETools.Codecs.FLAKE/lpc.cs b/CUETools.Codecs.FLAKE/lpc.cs new file mode 100644 index 0000000..6e8e01e --- /dev/null +++ b/CUETools.Codecs.FLAKE/lpc.cs @@ -0,0 +1,650 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CUETools.Codecs.FLAKE +{ + class lpc + { + public const int MAX_LPC_ORDER = 32; + public const int MAX_LPC_WINDOWS = 4; + + /** + * Apply Welch window function to audio block + */ + static unsafe void + apply_welch_window(/*const*/int* data, uint len, double* w_data) + { + double c = (2.0 / (len - 1.0)) - 1.0; + for (uint i = 0; i < (len >> 1); i++) + { + double w = 1.0 - ((c - i) * (c - i)); + w_data[i] = data[i] * w; + w_data[len - 1 - i] = data[len - 1 - i] * w; + } + } + + /** + * Calculates autocorrelation data from audio samples + * A Welch window function is applied before calculation. + */ + static unsafe void + compute_autocorr(/*const*/ int* data, uint len, uint lag, double* autoc, double* window) + { + double* data1 = stackalloc double[(int)len + 16]; + uint i, j; + double temp, temp2; + + if (window == null) + apply_welch_window(data, len, data1); + else + { + for (i = 0; i < len; i++) + data1[i] = data[i] * window[i]; + } + data1[len] = 0; + + for (i = 0; i <= lag; ++i) + { + temp = 1.0; + temp2 = 1.0; + for (j = 0; j <= lag - i; ++j) + temp += data1[j + i] * data1[j]; + + double* finish = data1 + len - i; + for (double* pdata = data1 + lag + 1 - i; pdata < finish; pdata += 2) + { + temp += pdata[i] * pdata[0]; + temp2 += pdata[i + 1] * pdata[1]; + } + autoc[i] = temp + temp2; + } + + //int sample, coeff; + //for (coeff = 0; coeff <= lag; coeff++) + // autoc[coeff] = 0.0; + //int data_len = (int)len; + //int limit = data_len - (int)lag - 1; + //for (sample = 0; sample <= limit; sample++) + //{ + // double d = data1[sample]; + // for (coeff = 0; coeff <= lag; coeff++) + // autoc[coeff] += d * data1[sample + coeff]; + //} + //for (; sample < data_len; sample++) + //{ + // double d = data1[sample]; + // for (coeff = 0; coeff < data_len - sample; coeff++) + // autoc[coeff] += d * data1[sample + coeff]; + //} + } + + /** + * Levinson-Durbin recursion. + * Produces LPC coefficients from autocorrelation data. + */ + static unsafe void + compute_lpc_coefs(/*const*/ double* autoc, uint max_order, double* reff, + double* lpc/*[][MAX_LPC_ORDER]*/) + { + double* lpc_tmp = stackalloc double[MAX_LPC_ORDER]; + + int i, j, i2; + double r, err, tmp; + + if (max_order > MAX_LPC_ORDER) + throw new Exception("wierd"); + + for (i = 0; i < max_order; i++) + lpc_tmp[i] = 0; + + err = 1.0; + if (autoc != null) + err = autoc[0]; + + for (i = 0; i < max_order; i++) + { + if (reff != null) + { + r = reff[i]; + } + else + { + r = -autoc[i + 1]; + for (j = 0; j < i; j++) + { + r -= lpc_tmp[j] * autoc[i - j]; + } + r /= err; + err *= 1.0 - (r * r); + } + + i2 = (i >> 1); + lpc_tmp[i] = r; + for (j = 0; j < i2; j++) + { + tmp = lpc_tmp[j]; + lpc_tmp[j] += r * lpc_tmp[i - 1 - j]; + lpc_tmp[i - 1 - j] += r * tmp; + } + if (0 != (i & 1)) + { + lpc_tmp[j] += lpc_tmp[j] * r; + } + + for (j = 0; j <= i; j++) + { + lpc[i * MAX_LPC_ORDER + j] = -lpc_tmp[j]; + } + } + } + + /** + * Compute LPC coefs for Flake.OrderMethod._EST + * Faster LPC coeff computation by first calculating the reflection coefficients + * using Schur recursion. That allows for estimating the optimal order before + * running Levinson recursion. + */ + static unsafe uint + compute_lpc_coefs_est(/*const*/ double* autoc, uint max_order, + double* lpc/*[][MAX_LPC_ORDER]*/) + { + double* gen0 = stackalloc double[MAX_LPC_ORDER]; + double* gen1 = stackalloc double[MAX_LPC_ORDER]; + double* reff = stackalloc double[MAX_LPC_ORDER]; + + // Schur recursion + for (uint i = 0; i < max_order; i++) + gen0[i] = gen1[i] = autoc[i + 1]; + + double error = autoc[0]; + reff[0] = -gen1[0] / error; + error += gen1[0] * reff[0]; + for (uint i = 1; i < max_order; i++) + { + for (uint j = 0; j < max_order - i; j++) + { + gen1[j] = gen1[j + 1] + reff[i - 1] * gen0[j]; + gen0[j] = gen1[j + 1] * reff[i - 1] + gen0[j]; + } + reff[i] = -gen1[0] / error; + error += gen1[0] * reff[i]; + } + + // Estimate optimal order using reflection coefficients + uint order_est = 1; + for (int i = (int)max_order - 1; i >= 0; i--) + { + if (Math.Abs(reff[i]) > 0.10) + { + order_est = (uint)i + 1; + break; + } + } + + // Levinson recursion + compute_lpc_coefs(null, order_est, reff, lpc); + return order_est; + } + + /** + * Quantize LPC coefficients + */ + public static unsafe void + quantize_lpc_coefs(double* lpc_in, int order, uint precision, int* lpc_out, + out int shift) + { + int i; + double d, cmax, error; + int qmax; + int sh, q; + + // define maximum levels + qmax = (1 << ((int)precision - 1)) - 1; + + // find maximum coefficient value + cmax = 0.0; + for (i = 0; i < order; i++) + { + d = Math.Abs(lpc_in[i]); + if (d > cmax) + cmax = d; + } + // if maximum value quantizes to zero, return all zeros + if (cmax * (1 << 15) < 1.0) + { + shift = 0; + for (i = 0; i < order; i++) + lpc_out[i] = 0; + return; + } + + // calculate level shift which scales max coeff to available bits + sh = 15; + while ((cmax * (1 << sh) > qmax) && (sh > 0)) + { + sh--; + } + + // since negative shift values are unsupported in decoder, scale down + // coefficients instead + if (sh == 0 && cmax > qmax) + { + double scale = ((double)qmax) / cmax; + for (i = 0; i < order; i++) + { + lpc_in[i] *= scale; + } + } + + // output quantized coefficients and level shift + error = 0; + for (i = 0; i < order; i++) + { + error += lpc_in[i] * (1 << sh); + q = (int)(error + 0.5); + if (q <= -qmax) q = -qmax + 1; + if (q > qmax) q = qmax; + error -= q; + lpc_out[i] = q; + } + shift = sh; + } + + /** + * Calculate LPC coefficients for multiple orders + */ + public static unsafe uint + calc_coefs(/*const*/ int* samples, uint blocksize, uint max_order, OrderMethod omethod, double* lpcs, double* window) + { + double* autoc = stackalloc double[MAX_LPC_ORDER + 1]; + + compute_autocorr(samples, blocksize, max_order + 1, autoc, window); + + uint opt_order = max_order; + if (omethod == OrderMethod.Estimate || omethod == OrderMethod.EstSearch) + opt_order = compute_lpc_coefs_est(autoc, max_order, lpcs); + else + compute_lpc_coefs(autoc, max_order, null, lpcs); + + return opt_order; + } + + public static unsafe void + encode_residual(int* res, int* smp, int n, int order, + int* coefs, int shift) + { + for (int i = 0; i < order; i++) + res[i] = smp[i]; + + int* s = smp; + int* r = res + order; + int c0 = coefs[0]; + int c1 = coefs[1]; + switch (order) + { + case 1: + for (int i = n - order; i > 0; i--) + { + int pred = c0 * *(s++); + *(r++) = *s - (pred >> shift); + } + break; + case 2: + for (int i = n - order; i > 0; i--) + { + int pred = c1 * *(s++); + pred += c0 * *(s++); + *(r++) = *(s--) - (pred >> shift); + } + break; + case 3: + for (int i = n - order; i > 0; i--) + { + int pred = coefs[2] * *(s++); + pred += c1 * *(s++); + pred += c0 * *(s++); + *(r++) = *s - (pred >> shift); + s -= 2; + } + break; + case 4: + for (int i = n - order; i > 0; i--) + { + int pred = coefs[3] * *(s++); + pred += coefs[2] * *(s++); + pred += c1 * *(s++); + pred += c0 * *(s++); + *(r++) = *s - (pred >> shift); + s -= 3; + } + break; + case 5: + for (int i = n - order; i > 0; i--) + { + int pred = coefs[4] * *(s++); + pred += coefs[3] * *(s++); + pred += coefs[2] * *(s++); + pred += c1 * *(s++); + pred += c0 * *(s++); + *(r++) = *s - (pred >> shift); + s -= 4; + } + break; + case 6: + for (int i = n - order; i > 0; i--) + { + int pred = coefs[5] * *(s++); + pred += coefs[4] * *(s++); + pred += coefs[3] * *(s++); + pred += coefs[2] * *(s++); + pred += c1 * *(s++); + pred += c0 * *(s++); + *(r++) = *s - (pred >> shift); + s -= 5; + } + break; + case 7: + for (int i = n - order; i > 0; i--) + { + int pred = coefs[6] * *(s++); + pred += coefs[5] * *(s++); + pred += coefs[4] * *(s++); + pred += coefs[3] * *(s++); + pred += coefs[2] * *(s++); + pred += c1 * *(s++); + pred += c0 * *(s++); + *(r++) = *s - (pred >> shift); + s -= 6; + } + break; + case 8: + for (int i = n - order; i > 0; i--) + { + int pred = coefs[7] * *(s++); + pred += coefs[6] * *(s++); + pred += coefs[5] * *(s++); + pred += coefs[4] * *(s++); + pred += coefs[3] * *(s++); + pred += coefs[2] * *(s++); + pred += c1 * *(s++); + pred += c0 * *(s++); + *(r++) = *s - (pred >> shift); + s -= 7; + } + break; + default: + for (int i = order; i < n; i++) + { + s = smp + i - order; + int pred = 0; + int* co = coefs + order - 1; + int* c7 = coefs + 7; + while (co > c7) + pred += *(co--) * *(s++); + pred += coefs[7] * *(s++); + pred += coefs[6] * *(s++); + pred += coefs[5] * *(s++); + pred += coefs[4] * *(s++); + pred += coefs[3] * *(s++); + pred += coefs[2] * *(s++); + pred += c1 * *(s++); + pred += c0 * *(s++); + *(r++) = *s - (pred >> shift); + } + break; + } + } + + public static unsafe void + decode_residual(int* res, int* smp, int n, int order, + int* coefs, int shift) + { + for (int i = 0; i < order; i++) + smp[i] = res[i]; + + int* s = smp; + int* r = res + order; + int c0 = coefs[0]; + int c1 = coefs[1]; + switch (order) + { + case 1: + for (int i = n - order; i > 0; i--) + { + int pred = c0 * *(s++); + *s = *(r++) + (pred >> shift); + } + break; + case 2: + for (int i = n - order; i > 0; i--) + { + int pred = c1 * *(s++); + pred += c0 * *(s++); + *(s--) = *(r++) + (pred >> shift); + } + break; + case 3: + for (int i = n - order; i > 0; i--) + { + int pred = coefs[2] * *(s++); + pred += c1 * *(s++); + pred += c0 * *(s++); + *s = *(r++) + (pred >> shift); + s -= 2; + } + break; + case 4: + for (int i = n - order; i > 0; i--) + { + int pred = coefs[3] * *(s++); + pred += coefs[2] * *(s++); + pred += c1 * *(s++); + pred += c0 * *(s++); + *s = *(r++) + (pred >> shift); + s -= 3; + } + break; + case 5: + for (int i = n - order; i > 0; i--) + { + int pred = coefs[4] * *(s++); + pred += coefs[3] * *(s++); + pred += coefs[2] * *(s++); + pred += c1 * *(s++); + pred += c0 * *(s++); + *s = *(r++) + (pred >> shift); + s -= 4; + } + break; + case 6: + for (int i = n - order; i > 0; i--) + { + int pred = coefs[5] * *(s++); + pred += coefs[4] * *(s++); + pred += coefs[3] * *(s++); + pred += coefs[2] * *(s++); + pred += c1 * *(s++); + pred += c0 * *(s++); + *s = *(r++) + (pred >> shift); + s -= 5; + } + break; + case 7: + for (int i = n - order; i > 0; i--) + { + int pred = coefs[6] * *(s++); + pred += coefs[5] * *(s++); + pred += coefs[4] * *(s++); + pred += coefs[3] * *(s++); + pred += coefs[2] * *(s++); + pred += c1 * *(s++); + pred += c0 * *(s++); + *s = *(r++) + (pred >> shift); + s -= 6; + } + break; + case 8: + for (int i = n - order; i > 0; i--) + { + int pred = coefs[7] * *(s++); + pred += coefs[6] * *(s++); + pred += coefs[5] * *(s++); + pred += coefs[4] * *(s++); + pred += coefs[3] * *(s++); + pred += coefs[2] * *(s++); + pred += c1 * *(s++); + pred += c0 * *(s++); + *s = *(r++) + (pred >> shift); + s -= 7; + } + break; + default: + for (int i = order; i < n; i++) + { + s = smp + i - order; + int pred = 0; + int* co = coefs + order - 1; + int* c7 = coefs + 7; + while (co > c7) + pred += *(co--) * *(s++); + pred += coefs[7] * *(s++); + pred += coefs[6] * *(s++); + pred += coefs[5] * *(s++); + pred += coefs[4] * *(s++); + pred += coefs[3] * *(s++); + pred += coefs[2] * *(s++); + pred += c1 * *(s++); + pred += c0 * *(s++); + *s = *(r++) + (pred >> shift); + } + break; + } + } + public static unsafe void + decode_residual_long(int* res, int* smp, int n, int order, + int* coefs, int shift) + { + for (int i = 0; i < order; i++) + smp[i] = res[i]; + + int* s = smp; + int* r = res + order; + int c0 = coefs[0]; + int c1 = coefs[1]; + switch (order) + { + case 1: + for (int i = n - order; i > 0; i--) + { + long pred = c0 * (long)*(s++); + *s = *(r++) + (int)(pred >> shift); + } + break; + case 2: + for (int i = n - order; i > 0; i--) + { + long pred = c1 * (long)*(s++); + pred += c0 * (long)*(s++); + *(s--) = *(r++) + (int)(pred >> shift); + } + break; + case 3: + for (int i = n - order; i > 0; i--) + { + long pred = coefs[2] * (long)*(s++); + pred += c1 * (long)*(s++); + pred += c0 * (long)*(s++); + *s = *(r++) + (int)(pred >> shift); + s -= 2; + } + break; + case 4: + for (int i = n - order; i > 0; i--) + { + long pred = coefs[3] * (long)*(s++); + pred += coefs[2] * (long)*(s++); + pred += c1 * (long)*(s++); + pred += c0 * (long)*(s++); + *s = *(r++) + (int)(pred >> shift); + s -= 3; + } + break; + case 5: + for (int i = n - order; i > 0; i--) + { + long pred = coefs[4] * (long)*(s++); + pred += coefs[3] * (long)*(s++); + pred += coefs[2] * (long)*(s++); + pred += c1 * (long)*(s++); + pred += c0 * (long)*(s++); + *s = *(r++) + (int)(pred >> shift); + s -= 4; + } + break; + case 6: + for (int i = n - order; i > 0; i--) + { + long pred = coefs[5] * (long)*(s++); + pred += coefs[4] * (long)*(s++); + pred += coefs[3] * (long)*(s++); + pred += coefs[2] * (long)*(s++); + pred += c1 * (long)*(s++); + pred += c0 * (long)*(s++); + *s = *(r++) + (int)(pred >> shift); + s -= 5; + } + break; + case 7: + for (int i = n - order; i > 0; i--) + { + long pred = coefs[6] * (long)*(s++); + pred += coefs[5] * (long)*(s++); + pred += coefs[4] * (long)*(s++); + pred += coefs[3] * (long)*(s++); + pred += coefs[2] * (long)*(s++); + pred += c1 * (long)*(s++); + pred += c0 * (long)*(s++); + *s = *(r++) + (int)(pred >> shift); + s -= 6; + } + break; + case 8: + for (int i = n - order; i > 0; i--) + { + long pred = coefs[7] * (long)*(s++); + pred += coefs[6] * (long)*(s++); + pred += coefs[5] * (long)*(s++); + pred += coefs[4] * (long)*(s++); + pred += coefs[3] * (long)*(s++); + pred += coefs[2] * (long)*(s++); + pred += c1 * (long)*(s++); + pred += c0 * (long)*(s++); + *s = *(r++) + (int)(pred >> shift); + s -= 7; + } + break; + default: + for (int i = order; i < n; i++) + { + s = smp + i - order; + long pred = 0; + int* co = coefs + order - 1; + int* c7 = coefs + 7; + while (co > c7) + pred += *(co--) * (long)*(s++); + pred += coefs[7] * (long)*(s++); + pred += coefs[6] * (long)*(s++); + pred += coefs[5] * (long)*(s++); + pred += coefs[4] * (long)*(s++); + pred += coefs[3] * (long)*(s++); + pred += coefs[2] * (long)*(s++); + pred += c1 * (long)*(s++); + pred += c0 * (long)*(s++); + *s = *(r++) + (int)(pred >> shift); + } + break; + } + } + } +}