/******************************************************************************
 *
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *****************************************************************************
 * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore
*/
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include "impd_type_def.h"
#include "impd_drc_extr_delta_coded_info.h"
#include "impd_drc_common.h"
#include "impd_drc_struct.h"
#include "impd_parametric_drc_dec.h"
#include "impd_drc_filter_bank.h"
#include "impd_drc_rom.h"

#define PI 3.14159265f

#ifndef max
#define max(a, b) (((a) > (b)) ? (a) : (b))
#endif
#ifndef min
#define min(a, b) (((a) < (b)) ? (a) : (b))
#endif

WORD32 impd_init_parametric_drc(
    WORD32 drc_frame_size, WORD32 sampling_rate, WORD32 sub_band_domain_mode,
    ia_parametric_drc_params_struct* p_parametricdrc_params) {
  WORD32 sub_band_count_tbl[4] = {0, 64, 71, 256};
  p_parametricdrc_params->drc_frame_size = drc_frame_size;
  p_parametricdrc_params->sampling_rate = sampling_rate;
  p_parametricdrc_params->sub_band_domain_mode = sub_band_domain_mode;

  p_parametricdrc_params->sub_band_count =
      sub_band_count_tbl[sub_band_domain_mode];

  return 0;
}

WORD32 impd_init_parametric_drc_feed_fwd(
    ia_drc_config* pstr_drc_config, WORD32 instance_idx,
    WORD32 ch_count_from_dwnmix_id,
    ia_parametric_drc_params_struct* p_parametricdrc_params) {
  WORD32 err = 0, i = 0;

  WORD32 parametric_drc_idx =
      p_parametricdrc_params->parametric_drc_idx[instance_idx];
  WORD32 gain_set_index = p_parametricdrc_params->gain_set_index[instance_idx];
  WORD32* channel_map = p_parametricdrc_params->channel_map[instance_idx];

  ia_drc_coeff_parametric_drc_struct* hDrcCoefficientsParametricDrcBs =
      &(pstr_drc_config->str_drc_config_ext.str_drc_coeff_param_drc);
  ia_parametric_drc_type_feed_forward_struct* hParametricDrcTypeFeedForwardBs =
      &(pstr_drc_config->str_drc_config_ext
            .str_parametric_drc_instructions[parametric_drc_idx]
            .str_parametric_drc_type_feed_forward);
  ia_parametric_drc_type_ff_params_struct*
      pstr_parametric_ffwd_type_drc_params =
          &(p_parametricdrc_params
                ->str_parametric_drc_instance_params[instance_idx]
                .str_parametric_drc_type_ff_params);

  /* level estimation */
  pstr_parametric_ffwd_type_drc_params->frame_size =
      p_parametricdrc_params->parametric_drc_frame_size;
  pstr_parametric_ffwd_type_drc_params->sub_band_domain_mode =
      p_parametricdrc_params->sub_band_domain_mode;
  pstr_parametric_ffwd_type_drc_params->sub_band_count =
      p_parametricdrc_params->sub_band_count;
  pstr_parametric_ffwd_type_drc_params->sub_band_compensation_type = 0;

  if (pstr_parametric_ffwd_type_drc_params->sub_band_domain_mode ==
      SUBBAND_DOMAIN_MODE_QMF64) {
    if (p_parametricdrc_params->sampling_rate == 48000) {
      pstr_parametric_ffwd_type_drc_params->sub_band_compensation_type = 1;
    } else {
      /* support of other sampling rates than 48000 might be missing */
      return UNEXPECTED_ERROR;
    }
  }

  pstr_parametric_ffwd_type_drc_params->audio_num_chan =
      p_parametricdrc_params->audio_num_chan;
  pstr_parametric_ffwd_type_drc_params->level_estim_k_weighting_type =
      hParametricDrcTypeFeedForwardBs->level_estim_k_weighting_type;
  pstr_parametric_ffwd_type_drc_params->level_estim_integration_time =
      hParametricDrcTypeFeedForwardBs->level_estim_integration_time;
  pstr_parametric_ffwd_type_drc_params->level_estim_frame_index = 0;
  pstr_parametric_ffwd_type_drc_params->level_estim_frame_count =
      hParametricDrcTypeFeedForwardBs->level_estim_integration_time /
      pstr_parametric_ffwd_type_drc_params->frame_size;

  memset(pstr_parametric_ffwd_type_drc_params->level, 0,
         PARAM_DRC_TYPE_FF_LEVEL_ESTIM_FRAME_COUNT_MAX * sizeof(FLOAT32));

  if (ch_count_from_dwnmix_id != 0) {
    memcpy(pstr_parametric_ffwd_type_drc_params->level_estim_ch_weight,
           hDrcCoefficientsParametricDrcBs
               ->str_parametric_drc_gain_set_params[gain_set_index]
               .level_estim_ch_weight,
           ch_count_from_dwnmix_id * sizeof(FLOAT32));
  } else {
    for (i = 0; i < pstr_parametric_ffwd_type_drc_params->audio_num_chan; i++) {
      pstr_parametric_ffwd_type_drc_params->level_estim_ch_weight[i] =
          (FLOAT32)channel_map[i];
    }
  }

  if (pstr_parametric_ffwd_type_drc_params->sub_band_domain_mode ==
      SUBBAND_DOMAIN_MODE_OFF) {
    err = impd_init_lvl_est_filt_time(
        pstr_parametric_ffwd_type_drc_params->level_estim_k_weighting_type,
        p_parametricdrc_params->sampling_rate,
        &pstr_parametric_ffwd_type_drc_params->pre_filt_coeff,
        &pstr_parametric_ffwd_type_drc_params->rlb_filt_coeff);

    if (err) return (err);
  } else {
    err = impd_init_lvl_est_filt_subband(
        pstr_parametric_ffwd_type_drc_params->level_estim_k_weighting_type,
        p_parametricdrc_params->sampling_rate,
        p_parametricdrc_params->sub_band_domain_mode,
        p_parametricdrc_params->sub_band_count,
        pstr_parametric_ffwd_type_drc_params->sub_band_compensation_type,
        pstr_parametric_ffwd_type_drc_params->weighting_filt,
        &pstr_parametric_ffwd_type_drc_params->filt_coeff_subband);

    if (err) return (err);
  }

  pstr_parametric_ffwd_type_drc_params->node_count =
      hParametricDrcTypeFeedForwardBs->node_count;

  memcpy(pstr_parametric_ffwd_type_drc_params->node_level,
         hParametricDrcTypeFeedForwardBs->node_level,
         pstr_parametric_ffwd_type_drc_params->node_count * sizeof(WORD32));
  memcpy(pstr_parametric_ffwd_type_drc_params->node_gain,
         hParametricDrcTypeFeedForwardBs->node_gain,
         pstr_parametric_ffwd_type_drc_params->node_count * sizeof(WORD32));

  pstr_parametric_ffwd_type_drc_params->ref_level_parametric_drc =
      hDrcCoefficientsParametricDrcBs
          ->str_parametric_drc_gain_set_params[gain_set_index]
          .drc_input_loudness;

  {
    WORD32 gain_smooth_attack_time_fast =
        hParametricDrcTypeFeedForwardBs->gain_smooth_attack_time_fast;
    WORD32 gain_smooth_release_time_fast =
        hParametricDrcTypeFeedForwardBs->gain_smooth_release_time_fast;
    WORD32 gain_smooth_attack_time_slow =
        hParametricDrcTypeFeedForwardBs->gain_smooth_attack_time_slow;
    WORD32 gain_smooth_release_time_slow =
        hParametricDrcTypeFeedForwardBs->gain_smooth_release_time_slow;
    WORD32 gain_smooth_hold_off =
        hParametricDrcTypeFeedForwardBs->gain_smooth_hold_off;
    WORD32 sampling_rate = p_parametricdrc_params->sampling_rate;
    WORD32 parametric_drc_frame_size =
        p_parametricdrc_params->parametric_drc_frame_size;

    pstr_parametric_ffwd_type_drc_params->gain_smooth_attack_alpha_fast =
        1 -
        (FLOAT32)exp(-1.0 * parametric_drc_frame_size /
                     (gain_smooth_attack_time_fast * sampling_rate * 0.001));
    pstr_parametric_ffwd_type_drc_params->gain_smooth_rel_alpha_fast =
        1 -
        (FLOAT32)exp(-1.0 * parametric_drc_frame_size /
                     (gain_smooth_release_time_fast * sampling_rate * 0.001));
    pstr_parametric_ffwd_type_drc_params->gain_smooth_attack_alpha_slow =
        1 -
        (FLOAT32)exp(-1.0 * parametric_drc_frame_size /
                     (gain_smooth_attack_time_slow * sampling_rate * 0.001));
    pstr_parametric_ffwd_type_drc_params->gain_smooth_rel_alpha_slow =
        1 -
        (FLOAT32)exp(-1.0 * parametric_drc_frame_size /
                     (gain_smooth_release_time_slow * sampling_rate * 0.001));
    pstr_parametric_ffwd_type_drc_params->gain_smooth_hold_off_count =
        gain_smooth_hold_off * 256 * sampling_rate /
        (parametric_drc_frame_size * 48000);
    pstr_parametric_ffwd_type_drc_params->gain_smooth_attack_threshold =
        hParametricDrcTypeFeedForwardBs->gain_smooth_attack_threshold;
    pstr_parametric_ffwd_type_drc_params->gain_smooth_rel_threshold =
        hParametricDrcTypeFeedForwardBs->gain_smooth_rel_threshold;
  }

  err =
      impd_parametric_ffwd_type_drc_reset(pstr_parametric_ffwd_type_drc_params);

  if (err) return (err);

  return 0;
}

