diff options
Diffstat (limited to 'src/server/ingen_lv2.cpp')
-rw-r--r-- | src/server/ingen_lv2.cpp | 422 |
1 files changed, 422 insertions, 0 deletions
diff --git a/src/server/ingen_lv2.cpp b/src/server/ingen_lv2.cpp new file mode 100644 index 00000000..bd64cf34 --- /dev/null +++ b/src/server/ingen_lv2.cpp @@ -0,0 +1,422 @@ +/* Ingen.LV2 - A thin wrapper which allows Ingen to run as an LV2 plugin. + * Copyright 2008-2011 David Robillard <http://drobilla.net> + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This library 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 General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdlib.h> + +#include <string> +#include <vector> + +#include <glib.h> +#include <glibmm/convert.h> +#include <glibmm/miscutils.h> + +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" + +#include "raul/SharedPtr.hpp" +#include "raul/Thread.hpp" +#include "raul/log.hpp" + +#include "server/AudioBuffer.hpp" +#include "server/Driver.hpp" +#include "server/Engine.hpp" +#include "server/PatchImpl.hpp" +#include "server/PostProcessor.hpp" +#include "server/ProcessContext.hpp" +#include "server/QueuedEngineInterface.hpp" +#include "server/ThreadManager.hpp" +#include "ingen/ServerInterface.hpp" +#include "shared/World.hpp" +#include "serialisation/Parser.hpp" +#include "shared/Configuration.hpp" +#include "shared/runtime_paths.hpp" + +#include "ingen-config.h" + +/** Record of a patch in this Ingen LV2 bundle */ +struct LV2Patch { + LV2Patch(const std::string& u, const std::string& f); + + const std::string uri; + const std::string filename; + LV2_Descriptor descriptor; +}; + +class Lib { +public: + Lib(); + ~Lib(); + + typedef std::vector< SharedPtr<const LV2Patch> > Patches; + + Patches patches; + Ingen::Shared::Configuration conf; + int argc; + char** argv; +}; + +/** Library state (constructed/destructed on library load/unload) */ +Lib lib; + +namespace Ingen { +namespace Server { + +class 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; + + 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; +}; + +class LV2Driver : public Ingen::Server::Driver { +private: + typedef std::vector<LV2Port*> 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) + {} + + 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); + + _context.engine().process_events(_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) { _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<LV2Port*>(port)); + assert(port->patch_port()->index() == _ports.size()); + _ports.push_back((LV2Port*)port); + } + + virtual Raul::Deletable* remove_port(const Raul::Path& path, + 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(PortType port_type, EventType event_type) { + return true; + } + + virtual DriverPort* create_port(DuplexPort* patch_port) { + return new LV2Port(this, patch_port); + } + + virtual DriverPort* driver_port(const Raul::Path& path) { + ThreadManager::assert_thread(THREAD_PROCESS); + + for (Ports::iterator i = _ports.begin(); i != _ports.end(); ++i) + if ((*i)->patch_port()->path() == path) + return (*i); + + return NULL; + } + + virtual SampleCount block_length() const { return _buffer_size; } + virtual SampleCount sample_rate() const { return _sample_rate; } + virtual SampleCount frame_time() const { return _frame_time;} + + virtual bool is_realtime() const { return true; } + virtual ProcessContext& context() { return _context; } + + Ports& ports() { return _ports; } + +private: + ProcessContext _context; + PatchImpl* _root_patch; + SampleCount _buffer_size; + SampleCount _sample_rate; + SampleCount _frame_time; + Ports _ports; +}; + +} // namespace Server +} // namespace Ingen + +extern "C" { + +using namespace Ingen; +using namespace Ingen::Server; + +/** LV2 plugin library entry point */ +LV2_SYMBOL_EXPORT +const LV2_Descriptor* +lv2_descriptor(uint32_t index) +{ + return index < lib.patches.size() ? &lib.patches[index]->descriptor : NULL; +} + +struct IngenPlugin { + Ingen::Shared::World* world; +}; + +static LV2_Handle +ingen_instantiate(const LV2_Descriptor* descriptor, + double rate, + const char* bundle_path, + const LV2_Feature*const* features) +{ + Shared::set_bundle_path(bundle_path); + + const LV2Patch* patch = NULL; + for (Lib::Patches::iterator i = lib.patches.begin(); i != lib.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 = new Ingen::Shared::World(&lib.conf, lib.argc, lib.argv); + if (!plugin->world->load_module("serialisation")) { + delete plugin->world; + return NULL; + } + + SharedPtr<Server::Engine> engine(new Server::Engine(plugin->world)); + plugin->world->set_local_engine(engine); + + SharedPtr<Server::QueuedEngineInterface> interface( + new Server::QueuedEngineInterface( + *engine.get(), + engine->event_queue_size())); + + plugin->world->set_engine(interface); + engine->add_event_source(interface); + + Raul::Thread::get().set_context(Server::THREAD_PRE_PROCESS); + Server::ThreadManager::single_threaded = true; + + // FIXME: fixed (or at least maximum) buffer size + LV2Driver* driver = new LV2Driver(*engine.get(), rate, 4096); + engine->set_driver(SharedPtr<Ingen::Server::Driver>(driver)); + + engine->activate(); + Server::ThreadManager::single_threaded = true; + + ProcessContext context(*engine.get()); + context.locate(0, UINT_MAX, 0); + + engine->post_processor()->set_end_time(UINT_MAX); + + // TODO: Load only necessary plugins + plugin->world->engine()->get("ingen:plugins"); + interface->process(*engine->post_processor(), context, false); + engine->post_processor()->process(); + + plugin->world->parser()->parse_file(plugin->world, + plugin->world->engine().get(), + patch->filename); + + while (!interface->empty()) { + interface->process(*engine->post_processor(), context, false); + engine->post_processor()->process(); + } + + engine->deactivate(); + + 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->local_engine().get(); + LV2Driver* driver = (LV2Driver*)engine->driver(); + if (port < driver->ports().size()) { + driver->ports().at(port)->set_buffer(data); + assert(driver->ports().at(port)->patch_port()->index() == port); + } else { + Raul::warn << "Connect to non-existent port " << port << std::endl; + } +} + +static void +ingen_activate(LV2_Handle instance) +{ + IngenPlugin* me = (IngenPlugin*)instance; + me->world->local_engine()->activate(); +} + +static void +ingen_run(LV2_Handle instance, uint32_t sample_count) +{ + IngenPlugin* me = (IngenPlugin*)instance; + Server::Engine* engine = (Server::Engine*)me->world->local_engine().get(); + // FIXME: don't do this every call + Raul::Thread::get().set_context(Ingen::Server::THREAD_PROCESS); + ((LV2Driver*)engine->driver())->run(sample_count); +} + +static void +ingen_deactivate(LV2_Handle instance) +{ + IngenPlugin* me = (IngenPlugin*)instance; + me->world->local_engine()->deactivate(); +} + +static void +ingen_cleanup(LV2_Handle instance) +{ + IngenPlugin* me = (IngenPlugin*)instance; + me->world->set_local_engine(SharedPtr<Ingen::Server::Engine>()); + me->world->set_engine(SharedPtr<Ingen::ServerInterface>()); + delete me->world; + free(instance); +} + +static const void* +ingen_extension_data(const char* uri) +{ + return NULL; +} + +LV2Patch::LV2Patch(const std::string& u, const std::string& f) + : uri(u) + , filename(f) +{ + 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; +} + +/** Library constructor (called on shared library load) */ +Lib::Lib() + : argc(0) + , argv(NULL) +{ + if (!Glib::thread_supported()) + Glib::thread_init(); + + using namespace Ingen; + + Ingen::Shared::set_bundle_path_from_code((void*)&lv2_descriptor); + + Ingen::Shared::World* world = new Ingen::Shared::World(&conf, argc, argv); + if (!world->load_module("serialisation")) { + delete world; + return; + } + + assert(world->parser()); + + typedef Serialisation::Parser::PatchRecords Records; + + Records records(world->parser()->find_patches( + world, Glib::filename_to_uri( + Shared::bundle_file_path("manifest.ttl")))); + + for (Records::iterator i = records.begin(); i != records.end(); ++i) { + patches.push_back( + SharedPtr<const LV2Patch>(new LV2Patch(i->patch_uri.str(), + i->file_uri))); + } + + delete world; +} + +/** Library destructor (called on shared library unload) */ +Lib::~Lib() +{ +} + +} // extern "C" |