/* -*- Mode: c++ -*- */ /*************************************************************************** * powermap.cc * * Fri Apr 17 23:06:12 CEST 2020 * Copyright 2020 André Nusser * andre.nusser@googlemail.com ****************************************************************************/ /* * This file is part of DrumGizmo. * * DrumGizmo is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * DrumGizmo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with DrumGizmo; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #include "powermap.h" #include #include namespace { using Power = Powermap::Power; using PowerPair = Powermap::PowerPair; Power h00(Power x) { return (1 + 2 * x) * pow(1 - x, 2); } Power h10(Power x) { return x * pow(1 - x, 2); } Power h01(Power x) { return x * x * (3 - 2 * x); } Power h11(Power x) { return x * x * (x - 1); } Power computeValue(const Power x, const PowerPair& P0, const PowerPair& P1, const Power m0, const Power m1) { const auto x0 = P0.in; const auto x1 = P1.in; const auto y0 = P0.out; const auto y1 = P1.out; const auto dx = x1 - x0; const auto x_prime = (x - x0)/dx; return h00(x_prime) * y0 + h10(x_prime) * dx * m0 + h01(x_prime) * y1 + h11(x_prime) * dx * m1; } } // end anonymous namespace Powermap::Powermap() { reset(); } Power Powermap::map(Power in) { assert(in >= 0. && in <= 1.); if (spline_needs_update) { updateSpline(); } Power out; if (in < fixed[0].in) { out = shelf ? fixed[0].out : computeValue(in, {0.,0.}, fixed[0], m[0], m[1]); } else if (in < fixed[1].in) { out = computeValue(in, fixed[0], fixed[1], m[1], m[2]); } else if (in < fixed[2].in) { out = computeValue(in, fixed[1], fixed[2], m[2], m[3]); } else { // in >= fixed[2].in out = shelf ? fixed[2].out : computeValue(in, fixed[2], {1.,1.}, m[3], m[4]); } assert(out >= 0. && out <= 1.); return out; } void Powermap::reset() { setFixed0({eps, eps}); setFixed1({.5, .5}); setFixed2({1 - eps, 1 - eps}); // FIXME: better false? shelf = true; updateSpline(); } void Powermap::setFixed0(PowerPair new_value) { if (fixed[0] != new_value) { spline_needs_update = true; fixed[0].in = clamp(new_value.in, eps, fixed[1].in - eps); fixed[0].out = clamp(new_value.out, eps, fixed[1].out - eps); } } void Powermap::setFixed1(PowerPair new_value) { if (fixed[1] != new_value) { spline_needs_update = true; fixed[1].in = clamp(new_value.in, fixed[0].in + eps, fixed[2].in - eps); fixed[1].out = clamp(new_value.out, fixed[0].out + eps, fixed[2].out - eps); } } void Powermap::setFixed2(PowerPair new_value) { if (fixed[2] != new_value) { spline_needs_update = true; fixed[2].in = clamp(new_value.in, fixed[1].in + eps, 1 - eps); fixed[2].out = clamp(new_value.out, fixed[1].out + eps, 1 - eps); } } void Powermap::setShelf(bool enable) { if (shelf != enable) { spline_needs_update = true; this->shelf = enable; } } PowerPair Powermap::getFixed0() const { return fixed[0]; } PowerPair Powermap::getFixed1() const { return fixed[1]; } PowerPair Powermap::getFixed2() const { return fixed[2]; } // This mostly followes the wikipedia article for monotone cubic splines: // https://en.wikipedia.org/wiki/Monotone_cubic_interpolation void Powermap::updateSpline() { assert(0. <= fixed[0].in && fixed[0].in < fixed[1].in && fixed[1].in < fixed[2].in && fixed[2].in <= 1.); assert(0. <= fixed[0].out && fixed[0].out <= fixed[1].out && fixed[1].out <= fixed[2].out && fixed[2].out <= 1.); Powers X = shelf ? Powers{fixed[0].in, fixed[1].in, fixed[2].in} : Powers{0., fixed[0].in, fixed[1].in, fixed[2].in, 1.}; Powers Y = shelf ? Powers{fixed[0].out, fixed[1].out, fixed[2].out} : Powers{0., fixed[0].out, fixed[1].out, fixed[2].out, 1.}; auto slopes = calcSlopes(X, Y); if (shelf) { assert(slopes.size() == 3); this->m[1] = slopes[0]; this->m[2] = slopes[1]; this->m[3] = slopes[2]; } else { assert(slopes.size() == 5); for (std::size_t i = 0; i < m.size(); ++i) { this->m[i] = slopes[i]; } } spline_needs_update = false; } // This follows the monotone cubic spline algorithm of Steffen, from: // "A Simple Method for Monotonic Interpolation in One Dimension" std::vector Powermap::calcSlopes(const Powers& X, const Powers& Y) { Powers m(X.size()); Powers d(X.size() - 1); Powers h(X.size() - 1); for (std::size_t i = 0; i < d.size(); ++i) { h[i] = X[i + 1] - X[i]; d[i] = (Y[i + 1] - Y[i]) / h[i]; } m.front() = d.front(); for (std::size_t i = 1; i < m.size() - 1; ++i) { m[i] = (d[i - 1] + d[i]) / 2.; } m.back() = d.back(); for (std::size_t i = 1; i < m.size() - 1; ++i) { const auto min_d = 2*std::min(d[i - 1], d[i]); m[i] = std::min(min_d, (h[i] * d[i - 1] + h[i - 1] * d[i]) / (h[i - 1] + h[i])); } return m; } Power Powermap::clamp(Power in, Power min, Power max) const { return std::max(min, std::min(in, max)); }