VOID impd_init_parametric_drc_lim(
    ia_drc_config* pstr_drc_config, WORD32 instance_idx,
    WORD32 ch_count_from_dwnmix_id,
    ia_parametric_drc_params_struct* p_parametricdrc_params, pVOID* mem_ptr) {
  WORD32 i = 0;
  UWORD32 j;
  UWORD32 attack, sec_len;

  WORD32 parametric_drc_idx =
      p_parametricdrc_params->parametric_drc_idx[instance_idx];
  WORD32 gain_set_index = p_parametricdrc_params->gain_set_index[instance_idx];
  WORD32* channel_map = p_parametricdrc_params->channel_map[instance_idx];

  ia_drc_coeff_parametric_drc_struct* hDrcCoefficientsParametricDrcBs =
      &(pstr_drc_config->str_drc_config_ext.str_drc_coeff_param_drc);
  ia_parametric_drc_lim_struct* hParametricDrcTypeLimBs =
      &(pstr_drc_config->str_drc_config_ext
            .str_parametric_drc_instructions[parametric_drc_idx]
            .parametric_drc_lim);
  ia_parametric_drc_type_lim_params_struct*
      pstr_parametric_lim_type_drc_params =
          &(p_parametricdrc_params
                ->str_parametric_drc_instance_params[instance_idx]
                .str_parametric_drc_type_lim_params);

  pstr_parametric_lim_type_drc_params->frame_size =
      p_parametricdrc_params->drc_frame_size;
  pstr_parametric_lim_type_drc_params->audio_num_chan =
      p_parametricdrc_params->audio_num_chan;

  if (ch_count_from_dwnmix_id != 0) {
    memcpy(pstr_parametric_lim_type_drc_params->level_estim_ch_weight,
           hDrcCoefficientsParametricDrcBs
               ->str_parametric_drc_gain_set_params[gain_set_index]
               .level_estim_ch_weight,
           ch_count_from_dwnmix_id * sizeof(FLOAT32));
  } else {
    for (i = 0; i < pstr_parametric_lim_type_drc_params->audio_num_chan; i++) {
      pstr_parametric_lim_type_drc_params->level_estim_ch_weight[i] =
          (FLOAT32)channel_map[i];
    }
  }

  attack = (UWORD32)(hParametricDrcTypeLimBs->parametric_lim_attack *
                     p_parametricdrc_params->sampling_rate / 1000);

  sec_len = (UWORD32)sqrt(attack + 1);

  pstr_parametric_lim_type_drc_params->sec_len = sec_len;
  pstr_parametric_lim_type_drc_params->num_max_buf_sec = (attack + 1) / sec_len;
  if (pstr_parametric_lim_type_drc_params->num_max_buf_sec * sec_len <
      (attack + 1))
    pstr_parametric_lim_type_drc_params->num_max_buf_sec++;

  pstr_parametric_lim_type_drc_params->max_buf = (FLOAT32*)(*mem_ptr);
  *mem_ptr = (pVOID)((SIZE_T)(*mem_ptr) +
                     pstr_parametric_lim_type_drc_params->num_max_buf_sec *
                         sec_len * sizeof(FLOAT32));

  pstr_parametric_lim_type_drc_params->attack_ms =
      (FLOAT32)hParametricDrcTypeLimBs->parametric_lim_attack;
  pstr_parametric_lim_type_drc_params->release_ms =
      (FLOAT32)hParametricDrcTypeLimBs->parametric_lim_release;
  pstr_parametric_lim_type_drc_params->attack = attack;
  pstr_parametric_lim_type_drc_params->attack_constant =
      (FLOAT32)pow(0.1, 1.0 / (attack + 1));
  pstr_parametric_lim_type_drc_params->release_constant = (FLOAT32)pow(
      0.1, 1.0 / (hParametricDrcTypeLimBs->parametric_lim_release *
                      p_parametricdrc_params->sampling_rate / 1000 +
                  1));
  pstr_parametric_lim_type_drc_params->threshold = (FLOAT32)pow(
      10.0f, 0.05f * hParametricDrcTypeLimBs->parametric_lim_threshold);
  pstr_parametric_lim_type_drc_params->channels =
      pstr_parametric_lim_type_drc_params->audio_num_chan;
  pstr_parametric_lim_type_drc_params->sampling_rate =
      p_parametricdrc_params->sampling_rate;
  pstr_parametric_lim_type_drc_params->cor = 1.0f;
  pstr_parametric_lim_type_drc_params->smooth_state_0 = 1.0;

  for (j = 0; j < pstr_parametric_lim_type_drc_params->num_max_buf_sec *
                      pstr_parametric_lim_type_drc_params->sec_len;
       j++) {
    pstr_parametric_lim_type_drc_params->max_buf[j] = 0.f;
  }
}

