diff options
-rw-r--r-- | plugin/Makefile.mingw32.in | 1 | ||||
-rw-r--r-- | src/Makefile.am | 1 | ||||
-rw-r--r-- | src/inputprocessor.cc | 10 | ||||
-rw-r--r-- | src/instrument.cc | 26 | ||||
-rw-r--r-- | src/instrument.h | 11 | ||||
-rw-r--r-- | src/position_power.cc | 99 | ||||
-rw-r--r-- | src/position_power.h | 87 | ||||
-rw-r--r-- | src/sample_selection.cc | 34 | ||||
-rw-r--r-- | src/sample_selection.h | 2 | ||||
-rw-r--r-- | src/settings.h | 4 | ||||
-rw-r--r-- | test/Makefile.am | 13 | ||||
-rw-r--r-- | test/position_power_test.cc | 165 |
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; |