diff options
Diffstat (limited to 'src/server/ingen_lv2.cpp')
-rw-r--r-- | src/server/ingen_lv2.cpp | 848 |
1 files changed, 848 insertions, 0 deletions
diff --git a/src/server/ingen_lv2.cpp b/src/server/ingen_lv2.cpp new file mode 100644 index 00000000..04cc5a33 --- /dev/null +++ b/src/server/ingen_lv2.cpp @@ -0,0 +1,848 @@ +/* + This file is part of Ingen. + Copyright 2007-2016 David Robillard <http://drobilla.net/> + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or any later version. + + Ingen 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 Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <cstdlib> +#include <string> +#include <thread> +#include <vector> + +#include "lv2/atom/util.h" +#include "lv2/buf-size/buf-size.h" +#include "lv2/log/log.h" +#include "lv2/log/logger.h" +#include "lv2/options/options.h" +#include "lv2/state/state.h" +#include "lv2/urid/urid.h" +#include "lv2/core/lv2.h" + +#include "ingen/AtomReader.hpp" +#include "ingen/AtomWriter.hpp" +#include "ingen/Configuration.hpp" +#include "ingen/Interface.hpp" +#include "ingen/Log.hpp" +#include "ingen/Parser.hpp" +#include "ingen/Serialiser.hpp" +#include "ingen/Store.hpp" +#include "ingen/URI.hpp" +#include "ingen/World.hpp" +#include "ingen/ingen.h" +#include "ingen/runtime_paths.hpp" +#include "ingen/types.hpp" +#include "raul/Semaphore.hpp" + +#include "Buffer.hpp" +#include "Driver.hpp" +#include "Engine.hpp" +#include "EnginePort.hpp" +#include "EventWriter.hpp" +#include "GraphImpl.hpp" +#include "PostProcessor.hpp" +#include "RunContext.hpp" +#include "ThreadManager.hpp" + +#define NS_RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#" +#define NS_RDFS "http://www.w3.org/2000/01/rdf-schema#" + +namespace ingen { + +/** Record of a graph in this bundle. */ +struct LV2Graph : public Parser::ResourceRecord { + LV2Graph(Parser::ResourceRecord record); + + LV2_Descriptor descriptor; +}; + +/** Ingen LV2 library. */ +class Lib { +public: + explicit Lib(const char* bundle_path); + + typedef std::vector< SPtr<const LV2Graph> > Graphs; + + Graphs graphs; +}; + +namespace server { + +class LV2Driver; + +void signal_main(RunContext& context, LV2Driver* driver); + +inline size_t +ui_ring_size(SampleCount block_length) +{ + return std::max((size_t)8192, (size_t)block_length * 16); +} + +class LV2Driver : public ingen::server::Driver + , public ingen::AtomSink +{ +public: + LV2Driver(Engine& engine, + SampleCount block_length, + size_t seq_size, + SampleCount sample_rate) + : _engine(engine) + , _main_sem(0) + , _reader(engine.world()->uri_map(), + engine.world()->uris(), + engine.world()->log(), + *engine.world()->interface().get()) + , _writer(engine.world()->uri_map(), + engine.world()->uris(), + *this) + , _from_ui(ui_ring_size(block_length)) + , _to_ui(ui_ring_size(block_length)) + , _root_graph(nullptr) + , _notify_capacity(0) + , _block_length(block_length) + , _seq_size(seq_size) + , _sample_rate(sample_rate) + , _frame_time(0) + , _to_ui_overflow_sem(0) + , _to_ui_overflow(false) + , _instantiated(false) + {} + + bool dynamic_ports() const override { return !_instantiated; } + + void pre_process_port(RunContext& context, EnginePort* port) { + const URIs& uris = _engine.world()->uris(); + const SampleCount nframes = context.nframes(); + DuplexPort* graph_port = port->graph_port(); + Buffer* graph_buf = graph_port->buffer(0).get(); + void* lv2_buf = port->buffer(); + + if (graph_port->is_a(PortType::AUDIO) || graph_port->is_a(PortType::CV)) { + graph_port->set_driver_buffer(lv2_buf, nframes * sizeof(float)); + } else if (graph_port->buffer_type() == uris.atom_Sequence) { + graph_port->set_driver_buffer(lv2_buf, lv2_atom_total_size((LV2_Atom*)lv2_buf)); + if (graph_port->symbol() == "control") { // TODO: Safe to use index? + LV2_Atom_Sequence* seq = (LV2_Atom_Sequence*)lv2_buf; + bool enqueued = false; + LV2_ATOM_SEQUENCE_FOREACH(seq, ev) { + if (AtomReader::is_message(uris, &ev->body)) { + enqueued = enqueue_message(&ev->body) || enqueued; + } + } + + if (enqueued) { + // Enqueued a message for processing, raise semaphore + _main_sem.post(); + } + } + } + + if (graph_port->is_input()) { + graph_port->monitor(context); + } else { + graph_buf->prepare_write(context); + } + } + + void post_process_port(RunContext& context, EnginePort* port) { + DuplexPort* graph_port = port->graph_port(); + + // No copying necessary, host buffers are used directly + // Reset graph port buffer pointer to no longer point to the Jack buffer + if (graph_port->is_driver_port()) { + graph_port->set_driver_buffer(nullptr, 0); + } + } + + void run(uint32_t nframes) { + _engine.locate(_frame_time, nframes); + + // Notify buffer is a Chunk with size set to the available space + _notify_capacity = ((LV2_Atom_Sequence*)_ports[1]->buffer())->atom.size; + + for (auto& p : _ports) { + pre_process_port(_engine.run_context(), p); + } + + _engine.run(nframes); + if (_engine.post_processor()->pending()) { + _main_sem.post(); + } + + flush_to_ui(_engine.run_context()); + + for (auto& p : _ports) { + post_process_port(_engine.run_context(), p); + } + + _frame_time += nframes; + } + + void deactivate() override { + _engine.quit(); + _main_sem.post(); + } + + virtual void set_root_graph(GraphImpl* graph) { _root_graph = graph; } + virtual GraphImpl* root_graph() { return _root_graph; } + + EnginePort* get_port(const Raul::Path& path) override { + for (auto& p : _ports) { + if (p->graph_port()->path() == path) { + return p; + } + } + + return nullptr; + } + + /** Add a port. Called only during init or restore. */ + void add_port(RunContext& context, EnginePort* port) override { + const uint32_t index = port->graph_port()->index(); + if (_ports.size() <= index) { + _ports.resize(index + 1); + } + _ports[index] = port; + } + + /** Remove a port. Called only during init or restore. */ + void remove_port(RunContext& context, EnginePort* port) override { + const uint32_t index = port->graph_port()->index(); + _ports[index] = nullptr; + } + + /** Unused since LV2 has no dynamic ports. */ + void register_port(EnginePort& port) override {} + + /** Unused since LV2 has no dynamic ports. */ + void unregister_port(EnginePort& port) override {} + + /** Unused since LV2 has no dynamic ports. */ + void rename_port(const Raul::Path& old_path, + const Raul::Path& new_path) override {} + + /** Unused since LV2 has no dynamic ports. */ + void port_property(const Raul::Path& path, + const URI& uri, + const Atom& value) override {} + + EnginePort* create_port(DuplexPort* graph_port) override { + graph_port->set_is_driver_port(*_engine.buffer_factory()); + return new EnginePort(graph_port); + } + + void append_time_events(RunContext& context, Buffer& buffer) override { + const URIs& uris = _engine.world()->uris(); + LV2_Atom_Sequence* seq = (LV2_Atom_Sequence*)_ports[0]->buffer(); + LV2_ATOM_SEQUENCE_FOREACH(seq, ev) { + if (ev->body.type == uris.atom_Object) { + const LV2_Atom_Object* obj = (LV2_Atom_Object*)&ev->body; + if (obj->body.otype == uris.time_Position) { + buffer.append_event(ev->time.frames, + ev->body.size, + ev->body.type, + (const uint8_t*)(&ev->body + 1)); + } + } + } + } + + int real_time_priority() override { return 60; } + + /** Called in run thread for events received at control input port. */ + bool enqueue_message(const LV2_Atom* atom) { + if (_from_ui.write(lv2_atom_total_size(atom), atom) == 0) { +#ifndef NDEBUG + _engine.log().error("Control input buffer overflow\n"); +#endif + return false; + } + return true; + } + + Raul::Semaphore& main_sem() { return _main_sem; } + + /** AtomSink::write implementation called by the PostProcessor in the main + * thread to write responses to the UI. + */ + bool write(const LV2_Atom* atom, int32_t default_id) override { + // Called from post-processor in main thread + while (_to_ui.write(lv2_atom_total_size(atom), atom) == 0) { + // Overflow, wait until ring is drained next cycle + _to_ui_overflow = true; + _to_ui_overflow_sem.wait(); + _to_ui_overflow = false; + } + return true; + } + + void consume_from_ui() { + const uint32_t read_space = _from_ui.read_space(); + void* buf = nullptr; + for (uint32_t read = 0; read < read_space;) { + LV2_Atom atom; + if (!_from_ui.read(sizeof(LV2_Atom), &atom)) { + _engine.log().rt_error("Error reading head from from-UI ring\n"); + break; + } + + buf = realloc(buf, sizeof(LV2_Atom) + atom.size); + memcpy(buf, &atom, sizeof(LV2_Atom)); + + if (!_from_ui.read(atom.size, (char*)buf + sizeof(LV2_Atom))) { + _engine.log().rt_error("Error reading body from from-UI ring\n"); + break; + } + + _reader.write((LV2_Atom*)buf); + read += sizeof(LV2_Atom) + atom.size; + } + free(buf); + } + + void flush_to_ui(RunContext& context) { + if (_ports.size() < 2) { + _engine.log().rt_error("Standard control ports are not present\n"); + return; + } + + LV2_Atom_Sequence* seq = (LV2_Atom_Sequence*)_ports[1]->buffer(); + if (!seq) { + _engine.log().rt_error("Notify output not connected\n"); + return; + } + + // Initialise output port buffer to an empty Sequence + seq->atom.type = _engine.world()->uris().atom_Sequence; + seq->atom.size = sizeof(LV2_Atom_Sequence_Body); + + const uint32_t read_space = _to_ui.read_space(); + for (uint32_t read = 0; read < read_space;) { + LV2_Atom atom; + if (!_to_ui.peek(sizeof(LV2_Atom), &atom)) { + _engine.log().rt_error("Error reading head from to-UI ring\n"); + break; + } + + if (seq->atom.size + lv2_atom_pad_size( + sizeof(LV2_Atom_Event) + atom.size) + > _notify_capacity) { + break; // Output port buffer full, resume next time + } + + LV2_Atom_Event* ev = (LV2_Atom_Event*)( + (uint8_t*)seq + lv2_atom_total_size(&seq->atom)); + + ev->time.frames = 0; // TODO: Time? + ev->body = atom; + + _to_ui.skip(sizeof(LV2_Atom)); + if (!_to_ui.read(ev->body.size, LV2_ATOM_BODY(&ev->body))) { + _engine.log().rt_error("Error reading body from to-UI ring\n"); + break; + } + + read += lv2_atom_total_size(&ev->body); + seq->atom.size += lv2_atom_pad_size( + sizeof(LV2_Atom_Event) + ev->body.size); + } + + if (_to_ui_overflow) { + _to_ui_overflow_sem.post(); + } + } + + SampleCount block_length() const override { return _block_length; } + size_t seq_size() const override { return _seq_size; } + SampleCount sample_rate() const override { return _sample_rate; } + SampleCount frame_time() const override { return _frame_time; } + + AtomReader& reader() { return _reader; } + AtomWriter& writer() { return _writer; } + + typedef std::vector<EnginePort*> Ports; + + Ports& ports() { return _ports; } + + void set_instantiated(bool instantiated) { _instantiated = instantiated; } + +private: + Engine& _engine; + Ports _ports; + Raul::Semaphore _main_sem; + AtomReader _reader; + AtomWriter _writer; + Raul::RingBuffer _from_ui; + Raul::RingBuffer _to_ui; + GraphImpl* _root_graph; + uint32_t _notify_capacity; + SampleCount _block_length; + size_t _seq_size; + SampleCount _sample_rate; + SampleCount _frame_time; + Raul::Semaphore _to_ui_overflow_sem; + bool _to_ui_overflow; + bool _instantiated; +}; + +} // namespace server +} // namespace ingen + +extern "C" { + +using namespace ingen; +using namespace ingen::server; + +static void +ingen_lv2_main(SPtr<Engine> engine, const SPtr<LV2Driver>& driver) +{ + while (true) { + // Wait until there is work to be done + driver->main_sem().wait(); + + // Convert pending messages to events and push to pre processor + driver->consume_from_ui(); + + // Run post processor and maid to finalise events from last time + if (!engine->main_iteration()) { + return; + } + } +} + +struct IngenPlugin { + IngenPlugin() + : world(nullptr) + , main(nullptr) + , map(nullptr) + , argc(0) + , argv(nullptr) + {} + + ingen::World* world; + SPtr<Engine> engine; + std::thread* main; + LV2_URID_Map* map; + int argc; + char** argv; +}; + +static Lib::Graphs +find_graphs(const URI& manifest_uri) +{ + Sord::World world; + Parser parser; + + const std::set<Parser::ResourceRecord> resources = parser.find_resources( + world, + manifest_uri, + URI(INGEN__Graph)); + + Lib::Graphs graphs; + for (const auto& r : resources) { + graphs.push_back(SPtr<const LV2Graph>(new LV2Graph(r))); + } + + return graphs; +} + +static LV2_Handle +ingen_instantiate(const LV2_Descriptor* descriptor, + double rate, + const char* bundle_path, + const LV2_Feature*const* features) +{ + // Get features from features array + LV2_URID_Map* map = nullptr; + LV2_URID_Unmap* unmap = nullptr; + LV2_Log_Log* log = nullptr; + const LV2_Options_Option* options = nullptr; + for (int i = 0; features[i]; ++i) { + if (!strcmp(features[i]->URI, LV2_URID__map)) { + map = (LV2_URID_Map*)features[i]->data; + } else if (!strcmp(features[i]->URI, LV2_URID__unmap)) { + unmap = (LV2_URID_Unmap*)features[i]->data; + } else if (!strcmp(features[i]->URI, LV2_LOG__log)) { + log = (LV2_Log_Log*)features[i]->data; + } else if (!strcmp(features[i]->URI, LV2_OPTIONS__options)) { + options = (const LV2_Options_Option*)features[i]->data; + } + } + + LV2_Log_Logger logger; + lv2_log_logger_init(&logger, map, log); + + if (!map) { + lv2_log_error(&logger, "host did not provide URI map feature\n"); + return nullptr; + } else if (!unmap) { + lv2_log_error(&logger, "host did not provide URI unmap feature\n"); + return nullptr; + } + + set_bundle_path(bundle_path); + const std::string manifest_path = ingen::bundle_file_path("manifest.ttl"); + SerdNode manifest_node = serd_node_new_file_uri( + (const uint8_t*)manifest_path.c_str(), nullptr, nullptr, true); + + Lib::Graphs graphs = find_graphs(URI((const char*)manifest_node.buf)); + serd_node_free(&manifest_node); + + const LV2Graph* graph = nullptr; + for (const auto& g : graphs) { + if (g->uri == descriptor->URI) { + graph = g.get(); + break; + } + } + + if (!graph) { + lv2_log_error(&logger, "could not find graph <%s>\n", descriptor->URI); + return nullptr; + } + + IngenPlugin* plugin = new IngenPlugin(); + plugin->map = map; + plugin->world = new ingen::World(map, unmap, log); + plugin->world->load_configuration(plugin->argc, plugin->argv); + + LV2_URID bufsz_max = map->map(map->handle, LV2_BUF_SIZE__maxBlockLength); + LV2_URID bufsz_seq = map->map(map->handle, LV2_BUF_SIZE__sequenceSize); + LV2_URID atom_Int = map->map(map->handle, LV2_ATOM__Int); + int32_t block_length = 0; + int32_t seq_size = 0; + if (options) { + for (const LV2_Options_Option* o = options; o->key; ++o) { + if (o->key == bufsz_max && o->type == atom_Int) { + block_length = *(const int32_t*)o->value; + } else if (o->key == bufsz_seq && o->type == atom_Int) { + seq_size = *(const int32_t*)o->value; + } + } + } + if (block_length == 0) { + block_length = 4096; + plugin->world->log().warn("No maximum block length given\n"); + } + if (seq_size == 0) { + seq_size = 16384; + plugin->world->log().warn("No maximum sequence size given\n"); + } + + plugin->world->log().info( + fmt("Block: %1% frames, Sequence: %2% bytes\n") + % block_length % seq_size); + plugin->world->conf().set( + "queue-size", + plugin->world->forge().make(std::max(block_length, seq_size) * 4)); + + SPtr<server::Engine> engine(new server::Engine(plugin->world)); + plugin->engine = engine; + plugin->world->set_engine(engine); + + SPtr<Interface> interface = engine->interface(); + + plugin->world->set_interface(interface); + + server::ThreadManager::set_flag(server::THREAD_PRE_PROCESS); + server::ThreadManager::single_threaded = true; + + LV2Driver* driver = new LV2Driver(*engine.get(), block_length, seq_size, rate); + engine->set_driver(SPtr<ingen::server::Driver>(driver)); + + engine->activate(); + server::ThreadManager::single_threaded = true; + + std::lock_guard<std::mutex> lock(plugin->world->rdf_mutex()); + + // Locate to time 0 to process initialization events + engine->locate(0, block_length); + engine->post_processor()->set_end_time(block_length); + + // Parse graph, filling the queue with events to create it + plugin->world->interface()->bundle_begin(); + plugin->world->parser()->parse_file(plugin->world, + plugin->world->interface().get(), + graph->filename); + plugin->world->interface()->bundle_end(); + + // Drain event queue + while (engine->pending_events()) { + engine->process_all_events(); + engine->post_processor()->process(); + engine->maid()->cleanup(); + } + + /* Register client after loading graph so the to-ui ring does not overflow. + Since we are not yet rolling, it won't be drained, causing a deadlock. */ + SPtr<Interface> client(&driver->writer(), NullDeleter<Interface>); + interface->set_respondee(client); + engine->register_client(client); + + driver->set_instantiated(true); + return (LV2_Handle)plugin; +} + +static void +ingen_connect_port(LV2_Handle instance, uint32_t port, void* data) +{ + using namespace ingen::server; + + IngenPlugin* me = (IngenPlugin*)instance; + server::Engine* engine = (server::Engine*)me->world->engine().get(); + const SPtr<LV2Driver>& driver = static_ptr_cast<LV2Driver>(engine->driver()); + if (port < driver->ports().size()) { + driver->ports().at(port)->set_buffer(data); + } else { + engine->log().rt_error("Connect to non-existent port\n"); + } +} + +static void +ingen_activate(LV2_Handle instance) +{ + IngenPlugin* me = (IngenPlugin*)instance; + SPtr<server::Engine> engine = static_ptr_cast<server::Engine>(me->world->engine()); + const SPtr<LV2Driver>& driver = static_ptr_cast<LV2Driver>(engine->driver()); + engine->activate(); + me->main = new std::thread(ingen_lv2_main, engine, driver); +} + +static void +ingen_run(LV2_Handle instance, uint32_t sample_count) +{ + IngenPlugin* me = (IngenPlugin*)instance; + SPtr<server::Engine> engine = static_ptr_cast<server::Engine>(me->world->engine()); + const SPtr<LV2Driver>& driver = static_ptr_cast<LV2Driver>(engine->driver()); + + server::ThreadManager::set_flag(ingen::server::THREAD_PROCESS); + server::ThreadManager::set_flag(ingen::server::THREAD_IS_REAL_TIME); + + driver->run(sample_count); +} + +static void +ingen_deactivate(LV2_Handle instance) +{ + IngenPlugin* me = (IngenPlugin*)instance; + me->world->engine()->deactivate(); + if (me->main) { + me->main->join(); + delete me->main; + me->main = nullptr; + } +} + +static void +ingen_cleanup(LV2_Handle instance) +{ + IngenPlugin* me = (IngenPlugin*)instance; + me->world->set_engine(SPtr<ingen::server::Engine>()); + me->world->set_interface(SPtr<ingen::Interface>()); + if (me->main) { + me->main->join(); + delete me->main; + } + + World* world = me->world; + delete me; + delete world; +} + +static void +get_state_features(const LV2_Feature* const* features, + LV2_State_Map_Path** map, + LV2_State_Make_Path** make) +{ + for (int i = 0; features[i]; ++i) { + if (map && !strcmp(features[i]->URI, LV2_STATE__mapPath)) { + *map = (LV2_State_Map_Path*)features[i]->data; + } else if (make && !strcmp(features[i]->URI, LV2_STATE__makePath)) { + *make = (LV2_State_Make_Path*)features[i]->data; + } + } +} + +static LV2_State_Status +ingen_save(LV2_Handle instance, + LV2_State_Store_Function store, + LV2_State_Handle handle, + uint32_t flags, + const LV2_Feature* const* features) +{ + IngenPlugin* plugin = (IngenPlugin*)instance; + + LV2_State_Map_Path* map_path = nullptr; + LV2_State_Make_Path* make_path = nullptr; + get_state_features(features, &map_path, &make_path); + if (!map_path || !make_path || !plugin->map) { + plugin->world->log().error("Missing state:mapPath, state:makePath, or urid:Map\n"); + return LV2_STATE_ERR_NO_FEATURE; + } + + LV2_URID ingen_file = plugin->map->map(plugin->map->handle, INGEN__file); + LV2_URID atom_Path = plugin->map->map(plugin->map->handle, + LV2_ATOM__Path); + + char* real_path = make_path->path(make_path->handle, "main.ttl"); + char* state_path = map_path->abstract_path(map_path->handle, real_path); + + auto root = plugin->world->store()->find(Raul::Path("/")); + + { + std::lock_guard<std::mutex> lock(plugin->world->rdf_mutex()); + + plugin->world->serialiser()->start_to_file(root->second->path(), real_path); + plugin->world->serialiser()->serialise(root->second); + plugin->world->serialiser()->finish(); + } + + store(handle, + ingen_file, + state_path, + strlen(state_path) + 1, + atom_Path, + LV2_STATE_IS_POD); + + free(state_path); + free(real_path); + return LV2_STATE_SUCCESS; +} + +static LV2_State_Status +ingen_restore(LV2_Handle instance, + LV2_State_Retrieve_Function retrieve, + LV2_State_Handle handle, + uint32_t flags, + const LV2_Feature* const* features) +{ + IngenPlugin* plugin = (IngenPlugin*)instance; + + LV2_State_Map_Path* map_path = nullptr; + get_state_features(features, &map_path, nullptr); + if (!map_path) { + plugin->world->log().error("Missing state:mapPath\n"); + return LV2_STATE_ERR_NO_FEATURE; + } + + LV2_URID ingen_file = plugin->map->map(plugin->map->handle, INGEN__file); + size_t size; + uint32_t type; + uint32_t valflags; + + // Get abstract path to graph file + const char* path = (const char*)retrieve( + handle, ingen_file, &size, &type, &valflags); + if (!path) { + return LV2_STATE_ERR_NO_PROPERTY; + } + + // Convert to absolute path + char* real_path = map_path->absolute_path(map_path->handle, path); + if (!real_path) { + return LV2_STATE_ERR_UNKNOWN; + } + +#if 0 + // Remove existing root graph contents + SPtr<Engine> engine = plugin->engine; + for (const auto& b : engine->root_graph()->blocks()) { + plugin->world->interface()->del(b.uri()); + } + + const uint32_t n_ports = engine->root_graph()->num_ports_non_rt(); + for (int32_t i = n_ports - 1; i >= 0; --i) { + PortImpl* port = engine->root_graph()->port_impl(i); + if (port->symbol() != "control" && port->symbol() != "notify") { + plugin->world->interface()->del(port->uri()); + } + } +#endif + + // Load new graph + std::lock_guard<std::mutex> lock(plugin->world->rdf_mutex()); + plugin->world->parser()->parse_file( + plugin->world, plugin->world->interface().get(), real_path); + + free(real_path); + return LV2_STATE_SUCCESS; +} + +static const void* +ingen_extension_data(const char* uri) +{ + static const LV2_State_Interface state = { ingen_save, ingen_restore }; + if (!strcmp(uri, LV2_STATE__interface)) { + return &state; + } + return nullptr; +} + +LV2Graph::LV2Graph(Parser::ResourceRecord record) + : Parser::ResourceRecord(std::move(record)) +{ + descriptor.URI = uri.c_str(); + descriptor.instantiate = ingen_instantiate; + descriptor.connect_port = ingen_connect_port; + descriptor.activate = ingen_activate; + descriptor.run = ingen_run; + descriptor.deactivate = ingen_deactivate; + descriptor.cleanup = ingen_cleanup; + descriptor.extension_data = ingen_extension_data; +} + +Lib::Lib(const char* bundle_path) +{ + ingen::set_bundle_path(bundle_path); + const std::string manifest_path = ingen::bundle_file_path("manifest.ttl"); + SerdNode manifest_node = serd_node_new_file_uri( + (const uint8_t*)manifest_path.c_str(), nullptr, nullptr, true); + + graphs = find_graphs(URI((const char*)manifest_node.buf)); + + serd_node_free(&manifest_node); +} + +static void +lib_cleanup(LV2_Lib_Handle handle) +{ + Lib* lib = (Lib*)handle; + delete lib; +} + +static const LV2_Descriptor* +lib_get_plugin(LV2_Lib_Handle handle, uint32_t index) +{ + Lib* lib = (Lib*)handle; + return index < lib->graphs.size() ? &lib->graphs[index]->descriptor : nullptr; +} + +/** LV2 plugin library entry point */ +LV2_SYMBOL_EXPORT +const LV2_Lib_Descriptor* +lv2_lib_descriptor(const char* bundle_path, + const LV2_Feature*const* features) +{ + static const uint32_t desc_size = sizeof(LV2_Lib_Descriptor); + Lib* lib = new Lib(bundle_path); + + // FIXME: memory leak. I think the LV2_Lib_Descriptor API is botched :( + LV2_Lib_Descriptor* desc = (LV2_Lib_Descriptor*)malloc(desc_size); + desc->handle = lib; + desc->size = desc_size; + desc->cleanup = lib_cleanup; + desc->get_plugin = lib_get_plugin; + + return desc; +} + +} // extern "C" |