/*
 *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "modules/audio_processing/agc2/fixed_digital_level_estimator.h"

#include <algorithm>
#include <cmath>

#include "api/array_view.h"
#include "modules/audio_processing/logging/apm_data_dumper.h"
#include "rtc_base/checks.h"

namespace webrtc {
namespace {

constexpr float kInitialFilterStateLevel = 0.0f;

// Instant attack.
constexpr float kAttackFilterConstant = 0.0f;

// Limiter decay constant.
// Computed as `10 ** (-1/20 * subframe_duration / kDecayMs)` where:
// - `subframe_duration` is `kFrameDurationMs / kSubFramesInFrame`;
// - `kDecayMs` is defined in agc2_testing_common.h.
constexpr float kDecayFilterConstant = 0.9971259f;

}  // namespace

FixedDigitalLevelEstimator::FixedDigitalLevelEstimator(
    int sample_rate_hz,
    ApmDataDumper* apm_data_dumper)
    : apm_data_dumper_(apm_data_dumper),
      filter_state_level_(kInitialFilterStateLevel) {
  SetSampleRate(sample_rate_hz);
  CheckParameterCombination();
  RTC_DCHECK(apm_data_dumper_);
  apm_data_dumper_->DumpRaw("agc2_level_estimator_samplerate", sample_rate_hz);
}

void FixedDigitalLevelEstimator::CheckParameterCombination() {
  RTC_DCHECK_GT(samples_in_frame_, 0);
  RTC_DCHECK_LE(kSubFramesInFrame, samples_in_frame_);
  RTC_DCHECK_EQ(samples_in_frame_ % kSubFramesInFrame, 0);
  RTC_DCHECK_GT(samples_in_sub_frame_, 1);
}

std::array<float, kSubFramesInFrame> FixedDigitalLevelEstimator::ComputeLevel(
    const AudioFrameView<const float>& float_frame) {
  RTC_DCHECK_GT(float_frame.num_channels(), 0);
  RTC_DCHECK_EQ(float_frame.samples_per_channel(), samples_in_frame_);

  // Compute max envelope without smoothing.
  std::array<float, kSubFramesInFrame> envelope{};
  for (int channel_idx = 0; channel_idx < float_frame.num_channels();
       ++channel_idx) {
    const auto channel = float_frame.channel(channel_idx);
    for (int sub_frame = 0; sub_frame < kSubFramesInFrame; ++sub_frame) {
      for (int sample_in_sub_frame = 0;
           sample_in_sub_frame < samples_in_sub_frame_; ++sample_in_sub_frame) {
        envelope[sub_frame] =
            std::max(envelope[sub_frame],
                     std::abs(channel[sub_frame * samples_in_sub_frame_ +
                                      sample_in_sub_frame]));
      }
    }
  }

  // Make sure envelope increases happen one step earlier so that the
  // corresponding *gain decrease* doesn't miss a sudden signal
  // increase due to interpolation.
  for (int sub_frame = 0; sub_frame < kSubFramesInFrame - 1; ++sub_frame) {
    if (envelope[sub_frame] < envelope[sub_frame + 1]) {
      envelope[sub_frame] = envelope[sub_frame + 1];
    }
  }

  // Add attack / decay smoothing.
  for (int sub_frame = 0; sub_frame < kSubFramesInFrame; ++sub_frame) {
    const float envelope_value = envelope[sub_frame];
    if (envelope_value > filter_state_level_) {
      envelope[sub_frame] = envelope_value * (1 - kAttackFilterConstant) +
                            filter_state_level_ * kAttackFilterConstant;
    } else {
      envelope[sub_frame] = envelope_value * (1 - kDecayFilterConstant) +
                            filter_state_level_ * kDecayFilterConstant;
    }
    filter_state_level_ = envelope[sub_frame];

    // Dump data for debug.
    RTC_DCHECK(apm_data_dumper_);
    const auto channel = float_frame.channel(0);
    apm_data_dumper_->DumpRaw("agc2_level_estimator_samples",
                              samples_in_sub_frame_,
                              &channel[sub_frame * samples_in_sub_frame_]);
    apm_data_dumper_->DumpRaw("agc2_level_estimator_level",
                              envelope[sub_frame]);
  }

  return envelope;
}

void FixedDigitalLevelEstimator::SetSampleRate(int sample_rate_hz) {
  samples_in_frame_ =
      rtc::CheckedDivExact(sample_rate_hz * kFrameDurationMs, 1000);
  samples_in_sub_frame_ =
      rtc::CheckedDivExact(samples_in_frame_, kSubFramesInFrame);
  CheckParameterCombination();
}

void FixedDigitalLevelEstimator::Reset() {
  filter_state_level_ = kInitialFilterStateLevel;
}

}  // namespace webrtc