WORD32 impd_init_parametric_drcInstance(
    ia_drc_config* pstr_drc_config, WORD32 instance_idx,
    WORD32 ch_count_from_dwnmix_id,
    ia_parametric_drc_params_struct* p_parametricdrc_params, pVOID* mem_ptr) {
  WORD32 err = 0;

  WORD32 parametric_drc_idx =
      p_parametricdrc_params->parametric_drc_idx[instance_idx];
  ia_parametric_drc_instructions_struct* hParametricDrcInstructions =
      &(pstr_drc_config->str_drc_config_ext
            .str_parametric_drc_instructions[parametric_drc_idx]);

  p_parametricdrc_params->str_parametric_drc_instance_params[instance_idx]
      .disable_paramteric_drc =
      hParametricDrcInstructions->disable_paramteric_drc;
  p_parametricdrc_params->str_parametric_drc_instance_params[instance_idx]
      .parametric_drc_type = hParametricDrcInstructions->parametric_drc_type;
  p_parametricdrc_params->str_parametric_drc_instance_params[instance_idx]
      .str_spline_nodes.num_nodes = p_parametricdrc_params->num_nodes;

  if (p_parametricdrc_params->str_parametric_drc_instance_params[instance_idx]
          .disable_paramteric_drc == 0) {
    if (p_parametricdrc_params->str_parametric_drc_instance_params[instance_idx]
            .parametric_drc_type == PARAM_DRC_TYPE_FF) {
      err = impd_init_parametric_drc_feed_fwd(pstr_drc_config, instance_idx,
                                              ch_count_from_dwnmix_id,
                                              p_parametricdrc_params);

      if (err) return (err);

    } else if (p_parametricdrc_params
                   ->str_parametric_drc_instance_params[instance_idx]
                   .parametric_drc_type == PARAM_DRC_TYPE_LIM) {
      p_parametricdrc_params->str_parametric_drc_instance_params[instance_idx]
          .str_spline_nodes.num_nodes = p_parametricdrc_params->drc_frame_size;

      impd_init_parametric_drc_lim(pstr_drc_config, instance_idx,
                                   ch_count_from_dwnmix_id,
                                   p_parametricdrc_params, mem_ptr);

    } else {
      return (UNEXPECTED_ERROR);
    }
  }

  return 0;
}

