diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Makefile.am | 2 | ||||
| -rw-r--r-- | src/powermap.cc | 251 | ||||
| -rw-r--r-- | src/powermap.h | 76 | ||||
| -rw-r--r-- | src/settings.h | 60 | 
4 files changed, 389 insertions, 0 deletions
| diff --git a/src/Makefile.am b/src/Makefile.am index a8e5c74..09df750 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -58,6 +58,7 @@ libdg_la_SOURCES = \  	midimapper.cc \  	path.cc \  	powerlist.cc \ +	powermap.cc \  	random.cc \  	sample.cc \  	sample_selection.cc \ @@ -113,6 +114,7 @@ EXTRA_DIST = \  	path.h \  	platform.h \  	powerlist.h \ +	powermap.h \  	random.h \  	range.h \  	rangemap.h \ diff --git a/src/powermap.cc b/src/powermap.cc new file mode 100644 index 0000000..2bb45b7 --- /dev/null +++ b/src/powermap.cc @@ -0,0 +1,251 @@ +/* -*- 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 <cassert> +#include <cmath> + +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<float> 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<float>(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)); +} diff --git a/src/powermap.h b/src/powermap.h new file mode 100644 index 0000000..3a406cc --- /dev/null +++ b/src/powermap.h @@ -0,0 +1,76 @@ +/* -*- Mode: c++ -*- */ +/*************************************************************************** + *            powermap.h + * + *  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. + */ +#pragma once + +#include <array> +#include <vector> + +class Powermap +{ +public: +	using Power = float; +	using Powers = std::vector<Power>; +	struct PowerPair +	{ +		Power in; +		Power out; + +		bool operator!=(const PowerPair& other) +		{ +			return in != other.in || out != other.out; +		} +	}; + +	Powermap(); + +	Power map(Power in); +	void reset(); + +	void setFixed0(PowerPair new_value); +	void setFixed1(PowerPair new_value); +	void setFixed2(PowerPair new_value); +	void setShelf(bool enable); + +	PowerPair getFixed0() const; +	PowerPair getFixed1() const; +	PowerPair getFixed2() const; + +private: +	// input parameters (state of this class) +	std::array<PowerPair, 3> fixed; +	bool shelf; + +	// spline parameters (deterministically computed from the input parameters) +	bool spline_needs_update; +	std::array<float, 5> m; +	const Power eps = 1e-4; + +	void updateSpline(); +	std::vector<float> calcSlopes(const Powers& X, const Powers& P); + +	Power clamp(Power in, Power min, Power max) const; +}; diff --git a/src/settings.h b/src/settings.h index 9fa3896..a3d21d0 100644 --- a/src/settings.h +++ b/src/settings.h @@ -144,6 +144,20 @@ struct Settings  	// Current latency offset in ms - for UI  	Atomic<float> latency_current{0}; +	// Powermap parameters +	Atomic<bool> enable_powermap; +	Atomic<float> powermap_fixed0_x{0.}; +	Atomic<float> powermap_fixed0_y{0.}; +	Atomic<float> powermap_fixed1_x{.5}; +	Atomic<float> powermap_fixed1_y{.5}; +	Atomic<float> powermap_fixed2_x{1.}; +	Atomic<float> powermap_fixed2_y{1.}; +	Atomic<bool> powermap_shelf{true}; + +	// Powermap visualizer; -1 is "none" +	Atomic<float> powermap_input{-1.}; +	Atomic<float> powermap_output{-1.}; +  	Atomic<std::size_t> audition_counter{0};  	Atomic<std::string> audition_instrument;  	Atomic<float> audition_velocity; @@ -209,6 +223,18 @@ struct SettingsGetter  	SettingRef<float> latency_regain;  	SettingRef<float> latency_current; +	SettingRef<bool> enable_powermap; +	SettingRef<float> powermap_fixed0_x; +	SettingRef<float> powermap_fixed0_y; +	SettingRef<float> powermap_fixed1_x; +	SettingRef<float> powermap_fixed1_y; +	SettingRef<float> powermap_fixed2_x; +	SettingRef<float> powermap_fixed2_y; +	SettingRef<bool> powermap_shelf; + +	SettingRef<float> powermap_input; +	SettingRef<float> powermap_output; +  	SettingRef<std::size_t> audition_counter;  	SettingRef<std::string> audition_instrument;  	SettingRef<float> audition_velocity; @@ -257,6 +283,16 @@ struct SettingsGetter  		, latency_stddev{settings.latency_stddev}  		, latency_regain{settings.latency_regain}  		, latency_current{settings.latency_current} +		, enable_powermap{settings.enable_powermap} +		, powermap_fixed0_x{settings.powermap_fixed0_x} +		, powermap_fixed0_y{settings.powermap_fixed0_y} +		, powermap_fixed1_x{settings.powermap_fixed1_x} +		, powermap_fixed1_y{settings.powermap_fixed1_y} +		, powermap_fixed2_x{settings.powermap_fixed2_x} +		, powermap_fixed2_y{settings.powermap_fixed2_y} +		, powermap_shelf{settings.powermap_shelf} +		, powermap_input{settings.powermap_input} +		, powermap_output{settings.powermap_output}  		, audition_counter{settings.audition_counter}  		, audition_instrument{settings.audition_instrument}  		, audition_velocity{settings.audition_velocity} @@ -321,6 +357,18 @@ public:  	Notifier<float> latency_regain;  	Notifier<float> latency_current; +	Notifier<bool> enable_powermap; +	Notifier<float> powermap_fixed0_x; +	Notifier<float> powermap_fixed0_y; +	Notifier<float> powermap_fixed1_x; +	Notifier<float> powermap_fixed1_y; +	Notifier<float> powermap_fixed2_x; +	Notifier<float> powermap_fixed2_y; +	Notifier<bool> powermap_shelf; + +	Notifier<float> powermap_input; +	Notifier<float> powermap_output; +  	Notifier<std::size_t> audition_counter;  	Notifier<std::string> audition_instrument;  	Notifier<int> audition_velocity; @@ -383,6 +431,18 @@ public:  		EVAL(latency_regain);  		EVAL(latency_current); +		EVAL(enable_powermap); +		EVAL(powermap_fixed0_x); +		EVAL(powermap_fixed0_y); +		EVAL(powermap_fixed1_x); +		EVAL(powermap_fixed1_y); +		EVAL(powermap_fixed2_x); +		EVAL(powermap_fixed2_y); +		EVAL(powermap_shelf); + +		EVAL(powermap_input); +		EVAL(powermap_output); +  		EVAL(audition_counter);  		EVAL(audition_instrument);  		EVAL(audition_velocity); | 
