From 059f20c9666234f2be01498ee04f1e7ee795ba8f Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sat, 6 Mar 2010 10:23:19 +0000 Subject: Save Ingen patches as working standard LV2 plugin bundles. This allows you to create an Ingen patch in Ingen running as a Jack client, save it, then load that patch as an LV2 plugin in any LV2 compliant host. Eliminate (hopefully) all static data in the engine (for multiple instantiations in a single process). More API/ABI stable interface for Ingen::Shared::World. git-svn-id: http://svn.drobilla.net/lad/trunk/ingen@2533 a436a847-0d15-0410-975c-d299462d15a1 --- src/engine/ingen_lv2.cpp | 369 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 292 insertions(+), 77 deletions(-) (limited to 'src/engine/ingen_lv2.cpp') diff --git a/src/engine/ingen_lv2.cpp b/src/engine/ingen_lv2.cpp index 424df46c..8d14cba7 100644 --- a/src/engine/ingen_lv2.cpp +++ b/src/engine/ingen_lv2.cpp @@ -16,55 +16,177 @@ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef PLUGIN_URI -#error "This file requires PLUGIN_URI to be defined" -#endif - #include +#include +#include +#include #include "lv2.h" +#include "ingen-config.h" +#include "raul/log.hpp" +#include "raul/Thread.hpp" +#include "raul/SharedPtr.hpp" +#include "engine/AudioBuffer.hpp" #include "engine/Engine.hpp" +#include "engine/ThreadManager.hpp" #include "engine/Driver.hpp" #include "engine/ProcessContext.hpp" #include "engine/PatchImpl.hpp" +#include "engine/QueuedEngineInterface.hpp" #include "module/World.hpp" - -extern "C" { +#include "module/ingen_module.hpp" +#include "shared/runtime_paths.hpp" +#include "shared/Configuration.hpp" +#include "serialisation/Parser.hpp" +#include "interface/EngineInterface.hpp" using namespace Ingen; -/* Plugin */ +namespace Ingen { +namespace LV2 { + +/** Record of a patch found in this LV2 bundle */ +struct LV2Patch { + LV2Patch(const std::string& u, const std::string& f); + + const std::string uri; + const std::string filename; + LV2_Descriptor descriptor; +}; + + +/* Static library data */ +typedef std::vector< SharedPtr > LV2Patches; +static bool initialised = false; +static LV2Patches patches; +static Configuration conf; +static int argc = 0; +static char** argv = NULL; +static Shared::World* world = NULL; + + +struct LV2Driver; + +class LV2Port : public DriverPort +{ +public: + LV2Port(LV2Driver* driver, DuplexPort* patch_port) + : DriverPort(patch_port) + , _driver(driver) + , _buffer(NULL) + {} + + // TODO: LV2 dynamic ports + void create() {} + void destroy() {} + void move(const Raul::Path& path) {} + + void pre_process(ProcessContext& context) { + if (!is_input() || !_buffer) + return; + + if (_patch_port->buffer_type() == PortType::AUDIO) { + AudioBuffer* patch_buf = (AudioBuffer*)_patch_port->buffer(0).get(); + patch_buf->copy((Sample*)_buffer, 0, context.nframes() - 1); + } else if (_patch_port->buffer_type() == PortType::EVENTS) { + //Raul::warn << "TODO: LV2 event I/O" << std::endl; + } + } + + void post_process(ProcessContext& context) { + if (is_input() || !_buffer) + return; -struct IngenLV2Driver : public Ingen::Driver { - IngenLV2Driver(Engine& engine, SampleCount buffer_size, SampleCount sample_rate) + if (_patch_port->buffer_type() == PortType::AUDIO) { + AudioBuffer* patch_buf = (AudioBuffer*)_patch_port->buffer(0).get(); + memcpy((Sample*)_buffer, patch_buf->data(), context.nframes() * sizeof(Sample)); + } else if (_patch_port->buffer_type() == PortType::EVENTS) { + //Raul::warn << "TODO: LV2 event I/O" << std::endl; + } + } + + void* buffer() const { return _buffer; } + void set_buffer(void* buf) { _buffer = buf; } + +private: + LV2Driver* _driver; + void* _buffer; +}; + + +struct LV2Driver : public Driver { +private: + typedef std::vector Ports; + +public: + LV2Driver(Engine& engine, SampleCount buffer_size, SampleCount sample_rate) : _context(engine) + , _root_patch(NULL) , _buffer_size(buffer_size) , _sample_rate(sample_rate) , _frame_time(0) - , _root_patch(NULL) {} - void activate() {} - void deactivate() {} - bool is_activated() const { return true; } - void run(uint32_t nframes) { _context.locate(_frame_time, nframes, 0); + + for (Ports::iterator i = _ports.begin(); i != _ports.end(); ++i) + (*i)->pre_process(_context); + if (_root_patch) _root_patch->process(_context); + + for (Ports::iterator i = _ports.begin(); i != _ports.end(); ++i) + (*i)->post_process(_context); + _frame_time += nframes; } - virtual void set_root_patch(PatchImpl* patch) {} - virtual PatchImpl* root_patch() { return NULL; } + virtual void set_root_patch(PatchImpl* patch) { _root_patch = patch; } + virtual PatchImpl* root_patch() { return _root_patch; } + + virtual void add_port(DriverPort* port) { + // Note this doesn't have to be realtime safe since there's no dynamic LV2 ports + ThreadManager::assert_thread(THREAD_PROCESS); + assert(dynamic_cast(port)); + assert(port->patch_port()->index() == _ports.size()); + _ports.push_back((LV2Port*)port); + } + + virtual Raul::Deletable* remove_port(const Raul::Path& path, Ingen::DriverPort** port=NULL) { + // Note this doesn't have to be realtime safe since there's no dynamic LV2 ports + ThreadManager::assert_thread(THREAD_PROCESS); + + for (Ports::iterator i = _ports.begin(); i != _ports.end(); ++i) { + if ((*i)->patch_port()->path() == path) { + _ports.erase(i); + return NULL; + } + } + + Raul::warn << "Unable to find port " << path << std::endl; + return NULL; + } + + + virtual bool supports(Shared::PortType port_type, Shared::EventType event_type) { + return true; + } + + virtual DriverPort* create_port(DuplexPort* patch_port) { + return new LV2Port(this, patch_port); + } - virtual void add_port(DriverPort* port) {} - virtual DriverPort* remove_port(const Raul::Path& path) { return NULL; } + virtual DriverPort* driver_port(const Raul::Path& path) { + ThreadManager::assert_thread(THREAD_PROCESS); - virtual DriverPort* create_port(DuplexPort* patch_port) { return NULL; } + for (Ports::iterator i = _ports.begin(); i != _ports.end(); ++i) + if ((*i)->patch_port()->path() == path) + return (*i); - virtual DriverPort* driver_port(const Raul::Path& path) { return NULL; } + return NULL; + } - virtual SampleCount buffer_size() const { return _buffer_size; } + virtual SampleCount block_length() const { return _buffer_size; } virtual SampleCount sample_rate() const { return _sample_rate; } virtual SampleCount frame_time() const { return _frame_time;} @@ -77,30 +199,84 @@ private: SampleCount _buffer_size; SampleCount _sample_rate; SampleCount _frame_time; + Ports _ports; }; +} // namespace LV2 +} // namespace Ingen + + +/* LV2 Plugin Interface */ + +extern "C" { + struct IngenPlugin { - Ingen::Shared::World* world; - SharedPtr engine; + Ingen::Shared::World* world; }; -static void -ingen_activate(LV2_Handle instance) +static LV2_Handle +ingen_instantiate(const LV2_Descriptor* descriptor, + double rate, + const char* bundle_path, + const LV2_Feature*const* features) { - IngenPlugin* plugin = (IngenPlugin*)instance; - plugin->engine->activate(1); -} + Shared::set_bundle_path(bundle_path); + using namespace LV2; -static void -ingen_cleanup(LV2_Handle instance) -{ - IngenPlugin* plugin = (IngenPlugin*)instance; - plugin->engine.reset(); - ingen_destroy_world(); - free(instance); + const LV2Patch* patch = NULL; + for (LV2Patches::iterator i = patches.begin(); i != patches.end(); ++i) { + if (&(*i)->descriptor == descriptor) { + patch = (*i).get(); + break; + } + } + + if (!patch) { + Raul::error << "Could not find patch " << descriptor->URI << std::endl; + return NULL; + } + + IngenPlugin* plugin = (IngenPlugin*)malloc(sizeof(IngenPlugin)); + //plugin->world = ingen_world_new(&conf, argc, argv); + plugin->world = LV2::world; + + SharedPtr engine(new Engine(plugin->world)); + plugin->world->set_local_engine(engine); + + SharedPtr interface( + new Ingen::QueuedEngineInterface(*plugin->world->local_engine(), event_queue_size)); + plugin->world->set_engine(interface); + plugin->world->local_engine()->add_event_source(interface); + + Raul::Thread::get().set_context(THREAD_PRE_PROCESS); + ThreadManager::single_threaded = true; + + ProcessContext context(*engine.get()); + + // FIXME: fixed (or at least maximum) buffer size + engine->set_driver(SharedPtr(new LV2Driver(*engine.get(), rate, 4096))); + + engine->activate(); + ThreadManager::single_threaded = true; + + // FIXME: don't load all plugins, only necessary ones + plugin->world->engine()->load_plugins(); + engine->process_events(context); + + plugin->world->parser()->parse_document(plugin->world, + plugin->world->engine().get(), patch->filename); + engine->process_events(context); + + engine->deactivate(); + + // Activate and deactivate to create root patch, allocate buffers, etc + //engine->activate(); + //engine->deactivate(); + + return (LV2_Handle)plugin; } @@ -110,33 +286,40 @@ ingen_connect_port(LV2_Handle instance, uint32_t port, void* data) } -static LV2_Handle -ingen_instantiate(const LV2_Descriptor* descriptor, - double rate, - const char* bundle_path, - const LV2_Feature*const* features) +static void +ingen_activate(LV2_Handle instance) { - IngenPlugin* plugin = (IngenPlugin*)malloc(sizeof(IngenPlugin)); + IngenPlugin* me = (IngenPlugin*)instance; + me->world->local_engine()->activate(); +} - Shared::bundle_path = bundle_path; - plugin->world = ingen_get_world(); - plugin->engine = SharedPtr(new Engine(plugin->world)); - plugin->world->local_engine = plugin->engine; +static void +ingen_run(LV2_Handle instance, uint32_t sample_count) +{ + IngenPlugin* me = (IngenPlugin*)instance; + // FIXME: don't do this every call + Raul::Thread::get().set_context(THREAD_PROCESS); + ((LV2::LV2Driver*)me->world->local_engine()->driver())->run(sample_count); +} - // FIXME: fixed buffer size - plugin->engine->set_driver(PortType::AUDIO, - SharedPtr(new IngenLV2Driver(*plugin->engine, rate, 4096))); - return (LV2_Handle)plugin; +static void +ingen_deactivate(LV2_Handle instance) +{ + IngenPlugin* me = (IngenPlugin*)instance; + me->world->local_engine()->deactivate(); } static void -ingen_run(LV2_Handle instance, uint32_t sample_count) +ingen_cleanup(LV2_Handle instance) { - IngenPlugin* plugin = (IngenPlugin*)instance; - ((IngenLV2Driver*)plugin->engine->driver())->run(sample_count); + IngenPlugin* me = (IngenPlugin*)instance; + me->world->set_local_engine(SharedPtr()); + me->world->set_engine(SharedPtr()); + //ingen_world_free(me->world); + free(instance); } @@ -147,47 +330,79 @@ ingen_extension_data(const char* uri) } -static void -ingen_deactivate(LV2_Handle instance) +/* Library Code */ + +namespace Ingen { +namespace LV2 { + +LV2Patch::LV2Patch(const std::string& u, const std::string& f) + : uri(u), filename(f) { - IngenPlugin* plugin = (IngenPlugin*)instance; - plugin->engine->deactivate(); + 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; } +} // namespace LV2 +} // namespace Ingen -/* Library */ - -static LV2_Descriptor *ingen_descriptor = NULL; static void -init_descriptor() +init() { - ingen_descriptor = (LV2_Descriptor*)malloc(sizeof(LV2_Descriptor)); - - ingen_descriptor->URI = PLUGIN_URI; - ingen_descriptor->instantiate = ingen_instantiate; - ingen_descriptor->connect_port = ingen_connect_port; - ingen_descriptor->activate = ingen_activate; - ingen_descriptor->run = ingen_run; - ingen_descriptor->deactivate = ingen_deactivate; - ingen_descriptor->cleanup = ingen_cleanup; - ingen_descriptor->extension_data = ingen_extension_data; + Shared::set_bundle_path_from_code((void*)&init); + + using namespace LV2; + + //Shared::World* world = ingen_world_new(&conf, argc, argv); + + world = ingen_world_new(&conf, argc, argv); + if (!world->load("ingen_serialisation")) { + Raul::error << "Unable to load serialisation module" << std::endl; + //ingen_world_free(world); + return; + } + + + Serialisation::Parser::PatchRecords records( + world->parser()->find_patches(world, + Glib::filename_to_uri(Shared::bundle_file_path("manifest.ttl")))); + + for (Serialisation::Parser::PatchRecords::iterator i = records.begin(); + i != records.end(); ++i) { + patches.push_back(SharedPtr( + new LV2Patch(i->uri.str(), i->filename))); + } + + + //ingen_world_free(world); + + initialised = true; } +/* LV2 Library Interface */ + +extern "C" { + LV2_SYMBOL_EXPORT const LV2_Descriptor* lv2_descriptor(uint32_t index) { - if (!ingen_descriptor) - init_descriptor(); + if (!LV2::initialised) + init(); - switch (index) { - case 0: - return ingen_descriptor; - default: + if (index >= LV2::patches.size()) return NULL; - } + else + return &LV2::patches[index]->descriptor; +} + } -- cgit v1.2.1