WORD32 impd_init_parametric_drc_after_config(
    ia_drc_config* pstr_drc_config,
    ia_drc_loudness_info_set_struct* pstr_loudness_info,
    ia_parametric_drc_params_struct* p_parametricdrc_params, pVOID* mem_ptr) {
  WORD32 err = 0, instance_idx = 0, gain_set_index = 0,
         side_chain_config_type = 0, downmix_id = 0,
         ch_count_from_dwnmix_id = 0, L = 0;

  p_parametricdrc_params->parametric_drc_frame_size =
      pstr_drc_config->str_drc_config_ext.str_drc_coeff_param_drc
          .parametric_drc_frame_size;
  p_parametricdrc_params->reset_parametric_drc =
      pstr_drc_config->str_drc_config_ext.str_drc_coeff_param_drc
          .reset_parametric_drc;
  p_parametricdrc_params->num_nodes =
      p_parametricdrc_params->drc_frame_size /
      p_parametricdrc_params->parametric_drc_frame_size;

  switch (p_parametricdrc_params->sub_band_domain_mode) {
    case SUBBAND_DOMAIN_MODE_QMF64:
      L = AUDIO_CODEC_SUBBAND_DOWNSAMPLING_FACTOR_QMF64;
      break;
    case SUBBAND_DOMAIN_MODE_QMF71:
      L = AUDIO_CODEC_SUBBAND_DOWNSAMPLING_FACTOR_QMF71;
      break;
    case SUBBAND_DOMAIN_MODE_STFT256:
      L = AUDIO_CODEC_SUBBAND_DOWNSAMPLING_FACTOR_STFT256;
      break;
    case SUBBAND_DOMAIN_MODE_OFF:
    default:
      L = 0;
      break;
  }

  if (p_parametricdrc_params->sub_band_domain_mode != SUBBAND_DOMAIN_MODE_OFF &&
      p_parametricdrc_params->parametric_drc_frame_size != L) {
    return (EXTERNAL_ERROR);
  }

  for (instance_idx = 0;
       instance_idx < p_parametricdrc_params->parametric_drc_instance_count;
       instance_idx++) {
    gain_set_index = p_parametricdrc_params->gain_set_index[instance_idx];
    side_chain_config_type =
        pstr_drc_config->str_drc_config_ext.str_drc_coeff_param_drc
            .str_parametric_drc_gain_set_params[gain_set_index]
            .side_chain_config_type;
    downmix_id = pstr_drc_config->str_drc_config_ext.str_drc_coeff_param_drc
                     .str_parametric_drc_gain_set_params[gain_set_index]
                     .downmix_id;

    if (side_chain_config_type == 1 &&
        downmix_id ==
            p_parametricdrc_params
                ->dwnmix_id_from_drc_instructions[instance_idx]) {
      ch_count_from_dwnmix_id =
          pstr_drc_config->str_drc_config_ext.str_drc_coeff_param_drc
              .str_parametric_drc_gain_set_params[gain_set_index]
              .ch_count_from_dwnmix_id;
    } else {
      ch_count_from_dwnmix_id = 0;
    }

    if (pstr_drc_config->str_drc_config_ext.str_drc_coeff_param_drc
            .str_parametric_drc_gain_set_params[gain_set_index]
            .drc_input_loudness_present == 0) {
      WORD32 n = 0, m = 0, drcInputLoudnessFound = 0;
      FLOAT32 drc_input_loudness = 0.f;

      for (n = 0; n < pstr_loudness_info->loudness_info_count; n++) {
        ia_loudness_info_struct* loudness_info =
            &pstr_loudness_info->loudness_info[n];
        if (p_parametricdrc_params
                ->dwnmix_id_from_drc_instructions[instance_idx] ==
            loudness_info->downmix_id) {
          if (0 == loudness_info->drc_set_id) {
            for (m = 0; m < loudness_info->measurement_count; m++) {
              if (loudness_info->loudness_measure[m].method_def ==
                  METHOD_DEFINITION_PROGRAM_LOUDNESS) {
                drc_input_loudness =
                    loudness_info->loudness_measure[m].method_val;
                drcInputLoudnessFound = 1;
                break;
              }
            }
            if (drcInputLoudnessFound == 0) {
              for (m = 0; m < loudness_info->measurement_count; m++) {
                if (loudness_info->loudness_measure[m].method_def ==
                    METHOD_DEFINITION_ANCHOR_LOUDNESS) {
                  drc_input_loudness =
                      loudness_info->loudness_measure[m].method_val;
                  drcInputLoudnessFound = 1;
                  break;
                }
              }
            }
          }
        }
      }
      if (drcInputLoudnessFound == 0) {
        for (n = 0; n < pstr_loudness_info->loudness_info_count; n++) {
          ia_loudness_info_struct* loudness_info =
              &pstr_loudness_info->loudness_info[n];
          if (0 == loudness_info->downmix_id) {
            if (0 == loudness_info->drc_set_id) {
              for (m = 0; m < loudness_info->measurement_count; m++) {
                if (loudness_info->loudness_measure[m].method_def ==
                    METHOD_DEFINITION_PROGRAM_LOUDNESS) {
                  drc_input_loudness =
                      loudness_info->loudness_measure[m].method_val;
                  drcInputLoudnessFound = 1;
                  break;
                }
              }
              if (drcInputLoudnessFound == 0) {
                for (m = 0; m < loudness_info->measurement_count; m++) {
                  if (loudness_info->loudness_measure[m].method_def ==
                      METHOD_DEFINITION_ANCHOR_LOUDNESS) {
                    drc_input_loudness =
                        loudness_info->loudness_measure[m].method_val;
                    drcInputLoudnessFound = 1;
                    break;
                  }
                }
              }
            }
          }
        }
      }
      if (drcInputLoudnessFound == 0) {
        return (UNEXPECTED_ERROR);
      } else {
        pstr_drc_config->str_drc_config_ext.str_drc_coeff_param_drc
            .str_parametric_drc_gain_set_params[gain_set_index]
            .drc_input_loudness = drc_input_loudness;
      }
    }

    impd_init_parametric_drcInstance(pstr_drc_config, instance_idx,
                                     ch_count_from_dwnmix_id,
                                     p_parametricdrc_params, mem_ptr);
    if (err) return (err);
  }

  return 0;
}

WORD32 impd_init_lvl_est_filt_time(
    WORD32 level_estim_k_weighting_type, WORD32 sampling_rate,
    ia_2nd_order_filt_coeff_struct* pre_filt_coeff,
    ia_2nd_order_filt_coeff_struct* rlb_filt_coeff) {
  WORD32 i;
  const FLOAT32* ptr_samp_tbl;

  switch (sampling_rate) {
    case 96000:
      i = 0;
      break;
    case 88200:
      i = 1;
      break;
    case 64000:
      i = 2;
      break;
    case 48000:
      i = 3;
      break;
    case 44100:
      i = 4;
      break;
    case 32000:
      i = 5;
      break;
    case 24000:
      i = 6;
      break;
    case 22050:
      i = 7;
      break;
    case 16000:
      i = 8;
      break;
    case 12000:
      i = 9;
      break;
    case 11025:
      i = 10;
      break;
    case 8000:
      i = 11;
      break;
    case 7350:
      i = 12;
      break;
    default:
      i = 3;
      break;
  }

  ptr_samp_tbl = samp_rate_tbl[i];

  if (level_estim_k_weighting_type == 2) {
    pre_filt_coeff->b0 = ptr_samp_tbl[0];
    pre_filt_coeff->b1 = ptr_samp_tbl[1];
    pre_filt_coeff->b2 = ptr_samp_tbl[2];
    pre_filt_coeff->a1 = ptr_samp_tbl[3];
    pre_filt_coeff->a2 = ptr_samp_tbl[4];
  }

  if (level_estim_k_weighting_type == 1 || level_estim_k_weighting_type == 2) {
    rlb_filt_coeff->b0 = ptr_samp_tbl[5];
    rlb_filt_coeff->b1 = ptr_samp_tbl[6];
    rlb_filt_coeff->b2 = ptr_samp_tbl[7];
    rlb_filt_coeff->a1 = ptr_samp_tbl[8];
    rlb_filt_coeff->a2 = ptr_samp_tbl[9];
  }

  return 0;
}

