#include #include #include #include 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 values, std::vector segments) { if (values.size() != segments.size() + 1) throw util::exception("Invalid envelope"); std::optional truncate; if (!keep_playing) { truncate = duration::from_frames(0); for (auto const & segment : segments) *truncate += segment.duration; } std::vector 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 values, std::vector 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 values, std::vector segments) { return envelope_impl(std::move(stream), true, std::move(values), std::move(segments)); } }