172 std::span<const double>
ww;
173 std::span<const double>
dd;
185 double sample_rate)
noexcept {
186 if (!k.valid || !(sample_rate > 0.0))
201 return limits.max_boost_db;
202 return static_cast<float>(std::clamp(-10.0 * std::log10(
ratio),
203 -
static_cast<double>(limits.max_cut_db),
204 static_cast<double>(limits.max_boost_db)));
214 const std::size_t n =
215 std::min({response.size(), s.ww.size(), s.dd.size(), s.wd_re.size(), s.wd_im.size()});
216 const double m = std::clamp(mix, 0.0, 1.0);
217 const double a = m * m, b = (1.0 - m) * (1.0 - m),
c = 2.0 * m * (1.0 - m);
218 double out = 0.0, flat = 0.0;
219 for (std::size_t k = 0; k < n; ++k) {
220 const auto h = response[k];
221 out +=
a * std::norm(h) * s.ww[k] + b * s.dd[k] +
222 c * (h.real() * s.wd_re[k] - h.imag() * s.wd_im[k]);
223 flat +=
a * s.ww[k] + b * s.dd[k] +
c * s.wd_re[k];
233 std::span<const double> spectrum_power,
235 const std::size_t n = std::min(response_power.size(), spectrum_power.size());
236 double total = 0.0,
energy = 0.0;
237 for (std::size_t
i = 0;
i < n; ++
i) {
238 const double w = spectrum_power[
i];
242 energy += w * std::max(0.0, response_power[
i]);
259template <
typename WeightFn>
262 double total = 0.0,
energy = 0.0;
263 for (
const auto& band : bands) {
264 const double w = weight(band.lo_hz, band.hi_hz);
268 energy += w * std::max(0.0, band.power_gain);
281 cumulative_.assign(bins + 1, 0.0);
285 void assign(std::span<const double> power,
double bin_hz)
noexcept {
286 bin_hz_ = bin_hz > 0.0 ? bin_hz : 1.0;
287 bins_ = std::min(power.size(), cumulative_.empty() ? 0 : cumulative_.size() - 1);
290 cumulative_[0] = 0.0;
292 for (std::size_t k = 0; k < bins_; ++k) {
293 sum += std::max(0.0, power[k]);
294 cumulative_[k + 1] = sum;
297 for (std::size_t k = 0; k <= bins_; ++k)
298 cumulative_[k] /= sum;
302 [[nodiscard]]
double weight(
double lo_hz,
double hi_hz)
const noexcept {
303 if (bins_ == 0 || !(hi_hz > lo_hz))
305 return at_(hi_hz) - at_(lo_hz);
309 [[nodiscard]]
double at_(
double hz)
const noexcept {
310 const double x = std::max(0.0, hz / bin_hz_ + 0.5);
311 const auto i =
static_cast<std::size_t
>(std::floor(x));
313 return cumulative_[bins_];
314 const double t = x -
static_cast<double>(
i);
315 return cumulative_[
i] + t * (cumulative_[
i + 1] - cumulative_[
i]);
318 std::vector<double> cumulative_;
319 std::size_t bins_ = 0;
320 double bin_hz_ = 1.0;
334 fft_ = std::make_unique<pulp::signal::FftT<float>>(n_);
335 work_.assign(
static_cast<std::size_t
>(n_), {});
338 return fft_ && fft_->ready();
342 bool compute(std::span<const double> magnitude,
double floor,
343 std::span<std::complex<double>> out)
noexcept {
344 const auto bins =
static_cast<std::size_t
>(n_ / 2 + 1);
345 if (!
prepared() || magnitude.size() < bins || out.size() < bins)
347 const double lo = floor > 0.0 ? floor : 1.0e-12;
348 for (std::size_t k = 0; k < bins; ++k)
349 work_[k] = {
static_cast<float>(std::log(std::max(lo, magnitude[k]))), 0.0f};
350 for (std::size_t k = bins; k < static_cast<std::size_t>(n_); ++k)
351 work_[k] = work_[
static_cast<std::size_t
>(n_) - k];
352 fft_->inverse(work_.data());
353 const auto half =
static_cast<std::size_t
>(n_ / 2);
354 for (std::size_t q = 1; q < half; ++q) {
355 work_[q] = {2.0f * work_[q].real(), 0.0f};
356 work_[
static_cast<std::size_t
>(n_) - q] = {0.0f, 0.0f};
358 work_[0] = {work_[0].real(), 0.0f};
359 work_[half] = {work_[half].real(), 0.0f};
360 fft_->forward(work_.data());
361 for (std::size_t k = 0; k < bins; ++k)
362 out[k] = std::exp(std::complex<double>(work_[k].real(), work_[k].imag()));
368 std::unique_ptr<pulp::signal::FftT<float>> fft_;
369 std::vector<std::complex<float>> work_;
388 [[nodiscard]]
static int band_of(
double hz)
noexcept {
393 return std::clamp(
static_cast<int>(std::floor(x)), 0,
kBands - 1);
395 [[nodiscard]]
static double centre_hz(
int band)
noexcept {
397 (
static_cast<double>(band) + 0.5) /
static_cast<double>(
kBands));
442 level_fast_ = level_slow_ = 0.0;
443 close_run_ = last_close_run_ = level_frames_ = 0;
446 [[nodiscard]]
int run() const noexcept {
451 return level_fast_ > 0.0 && level_slow_ > 0.0 ? 10.0 * std::log10(level_slow_ / level_fast_)
462 const bool levelled = frame_lufs >
kNoLevel;
464 const double p = std::pow(10.0, frame_lufs / 10.0);
474 }
else if (close_run_ > 0) {
475 last_close_run_ = close_run_;
483 const double gap = std::abs(
static_cast<double>(fast_db) - slow_db);
488 if (!(gap > threshold)) {
491 run_ = std::max(0, run_ - 2);
492 huge_run_ = std::max(0, huge_run_ - 2);
497 const bool started = run_ == 0;
503 huge_run_ = gap >
huge_db ? huge_run_ + 1 : std::max(0, huge_run_ - 2);
517 last_drop_ = via_drop;
530 Event end_run_() noexcept {
531 const bool had = run_ > 0;
537 void clear_fit_() noexcept {
539 fit_x_ = fit_y_ = fit_xx_ = fit_xy_ = 0.0;
541 void fit_(
double y)
noexcept {
542 const double x =
static_cast<double>(fit_n_++);
549 [[nodiscard]]
double slope_() const noexcept {
552 const double n =
static_cast<double>(fit_n_);
553 const double den = n * fit_xx_ - fit_x_ * fit_x_;
554 return den > 0.0 ? (n * fit_xy_ - fit_x_ * fit_y_) / den : 0.0;
559 double level_fast_ = 0.0, level_slow_ = 0.0;
560 int close_run_ = 0, last_close_run_ = 0, level_frames_ = 0;
562 double fit_x_ = 0.0, fit_y_ = 0.0, fit_xx_ = 0.0, fit_xy_ = 0.0;
563 bool last_drop_ =
false;
572 std::span<const double>
prior = {}) {
574 channels_ = std::max(1, channels);
577 hop_ = fft_size_ / 2;
578 bins_ = fft_size_ / 2 + 1;
579 bin_hz_ = sample_rate_ /
static_cast<double>(fft_size_);
580 const auto nb =
static_cast<std::size_t
>(bins_);
581 fft_ = std::make_unique<pulp::signal::FftT<float>>(fft_size_);
585 history_.assign(
static_cast<std::size_t
>(
legs * channels_ * fft_size_), 0.0f);
586 frame_in_.assign(
static_cast<std::size_t
>(fft_size_), 0.0f);
587 wet_spectrum_.assign(
static_cast<std::size_t
>(fft_size_), {});
588 dry_spectrum_.assign(
static_cast<std::size_t
>(fft_size_), {});
589 for (
auto* v : {&frame_ww_, &frame_dd_, &frame_re_, &frame_im_, &k_power_, &prior_})
593 fast_shape_.assign(nb, 0.0);
594 slow_shape_.assign(nb, 0.0);
595 candidate_.assign(nb);
596 previous_.assign(nb);
597 previous_shape_.assign(nb, 0.0);
598 arrived_shape_.assign(nb, 0.0);
599 alt_a_shape_.assign(nb, 0.0);
600 alt_b_shape_.assign(nb, 0.0);
602 flat_.assign(nb, 1.0);
603 zero_.assign(nb, 0.0);
605 double window_energy = 0.0;
606 for (
const float w : window_)
607 window_energy +=
static_cast<double>(
w) * w;
610 power_to_mean_square_ =
611 window_energy > 0.0 ? 2.0 / (
static_cast<double>(fft_size_) * window_energy) : 0.0;
613 for (
int b = 0;
b < bins_; ++
b)
614 k_power_[
static_cast<std::size_t
>(b)] =
617 if (
prior.size() >= nb)
618 for (std::size_t b = 0;
b < nb; ++
b)
619 total += std::max(0.0,
prior[b]);
620 for (std::size_t b = 0;
b < nb; ++
b)
622 total > 0.0 ? std::max(0.0,
prior[b]) / total : 1.0 /
static_cast<double>(nb);
623 const double hop_seconds =
static_cast<double>(hop_) / sample_rate_;
624 alpha_ =
config.time_constant_seconds > 0.0
627 fast_alpha_ =
config.fast_time_constant_seconds > 0.0
631 local_alpha_ =
config.local_level_seconds > 0.0
634 alt_alpha_ =
config.alternating_time_constant_seconds > 0.0
637 memory_frames_ =
static_cast<std::int64_t
>(std::ceil(
config.memory_seconds /
hop_seconds));
638 geometry_rate_ = sample_rate_;
639 geometry_channels_ = channels_;
641 std::clamp(
static_cast<int>(std::ceil(
config.level_drop_window_seconds /
hop_seconds)),
643 dry_drop_frames_ = std::clamp(
644 static_cast<int>(std::ceil(
config.dry_level_drop_window_seconds /
hop_seconds)), 1,
646 prepared_ = fft_ && fft_->ready() &&
static_cast<int>(window_.size()) == fft_size_;
653 if (
config.fft_size > 0) {
655 while (n <
config.fft_size && n < 65536)
678 candidate_frames_ = 0;
679 candidate_active_ =
false;
680 std::fill(fast_shape_.begin(), fast_shape_.end(), 0.0);
681 std::fill(slow_shape_.begin(), slow_shape_.end(), 0.0);
683 prior_fade_scale_ = 1.0;
685 observed_frames_ = 0;
701 std::fill(history_.begin(), history_.end(), 0.0f);
714 candidate_frames_ = 0;
715 candidate_active_ =
true;
716 candidate_skip_ = fft_size_ / std::max(1, hop_) - 1;
720 candidate_active_ =
false;
721 candidate_frames_ = 0;
732 if (slow_frames_ > 0) {
733 previous_.copy_from(slow_);
734 std::copy(slow_shape_.begin(), slow_shape_.end(), previous_shape_.begin());
735 previous_valid_ =
true;
736 frames_since_previous_ = 0;
738 alternating_ =
false;
739 if (candidate_active_ && candidate_frames_ > 0) {
740 slow_.copy_from(candidate_);
741 fast_.copy_from(candidate_);
742 slow_frames_ = candidate_frames_;
743 fast_frames_ = std::min(candidate_frames_, fast_equivalent_frames_());
751 std::copy(fast_shape_.begin(), fast_shape_.end(), slow_shape_.begin());
752 std::copy(fast_shape_.begin(), fast_shape_.end(), arrived_shape_.begin());
753 arrived_valid_ =
true;
754 frames_since_arrival_ = 0;
759 if (slow_frames_ > 0)
769 if (!previous_valid_)
771 std::copy(previous_shape_.begin(), previous_shape_.end(), alt_a_shape_.begin());
772 std::copy(slow_shape_.begin(), slow_shape_.end(), alt_b_shape_.begin());
773 const auto mix = [](std::vector<double>&
a,
const std::vector<double>& b) {
774 for (std::size_t k = 0; k <
a.size(); ++k)
775 a[k] = 0.5 * (
a[k] + b[k]);
777 mix(slow_.ww, previous_.ww);
778 mix(slow_.dd, previous_.dd);
779 mix(slow_.re, previous_.re);
780 mix(slow_.im, previous_.im);
781 for (std::size_t k = 0; k < slow_shape_.size(); ++k)
782 slow_shape_[k] = 0.5 * (alt_a_shape_[k] + alt_b_shape_[k]);
783 slow_frames_ = std::max<std::int64_t>(
784 slow_frames_,
static_cast<std::int64_t
>(std::ceil(1.0 / std::max(alpha_, 1
e-9))));
786 previous_valid_ =
false;
797 std::copy(arrived_shape_.begin(), arrived_shape_.end(), alt_a_shape_.begin());
798 std::copy(slow_shape_.begin(), slow_shape_.end(), alt_b_shape_.begin());
800 arrived_valid_ =
false;
801 previous_valid_ =
false;
806 return arrived_valid_;
809 if (!config_.weight_by_material)
810 return {flat_, flat_, flat_, zero_};
811 return {arrived_shape_, arrived_shape_, arrived_shape_, zero_};
814 return previous_valid_;
820 if (!config_.weight_by_material)
821 return {flat_, flat_, flat_, zero_};
822 return {previous_shape_, previous_shape_, previous_shape_, zero_};
826 if (!config_.weight_by_material)
827 return {flat_, flat_, flat_, zero_};
828 const auto& v = which == 0 ? alt_a_shape_ : alt_b_shape_;
829 return {v, v, v, zero_};
831 [[nodiscard]] std::uint64_t
merges() const noexcept {
837 return prepared_ && geometry_rate_ ==
sample_rate &&
838 geometry_channels_ == std::max(1, channels) &&
851 if (!config_.track_dry_leg)
853 std::copy(fast_.dd.begin(), fast_.dd.end(), fast_.ww.begin());
854 std::copy(fast_.dd.begin(), fast_.dd.end(), fast_.re.begin());
855 std::fill(fast_.im.begin(), fast_.im.end(), 0.0);
856 slow_.copy_from(fast_);
857 slow_frames_ = std::max<std::int64_t>(1, fast_equivalent_frames_());
859 const double t = fast_.total();
860 for (
int b = 0; b < bins_; ++b) {
861 const auto i =
static_cast<std::size_t
>(b);
862 slow_shape_[
i] = fast_shape_[
i] = t > 0.0 ? fast_.ww[
i] / t : 0.0;
871 settle_frames_ = fft_size_ / std::max(1, hop_);
878 if (!config_.track_dry_leg)
880 for (
auto*
e : {&slow_, &fast_}) {
881 std::fill(
e->re.begin(),
e->re.end(), 0.0);
882 std::fill(
e->im.begin(),
e->im.end(), 0.0);
892 template <
typename OnFrame>
893 void push(
const float*
const* wet,
const float*
const* dry,
int channels,
int num_samples,
894 OnFrame&& on_frame)
noexcept {
895 if (!prepared_ || wet ==
nullptr || num_samples <= 0)
897 const int count = std::min(channels, channels_);
898 const bool two = config_.track_dry_leg;
900 while (done < num_samples) {
901 const int chunk = std::min(num_samples - done, hop_ - hop_pos_);
902 for (
int leg = 0; leg < (two ? 2 : 1); ++leg) {
903 const float*
const* x = leg == 0 ? wet : (dry !=
nullptr ? dry : wet);
904 for (
int ch = 0; ch < count; ++ch)
905 record_(ring_(leg, ch), x[ch], done, chunk);
906 for (
int ch = count; ch < channels_; ++ch)
907 record_silence_(ring_(leg, ch), chunk);
909 write_pos_ = (write_pos_ + chunk) % fft_size_;
910 filled_ = std::min(fft_size_, filled_ + chunk);
913 if (hop_pos_ == hop_) {
915 if (filled_ == fft_size_) {
922 template <
typename OnFrame>
923 void push(
const float*
const* x,
int channels,
int num_samples, OnFrame&& on_frame)
noexcept {
924 push(x,
nullptr, channels, num_samples, on_frame);
926 void push(
const float*
const* x,
int channels,
int num_samples)
noexcept {
927 push(x,
nullptr, channels, num_samples, [](
int) {});
934 if (!prepared_ || (observed_frames_ == 0 && prior_weight_ >= 1.0))
936 const auto&
e = published_;
938 for (
int b = 0; b < bins_; ++b) {
939 const auto i =
static_cast<std::size_t
>(b);
941 out.ww[
static_cast<std::size_t
>(band)] +=
static_cast<float>(
e.ww[
i]);
942 out.dd[
static_cast<std::size_t
>(band)] +=
static_cast<float>(
e.dd[
i]);
943 out.re[
static_cast<std::size_t
>(band)] +=
static_cast<float>(
e.re[
i]);
944 out.im[
static_cast<std::size_t
>(band)] +=
static_cast<float>(
e.im[
i]);
950 const auto j =
static_cast<std::size_t
>(band);
951 out.ww[j] =
static_cast<float>(out.ww[j] / total);
952 out.dd[j] =
static_cast<float>(out.dd[j] / total);
953 out.re[j] =
static_cast<float>(out.re[j] / total);
954 out.im[j] =
static_cast<float>(out.im[j] / total);
956 out.
level_ms = level_local_ * power_to_mean_square_;
974 if (!prepared_ || !in.valid || !(in.level_ms > 0.0) || !(power_to_mean_square_ > 0.0))
976 std::array<int, SpectrumBands::kBands> count{};
977 for (
int b = 0; b < bins_; ++b)
978 ++count[
static_cast<std::size_t
>(
980 Estimate&
e = candidate_;
985 const double total = 1.0;
986 level_local_ = in.level_ms / power_to_mean_square_;
988 const auto j =
static_cast<std::size_t
>(band);
991 const auto b =
static_cast<std::size_t
>(
994 e.ww[b] += total * in.ww[j];
995 e.dd[b] += total * in.dd[j];
996 e.re[b] += total * in.re[j];
997 e.im[b] += total * in.im[j];
999 for (
int b = 0; b < bins_; ++b) {
1000 const auto i =
static_cast<std::size_t
>(b);
1005 const double share = total /
static_cast<double>(count[j]);
1006 e.ww[
i] += share * in.ww[j];
1007 e.dd[
i] += share * in.dd[j];
1008 e.re[
i] += share * in.re[j];
1009 e.im[
i] += share * in.im[j];
1011 if (!config_.track_dry_leg) {
1014 std::fill(
e.im.begin(),
e.im.end(), 0.0);
1017 const double t =
e.total();
1018 for (
int b = 0; b < bins_; ++b) {
1019 const auto i =
static_cast<std::size_t
>(b);
1020 prior_[
i] = t > 0.0 ? std::max(0.0,
e.ww[
i]) / t : 0.0;
1029 const double t = slow_.total();
1030 for (
int b = 0; b < bins_; ++b) {
1031 const auto i =
static_cast<std::size_t
>(b);
1032 slow_shape_[
i] = fast_shape_[
i] = t > 0.0 ? slow_.ww[
i] / t : 0.0;
1034 prior_weight_ = 0.0;
1035 slow_frames_ =
static_cast<std::int64_t
>(std::ceil(1.0 / std::max(alpha_, 1
e-9)));
1036 fast_frames_ = fast_equivalent_frames_();
1040 candidate_frames_ = 0;
1041 candidate_active_ =
false;
1042 observed_frames_ = std::max<std::uint64_t>(observed_frames_, 1);
1056 [[nodiscard]]
int hop() const noexcept {
1061 return hop_ - hop_pos_;
1063 [[nodiscard]]
int bins() const noexcept {
1066 [[nodiscard]]
double bin_hz() const noexcept {
1070 return sample_rate_;
1073 return static_cast<double>(hop_) / sample_rate_;
1079 return observed_frames_;
1083 return gated_frames_;
1086 return level_drops_;
1088 [[nodiscard]] std::uint64_t
restarts() const noexcept {
1093 return last_frame_lufs_;
1097 return prior_weight_;
1101 return {prior_, prior_, prior_, zero_};
1108 prior_fade_scale_ = std::clamp(scale, 0.0, 1.0);
1112 return last_audible_;
1116 [[nodiscard]] std::span<const double>
spectrum() const noexcept {
1117 return config_.weight_by_material ? std::span<const double>(published_.ww) : flat_;
1121 return legs_of_(published_);
1125 return legs_of_(published_fast_);
1130 if (!config_.weight_by_material)
1131 return {flat_, flat_, flat_, zero_};
1132 return {fast_shape_, fast_shape_, fast_shape_, zero_};
1135 if (!config_.weight_by_material)
1136 return {flat_, flat_, flat_, zero_};
1137 return {slow_shape_, slow_shape_, slow_shape_, zero_};
1140 return shape_frames_;
1143 [[nodiscard]]
double weight(
double lo_hz,
double hi_hz)
const noexcept {
1144 return cdf_.weight(lo_hz, hi_hz);
1151 static constexpr int kMaxRecent = 128;
1156 void rearm_shapes_() noexcept {
1157 shape_frames_ =
static_cast<std::int64_t
>(std::ceil(1.0 / std::max(alpha_, 1
e-9)));
1159 void forget_memory_() noexcept {
1160 previous_valid_ =
false;
1161 arrived_valid_ =
false;
1162 alternating_ =
false;
1163 frames_since_previous_ = 0;
1167 std::vector<double> ww, dd, re, im;
1168 void assign(std::size_t n) {
1169 for (
auto* v : {&ww, &dd, &re, &im})
1172 void clear() noexcept {
1173 for (
auto* v : {&ww, &dd, &re, &im})
1174 std::fill(
v->begin(),
v->end(), 0.0);
1176 void copy_from(
const Estimate&
o)
noexcept {
1177 std::copy(
o.ww.begin(),
o.ww.end(), ww.begin());
1178 std::copy(
o.dd.begin(),
o.dd.end(), dd.begin());
1179 std::copy(
o.re.begin(),
o.re.end(), re.begin());
1180 std::copy(
o.im.begin(),
o.im.end(), im.begin());
1182 void scale(
double k)
noexcept {
1183 for (
auto* v : {&ww, &dd, &re, &im})
1187 [[nodiscard]]
double total() const noexcept {
1189 for (
const double x : ww)
1195 [[nodiscard]] LegSpectra legs_of_(
const Estimate&
e)
const noexcept {
1196 if (!config_.weight_by_material)
1197 return {flat_, flat_, flat_, zero_};
1198 if (!config_.track_dry_leg)
1199 return {
e.ww,
e.ww,
e.ww, zero_};
1200 return {
e.ww,
e.dd,
e.re,
e.im};
1202 [[nodiscard]] std::int64_t fast_equivalent_frames_() const noexcept {
1203 return static_cast<std::int64_t
>(std::ceil(1.0 / std::max(fast_alpha_, 1
e-9)));
1205 [[nodiscard]]
double level_total_() const noexcept {
1206 return slow_frames_ > 0 ? slow_.total() : 0.0;
1209 [[nodiscard]]
float* ring_(
int leg,
int ch)
noexcept {
1210 return history_.data() +
static_cast<std::size_t
>((leg * channels_ + ch) * fft_size_);
1212 void record_(
float* ring,
const float* src,
int offset,
int count)
noexcept {
1215 int pos = write_pos_;
1217 const float v = src[offset +
i];
1218 ring[pos] = std::isfinite(v) ?
v : 0.0f;
1219 if (++pos == fft_size_)
1223 void record_silence_(
float* ring,
int count)
noexcept {
1224 int pos = write_pos_;
1227 if (++pos == fft_size_)
1231 void transform_(
float* ring, std::vector<std::complex<float>>& out)
noexcept {
1233 for (
int i = 0;
i < fft_size_; ++
i) {
1234 int at = write_pos_ +
i;
1235 if (at >= fft_size_)
1237 frame_in_[
static_cast<std::size_t
>(
i)] =
1238 ring[at] * window_[
static_cast<std::size_t
>(
i)];
1240 fft_->forward_real(frame_in_.data(), out.data());
1243 void accumulate_(Estimate&
e,
double a)
noexcept {
1244 const bool two = config_.track_dry_leg;
1245 for (
int b = 0;
b < bins_; ++
b) {
1246 const auto i =
static_cast<std::size_t
>(
b);
1247 e.ww[
i] +=
a * (frame_ww_[
i] -
e.ww[
i]);
1249 e.dd[
i] +=
a * (frame_dd_[
i] -
e.dd[
i]);
1250 e.re[
i] +=
a * (frame_re_[
i] -
e.re[
i]);
1251 e.im[
i] +=
a * (frame_im_[
i] -
e.im[
i]);
1256 void frame_() noexcept {
1257 const bool two = config_.track_dry_leg;
1258 for (
auto* v : {&frame_ww_, &frame_dd_, &frame_re_, &frame_im_})
1259 std::fill(
v->begin(),
v->end(), 0.0);
1260 for (
int ch = 0; ch < channels_; ++ch) {
1261 transform_(ring_(0, ch), wet_spectrum_);
1263 transform_(ring_(1, ch), dry_spectrum_);
1264 for (
int b = 0;
b < bins_; ++
b) {
1265 const auto i =
static_cast<std::size_t
>(
b);
1266 const std::complex<double>
w(wet_spectrum_[
i].real(), wet_spectrum_[
i].imag());
1267 frame_ww_[
i] += std::norm(w);
1269 const std::complex<double>
d(dry_spectrum_[
i].real(), dry_spectrum_[
i].imag());
1270 frame_dd_[
i] += std::norm(d);
1271 const auto x =
w * std::conj(d);
1272 frame_re_[
i] +=
x.real();
1273 frame_im_[
i] +=
x.imag();
1278 for (
int b = 0;
b < bins_; ++
b) {
1279 const auto i =
static_cast<std::size_t
>(
b);
1280 const double kp = k_power_[
i];
1285 total += frame_ww_[
i];
1287 const double mean_square = total * power_to_mean_square_;
1288 last_frame_lufs_ = mean_square > 0.0 ? -0.691 + 10.0 * std::log10(mean_square) : -1.0e9;
1289 last_audible_ = last_frame_lufs_ >= config_.gate_lufs;
1290 if (!last_audible_) {
1294 if (settle_frames_ > 0) {
1306 level_local_ > 0.0 ? level_local_ + local_alpha_ * (total - level_local_) : total;
1309 double norm = level_local_ > 0.0 ? 1.0 / level_local_ : 0.0;
1310 if (config_.level_weight_exponent != 1.0 && total > 0.0 && level_local_ > 0.0)
1311 norm = std::pow(total / level_local_, config_.level_weight_exponent) / total;
1312 for (
int b = 0;
b < bins_; ++
b) {
1313 const auto i =
static_cast<std::size_t
>(
b);
1314 frame_ww_[
i] *= norm;
1315 frame_dd_[
i] *= norm;
1316 frame_re_[
i] *= norm;
1317 frame_im_[
i] *= norm;
1324 const double slow_alpha = alternating_ ? alt_alpha_ : alpha_;
1325 const double a_slow =
1326 slow_alpha >= 1.0 ? 1.0 : std::max(slow_alpha, 1.0 /
static_cast<double>(slow_frames_));
1327 const double a_fast = fast_alpha_ >= 1.0
1329 : std::max(fast_alpha_, 1.0 /
static_cast<double>(fast_frames_));
1330 accumulate_(slow_, a_slow);
1331 accumulate_(fast_, a_fast);
1332 if (candidate_active_) {
1333 if (candidate_skip_ > 0) {
1336 ++candidate_frames_;
1337 accumulate_(candidate_, 1.0 /
static_cast<double>(candidate_frames_));
1340 if (previous_valid_ && ++frames_since_previous_ > memory_frames_)
1341 previous_valid_ =
false;
1342 if (arrived_valid_ && ++frames_since_arrival_ > memory_frames_)
1343 arrived_valid_ =
false;
1349 const double shape_alpha = alternating_ ? alt_alpha_ : alpha_;
1350 const double sa = shape_alpha >= 1.0
1352 : std::max(shape_alpha, 1.0 /
static_cast<double>(shape_frames_));
1353 const double fa = fast_alpha_ >= 1.0
1355 : std::max(fast_alpha_, 1.0 /
static_cast<double>(shape_frames_));
1356 const double inv = total > 0.0 ? 1.0 / total : 0.0;
1357 for (
int b = 0;
b < bins_; ++
b) {
1358 const auto i =
static_cast<std::size_t
>(
b);
1359 const double x = frame_ww_[
i] * inv;
1360 slow_shape_[
i] += sa * (
x - slow_shape_[
i]);
1361 fast_shape_[
i] += fa * (
x - fast_shape_[
i]);
1364 double dry_total = total;
1367 for (
const double x : frame_dd_)
1370 level_drop_(total, dry_total);
1371 prior_weight_ = std::max(0.0, prior_weight_ - prior_step_ * prior_fade_scale_);
1381 void level_drop_(
double wet_total,
double dry_total)
noexcept {
1382 const auto slot =
static_cast<std::size_t
>(recent_pos_);
1383 recent_[slot] = wet_total;
1384 recent_dry_[slot] = dry_total;
1385 recent_pos_ = (recent_pos_ + 1) % kMaxRecent;
1386 recent_count_ = std::min(recent_count_ + 1, kMaxRecent);
1387 if (!(config_.level_drop_db > 0.0))
1389 const auto max_of = [&](
const std::array<double, kMaxRecent>&
ring,
int frames) {
1391 for (
int k = 1;
k <= frames; ++
k)
1393 m, ring[
static_cast<std::size_t
>((recent_pos_ - k + kMaxRecent) % kMaxRecent)]);
1396 const double floor = std::pow(10.0, -config_.level_drop_db / 10.0);
1397 double rw = 1.0, rd = 1.0, wet_max = 0.0, dry_max = 0.0;
1398 if (recent_count_ >= level_drop_frames_) {
1399 wet_max = max_of(recent_, level_drop_frames_);
1400 const double wet_level = sum_(slow_.ww);
1401 if (wet_level > 0.0 && wet_max < wet_level * floor)
1402 rw = wet_max / wet_level;
1407 if (config_.track_dry_leg && recent_count_ >= dry_drop_frames_) {
1408 dry_max = max_of(recent_dry_, dry_drop_frames_);
1409 const double dry_level = sum_(slow_.dd);
1410 if (dry_level > 0.0 && dry_max < dry_level * floor)
1411 rd = dry_max / dry_level;
1413 if (!config_.track_dry_leg)
1415 drop_run_ = rw < 1.0 ? drop_run_ + 1 : 0;
1416 dry_drop_run_ = rd < 1.0 ? dry_drop_run_ + 1 : 0;
1419 if (dry_drop_run_ < 2)
1421 if (rw >= 1.0 && rd >= 1.0)
1424 rescale_legs_(slow_, rw, rd);
1425 const double fw = sum_(fast_.ww);
1426 const double fd = config_.track_dry_leg ? sum_(fast_.dd) : fw;
1427 rescale_legs_(fast_, rw < 1.0 && fw > wet_max && wet_max > 0.0 ? wet_max / fw : 1.0,
1428 rd < 1.0 && fd > dry_max && dry_max > 0.0 ? dry_max / fd : 1.0);
1429 if (previous_valid_)
1430 rescale_legs_(previous_, rw, rd);
1441 void rescale_legs_(Estimate&
e,
double rw,
double rd)
noexcept {
1442 const double rx = std::sqrt(rw * rd);
1443 for (
auto& x :
e.ww)
1445 if (!config_.track_dry_leg)
1447 for (
auto& x :
e.dd)
1449 for (
auto& x :
e.re)
1451 for (
auto& x :
e.im)
1454 [[nodiscard]]
static double sum_(
const std::vector<double>& v)
noexcept {
1456 for (
const double x : v)
1463 void blend_into_(
const Estimate& avg, std::int64_t frames, Estimate& out)
noexcept {
1466 if (frames == 0 && observed_frames_ > 0)
1468 const double total = frames > 0 ? avg.total() : 0.0;
1469 const double pw = frames > 0 && total > 0.0 ? prior_weight_ : 1.0;
1470 const double scale = total > 0.0 ? (1.0 - pw) / total : 0.0;
1471 const bool two = config_.track_dry_leg;
1472 for (
int b = 0;
b < bins_; ++
b) {
1473 const auto i =
static_cast<std::size_t
>(
b);
1474 const double p = pw * prior_[
i];
1479 out.im[
i] =
scale * avg.im[
i];
1483 void publish_() noexcept {
1484 if (published_.ww.size() !=
static_cast<std::size_t
>(bins_)) {
1488 blend_into_(slow_, slow_frames_, published_);
1489 blend_into_(fast_, fast_frames_, published_fast_);
1494 void size_published_() {
1495 published_.assign(
static_cast<std::size_t
>(bins_));
1496 published_fast_.assign(
static_cast<std::size_t
>(bins_));
1499 double sample_rate_ = 48000.0;
1501 LoudnessCompensationConfig config_{};
1502 int fft_size_ = 4096;
1505 double bin_hz_ = 48000.0 / 4096.0;
1506 double power_to_mean_square_ = 0.0;
1507 double alpha_ = 0.0;
1508 double fast_alpha_ = 0.0;
1509 double prior_step_ = 1.0;
1510 double prior_fade_scale_ = 1.0;
1511 double prior_weight_ = 1.0;
1512 double level_local_ = 0.0;
1513 double local_alpha_ = 1.0;
1514 bool prepared_ =
false;
1515 std::unique_ptr<pulp::signal::FftT<float>> fft_;
1516 std::vector<float> window_;
1517 std::vector<float> history_;
1518 std::vector<float> frame_in_;
1519 std::vector<std::complex<float>> wet_spectrum_;
1520 std::vector<std::complex<float>> dry_spectrum_;
1521 std::vector<double> frame_ww_, frame_dd_, frame_re_, frame_im_;
1522 Estimate slow_, fast_, published_, published_fast_;
1523 std::vector<double> k_power_;
1524 std::vector<double> prior_;
1525 std::vector<double> flat_;
1526 std::vector<double> zero_;
1528 std::int64_t slow_frames_ = 0, fast_frames_ = 0, shape_frames_ = 0;
1529 Estimate candidate_, previous_;
1530 std::int64_t candidate_frames_ = 0;
1531 int candidate_skip_ = 0;
1532 bool candidate_active_ =
false;
1533 std::vector<double> previous_shape_, alt_a_shape_, alt_b_shape_, arrived_shape_;
1534 bool arrived_valid_ =
false;
1535 std::int64_t frames_since_arrival_ = 0;
1536 bool previous_valid_ =
false;
1537 bool alternating_ =
false;
1538 std::int64_t frames_since_previous_ = 0, memory_frames_ = 0;
1539 std::uint64_t merges_ = 0;
1540 double alt_alpha_ = 0.0;
1541 double geometry_rate_ = 0.0;
1542 int geometry_channels_ = 0;
1543 std::vector<double> fast_shape_, slow_shape_;
1544 std::uint64_t observed_frames_ = 0;
1545 std::uint64_t gated_frames_ = 0;
1546 std::uint64_t level_drops_ = 0;
1547 std::uint64_t restarts_ = 0;
1548 std::array<double, kMaxRecent> recent_{}, recent_dry_{};
1549 int recent_pos_ = 0, recent_count_ = 0, level_drop_frames_ = 9, drop_run_ = 0;
1550 int dry_drop_frames_ = 9, dry_drop_run_ = 0;
1551 int settle_frames_ = 0;
1552 double last_frame_lufs_ = -1.0e9;
1553 bool last_audible_ =
false;
1584 const bool moved = !primed_ || db != value_db_;
1592 bool follow(
float wanted_db,
double seconds,
double slew_db_per_second)
noexcept {
1593 return follow(wanted_db, seconds, slew_db_per_second, 0.0, 0.0);
1598 bool follow(
float wanted_db,
double seconds,
double min_slew_db_per_second,
1599 double rate_per_second,
double max_slew_db_per_second,
1600 double knee_db = 0.0) noexcept {
1603 float next = wanted_db;
1604 if (min_slew_db_per_second > 0.0) {
1605 const double distance = std::abs(
static_cast<double>(wanted_db) - value_db_);
1606 double slew = std::max(min_slew_db_per_second,
1607 rate_per_second * std::max(0.0, distance - knee_db));
1608 if (max_slew_db_per_second > 0.0)
1609 slew = std::min(slew, max_slew_db_per_second);
1610 slew = std::max(slew, min_slew_db_per_second);
1611 const auto step =
static_cast<float>(slew * std::max(0.0, seconds));
1612 next = value_db_ + std::clamp(wanted_db - value_db_, -step, step);
1614 if (next == value_db_)
1621 bool primed_ =
false;
1622 float value_db_ = 0.0f;
Definition loudness_compensation.hpp:566
const LoudnessCompensationConfig & config() const noexcept
Definition loudness_compensation.hpp:1075
bool prepared() const noexcept
Definition loudness_compensation.hpp:1050
int samples_to_next_frame() const noexcept
Samples until the next frame boundary of the grid.
Definition loudness_compensation.hpp:1060
int hop() const noexcept
Definition loudness_compensation.hpp:1056
void legs_uncorrelated() noexcept
Freeze engaged: the wet leg is now a held past, unrelated to the live input from here on.
Definition loudness_compensation.hpp:877
void restart() noexcept
The material changed.
Definition loudness_compensation.hpp:731
void push(const float *const *x, int channels, int num_samples) noexcept
Definition loudness_compensation.hpp:926
double weight(double lo_hz, double hi_hz) const noexcept
The slow wet estimate's energy between two frequencies; the whole weighs 1.
Definition loudness_compensation.hpp:1143
void reset() noexcept
Cold start: back to the prior, and the frame grid restarts at the next sample.
Definition loudness_compensation.hpp:671
LegSpectra fast_shape_legs() const noexcept
The change detector's pair: fast and slow averages of each audible frame's wet SHAPE (level-independe...
Definition loudness_compensation.hpp:1129
void prepare(double sample_rate, int channels, const LoudnessCompensationConfig &config, std::span< const double > prior={})
Control thread; allocates.
Definition loudness_compensation.hpp:571
void export_bands(SpectrumBands &out) const noexcept
The slow estimate, band-compressed (allocation-free).
Definition loudness_compensation.hpp:932
LegSpectra arrived_shape_legs() const noexcept
Definition loudness_compensation.hpp:808
int bins() const noexcept
Definition loudness_compensation.hpp:1063
std::uint64_t observed_frames() const noexcept
Definition loudness_compensation.hpp:1078
const SpectrumCdf & cdf() const noexcept
Definition loudness_compensation.hpp:1146
bool has_arrived() const noexcept
Definition loudness_compensation.hpp:805
bool prepared_for(double sample_rate, int channels, const LoudnessCompensationConfig &config) const noexcept
The estimator has the geometry prepare() would give these arguments.
Definition loudness_compensation.hpp:835
void set_prior_fade_scale(double scale) noexcept
How fast the prior fades from the next frame on, as a multiple of prior_seconds' rate (1: as configur...
Definition loudness_compensation.hpp:1107
std::uint64_t gated_frames() const noexcept
Frames under the absolute gate (silence).
Definition loudness_compensation.hpp:1082
bool last_frame_audible() const noexcept
Whether the last frame was audible (above the absolute gate).
Definition loudness_compensation.hpp:1111
LegSpectra legs() const noexcept
All the legs' slow estimates on the wet leg's scale.
Definition loudness_compensation.hpp:1120
Import
How import_bands() treats a saved estimate.
Definition loudness_compensation.hpp:961
@ warm
As the estimate itself (the same stream, carried across a change of geometry).
Definition loudness_compensation.hpp:968
@ prior
As a better prior: playback starts at the saved estimate's level and the material's own estimate take...
Definition loudness_compensation.hpp:965
LegSpectra alternate_shape_legs(int which) const noexcept
The two remembered halves of an alternation (0: the earlier, 1: the later).
Definition loudness_compensation.hpp:825
void wet_becomes_dry() noexcept
Freeze released: the wet leg becomes the live input again, whose spectrum the dry leg kept warm throu...
Definition loudness_compensation.hpp:850
double last_frame_lufs() const noexcept
K-weighted loudness of the last frame's wet leg (LUFS-equivalent).
Definition loudness_compensation.hpp:1092
LegSpectra slow_shape_legs() const noexcept
Definition loudness_compensation.hpp:1134
double bin_hz() const noexcept
Definition loudness_compensation.hpp:1066
void settle_alternation() noexcept
The material came back to what the LAST restart went to, after something else in between that never c...
Definition loudness_compensation.hpp:796
std::int64_t shape_frames() const noexcept
Definition loudness_compensation.hpp:1139
double sample_rate() const noexcept
Definition loudness_compensation.hpp:1069
void cancel_candidate() noexcept
The run broke: that was a passing moment, not new material.
Definition loudness_compensation.hpp:719
LegSpectra prior_legs() const noexcept
The prior's per-bin share (sums to one), as legs.
Definition loudness_compensation.hpp:1100
std::uint64_t level_drops() const noexcept
Definition loudness_compensation.hpp:1085
void import_bands(const SpectrumBands &in, Import how=Import::warm) noexcept
A band's energy is spread evenly over the bins whose centres fall in it (or given to the bin nearest ...
Definition loudness_compensation.hpp:973
std::int64_t frames_since_restart() const noexcept
Audible frames averaged since the last restart (or cold start).
Definition loudness_compensation.hpp:842
std::span< const double > spectrum() const noexcept
The slow (wet) estimate per bin, K-weighted, normalised to sum 1.
Definition loudness_compensation.hpp:1116
LegSpectra previous_shape_legs() const noexcept
Definition loudness_compensation.hpp:819
static int bins_for(double sample_rate, const LoudnessCompensationConfig &config) noexcept
Definition loudness_compensation.hpp:664
bool has_previous() const noexcept
Definition loudness_compensation.hpp:813
double prior_weight() const noexcept
The prior's remaining share of the estimate (1 until material is heard).
Definition loudness_compensation.hpp:1096
void restart_grid() noexcept
A transport jump or host reset: the frame grid restarts at the next sample (so how the stream is chop...
Definition loudness_compensation.hpp:700
static int fft_size_for(double sample_rate, const LoudnessCompensationConfig &config) noexcept
The FFT size prepare() picks for a rate and config.
Definition loudness_compensation.hpp:651
void begin_candidate() noexcept
A detector run began: collect the would-be new material's own energy average, so a confirmed change s...
Definition loudness_compensation.hpp:712
std::uint64_t merges() const noexcept
Definition loudness_compensation.hpp:831
std::uint64_t restarts() const noexcept
Definition loudness_compensation.hpp:1088
bool alternating() const noexcept
Definition loudness_compensation.hpp:816
LegSpectra fast_legs() const noexcept
The same of the fast estimate.
Definition loudness_compensation.hpp:1124
void merge_with_previous() noexcept
The material went back to what it was before the last change: it alternates (a kick-only bar and a fu...
Definition loudness_compensation.hpp:768
void push(const float *const *wet, const float *const *dry, int channels, int num_samples, OnFrame &&on_frame) noexcept
Feed planar samples of the wet leg and, when tracking it, the dry leg (nullptr: the dry leg is the we...
Definition loudness_compensation.hpp:893
double hop_seconds() const noexcept
Definition loudness_compensation.hpp:1072
void push(const float *const *x, int channels, int num_samples, OnFrame &&on_frame) noexcept
Definition loudness_compensation.hpp:923
int fft_size() const noexcept
Definition loudness_compensation.hpp:1053
What the make-up gain is heading for.
Definition loudness_compensation.hpp:1569
void reset() noexcept
Definition loudness_compensation.hpp:1571
float value_db() const noexcept
Definition loudness_compensation.hpp:1578
bool follow(float wanted_db, double seconds, double slew_db_per_second) noexcept
Move toward wanted_db by at most slew x seconds (no limit when the slew is <= 0).
Definition loudness_compensation.hpp:1592
bool primed() const noexcept
Definition loudness_compensation.hpp:1575
bool follow(float wanted_db, double seconds, double min_slew_db_per_second, double rate_per_second, double max_slew_db_per_second, double knee_db=0.0) noexcept
The proportional form described above; the speed-up counts only the distance beyond knee_db.
Definition loudness_compensation.hpp:1598
bool retarget(float db) noexcept
Jump to db. Returns whether the target changed.
Definition loudness_compensation.hpp:1583
Detects a genuine change of material from the gap between the make-up the fast and the slow estimates...
Definition loudness_compensation.hpp:409
double drop_threshold_db
Quieter after louder.
Definition loudness_compensation.hpp:424
int frames
Definition loudness_compensation.hpp:412
void hold_off() noexcept
Definition loudness_compensation.hpp:522
Event
Definition loudness_compensation.hpp:436
@ run_started
Definition loudness_compensation.hpp:436
@ none
Definition loudness_compensation.hpp:436
@ run_broken
Definition loudness_compensation.hpp:436
@ confirmed
Definition loudness_compensation.hpp:436
double level_slow_alpha
Definition loudness_compensation.hpp:434
Event observe(float fast_db, float slow_db, double frame_lufs=kNoLevel) noexcept
One audible frame: the make-up the fast and slow shapes give, and (optional) the frame's loudness,...
Definition loudness_compensation.hpp:461
int run() const noexcept
Definition loudness_compensation.hpp:446
int drop_min_loud_frames
... and only after louder material that lasted: the level must have stayed within half of drop_level_...
Definition loudness_compensation.hpp:431
int huge_frames
Definition loudness_compensation.hpp:414
double threshold_db
Definition loudness_compensation.hpp:411
double drop_level_db
Definition loudness_compensation.hpp:425
double level_fast_alpha
Per-frame coefficients of the fast and slow level averages (power).
Definition loudness_compensation.hpp:433
static constexpr double kNoLevel
Definition loudness_compensation.hpp:527
void reset() noexcept
Definition loudness_compensation.hpp:438
double level_drop_db() const noexcept
The fast level's fall below the slow one, dB (>= 0 while quieter).
Definition loudness_compensation.hpp:450
double huge_db
Definition loudness_compensation.hpp:413
int holdoff_frames
Definition loudness_compensation.hpp:415
double max_decay_db_per_frame
Definition loudness_compensation.hpp:426
bool last_was_drop() const noexcept
Whether the last confirmation came through the quieter-after-louder path.
Definition loudness_compensation.hpp:455
The minimum-phase response with a given magnitude on an FFT grid, by the folded real cepstrum: ln|H| ...
Definition loudness_compensation.hpp:329
bool compute(std::span< const double > magnitude, double floor, std::span< std::complex< double > > out) noexcept
magnitude: N/2 + 1 linear magnitudes. out: N/2 + 1 values.
Definition loudness_compensation.hpp:342
void prepare(int fft_size)
Control thread; allocates. fft_size: the grid (bins = N/2 + 1).
Definition loudness_compensation.hpp:332
bool prepared() const noexcept
Definition loudness_compensation.hpp:337
The normalised cumulative of a power spectrum on a uniform bin grid.
Definition loudness_compensation.hpp:277
double weight(double lo_hz, double hi_hz) const noexcept
Energy between two frequencies; the whole spectrum weighs 1.
Definition loudness_compensation.hpp:302
void assign(std::span< const double > power, double bin_hz) noexcept
Allocation-free once prepared for at least power.size() bins.
Definition loudness_compensation.hpp:285
void prepare(std::size_t bins)
Control thread: size for bins.
Definition loudness_compensation.hpp:280
static std::vector< SampleType > generate(int size, Type type, SampleType param=SampleType{0.0f})
Definition windowing.hpp:56
@ hann
Definition windowing.hpp:45
Evaluate what a biquad (or a cascade of them) does to the spectrum.
Multi-channel metering: sample peak, RMS, K-weighted momentary and gated integrated loudness,...
@ count
sentinel: number of real capabilities (keep LAST)
Definition canvas_capability.hpp:38
MidiEvent at(MidiEvent event, std::int32_t sample_offset) noexcept
Definition utility_contract.hpp:140
@ d
Definition pitch.hpp:22
@ b
Definition pitch.hpp:31
@ ring
Definition string.hpp:18
Definition additive_bank.hpp:218
float band_makeup_gain_db(std::span< const BandPowerGain > bands, WeightFn &&weight, MakeupLimits limits) noexcept
The single-leg form over bands, each weighted by weight(lo_hz, hi_hz) – the material's energy between...
Definition loudness_compensation.hpp:260
double section_magnitude(const BiquadCoefficientsT< SampleType > &c, double omega)
Linear magnitude |H(e^jω)| of one section at normalized angular frequency ω = 2π·f/fs (radians/sample...
Definition frequency_response.hpp:45
@ c
Definition spectrum_trace.hpp:35
@ energy
sum h^2 = 1 per output ear (default; convolution reverb).
Definition zero_latency_convolver_support.hpp:160
float clamp_makeup_db_(double ratio, MakeupLimits limits) noexcept
Definition loudness_compensation.hpp:199
float makeup_gain_db(std::span< const double > response_power, std::span< const double > spectrum_power, MakeupLimits limits) noexcept
The single-leg, real-response form: per-bin power response |H|^2 against a per-bin power spectrum on ...
Definition loudness_compensation.hpp:232
float blend_makeup_gain_db(std::span< const std::complex< double > > response, const LegSpectra &s, double mix, MakeupLimits limits) noexcept
Make-up, dB, for the two-leg model in the file comment: response is the wet leg's complex response pe...
Definition loudness_compensation.hpp:211
@ ratio
base_frequency_hz * ratio.
Definition fm_operator_engine.hpp:20
@ a
Definition formant_filter_bank.hpp:20
@ i
Definition formant_filter_bank.hpp:20
@ o
Definition formant_filter_bank.hpp:20
@ e
Definition formant_filter_bank.hpp:20
double angular_frequency(double freq_hz, double sample_rate)
Angular frequency (radians/sample) of freq_hz at sample_rate, clamped to [0, π].
Definition frequency_response.hpp:97
double k_weighting_power(double hz, const pulp::signal::KWeightingCoefficients &k, double sample_rate) noexcept
|K(f)|^2 for BS.1770 K-weighting at sample_rate, through Pulp's own K-weighting design and biquad res...
Definition loudness_compensation.hpp:183
KWeightingCoefficients k_weighting_coefficients(double sample_rate)
Return the two-stage K-weighting design for a sample rate.
Definition multi_channel_meter.hpp:91
void scale(const float *a, float scalar, float *dst, std::size_t count) noexcept
@ m
Definition input_events.hpp:40
@ p
Definition input_events.hpp:40
@ k
Definition input_events.hpp:40
@ x
Definition input_events.hpp:41
@ v
Definition input_events.hpp:41
@ t
Definition input_events.hpp:41
@ w
Definition input_events.hpp:41
One band of a band-wise response: its frequency span and power gain.
Definition loudness_compensation.hpp:250
double power_gain
Definition loudness_compensation.hpp:253
double hi_hz
Definition loudness_compensation.hpp:252
double lo_hz
Definition loudness_compensation.hpp:251
Definition multi_channel_meter.hpp:81
The long-term spectra of the two legs on one bin grid, K-weighted, on a common scale.
Definition loudness_compensation.hpp:171
std::span< const double > ww
Definition loudness_compensation.hpp:172
std::span< const double > wd_re
Definition loudness_compensation.hpp:174
std::span< const double > dd
Definition loudness_compensation.hpp:173
std::span< const double > wd_im
Definition loudness_compensation.hpp:175
Definition multi_channel_meter.hpp:77
Every tunable of the stateful stages.
Definition loudness_compensation.hpp:85
double fast_time_constant_seconds
Time constant of the FAST estimate, seconds: the change detector's, and what the slow estimate restar...
Definition loudness_compensation.hpp:102
double gate_lufs
Frames below this K-weighted loudness (LUFS-equivalent over the frame, channels summed as BS....
Definition loudness_compensation.hpp:99
double memory_seconds
How long the material before the last change is remembered.
Definition loudness_compensation.hpp:122
double material_slew_max_db_per_second
Definition loudness_compensation.hpp:128
float max_boost_db
Definition loudness_compensation.hpp:149
double level_weight_exponent
Definition loudness_compensation.hpp:117
double material_slew_knee_db
The proportional speed-up counts only the distance beyond this, so a groove's wobble around the targe...
Definition loudness_compensation.hpp:131
double change_seconds
Definition loudness_compensation.hpp:136
double dry_level_drop_window_seconds
The same for the live (dry) leg, which has no change detector.
Definition loudness_compensation.hpp:111
double level_drop_db
Level-drop rule: when no audible frame in the last level_drop_window_seconds came within level_drop_d...
Definition loudness_compensation.hpp:108
double change_max_decay_db_per_second
Definition loudness_compensation.hpp:145
double change_drop_threshold_db
Quieter after louder (MaterialChangeDetector::drop_threshold_db): a gap above change_drop_threshold_d...
Definition loudness_compensation.hpp:143
bool track_dry_leg
true: push() takes a dry leg too, and the estimator keeps Pdd and Pwd.
Definition loudness_compensation.hpp:154
double local_level_seconds
Frames are weighted by their power relative to a running average of frame power over this long (see L...
Definition loudness_compensation.hpp:116
double change_drop_min_loud_seconds
Definition loudness_compensation.hpp:146
int fft_size
Definition loudness_compensation.hpp:160
double material_slew_rate_per_second
Definition loudness_compensation.hpp:127
double level_drop_window_seconds
Definition loudness_compensation.hpp:109
float max_cut_db
Make-up range.
Definition loudness_compensation.hpp:148
double change_threshold_db
Change detector (MaterialChangeDetector): the fast and slow estimates' make-up for the current shape ...
Definition loudness_compensation.hpp:135
double change_drop_level_db
Definition loudness_compensation.hpp:144
double material_slew_db_per_second
Slowest rate the material moves the make-up target, dB/s (<= 0: no limit) – and the proportional spee...
Definition loudness_compensation.hpp:126
double alternating_time_constant_seconds
Time constant of the slow estimate while the material alternates between two remembered states (see L...
Definition loudness_compensation.hpp:120
double change_huge_db
Definition loudness_compensation.hpp:137
double prior_seconds
How long the prior keeps a say once material is heard: its weight falls linearly from 1 to 0 over thi...
Definition loudness_compensation.hpp:95
bool weight_by_material
false: every bin weighs the same (ignores the material and K).
Definition loudness_compensation.hpp:152
double change_huge_seconds
Definition loudness_compensation.hpp:138
double time_constant_seconds
Time constant of the material spectrum, seconds.
Definition loudness_compensation.hpp:88
double frame_seconds
Analysis frame length, seconds; the FFT size is the next power of two at or above it (4096 at 44....
Definition loudness_compensation.hpp:157
Definition loudness_compensation.hpp:163
float max_boost_db
Definition loudness_compensation.hpp:165
float max_cut_db
Definition loudness_compensation.hpp:164
A band-compressed, rate-independent copy of a LongTermSpectrum's estimate, for saving with a session ...
Definition loudness_compensation.hpp:380
std::array< float, kBands > im
Definition loudness_compensation.hpp:386
std::array< float, kBands > re
Definition loudness_compensation.hpp:386
static double centre_hz(int band) noexcept
Definition loudness_compensation.hpp:395
static int band_of(double hz) noexcept
Definition loudness_compensation.hpp:388
std::array< float, kBands > ww
Definition loudness_compensation.hpp:386
double level_ms
Definition loudness_compensation.hpp:385
static constexpr double kHighHz
Definition loudness_compensation.hpp:383
bool valid
Definition loudness_compensation.hpp:384
static constexpr int kBands
Definition loudness_compensation.hpp:381
std::array< float, kBands > dd
Definition loudness_compensation.hpp:386
static constexpr double kLowHz
Definition loudness_compensation.hpp:382