using System; using System.Text; namespace CUETools.Codecs.LossyWAV { public class LossyWAVWriter : IAudioDest { #region Private fields public const string version_string = "1.1.1#"; private IAudioDest _audioDest, _lwcdfDest; private Codecs.WAV.EncoderSettings m_settings; private AudioBuffer _audioBuffer; private short[] fft_bit_length; private float[] frequency_limits; private int[] fill_fft_lookup_block; private int[] fill_fft_lookup_offset; private fft_results_rec[,] saved_fft_results; private float[] window_function; private int clipped_samples; private int this_max_sample, this_min_sample; private double[] shaping_a; private double[] shaping_b; private double scaling_factor; private double shaping_factor; private short static_maximum_bits_to_remove; private analysis_rec[] analysis_recs; private channel_rec[] channel_recs; private int blocks_processed, overall_bits_removed, overall_bits_lost; private int[][,] rotating_blocks_ptr; private int[,] btrd_codec_block, corr_codec_block; private int last_codec_block_size; private int prev_codec_block_size; private int this_codec_block_size; private int next_codec_block_size; private int[,] sampleBuffer; private int samplesInBuffer, samplesWritten; private int fft_minimum = 0, fft_average = 0; private double[] fft_array; private double[] fft_result; private double[] fft_a; private int[] reversedbits; private string[] fft_analysis_string; private double[] fifo; private bool initialized = false; // settings private int this_analysis_number; private bool impulse; private bool linkchannels; private bool shaping_is_on; private double snr_value; private double noise_threshold_shift; private double dynamic_minimum_bits_to_keep; private int maximum_clips_per_channel; private float frequency_limit = 16000F; #endregion #region Properties public string Path { get { return _audioDest.Path; } } public long FinalSampleCount { set { if (_audioDest != null) _audioDest.FinalSampleCount = value; if (_lwcdfDest != null) _lwcdfDest.FinalSampleCount = value; } } public IAudioEncoderSettings Settings => m_settings; public int OverallBitsRemoved { get { return overall_bits_removed; } } public int BlocksProcessed { get { return blocks_processed; } } public int SamplesProcessed { get { return (blocks_processed - 1) * codec_block_size + this_codec_block_size; } } public int Analysis { get { return this_analysis_number; } set { this_analysis_number = value; } } public bool Impulse { get { return impulse; } set { impulse = value; } } public bool LinkChannels { get { return linkchannels; } set { throw new Exception("Unsupported"); //linkchannels = value; } } public double ScalingFactor { get { return scaling_factor; } set { if (scaling_factor <= 0 || scaling_factor > 8) throw new Exception("Invalid scaling_factor"); scaling_factor = value; } } public double ShapingFactor { get { return shaping_factor; } set { if (shaping_factor < 0 || shaping_factor > 1) throw new Exception("Invalid shaping_factor"); shaping_factor = value; shaping_is_on = shaping_factor > 0; } } public float FrequencyLimit { get { return frequency_limit; } set { if (frequency_limit <= 14470.3125F || frequency_limit > 48000F) throw new Exception("Invalid frequency_limit"); frequency_limit = value; } } #endregion #region Constructor public LossyWAVWriter(IAudioDest audioDest, IAudioDest lwcdfDest, double quality, Codecs.WAV.EncoderSettings settings) { _audioDest = audioDest; _lwcdfDest = lwcdfDest; m_settings = settings; if (_audioDest != null && _audioDest.Settings.PCM.BitsPerSample > Settings.PCM.BitsPerSample) throw new Exception("audio parameters mismatch"); if (_lwcdfDest != null && _lwcdfDest.Settings.PCM.BitsPerSample != Settings.PCM.BitsPerSample) throw new Exception("audio parameters mismatch"); int quality_integer = (int)Math.Floor(quality); fft_analysis_string = new string[4] { "0100010", "0110010", "0111010", "0111110" }; bool[] quality_auto_fft32_on = { false, false, false, true, true, true, true, true, true, true, true }; double[] quality_noise_threshold_shifts = { 20, 16, 9, 6, 3, 0, -2.4, -4.8, -7.2, -9.6, -12 }; double[] quality_signal_to_noise_ratio = { -18, -22, -23.5, -23.5, -23.5, -25, -28, -31, -34, -37, -40 }; double[] quality_dynamic_minimum_bits_to_keep = { 2.5, 2.75, 3.00, 3.25, 3.50, 3.75, 4.0, 4.25, 4.5, 4.75, 5.00 }; double[] quality_maximum_clips_per_channel = { 3, 3, 3, 3, 2, 1, 0, 0, 0, 0, 0 }; this_analysis_number = 2; impulse = quality_auto_fft32_on[quality_integer]; linkchannels = false; noise_threshold_shift = Math.Round(interpolate_param(quality_noise_threshold_shifts, quality) * 1000) / 1000; snr_value = Math.Round(interpolate_param(quality_signal_to_noise_ratio, quality) * 1000) / 1000; dynamic_minimum_bits_to_keep = Math.Round(interpolate_param(quality_dynamic_minimum_bits_to_keep, quality) * 1000) / 1000; maximum_clips_per_channel = (int)Math.Round(interpolate_param(quality_maximum_clips_per_channel, quality)); scaling_factor = 1.0; shaping_factor = Math.Min(1, quality / 10); shaping_is_on = shaping_factor > 0; _audioBuffer = new AudioBuffer(Settings.PCM, 256); } #endregion #region Public Methods public void Close() { if (next_codec_block_size > 0) { shift_codec_blocks(); if (samplesInBuffer > 0) Array.Copy(sampleBuffer, 0, rotating_blocks_ptr[3], 0, samplesInBuffer * Settings.PCM.ChannelCount); next_codec_block_size = samplesInBuffer; process_this_codec_block(); if (next_codec_block_size > 0) { samplesWritten += next_codec_block_size; shift_codec_blocks(); process_this_codec_block(); } } if (_lwcdfDest != null) try { _lwcdfDest.Close(); } catch { } if (_audioDest != null) _audioDest.Close(); } public void Write(AudioBuffer buff) { if (!initialized) Initialize(); buff.Prepare(this); int pos = 0; int sampleCount = buff.Length; while (sampleCount + samplesInBuffer > codec_block_size) { shift_codec_blocks(); // next_codec_block_size is now zero if (samplesInBuffer > 0) Array.Copy(sampleBuffer, 0, rotating_blocks_ptr[3], 0, samplesInBuffer * Settings.PCM.ChannelCount); Array.Copy(buff.Samples, pos * Settings.PCM.ChannelCount, rotating_blocks_ptr[3], samplesInBuffer * Settings.PCM.ChannelCount, (codec_block_size - samplesInBuffer) * Settings.PCM.ChannelCount); next_codec_block_size = codec_block_size; pos += codec_block_size - samplesInBuffer; sampleCount -= codec_block_size - samplesInBuffer; samplesInBuffer = 0; if (samplesWritten > 0) process_this_codec_block(); samplesWritten += next_codec_block_size; } if (sampleCount > 0) { Array.Copy(buff.Samples, pos * Settings.PCM.ChannelCount, sampleBuffer, samplesInBuffer * Settings.PCM.ChannelCount, sampleCount * Settings.PCM.ChannelCount); samplesInBuffer += sampleCount; } } public void Delete() { if (_audioDest != null) _audioDest.Delete(); if (_lwcdfDest != null) _lwcdfDest.Delete(); } #endregion #region Private Methods static double fastlog2(double x) { return Math.Log(x, 2); } static double fastsqr(double x) { return x * x; } static double interpolate_param(double[] param, double quality) { if (quality >= quality_presets) return param[quality_presets]; int quality_integer = (int)Math.Floor(quality); double quality_fraction = quality - quality_integer; return quality_fraction * (param[quality_integer + 1] - param[quality_integer]) + param[quality_integer]; } //const double LN2 = 0.69314718055994530941; const double lg2x20 = 6.020599913279623904274778; const int precalc_analyses = 6; const int spread_freqs = 8; const int codec_block_size = 512; const int maxblocksize = 4096; const int maxchannels = 8; const int MaxFFTBitLength = 13; const int shaping_length = 4; const short static_minimum_bits_to_keep = 6; const double SP = 1.0; const double SW = 0.7071067811865475; const int skewing_amplitude = 36; const int threshold_index_spread = 64; const int threshold_index_spread_max = threshold_index_spread * 256; const int quality_presets = 10; void Initialize() { fft_bit_length = new short[precalc_analyses] { 5, 6, 7, 8, 9, 10 }; frequency_limits = new float[spread_freqs + 1] { 20F, 1378.125F, 3445.3125F, 5512.5F, 8268.75F, 10335.9375F, 12403.125F, 14470.3125F, 16000F };//537.5F); frequency_limits[spread_freqs] = frequency_limit; float[,] reference_threshold = { { 0.855049F,1.647705F,2.586367F,3.570348F,4.566311F,5.565302F, 6.565059F, 7.565002F, 8.564982F, 9.564947F,10.564983F,11.564966F,12.564990F,13.564963F,14.564963F,15.564953F,16.564938F,17.564932F,18.564946F,19.564977F,20.564993F,21.564943F,22.564971F,23.564972F,24.564971F,25.564942F,26.564973F,27.564952F,28.564934F,29.564964F,30.565000F,31.564957F}, { 1.365595F,2.158121F,3.096140F,4.079761F,5.075650F,6.074656F, 7.074348F, 8.074301F, 9.074286F,10.074265F,11.074260F,12.074282F,13.074297F,14.074298F,15.074254F,16.074310F,17.074259F,18.074278F,19.074298F,20.074277F,21.074247F,22.074304F,23.074269F,24.074309F,25.074261F,26.074301F,27.074316F,28.074259F,29.074281F,30.074272F,31.074257F,32.074265F}, { 1.870837F,2.663372F,3.600974F,4.584524F,5.580366F,6.579303F, 7.579018F, 8.578990F, 9.578999F,10.578921F,11.578967F,12.578961F,13.578965F,14.578950F,15.578952F,16.578964F,17.578957F,18.578957F,19.578943F,20.578949F,21.578953F,22.578919F,23.578966F,24.578940F,25.578934F,26.578952F,27.578921F,28.578943F,29.578934F,30.578939F,31.578957F,32.578939F}, { 2.373478F,3.165937F,4.103403F,5.086839F,6.082678F,7.081625F, 8.081372F, 9.081305F,10.081304F,11.081288F,12.081279F,13.081282F,14.081269F,15.081277F,16.081299F,17.081315F,18.081269F,19.081260F,20.081285F,21.081299F,22.081287F,23.081292F,24.081301F,25.081300F,26.081281F,27.081274F,28.081268F,29.081285F,30.081311F,31.081289F,32.081297F,33.081284F}, { 2.874786F,3.667300F,4.604600F,5.588036F,6.583839F,7.582784F, 8.582545F, 9.582496F,10.582470F,11.582446F,12.582467F,13.582453F,14.582474F,15.582478F,16.582438F,17.582437F,18.582458F,19.582484F,20.582439F,21.582456F,22.582444F,23.582433F,24.582435F,25.582454F,26.582425F,27.582443F,28.582443F,29.582451F,30.582450F,31.582509F,32.582402F,33.582447F}, { 3.375442F,4.167948F,5.105193F,6.088653F,7.084443F,8.083383F, 9.083136F,10.083083F,11.083088F,12.083042F,13.083045F,14.083021F,15.083045F,16.083047F,17.083032F,18.083045F,19.083051F,20.083031F,21.083038F,22.083041F,23.083036F,24.083033F,25.083059F,26.083044F,27.083043F,28.083031F,29.083039F,30.083038F,31.083027F,32.083062F,33.083057F,34.083017F}, { 3.875760F,4.668262F,5.605555F,6.588945F,7.584770F,8.583679F, 9.583467F,10.583333F,11.583352F,12.583357F,13.583353F,14.583342F,15.583322F,16.583328F,17.583336F,18.583304F,19.583327F,20.583344F,21.583317F,22.583326F,23.583307F,24.583321F,25.583300F,26.583365F,27.583308F,28.583330F,29.583333F,30.583336F,31.583339F,32.583320F,33.583304F,34.583342F}, { 4.375942F,5.168419F,6.105638F,7.089092F,8.084885F,9.083830F,10.083552F,11.083465F,12.083491F,13.083485F,14.083446F,15.083493F,16.083486F,17.083496F,18.083485F,19.083490F,20.083477F,21.083474F,22.083482F,23.083483F,24.083463F,25.083500F,26.083483F,27.083467F,28.083480F,29.083465F,30.083478F,31.083495F,32.083484F,33.083481F,34.083507F,35.083478F}, { 4.876022F,5.668487F,6.605748F,7.589148F,8.584943F,9.583902F,10.583678F,11.583589F,12.583571F,13.583568F,14.583551F,15.583574F,16.583541F,17.583544F,18.583555F,19.583554F,20.583574F,21.583577F,22.583531F,23.583560F,24.583548F,25.583570F,26.583561F,27.583581F,28.583552F,29.583540F,30.583564F,31.583530F,32.583546F,33.583585F,34.583557F,35.583567F} }; if (Settings.PCM.SampleRate > 46050) { shaping_a = /* order_4_48000_a */ new double[4] { +0.90300, +0.01160, -0.58530, -0.25710 }; shaping_b = /* order_4_48000_b */ new double[4] { -2.23740, +0.73390, +0.12510, +0.60330 }; } else { shaping_a = /* order_4_44100_a */ new double[4] { +1.05870, +0.06760, -0.60540, -0.27380 }; shaping_b = /* order_4_44100_b */ new double[4] { -2.20610, +0.47070, +0.25340, +0.62130 }; } fifo = new double[shaping_length + maxblocksize]; window_function = new float[1 << (MaxFFTBitLength + 1)]; short bits_in_block_size = (short)Math.Floor(fastlog2(codec_block_size)); for (int i = 0; i < precalc_analyses; i++) fft_bit_length[i] += (short)(bits_in_block_size - 9); if (frequency_limits[spread_freqs] > Settings.PCM.SampleRate / 2) frequency_limits[spread_freqs] = Settings.PCM.SampleRate / 2; fill_fft_lookup_block = new int[maxblocksize * 4]; fill_fft_lookup_offset = new int[maxblocksize * 4]; for (int i = 0; i < codec_block_size * 4; i++) { fill_fft_lookup_block[i] = i / codec_block_size; fill_fft_lookup_offset[i] = i % codec_block_size; } saved_fft_results = new fft_results_rec[maxchannels, precalc_analyses]; for (int i = 0; i < maxchannels; i++) for (int j = 0; j < precalc_analyses; j++) saved_fft_results[i, j].start = -1; clipped_samples = 0; this_max_sample = (1 << (Settings.PCM.BitsPerSample - 1)) - 1; this_min_sample = 0 - (1 << (Settings.PCM.BitsPerSample - 1)); for (int this_fft_bit_length = 1; this_fft_bit_length <= MaxFFTBitLength; this_fft_bit_length++) { int this_fft_length = 1 << this_fft_bit_length; // Generate window_function lookup table for each fft_length for (int i = 0; i < this_fft_length; i++) window_function[this_fft_length + i] = (float) (0.5 * (1 - Math.Cos(((i + 0.5) * 2 * Math.PI / this_fft_length))) * scaling_factor); } double sf = 1.0; for (int i = 0; i < shaping_length; i++) { sf *= shaping_factor; shaping_a[i] *= sf; shaping_b[i] *= sf; } static_maximum_bits_to_remove = (short)(Settings.PCM.BitsPerSample - static_minimum_bits_to_keep); double lfb = Math.Log10(frequency_limits[0]); // 20Hz lower limit for skewing; double mfb = Math.Log10(frequency_limits[2]); // 3445.3125Hz upper limit for skewing; double dfb = mfb - lfb; // skewing range; fft_a = new double[2 << MaxFFTBitLength]; for (int i = 0; i < 1 << MaxFFTBitLength; i++) { fft_a[2 * i] = Math.Cos(-i * Math.PI / (1 << MaxFFTBitLength)); fft_a[2 * i + 1] = Math.Sin(-i * Math.PI / (1 << MaxFFTBitLength)); } reversedbits = new int[1 << MaxFFTBitLength]; reversedbits[0] = 0; int rb1 = 1 << (MaxFFTBitLength - 1); reversedbits[1] = rb1; for (int i = 1; i < 1 << (MaxFFTBitLength - 1); i++) { int rb2n = reversedbits[i] >> 1; reversedbits[i << 1] = rb2n; reversedbits[(i << 1) + 1] = rb2n | rb1; } fft_array = new double[1 << (MaxFFTBitLength + 1)]; fft_result = new double[1 << MaxFFTBitLength]; rotating_blocks_ptr = new int[4][,]; sampleBuffer = new int[codec_block_size, Settings.PCM.ChannelCount]; channel_recs = new channel_rec[Settings.PCM.ChannelCount]; analysis_recs = new analysis_rec[precalc_analyses]; for (int analysis_number = 0; analysis_number < precalc_analyses; analysis_number++) { int this_fft_bit_length = fft_bit_length[analysis_number]; //analysis_rec analyzis = analysis_recs[analysis_number]; analysis_recs[analysis_number].spreading_averages_int = new int[1 << (MaxFFTBitLength)]; analysis_recs[analysis_number].spreading_averages_rem = new float[1 << (MaxFFTBitLength)]; analysis_recs[analysis_number].spreading_averages_rec = new float[1 << (MaxFFTBitLength)]; analysis_recs[analysis_number].skewing_function = new float[1 << (MaxFFTBitLength)]; analysis_recs[analysis_number].threshold_index = new byte[threshold_index_spread_max]; analysis_recs[analysis_number].end_overlap_length = Math.Min(1 << (this_fft_bit_length - 1), codec_block_size); analysis_recs[analysis_number].actual_analysis_blocks_start = -analysis_recs[analysis_number].end_overlap_length; int total_overlap_length = codec_block_size + analysis_recs[analysis_number].end_overlap_length * 2 - (1 << this_fft_bit_length); analysis_recs[analysis_number].fft_underlap_length = 1 << (this_fft_bit_length - 1); analysis_recs[analysis_number].analysis_blocks = total_overlap_length >> (this_fft_bit_length - 1); //(int) Math.Max(0, Math.Floor(total_overlap_length / analysis_recs[analysis_number].fft_underlap_length)); analysis_recs[analysis_number].fft_underlap_length = total_overlap_length * 1.0 / Math.Max(1, analysis_recs[analysis_number].analysis_blocks); // Calculate actual analysis_time values for fft_lengths analysis_recs[analysis_number].bin_width = Settings.PCM.SampleRate * 1.0 / (1 << this_fft_bit_length); analysis_recs[analysis_number].bin_time = 1.0 / analysis_recs[analysis_number].bin_width; // Calculate which FFT bin corresponds to the low frequency limit analysis_recs[analysis_number].lo_bins = (int)Math.Max(1, Math.Round(frequency_limits[0] * analysis_recs[analysis_number].bin_time) - 1); analysis_recs[analysis_number].hi_bins = (int)Math.Max(1, Math.Round(frequency_limits[spread_freqs] * analysis_recs[analysis_number].bin_time) - 1); if (analysis_recs[analysis_number].hi_bins > (1 << (this_fft_bit_length - 1)) - 1) throw new Exception("frequency too high"); double f_lfb = analysis_recs[analysis_number].lo_bins * analysis_recs[analysis_number].bin_width; double f_hfb = analysis_recs[analysis_number].hi_bins * analysis_recs[analysis_number].bin_width; double f_dfb = f_hfb - f_lfb; for (int i = analysis_recs[analysis_number].lo_bins; i <= analysis_recs[analysis_number].hi_bins; i++) { double f_tfb = i * analysis_recs[analysis_number].bin_width; double sp = 1 + Math.Pow(Math.Min(f_dfb, f_tfb - f_lfb) / f_dfb, SP) * SW; int sp_int = (int)Math.Floor(sp - 1); double sp_rem = Math.Max(0, sp - sp_int - (1 - (sp_int & 1))) / 2.0; analysis_recs[analysis_number].spreading_averages_int[i] = sp_int; analysis_recs[analysis_number].spreading_averages_rem[i] = (float)sp_rem; analysis_recs[analysis_number].spreading_averages_rec[i] = (float)(1 / sp); } analysis_recs[analysis_number].max_bins = analysis_recs[analysis_number].hi_bins - (analysis_recs[analysis_number].spreading_averages_int[analysis_recs[analysis_number].hi_bins] & 0x7FFFFFFE); analysis_recs[analysis_number].num_bins = analysis_recs[analysis_number].max_bins - analysis_recs[analysis_number].lo_bins + 1; analysis_recs[analysis_number].skewing_function[0] = 0F; for (int i = 1; i <= analysis_recs[analysis_number].hi_bins + 1; i++) { double sa_tfb = Math.Log10(i * analysis_recs[analysis_number].bin_width); if (sa_tfb < mfb) analysis_recs[analysis_number].skewing_function[i] = (float)Math.Pow(10, (Math.Pow(Math.Sin(Math.PI / 2 * Math.Max(0, sa_tfb - lfb) / dfb), 0.75) - 1) * skewing_amplitude / 20); else analysis_recs[analysis_number].skewing_function[i] = 1F; } int last_filled = 0; for (byte sa_bit = 0; sa_bit < 32; sa_bit++) { double this_reference_threshold = (reference_threshold[fft_bit_length[analysis_number] - 5, sa_bit]) * threshold_index_spread * lg2x20; while (last_filled < this_reference_threshold) analysis_recs[analysis_number].threshold_index[last_filled++] = sa_bit; } while (last_filled < threshold_index_spread_max) analysis_recs[analysis_number].threshold_index[last_filled++] = 32; // ?? 31? } // calculating for each analysis_number for (int i = 0; i < 4; i++) rotating_blocks_ptr[i] = new int[codec_block_size, Settings.PCM.ChannelCount]; btrd_codec_block = new int[codec_block_size, Settings.PCM.ChannelCount]; corr_codec_block = new int[codec_block_size, Settings.PCM.ChannelCount]; blocks_processed = 0; overall_bits_removed = 0; overall_bits_lost = 0; initialized = true; const uint fccFact = 0x74636166; string datestamp = DateTime.Now.ToString(); string parameter_string = "--standard "; // !!!!! string factString = "lossyWAV " + version_string + " @ " + datestamp + ", " + parameter_string + "\r\n\0"; if (_audioDest != null && _audioDest is WAV.AudioEncoder) ((WAV.AudioEncoder)_audioDest).WriteChunk(fccFact, new ASCIIEncoding().GetBytes(factString)); if (_lwcdfDest != null && _lwcdfDest is WAV.AudioEncoder) ((WAV.AudioEncoder)_lwcdfDest).WriteChunk(fccFact, new ASCIIEncoding().GetBytes(factString)); if (_audioDest != null) _audioDest.Settings.BlockSize = codec_block_size; if (_lwcdfDest != null) _lwcdfDest.Settings.BlockSize = codec_block_size * 2; } double fill_fft_input(int actual_analysis_block_start, int this_fft_length, int channel) { double this_fft_fill_rms = 0; int ff_n = 2 * codec_block_size + actual_analysis_block_start; for (int ff_i = 0; ff_i < this_fft_length; ff_i++) { int ff_j = ff_i + ff_n; double ff_m = rotating_blocks_ptr[fill_fft_lookup_block[ff_j]][fill_fft_lookup_offset[ff_j], channel]; fft_array[ff_i] = ff_m * window_function[this_fft_length + ff_i]; this_fft_fill_rms += ff_m * ff_m; } return Math.Sqrt(this_fft_fill_rms / this_fft_length); } double spread_complex(ref fft_results_rec this_fft_result, int analysis_number) { double sc_y = 0.0, sc_x; int sc_i = analysis_recs[analysis_number].lo_bins - 1; fft_result[sc_i] = Math.Sqrt(fastsqr(fft_array[sc_i * 2]) + fastsqr(fft_array[sc_i * 2 + 1])) * analysis_recs[analysis_number].skewing_function[sc_i]; for (sc_i = analysis_recs[analysis_number].lo_bins; sc_i <= analysis_recs[analysis_number].hi_bins; sc_i++) { sc_x = Math.Sqrt(fastsqr(fft_array[sc_i * 2]) + fastsqr(fft_array[sc_i * 2 + 1])) * analysis_recs[analysis_number].skewing_function[sc_i]; sc_y += sc_x; fft_result[sc_i] = sc_x; } sc_i = analysis_recs[analysis_number].hi_bins + 1; sc_x = Math.Sqrt(fastsqr(fft_array[sc_i * 2]) + fastsqr(fft_array[sc_i * 2 + 1])) * analysis_recs[analysis_number].skewing_function[sc_i]; //sc_y += sc_x; fft_result[sc_i] = sc_x; double snr_value_exp = Math.Pow(2, snr_value / lg2x20); double noise_threshold_shift_exp = Math.Pow(2, noise_threshold_shift / lg2x20); this_fft_result.savebin = (float)(sc_y / analysis_recs[analysis_number].num_bins * snr_value_exp); sc_y = 5623413251903.490803949510; // MaxDb { 10^(255/20) } for (sc_i = analysis_recs[analysis_number].lo_bins; sc_i <= analysis_recs[analysis_number].max_bins; sc_i++) { double sc_z = ((fft_result[sc_i - 1] + fft_result[sc_i + 1]) * analysis_recs[analysis_number].spreading_averages_rem[sc_i] + fft_result[sc_i]) * analysis_recs[analysis_number].spreading_averages_rec[sc_i]; sc_y = Math.Min(sc_y, sc_z); } this_fft_result.sminbin = (float)(sc_y * noise_threshold_shift_exp); return lg2x20 * fastlog2(Math.Min(this_fft_result.savebin, this_fft_result.sminbin)); } void remove_bits(int channel, short bits_to_remove_from_this_channel) { short min_bits_to_remove = 0; if (_audioDest != null && _audioDest.Settings.PCM.BitsPerSample < Settings.PCM.BitsPerSample) min_bits_to_remove = (short)(Settings.PCM.BitsPerSample - _audioDest.Settings.PCM.BitsPerSample); if (bits_to_remove_from_this_channel < min_bits_to_remove) bits_to_remove_from_this_channel = min_bits_to_remove; channel_recs[channel].bits_to_remove = bits_to_remove_from_this_channel; channel_recs[channel].bits_lost = 0; channel_recs[channel].clipped_samples = 0; while (bits_to_remove_from_this_channel >= 0) { short this_channel_clips = 0; int max_sample = (this_max_sample >> bits_to_remove_from_this_channel) << bits_to_remove_from_this_channel; int min_sample = (this_min_sample >> bits_to_remove_from_this_channel) << bits_to_remove_from_this_channel; if (shaping_is_on && bits_to_remove_from_this_channel > 0) { for (int i = 0; i < this_codec_block_size; i++) { int sample = rotating_blocks_ptr[2][i, channel]; double wanted_temp = sample * scaling_factor / (1 << bits_to_remove_from_this_channel) + fifo[i + 3] * shaping_b[0] + fifo[i + 2] * shaping_b[1] + fifo[i + 1] * shaping_b[2] + fifo[i] * shaping_b[3]; int output_temp = (int)Math.Round(wanted_temp); fifo[i + 4] = output_temp - wanted_temp - fifo[i + 3] * shaping_a[0] - fifo[i + 2] * shaping_a[1] - fifo[i + 1] * shaping_a[2] - fifo[i] * shaping_a[3]; int new_sample = output_temp << bits_to_remove_from_this_channel; if (new_sample > max_sample) { new_sample = max_sample; this_channel_clips++; } else if (new_sample < min_sample) { new_sample = min_sample; this_channel_clips++; } btrd_codec_block[i, channel] = new_sample; corr_codec_block[i, channel] = sample - (int)Math.Round(new_sample / scaling_factor); } } else { for (int i = 0; i < this_codec_block_size; i++) { int sample = rotating_blocks_ptr[2][i, channel]; int new_sample = ((int)Math.Round(sample * scaling_factor / (1 << bits_to_remove_from_this_channel))) << bits_to_remove_from_this_channel; if (new_sample > max_sample) { new_sample = max_sample; this_channel_clips++; } else if (new_sample < min_sample) { new_sample = min_sample; this_channel_clips++; } btrd_codec_block[i, channel] = new_sample; corr_codec_block[i, channel] = sample - (int)Math.Round(new_sample / scaling_factor); } } channel_recs[channel].clipped_samples = this_channel_clips; if (this_channel_clips <= maximum_clips_per_channel) break; if (bits_to_remove_from_this_channel <= min_bits_to_remove) break; bits_to_remove_from_this_channel--; channel_recs[channel].bits_lost++; channel_recs[channel].bits_to_remove--; } } void process_this_codec_block() { short codec_block_dependent_bits_to_remove = (short)Settings.PCM.BitsPerSample; double min_codec_block_channel_rms = channel_recs[0].this_codec_block_rms; for (int channel = 0; channel < Settings.PCM.ChannelCount; channel++) min_codec_block_channel_rms = Math.Min(min_codec_block_channel_rms, channel_recs[channel].this_codec_block_rms); for (int channel = 0; channel < Settings.PCM.ChannelCount; channel++) { // if (linkchannels)... channel_recs[channel].this_codec_block_bits = channel_recs[channel].this_codec_block_rms; } for (int channel = 0; channel < Settings.PCM.ChannelCount; channel++) { fft_results_rec min_fft_result; min_fft_result.savebin = 999; min_fft_result.sminbin = 999; channel_recs[channel].maximum_bits_to_remove = Math.Max((short)0, (short)Math.Floor(channel_recs[channel].this_codec_block_bits - dynamic_minimum_bits_to_keep)); channel_recs[channel].maximum_bits_to_remove = Math.Min(channel_recs[channel].maximum_bits_to_remove, static_maximum_bits_to_remove); if (channel_recs[channel].this_codec_block_bits == 0) { min_fft_result.start = -9999; min_fft_result.btr = static_maximum_bits_to_remove; min_fft_result.savebin = 0; min_fft_result.nminbin = 0; // ?? sminbin? min_fft_result.sminbin = -1; // ?? nminbin? min_fft_result.analysis = -1; for (int analysis_number = 0; analysis_number < precalc_analyses; analysis_number++) saved_fft_results[channel, analysis_number] = min_fft_result; min_fft_result.btr = 0; } else { fft_results_rec this_fft_result; this_fft_result.savebin = 0; // initializing structure to keep compiler happy. this_fft_result.nminbin = 0; // No idea why it wasn't initialized in delphi code, this_fft_result.sminbin = -1; // and no idea if i initialized it right!!! this_fft_result.analysis = -1; this_fft_result.start = -9999; this_fft_result.btr = channel_recs[channel].maximum_bits_to_remove; min_fft_result.btr = this_fft_result.btr; for (short analysis_number = 0; analysis_number < precalc_analyses; analysis_number++) { if (((analysis_number == 0 && impulse) || fft_analysis_string[this_analysis_number - 2][analysis_number] == '1') && (1 << fft_bit_length[analysis_number] <= codec_block_size * 2)) { short this_fft_bit_length = fft_bit_length[analysis_number]; int this_fft_length = 1 << this_fft_bit_length; this_fft_result.analysis = analysis_number; this_fft_result.nminbin = -1; int neg_codec_block_start = -(last_codec_block_size + prev_codec_block_size); int pos_codec_block_start = this_codec_block_size + next_codec_block_size - this_fft_length; int this_actual_analysis_blocks_start = analysis_recs[analysis_number].actual_analysis_blocks_start; double this_fft_underlap_length = analysis_recs[analysis_number].fft_underlap_length; for (int analysis_block_number = 0; analysis_block_number <= analysis_recs[analysis_number].analysis_blocks; analysis_block_number++) { int actual_analysis_block_start = (int)Math.Floor(this_actual_analysis_blocks_start + analysis_block_number * this_fft_underlap_length); actual_analysis_block_start = Math.Max(actual_analysis_block_start, neg_codec_block_start); actual_analysis_block_start = Math.Min(actual_analysis_block_start, pos_codec_block_start); if (analysis_block_number == 0) { if (actual_analysis_block_start == saved_fft_results[channel, analysis_number].start) { this_fft_result = saved_fft_results[channel, analysis_number]; if (this_fft_result.btr < min_fft_result.btr || min_fft_result.btr == -1) min_fft_result = this_fft_result; } } else { if (fill_fft_input(actual_analysis_block_start, this_fft_length, channel) > 0) { FFT_DReal(this_fft_bit_length - 1); double spread = spread_complex(ref this_fft_result, analysis_number); this_fft_result.btr = analysis_recs[analysis_number].threshold_index[(int)Math.Floor(Math.Max(0, spread) * threshold_index_spread)]; if (this_fft_result.btr < min_fft_result.btr || min_fft_result.btr == -1) min_fft_result = this_fft_result; } if (analysis_block_number == analysis_recs[analysis_number].analysis_blocks) { this_fft_result.start = (short)(actual_analysis_block_start - codec_block_size); saved_fft_results[channel, analysis_number] = this_fft_result; } } } } } if (min_fft_result.sminbin > min_fft_result.savebin) fft_average = fft_average + 1; else fft_minimum = fft_minimum + 1; } min_fft_result.btr = Math.Max(min_fft_result.btr, (short)0); remove_bits(channel, min_fft_result.btr); codec_block_dependent_bits_to_remove = Math.Min(codec_block_dependent_bits_to_remove, channel_recs[channel].bits_to_remove); } for (int channel = 0; channel < Settings.PCM.ChannelCount; channel++) { // if (linkchannels) overall_bits_removed += channel_recs[channel].bits_to_remove; overall_bits_lost += channel_recs[channel].bits_lost; clipped_samples += channel_recs[channel].clipped_samples; } if (_audioDest != null) { if (_audioDest.Settings.PCM.BitsPerSample < Settings.PCM.BitsPerSample) { int sh = Settings.PCM.BitsPerSample - _audioDest.Settings.PCM.BitsPerSample; for (int i = 0; i < this_codec_block_size; i++) for (int c = 0; c < Settings.PCM.ChannelCount; c++) btrd_codec_block[i, c] >>= sh; } _audioBuffer.Prepare(btrd_codec_block, this_codec_block_size); _audioDest.Write(_audioBuffer); } if (_lwcdfDest != null) { _audioBuffer.Prepare(corr_codec_block, this_codec_block_size); _lwcdfDest.Write(_audioBuffer); } } void shift_codec_blocks() { int[,] sc_p = rotating_blocks_ptr[0]; rotating_blocks_ptr[0] = rotating_blocks_ptr[1]; rotating_blocks_ptr[1] = rotating_blocks_ptr[2]; rotating_blocks_ptr[2] = rotating_blocks_ptr[3]; rotating_blocks_ptr[3] = sc_p; prev_codec_block_size = last_codec_block_size; last_codec_block_size = this_codec_block_size; this_codec_block_size = next_codec_block_size; next_codec_block_size = 0; if (this_codec_block_size > 0) { blocks_processed++; for (int channel = 0; channel < Settings.PCM.ChannelCount; channel++) { double x = 0; for (int i = 0; i < this_codec_block_size; i++) { double s = rotating_blocks_ptr[2][i, channel]; x += s * s; } channel_recs[channel].this_codec_block_rms = fastlog2(x / this_codec_block_size) / 2; } } } unsafe static void shuffle_in_place_dcomplex(double* fft_ptr, int* reversedbits_ptr, int bits) { for (int i = 0; i < 1 << bits; i++) { int j = reversedbits_ptr[i] >> (MaxFFTBitLength - bits); if (j > i) { double re = fft_ptr[2 * i]; double im = fft_ptr[2 * i + 1]; fft_ptr[2 * i] = fft_ptr[2 * j]; fft_ptr[2 * i + 1] = fft_ptr[2 * j + 1]; fft_ptr[2 * j] = re; fft_ptr[2 * j + 1] = im; } } } unsafe static void FFT_DComplex(double* fft_ptr, double* fft_a_ptr, int* reversedbits_ptr, int bits) { shuffle_in_place_dcomplex(fft_ptr, reversedbits_ptr, bits); for (int blockbitlen = 0; blockbitlen < bits; blockbitlen++) { int blockend = 1 << blockbitlen; int blocksize = 1 << (blockbitlen + 1); int i = 0; do { int j = 2 * i; for (int n = 0; n < blockend; n++) { int k = j + 2 * blockend; int a_index = n << (MaxFFTBitLength - blockbitlen + 1); double ARe = fft_a_ptr[a_index]; double AIm = fft_a_ptr[a_index + 1]; double KRe = fft_ptr[k]; double KIm = fft_ptr[k + 1]; double TRe = ARe * KRe - AIm * KIm; double TIm = ARe * KIm + AIm * KRe; fft_ptr[k] = fft_ptr[j] - TRe; fft_ptr[k + 1] = fft_ptr[j + 1] - TIm; fft_ptr[j] += TRe; fft_ptr[j + 1] += TIm; j += 2; } i += blocksize; } while (i < 1 << bits); } } unsafe void FFT_DReal(int bits) { fixed (double* fft_ptr = fft_array) fixed (double* fft_a_ptr = fft_a) fixed (int* reversedbits_ptr = reversedbits) { FFT_DComplex(fft_ptr, fft_a_ptr, reversedbits_ptr, bits); fft_ptr[(1 << bits) * 2] = fft_ptr[0] - fft_ptr[1]; fft_ptr[(1 << bits) * 2 + 1] = 0; fft_ptr[0] = fft_ptr[0] + fft_ptr[1]; fft_ptr[1] = 0; for (int j = 1; j < 1 << (bits - 1); j++) { int k = (1 << bits) - j; double ARe = fft_a_ptr[j << (MaxFFTBitLength - bits + 1)]; double AIm = fft_a_ptr[(j << (MaxFFTBitLength - bits + 1)) + 1]; double XRe = fft_ptr[j * 2]; double XIm = fft_ptr[j * 2 + 1]; double TRe = ARe * (XIm + fft_ptr[k * 2 + 1]) + AIm * (XRe - fft_ptr[k * 2]); double TIm = ARe * (XRe - fft_ptr[k * 2]) - AIm * (XIm + fft_ptr[k * 2 + 1]); fft_ptr[j * 2] = (XRe + fft_ptr[k * 2] + TRe) / 2; fft_ptr[j * 2 + 1] = (XIm - fft_ptr[k * 2 + 1] - TIm) / 2; fft_ptr[k * 2] = (XRe + fft_ptr[k * 2] - TRe) / 2; fft_ptr[k * 2 + 1] = -(XIm - fft_ptr[k * 2 + 1] + TIm) / 2; } } } #endregion } }