/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /*************************************************************************** * pluginlv2.cc * * Sun Feb 7 15:15:24 CET 2016 * Copyright 2016 Bent Bisballe Nyeng * deva@aasimon.org ****************************************************************************/ /* * This file is part of PluginGizmo. * * PluginGizmo 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. * * PluginGizmo 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 PluginGizmo; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #include "pluginlv2.h" #include #include #include "midievent.h" #define LV2_PLUGIN_URI__atom LV2_PLUGIN_URI "/atom#" #define LV2_PLUGIN_URI__instance LV2_PLUGIN_URI "#plugin-instance" #define LV2_PLUGIN_URI__ui LV2_PLUGIN_URI "#ui" #include "lv2/lv2plug.in/ns/ext/atom/util.h" #include #include void PluginLV2::init() { for(auto& m : midnamData) { m.first = -1; // Mark midnam slot as unsued. m.second.reserve(64); // Reserve 64 characters for the midnam name. } } bool PluginLV2::getFreeWheel() const { return free_wheel; } float PluginLV2::getSamplerate() { return sample_rate; } std::size_t PluginLV2::getFramesize() { return frame_size; } bool PluginLV2::getActive() { return active; } float PluginLV2::getLatency() { if(latency_port) { return *latency_port; } return 0.0f; } void PluginLV2::setLatency(float latency) { if(latency_port) { *latency_port = latency; } } LV2_Handle PluginLV2::instantiate(const struct _LV2_Descriptor* descriptor, double sample_rate, const char* bundle_path, const LV2_Feature *const *features) { PluginLV2* plugin_lv2 = createEffectInstance(); plugin_lv2->sample_rate = sample_rate; plugin_lv2->input_event_ports.resize(plugin_lv2->getNumberOfMidiInputs(), nullptr); plugin_lv2->output_event_ports.resize(plugin_lv2->getNumberOfMidiOutputs(), nullptr); plugin_lv2->input_audio_ports.resize(plugin_lv2->getNumberOfAudioInputs()); plugin_lv2->output_audio_ports.resize(plugin_lv2->getNumberOfAudioOutputs()); for(auto& port : plugin_lv2->output_audio_ports) { port = nullptr; } for(auto& port : plugin_lv2->input_audio_ports) { port = nullptr; } while(*features != nullptr) { std::string uri = (*features)->URI; void* data = (*features)->data; if(uri == LV2_URID__map) { plugin_lv2->map = (LV2_URID_Map*)data; } #ifdef DISPLAY_INTERFACE if(uri == LV2_INLINEDISPLAY__queue_draw) { plugin_lv2->queue_draw = (LV2_Inline_Display*)data; } #endif #ifdef MIDNAM_INTERFACE if(uri == LV2_MIDNAM__update) { plugin_lv2->midnam = (LV2_Midnam*)data; } #endif ++features; } // Only reported on creation. plugin_lv2->onSamplerateChange(sample_rate); return (LV2_Handle)plugin_lv2; } void PluginLV2::connectPort(LV2_Handle instance, uint32_t port, void *data_location) { PluginLV2* plugin_lv2 = (PluginLV2*)instance; if(port == static_cast(LV2Ports::FreeWheel)) { plugin_lv2->free_wheel_port = (float*)data_location; if(plugin_lv2->free_wheel_port) { plugin_lv2->free_wheel = (*plugin_lv2->free_wheel_port != 0.0f); // Signal first time. plugin_lv2->onFreeWheelChange(plugin_lv2->free_wheel); } } if(port == static_cast(LV2Ports::Latency)) { plugin_lv2->latency_port = (float*)data_location; } uint32_t port_offset = static_cast(LV2Ports::PortOffset); if((port >= port_offset) && (port < (port_offset + plugin_lv2->getNumberOfMidiInputs()))) { int port_index = port - port_offset; plugin_lv2->input_event_ports[port_index] = (LV2_Atom_Sequence*)data_location; } port_offset += plugin_lv2->getNumberOfMidiInputs(); if((port >= port_offset) && (port < (port_offset + plugin_lv2->getNumberOfMidiOutputs()))) { int port_index = port - port_offset; plugin_lv2->output_event_ports[port_index] = (LV2_Atom_Sequence*)data_location; } port_offset += plugin_lv2->getNumberOfMidiOutputs(); if((port >= port_offset) && (port < (port_offset + plugin_lv2->getNumberOfAudioInputs()))) { int port_index = port - port_offset; plugin_lv2->input_audio_ports[port_index] = (float*)data_location; } port_offset += plugin_lv2->getNumberOfAudioInputs(); if((port >= port_offset) && (port < (port_offset + plugin_lv2->getNumberOfAudioOutputs()))) { int port_index = port - port_offset; plugin_lv2->output_audio_ports[port_index] = (float*)data_location; } } class Sequence { public: Sequence(LV2_URID_Map& map, void* buffer, std::size_t buffer_size); void clear(); void addMidiEvent(std::size_t pos, const char* data, std::size_t size); void* data(); private: void *buffer; std::size_t buffer_size; LV2_Atom_Sequence *seq; LV2_URID_Map& map; }; Sequence::Sequence(LV2_URID_Map& map, void* buffer, std::size_t buffer_size) : map(map) { this->buffer = buffer; this->buffer_size = buffer_size; seq = (LV2_Atom_Sequence*)buffer; seq->atom.size = sizeof(LV2_Atom_Sequence_Body); seq->atom.type = map.map(map.handle, LV2_ATOM__Sequence); seq->body.unit = 0; seq->body.pad = 0; } // Keep this to support atom extension from lv2 < 1.10 static inline void _lv2_atom_sequence_clear(LV2_Atom_Sequence* seq) { seq->atom.size = sizeof(LV2_Atom_Sequence_Body); } void Sequence::clear() { _lv2_atom_sequence_clear(seq); } // Keep this to support atom extension from lv2 < 1.10 static inline LV2_Atom_Event* _lv2_atom_sequence_append_event(LV2_Atom_Sequence* seq, uint32_t capacity, const LV2_Atom_Event* event) { const uint32_t total_size = (uint32_t)sizeof(*event) + event->body.size; if(capacity - seq->atom.size < total_size) { return nullptr; } LV2_Atom_Event* e = lv2_atom_sequence_end(&seq->body, seq->atom.size); memcpy(e, event, total_size); seq->atom.size += lv2_atom_pad_size(total_size); return e; } void Sequence::addMidiEvent(std::size_t pos, const char* data, std::size_t size) { struct MIDINoteEvent { LV2_Atom_Event event; uint8_t msg[6]; }; MIDINoteEvent ev; ev.event.time.frames = pos; ev.event.body.type = map.map(map.handle, LV2_MIDI__MidiEvent); ev.event.body.size = size; assert(size <= sizeof(ev.msg)); // Assert that we have room for the message memcpy(ev.msg, data, size); _lv2_atom_sequence_append_event(seq, this->buffer_size, &ev.event); } void* Sequence::data() { return buffer; } void PluginLV2::run(LV2_Handle instance, uint32_t sample_count) { PluginLV2* plugin_lv2 = (PluginLV2*)instance; // Handle free-wheel state if(plugin_lv2->free_wheel_port != nullptr) { bool new_free_wheel = *plugin_lv2->free_wheel_port != 0.0f; if(new_free_wheel != plugin_lv2->free_wheel) { plugin_lv2->free_wheel = new_free_wheel; plugin_lv2->onFreeWheelChange(plugin_lv2->free_wheel); } } // Handle frame size if(plugin_lv2->frame_size != sample_count) { plugin_lv2->frame_size = sample_count; plugin_lv2->onFramesizeChange(plugin_lv2->frame_size); } // Convert input lv2 events to input events. std::vector input_events; for(std::size_t port = 0; port < plugin_lv2->getNumberOfMidiInputs(); ++port) { if(plugin_lv2->input_event_ports[port] == nullptr) { continue; // Not yet connected. } auto& event_port = plugin_lv2->input_event_ports[port]; for(LV2_Atom_Event* ev = lv2_atom_sequence_begin(&event_port->body); !lv2_atom_sequence_is_end(&event_port->body, event_port->atom.size, ev); ev = lv2_atom_sequence_next(ev)) { if(ev->body.type != plugin_lv2->map->map(plugin_lv2->map->handle, LV2_MIDI__MidiEvent)) { continue; // not a midi event. } const char* data = (char*)(ev + 1); input_events.emplace_back(ev->time.frames, data, ev->body.size); } } std::vector output_events; // Process events and audio plugin_lv2->process(plugin_lv2->pos, input_events, output_events, plugin_lv2->input_audio_ports, plugin_lv2->output_audio_ports, sample_count); // Convert output events to lv2 events if(plugin_lv2->getNumberOfMidiOutputs() > 0) { if(plugin_lv2->map != nullptr) { if(plugin_lv2->output_event_ports[0] != nullptr) // Not yet connected? { auto& event_port = plugin_lv2->output_event_ports[0]; // TODO: Split? Sequence seq(*plugin_lv2->map, &event_port->body + 1, event_port->atom.size); for(auto midi_event : output_events) { seq.addMidiEvent(midi_event.getTime(), midi_event.getData(), midi_event.getSize()); } } } } plugin_lv2->pos += sample_count; #ifdef MIDNAM_INTERFACE if(plugin_lv2->midnam && plugin_lv2->midnam_changed.load()) { plugin_lv2->midnam->update(plugin_lv2->midnam->handle); plugin_lv2->midnam_changed.store(false); } #endif #ifdef DISPLAY_INTERFACE if(plugin_lv2->queue_draw) { plugin_lv2->queue_draw->queue_draw(plugin_lv2->queue_draw->handle); } #endif } void PluginLV2::activate(LV2_Handle instance) { PluginLV2* plugin_lv2 = (PluginLV2*)instance; plugin_lv2->active = true; plugin_lv2->onActiveChange(plugin_lv2->active); } void PluginLV2::deactivate(LV2_Handle instance) { PluginLV2* plugin_lv2 = (PluginLV2*)instance; plugin_lv2->active = false; plugin_lv2->onActiveChange(plugin_lv2->active); } void PluginLV2::cleanup(LV2_Handle instance) { PluginLV2* plugin_lv2 = (PluginLV2*)instance; delete plugin_lv2; } // // State handling // LV2_State_Status PluginLV2::save(LV2_Handle instance, LV2_State_Store_Function store, LV2_State_Handle handle, uint32_t flags, const LV2_Feature *const * features) { PluginLV2* plugin_lv2 = (PluginLV2*)instance; if(plugin_lv2->map == nullptr) { // Missing urid feature? return LV2_STATE_ERR_NO_FEATURE; } std::string config = plugin_lv2->onStateSave(); store(handle, plugin_lv2->map->map(plugin_lv2->map->handle, LV2_PLUGIN_URI__atom "config"), config.data(), config.length(), plugin_lv2->map->map(plugin_lv2->map->handle, LV2_ATOM__Chunk), LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); return LV2_STATE_SUCCESS; } LV2_State_Status PluginLV2::restore(LV2_Handle instance, LV2_State_Retrieve_Function retrieve, LV2_State_Handle handle, uint32_t flags, const LV2_Feature *const * features) { PluginLV2* plugin_lv2 = (PluginLV2*)instance; if(plugin_lv2->map == nullptr) { // Missing urid feature? return LV2_STATE_ERR_NO_FEATURE; } std::size_t size; uint32_t type; const char* data = (const char*)retrieve(handle, plugin_lv2->map->map(plugin_lv2->map->handle, LV2_PLUGIN_URI__atom "config"), &size, &type, &flags); if(data && size) { std::string config; config.append(data, size); plugin_lv2->onStateRestore(config); } return LV2_STATE_SUCCESS; } static LV2_State_Interface persist = { PluginLV2::save, PluginLV2::restore }; LV2_Inline_Display_Image_Surface* PluginLV2::inlineRender(LV2_Handle instance, uint32_t w, uint32_t max_h) { PluginLV2* plugin_lv2 = (PluginLV2*)instance; plugin_lv2->onInlineRedraw(w, max_h, plugin_lv2->drawContext); plugin_lv2->surf.width = plugin_lv2->drawContext.width; plugin_lv2->surf.height = plugin_lv2->drawContext.height; plugin_lv2->surf.stride = plugin_lv2->surf.width * 4; // stride is in bytes plugin_lv2->surf.data = plugin_lv2->drawContext.data; return &plugin_lv2->surf; } void PluginLV2::setMidnamData(const std::vector>& midnam) { auto idx = 0u; for(const auto& m : midnam) { midnamData[idx].first = m.first; // Duplicate name making sure no reallocation is being performed. midnamData[idx].second = m.second.substr(0, midnamData[idx].second.capacity() - 1); idx++; } for(;idx < this->midnamData.size(); ++idx) { midnamData[idx].first = -1; // Mark as unused } midnam_changed.store(true); } #ifdef MIDNAM_INTERFACE char* PluginLV2::MidnamFile (LV2_Handle instance) { PluginLV2* plugin_lv2 = (PluginLV2*)instance; std::stringstream ss; ss << "\n" "\n" "\n" " \n" " \n" " " << plugin_lv2->getVendorString() << "\n" " " << plugin_lv2->getProductString() << ":" << ((const void *) instance) << "\n"; //<< needs to match MidnamModel() ss << " \n"; ss << " \n"; for (int c = 0; c < 16; ++c) { ss << " \n"; } ss << " \n"; ss << " \n"; ss << " \n" " \n"; for (int c = 0; c < 16; ++c) { ss << " \n"; } ss << " \n" " \n" " \n" " \n" " \n" " \n" " \n"; // TODO: Fill in empty slots for the notes that are not in the map // Does Ardour preserve the note names for the unspecified notes? // ... and is this a bug?? for(const auto& m : plugin_lv2->midnamData) { if(m.first == -1) { continue; } ss << " \n"; } ss << " \n" " \n" ""; return strdup (ss.str().c_str()); } char* PluginLV2::MidnamModel(LV2_Handle instance) { PluginLV2* plugin_lv2 = (PluginLV2*)instance; char* rv = (char*) malloc (64 * sizeof (char)); snprintf(rv, 64, "%s:%p", plugin_lv2->getProductString().data(), (void*) instance); rv[63] = 0; return rv; } void PluginLV2::MidnamFree (char* v) { free (v); } #endif const void* PluginLV2::extensionData(const char *uri) { if(!strcmp(uri, LV2_STATE__interface)) { return &persist; } #ifdef DISPLAY_INTERFACE static const LV2_Inline_Display_Interface display = { inlineRender }; if(!strcmp(uri, LV2_INLINEDISPLAY__interface)) { return &display; } #endif #ifdef MIDNAM_INTERFACE static const LV2_Midnam_Interface midnam = { MidnamFile, MidnamModel, MidnamFree }; if (!strcmp (uri, LV2_MIDNAM__interface)) { return &midnam; } #endif return nullptr; } // // GUI // LV2UI_Handle PluginLV2::uiInstantiate(const struct _LV2UI_Descriptor*descriptor, const char * plugin_uri, const char * bundle_path, LV2UI_Write_Function write_function, LV2UI_Controller controller, LV2UI_Widget* widget, const LV2_Feature * const * features) { LV2_Handle instance = nullptr; void* parent = nullptr; LV2UI_Resize* resize = nullptr; while(*features != nullptr) { std::string uri = (*features)->URI; void *data = (*features)->data; if(uri == LV2_INSTANCE_ACCESS_URI) { instance = (LV2_Handle)data; } if(uri == LV2_UI__parent) { parent = data; } if(uri == LV2_UI__resize) { resize = (LV2UI_Resize*)data; } features++; } if(instance == nullptr) { return nullptr; } PluginLV2* plugin_lv2 = (PluginLV2*)instance; // Do we have a GUI? if(plugin_lv2->hasGUI() == false) { return nullptr; } plugin_lv2->resize = resize; *widget = plugin_lv2->createWindow(parent); return plugin_lv2; } void PluginLV2::resizeWindow(std::size_t width, std::size_t height) { if(resize) { resize->ui_resize(resize->handle, width, height); } } void PluginLV2::closeWindow() { } static const LV2_Descriptor descriptor = { LV2_PLUGIN_URI, PluginLV2::instantiate, PluginLV2::connectPort, PluginLV2::activate, PluginLV2::run, PluginLV2::deactivate, PluginLV2::cleanup, PluginLV2::extensionData }; void PluginLV2::uiCleanup(LV2UI_Handle handle) { PluginLV2* plugin_lv2 = (PluginLV2*)handle; plugin_lv2->onDestroyWindow () ; } int PluginLV2::uiIdle(LV2UI_Handle handle) { PluginLV2* plugin_lv2 = (PluginLV2*)handle; plugin_lv2->onIdle(); return 0; } static const LV2UI_Idle_Interface idle_iface = { PluginLV2::uiIdle }; const void* PluginLV2::uiExtensionData(const char* uri) { if(!strcmp(uri, LV2_UI__idleInterface)) { return &idle_iface; } return NULL; } static LV2UI_Descriptor ui_descriptor = { LV2_PLUGIN_URI__ui, PluginLV2::uiInstantiate, PluginLV2::uiCleanup, nullptr,//PluginLV2::ui_port_event, PluginLV2::uiExtensionData }; #ifdef __cplusplus extern "C" { #endif LV2_SYMBOL_EXPORT const LV2_Descriptor* lv2_descriptor(uint32_t index) { switch (index) { case 0: return &descriptor; default: return nullptr; } } LV2_SYMBOL_EXPORT const LV2UI_Descriptor *lv2ui_descriptor(uint32_t index) { switch(index) { case 0: return &ui_descriptor; default: return nullptr; } } // // DynManifest experiments failed, but the code is kept here for nostalgic // reasons :-) // // //int lv2_dyn_manifest_open(LV2_Dyn_Manifest_Handle* handle, // const LV2_Feature* const* features) //{ // *handle = createEffectInstance(); // return 0; //} // //int lv2_dyn_manifest_get_subjects(LV2_Dyn_Manifest_Handle handle, FILE* fp) //{ //// PluginLV2* plugin_lv2 = (PluginLV2*)handle; // // fprintf(fp, "@prefix lv2: .\n"); // fprintf(fp, "<" LV2_PLUGIN_URI "> a lv2:Plugin .\n"); // return 0; //} // //int lv2_dyn_manifest_get_data(LV2_Dyn_Manifest_Handle handle, FILE* fp, // const char* uri) //{ // //PluginLV2* plugin_lv2 = (PluginLV2*)handle; // printf("%s '%s'\n", __PRETTY_FUNCTION__, uri); // return 0; //} // //void lv2_dyn_manifest_close(LV2_Dyn_Manifest_Handle handle) //{ // PluginLV2* plugin_lv2 = (PluginLV2*)handle; // delete plugin_lv2; //} #ifdef __cplusplus } #endif