summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBent Bisballe Nyeng <deva@aasimon.org>2024-07-24 18:57:32 +0200
committerBent Bisballe Nyeng <deva@aasimon.org>2024-07-24 19:16:42 +0200
commitefebb624788963cb00c9e3af7fef40794336c361 (patch)
tree584796f639dd82cbe02c9b7479566ea51f0a555a
parentddd12cae8a21eba52a4d8fae9dced18878e0e055 (diff)
Experimental dynamic, position based, velocity range detection.
-rw-r--r--plugin/Makefile.mingw32.in1
-rw-r--r--src/Makefile.am1
-rw-r--r--src/inputprocessor.cc10
-rw-r--r--src/instrument.cc26
-rw-r--r--src/instrument.h11
-rw-r--r--src/position_power.cc99
-rw-r--r--src/position_power.h87
-rw-r--r--src/sample_selection.cc34
-rw-r--r--src/sample_selection.h2
-rw-r--r--src/settings.h4
-rw-r--r--test/Makefile.am13
-rw-r--r--test/position_power_test.cc165
12 files changed, 406 insertions, 47 deletions
diff --git a/plugin/Makefile.mingw32.in b/plugin/Makefile.mingw32.in
index 1b8c848..7fbc3f5 100644
--- a/plugin/Makefile.mingw32.in
+++ b/plugin/Makefile.mingw32.in
@@ -50,6 +50,7 @@ DG_SRC = \
@top_srcdir@/src/translation.cc \
@top_srcdir@/src/velocityfilter.cc \
@top_srcdir@/src/positionfilter.cc \
+ @top_srcdir@/src/position_power.cc \
@top_srcdir@/src/versionstr.cc
DG_CFLAGS = -I@top_srcdir@ -I@top_srcdir@/src \
-I@top_srcdir@/zita-resampler/libs \
diff --git a/src/Makefile.am b/src/Makefile.am
index 31ce8da..eb6f214 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -77,6 +77,7 @@ libdg_la_SOURCES = \
$(top_srcdir)/src/thread.cc \
$(top_srcdir)/src/velocityfilter.cc \
$(top_srcdir)/src/positionfilter.cc \
+ $(top_srcdir)/src/position_power.cc \
$(top_srcdir)/src/versionstr.cc
EXTRA_DIST = \
diff --git a/src/inputprocessor.cc b/src/inputprocessor.cc
index fa3498c..67a0ec2 100644
--- a/src/inputprocessor.cc
+++ b/src/inputprocessor.cc
@@ -249,12 +249,10 @@ bool InputProcessor::processOnset(event_t& event, std::size_t pos,
// Apply directed chokes to mute other instruments if needed
applyDirectedChoke(settings, kit, *instr, event, events_ds, pos);
- auto const power_max = instr->getMaxPower();
- auto const power_min = instr->getMinPower();
- float const power_span = power_max - power_min;
- float const instrument_level = power_min + event.velocity*power_span;
- // FIXME: bad variable naming of parameters
- const auto sample = instr->sample(instrument_level, event.position, event.offset + pos);
+ const auto power_range = instr->getPowers(event.position);
+ const auto power_span = power_range.max - power_range.min;
+ const auto note_power = power_range.min + event.velocity * power_span;
+ const auto sample = instr->sample(note_power, power_span, event.position, event.offset + pos);
if(sample == nullptr)
{
diff --git a/src/instrument.cc b/src/instrument.cc
index ac6aa28..07b3ddf 100644
--- a/src/instrument.cc
+++ b/src/instrument.cc
@@ -29,6 +29,7 @@
#include <hugin.hpp>
#include "sample.h"
+#include "position_power.h"
Instrument::Instrument(Settings& settings, Random& rand)
: settings(settings)
@@ -36,7 +37,6 @@ Instrument::Instrument(Settings& settings, Random& rand)
, sample_selection(settings, rand, powerlist)
{
DEBUG(instrument, "new %p\n", this);
- mod = 1.0;
lastpos = 0;
magic = this;
@@ -55,17 +55,17 @@ bool Instrument::isValid() const
}
// FIXME: very bad variable naming of parameters
-const Sample* Instrument::sample(level_t level, float position , std::size_t pos)
+const Sample* Instrument::sample(float power, float instrument_power_range, float position, std::size_t pos)
{
if(version >= VersionStr("2.0"))
{
// Version 2.0
- return sample_selection.get(level * mod, position, pos);
+ return sample_selection.get(power, instrument_power_range, position, pos);
}
else
{
// Version 1.0
- auto s = samples.get(level * mod);
+ auto s = samples.get(power);
if(s.size() == 0)
{
return nullptr;
@@ -130,27 +130,15 @@ std::size_t Instrument::getNumberOfFiles() const
return audiofiles.size();
}
-float Instrument::getMaxPower() const
+Instrument::PowerRange Instrument::getPowers(float position) const
{
if(version >= VersionStr("2.0"))
{
- return powerlist.getMaxPower();
+ return positionPower(samplelist, position);
}
else
{
- return 1.0f;
- }
-}
-
-float Instrument::getMinPower() const
-{
- if(version >= VersionStr("2.0"))
- {
- return powerlist.getMinPower();
- }
- else
- {
- return 0.0f;
+ return { 0.0f, 1.0f };
}
}
diff --git a/src/instrument.h b/src/instrument.h
index 89918de..5f79882 100644
--- a/src/instrument.h
+++ b/src/instrument.h
@@ -50,7 +50,7 @@ public:
~Instrument();
// FIXME: variable naming
- const Sample* sample(level_t level, float position, std::size_t pos);
+ const Sample* sample(float power, float instrument_power_range, float position, std::size_t pos);
std::size_t getID() const;
const std::string& getName() const;
@@ -68,8 +68,12 @@ public:
//! Get the number of audio files (as in single channel) in this instrument.
std::size_t getNumberOfFiles() const;
- float getMaxPower() const;
- float getMinPower() const;
+ struct PowerRange
+ {
+ double min;
+ double max;
+ };
+ PowerRange getPowers(float position) const;
const std::vector<Choke>& getChokes();
@@ -98,7 +102,6 @@ private:
std::deque<InstrumentChannel> instrument_channels;
size_t lastpos;
- float mod;
Settings& settings;
Random& rand;
PowerList powerlist;
diff --git a/src/position_power.cc b/src/position_power.cc
new file mode 100644
index 0000000..0dc369b
--- /dev/null
+++ b/src/position_power.cc
@@ -0,0 +1,99 @@
+/* -*- Mode: c++ -*- */
+/***************************************************************************
+ * position_power.cc
+ *
+ * Wed Jul 24 15:05:27 CEST 2024
+ * Copyright 2024 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * 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 "position_power.h"
+
+#include "sample.h"
+#include "instrument.h"
+
+#include <set>
+#include <algorithm>
+
+Instrument::PowerRange positionPower(const std::vector<Sample*>& samplelist, double position)
+{
+ if(samplelist.empty())
+ {
+ return {0.0, 1.0};
+ }
+
+ struct PosPower
+ {
+ double position;
+ double power;
+ Sample* sample;
+ };
+ auto dist_cmp =
+ [position](const PosPower& a, const PosPower& b)
+ {
+ auto position_delta =
+ std::abs(a.position - position) - std::abs(b.position - position);
+ if(position_delta != 0)
+ {
+ return position_delta < 0.0;
+ }
+ return a.sample < b.sample;
+ };
+ std::set<PosPower, decltype(dist_cmp)> sorted_samples(dist_cmp);
+
+ std::for_each(samplelist.begin(), samplelist.end(),
+ [&](Sample* s)
+ {
+ sorted_samples.insert({s->getPosition(), s->getPower(), s});
+ });
+
+ // Find the smallest, closest set in terms of delta-position against the note position
+ // and find the contained power range.
+ double power_min{std::numeric_limits<double>::max()};
+ double power_max{std::numeric_limits<double>::min()};
+ auto sample_iter = sorted_samples.begin();
+ auto final_position_boundary = sample_iter->position;
+ for(std::size_t i = 0; i < std::max(sorted_samples.size() / 4, std::size_t(1)); ++i)
+ {
+ auto power = sample_iter->power;
+ final_position_boundary = sample_iter->position;
+ power_min = std::min(power_min, power);
+ power_max = std::max(power_max, power);
+ ++sample_iter;
+ }
+
+ // Include upcoming samples from the list as long as their distances are contained in
+ // the final position range.
+ while(sample_iter != sorted_samples.end())
+ {
+ if(sample_iter->position != final_position_boundary)
+ {
+ // Position has left the range - and since the list is sorted; stop.
+ break;
+ }
+
+ auto power = sample_iter->power;
+ power_min = std::min(power_min, power);
+ power_max = std::max(power_max, power);
+ ++sample_iter;
+ }
+
+ return {power_min, power_max};
+}
diff --git a/src/position_power.h b/src/position_power.h
new file mode 100644
index 0000000..9b92a4f
--- /dev/null
+++ b/src/position_power.h
@@ -0,0 +1,87 @@
+/* -*- Mode: c++ -*- */
+/***************************************************************************
+ * position_power.h
+ *
+ * Wed Jul 24 15:05:26 CEST 2024
+ * Copyright 2024 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * 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 "instrument.h"
+
+#include <vector>
+
+class Sample;
+
+//!
+//! Consider samples in the samplelist as a two dimensional "point cloud" - each
+//! also pointing to sample data (which is not used here):
+//!
+//! (vel)
+//! S_v
+//! max ^
+//! |x x
+//! | x x x
+//! | x x
+//! | x x
+//! | x x x
+//! min |________________> S_p (pos)
+//! center rim
+//!
+//! N: is the total number of samples
+//!
+//! S, is a specific sample
+//! S_v, is the sample's velocity
+//! S_p, is the sample's position
+//!
+//! I_v, is the input note velocity
+//! I_p, is the input note position
+//!
+//! -----
+//!
+//! Define the range R with width R_w around I_p, such that at least N/4 samples are
+//! included (note the count N/4 probably needs narrowing):
+//!
+//! (vel)
+//! S_v
+//! max ^
+//! |x x
+//! | x . +. x
+//! | . + . x
+//! | x. . x
+//! | x . +.x
+//! min |____._____._____> S_p (pos)
+//! center . ^ . rim
+//! . I_p .
+//! { R }
+//! x is a sample that is not included in the set, + is a sample that is.
+//!
+//! Now for the range R, find the R_max velocity and the R_min velocity.
+//! Use these as the boundaries for the velocity [0; 1] range.
+//!
+//! If no position information is available, the range will include all samples in
+//! the range, because all have the default value 0.
+//! This mimics perfectly the behaviour we have today.
+//!
+//! \param samplelist is the search space, \param position is the search origo (S_p)
+//! \returns a tuple {R_min, R_max}, which defaults to {0, 1} if the samplelist is empty
+Instrument::PowerRange positionPower(const std::vector<Sample*>& samplelist, double position);
diff --git a/src/sample_selection.cc b/src/sample_selection.cc
index eb13e55..b7b6bcd 100644
--- a/src/sample_selection.cc
+++ b/src/sample_selection.cc
@@ -45,7 +45,9 @@ float pow2(float f)
} // end anonymous namespace
SampleSelection::SampleSelection(Settings& settings, Random& rand, const PowerList& powerlist)
- : settings(settings), rand(rand), powerlist(powerlist)
+ : settings(settings)
+ , rand(rand)
+ , powerlist(powerlist)
{
}
@@ -56,7 +58,7 @@ void SampleSelection::finalise()
// FIXME: For the position, weird hacks via the powerlist are necessary. Refactor!
// FIXME: bad variable naming
-const Sample* SampleSelection::get(level_t level, float position, std::size_t pos)
+const Sample* SampleSelection::get(float power, float instrument_power_span, float position, std::size_t pos)
{
const auto& samples = powerlist.getPowerListItems();
if(!samples.size())
@@ -76,31 +78,35 @@ const Sample* SampleSelection::get(level_t level, float position, std::size_t po
// Note the magic values in front of the settings factors.
const float f_close = 4.*settings.sample_selection_f_close.load();
- const float f_position = 1000.*settings.sample_selection_f_position.load(); // FIXME: huge factor for now
+ const float f_position = 4.*settings.sample_selection_f_position.load();
const float f_diverse = (1./2.)*settings.sample_selection_f_diverse.load();
const float f_random = (1./3.)*settings.sample_selection_f_random.load();
- float power_range = powerlist.getMaxPower() - powerlist.getMinPower();
- // If all power values are the same then power_range is invalid but we want
+ // If all power values are the same then instrument_power_span is invalid but we want
// to avoid division by zero.
- if (power_range == 0.) { power_range = 1.0; }
+ if (instrument_power_span == 0.)
+ {
+ instrument_power_span = 1.0;
+ }
// start with most promising power value and then stop when reaching far values
// which cannot become opt anymore
- auto closest_it = std::lower_bound(samples.begin(), samples.end(), level);
+ auto closest_it = std::lower_bound(samples.begin(), samples.end(), power);
std::size_t up_index = std::distance(samples.begin(), closest_it);
std::size_t down_index = (up_index == 0 ? 0 : up_index - 1);
float up_value_lb;
- if (up_index < samples.size()) {
- auto const close_up = (samples[up_index].power-level)/power_range;
+ if (up_index < samples.size())
+ {
+ auto const close_up = (samples[up_index].power-power)/instrument_power_span;
up_value_lb = f_close*pow2(close_up);
}
- else {
+ else
+ {
--up_index;
up_value_lb = std::numeric_limits<float>::max();
}
- auto const close_down = (samples[down_index].power-level)/power_range;
+ auto const close_down = (samples[down_index].power-power)/instrument_power_span;
float down_value_lb = (up_index != 0 ? f_close*pow2(close_down) : std::numeric_limits<float>::max());
std::size_t count = 0;
@@ -117,7 +123,7 @@ const Sample* SampleSelection::get(level_t level, float position, std::size_t po
if (up_index != samples.size()-1)
{
++up_index;
- up_value_lb = f_close*pow2((samples[up_index].power-level)/power_range);
+ up_value_lb = f_close*pow2((samples[up_index].power-power)/instrument_power_span);
}
else
{
@@ -130,7 +136,7 @@ const Sample* SampleSelection::get(level_t level, float position, std::size_t po
if (down_index != 0)
{
--down_index;
- down_value_lb = f_close*pow2((samples[down_index].power-level)/power_range);
+ down_value_lb = f_close*pow2((samples[down_index].power-power)/instrument_power_span);
}
else
{
@@ -139,7 +145,7 @@ const Sample* SampleSelection::get(level_t level, float position, std::size_t po
}
auto random = rand.floatInRange(0.,1.);
- auto close = (samples[current_index].power - level)/power_range;
+ auto close = (samples[current_index].power - power)/instrument_power_span;
auto diverse = 1./(1. + (float)(pos - last[current_index])/settings.samplerate);
auto closepos = samples[current_index].sample->getPosition() - position;
// note that the value below for close and closepos is actually the weighted squared l2 distance in 2d
diff --git a/src/sample_selection.h b/src/sample_selection.h
index 1f6b290..c7ac709 100644
--- a/src/sample_selection.h
+++ b/src/sample_selection.h
@@ -40,7 +40,7 @@ public:
SampleSelection(Settings& settings, Random& rand, const PowerList& powerlist);
void finalise();
- const Sample* get(level_t level, float position, std::size_t pos);
+ const Sample* get(float power, float instrument_power_span, float position, std::size_t pos);
private:
Settings& settings;
diff --git a/src/settings.h b/src/settings.h
index 5c2e4ee..074c7bc 100644
--- a/src/settings.h
+++ b/src/settings.h
@@ -76,9 +76,9 @@ struct Settings
static float constexpr velocity_modifier_falloff_default = 0.5f;
static float constexpr velocity_modifier_weight_default = 0.25f;
static float constexpr velocity_stddev_default = .45f;
- static float constexpr position_stddev_default = 0.f; // FIXME: set to something sensible
+ static float constexpr position_stddev_default = .45f;
static float constexpr sample_selection_f_close_default = .85f;
- static float constexpr sample_selection_f_position_default = 1.f;
+ static float constexpr sample_selection_f_position_default = .85f;
static float constexpr sample_selection_f_diverse_default = .16f;
static float constexpr sample_selection_f_random_default = .07f;
Atomic<float> velocity_modifier_falloff{velocity_modifier_falloff_default};
diff --git a/test/Makefile.am b/test/Makefile.am
index 15ceb7d..64b2462 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -8,7 +8,7 @@ TESTS = resource enginetest paintertest configfile audiocache \
randomtest atomictest syncedsettingstest imagecachetest \
semaphoretest drumkitcreatortest bytesizeparsertest notifiertest \
dgxmlparsertest domloadertest configparsertest midimapparsertest \
- eventsdstest powermaptest midimappertest
+ eventsdstest powermaptest midimappertest positionpowertest
if WITH_NLS
TESTS += translationtest
@@ -354,6 +354,17 @@ midimappertest_SOURCES = \
midimappertest.cc \
uunit/uunit.cc
+positionpowertest_CXXFLAGS = \
+ -I$(top_srcdir)/test/uunit -DOUTPUT=\"positionpowertest\" \
+ $(DEBUG_FLAGS) \
+ -I$(top_srcdir)/src
+positionpowertest_LDFLAGS =
+positionpowertest_SOURCES = \
+ $(top_srcdir)/src/sample.cc \
+ $(top_srcdir)/src/position_power.cc \
+ position_power_test.cc \
+ uunit/uunit.cc
+
RES = \
$(top_srcdir)/test/locale/da.mo
diff --git a/test/position_power_test.cc b/test/position_power_test.cc
new file mode 100644
index 0000000..bd8906d
--- /dev/null
+++ b/test/position_power_test.cc
@@ -0,0 +1,165 @@
+/* -*- Mode: c++ -*- */
+/***************************************************************************
+ * position_power_test.cc
+ *
+ * Wed Jul 24 15:24:53 CEST 2024
+ * Copyright 2024 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * 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 <uunit.h>
+
+#include "../src/position_power.h"
+
+class test_position_powertest
+ : public uUnit
+{
+public:
+ test_position_powertest()
+ {
+ uTEST(test_position_powertest::empty);
+ uTEST(test_position_powertest::boundary);
+ }
+
+ void empty()
+ {
+ std::vector<Sample*> samplelist;
+ auto res = positionPower(samplelist, 0);
+ uASSERT_EQUAL(0.0, res.min);
+ uASSERT_EQUAL(1.0, res.max);
+ }
+
+ void boundary()
+ {
+ // Sig: nam, pwr, pos
+ Sample s1_1{{}, 1.1, 1.0};
+ Sample s2_1{{}, 2.1, 1.0};
+ Sample s3_1{{}, 3.1, 1.0};
+ Sample s4_1{{}, 4.1, 1.0};
+ Sample s5_1{{}, 5.1, 1.0};
+
+ Sample s1_2{{}, 1.2, 2.0};
+ Sample s2_2{{}, 2.2, 2.0};
+ Sample s3_2{{}, 3.2, 2.0};
+ Sample s4_2{{}, 4.2, 2.0};
+ Sample s5_2{{}, 5.2, 2.0};
+ Sample s6_2{{}, 6.2, 2.0};
+ Sample s7_2{{}, 7.2, 2.0};
+
+ Sample s1_3{{}, 1.3, 3.0};
+ Sample s2_3{{}, 2.3, 3.0};
+ Sample s3_3{{}, 3.3, 3.0};
+ Sample s4_3{{}, 4.3, 3.0};
+ Sample s5_3{{}, 5.3, 3.0};
+ Sample s6_3{{}, 6.3, 3.0};
+ Sample s7_3{{}, 7.3, 3.0};
+
+ { // one [s1_1, s1_1]
+ std::vector<Sample*> samplelist{&s1_1};
+ auto res = positionPower(samplelist, 1.0);
+ uASSERT_EQUAL(1.1, res.min);
+ uASSERT_EQUAL(1.1, res.max);
+ }
+
+ { // two with same position [s1_1, s2_1]
+ std::vector<Sample*> samplelist{&s1_1, &s2_1};
+ auto res = positionPower(samplelist, 1.0);
+ uASSERT_EQUAL(1.1, res.min);
+ uASSERT_EQUAL(2.1, res.max);
+ }
+
+ { // two with different position [s1_1, s2_2]
+ std::vector<Sample*> samplelist{&s1_1, &s2_2};
+ auto res = positionPower(samplelist, 1.0);
+ uASSERT_EQUAL(1.1, res.min);
+ uASSERT_EQUAL(1.1, res.max);
+ }
+
+ { // three [s1, (s2), s3] - one "hidden" inside range
+ std::vector<Sample*> samplelist{&s1_1, &s2_1, &s3_1};
+ auto res = positionPower(samplelist, 1.0);
+ uASSERT_EQUAL(1.1, res.min);
+ uASSERT_EQUAL(3.1, res.max);
+ }
+
+ { // six [s1, (s2), s3] - one "hidden" inside range and three ouside boundary
+ std::vector<Sample*> samplelist{&s1_1, &s2_1, &s3_1, &s1_2, &s2_2, &s3_2};
+ auto res = positionPower(samplelist, 1.0);
+ uASSERT_EQUAL(1.1, res.min);
+ uASSERT_EQUAL(3.1, res.max);
+ }
+
+ { // six [s1, (s2), s3] - one "hidden" inside range and three ouside boundary
+ std::vector<Sample*> samplelist{&s1_1, &s2_1, &s3_1, &s1_2, &s2_2, &s3_2};
+ auto res = positionPower(samplelist, 2.0);
+ uASSERT_EQUAL(1.2, res.min);
+ uASSERT_EQUAL(3.2, res.max);
+ }
+
+ { // again, six in two position groups (1 and 2), lower three is the closest to 1.49
+ std::vector<Sample*> samplelist{&s1_1, &s2_1, &s3_1, &s1_2, &s2_2, &s3_2};
+ auto res = positionPower(samplelist, 1.49);
+ uASSERT_EQUAL(1.1, res.min);
+ uASSERT_EQUAL(3.1, res.max);
+ }
+
+ { // again, six in two position groups (1 and 2), upper three is the closest to 1.51
+ std::vector<Sample*> samplelist{&s1_1, &s2_1, &s3_1, &s1_2, &s2_2, &s3_2};
+ auto res = positionPower(samplelist, 1.51);
+ uASSERT_EQUAL(1.2, res.min);
+ uASSERT_EQUAL(3.2, res.max);
+ }
+
+ { // 8, first one at position the remaining at other position
+ // 1/4th of the samples are 2, and the second one belongs to group 2, which
+ // will drag in the rest of group 2 with it
+ std::vector<Sample*> samplelist{&s1_1, &s1_2, &s2_2, &s3_2,
+ &s4_2, &s5_2, &s6_2, &s7_2};
+ auto res = positionPower(samplelist, 1);
+ uASSERT_EQUAL(1.1, res.min);
+ uASSERT_EQUAL(7.2, res.max);
+ }
+
+ { // 9, first one at position the remaining at other position + one from group 3
+ // at the end which is ignored
+ // 1/4th of the samples are 2, and the second one belongs to group 2, which
+ // will drag in the rest of group 2 with it
+ std::vector<Sample*> samplelist{&s1_1, &s1_2, &s2_2, &s3_2,
+ &s4_2, &s5_2, &s6_2, &s7_2, &s7_3};
+ auto res = positionPower(samplelist, 1);
+ uASSERT_EQUAL(1.1, res.min);
+ uASSERT_EQUAL(7.2, res.max);
+ }
+
+ { // 8, first one from group 1, then 6 from group 2 and finally one from group 3
+ // first and last should be ignored - input pos is closest to group 2
+ std::vector<Sample*> samplelist{&s1_1,
+ &s1_2, &s2_2, &s3_2, &s4_2,
+ &s7_3};
+ auto res = positionPower(samplelist, 2.1);
+ uASSERT_EQUAL(1.2, res.min);
+ uASSERT_EQUAL(4.2, res.max);
+ }
+
+ }
+};
+
+// Registers the fixture into the 'registry'
+static test_position_powertest test;