Add generic envelope filter and re-implement fade in/out using it

This commit is contained in:
Nikita Lisitsa 2026-07-18 19:03:56 +03:00
parent dbf5117dc6
commit c177911737
3 changed files with 78 additions and 127 deletions

View file

@ -0,0 +1,67 @@
#include <psemek/audio/stream.hpp>
#include <psemek/audio/duration.hpp>
namespace psemek::audio
{
namespace
{
template <typename EnvelopeFn>
struct envelope_impl
: stream
{
envelope_impl(stream_ptr stream, EnvelopeFn envelope_fn, std::optional<duration> 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<std::size_t> length() const override
{
return total_length_;
}
std::size_t read(util::span<float> 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<std::size_t> remaining_;
std::optional<std::size_t> total_length_;
};
}
template <typename EnvelopeFn>
stream_ptr envelope(stream_ptr stream, EnvelopeFn envelope_fn, std::optional<duration> truncate = {})
{
return std::make_shared<envelope_impl<EnvelopeFn>>(std::move(stream), std::move(envelope_fn), truncate);
}
}

View file

@ -1,71 +1,15 @@
#include <psemek/audio/effect/fade_in.hpp>
#include <psemek/audio/constants.hpp>
#include <algorithm>
#include <psemek/audio/effect/fade_out.hpp>
#include <psemek/audio/effect/envelope.hpp>
#include <psemek/math/interval.hpp>
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<std::size_t> length() const override
{
return stream_->length();
}
std::size_t played() const override
{
return stream_->played();
}
std::size_t read(util::span<float> samples) override
{
auto const count = stream_->read(samples);
std::fill(samples.begin(), samples.begin() + std::min<std::size_t>(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<float>(std::min<std::size_t>(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<fade_in_impl>(std::move(stream), length, start);
return envelope(std::move(stream), [start, length](duration time){
return math::clamp(static_cast<float>(time.frames() - start.frames()) / length.frames(), {0.f, 1.f});
});
}
}

View file

@ -1,75 +1,15 @@
#include <psemek/audio/effect/fade_out.hpp>
#include <psemek/audio/constants.hpp>
#include <algorithm>
#include <psemek/audio/effect/envelope.hpp>
#include <psemek/math/interval.hpp>
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<std::size_t> length() const override
{
return total_length_;
}
std::size_t played() const override
{
return stream_->played();
}
std::size_t read(util::span<float> 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<float>(length_.samples() - std::min<std::size_t>(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<fade_out_impl>(std::move(stream), length, start);
return envelope(std::move(stream), [start, length](duration time){
return math::clamp(static_cast<float>(length.frames() + start.frames() - time.frames()) / length.frames(), {0.f, 1.f});
}, start + length);
}
}