WORD32 impd_init_lvl_est_filt_subband(
    WORD32 level_estim_k_weighting_type, WORD32 sampling_rate,
    WORD32 sub_band_domain_mode, WORD32 sub_band_count,
    WORD32 sub_band_compensation_type, FLOAT32* weighting_filt,
    ia_2nd_order_filt_coeff_struct* filt_coeff_subband) {
  FLOAT32 w0, alpha, sinw0, cosw0;
  FLOAT32 b0, b1, b2, a0, a1, a2;
  FLOAT32 num_real, num_imag, den_real, den_imag;
  FLOAT32* f_bands_nrm;
  WORD32 b;
  WORD32 i;
  const FLOAT32* ptr_samp_tbl;

  switch (sampling_rate) {
    case 96000:
      i = 0;
      break;
    case 88200:
      i = 1;
      break;
    case 64000:
      i = 2;
      break;
    case 48000:
      i = 3;
      break;
    case 44100:
      i = 4;
      break;
    case 32000:
      i = 5;
      break;
    case 24000:
      i = 6;
      break;
    case 22050:
      i = 7;
      break;
    case 16000:
      i = 8;
      break;
    case 12000:
      i = 9;
      break;
    case 11025:
      i = 10;
      break;
    case 8000:
      i = 11;
      break;
    case 7350:
      i = 12;
      break;
    default:
      i = 3;
      break;
  }

  ptr_samp_tbl = samp_rate_tbl[i];

  switch (sub_band_domain_mode) {
    case SUBBAND_DOMAIN_MODE_QMF64:
      f_bands_nrm = f_bands_nrm_QMF64;
      break;
    case SUBBAND_DOMAIN_MODE_QMF71:
      f_bands_nrm = f_bands_nrm_QMF71;
      break;
    case SUBBAND_DOMAIN_MODE_STFT256:
      f_bands_nrm = f_bands_nrm_STFT256;
      break;
    default:
      return UNEXPECTED_ERROR;
      break;
  }

  for (b = 0; b < sub_band_count; b++) {
    weighting_filt[b] = 1.f;
  }

  if (level_estim_k_weighting_type == 2) {
    b0 = ptr_samp_tbl[0];
    b1 = ptr_samp_tbl[1];
    b2 = ptr_samp_tbl[2];
    a1 = ptr_samp_tbl[3];
    a2 = ptr_samp_tbl[4];
    a0 = 1.f;

    for (b = 0; b < sub_band_count; b++) {
      num_real = b0 + b1 * (FLOAT32)cos(PI * f_bands_nrm[b]) +
                 b2 * (FLOAT32)cos(PI * 2 * f_bands_nrm[b]);
      num_imag = -b1 * (FLOAT32)sin(PI * f_bands_nrm[b]) -
                 b2 * (FLOAT32)sin(PI * 2 * f_bands_nrm[b]);
      den_real = a0 + a1 * (FLOAT32)cos(PI * f_bands_nrm[b]) +
                 a2 * (FLOAT32)cos(PI * 2 * f_bands_nrm[b]);
      den_imag = -a1 * (FLOAT32)sin(PI * f_bands_nrm[b]) -
                 a2 * (FLOAT32)sin(PI * 2 * f_bands_nrm[b]);

      weighting_filt[b] *=
          (FLOAT32)(sqrt((num_real * num_real + num_imag * num_imag) /
                         (den_real * den_real + den_imag * den_imag)));
    }
  }

  if (level_estim_k_weighting_type == 1 || level_estim_k_weighting_type == 2) {
    b0 = ptr_samp_tbl[5];
    b1 = ptr_samp_tbl[6];
    b2 = ptr_samp_tbl[7];
    a1 = ptr_samp_tbl[8];
    a2 = ptr_samp_tbl[9];
    a0 = 1.f;

    for (b = 0; b < sub_band_count; b++) {
      if (!(sub_band_compensation_type == 1 && b == 0)) {
        num_real = (FLOAT32)(b0 + b1 * cos(PI * f_bands_nrm[b]) +
                             b2 * cos(PI * 2 * f_bands_nrm[b]));
        num_imag = (FLOAT32)(-b1 * sin(PI * f_bands_nrm[b]) -
                             b2 * sin(PI * 2 * f_bands_nrm[b]));
        den_real = (FLOAT32)(a0 + a1 * cos(PI * f_bands_nrm[b]) +
                             a2 * cos(PI * 2 * f_bands_nrm[b]));
        den_imag = (FLOAT32)(-a1 * sin(PI * f_bands_nrm[b]) -
                             a2 * sin(PI * 2 * f_bands_nrm[b]));

        weighting_filt[b] *=
            (FLOAT32)(sqrt((num_real * num_real + num_imag * num_imag) /
                           (den_real * den_real + den_imag * den_imag)));
      }
    }

    if (sub_band_compensation_type == 1) {
      w0 = 2.0f * PI * 38.0f / (FLOAT32)sampling_rate *
           AUDIO_CODEC_SUBBAND_DOWNSAMPLING_FACTOR_QMF64;
      sinw0 = (FLOAT32)sin(w0);
      cosw0 = (FLOAT32)cos(w0);
      alpha = sinw0;

      b0 = (1 + cosw0) / 2;
      b1 = -(1 + cosw0);
      b2 = (1 + cosw0) / 2;
      a0 = 1 + alpha;
      a1 = -2 * cosw0;
      a2 = 1 - alpha;

      filt_coeff_subband->b0 = b0 / a0;
      filt_coeff_subband->b1 = b1 / a0;
      filt_coeff_subband->b2 = b2 / a0;
      filt_coeff_subband->a1 = a1 / a0;
      filt_coeff_subband->a2 = a2 / a0;
    }
  }

  return 0;
}

