#include #include #include #include #include namespace psemek::audio { namespace { struct compressor_impl : stream { compressor_impl(stream_ptr stream, float volume_threshold, float strength, float envelope_attack, float envelope_release, float knee) : stream_(std::move(stream)) , volume_threshold_log_(std::log(volume_threshold)) , strength_(strength) , envelope_attack_multiplier_(smoothness_to_multiplier(envelope_attack)) , envelope_release_multiplier_(smoothness_to_multiplier(envelope_release)) , knee_range_(math::expand(math::interval::singleton(volume_threshold_log_), std::log(knee) * 0.5f)) {} std::optional length() const override { return stream_->length(); } std::size_t read(util::span samples) override { auto count = stream_->read(samples); for (std::size_t i = 0; i < count; i += 2) { float v = std::max(std::abs(samples[i]), std::abs(samples[i + 1])); float multiplier = (v > envelope_) ? envelope_attack_multiplier_ : envelope_release_multiplier_; envelope_ += (v - envelope_) * multiplier; float strength = strength_; float envelope_log = std::log(envelope_); if (math::contains(knee_range_, envelope_log)) strength *= math::unlerp(knee_range_, envelope_log); float log_gain = strength * std::min(0.f, volume_threshold_log_ - envelope_log); float gain = std::exp(log_gain); samples[i + 0] *= gain; samples[i + 1] *= gain; } return count; } std::size_t played() const override { return stream_->played(); } private: stream_ptr stream_; float volume_threshold_log_; float strength_; float envelope_attack_multiplier_; float envelope_release_multiplier_; math::interval knee_range_; float envelope_ = 0.f; }; } stream_ptr compressor(stream_ptr stream, float volume_threshold, float strength, float envelope_attack, float envelope_release, float knee) { return std::make_shared(std::move(stream), volume_threshold, strength, envelope_attack, envelope_release, knee); } stream_ptr limiter(stream_ptr stream, float volume_threshold, float envelope_attack, float envelope_release, float knee) { return compressor(std::move(stream), volume_threshold, 1.f, envelope_attack, envelope_release, knee); } }