diff options
author | Sander Vocke <sandervocke@gmail.com> | 2024-07-25 09:09:35 +0200 |
---|---|---|
committer | Sander Vocke <sandervocke@gmail.com> | 2024-07-25 11:06:32 +0200 |
commit | 624aafbc9cde2b9e83c7c278e44f19ab9e3bc9fc (patch) | |
tree | f6e2ef7329b32e4601326012221e0b5ce64a0367 /src/curvemap.cc | |
parent | 019d478818950f7880d2c0f80d8fc8f963e9736b (diff) |
Support curve maps in midi map file
Diffstat (limited to 'src/curvemap.cc')
-rw-r--r-- | src/curvemap.cc | 278 |
1 files changed, 278 insertions, 0 deletions
diff --git a/src/curvemap.cc b/src/curvemap.cc new file mode 100644 index 0000000..858c7b8 --- /dev/null +++ b/src/curvemap.cc @@ -0,0 +1,278 @@ +/* -*- 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 "curvemap.h" + +#include <cassert> +#include <cmath> + +namespace +{ + +using CurveValue = CurveMap::CurveValue; +using CurveValuePair = CurveMap::CurveValuePair; + +CurveValue h00(CurveValue x) +{ + return (1 + 2 * x) * pow(1 - x, 2); +} + +CurveValue h10(CurveValue x) +{ + return x * pow(1 - x, 2); +} + +CurveValue h01(CurveValue x) +{ + return x * x * (3 - 2 * x); +} + +CurveValue h11(CurveValue x) +{ + return x * x * (x - 1); +} + +CurveValue computeValue(const CurveValue x, const CurveValuePair& P0, const CurveValuePair& P1, + const CurveValue m0, const CurveValue 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 + +CurveMap::CurveMap() +{ + reset(); +} + +CurveValue CurveMap::map(CurveValue in) +{ + assert(in >= 0. && in <= 1.); + if (invert) + { + in = 1.0 - in; + } + + if (spline_needs_update) + { + updateSpline(); + } + + CurveValue 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 CurveMap::reset() +{ + setFixed0({eps, eps}); + setFixed1({.5, .5}); + setFixed2({1 - eps, 1 - eps}); + // FIXME: better false? + shelf = true; + invert = false; + + updateSpline(); +} + +void CurveMap::setFixed0(CurveValuePair 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 CurveMap::setFixed1(CurveValuePair 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 CurveMap::setFixed2(CurveValuePair 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 CurveMap::setInvert(bool enable) +{ + invert = enable; +} + +void CurveMap::setShelf(bool enable) +{ + if (shelf != enable) + { + spline_needs_update = true; + this->shelf = enable; + } +} + +CurveValuePair CurveMap::getFixed0() const +{ + return fixed[0]; +} + +CurveValuePair CurveMap::getFixed1() const +{ + return fixed[1]; +} + +CurveValuePair CurveMap::getFixed2() const +{ + return fixed[2]; +} + +bool CurveMap::getInvert() const { + return invert; +} + +bool CurveMap::getShelf() const { + return shelf; +} + +// This mostly followes the wikipedia article for monotone cubic splines: +// https://en.wikipedia.org/wiki/Monotone_cubic_interpolation +void CurveMap::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.); + + CurveValues X = shelf ? CurveValues{fixed[0].in, fixed[1].in, fixed[2].in} + : CurveValues{0., fixed[0].in, fixed[1].in, fixed[2].in, 1.}; + CurveValues Y = shelf ? CurveValues{fixed[0].out, fixed[1].out, fixed[2].out} + : CurveValues{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<float> CurveMap::calcSlopes(const CurveValues& X, const CurveValues& Y) +{ + CurveValues m(X.size()); + + CurveValues d(X.size() - 1); + CurveValues 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<float>(min_d, + (h[i] * d[i - 1] + h[i - 1] * d[i]) / (h[i - 1] + h[i])); + } + + return m; +} + +CurveValue CurveMap::clamp(CurveValue in, CurveValue min, CurveValue max) const +{ + return std::max(min, std::min(in, max)); +} + +bool CurveMap::operator==(const CurveMap& other) const +{ + return getFixed0() == other.getFixed0() && + getFixed1() == other.getFixed1() && + getFixed2() == other.getFixed2() && + getShelf() == other.getShelf() && + getInvert() == other.getInvert(); +} |