// curve_tessellate.cpp — see curve_tessellate.h. Pure geometry; no host types. #include "core/instrument/ui/curve_tessellate.h" #include #include #include "core/util/curve_law.h" namespace reasampler::instrument::ui { using StrokePoint = reasampler::ui::StrokePoint; namespace { StrokePoint pt(double x, double y) { return StrokePoint{static_cast(x), static_cast(y)}; } // The samples strictly BETWEEN two nodes. The endpoints are the caller's, so a shared node is // emitted once and the polyline carries no zero-length joint. void appendInterior(int x0, int y0, int x1, int y1, double exponent, std::vector& out) { const int span = std::abs(x1 - x0); if (span < 2 || y1 == y0 || exponent == util::kCurveNeutral) return; const double dx = static_cast(x1 - x0); const double dy = static_cast(y1 - y0); for (int i = 1; i < span; ++i) { // phi is exact at every column, so x lands on the integer column and the last interior // sample is one column short of the end node. const double phi = static_cast(i) / static_cast(span); out.push_back(pt(static_cast(x0) + dx * phi, static_cast(y0) + dy * util::curveMap(phi, exponent))); } } } // namespace double segmentCurve(const StageEnvelope& env, EnvNode endNode) { switch (endNode) { case EnvNode::AttackEnd: return env.attackCurve; case EnvNode::DecayEnd: return env.decayCurve; case EnvNode::ReleaseEnd: return env.releaseCurve; default: return util::kCurveNeutral; } } void buildEnvelopeTrace(const std::vector& poly, const StageEnvelope& env, int xLo, int xHi, std::vector& out) { out.clear(); bool started = false; int px = 0; int py = 0; for (const EnvVertex& v : poly) { if (v.knot) continue; const int x = std::max(xLo, std::min(xHi, v.x)); if (started) appendInterior(px, py, x, v.y, segmentCurve(env, v.node), out); out.push_back(pt(x, v.y)); started = true; px = x; py = v.y; } } } // namespace reasampler::instrument::ui