WORD32 impd_parametric_ffwd_type_drc_reset(
    ia_parametric_drc_type_ff_params_struct*
        pstr_parametric_ffwd_type_drc_params) {
  WORD32 i = 0;

  pstr_parametric_ffwd_type_drc_params->level_estim_frame_index = 0;
  pstr_parametric_ffwd_type_drc_params->start_up_phase = 1;
  for (i = 0; i < PARAM_DRC_TYPE_FF_LEVEL_ESTIM_FRAME_COUNT_MAX; i++) {
    pstr_parametric_ffwd_type_drc_params->level[i] = 0.f;
  }

  for (i = 0; i < MAX_CHANNEL_COUNT; i++) {
    pstr_parametric_ffwd_type_drc_params->pre_filt_state[i].z1 = 0.f;
    pstr_parametric_ffwd_type_drc_params->pre_filt_state[i].z2 = 0.f;
    pstr_parametric_ffwd_type_drc_params->rlb_filt_state[i].z1 = 0.f;
    pstr_parametric_ffwd_type_drc_params->rlb_filt_state[i].z2 = 0.f;
    pstr_parametric_ffwd_type_drc_params->filt_state_subband_real[i].z1 = 0.f;
    pstr_parametric_ffwd_type_drc_params->filt_state_subband_real[i].z2 = 0.f;
    pstr_parametric_ffwd_type_drc_params->filt_state_subband_imag[i].z1 = 0.f;
    pstr_parametric_ffwd_type_drc_params->filt_state_subband_imag[i].z2 = 0.f;
  }

  pstr_parametric_ffwd_type_drc_params->db_level_smooth = -135.f;
  pstr_parametric_ffwd_type_drc_params->db_gain_smooth = 0.f;
  pstr_parametric_ffwd_type_drc_params->hold_counter = 0;

  return 0;
}

WORD32 impd_parametric_drc_instance_process(
    FLOAT32* audio_in_out_buf[], FLOAT32* audio_real_buff[],
    FLOAT32* audio_imag_buff[],
    ia_parametric_drc_params_struct* p_parametricdrc_params,
    ia_parametric_drc_instance_params_struct*
        pstr_parametric_drc_instance_params) {
  WORD32 err = 0, i = 0;

  if (pstr_parametric_drc_instance_params->disable_paramteric_drc) {
    for (i = 0; i < p_parametricdrc_params->num_nodes; i++) {
      pstr_parametric_drc_instance_params->str_spline_nodes.str_node[i]
          .loc_db_gain = 0.f;
      pstr_parametric_drc_instance_params->str_spline_nodes.str_node[i].slope =
          0.f;
      pstr_parametric_drc_instance_params->str_spline_nodes.str_node[i].time =
          (i + 1) * p_parametricdrc_params->parametric_drc_frame_size - 1;
    }

  } else {
    if (pstr_parametric_drc_instance_params->parametric_drc_type ==
        PARAM_DRC_TYPE_FF) {
      ia_parametric_drc_type_ff_params_struct*
          pstr_parametric_ffwd_type_drc_params =
              &(pstr_parametric_drc_instance_params
                    ->str_parametric_drc_type_ff_params);
      for (i = 0; i < p_parametricdrc_params->num_nodes; i++) {
        err = impd_parametric_ffwd_type_drc_process(
            audio_in_out_buf, audio_real_buff, audio_imag_buff, i,
            pstr_parametric_ffwd_type_drc_params,
            &pstr_parametric_drc_instance_params->str_spline_nodes);
        if (err) return (err);
      }

    } else if (pstr_parametric_drc_instance_params->parametric_drc_type ==
               PARAM_DRC_TYPE_LIM) {
      return (UNEXPECTED_ERROR);

    } else {
      return (UNEXPECTED_ERROR);
    }
  }

  return 0;
}

