/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /*************************************************************************** * inputprocessor.cc * * Sat Apr 23 20:39:30 CEST 2016 * Copyright 2016 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 "inputprocessor.h" #include #include #include #include #include "instrument.h" #include "staminafilter.h" #include "latencyfilter.h" #include "velocityfilter.h" #include "cpp11fix.h" //TODO: This is just for development purposes. Remove when done. #include "tracer.h" //Anonymous namespace for local functions. namespace { //! Applies choke with rampdown time in ms to event starting at offset. static void applyChoke(Settings& settings, EventSample& event, double length_ms, timepos_t offset) { std::size_t ramp_length = (length_ms / 1000.) * settings.samplerate.load(); event.rampdown_count = ramp_length; event.rampdown_offset = offset; event.ramp_length = ramp_length; } //! Applies choke group actions to active events based on the input event static void applyChokeGroup(Settings& settings, DrumKit& kit, Instrument& instr, event_t& event, std::list* activeevents) { std::size_t instrument_id = event.instrument; if(instr.getGroup() == "") { return; } // Add event to ramp down all existing events with the same groupname. for(const auto& ch : kit.channels) { for(auto active_event : activeevents[ch.num]) { if(active_event->getType() == Event::sample) { auto& event_sample = *static_cast(active_event); if(event_sample.group == instr.getGroup() && event_sample.instrument_id != instrument_id && 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); } } } } } //! Applies directed choke actions to active events based on the input event static void applyDirectedChoke(Settings& settings, DrumKit& kit, Instrument& instr, event_t& event, std::list* activeevents) { for(const auto& choke : instr.getChokes()) { // Add event to ramp down all existing events with the same groupname. for(const auto& ch : kit.channels) { for(auto active_event : activeevents[ch.num]) { if(active_event->getType() == Event::sample) { auto& event_sample = *static_cast(active_event); if(choke.instrument_id == event_sample.instrument_id && event_sample.rampdown_count == -1) // Only if not already ramping. { // choke.choketime is in ms applyChoke(settings, event_sample, choke.choketime, event.offset); } } } } } } } //End of anonymous namespace. InputProcessor::InputProcessor(Settings& settings, DrumKit& kit, std::list* activeevents, Random& random) : kit(kit) , activeevents(activeevents) , is_stopping(false) , settings(settings) , insert_group_id(0) { tracer::trace("building InputProcessor", '\n'); // Build filter list filters.emplace_back(std::make_unique(settings)); filters.emplace_back(std::make_unique(settings, random)); filters.emplace_back(std::make_unique(settings, random)); } bool InputProcessor::process(std::vector& events, std::size_t pos, double resample_ratio) { for(auto& event: events) { if(event.type == EventType::OnSet) { if(!processOnset(event, pos, resample_ratio)) { continue; } } if(event.type == EventType::Choke) { if(!processChoke(event, pos, resample_ratio)) { continue; } } if(!processStop(event)) { return false; } } return true; } std::size_t InputProcessor::getLatency() const { std::size_t latency = 0; for(const auto& filter : filters) { latency += filter->getLatency(); } return latency; } bool InputProcessor::processOnset(event_t& event, std::size_t pos, double resample_ratio) { tracer::trace("processOnset was called", '\n'); 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; } auto const original_level = event.velocity; for(auto& filter : filters) { // This line might change the 'event' variable bool keep = filter->filter(event, event.offset + pos); if(!keep) { return false; // Skip event completely } } //TODO: A lookup map would be much better... const size_t max_voices=getMaxVoicesForInstrument(instrument_id); //TODO: Somehow this seems like a very specific case of a group. applyVoiceLimit(event, max_voices); // Mute other instruments in the same group applyChokeGroup(settings, kit, *instr, event, activeevents); // Apply directed chokes to mute other instruments if needed applyDirectedChoke(settings, kit, *instr, event, activeevents); 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); if(sample == nullptr) { ERR(inputprocessor, "Missing Sample.\n"); return false; } auto const selected_level = (sample->getPower() - power_min)/power_span; settings.velocity_modifier_current.store(selected_level/original_level); ++insert_group_id; //Insert group id 0 is not allowed. for(Channel& ch: kit.channels) { const auto af = sample->getAudioFile(ch); if(af == nullptr || !af->isValid()) { //DEBUG(inputprocessor, "Missing AudioFile.\n"); continue; } //DEBUG(inputprocessor, "Adding event %d.\n", event.offset); auto evt = new EventSample(ch.num, 1.0, af, instr->getGroup(), instrument_id, insert_group_id); evt->offset = (event.offset + pos) * resample_ratio; if(settings.normalized_samples.load() && sample->getNormalized()) { evt->scale *= event.velocity; } tracer::trace("event added to channel ", ch.num, " for instrument id ", instrument_id, '\n'); activeevents[ch.num].push_back(evt); } return true; } bool InputProcessor::processChoke(event_t& event, std::size_t pos, double resample_ratio) { 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; } for(auto& filter : filters) { // This line might change the 'event' variable bool keep = filter->filter(event, event.offset + pos); if(!keep) { return false; // Skip event completely } } // Add event to ramp down all existing events with the same groupname. for(const auto& ch : kit.channels) { for(auto active_event : activeevents[ch.num]) { if(active_event->getType() == Event::sample) { auto& event_sample = *static_cast(active_event); if(event_sample.instrument_id == instrument_id && 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); } } } } return true; } bool InputProcessor::processStop(event_t& event) { if(event.type == EventType::Stop) { is_stopping = true; } if(is_stopping) { // Count the number of active events. int num_active_events = 0; for(auto& ch: kit.channels) { num_active_events += activeevents[ch.num].size(); } if(num_active_events == 0) { // No more active events - now we can stop the engine. return false; } } return true; } //TODO: Document. void InputProcessor::applyVoiceLimit(const event_t& event, size_t max_voices) { //Find out how many voices for the instrument we are currently playing... const auto instrument_id = event.instrument; size_t current_count{0}; tracer::trace("applyVoiceLimit will count for instrument id ", instrument_id, '\n'); for(const auto& ch : kit.channels) { current_count+=std::count_if(std::begin(activeevents[ch.num]), std::end(activeevents[ch.num]), [instrument_id](Event const * active_event) { return active_event->getType() == Event::sample && static_cast(active_event)->instrument_id == instrument_id; }); } //Early exit if the max number of voices has yet to be reached... tracer::trace("found ", current_count, " for instrument id ", instrument_id, '\n'); if(current_count <= max_voices) { return; } //Locate the earliest sample group for this instrument that is not ramping. tracer::trace("locating earliest sample group\n"); std::size_t earliest_group{0}; for(const auto& ch : kit.channels) { for(const auto active_event : activeevents[ch.num]) { if(active_event->getType() == Event::sample) { auto& event_sample = *static_cast(active_event); //Same instrument, not ramping, with a lesser group. if(event_sample.instrument_id != instrument_id && -1 == event_sample.rampdown_count //TODO: Not proud of this, not proud of it at all. && (event_sample.insert_group_id < earliest_group || 0 == earliest_group)) { earliest_group=event_sample.insert_group_id; } } } } tracer::trace("earliest sample group was ", earliest_group, '\n'); //Ramp down everyone belonging to that group... for(const auto& ch : kit.channels) { for(auto active_event : activeevents[ch.num]) { if(active_event->getType() == Event::sample) { auto& event_sample = *static_cast(active_event); if(event_sample.insert_group_id == earliest_group) { tracer::trace("found and ramping in channel ", ch.num, '\n'); applyChoke(settings, event_sample, 68, event.offset); } } } } } //TODO: Document. size_t InputProcessor::getMaxVoicesForInstrument(size_t instrument_id) const { //TODO: This would depend on the configuration set on the GUI. return 16; }