From c1779117371054048f4c17618c6d09c7f8879cca Mon Sep 17 00:00:00 2001 From: lisyarus Date: Sat, 18 Jul 2026 19:03:56 +0300 Subject: [PATCH] Add generic envelope filter and re-implement fade in/out using it --- .../include/psemek/audio/effect/envelope.hpp | 67 ++++++++++++++++++ libs/audio/source/effect/fade_in.cpp | 68 ++---------------- libs/audio/source/effect/fade_out.cpp | 70 ++----------------- 3 files changed, 78 insertions(+), 127 deletions(-) create mode 100644 libs/audio/include/psemek/audio/effect/envelope.hpp diff --git a/libs/audio/include/psemek/audio/effect/envelope.hpp b/libs/audio/include/psemek/audio/effect/envelope.hpp new file mode 100644 index 00000000..b4607456 --- /dev/null +++ b/libs/audio/include/psemek/audio/effect/envelope.hpp @@ -0,0 +1,67 @@ +#include +#include + +namespace psemek::audio +{ + + namespace + { + + template + struct envelope_impl + : stream + { + envelope_impl(stream_ptr stream, EnvelopeFn envelope_fn, std::optional truncate) + : stream_(std::move(stream)) + , envelope_fn_(std::move(envelope_fn)) + , remaining_(truncate ? std::optional{truncate->samples()} : std::nullopt) + , total_length_(truncate ? stream_->played() + *remaining_ : stream_->length()) + {} + + std::optional length() const override + { + return total_length_; + } + + std::size_t read(util::span samples) override + { + std::size_t const max_count = remaining_ ? std::min(*remaining_, samples.size()) : samples.size(); + + auto const count = stream_->read(samples.prefix(max_count)); + if (remaining_) + *remaining_ -= count; + + auto out = samples.data(); + for (std::size_t i = 0; i < count; i += 2) + { + float gain = envelope_fn_(played_); + out[0] *= gain; + out[1] *= gain; + out += 2; + played_ += duration::from_frames(1); + } + + return count; + } + + std::size_t played() const override + { + return stream_->played(); + } + + private: + stream_ptr stream_; + EnvelopeFn envelope_fn_; + duration played_; + std::optional remaining_; + std::optional total_length_; + }; + + } + + template + stream_ptr envelope(stream_ptr stream, EnvelopeFn envelope_fn, std::optional truncate = {}) + { + return std::make_shared>(std::move(stream), std::move(envelope_fn), truncate); + } +} diff --git a/libs/audio/source/effect/fade_in.cpp b/libs/audio/source/effect/fade_in.cpp index ea4c0384..70980ebb 100644 --- a/libs/audio/source/effect/fade_in.cpp +++ b/libs/audio/source/effect/fade_in.cpp @@ -1,71 +1,15 @@ -#include -#include - -#include +#include +#include +#include namespace psemek::audio { - namespace - { - - struct fade_in_impl - : stream - { - fade_in_impl(stream_ptr stream, duration length, duration start) - : stream_(std::move(stream)) - , length_(length) - , start_(start) - {} - - std::optional length() const override - { - return stream_->length(); - } - - std::size_t played() const override - { - return stream_->played(); - } - - std::size_t read(util::span samples) override - { - auto const count = stream_->read(samples); - std::fill(samples.begin(), samples.begin() + std::min(count, start_.samples()), 0.f); - - if (count <= start_.samples()) - { - start_ -= count; - } - else - { - for (std::size_t i = start_.samples(); i < count; i += 2) - { - float m = static_cast(std::min(current_, length_.samples())) / length_.samples(); - - samples[i + 0] *= m; - samples[i + 1] *= m; - - current_ += 2; - } - start_ = duration{}; - } - - return count; - } - - private: - stream_ptr stream_; - duration length_; - duration start_; - std::size_t current_ = 0; - }; - - } - stream_ptr fade_in(stream_ptr stream, duration length, duration start) { - return std::make_shared(std::move(stream), length, start); + return envelope(std::move(stream), [start, length](duration time){ + return math::clamp(static_cast(time.frames() - start.frames()) / length.frames(), {0.f, 1.f}); + }); } } diff --git a/libs/audio/source/effect/fade_out.cpp b/libs/audio/source/effect/fade_out.cpp index 80b9465e..53b7f29b 100644 --- a/libs/audio/source/effect/fade_out.cpp +++ b/libs/audio/source/effect/fade_out.cpp @@ -1,75 +1,15 @@ #include -#include - -#include +#include +#include namespace psemek::audio { - namespace - { - - struct fade_out_impl - : stream - { - fade_out_impl(stream_ptr stream, duration length, duration start) - : stream_(std::move(stream)) - , length_(length) - , start_(start) - , total_length_(stream_->played() + start_.samples() + length_.samples()) - {} - - std::optional length() const override - { - return total_length_; - } - - std::size_t played() const override - { - return stream_->played(); - } - - std::size_t read(util::span samples) override - { - if (current_ >= length_.samples()) - return 0; - - auto result = stream_->read(samples); - - if (result <= start_.samples()) - { - start_ -= duration::from_samples(result); - } - else - { - for (std::size_t i = start_.samples(); i < result; i += 2) - { - float m = static_cast(length_.samples() - std::min(current_, length_.samples())) / length_.samples(); - - samples[i + 0] *= m; - samples[i + 1] *= m; - - current_ += 2; - } - start_ = {}; - } - - return result; - } - - private: - stream_ptr stream_; - duration length_; - duration start_; - std::size_t current_ = 0; - std::size_t total_length_ = 0; - }; - - } - stream_ptr fade_out(stream_ptr stream, duration length, duration start) { - return std::make_shared(std::move(stream), length, start); + return envelope(std::move(stream), [start, length](duration time){ + return math::clamp(static_cast(length.frames() + start.frames() - time.frames()) / length.frames(), {0.f, 1.f}); + }, start + length); } }