VOID iir_second_order_filter(ia_2nd_order_filt_coeff_struct* coeff,
                             ia_2nd_order_filt_state_struct* state,
                             WORD32 frame_len, FLOAT32* input,
                             FLOAT32* output) {
  FLOAT32 z2 = state->z2;
  FLOAT32 z1 = state->z1;
  FLOAT32 z0;
  WORD32 i;

  for (i = 0; i < frame_len; i++) {
    z0 = input[i] - coeff->a1 * z1 - coeff->a2 * z2;
    output[i] = coeff->b0 * z0 + coeff->b1 * z1 + coeff->b2 * z2;
    z2 = z1;
    z1 = z0;
  }
  state->z1 = z1;
  state->z2 = z2;
}
WORD32 impd_parametric_ffwd_type_drc_process(
    FLOAT32* audio_in_out_buf[], FLOAT32* audio_real_buff[],
    FLOAT32* audio_imag_buff[], WORD32 nodeIdx,
    ia_parametric_drc_type_ff_params_struct*
        pstr_parametric_ffwd_type_drc_params,
    ia_spline_nodes_struct* str_spline_nodes) {
  WORD32 c, t, b, n, i, offset;
  FLOAT32 x, y, channelLevel, level, levelDb, loc_db_gain, levelDelta, alpha;

  WORD32 frame_size = pstr_parametric_ffwd_type_drc_params->frame_size;
  WORD32 sub_band_count = pstr_parametric_ffwd_type_drc_params->sub_band_count;
  FLOAT32* level_estim_ch_weight =
      pstr_parametric_ffwd_type_drc_params->level_estim_ch_weight;
  WORD32 level_estim_k_weighting_type =
      pstr_parametric_ffwd_type_drc_params->level_estim_k_weighting_type;

  ia_2nd_order_filt_coeff_struct preC =
      pstr_parametric_ffwd_type_drc_params->pre_filt_coeff;
  ia_2nd_order_filt_coeff_struct rlbC =
      pstr_parametric_ffwd_type_drc_params->rlb_filt_coeff;
  ia_2nd_order_filt_state_struct* preS =
      pstr_parametric_ffwd_type_drc_params->pre_filt_state;
  ia_2nd_order_filt_state_struct* rlbS =
      pstr_parametric_ffwd_type_drc_params->rlb_filt_state;

  ia_2nd_order_filt_coeff_struct rlbC_sb =
      pstr_parametric_ffwd_type_drc_params->filt_coeff_subband;
  ia_2nd_order_filt_state_struct* rlbS_sbReal =
      pstr_parametric_ffwd_type_drc_params->filt_state_subband_real;
  ia_2nd_order_filt_state_struct* rlbS_sbImag =
      pstr_parametric_ffwd_type_drc_params->filt_state_subband_imag;
  FLOAT32* weighting_filt =
      pstr_parametric_ffwd_type_drc_params->weighting_filt;
  WORD32 sub_band_compensation_type =
      pstr_parametric_ffwd_type_drc_params->sub_band_compensation_type;

  if (audio_in_out_buf != NULL) {
    level = 0;
    offset = nodeIdx * pstr_parametric_ffwd_type_drc_params->frame_size;
    for (c = 0; c < pstr_parametric_ffwd_type_drc_params->audio_num_chan; c++) {
      channelLevel = 0.f;

      if (!level_estim_ch_weight[c]) continue;

      if (level_estim_k_weighting_type == 0) {
        for (t = 0; t < frame_size; t++) {
          x = audio_in_out_buf[c][offset + t];

          channelLevel += x * x;
        }

      } else if (level_estim_k_weighting_type == 1) {
        for (t = 0; t < frame_size; t++) {
          x = audio_in_out_buf[c][offset + t];

          iir_second_order_filter(&rlbC, &rlbS[c], 1, &x, &x);

          channelLevel += x * x;
        }

      } else if (level_estim_k_weighting_type == 2) {
        for (t = 0; t < frame_size; t++) {
          x = audio_in_out_buf[c][offset + t];

          iir_second_order_filter(&preC, &preS[c], 1, &x, &x);

          iir_second_order_filter(&rlbC, &rlbS[c], 1, &x, &x);

          channelLevel += x * x;
        }

      } else {
        return (UNEXPECTED_ERROR);
      }

      level += level_estim_ch_weight[c] * channelLevel;
    }

  } else {
    level = 0;
    offset = nodeIdx * pstr_parametric_ffwd_type_drc_params->sub_band_count;
    for (c = 0; c < pstr_parametric_ffwd_type_drc_params->audio_num_chan; c++) {
      channelLevel = 0.f;

      if (!level_estim_ch_weight[c]) continue;

      if (level_estim_k_weighting_type == 0) {
        for (b = 0; b < sub_band_count; b++) {
          x = audio_real_buff[c][offset + b];
          y = audio_imag_buff[c][offset + b];

          channelLevel += x * x + y * y;
        }

      } else if (level_estim_k_weighting_type == 1 ||
                 level_estim_k_weighting_type == 2) {
        for (b = 0; b < sub_band_count; b++) {
          x = audio_real_buff[c][offset + b] * weighting_filt[b];
          y = audio_imag_buff[c][offset + b] * weighting_filt[b];

          if (b == 0 && sub_band_compensation_type == 1) {
            iir_second_order_filter(&rlbC_sb, &rlbS_sbReal[c], 1, &x, &x);

            iir_second_order_filter(&rlbC_sb, &rlbS_sbImag[c], 1, &y, &y);
          }

          channelLevel += x * x + y * y;
        }

      } else {
        return (UNEXPECTED_ERROR);
      }

      level += level_estim_ch_weight[c] * channelLevel;
    }

    level /= sub_band_count;
  }
  pstr_parametric_ffwd_type_drc_params
      ->level[pstr_parametric_ffwd_type_drc_params->level_estim_frame_index] =
      level;
  pstr_parametric_ffwd_type_drc_params->level_estim_frame_index++;

  level = 0.f;
  if (pstr_parametric_ffwd_type_drc_params->start_up_phase) {
    for (i = 0;
         i < pstr_parametric_ffwd_type_drc_params->level_estim_frame_index;
         i++) {
      level += pstr_parametric_ffwd_type_drc_params->level[i];
    }
    level /= pstr_parametric_ffwd_type_drc_params->level_estim_frame_index *
             pstr_parametric_ffwd_type_drc_params->frame_size;
  } else {
    for (i = 0;
         i < pstr_parametric_ffwd_type_drc_params->level_estim_frame_count;
         i++) {
      level += pstr_parametric_ffwd_type_drc_params->level[i];
    }
    level /= pstr_parametric_ffwd_type_drc_params->level_estim_integration_time;
  }
  if (pstr_parametric_ffwd_type_drc_params->level_estim_frame_index ==
      pstr_parametric_ffwd_type_drc_params->level_estim_frame_count) {
    pstr_parametric_ffwd_type_drc_params->level_estim_frame_index = 0;
    pstr_parametric_ffwd_type_drc_params->start_up_phase = 0;
  }

  if (level < 1e-10f) level = 1e-10f;
  if (level_estim_k_weighting_type == 2) {
    levelDb = -0.691f + 10 * (FLOAT32)log10(level) + 3;
  } else {
    levelDb = 10 * (FLOAT32)log10(level) + 3;
  }
  levelDb -= pstr_parametric_ffwd_type_drc_params->ref_level_parametric_drc;

  for (n = 0; n < pstr_parametric_ffwd_type_drc_params->node_count; n++) {
    if (levelDb <=
        (FLOAT32)pstr_parametric_ffwd_type_drc_params->node_level[n]) {
      break;
    }
  }
  if (n == 0) {
    loc_db_gain = (FLOAT32)pstr_parametric_ffwd_type_drc_params->node_gain[n];
  } else if (n == pstr_parametric_ffwd_type_drc_params->node_count) {
    loc_db_gain =
        (FLOAT32)pstr_parametric_ffwd_type_drc_params->node_gain[n - 1] -
        levelDb +
        (FLOAT32)pstr_parametric_ffwd_type_drc_params->node_level[n - 1];
  } else {
    loc_db_gain =
        (FLOAT32)pstr_parametric_ffwd_type_drc_params->node_gain[n] +
        (levelDb -
         (FLOAT32)pstr_parametric_ffwd_type_drc_params->node_level[n]) /
            (FLOAT32)(pstr_parametric_ffwd_type_drc_params->node_level[n - 1] -
                      pstr_parametric_ffwd_type_drc_params->node_level[n]) *
            (FLOAT32)(pstr_parametric_ffwd_type_drc_params->node_gain[n - 1] -
                      pstr_parametric_ffwd_type_drc_params->node_gain[n]);
  }

  levelDelta = levelDb - pstr_parametric_ffwd_type_drc_params->db_level_smooth;
  if (loc_db_gain < pstr_parametric_ffwd_type_drc_params->db_gain_smooth) {
    if (levelDelta >
        pstr_parametric_ffwd_type_drc_params->gain_smooth_attack_threshold) {
      alpha =
          pstr_parametric_ffwd_type_drc_params->gain_smooth_attack_alpha_fast;
    } else {
      alpha =
          pstr_parametric_ffwd_type_drc_params->gain_smooth_attack_alpha_slow;
    }
  } else {
    if (levelDelta <
        -pstr_parametric_ffwd_type_drc_params->gain_smooth_rel_threshold) {
      alpha = pstr_parametric_ffwd_type_drc_params->gain_smooth_rel_alpha_fast;
    } else {
      alpha = pstr_parametric_ffwd_type_drc_params->gain_smooth_rel_alpha_slow;
    }
  }
  if (loc_db_gain < pstr_parametric_ffwd_type_drc_params->db_gain_smooth ||
      pstr_parametric_ffwd_type_drc_params->hold_counter == 0) {
    pstr_parametric_ffwd_type_drc_params->db_level_smooth =
        (1 - alpha) * pstr_parametric_ffwd_type_drc_params->db_level_smooth +
        alpha * levelDb;
    pstr_parametric_ffwd_type_drc_params->db_gain_smooth =
        (1 - alpha) * pstr_parametric_ffwd_type_drc_params->db_gain_smooth +
        alpha * loc_db_gain;
  }
  if (pstr_parametric_ffwd_type_drc_params->hold_counter) {
    pstr_parametric_ffwd_type_drc_params->hold_counter -= 1;
  }
  if (loc_db_gain < pstr_parametric_ffwd_type_drc_params->db_gain_smooth) {
    pstr_parametric_ffwd_type_drc_params->hold_counter =
        pstr_parametric_ffwd_type_drc_params->gain_smooth_hold_off_count;
  }

  str_spline_nodes->str_node[nodeIdx].loc_db_gain =
      pstr_parametric_ffwd_type_drc_params->db_gain_smooth;
  str_spline_nodes->str_node[nodeIdx].slope = 0.f;
  str_spline_nodes->str_node[nodeIdx].time =
      pstr_parametric_ffwd_type_drc_params->frame_size + offset - 1;

  return 0;
}

