From a673a209a71b06488df3244903b5b4b7f994451d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Nusser?= Date: Wed, 6 Mar 2019 09:54:29 +0100 Subject: Split sample selection into own class. --- src/Makefile.am | 1 + src/instrument.cc | 5 +- src/instrument.h | 3 +- src/powerlist.cc | 104 ++-------------------- src/powerlist.h | 26 ++---- src/sample_selection.cc | 226 ++++++++++++++++++++++++++++++++++++++++++++++++ src/sample_selection.h | 63 ++++++++++++++ 7 files changed, 309 insertions(+), 119 deletions(-) create mode 100644 src/sample_selection.cc create mode 100644 src/sample_selection.h diff --git a/src/Makefile.am b/src/Makefile.am index 7d07719..3da5d0f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -37,6 +37,7 @@ nodist_libdg_la_SOURCES = \ powerlist.cc \ random.cc \ sample.cc \ + sample_selection.cc \ semaphore.cc \ staminafilter.cc \ thread.cc \ diff --git a/src/instrument.cc b/src/instrument.cc index ffc928d..b7bcdd9 100644 --- a/src/instrument.cc +++ b/src/instrument.cc @@ -33,7 +33,7 @@ Instrument::Instrument(Settings& settings, Random& rand) : settings(settings) , rand(rand) - , powerlist(rand, settings) + , sample_selection(settings, rand, powerlist) { DEBUG(instrument, "new %p\n", this); mod = 1.0; @@ -59,7 +59,7 @@ const Sample* Instrument::sample(level_t level, size_t pos) if(version >= VersionStr("2.0")) { // Version 2.0 - return powerlist.get(level * mod, pos); + return sample_selection.get(level * mod, pos); } else { @@ -91,6 +91,7 @@ void Instrument::finalise() } powerlist.finalise(); + sample_selection.finalise(); } } diff --git a/src/instrument.h b/src/instrument.h index e4cf481..2cb0813 100644 --- a/src/instrument.h +++ b/src/instrument.h @@ -33,6 +33,7 @@ #include "rangemap.h" // for v1.0 kits #include "powerlist.h" +#include "sample_selection.h" #include "sample.h" #include "versionstr.h" @@ -104,8 +105,8 @@ private: Settings& settings; Random& rand; PowerList powerlist; - std::vector chokes; + SampleSelection sample_selection; }; // typedef std::map< std::string, Instrument > Instruments; diff --git a/src/powerlist.cc b/src/powerlist.cc index e593f7e..87d6e9f 100644 --- a/src/powerlist.cc +++ b/src/powerlist.cc @@ -27,7 +27,6 @@ #include "powerlist.h" #include - #include #include @@ -41,35 +40,19 @@ #include "random.h" #include "settings.h" -/** - * Minimum sample set size. - * Smaller means wider 'velocity groups'. - * Limited by sample set size, ie. only kicks in if sample set size is smaller - * than this number. - */ -std::size_t const MIN_SAMPLE_SET_SIZE = 26u; +namespace +{ // Enable to calculate power on old samples without power attribute //#define AUTO_CALCULATE_POWER unsigned int const LOAD_SIZE = 500u; -namespace -{ - -float pow2(float f) -{ - return f*f; -} - } // end anonymous namespace -PowerList::PowerList(Random& rand, Settings& settings) - : rand(rand) - , settings(settings) +PowerList::PowerList() { power_max = 0; power_min = 100000000; - lastsample = nullptr; } void PowerList::add(Sample* sample) @@ -214,88 +197,11 @@ void PowerList::finalise() DEBUG(rand, " - power: %f\n", item.power); } - - last.resize(samples.size(), 0); } -Sample* PowerList::get(level_t level, std::size_t pos) +const PowerListItems& PowerList::getPowerListItems() const { - auto velocity_stddev = settings.velocity_stddev.load(); - - if(!samples.size()) - { - return nullptr; // No samples to choose from. - } - - float power_span = power_max - power_min; - - // Width is limited to at least 10. Fixes problem with instrument with a - // sample set smaller than MIN_SAMPLE_SET_SIZE. - float width = std::max(samples.size(), MIN_SAMPLE_SET_SIZE); - - // Spread out at most ~2 samples away from center if all samples have a - // uniform distribution over the power spectrum (which they probably don't). - float mean_stepwidth = power_span / width; - - // Cut off mean value with stddev/2 in both ends in order to make room for - // downwards expansion on velocity 0 and upwards expansion on velocity 1. - float mean = level * (power_span - mean_stepwidth) + (mean_stepwidth / 2.0); - float stddev = settings.enable_velocity_modifier.load() ? velocity_stddev * mean_stepwidth : 0.; - - std::size_t index_opt = 0; - float power_opt{0.f}; - float value_opt{std::numeric_limits::max()}; - // TODO: those are mostly for debugging at the moment - float random_opt = 0.; - float distance_opt = 0.; - float recent_opt = 0.; - - // Select normal distributed value between - // (stddev/2) and (power_span-stddev/2) - float lvl = rand.normalDistribution(mean, stddev); - - // Adjust this value to be in range - // (power_min+stddev/2) and (power_max-stddev/2) - lvl += power_min; - - DEBUG(rand, "level: %f, lvl: %f (mean: %.2f, stddev: %.2f, mean_stepwidth: %f," - "power_min: %f, power_max: %f)\n", level, lvl, mean, stddev, mean_stepwidth, - power_min, power_max); - - // TODO: expose parameters to GUI - float alpha = 1.0; - float beta = 1.0; - float gamma = .5; - - // TODO: start with most promising power value and then stop when reaching far values - // which cannot become opt anymore - for (std::size_t i = 0; i < samples.size(); ++i) - { - auto const& item = samples[i]; - - // compute objective function value - auto random = rand.floatInRange(0.,1.); - auto distance = item.power - lvl; - auto recent = (float)settings.samplerate/std::max(pos - last[i], 1); - auto value = alpha*pow2(distance) + beta*pow2(recent) + gamma*random; - - if (value < value_opt) - { - index_opt = i; - power_opt = item.power; - value_opt = value; - random_opt = random; - distance_opt = distance; - recent_opt = recent; - } - } - - DEBUG(rand, "Chose sample with index: %d, value: %f, power %f, random: %f, distance: %f, recent: %f", (int)index_opt, value_opt, power_opt, random_opt, distance_opt, recent_opt); - - lastsample = samples[index_opt].sample; - last[index_opt] = pos; - - return samples[index_opt].sample; + return samples; } float PowerList::getMaxPower() const diff --git a/src/powerlist.h b/src/powerlist.h index b3b47e4..000da15 100644 --- a/src/powerlist.h +++ b/src/powerlist.h @@ -30,38 +30,30 @@ #include "sample.h" -class Random; -struct Settings; +struct PowerListItem +{ + Sample* sample; + float power; +}; +using PowerListItems = std::vector; class PowerList { public: - PowerList(Random& rand, Settings& settings); + PowerList(); void add(Sample* s); void finalise(); ///< Call this when no more samples will be added. - Sample* get(level_t velocity, std::size_t pos); + const PowerListItems& getPowerListItems() const; float getMaxPower() const; float getMinPower() const; private: - struct PowerListItem - { - Sample* sample; - float power; - }; - - Random& rand; - Settings& settings; - - std::vector samples; + PowerListItems samples; float power_max; float power_min; const Channel* getMasterChannel(); - Sample* lastsample; - - std::vector last; }; diff --git a/src/sample_selection.cc b/src/sample_selection.cc new file mode 100644 index 0000000..8000879 --- /dev/null +++ b/src/sample_selection.cc @@ -0,0 +1,226 @@ +/* -*- Mode: c++ -*- */ +/*************************************************************************** + * sample_selection.h + * + * Mon Mar 4 23:58:12 CET 2019 + * Copyright 2019 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 "sample_selection.h" + +#include + +#include "powerlist.h" +#include "random.h" +#include "settings.h" + +namespace +{ + +// Minimum sample set size. +// Smaller means wider 'velocity groups'. +// Limited by sample set size, ie. only kicks in if sample set size is smaller +// than this number. +std::size_t const MIN_SAMPLE_SET_SIZE = 26u; + +float pow2(float f) +{ + return f*f; +} + +} // end anonymous namespace + +SampleSelection::SampleSelection(Settings& settings, Random& rand, const PowerList& powerlist) + : settings(settings), rand(rand), powerlist(powerlist) +{ +} + +void SampleSelection::setSelectionAlg(SelectionAlg alg) +{ + this->alg = alg; +} + +void SampleSelection::finalise() +{ + last.assign(powerlist.getPowerListItems().size(), 0); +} + +const Sample* SampleSelection::get(level_t level, std::size_t pos) +{ + // TODO: switch objective to default at some point + switch (alg) + { + case SelectionAlg::Objective: + return getObjective(level, pos); + break; + case SelectionAlg::Old: + default: + return getOld(level, pos); + } +} + +const Sample* SampleSelection::getOld(level_t level, std::size_t pos) +{ + auto velocity_stddev = settings.velocity_stddev.load(); + + const auto& samples = powerlist.getPowerListItems(); + if(!samples.size()) + { + return nullptr; // No samples to choose from. + } + + int retry = settings.sample_selection_retry_count.load(); + + Sample* sample{nullptr}; + + auto power_max = powerlist.getMaxPower(); + auto power_min = powerlist.getMinPower(); + float power_span = power_max - power_min; + + // Width is limited to at least 10. Fixes problem with instrument with a + // sample set smaller than MIN_SAMPLE_SET_SIZE. + float width = std::max(samples.size(), MIN_SAMPLE_SET_SIZE); + + // Spread out at most ~2 samples away from center if all samples have a + // uniform distribution over the power spectrum (which they probably don't). + float mean_stepwidth = power_span / width; + + // Cut off mean value with stddev/2 in both ends in order to make room for + // downwards expansion on velocity 0 and upwards expansion on velocity 1. + float mean = level * (power_span - mean_stepwidth) + (mean_stepwidth / 2.0); + float stddev = velocity_stddev * mean_stepwidth; + + float power{0.f}; + + // note: loop is executed once + #retry + do + { + --retry; + + // Select normal distributed value between + // (stddev/2) and (power_span-stddev/2) + float lvl = rand.normalDistribution(mean, stddev); + + // Adjust this value to be in range + // (power_min+stddev/2) and (power_max-stddev/2) + lvl += power_min; + + DEBUG(rand, + "level: %f, lvl: %f (mean: %.2f, stddev: %.2f, mean_stepwidth: %f, power_min: %f, power_max: %f)\n", + level, lvl, mean, stddev, mean_stepwidth, power_min, power_max); + + for (auto& item: samples) + { + if (sample == nullptr || std::fabs(item.power - lvl) < std::fabs(power - lvl)) + { + sample = item.sample; + power = item.power; + } + } + } while (lastsample == sample && retry >= 0); + + DEBUG(rand, "Found sample with power %f\n", power); + + lastsample = sample; + + return sample; +} + +const Sample* SampleSelection::getObjective(level_t level, std::size_t pos) +{ + auto velocity_stddev = settings.velocity_stddev.load(); + + const auto& samples = powerlist.getPowerListItems(); + if(!samples.size()) + { + return nullptr; // No samples to choose from. + } + + auto power_max = powerlist.getMaxPower(); + auto power_min = powerlist.getMinPower(); + float power_span = power_max - power_min; + + // Width is limited to at least 10. Fixes problem with instrument with a + // sample set smaller than MIN_SAMPLE_SET_SIZE. + float width = std::max(samples.size(), MIN_SAMPLE_SET_SIZE); + + // Spread out at most ~2 samples away from center if all samples have a + // uniform distribution over the power spectrum (which they probably don't). + float mean_stepwidth = power_span / width; + + // Cut off mean value with stddev/2 in both ends in order to make room for + // downwards expansion on velocity 0 and upwards expansion on velocity 1. + float mean = level * (power_span - mean_stepwidth) + (mean_stepwidth / 2.0); + float stddev = settings.enable_velocity_modifier.load() ? velocity_stddev * mean_stepwidth : 0.; + + std::size_t index_opt = 0; + float power_opt{0.f}; + float value_opt{std::numeric_limits::max()}; + // TODO: those are mostly for debugging at the moment + float random_opt = 0.; + float distance_opt = 0.; + float recent_opt = 0.; + + // Select normal distributed value between + // (stddev/2) and (power_span-stddev/2) + float lvl = rand.normalDistribution(mean, stddev); + + // Adjust this value to be in range + // (power_min+stddev/2) and (power_max-stddev/2) + lvl += power_min; + + DEBUG(rand, "level: %f, lvl: %f (mean: %.2f, stddev: %.2f, mean_stepwidth: %f," + "power_min: %f, power_max: %f)\n", level, lvl, mean, stddev, mean_stepwidth, + power_min, power_max); + + // TODO: expose parameters to GUI + float alpha = 1.0; + float beta = 1.0; + float gamma = .5; + + // TODO: start with most promising power value and then stop when reaching far values + // which cannot become opt anymore + for (std::size_t i = 0; i < samples.size(); ++i) + { + auto const& item = samples[i]; + + // compute objective function value + auto random = rand.floatInRange(0.,1.); + auto distance = item.power - lvl; + auto recent = (float)settings.samplerate/std::max(pos - last[i], 1); + auto value = alpha*pow2(distance) + beta*pow2(recent) + gamma*random; + + if (value < value_opt) + { + index_opt = i; + power_opt = item.power; + value_opt = value; + random_opt = random; + distance_opt = distance; + recent_opt = recent; + } + } + + DEBUG(rand, "Chose sample with index: %d, value: %f, power %f, random: %f, distance: %f, recent: %f", (int)index_opt, value_opt, power_opt, random_opt, distance_opt, recent_opt); + + last[index_opt] = pos; + return samples[index_opt].sample; +} diff --git a/src/sample_selection.h b/src/sample_selection.h new file mode 100644 index 0000000..29d8f54 --- /dev/null +++ b/src/sample_selection.h @@ -0,0 +1,63 @@ +/* -*- Mode: c++ -*- */ +/*************************************************************************** + * sample_selection.h + * + * Mon Mar 4 23:58:12 CET 2019 + * Copyright 2019 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 + +#include "sample.h" + +class PowerList; +class Random; +struct Settings; + +enum class SelectionAlg +{ + Old, + Objective, +}; + +class SampleSelection +{ +private: + Settings& settings; + Random& rand; + const PowerList& powerlist; + + Sample* lastsample; + std::vector last; + + SelectionAlg alg = SelectionAlg::Old; + const Sample* getOld(level_t level, std::size_t pos); + const Sample* getObjective(level_t level, std::size_t pos); + +public: + SampleSelection(Settings& settings, Random& rand, const PowerList& powerlist); + + void setSelectionAlg(SelectionAlg alg); + void finalise(); + const Sample* get(level_t level, std::size_t pos); +}; -- cgit v1.2.3