From faaaf5d328429fd2c3f38131f7a874d056761f3f Mon Sep 17 00:00:00 2001 From: Sander Vocke Date: Sun, 28 Jul 2024 09:24:01 +0200 Subject: Openness development --- src/DGDOM.h | 4 +- src/audioinputenginemidi.cc | 99 +++++++++++++++++++++++++++------------ src/audioinputenginemidi.h | 1 + src/dgxmlparser.cc | 22 +++++---- src/domloader.cc | 3 +- src/drumgizmo.cc | 4 +- src/engineevent.h | 12 +++-- src/events.h | 4 +- src/inputprocessor.cc | 111 ++++++++++++++++++++++++++++++++++++++++---- src/inputprocessor.h | 8 +++- src/instrument.cc | 12 +++-- src/instrument.h | 7 ++- src/instrumentstate.h | 40 ++++++++++++++++ src/midimapparser.cc | 44 ++++++++++++++---- src/midimapper.cc | 65 ++++++++++++++++++++++---- src/midimapper.h | 42 +++++++++++++++-- src/powermapfilter.cc | 6 +-- src/sample.cc | 9 +++- src/sample.h | 5 +- src/sample_selection.cc | 15 ++++-- src/sample_selection.h | 2 +- src/settings.h | 18 +++++-- src/staminafilter.cc | 2 +- src/velocityfilter.cc | 4 +- 24 files changed, 441 insertions(+), 98 deletions(-) create mode 100644 src/instrumentstate.h (limited to 'src') diff --git a/src/DGDOM.h b/src/DGDOM.h index 474b29c..d179268 100644 --- a/src/DGDOM.h +++ b/src/DGDOM.h @@ -58,7 +58,8 @@ struct AudioFileDOM struct SampleDOM { std::string name; - double power; // >= v2.0 only + double power; // >= v2.0 only + double openness; // >= v2.0 only bool normalized; // >= v2.0 only std::vector audiofiles; }; @@ -76,6 +77,7 @@ struct InstrumentDOM std::string description; std::vector samples; std::vector instrument_channels; + float openness_choke_threshold {0.0f}; // <= 0 means disabled // v1.0 only std::vector velocities; diff --git a/src/audioinputenginemidi.cc b/src/audioinputenginemidi.cc index 2e794f4..fa2fc71 100644 --- a/src/audioinputenginemidi.cc +++ b/src/audioinputenginemidi.cc @@ -26,6 +26,7 @@ */ #include "audioinputenginemidi.h" +#include "instrument.h" #include "midimapparser.h" #include @@ -75,8 +76,8 @@ bool AudioInputEngineMidi::loadMidiMap(const std::string& midimap_file, mmap.swap(instrmap, midimap_parser.midimap); midimap = file; + reloaded = true; is_valid = true; - return true; } @@ -94,6 +95,7 @@ bool AudioInputEngineMidi::isValid() const constexpr std::uint8_t NoteOff{0x80}; constexpr std::uint8_t NoteOn{0x90}; constexpr std::uint8_t NoteAftertouch{0xA0}; +constexpr std::uint8_t ControlChange{0xB0}; // Note type mask: constexpr std::uint8_t NoteMask{0xF0}; @@ -103,47 +105,86 @@ void AudioInputEngineMidi::processNote(const std::uint8_t* midi_buffer, std::size_t offset, std::vector& events) { + if(reloaded) + { + events.push_back({EventType::ResetInstrumentStates, 0, 0, InstrumentStateKind::NoneOrAny, 0.0f}); + reloaded = false; + } + if(midi_buffer_length < 3) { return; } - auto key = midi_buffer[1]; // NOLINT - span - auto velocity = midi_buffer[2]; // NOLINT - span - auto instrument_idx = mmap.lookup(key); - auto instruments = mmap.lookup(key); - for(const auto& instrument_idx : instruments) + switch(midi_buffer[0] & NoteMask) // NOLINT - span { - switch(midi_buffer[0] & NoteMask) // NOLINT - span - { - case NoteOff: - // Ignore for now - break; + case NoteOff: + // Ignore for now + break; - case NoteOn: - if(velocity != 0) + case NoteOn: + { + auto key = midi_buffer[1]; // NOLINT - span + auto velocity = midi_buffer[2]; // NOLINT - span + auto map_entries = mmap.lookup(key, MapFrom::Note, MapTo::PlayInstrument); + for(const auto& entry : map_entries) { - constexpr float lower_offset{0.5f}; - constexpr float midi_velocity_max{127.0f}; - // maps velocities to [.5/127, 126.5/127] - assert(velocity <= 127); // MIDI only support up to 127 - auto centered_velocity = - (static_cast(velocity) - lower_offset) / midi_velocity_max; - events.push_back({EventType::OnSet, (std::size_t)instrument_idx, - offset, centered_velocity}); + auto instrument_idx = mmap.lookup_instrument(entry.instrument_name); + if(instrument_idx >= 0 && velocity != 0) + { + constexpr float lower_offset{0.5f}; + constexpr float midi_velocity_max{127.0f}; + // maps velocities to [.5/127, 126.5/127] + assert(velocity <= 127); // MIDI only support up to 127 + auto centered_velocity = + (static_cast(velocity) - lower_offset) / midi_velocity_max; + events.push_back({EventType::OnSet, (std::size_t)instrument_idx, + offset, InstrumentStateKind::NoneOrAny, centered_velocity}); + } } - break; + } + break; - case NoteAftertouch: - if(velocity > 0) + case NoteAftertouch: + { + auto key = midi_buffer[1]; // NOLINT - span + auto velocity = midi_buffer[2]; // NOLINT - span + auto map_entries = mmap.lookup(key, MapFrom::Note, MapTo::PlayInstrument); + for(const auto& entry : map_entries) { - events.push_back({EventType::Choke, (std::size_t)instrument_idx, - offset, .0f}); + auto instrument_idx = mmap.lookup_instrument(entry.instrument_name); + if(instrument_idx >= 0 && velocity > 0) + { + events.push_back({EventType::Choke, (std::size_t)instrument_idx, + offset, InstrumentStateKind::NoneOrAny, .0f}); + } } - break; + } + break; + + case ControlChange: + { + auto controller_number = midi_buffer[1]; // NOLINT - span + auto value = midi_buffer[2]; // NOLINT - span - default: - break; + auto map_entries = mmap.lookup(controller_number, + MapFrom::CC, + MapTo::InstrumentState); + for(const auto& entry : map_entries) + { + auto instrument_idx = mmap.lookup_instrument(entry.instrument_name); + if (instrument_idx >= 0) { + constexpr float lower_offset{0.5f}; + constexpr float cc_value_max{127.0f}; + auto centered_value = + (static_cast(value) - lower_offset) / cc_value_max; + + events.push_back({EventType::ChangeInstrumentState, + (std::size_t)instrument_idx, 0, + entry.maybe_instrument_state_kind, + centered_value}); + } + } } } } diff --git a/src/audioinputenginemidi.h b/src/audioinputenginemidi.h index 386a055..a71fda9 100644 --- a/src/audioinputenginemidi.h +++ b/src/audioinputenginemidi.h @@ -69,6 +69,7 @@ protected: private: std::string midimap; bool is_valid{false}; + bool reloaded{true}; ConfigFile refs{REFSFILE}; }; diff --git a/src/dgxmlparser.cc b/src/dgxmlparser.cc index 0d3cdcd..180d4cd 100644 --- a/src/dgxmlparser.cc +++ b/src/dgxmlparser.cc @@ -351,16 +351,20 @@ bool parseInstrumentFile(const std::string& filename, InstrumentDOM& dom, LogFun dom.samples.emplace_back(); res &= attrcpy(dom.samples.back().name, sample, "name", logger, filename); - // Power only part of >= v2.0 instruments. - if(dom.version == "1.0") + // Settings which are only part of >= v2.0 instruments. + dom.samples.back().power = 0.0; + dom.samples.back().normalized = false; // optional - defaults to false + dom.samples.back().openness = 0.0; // optional - defaults to 0.0 + if(dom.version != "1.0") { - dom.samples.back().power = 0.0; - } - else - { - res &= attrcpy(dom.samples.back().power, sample, "power", logger, filename); - dom.samples.back().normalized = false; - res &= attrcpy(dom.samples.back().normalized, sample, "normalized", logger, filename, true); + res &= attrcpy(dom.samples.back().power, sample, "power", + logger, filename); + res &= attrcpy(dom.samples.back().normalized, sample, "normalized", + logger, filename, true); + res &= attrcpy(dom.samples.back().openness, sample, "openness", + logger, filename, true); + dom.samples.back().openness = + std::min(1.0, std::max(dom.samples.back().openness, 0.0)); } for(pugi::xml_node audiofile: sample.children("audiofile")) diff --git a/src/domloader.cc b/src/domloader.cc index c78ed75..c972f08 100644 --- a/src/domloader.cc +++ b/src/domloader.cc @@ -89,7 +89,7 @@ bool DOMLoader::loadDom(const std::string& basepath, continue; } - auto instrument = std::make_unique(settings, random); + auto instrument = std::make_unique(settings, random, instrumentdom.openness_choke_threshold); instrument->setGroup(instrumentref.group); instrument->_name = instrumentdom.name; instrument->version = instrumentdom.version; @@ -99,6 +99,7 @@ bool DOMLoader::loadDom(const std::string& basepath, for(const auto& sampledom : instrumentdom.samples) { auto sample = new Sample(sampledom.name, sampledom.power, + sampledom.openness, sampledom.normalized); for(const auto& audiofiledom : sampledom.audiofiles) { diff --git a/src/drumgizmo.cc b/src/drumgizmo.cc index 85624ca..7ccf6b7 100644 --- a/src/drumgizmo.cc +++ b/src/drumgizmo.cc @@ -26,11 +26,9 @@ */ #include "drumgizmo.h" -#include #include #include #include -#include #include "audiotypes.h" #include @@ -174,7 +172,7 @@ bool DrumGizmo::run(size_t pos, sample_t *samples, size_t nsamples) } } - events.push_back({EventType::OnSet, instrument_index, 0, velocity}); + events.push_back({EventType::OnSet, instrument_index, 0, InstrumentStateKind::NoneOrAny, velocity}); } bool active_events_left = diff --git a/src/engineevent.h b/src/engineevent.h index 9c60a4a..ff9b8ba 100644 --- a/src/engineevent.h +++ b/src/engineevent.h @@ -27,6 +27,7 @@ #pragma once #include +#include "instrumentstate.h" //! Event types enum class EventType @@ -34,13 +35,16 @@ enum class EventType OnSet, Choke, Stop, + ChangeInstrumentState, + ResetInstrumentStates }; //! POD datatype for input event transport. struct event_t { - EventType type; //!< The type of the event. - std::size_t instrument; //!< The instrument number. - std::size_t offset; //!< The offset position in the input buffer - float velocity; //!< The velocity if the type is a note on [0; 1] + EventType type; //!< The type of the event. + std::size_t instrument; //!< The instrument number. + std::size_t offset; //!< The offset position in the input buffer + InstrumentStateKind state_kind; //!< For instrument state changes: the state to change + float velocity_or_state; //!< State value, or the velocity if the type is a note on [0; 1] }; diff --git a/src/events.h b/src/events.h index 538127b..0ced567 100644 --- a/src/events.h +++ b/src/events.h @@ -71,7 +71,7 @@ class SampleEvent { public: SampleEvent(channel_t ch, float g, AudioFile* af, const std::string& grp, - std::size_t instrument_id) + std::size_t instrument_id, float openness) : Event(Event::Type::SampleEvent, ch) , cache_id(CACHE_NOID) , gain(g) @@ -81,6 +81,7 @@ public: , rampdown_count(-1) , ramp_length(0) , instrument_id(instrument_id) + , openness(openness) { } @@ -105,4 +106,5 @@ public: std::size_t rampdown_offset{0}; float scale{1.0f}; std::size_t instrument_id; + float openness; //< Openness related to this sample. }; diff --git a/src/inputprocessor.cc b/src/inputprocessor.cc index c0c0e92..6e5d205 100644 --- a/src/inputprocessor.cc +++ b/src/inputprocessor.cc @@ -3,7 +3,7 @@ * inputprocessor.cc * * Sat Apr 23 20:39:30 CEST 2016 - * Copyright 2016 André Nusser + * Copyright 2016 Andr� Nusser * andre.nusser@googlemail.com ****************************************************************************/ @@ -36,7 +36,6 @@ #include "powermapfilter.h" #include "staminafilter.h" #include "velocityfilter.h" - #include "cpp11fix.h" class VelocityStorer @@ -50,7 +49,7 @@ public: bool filter(event_t& event, std::size_t pos) override { - original_velocity = event.velocity; + original_velocity = event.velocity_or_state; return true; } @@ -70,7 +69,7 @@ public: bool filter(event_t& event, std::size_t pos) override { - settings.velocity_modifier_current.store(event.velocity / original_velocity); + settings.velocity_modifier_current.store(event.velocity_or_state / original_velocity); return true; } @@ -94,6 +93,10 @@ InputProcessor::InputProcessor(Settings& settings, filters.emplace_back(std::make_unique(settings, random)); filters.emplace_back(std::make_unique(settings, random)); filters.emplace_back(std::make_unique(settings, original_velocity)); + + // To prevent needing run-time allocation for first-time instruments + constexpr size_t reserved_n_instruments = 256; + instrument_states.reserve(reserved_n_instruments); } bool InputProcessor::process(std::vector& events, @@ -118,6 +121,22 @@ bool InputProcessor::process(std::vector& events, } } + if(event.type == EventType::ChangeInstrumentState) + { + if(!processStateChange(event, pos)) + { + continue; + } + } + + if(event.type == EventType::ResetInstrumentStates) + { + if(!processResetStates()) + { + continue; + } + } + if(!processStop(event)) { return false; @@ -207,6 +226,77 @@ void InputProcessor::applyDirectedChoke(Settings& settings, DrumKit& kit, } } +bool InputProcessor::processResetStates() +{ + instrument_states.clear(); + return true; +} + +bool InputProcessor::processOpennessChange(event_t& event, Instrument &inst, float openness, size_t pos) +{ + auto &state = instrument_states[event.instrument]; // Constructs if necessary + auto threshold = inst.getOpennessChokeThreshold(); + + if(threshold > 0.0f && + state.openness > threshold && openness <= threshold) + { + // We crossed the openness threshold and should choke all running samples that have + // higher openness. + for(const auto& ch : kit.channels) + { + if(ch.num >= NUM_CHANNELS) // kit may have more channels than the engine + { + continue; + } + + for(auto& event_sample : events_ds.iterateOver(ch.num)) + { + if(event_sample.instrument_id == event.instrument && // Only applies to self + event_sample.openness > threshold && // Only samples that are more open than the threshold + event_sample.rampdown_count == -1) // Only if not already ramping + { + // Fixed group rampdown time of 68ms, independent of samplerate + applyChoke(settings, event_sample, 68, event.offset, pos); + } + } + } + } + + state.openness = event.velocity_or_state; + return true; +} + +bool InputProcessor::processStateChange(event_t& event, size_t pos) +{ + if(!kit.isValid()) + { + return false; + } + + std::size_t instrument_id = event.instrument; + Instrument* instr = nullptr; + + if(instrument_id < kit.instruments.size()) + { + instr = kit.instruments[instrument_id].get(); + } + + if(instr == nullptr || !instr->isValid()) + { + ERR(inputprocessor, "Missing Instrument %d.\n", (int)instrument_id); + return false; + } + + switch (event.state_kind) + { + case InstrumentStateKind::Openness: + return processOpennessChange(event, *instr, event.velocity_or_state, pos); + default: + ERR(inputprocessor, "Unsupported state change"); + return false; + } +} + bool InputProcessor::processOnset(event_t& event, std::size_t pos, double resample_ratio) { @@ -229,7 +319,7 @@ bool InputProcessor::processOnset(event_t& event, std::size_t pos, return false; } - original_velocity = event.velocity; + original_velocity = event.velocity_or_state; for(auto& filter : filters) { // This line might change the 'event' variable @@ -250,8 +340,11 @@ bool InputProcessor::processOnset(event_t& event, std::size_t 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; - const auto sample = instr->sample(instrument_level, event.offset + pos); + float const instrument_level = power_min + event.velocity_or_state*power_span; + + auto state_it = instrument_states.find(instrument_id); + auto openness = (state_it != instrument_states.end()) ? state_it->second.openness : 0.0f; + const auto sample = instr->sample(instrument_level, openness, event.offset + pos); if(sample == nullptr) { @@ -292,12 +385,12 @@ bool InputProcessor::processOnset(event_t& event, std::size_t pos, auto& event_sample = events_ds.emplace(ch.num, ch.num, 1.0, af, - instr->getGroup(), instrument_id); + instr->getGroup(), instrument_id, openness); event_sample.offset = (event.offset + pos) * resample_ratio; if(settings.normalized_samples.load() && sample->getNormalized()) { - event_sample.scale *= event.velocity; + event_sample.scale *= event.velocity_or_state; } } } diff --git a/src/inputprocessor.h b/src/inputprocessor.h index a8dc45b..a68a517 100644 --- a/src/inputprocessor.h +++ b/src/inputprocessor.h @@ -3,7 +3,7 @@ * inputprocessor.h * * Sat Apr 23 20:39:30 CEST 2016 - * Copyright 2016 André Nusser + * Copyright 2016 Andr� Nusser * andre.nusser@googlemail.com ****************************************************************************/ @@ -28,6 +28,7 @@ #include #include +#include #include @@ -37,6 +38,7 @@ #include "id.h" #include "inputfilter.h" #include "engineevent.h" +#include "instrumentstate.h" struct Settings; class Random; @@ -62,7 +64,10 @@ private: bool processOnset(event_t& event, std::size_t pos, double resample_ratio); bool processChoke(event_t& event, std::size_t pos, double resample_ratio); + bool processStateChange(event_t& event, std::size_t pos); + bool processResetStates(); bool processStop(event_t& event); + bool processOpennessChange(event_t& event, Instrument &inst, float openness, size_t pos); //! Applies choke with rampdown time in ms to event starting at offset. void applyChoke(Settings& settings, SampleEvent& event, @@ -86,4 +91,5 @@ private: Settings& settings; float original_velocity{0.0f}; + std::unordered_map instrument_states; }; diff --git a/src/instrument.cc b/src/instrument.cc index b7bcdd9..6556fb1 100644 --- a/src/instrument.cc +++ b/src/instrument.cc @@ -30,10 +30,11 @@ #include "sample.h" -Instrument::Instrument(Settings& settings, Random& rand) +Instrument::Instrument(Settings& settings, Random& rand, float openness_choke_threshold) : settings(settings) , rand(rand) , sample_selection(settings, rand, powerlist) + , openness_choke_threshold(openness_choke_threshold) { DEBUG(instrument, "new %p\n", this); mod = 1.0; @@ -54,12 +55,12 @@ bool Instrument::isValid() const return this == magic; } -const Sample* Instrument::sample(level_t level, size_t pos) +const Sample* Instrument::sample(level_t level, float openness, std::size_t pos) { if(version >= VersionStr("2.0")) { // Version 2.0 - return sample_selection.get(level * mod, pos); + return sample_selection.get(level * mod, openness, pos); } else { @@ -153,6 +154,11 @@ float Instrument::getMinPower() const } } +float Instrument::getOpennessChokeThreshold() const +{ + return openness_choke_threshold; +} + const std::vector& Instrument::getChokes() { return chokes; diff --git a/src/instrument.h b/src/instrument.h index c06ccdc..e5de348 100644 --- a/src/instrument.h +++ b/src/instrument.h @@ -46,10 +46,10 @@ struct Choke; class Instrument { public: - Instrument(Settings& settings, Random& rand); + Instrument(Settings& settings, Random& rand, float openness_choke_threshold); ~Instrument(); - const Sample* sample(level_t level, size_t pos); + const Sample* sample(level_t level, float openness, std::size_t pos); std::size_t getID() const; const std::string& getName() const; @@ -70,6 +70,8 @@ public: float getMaxPower() const; float getMinPower() const; + float getOpennessChokeThreshold() const; + const std::vector& getChokes(); private: @@ -98,6 +100,7 @@ private: size_t lastpos; float mod; + float openness_choke_threshold; Settings& settings; Random& rand; PowerList powerlist; diff --git a/src/instrumentstate.h b/src/instrumentstate.h new file mode 100644 index 0000000..6c3c0b4 --- /dev/null +++ b/src/instrumentstate.h @@ -0,0 +1,40 @@ +/* -*- Mode: c++ -*- */ +/*************************************************************************** + * instrumentstate.h + * + * Wed Jul 24 12:55:00 CEST 2024 + * Copyright 2024 Sander Vocke + * + ****************************************************************************/ + +/* + * 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 + +enum class InstrumentStateKind { + Openness, + NoneOrAny +}; + +//! Tracks the persistent state of an instrument during play. +struct InstrumentState { + + //! Openness (typically for a hi-hat). + //! 0.0-1.0, where 0.0 is closed and 1.0 is fully open. + float openness = 0.0; +}; \ No newline at end of file diff --git a/src/midimapparser.cc b/src/midimapparser.cc index 363e1d5..20ccc77 100644 --- a/src/midimapparser.cc +++ b/src/midimapparser.cc @@ -29,6 +29,8 @@ #include #include +#include + bool MidiMapParser::parseFile(const std::string& filename) { pugi::xml_document doc; @@ -42,16 +44,42 @@ bool MidiMapParser::parseFile(const std::string& filename) pugi::xml_node midimap_node = doc.child("midimap"); for(pugi::xml_node map_node : midimap_node.children("map")) { - constexpr int bad_value = 10000; - auto note = map_node.attribute("note").as_int(bad_value); - auto instr = map_node.attribute("instr").as_string(); - if(std::string(instr) == "" || note == bad_value) + constexpr int default_int = 10000; + auto note = map_node.attribute("note").as_int(default_int); + auto cc = map_node.attribute("cc").as_int(default_int); + auto instr = std::string(map_node.attribute("instr").as_string()); + auto control_str = std::string(map_node.attribute("control").as_string()); + auto control = (control_str == "openness" ? InstrumentStateKind::Openness : + InstrumentStateKind::NoneOrAny); + + bool is_conflict = (note != default_int && cc != default_int); + bool is_note_play_instrument = + (!is_conflict && instr != "" && note != default_int); + bool is_cc_control = + (!is_conflict && instr != "" && control != InstrumentStateKind::NoneOrAny && cc != default_int); + + if (is_note_play_instrument) { - continue; + MidimapEntry entry( + MapFrom::Note, + note, + MapTo::PlayInstrument, + instr, + InstrumentStateKind::NoneOrAny + ); + midimap.push_back(entry); + } + else if (is_cc_control) + { + MidimapEntry entry( + MapFrom::CC, + cc, + MapTo::InstrumentState, + instr, + control + ); + midimap.push_back(entry); } - - MidimapEntry entry{note, instr}; - midimap.push_back(entry); } return true; diff --git a/src/midimapper.cc b/src/midimapper.cc index 345ce2f..b4de580 100644 --- a/src/midimapper.cc +++ b/src/midimapper.cc @@ -26,25 +26,72 @@ */ #include "midimapper.h" -std::vector MidiMapper::lookup(int note_id) +#include + +MidimapEntry::MidimapEntry(MapFrom from_kind, + int from_id, + MapTo to_kind, + std::string instrument_name, + InstrumentStateKind maybe_instrument_state_kind + ) : + from_kind(from_kind) + , from_id(from_id) + , to_kind(to_kind) + , instrument_name(instrument_name) + , maybe_instrument_state_kind(maybe_instrument_state_kind) +{} + +MidimapEntry::MidimapEntry(const MidimapEntry& other) +{ + *this = other; +} + +MidimapEntry &MidimapEntry::operator=(const MidimapEntry& other) { - std::vector instruments; + from_kind = other.from_kind; + from_id = other.from_id; + to_kind = other.to_kind; + instrument_name = other.instrument_name; + maybe_instrument_state_kind = other.maybe_instrument_state_kind; + + return *this; +} + +int MidiMapper::lookup_instrument(std::string name) { + const std::lock_guard guard(mutex); + auto instrmap_it = instrmap.find(name); + if(instrmap_it != instrmap.end()) + { + return instrmap_it->second; + } + return -1; +} + +std::vector MidiMapper::lookup( + int from_id, + MapFrom from_kind, + MapTo to_kind, + InstrumentStateKind state_kind) +{ + std::vector rval; const std::lock_guard guard(mutex); for(const auto& map_entry : midimap) { - if(map_entry.note_id == note_id) + bool match = true; + match = match && (from_id == -1 || from_id == map_entry.from_id); + match = match && (from_kind == MapFrom::NoneOrAny || from_kind == map_entry.from_kind); + match = match && (to_kind == MapTo::NoneOrAny || to_kind == map_entry.to_kind); + match = match && (state_kind == InstrumentStateKind::NoneOrAny || state_kind == map_entry.maybe_instrument_state_kind); + + if(match) { - auto instrmap_it = instrmap.find(map_entry.instrument_name); - if(instrmap_it != instrmap.end()) - { - instruments.push_back(instrmap_it->second); - } + rval.push_back(map_entry); } } - return instruments; + return rval; } void MidiMapper::swap(instrmap_t& instrmap, midimap_t& midimap) diff --git a/src/midimapper.h b/src/midimapper.h index 94781d4..dcca6dd 100644 --- a/src/midimapper.h +++ b/src/midimapper.h @@ -30,11 +30,40 @@ #include #include #include +#include + +#include "instrumentstate.h" + +enum class MapFrom { + Note, + CC, + NoneOrAny +}; + +enum class MapTo { + PlayInstrument, + InstrumentState, + NoneOrAny +}; struct MidimapEntry { - int note_id; + MapFrom from_kind; // What kind of input is mapped from + int from_id; // note or CC number + MapTo to_kind; // What kind of action is mapped to + std::string instrument_name; + + // For mapping to control state changes + InstrumentStateKind maybe_instrument_state_kind; + + MidimapEntry &operator=(const MidimapEntry& other); + MidimapEntry(const MidimapEntry& other); + MidimapEntry(MapFrom from_kind, + int from_id, + MapTo to_kind, + std::string instrument_name, + InstrumentStateKind maybe_instrument_state_kind); }; using midimap_t = std::vector; @@ -43,8 +72,15 @@ using instrmap_t = std::map; class MidiMapper { public: - //! Lookup note in map and returns the corresponding instrument index list. - std::vector lookup(int note_id); + //! Lookup midi map entries matching the given query. + std::vector lookup( + int from_id = -1, // note or cc #. -1 matches all notes/cc's + MapFrom from_kind = MapFrom::NoneOrAny, // NoneOrAny will return both CC and note maps + MapTo to_kind = MapTo::NoneOrAny, // NoneOrAny will return both instrument hits and controls + InstrumentStateKind state_kind = InstrumentStateKind::NoneOrAny // NoneOrAny maps all state control kinds + ); + + int lookup_instrument(std::string name); //! Set new map sets. void swap(instrmap_t& instrmap, midimap_t& midimap); diff --git a/src/powermapfilter.cc b/src/powermapfilter.cc index 45df51e..0bf02b4 100644 --- a/src/powermapfilter.cc +++ b/src/powermapfilter.cc @@ -38,7 +38,7 @@ bool PowermapFilter::filter(event_t& event, size_t pos) // the position is irrelevant for this filter (void) pos; - settings.powermap_input.store(event.velocity); + settings.powermap_input.store(event.velocity_or_state); if (settings.enable_powermap.load()) { powermap.setFixed0({settings.powermap_fixed0_x.load(), settings.powermap_fixed0_y.load()}); @@ -46,9 +46,9 @@ bool PowermapFilter::filter(event_t& event, size_t pos) powermap.setFixed2({settings.powermap_fixed2_x.load(), settings.powermap_fixed2_y.load()}); powermap.setShelf(settings.powermap_shelf.load()); - event.velocity = powermap.map(event.velocity); + event.velocity_or_state = powermap.map(event.velocity_or_state); } - settings.powermap_output.store(event.velocity); + settings.powermap_output.store(event.velocity_or_state); return true; } diff --git a/src/sample.cc b/src/sample.cc index 9af2c08..63a16d9 100644 --- a/src/sample.cc +++ b/src/sample.cc @@ -28,9 +28,11 @@ #include -Sample::Sample(const std::string& name, double power, bool normalized) +Sample::Sample(const std::string& name, double power, + double openness, bool normalized) : name{name} , power{power} + , openness{openness} , normalized(normalized) , audiofiles{} { @@ -69,6 +71,11 @@ double Sample::getPower() const return power; } +double Sample::getOpenness() const +{ + return openness; +} + bool Sample::getNormalized() const { return normalized; diff --git a/src/sample.h b/src/sample.h index 6c31b6b..31a3741 100644 --- a/src/sample.h +++ b/src/sample.h @@ -37,12 +37,14 @@ using AudioFiles = std::map; class Sample { public: - Sample(const std::string& name, double power, bool normalized = false); + Sample(const std::string& name, double power, + double openness, bool normalized = false); ~Sample(); AudioFile* getAudioFile(const Channel& channel) const; double getPower() const; + double getOpenness() const; bool getNormalized() const; private: @@ -55,6 +57,7 @@ private: std::string name; double power; + double openness; bool normalized; AudioFiles audiofiles; }; diff --git a/src/sample_selection.cc b/src/sample_selection.cc index 31313bb..8603687 100644 --- a/src/sample_selection.cc +++ b/src/sample_selection.cc @@ -54,7 +54,7 @@ void SampleSelection::finalise() last.assign(powerlist.getPowerListItems().size(), 0); } -const Sample* SampleSelection::get(level_t level, std::size_t pos) +const Sample* SampleSelection::get(level_t level, float openness, std::size_t pos) { const auto& samples = powerlist.getPowerListItems(); if(!samples.size()) @@ -64,6 +64,7 @@ const Sample* SampleSelection::get(level_t level, std::size_t pos) std::size_t index_opt = 0; float power_opt{0.f}; + float openness_opt{0.f}; float value_opt{std::numeric_limits::max()}; // the following three values are mostly for debugging float random_opt = 0.; @@ -72,6 +73,7 @@ const Sample* SampleSelection::get(level_t level, std::size_t pos) // Note the magic values in front of the settings factors. const float f_close = 4.*settings.sample_selection_f_close.load(); + const float f_openness = 10.*settings.sample_selection_f_openness.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(); @@ -136,12 +138,19 @@ const Sample* SampleSelection::get(level_t level, std::size_t pos) auto random = rand.floatInRange(0.,1.); auto close = (samples[current_index].power - level)/power_range; auto diverse = 1./(1. + (float)(pos - last[current_index])/settings.samplerate); - auto value = f_close*pow2(close) + f_diverse*diverse + f_random*random; + auto closeopenness = samples[current_index].sample->getOpenness() - openness; + // note that the value below for close and closepos is actually the weighted squared l2 distance in 2d + auto value = + f_close*pow2(close) + + f_openness*pow2(closeopenness) + + f_diverse*diverse + + f_random*random; if (value < value_opt) { index_opt = current_index; power_opt = samples[current_index].power; + openness_opt = samples[current_index].sample->getOpenness(); value_opt = value; random_opt = random; close_opt = close; @@ -151,7 +160,7 @@ const Sample* SampleSelection::get(level_t level, std::size_t pos) } while (up_value_lb <= value_opt || down_value_lb <= value_opt); - DEBUG(rand, "Chose sample with index: %d, value: %f, power %f, random: %f, close: %f, diverse: %f, count: %d", (int)index_opt, value_opt, power_opt, random_opt, close_opt, diverse_opt, (int)count); + DEBUG(rand, "Chose sample with index: %d, value: %f, power: %f, openness: %f, random: %f, close: %f, diverse: %f, count: %d", (int)index_opt, value_opt, power_opt, openness_opt, random_opt, close_opt, diverse_opt, (int)count); last[index_opt] = pos; return samples[index_opt].sample; diff --git a/src/sample_selection.h b/src/sample_selection.h index 8da4e0d..e5f2050 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, std::size_t pos); + const Sample* get(level_t level, float openness, std::size_t pos); private: Settings& settings; diff --git a/src/settings.h b/src/settings.h index fb93d79..d24d77a 100644 --- a/src/settings.h +++ b/src/settings.h @@ -3,7 +3,7 @@ * settings.h * * Tue Mar 22 10:59:46 CET 2016 - * Copyright 2016 Christian Glöckner + * Copyright 2016 Christian Gl�ckner * cgloeckner@freenet.de ****************************************************************************/ @@ -76,13 +76,17 @@ 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 openness_stddev_default = 0.f; // FIXME: set to something sensible static float constexpr sample_selection_f_close_default = .85f; + static float constexpr sample_selection_f_openness_default = 1.f; static float constexpr sample_selection_f_diverse_default = .16f; static float constexpr sample_selection_f_random_default = .07f; Atomic velocity_modifier_falloff{velocity_modifier_falloff_default}; Atomic velocity_modifier_weight{velocity_modifier_weight_default}; Atomic velocity_stddev{velocity_stddev_default}; + Atomic openness_stddev{openness_stddev_default}; Atomic sample_selection_f_close{sample_selection_f_close_default}; + Atomic sample_selection_f_openness{sample_selection_f_openness_default}; Atomic sample_selection_f_diverse{sample_selection_f_diverse_default}; Atomic sample_selection_f_random{sample_selection_f_random_default}; @@ -146,7 +150,7 @@ struct Settings // Current latency offset in ms - for UI Atomic latency_current{0}; - // Powermap parameters + // Power map parameters Atomic enable_powermap; Atomic powermap_fixed0_x{0.}; Atomic powermap_fixed0_y{0.}; @@ -156,7 +160,7 @@ struct Settings Atomic powermap_fixed2_y{1.}; Atomic powermap_shelf{true}; - // Powermap visualizer; -1 is "none" + // Power map visualizer; -1 is "none" Atomic powermap_input{-1.}; Atomic powermap_output{-1.}; @@ -200,7 +204,9 @@ struct SettingsGetter SettingRef velocity_modifier_falloff; SettingRef velocity_modifier_weight; SettingRef velocity_stddev; + SettingRef openness_stddev; SettingRef sample_selection_f_close; + SettingRef sample_selection_f_openness; SettingRef sample_selection_f_diverse; SettingRef sample_selection_f_random; @@ -275,7 +281,9 @@ struct SettingsGetter , velocity_modifier_falloff{settings.velocity_modifier_falloff} , velocity_modifier_weight{settings.velocity_modifier_weight} , velocity_stddev{settings.velocity_stddev} + , openness_stddev{settings.openness_stddev} , sample_selection_f_close{settings.sample_selection_f_close} + , sample_selection_f_openness{settings.sample_selection_f_openness} , sample_selection_f_diverse{settings.sample_selection_f_diverse} , sample_selection_f_random{settings.sample_selection_f_random} , sample_selection_retry_count(settings.sample_selection_retry_count) @@ -345,7 +353,9 @@ public: Notifier velocity_modifier_falloff; Notifier velocity_modifier_weight; Notifier velocity_stddev; + Notifier openness_stddev; Notifier sample_selection_f_close; + Notifier sample_selection_f_openness; Notifier sample_selection_f_diverse; Notifier sample_selection_f_random; Notifier sample_selection_retry_count; @@ -424,7 +434,9 @@ public: EVAL(velocity_modifier_falloff); EVAL(velocity_modifier_weight); EVAL(velocity_stddev); + EVAL(openness_stddev) EVAL(sample_selection_f_close); + EVAL(sample_selection_f_openness); EVAL(sample_selection_f_diverse); EVAL(sample_selection_f_random); EVAL(sample_selection_retry_count); diff --git a/src/staminafilter.cc b/src/staminafilter.cc index 5f7599a..fc14920 100644 --- a/src/staminafilter.cc +++ b/src/staminafilter.cc @@ -63,7 +63,7 @@ bool StaminaFilter::filter(event_t& event, size_t pos) lastpos = 0; } - event.velocity *= mod; + event.velocity_or_state *= mod; if(enable_velocity_modifier) { diff --git a/src/velocityfilter.cc b/src/velocityfilter.cc index 81587d5..04bb9d1 100644 --- a/src/velocityfilter.cc +++ b/src/velocityfilter.cc @@ -38,10 +38,10 @@ bool VelocityFilter::filter(event_t& event, size_t pos) { if (settings.enable_velocity_modifier.load()) { - float mean = event.velocity; + float mean = event.velocity_or_state; float stddev = settings.velocity_stddev.load(); // the 30.0f were determined empirically - event.velocity = random.normalDistribution(mean, stddev / 30.0f); + event.velocity_or_state = random.normalDistribution(mean, stddev / 30.0f); } return true; -- cgit v1.2.3