From eb0a72576c71557c8bb64cfd319620f5ea7ba24c Mon Sep 17 00:00:00 2001 From: TheMarlboroMan Date: Sun, 15 Nov 2020 16:50:27 +0100 Subject: Implementation of the voice limiting feature. --- src/inputprocessor.cc | 87 +++++++++++++++++++++++++++++++++++++++++++++++++-- src/inputprocessor.h | 5 +++ src/settings.h | 24 ++++++++++++++ 3 files changed, 113 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/inputprocessor.cc b/src/inputprocessor.cc index 2da5dbc..fd6e5b9 100644 --- a/src/inputprocessor.cc +++ b/src/inputprocessor.cc @@ -252,7 +252,16 @@ bool InputProcessor::processOnset(event_t& event, std::size_t pos, return false; } - events_ds.startAddingNewGroup(instrument_id); + if(settings.enable_voice_limit.load()) + { + limitVoices(instrument_id, + settings.voice_limit_max.load(), + settings.voice_limit_rampdown.load()); + } + + //Given that audio files could be invalid, maybe we must add the new + //group just before adding the first new sample... + bool new_group_added = false; for(Channel& ch: kit.channels) { const auto af = sample->getAudioFile(ch); @@ -263,8 +272,15 @@ bool InputProcessor::processOnset(event_t& event, std::size_t pos, else { //DEBUG(inputprocessor, "Adding event %d.\n", event.offset); - auto& event_sample = events_ds.emplace(ch.num, ch.num, 1.0, af, - instr->getGroup(), instrument_id); + if(!new_group_added) + { + new_group_added=true; + events_ds.startAddingNewGroup(instrument_id); + } + + auto& event_sample = + events_ds.emplace(ch.num, ch.num, 1.0, af, + instr->getGroup(), instrument_id); event_sample.offset = (event.offset + pos) * resample_ratio; if(settings.normalized_samples.load() && sample->getNormalized()) @@ -353,3 +369,68 @@ bool InputProcessor::processStop(event_t& event) return true; } + +void InputProcessor::limitVoices(std::size_t instrument_id, + std::size_t max_voices, + float rampdown_time) +{ + const auto& group_ids=events_ds.getSampleEventGroupIDsOf(instrument_id); + + if(group_ids.size() <= max_voices) + { + return; + } + + //Filter out ramping events... + auto filter_ramping_predicate = + [this](EventGroupID group_id) -> bool + { + const auto& event_ids=events_ds.getEventIDsOf(group_id); + //TODO: This should not happen. + if(!event_ids.size()) + { + return false; + } + + const auto& sample=events_ds.get(event_ids[0]); + return !sample.rampdownInProgress(); + }; + + EventGroupIDs non_ramping; + std::copy_if(std::begin(group_ids), + std::end(group_ids), + std::back_inserter(non_ramping), filter_ramping_predicate); + + if(!non_ramping.size()) + { + return; + } + + //Let us get the eldest... + //TODO: where is the playhead? Should we add it to the offset? + auto compare_event_offsets = + [this](EventGroupID a, EventGroupID b) + { + const auto& event_ids_a=events_ds.getEventIDsOf(a); + const auto& event_ids_b=events_ds.getEventIDsOf(b); + + const auto& sample_a=events_ds.get(event_ids_a[0]); + const auto& sample_b=events_ds.get(event_ids_b[0]); + return sample_a.offset < sample_b.offset; + }; + + auto it = std::min_element(std::begin(non_ramping), + std::end(non_ramping), + compare_event_offsets); + if(it == std::end(non_ramping)) + { + return; + } + + const auto& event_ids = events_ds.getEventIDsOf(*it); + for(const auto& event_id : event_ids) + { + auto& sample=events_ds.get(event_id); + applyChoke(settings, sample, rampdown_time, sample.offset); + } +} diff --git a/src/inputprocessor.h b/src/inputprocessor.h index 3c2cd5a..971cc85 100644 --- a/src/inputprocessor.h +++ b/src/inputprocessor.h @@ -64,6 +64,11 @@ private: bool processChoke(event_t& event, std::size_t pos, double resample_ratio); bool processStop(event_t& event); + //! Ramps down samples from events_ds is there are more groups playing than + //! max_voices for a given instrument. + void limitVoices(std::size_t instrument_id, std::size_t max_voices, + float rampdown_time); + std::vector> filters; Settings& settings; diff --git a/src/settings.h b/src/settings.h index 7749adf..7507827 100644 --- a/src/settings.h +++ b/src/settings.h @@ -165,6 +165,15 @@ struct Settings // Notify UI about load errors Atomic load_status_text; + + // Enables the ramping down of old samples once X groups of the same instrument are playing. + Atomic enable_voice_limit{false}; + // Max number of voices before old samples are ramped down. + static std::size_t constexpr voice_limit_max_default = 15; + Atomic voice_limit_max{voice_limit_max_default}; + // Time it takes for an old sample to completely fall silent. + static float constexpr voice_limit_rampdown_default = 0.5f; + Atomic voice_limit_rampdown{voice_limit_rampdown_default}; }; //! Settings getter class. @@ -243,6 +252,10 @@ struct SettingsGetter SettingRef load_status_text; + SettingRef enable_voice_limit; + SettingRef voice_limit_max; + SettingRef voice_limit_rampdown; + SettingsGetter(Settings& settings) : drumkit_file(settings.drumkit_file) , drumkit_load_status(settings.drumkit_load_status) @@ -300,6 +313,9 @@ struct SettingsGetter , audition_instrument{settings.audition_instrument} , audition_velocity{settings.audition_velocity} , load_status_text{settings.load_status_text} + , enable_voice_limit{settings.enable_voice_limit} + , voice_limit_max{settings.voice_limit_max} + , voice_limit_rampdown{settings.voice_limit_rampdown} { } }; @@ -379,6 +395,10 @@ public: Notifier load_status_text; + Notifier enable_voice_limit; + Notifier voice_limit_max; + Notifier voice_limit_rampdown; + void evaluate() { #define EVAL(x) if(settings.x.hasChanged()) { x(settings.x.getValue()); } @@ -453,6 +473,10 @@ public: EVAL(audition_velocity); EVAL(load_status_text); + + EVAL(enable_voice_limit); + EVAL(voice_limit_max); + EVAL(voice_limit_rampdown); } SettingsNotifier(Settings& settings) -- cgit v1.2.3