/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /*************************************************************************** * drumgizmo_lv2.cc * * Wed Mar 2 17:31:32 CET 2016 * Copyright 2016 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 2 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 "drumgizmo_plugin.h" #include #include #include #include #include #include #include "configparser.h" #include "nolocale.h" #ifdef LV2 // Entry point for lv2 plugin instantiation. PG_EXPORT PluginLV2* createEffectInstance() { return new DrumGizmoPlugin(); } #endif #ifdef VST // Entry point for vst plugin instantiation. AudioEffect* createEffectInstance(audioMasterCallback audioMaster) { return new DrumGizmoPlugin(audioMaster); } DrumGizmoPlugin::DrumGizmoPlugin(audioMasterCallback audioMaster) : PluginVST(audioMaster), #else DrumGizmoPlugin::DrumGizmoPlugin() : #endif config_string_io(settings) { init(); drumgizmo = std::make_shared(settings, output, input); resizeWindow(GUI::MainWindow::main_width, GUI::MainWindow::main_height); drumgizmo->setFreeWheel(true); drumgizmo->setSamplerate(44100); drumgizmo->setFrameSize(2048); } void DrumGizmoPlugin::onSamplerateChange(float samplerate) { drumgizmo->setSamplerate(samplerate); } void DrumGizmoPlugin::onFramesizeChange(size_t framesize) { drumgizmo->setFrameSize(framesize); } void DrumGizmoPlugin::onActiveChange(bool active) { } std::string DrumGizmoPlugin::onStateSave() { return config_string_io.get(); } void DrumGizmoPlugin::onStateRestore(const std::string& config) { config_string_io.set(config); } size_t DrumGizmoPlugin::getNumberOfMidiInputs() { return 1; } size_t DrumGizmoPlugin::getNumberOfMidiOutputs() { return 0; } size_t DrumGizmoPlugin::getNumberOfAudioInputs() { return 0; } size_t DrumGizmoPlugin::getNumberOfAudioOutputs() { return NUM_CHANNELS; } std::string DrumGizmoPlugin::getId() { return "DrumGizmo"; } std::string DrumGizmoPlugin::getURI() { return "http://drumgizmo.org"; } std::string DrumGizmoPlugin::getEffectName() { return "DrumGizmo"; } std::string DrumGizmoPlugin::getVendorString() { return "DrumGizmo Team"; } std::string DrumGizmoPlugin::getProductString() { return "DrumGizmo"; } std::string DrumGizmoPlugin::getHomepage() { return "https://www.drumgizmo.org"; } PluginCategory DrumGizmoPlugin::getPluginCategory() { return PluginCategory::Synth; } static float g_samples[NUM_CHANNELS * 4096]; void DrumGizmoPlugin::process(size_t pos, const std::vector& input_events, std::vector& output_events, const std::vector& input_samples, const std::vector& output_samples, size_t count) { setLatency(drumgizmo->getLatency()); this->input_events = &input_events; this->output_samples = &output_samples; drumgizmo->run(pos, g_samples, count); this->input_events = nullptr; this->output_samples = nullptr; } bool DrumGizmoPlugin::hasInlineGUI() { return true; } class InlinePixelBufferAlpha : public dggui::PixelBufferAlpha { public: InlinePixelBufferAlpha(Plugin::InlineDrawContext& context) { buf = context.data; width = context.width; height = context.height; x = 0; y = 0; } }; class InlineCanvas : public dggui::Canvas { public: InlineCanvas(Plugin::InlineDrawContext& context) : pixbuf(context) { } // From Canvas: dggui::PixelBufferAlpha& getPixelBuffer() override { return pixbuf; } private: InlinePixelBufferAlpha pixbuf; }; void DrumGizmoPlugin::onInlineRedraw(std::size_t width, std::size_t max_height, InlineDrawContext& context) { std::size_t bar_height = bar_red.height(); std::size_t image_height = ((double)width / inline_display_image.width()) * inline_display_image.height(); bool show_bar{false}; bool show_image{false}; std::size_t height = 0; if(bar_height <= max_height) { show_bar = true; height += bar_height; } if(bar_height + image_height <= max_height) { show_image = true; height += image_height; } // They have to be called seperately to avoid lazy evaluation bool nof_changed = settingsGetter.number_of_files.hasChanged(); bool nofl_changed = settingsGetter.number_of_files_loaded.hasChanged(); bool dls_changed = settingsGetter.drumkit_load_status.hasChanged(); bool in_progress = settingsGetter.number_of_files_loaded.getValue() < settingsGetter.number_of_files.getValue(); bool context_needs_update = !context.data || context.width != width || context.height != height; bool bar_needs_update = nof_changed || nofl_changed || dls_changed || context_needs_update | in_progress; // Always update while loading to prevent flickering. bool image_needs_update = inline_image_first_draw || context_needs_update; // TODO: settingsGetter.inline_image_filename.hasChanged(); bool something_needs_update = context_needs_update || bar_needs_update || image_needs_update; if (something_needs_update) { context.width = width; context.height = height; assert(context.width * context.height <= sizeof(inlineDisplayBuffer)); context.data = (unsigned char*)inlineDisplayBuffer; InlineCanvas canvas(context); dggui::Painter painter(canvas); double progress = (double)settingsGetter.number_of_files_loaded.getValue() / (double)settingsGetter.number_of_files.getValue(); if(show_bar && bar_needs_update) { box.setSize(context.width, bar_height); painter.drawImage(0, height - bar_height, box); int brd = 4; int val = (width - (2 * brd)) * progress; switch(settingsGetter.drumkit_load_status.getValue()) { case LoadStatus::Error: bar_red.setSize(val, bar_height); painter.drawImage(brd, height - bar_height, bar_red); break; case LoadStatus::Done: bar_green.setSize(val, bar_height); painter.drawImage(brd, height - bar_height, bar_green); break; case LoadStatus::Parsing: case LoadStatus::Loading: case LoadStatus::Idle: bar_blue.setSize(val, bar_height); painter.drawImage(brd, height - bar_height, bar_blue); break; } } if(show_image && image_needs_update) { // TODO: load new image and remove the bool inline_image_first_draw = false; painter.setColour(.5); painter.drawFilledRectangle(0, 0, width, image_height); painter.drawImageStretched(0, 0, inline_display_image, width, image_height); } // Convert to correct pixel format for(std::size_t i = 0; i < context.height * context.width; ++i) { std::uint32_t pixel = inlineDisplayBuffer[i]; unsigned char* p = (unsigned char*)&pixel; inlineDisplayBuffer[i] = pgzRGBA(p[0], p[1], p[2], p[3]); } } } bool DrumGizmoPlugin::hasGUI() { return true; } void* DrumGizmoPlugin::createWindow(void* parent) { plugin_gui = std::make_shared(settings, parent); resizeWindow(GUI::MainWindow::main_width, GUI::MainWindow::main_height); onShowWindow(); return plugin_gui->getNativeWindowHandle(); } void DrumGizmoPlugin::onDestroyWindow() { plugin_gui = nullptr; } void DrumGizmoPlugin::onShowWindow() { plugin_gui->show(); } void DrumGizmoPlugin::onHideWindow() { plugin_gui->hide(); } void DrumGizmoPlugin::onIdle() { plugin_gui->processEvents(); } void DrumGizmoPlugin::closeWindow() { } // // Input Engine // DrumGizmoPlugin::Input::Input(DrumGizmoPlugin& plugin) : plugin(plugin) { } bool DrumGizmoPlugin::Input::init(const Instruments &instruments) { this->instruments = &instruments; return true; } void DrumGizmoPlugin::Input::setParm(const std::string& parm, const std::string& value) { } bool DrumGizmoPlugin::Input::start() { return true; } void DrumGizmoPlugin::Input::stop() { } void DrumGizmoPlugin::Input::pre() { } void DrumGizmoPlugin::Input::run(size_t pos, size_t len, std::vector& events) { assert(events.empty()); assert(plugin.input_events); events.reserve(plugin.input_events->size()); for(auto& event : *plugin.input_events) { processNote((const std::uint8_t*)event.getData(), event.getSize(), event.getTime(), events); } } void DrumGizmoPlugin::Input::post() { } bool DrumGizmoPlugin::Input::isFreewheeling() const { return plugin.getFreeWheel(); } bool DrumGizmoPlugin::Input::loadMidiMap(const std::string& file, const Instruments& i) { bool result = AudioInputEngineMidi::loadMidiMap(file, i); std::vector> midnam; // Create MIDNAM mappings. // FIXME: Can CC entries somehow be added to the MIDNAM file? // For now, limiting this to note mappings. const auto& midimap = mmap.getMap(); std::map map; for(const auto& entry : midimap) { if(entry.from_kind == MapFrom::Note && entry.to_kind == MapTo::PlayInstrument) { // in case of multiple instruments mapped to one note, use '/' as separator if(!map[entry.from_id].empty()) { map[entry.from_id] += "/"; } map[entry.from_id] += entry.instrument_name; } } midnam.reserve(map.size()); for(const auto& m : map) { midnam.push_back(std::make_pair(m.first, m.second)); } if(midnam.size() > 0) { plugin.setMidnamData(midnam); } return result; } // // Output Engine // DrumGizmoPlugin::Output::Output(DrumGizmoPlugin& plugin) : plugin(plugin) { } bool DrumGizmoPlugin::Output::init(const Channels& channels) { return true; } void DrumGizmoPlugin::Output::setParm(const std::string& parm, const std::string& value) { } bool DrumGizmoPlugin::Output::start() { return true; } void DrumGizmoPlugin::Output::stop() { } void DrumGizmoPlugin::Output::pre(size_t nsamples) { // Clear all channels for(auto& channel : *plugin.output_samples) { if(channel) { std::memset(channel, 0, nsamples * sizeof(sample_t)); } } } void DrumGizmoPlugin::Output::run(int ch, sample_t* samples, size_t nsamples) { assert(plugin.output_samples); assert(sizeof(float) == sizeof(sample_t)); if((std::size_t)ch >= plugin.output_samples->size()) { return; } if((*plugin.output_samples)[ch] == nullptr) { // Port not connected. return; } // We are not running directly on the internal buffer: do a copy. if((*plugin.output_samples)[ch] != samples) { memcpy((*plugin.output_samples)[ch], samples, nsamples * sizeof(sample_t)); } } void DrumGizmoPlugin::Output::post(size_t nsamples) { } sample_t* DrumGizmoPlugin::Output::getBuffer(int ch) const { assert(plugin.output_samples); if((std::size_t)ch >= plugin.output_samples->size()) { return nullptr; } return (*plugin.output_samples)[ch]; } size_t DrumGizmoPlugin::Output::getBufferSize() const { return plugin.getFramesize(); } std::size_t DrumGizmoPlugin::Output::getSamplerate() const { return plugin.drumgizmo->samplerate(); } bool DrumGizmoPlugin::Output::isFreewheeling() const { return plugin.getFreeWheel(); } // // ConfigStringIO // // anonymous namespace for helper furnctions of ConfigStringIO namespace { std::string float2str(float a) { char buf[256]; snprintf_nol(buf, sizeof(buf) - 1, "%f", a); return buf; } std::string bool2str(bool a) { return a?"true":"false"; } std::string int2str(int a) { char buf[256]; snprintf(buf, sizeof(buf) - 1, "%d", a); return buf; } float str2float(std::string a) { if(a == "") { return 0.0; } return atof_nol(a.c_str()); } int str2int(std::string a) { try { return std::stoi(a); } catch(...) { return 0; } } long long str2ll(std::string a) { try { return std::stoll(a); } catch(...) { return 0; } } } // end anonymous namespace DrumGizmoPlugin::ConfigStringIO::ConfigStringIO(Settings& settings) : settings(settings) { } std::string DrumGizmoPlugin::ConfigStringIO::get() { return "\n" " " + settings.drumkit_file.load() + "\n" " " + settings.midimap_file.load() + "\n" " " + bool2str(settings.enable_velocity_modifier.load()) + "\n" " " + float2str(settings.velocity_modifier_falloff.load()) + "\n" " " + float2str(settings.velocity_modifier_weight.load()) + "\n" " " + float2str(settings.velocity_stddev.load()) + "\n" " " + float2str(settings.sample_selection_f_close.load()) + "\n" " " + float2str(settings.sample_selection_f_diverse.load()) + "\n" " " + float2str(settings.sample_selection_f_random.load()) + "\n" " " + bool2str(settings.enable_velocity_randomiser.load()) + "\n" " " + float2str(settings.velocity_randomiser_weight.load()) + "\n" " " + bool2str(settings.enable_resampling.load()) + "\n" " " + float2str(settings.resampling_quality.load()) + "\n" " " + int2str(settings.disk_cache_upper_limit.load()) + "\n" " " + int2str(settings.disk_cache_chunk_size.load()) + "\n" " " + bool2str(settings.disk_cache_enable.load()) + "\n" " " + bool2str(settings.enable_bleed_control.load()) + "\n" " " + float2str(settings.master_bleed.load()) + "\n" " " + bool2str(settings.enable_latency_modifier.load()) + "\n" // Do not store/reload this value //" " + //int2str(settings.latency_max.load()) + "\n" " " + float2str(settings.latency_laid_back_ms.load()) + "\n" " " + float2str(settings.latency_stddev.load()) + "\n" " " + float2str(settings.latency_regain.load()) + "\n" " " + bool2str(settings.enable_powermap.load()) + "\n" " " + float2str(settings.powermap_fixed0_x.load()) + "\n" " " + float2str(settings.powermap_fixed0_y.load()) + "\n" " " + float2str(settings.powermap_fixed1_x.load()) + "\n" " " + float2str(settings.powermap_fixed1_y.load()) + "\n" " " + float2str(settings.powermap_fixed2_x.load()) + "\n" " " + float2str(settings.powermap_fixed2_y.load()) + "\n" " " + bool2str(settings.powermap_shelf.load()) + "\n" " " + bool2str(settings.enable_voice_limit.load()) + "\n" " " + int2str(settings.voice_limit_max.load()) + "\n" " " + float2str(settings.voice_limit_rampdown.load()) + "\n" ""; } bool DrumGizmoPlugin::ConfigStringIO::set(std::string config_string) { DEBUG(config, "Load config: %s\n", config_string.c_str()); ConfigParser p; if(!p.parseString(config_string)) { ERR(config, "Config parse error.\n"); return false; } if(p.value("enable_velocity_modifier") != "") { settings.enable_velocity_modifier.store(p.value("enable_velocity_modifier") == "true"); } if(p.value("velocity_modifier_falloff") != "") { settings.velocity_modifier_falloff.store(str2float(p.value("velocity_modifier_falloff"))); } if(p.value("velocity_modifier_weight") != "") { settings.velocity_modifier_weight.store(str2float(p.value("velocity_modifier_weight"))); } if(p.value("velocity_stddev") != "") { settings.velocity_stddev.store(str2float(p.value("velocity_stddev"))); } if(p.value("sample_selection_f_close") != "") { settings.sample_selection_f_close.store(str2float(p.value("sample_selection_f_close"))); } if(p.value("sample_selection_f_diverse") != "") { settings.sample_selection_f_diverse.store(str2float(p.value("sample_selection_f_diverse"))); } if(p.value("sample_selection_f_random") != "") { settings.sample_selection_f_random.store(str2float(p.value("sample_selection_f_random"))); } if(p.value("enable_velocity_randomiser") != "") { settings.enable_velocity_randomiser.store(p.value("enable_velocity_randomiser") == "true"); } if(p.value("velocity_randomiser_weight") != "") { settings.velocity_randomiser_weight.store(str2float(p.value("velocity_randomiser_weight"))); } if(p.value("enable_resampling") != "") { settings.enable_resampling.store(p.value("enable_resampling") == "true"); } if(p.value("resampling_quality") != "") { settings.resampling_quality.store(str2float(p.value("resampling_quality"))); } if(p.value("disk_cache_upper_limit") != "") { settings.disk_cache_upper_limit.store(str2ll(p.value("disk_cache_upper_limit"))); } if(p.value("disk_cache_chunk_size") != "") { settings.disk_cache_chunk_size.store(str2int(p.value("disk_cache_chunk_size"))); } if(p.value("disk_cache_enable") != "") { settings.disk_cache_enable.store(p.value("disk_cache_enable") == "true"); } if(p.value("enable_bleed_control") != "") { settings.enable_bleed_control.store(p.value("enable_bleed_control") == "true"); } if(p.value("master_bleed") != "") { settings.master_bleed.store(str2float(p.value("master_bleed"))); } if(p.value("enable_latency_modifier") != "") { settings.enable_latency_modifier.store(p.value("enable_latency_modifier") == "true"); } // Do not store/reload this value //if(p.value("latency_max") != "") //{ // settings.latency_max.store(str2int(p.value("latency_max"))); //} if(p.value("latency_laid_back_ms") != "") { settings.latency_laid_back_ms.store(str2float(p.value("latency_laid_back_ms"))); } if(p.value("latency_stddev") != "") { settings.latency_stddev.store(str2float(p.value("latency_stddev"))); } if(p.value("latency_regain") != "") { settings.latency_regain.store(str2float(p.value("latency_regain"))); } if(p.value("enable_powermap") != "") { settings.enable_powermap.store(p.value("enable_powermap") == "true"); } if(p.value("powermap_fixed0_x") != "") { settings.powermap_fixed0_x.store(str2float(p.value("powermap_fixed0_x"))); } if(p.value("powermap_fixed0_y") != "") { settings.powermap_fixed0_y.store(str2float(p.value("powermap_fixed0_y"))); } if(p.value("powermap_fixed1_x") != "") { settings.powermap_fixed1_x.store(str2float(p.value("powermap_fixed1_x"))); } if(p.value("powermap_fixed1_y") != "") { settings.powermap_fixed1_y.store(str2float(p.value("powermap_fixed1_y"))); } if(p.value("powermap_fixed2_x") != "") { settings.powermap_fixed2_x.store(str2float(p.value("powermap_fixed2_x"))); } if(p.value("powermap_fixed2_y") != "") { settings.powermap_fixed2_y.store(str2float(p.value("powermap_fixed2_y"))); } if(p.value("powermap_shelf") != "") { settings.powermap_shelf.store(p.value("powermap_shelf") == "true"); } if(p.value("enable_voice_limit") != "") { settings.enable_voice_limit.store(p.value("enable_voice_limit") == "true"); } if(p.value("voice_limit_max") != "") { settings.voice_limit_max.store(str2int(p.value("voice_limit_max"))); } if(p.value("voice_limit_rampdown") != "") { settings.voice_limit_rampdown.store(str2float(p.value("voice_limit_rampdown"))); } std::string newkit = p.value("drumkitfile"); if(newkit != "") { settings.drumkit_file.store(newkit); } std::string newmidimap = p.value("midimapfile"); if(newmidimap != "") { settings.midimap_file.store(newmidimap); } return true; }