/* * 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/limiter_db_gain_curve.h" #include #include "common_audio/include/audio_util.h" #include "modules/audio_processing/agc2/agc2_common.h" #include "rtc_base/checks.h" namespace webrtc { namespace { double ComputeKneeStart(double max_input_level_db, double knee_smoothness_db, double compression_ratio) { RTC_CHECK_LT((compression_ratio - 1.0) * knee_smoothness_db / (2.0 * compression_ratio), max_input_level_db); return -knee_smoothness_db / 2.0 - max_input_level_db / (compression_ratio - 1.0); } std::array ComputeKneeRegionPolynomial(double knee_start_dbfs, double knee_smoothness_db, double compression_ratio) { const double a = (1.0 - compression_ratio) / (2.0 * knee_smoothness_db * compression_ratio); const double b = 1.0 - 2.0 * a * knee_start_dbfs; const double c = a * knee_start_dbfs * knee_start_dbfs; return {{a, b, c}}; } double ComputeLimiterD1(double max_input_level_db, double compression_ratio) { return (std::pow(10.0, -max_input_level_db / (20.0 * compression_ratio)) * (1.0 - compression_ratio) / compression_ratio) / kMaxAbsFloatS16Value; } constexpr double ComputeLimiterD2(double compression_ratio) { return (1.0 - 2.0 * compression_ratio) / compression_ratio; } double ComputeLimiterI2(double max_input_level_db, double compression_ratio, double gain_curve_limiter_i1) { RTC_CHECK_NE(gain_curve_limiter_i1, 0.f); return std::pow(10.0, -max_input_level_db / (20.0 * compression_ratio)) / gain_curve_limiter_i1 / std::pow(kMaxAbsFloatS16Value, gain_curve_limiter_i1 - 1); } } // namespace LimiterDbGainCurve::LimiterDbGainCurve() : max_input_level_linear_(DbfsToFloatS16(max_input_level_db_)), knee_start_dbfs_(ComputeKneeStart(max_input_level_db_, knee_smoothness_db_, compression_ratio_)), knee_start_linear_(DbfsToFloatS16(knee_start_dbfs_)), limiter_start_dbfs_(knee_start_dbfs_ + knee_smoothness_db_), limiter_start_linear_(DbfsToFloatS16(limiter_start_dbfs_)), knee_region_polynomial_(ComputeKneeRegionPolynomial(knee_start_dbfs_, knee_smoothness_db_, compression_ratio_)), gain_curve_limiter_d1_( ComputeLimiterD1(max_input_level_db_, compression_ratio_)), gain_curve_limiter_d2_(ComputeLimiterD2(compression_ratio_)), gain_curve_limiter_i1_(1.0 / compression_ratio_), gain_curve_limiter_i2_(ComputeLimiterI2(max_input_level_db_, compression_ratio_, gain_curve_limiter_i1_)) { static_assert(knee_smoothness_db_ > 0.0f, ""); static_assert(compression_ratio_ > 1.0f, ""); RTC_CHECK_GE(max_input_level_db_, knee_start_dbfs_ + knee_smoothness_db_); } constexpr double LimiterDbGainCurve::max_input_level_db_; constexpr double LimiterDbGainCurve::knee_smoothness_db_; constexpr double LimiterDbGainCurve::compression_ratio_; double LimiterDbGainCurve::GetOutputLevelDbfs(double input_level_dbfs) const { if (input_level_dbfs < knee_start_dbfs_) { return input_level_dbfs; } else if (input_level_dbfs < limiter_start_dbfs_) { return GetKneeRegionOutputLevelDbfs(input_level_dbfs); } return GetCompressorRegionOutputLevelDbfs(input_level_dbfs); } double LimiterDbGainCurve::GetGainLinear(double input_level_linear) const { if (input_level_linear < knee_start_linear_) { return 1.0; } return DbfsToFloatS16( GetOutputLevelDbfs(FloatS16ToDbfs(input_level_linear))) / input_level_linear; } // Computes the first derivative of GetGainLinear() in `x`. double LimiterDbGainCurve::GetGainFirstDerivativeLinear(double x) const { // Beyond-knee region only. RTC_CHECK_GE(x, limiter_start_linear_ - 1e-7 * kMaxAbsFloatS16Value); return gain_curve_limiter_d1_ * std::pow(x / kMaxAbsFloatS16Value, gain_curve_limiter_d2_); } // Computes the integral of GetGainLinear() in the range [x0, x1]. double LimiterDbGainCurve::GetGainIntegralLinear(double x0, double x1) const { RTC_CHECK_LE(x0, x1); // Valid interval. RTC_CHECK_GE(x0, limiter_start_linear_); // Beyond-knee region only. auto limiter_integral = [this](const double& x) { return gain_curve_limiter_i2_ * std::pow(x, gain_curve_limiter_i1_); }; return limiter_integral(x1) - limiter_integral(x0); } double LimiterDbGainCurve::GetKneeRegionOutputLevelDbfs( double input_level_dbfs) const { return knee_region_polynomial_[0] * input_level_dbfs * input_level_dbfs + knee_region_polynomial_[1] * input_level_dbfs + knee_region_polynomial_[2]; } double LimiterDbGainCurve::GetCompressorRegionOutputLevelDbfs( double input_level_dbfs) const { return (input_level_dbfs - max_input_level_db_) / compression_ratio_; } } // namespace webrtc