using System; using System.Collections.Generic; namespace CUETools.Codecs { unsafe public class LpcSubframeInfo { public LpcSubframeInfo() { autocorr_section_values = new double[lpc.MAX_LPC_SECTIONS, lpc.MAX_LPC_ORDER + 1]; autocorr_section_orders = new int[lpc.MAX_LPC_SECTIONS]; } // public LpcContext[] lpc_ctx; public double[,] autocorr_section_values; public int[] autocorr_section_orders; //public int obits; public void Reset() { for (int sec = 0; sec < autocorr_section_orders.Length; sec++) autocorr_section_orders[sec] = 0; } } unsafe public struct LpcWindowSection { public enum SectionType { Zero, One, OneLarge, Data, OneGlue, Glue }; public int m_start; public int m_end; public SectionType m_type; public int m_id; public LpcWindowSection(int end) { m_id = -1; m_start = 0; m_end = end; m_type = SectionType.Data; } public void setData(int start, int end) { m_id = -1; m_start = start; m_end = end; m_type = SectionType.Data; } public void setOne(int start, int end) { m_id = -1; m_start = start; m_end = end; m_type = SectionType.One; } public void setGlue(int start) { m_id = -1; m_start = start; m_end = start; m_type = SectionType.Glue; } public void setZero(int start, int end) { m_id = -1; m_start = start; m_end = end; m_type = SectionType.Zero; } unsafe public void compute_autocorr(/*const*/ int* data, float* window, int min_order, int order, int blocksize, double* autoc) { if (m_type == SectionType.OneLarge) lpc.compute_autocorr_windowless_large(data + m_start, m_end - m_start, min_order, order, autoc); else if (m_type == SectionType.One) lpc.compute_autocorr_windowless(data + m_start, m_end - m_start, min_order, order, autoc); else if (m_type == SectionType.Data) lpc.compute_autocorr(data + m_start, window + m_start, m_end - m_start, min_order, order, autoc); else if (m_type == SectionType.Glue) lpc.compute_autocorr_glue(data, window, m_start, m_end, min_order, order, autoc); else if (m_type == SectionType.OneGlue) lpc.compute_autocorr_glue(data + m_start, min_order, order, autoc); } unsafe public static void Detect(int _windowcount, float* window_segment, int stride, int sz, int bps, LpcWindowSection* sections) { int section_id = 0; var boundaries = new List(); var types = new LpcWindowSection.SectionType[_windowcount, lpc.MAX_LPC_SECTIONS * 2]; var alias = new int[_windowcount, lpc.MAX_LPC_SECTIONS * 2]; var alias_set = new int[_windowcount, lpc.MAX_LPC_SECTIONS * 2]; for (int x = 0; x < sz; x++) { for (int i = 0; i < _windowcount; i++) { int a = alias[i, boundaries.Count]; float w = window_segment[i * stride + x]; float wa = window_segment[a * stride + x]; if (wa != w) { for (int i1 = i; i1 < _windowcount; i1++) if (alias[i1, boundaries.Count] == a && w == window_segment[i1 * stride + x]) alias[i1, boundaries.Count] = i; } if (boundaries.Count >= lpc.MAX_LPC_SECTIONS * 2) throw new IndexOutOfRangeException(); types[i, boundaries.Count] = boundaries.Count >= lpc.MAX_LPC_SECTIONS * 2 - 2 ? LpcWindowSection.SectionType.Data : w == 0.0 ? LpcWindowSection.SectionType.Zero : w != 1.0 ? LpcWindowSection.SectionType.Data : bps * 2 + BitReader.log2i(sz) >= 61 ? LpcWindowSection.SectionType.OneLarge : LpcWindowSection.SectionType.One ; } bool isBoundary = false; for (int i = 0; i < _windowcount; i++) { isBoundary |= boundaries.Count == 0 || types[i, boundaries.Count - 1] != types[i, boundaries.Count]; } if (isBoundary) { for (int i = 0; i < _windowcount; i++) for (int i1 = 0; i1 < _windowcount; i1++) if (i != i1 && alias[i, boundaries.Count] == alias[i1, boundaries.Count]) alias_set[i, boundaries.Count] |= 1 << i1; boundaries.Add(x); } } boundaries.Add(sz); var secs = new int[_windowcount]; // Reconstruct segments list. for (int j = 0; j < boundaries.Count - 1; j++) { for (int i = 0; i < _windowcount; i++) { LpcWindowSection* window_sections = sections + i * lpc.MAX_LPC_SECTIONS; // leave room for glue if (secs[i] >= lpc.MAX_LPC_SECTIONS - 1) { throw new IndexOutOfRangeException(); window_sections[secs[i] - 1].m_type = LpcWindowSection.SectionType.Data; window_sections[secs[i] - 1].m_end = boundaries[j + 1]; continue; } window_sections[secs[i]].setData(boundaries[j], boundaries[j + 1]); window_sections[secs[i]++].m_type = types[i, j]; } for (int i = 0; i < _windowcount; i++) { LpcWindowSection* window_sections = sections + i * lpc.MAX_LPC_SECTIONS; int sec = secs[i] - 1; if (sec > 0 && j > 0 && (alias_set[i, j] == alias_set[i, j - 1] || window_sections[sec].m_type == SectionType.Zero) && window_sections[sec].m_start == boundaries[j] && window_sections[sec].m_end == boundaries[j + 1] && window_sections[sec - 1].m_end == boundaries[j] && window_sections[sec - 1].m_type == window_sections[sec].m_type) { window_sections[sec - 1].m_end = window_sections[sec].m_end; secs[i]--; continue; } if (section_id >= lpc.MAX_LPC_SECTIONS) throw new IndexOutOfRangeException(); if (alias_set[i, j] != 0 && types[i, j] != SectionType.Zero && section_id < lpc.MAX_LPC_SECTIONS) { for (int i1 = i; i1 < _windowcount; i1++) if (alias[i1, j] == i && secs[i1] > 0) sections[i1 * lpc.MAX_LPC_SECTIONS + secs[i1] - 1].m_id = section_id; section_id++; } // TODO: section_id for glue? nontrivial, must be sure next sections are the same size if (sec > 0 && (window_sections[sec].m_type == SectionType.One || window_sections[sec].m_type == SectionType.OneLarge) && window_sections[sec].m_end - window_sections[sec].m_start >= lpc.MAX_LPC_ORDER && (window_sections[sec - 1].m_type == SectionType.One || window_sections[sec - 1].m_type == SectionType.OneLarge) && window_sections[sec - 1].m_end - window_sections[sec - 1].m_start >= lpc.MAX_LPC_ORDER) { window_sections[sec + 1] = window_sections[sec]; window_sections[sec].m_end = window_sections[sec].m_start; window_sections[sec].m_type = SectionType.OneGlue; window_sections[sec].m_id = -1; secs[i]++; continue; } if (sec > 0 && window_sections[sec].m_type != SectionType.Zero && window_sections[sec - 1].m_type != SectionType.Zero) { window_sections[sec + 1] = window_sections[sec]; window_sections[sec].m_end = window_sections[sec].m_start; window_sections[sec].m_type = SectionType.Glue; window_sections[sec].m_id = -1; secs[i]++; continue; } } } for (int i = 0; i < _windowcount; i++) { for (int s = 0; s < secs[i]; s++) { LpcWindowSection* window_sections = sections + i * lpc.MAX_LPC_SECTIONS; if (window_sections[s].m_type == SectionType.Glue || window_sections[s].m_type == SectionType.OneGlue) { window_sections[s].m_end = window_sections[s + 1].m_end; } } while (secs[i] < lpc.MAX_LPC_SECTIONS) { LpcWindowSection* window_sections = sections + i * lpc.MAX_LPC_SECTIONS; window_sections[secs[i]++].setZero(sz, sz); } } } } /// /// Context for LPC coefficients calculation and order estimation /// unsafe public class LpcContext { public LpcContext() { coefs = new int[lpc.MAX_LPC_ORDER]; reflection_coeffs = new double[lpc.MAX_LPC_ORDER]; prediction_error = new double[lpc.MAX_LPC_ORDER]; autocorr_values = new double[lpc.MAX_LPC_ORDER + 1]; best_orders = new int[lpc.MAX_LPC_ORDER]; done_lpcs = new uint[lpc.MAX_LPC_PRECISIONS]; } /// /// Reset to initial (blank) state /// public void Reset() { autocorr_order = 0; for (int iPrecision = 0; iPrecision < lpc.MAX_LPC_PRECISIONS; iPrecision++) done_lpcs[iPrecision] = 0; } /// /// Calculate autocorrelation data and reflection coefficients. /// Can be used to incrementaly compute coefficients for higher orders, /// because it caches them. /// /// Maximum order /// Samples pointer /// Block size /// Window function public void GetReflection(LpcSubframeInfo subframe, int order, int blocksize, int* samples, float* window, LpcWindowSection* sections) { if (autocorr_order > order) return; fixed (double* reff = reflection_coeffs, autoc = autocorr_values, err = prediction_error) { for (int i = autocorr_order; i <= order; i++) autoc[i] = 0; for (int section = 0; section < lpc.MAX_LPC_SECTIONS; section++) { if (sections[section].m_type == LpcWindowSection.SectionType.Zero) { continue; } if (sections[section].m_id >= 0) { if (subframe.autocorr_section_orders[sections[section].m_id] <= order) { fixed (double* autocsec = &subframe.autocorr_section_values[sections[section].m_id, 0]) { int min_order = subframe.autocorr_section_orders[sections[section].m_id]; for (int i = min_order; i <= order; i++) autocsec[i] = 0; sections[section].compute_autocorr(samples, window, min_order, order, blocksize, autocsec); } subframe.autocorr_section_orders[sections[section].m_id] = order + 1; } for (int i = autocorr_order; i <= order; i++) autoc[i] += subframe.autocorr_section_values[sections[section].m_id, i]; } else { sections[section].compute_autocorr(samples, window, autocorr_order, order, blocksize, autoc); } } lpc.compute_schur_reflection(autoc, (uint)order, reff, err); autocorr_order = order + 1; } } #if XXX public void GetReflection1(int order, int* samples, int blocksize, float* window) { if (autocorr_order > order) return; fixed (double* reff = reflection_coeffs, autoc = autocorr_values, err = prediction_error) { lpc.compute_autocorr(samples, blocksize, 0, order + 1, autoc, window); for (int i = 1; i <= order; i++) autoc[i] = autoc[i + 1]; lpc.compute_schur_reflection(autoc, (uint)order, reff, err); autocorr_order = order + 1; } } public void ComputeReflection(int order, float* autocorr) { fixed (double* reff = reflection_coeffs, autoc = autocorr_values, err = prediction_error) { for (int i = 0; i <= order; i++) autoc[i] = autocorr[i]; lpc.compute_schur_reflection(autoc, (uint)order, reff, err); autocorr_order = order + 1; } } public void ComputeReflection(int order, double* autocorr) { fixed (double* reff = reflection_coeffs, autoc = autocorr_values, err = prediction_error) { for (int i = 0; i <= order; i++) autoc[i] = autocorr[i]; lpc.compute_schur_reflection(autoc, (uint)order, reff, err); autocorr_order = order + 1; } } #endif public double Akaike(int blocksize, int order, double alpha, double beta) { //return (blocksize - order) * (Math.Log(prediction_error[order - 1]) - Math.Log(1.0)) + Math.Log(blocksize) * order * (alpha + beta * order); //return blocksize * (Math.Log(prediction_error[order - 1]) - Math.Log(autocorr_values[0]) / 2) + Math.Log(blocksize) * order * (alpha + beta * order); return blocksize * (Math.Log(prediction_error[order - 1])) + Math.Log(blocksize) * order * (alpha + beta * order); } /// /// Sorts orders based on Akaike's criteria /// /// Frame size public void SortOrdersAkaike(int blocksize, int count, int min_order, int max_order, double alpha, double beta) { for (int i = min_order; i <= max_order; i++) best_orders[i - min_order] = i; int lim = max_order - min_order + 1; for (int i = 0; i < lim && i < count; i++) { for (int j = i + 1; j < lim; j++) { if (Akaike(blocksize, best_orders[j], alpha, beta) < Akaike(blocksize, best_orders[i], alpha, beta)) { int tmp = best_orders[j]; best_orders[j] = best_orders[i]; best_orders[i] = tmp; } } } } /// /// Produces LPC coefficients from autocorrelation data. /// /// LPC coefficients buffer (for all orders) public void ComputeLPC(float* lpcs) { fixed (double* reff = reflection_coeffs) lpc.compute_lpc_coefs((uint)autocorr_order - 1, reff, lpcs); } public double[] autocorr_values; double[] reflection_coeffs; public double[] prediction_error; public int[] best_orders; public int[] coefs; int autocorr_order; public int shift; public double[] Reflection { get { return reflection_coeffs; } } public uint[] done_lpcs; } }