85 lines
2.4 KiB
C++
85 lines
2.4 KiB
C++
#include <psemek/audio/effect/envelope.hpp>
|
|
#include <psemek/audio/effect/envelope_base.hpp>
|
|
#include <psemek/util/exception.hpp>
|
|
#include <psemek/util/enum.hpp>
|
|
|
|
namespace psemek::audio
|
|
{
|
|
|
|
namespace
|
|
{
|
|
|
|
float compute_param(float v0, float v1, segment::segment_type type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case segment::linear:
|
|
return v1 - v0;
|
|
case segment::exponential:
|
|
// f(t) = Aexp(Bt)
|
|
// t=0 => A=v0
|
|
// t=1 => Aexp(B)=v1 => B = log(v1/v0)
|
|
return std::log(v1 / v0);
|
|
}
|
|
|
|
throw util::unknown_enum_value_exception(type);
|
|
}
|
|
|
|
float interpolate(float v0, float /* v1 */, float t, segment::segment_type type, float param)
|
|
{
|
|
switch (type)
|
|
{
|
|
case segment::linear:
|
|
return v0 + param * t;
|
|
case segment::exponential:
|
|
return v0 * std::exp(param * t);
|
|
}
|
|
|
|
throw util::unknown_enum_value_exception(type);
|
|
}
|
|
|
|
stream_ptr envelope_impl(stream_ptr stream, bool keep_playing, std::vector<float> values, std::vector<segment> segments)
|
|
{
|
|
if (values.size() != segments.size() + 1)
|
|
throw util::exception("Invalid envelope");
|
|
|
|
std::optional<duration> truncate;
|
|
if (!keep_playing)
|
|
{
|
|
truncate = duration::from_frames(0);
|
|
for (auto const & segment : segments)
|
|
*truncate += segment.duration;
|
|
}
|
|
|
|
std::vector<float> params(segments.size());
|
|
for (std::size_t i = 0; i < segments.size(); ++i)
|
|
params[i] = compute_param(values[i], values[i + 1], segments[i].type);
|
|
|
|
return envelope_base(std::move(stream), [values = std::move(values), segments = std::move(segments), params = std::move(params), index = 0, played = std::size_t{0}](duration) mutable {
|
|
played += 1;
|
|
while (index < segments.size() && played >= segments[index].duration.frames())
|
|
{
|
|
++index;
|
|
played = 0;
|
|
}
|
|
|
|
if (index == segments.size())
|
|
return values.back();
|
|
|
|
return interpolate(values[index], values[index + 1], played * 1.f / segments[index].duration.frames(), segments[index].type, params[index]);
|
|
}, truncate);
|
|
}
|
|
|
|
}
|
|
|
|
stream_ptr envelope(stream_ptr stream, std::vector<float> values, std::vector<segment> segments)
|
|
{
|
|
return envelope_impl(std::move(stream), false, std::move(values), std::move(segments));
|
|
}
|
|
|
|
stream_ptr envelope(stream_ptr stream, keep_playing_tag, std::vector<float> values, std::vector<segment> segments)
|
|
{
|
|
return envelope_impl(std::move(stream), true, std::move(values), std::move(segments));
|
|
}
|
|
|
|
}
|