WORD32 impd_parametric_lim_type_drc_process(
    FLOAT32* samples[], FLOAT32 loudness_normalization_gain_db,
    ia_parametric_drc_type_lim_params_struct*
        pstr_parametric_lim_type_drc_params,
    FLOAT32* lpcm_gains) {
  WORD32 i, j;
  FLOAT32 tmp, gain;
  //  FLOAT32 min_gain = 1;
  FLOAT32 maximum, sectionMaximum;
  FLOAT32 loudness_normalization_gain =
      (FLOAT32)pow(10.0f, 0.05f * loudness_normalization_gain_db);
  FLOAT32* level_estim_ch_weight =
      pstr_parametric_lim_type_drc_params->level_estim_ch_weight;
  WORD32 num_channels = pstr_parametric_lim_type_drc_params->channels;
  WORD32 attack_time_samples = pstr_parametric_lim_type_drc_params->attack;
  FLOAT32 attack_constant =
      pstr_parametric_lim_type_drc_params->attack_constant;
  FLOAT32 release_constant =
      pstr_parametric_lim_type_drc_params->release_constant;
  FLOAT32 limit_threshold = pstr_parametric_lim_type_drc_params->threshold;
  FLOAT32* max_buf = pstr_parametric_lim_type_drc_params->max_buf;
  FLOAT32 gain_modified = pstr_parametric_lim_type_drc_params->cor;
  FLOAT64 pre_smoothed_gain =
      pstr_parametric_lim_type_drc_params->smooth_state_0;

  for (i = 0; i < pstr_parametric_lim_type_drc_params->frame_size; i++) {
    tmp = 0.0f;
    for (j = 0; j < num_channels; j++) {
      if (!level_estim_ch_weight[j]) continue;
      tmp =
          max(tmp, (FLOAT32)fabs(loudness_normalization_gain *
                                 (level_estim_ch_weight[j]) * (samples[j][i])));
    }

    for (j = attack_time_samples; j > 0; j--) {
      max_buf[j] = max_buf[j - 1];
    }
    max_buf[0] = tmp;
    sectionMaximum = tmp;
    for (j = 1; j < (attack_time_samples + 1); j++) {
      if (max_buf[j] > sectionMaximum) sectionMaximum = max_buf[j];
    }
    maximum = sectionMaximum;

    if (maximum > limit_threshold) {
      gain = limit_threshold / maximum;
    } else {
      gain = 1;
    }

    if (gain < pre_smoothed_gain) {
      gain_modified =
          min(gain_modified,
              (gain - 0.1f * (FLOAT32)pre_smoothed_gain) * 1.11111111f);
    } else {
      gain_modified = gain;
    }

    if (gain_modified < pre_smoothed_gain) {
      pre_smoothed_gain =
          attack_constant * (pre_smoothed_gain - gain_modified) + gain_modified;
      pre_smoothed_gain = max(pre_smoothed_gain, gain);
    } else {
      pre_smoothed_gain =
          release_constant * (pre_smoothed_gain - gain_modified) +
          gain_modified;
    }

    gain = (FLOAT32)pre_smoothed_gain;

    lpcm_gains[i] = gain;
  }

  pstr_parametric_lim_type_drc_params->cor = gain_modified;
  pstr_parametric_lim_type_drc_params->smooth_state_0 = pre_smoothed_gain;
  return 0;
}
