diff options
-rw-r--r-- | libs/ardour/ardour/lv2_plugin.h | 7 | ||||
-rw-r--r-- | libs/ardour/ardour/worker.h | 90 | ||||
-rw-r--r-- | libs/ardour/lv2/lv2plug.in/ns/ext/worker/worker.h | 150 | ||||
-rw-r--r-- | libs/ardour/lv2_plugin.cc | 84 | ||||
-rw-r--r-- | libs/ardour/worker.cc | 115 | ||||
-rw-r--r-- | libs/ardour/wscript | 1 | ||||
-rw-r--r-- | libs/pbd/pbd/semaphore.h | 198 |
7 files changed, 636 insertions, 9 deletions
diff --git a/libs/ardour/ardour/lv2_plugin.h b/libs/ardour/ardour/lv2_plugin.h index 49d349f844..0c8ecaa34e 100644 --- a/libs/ardour/ardour/lv2_plugin.h +++ b/libs/ardour/ardour/lv2_plugin.h @@ -26,6 +26,7 @@ #include "ardour/plugin.h" #include "ardour/uri_map.h" +#include "ardour/worker.h" #include "pbd/ringbuffer.h" namespace ARDOUR { @@ -33,7 +34,7 @@ namespace ARDOUR { class AudioEngine; class Session; -class LV2Plugin : public ARDOUR::Plugin +class LV2Plugin : public ARDOUR::Plugin, public ARDOUR::Workee { public: LV2Plugin (ARDOUR::AudioEngine& engine, @@ -129,6 +130,9 @@ class LV2Plugin : public ARDOUR::Plugin void enable_ui_emmission(); void emit_to_ui(void* controller, UIMessageSink sink); + void work(uint32_t size, const void* data); + void work_response(uint32_t size, const void* data); + static URIMap _uri_map; static uint32_t _midi_event_type_ev; @@ -195,6 +199,7 @@ class LV2Plugin : public ARDOUR::Plugin LV2_Feature _data_access_feature; LV2_Feature _instance_access_feature; LV2_Feature _make_path_feature; + LV2_Feature _work_schedule_feature; mutable unsigned _state_version; diff --git a/libs/ardour/ardour/worker.h b/libs/ardour/ardour/worker.h new file mode 100644 index 0000000000..eca5aa21ca --- /dev/null +++ b/libs/ardour/ardour/worker.h @@ -0,0 +1,90 @@ +/* + Copyright (C) 2012 Paul Davis + Author: David Robillard + + This program 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 program 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef __ardour_worker_h__ +#define __ardour_worker_h__ + +#include <stdint.h> + +#include <glibmm/thread.h> + +#include "pbd/ringbuffer.h" +#include "pbd/semaphore.h" + +namespace ARDOUR { + +/** + An object that needs to schedule non-RT work in the audio thread. +*/ +class Workee { +public: + virtual ~Workee() {} + + /** + Do some work in the worker thread. + */ + virtual void work(uint32_t size, const void* data) = 0; + + /** + Handle a response from the worker thread in the audio thread. + */ + virtual void work_response(uint32_t size, const void* data) = 0; +}; + +/** + A worker thread for non-realtime tasks scheduled in the audio thread. +*/ +class Worker +{ +public: + Worker(Workee* workee, uint32_t ring_size); + ~Worker(); + + /** + Schedule work (audio thread). + @return false on error. + */ + bool schedule(uint32_t size, const void* data); + + /** + Respond from work (worker thread). + @return false on error. + */ + bool respond(uint32_t size, const void* data); + + /** + Emit any pending responses (audio thread). + */ + void emit_responses(); + +private: + void run(); + + Workee* _workee; + Glib::Thread* _thread; + RingBuffer<uint8_t>* _requests; + RingBuffer<uint8_t>* _responses; + uint8_t* _response; + PBD::Semaphore _sem; + bool _exit; +}; + +} // namespace ARDOUR + +#endif /* __ardour_worker_h__ */ diff --git a/libs/ardour/lv2/lv2plug.in/ns/ext/worker/worker.h b/libs/ardour/lv2/lv2plug.in/ns/ext/worker/worker.h new file mode 100644 index 0000000000..74ac45bc1e --- /dev/null +++ b/libs/ardour/lv2/lv2plug.in/ns/ext/worker/worker.h @@ -0,0 +1,150 @@ +/* + Copyright 2012 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file worker.h C header for the LV2 Worker extension + <http://lv2plug.in/ns/ext/worker>. +*/ + +#ifndef LV2_WORKER_H +#define LV2_WORKER_H + +#include <stdint.h> + +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" + +#define LV2_WORKER_URI "http://lv2plug.in/ns/ext/worker" +#define LV2_WORKER_PREFIX LV2_WORKER_URI "#" + +#define LV2_WORKER__interface LV2_WORKER_PREFIX "interface" +#define LV2_WORKER__schedule LV2_WORKER_PREFIX "schedule" + +/** + A status code for worker functions. +*/ +typedef enum { + LV2_WORKER_SUCCESS = 0, /**< Completed successfully. */ + LV2_WORKER_ERR_UNKNOWN = 1, /**< Unknown error. */ + LV2_WORKER_ERR_NO_SPACE = 2 /**< Failed due to lack of space. */ +} LV2_Worker_Status; + +typedef void* LV2_Worker_Respond_Handle; + +/** + A function to respond to run() from the worker method. + + The @p data MUST be safe for the host to copy and later pass to + work_response(), and the host MUST guarantee that it will be eventually + passed to work_response() if this function returns LV2_WORKER_SUCCESS. +*/ +typedef LV2_Worker_Status (*LV2_Worker_Respond_Function)( + LV2_Worker_Respond_Handle handle, + uint32_t size, + const void* data); + +/** + LV2 Plugin Worker Interface. + + This is the interface provided by the plugin to implement a worker method. + The plugin's extension_data() method should return an LV2_Worker_Interface + when called with LV2_WORKER__interface as its argument. +*/ +typedef struct _LV2_Worker_Interface { + /** + The worker method. This is called by the host in a non-realtime context + as requested, possibly with an arbitrary message to handle. + + A response can be sent to run() using @p respond. The plugin MUST NOT + make any assumptions about which thread calls this method, other than + the fact that there are no real-time requirements. + + @param instance The LV2 instance this is a method on. + @param respond A function for sending a response to run(). + @param handle Must be passed to @p respond if it is called. + @param size The size of @p data. + @param data Data from run(), or NULL. + */ + LV2_Worker_Status (*work)(LV2_Handle instance, + LV2_Worker_Respond_Function respond, + LV2_Worker_Respond_Handle handle, + uint32_t size, + const void* data); + + /** + Handle a response from the worker. This is called by the host in the + run() context when a response from the worker is ready. + + @param instance The LV2 instance this is a method on. + @param size The size of @p body. + @param body Message body, or NULL. + */ + LV2_Worker_Status (*work_response)(LV2_Handle instance, + uint32_t size, + const void* body); + + /** + Called when all responses for this cycle have been delivered. + + Since work_response() may be called after run() finished, this provides + a hook for code that must run after the cycle is completed. + + This field may be NULL if the plugin has no use for it. Otherwise, the + host MUST call it after every run(), regardless of whether or not any + responses were sent that cycle. + */ + LV2_Worker_Status (*end_run)(LV2_Handle instance); +} LV2_Worker_Interface; + +typedef void* LV2_Worker_Schedule_Handle; + +typedef struct _LV2_Worker_Schedule { + /** + Opaque host data. + */ + LV2_Worker_Schedule_Handle handle; + + /** + Request from run() that the host call the worker. + + This function is in the audio threading class. It should be called from + run() to request that the host call the work() method in a non-realtime + context with the given arguments. + + This function is always safe to call from run(), but it is not + guaranteed that the worker is actually called from a different thread. + In particular, when free-wheeling (e.g. for offline rendering), the + worker may be executed immediately. This allows single-threaded + processing with sample accuracy and avoids timing problems when run() is + executing much faster or slower than real-time. + + Plugins SHOULD be written in such a way that if the worker runs + immediately, and responses from the worker are delivered immediately, + the effect of the work takes place immediately with sample accuracy. + + The @p data MUST be safe for the host to copy and later pass to work(), + and the host MUST guarantee that it will be eventually passed to work() + if this function returns LV2_WORKER_SUCCESS. + + @param handle The handle field of this struct. + @param size The size of @p data. + @param data Message to pass to work(), or NULL. + */ + LV2_Worker_Status (*schedule_work)(LV2_Worker_Schedule_Handle handle, + uint32_t size, + const void* data); +} LV2_Worker_Schedule; + +#endif /* LV2_WORKER_H */ diff --git a/libs/ardour/lv2_plugin.cc b/libs/ardour/lv2_plugin.cc index 91dd1578fb..556b8d4357 100644 --- a/libs/ardour/lv2_plugin.cc +++ b/libs/ardour/lv2_plugin.cc @@ -43,6 +43,7 @@ #include "ardour/debug.h" #include "ardour/lv2_plugin.h" #include "ardour/session.h" +#include "ardour/worker.h" #include "i18n.h" #include <locale.h> @@ -51,6 +52,7 @@ #include "lv2/lv2plug.in/ns/ext/atom/atom.h" #include "lv2/lv2plug.in/ns/ext/state/state.h" +#include "lv2/lv2plug.in/ns/ext/worker/worker.h" #include "lv2_evbuf.h" @@ -113,20 +115,45 @@ public: static LV2World _world; +/** Called by the plugin to schedule non-RT work. */ +static LV2_Worker_Status +work_schedule(LV2_Worker_Schedule_Handle handle, + uint32_t size, + const void* data) +{ + Worker* worker = (Worker*)handle; + return worker->schedule(size, data) ? + LV2_WORKER_SUCCESS : LV2_WORKER_ERR_UNKNOWN; +} + +/** Called by the plugin to respond to non-RT work. */ +static LV2_Worker_Status +work_respond(LV2_Worker_Respond_Handle handle, + uint32_t size, + const void* data) +{ + Worker* worker = (Worker*)handle; + return worker->respond(size, data) ? + LV2_WORKER_SUCCESS : LV2_WORKER_ERR_UNKNOWN; +} + struct LV2Plugin::Impl { Impl() : plugin(0), ui(0), ui_type(0), name(0), author(0), instance(0) + , worker(0), work_iface(0) #ifdef HAVE_NEW_LILV , state(0) #endif {} - LilvPlugin* plugin; - const LilvUI* ui; - const LilvNode* ui_type; - LilvNode* name; - LilvNode* author; - LilvInstance* instance; + LilvPlugin* plugin; + const LilvUI* ui; + const LilvNode* ui_type; + LilvNode* name; + LilvNode* author; + LilvInstance* instance; + Worker* worker; + LV2_Worker_Interface* work_iface; #ifdef HAVE_NEW_LILV - LilvState* state; + LilvState* state; #endif }; @@ -177,6 +204,7 @@ LV2Plugin::init(void* c_plugin, framecnt_t rate) _instance_access_feature.URI = "http://lv2plug.in/ns/ext/instance-access"; _data_access_feature.URI = "http://lv2plug.in/ns/ext/data-access"; _make_path_feature.URI = LV2_STATE__makePath; + _work_schedule_feature.URI = LV2_WORKER__schedule; LilvPlugin* plugin = _impl->plugin; @@ -192,7 +220,7 @@ LV2Plugin::init(void* c_plugin, framecnt_t rate) lilv_node_free(state_iface_uri); #endif - _features = (LV2_Feature**)malloc(sizeof(LV2_Feature*) * 7); + _features = (LV2_Feature**)malloc(sizeof(LV2_Feature*) * 8); _features[0] = &_instance_access_feature; _features[1] = &_data_access_feature; _features[2] = &_make_path_feature; @@ -200,6 +228,7 @@ LV2Plugin::init(void* c_plugin, framecnt_t rate) _features[4] = _uri_map.urid_map_feature(); _features[5] = _uri_map.urid_unmap_feature(); _features[6] = NULL; + _features[7] = NULL; LV2_State_Make_Path* make_path = (LV2_State_Make_Path*)malloc( sizeof(LV2_State_Make_Path)); @@ -207,6 +236,18 @@ LV2Plugin::init(void* c_plugin, framecnt_t rate) make_path->path = &lv2_state_make_path; _make_path_feature.data = make_path; + LilvNode* worker_schedule = lilv_new_uri(_world.world, LV2_WORKER__schedule); + if (lilv_plugin_has_feature(plugin, worker_schedule)) { + LV2_Worker_Schedule* schedule = (LV2_Worker_Schedule*)malloc( + sizeof(LV2_Worker_Schedule)); + _impl->worker = new Worker(this, 4096); + schedule->handle = _impl->worker; + schedule->schedule_work = work_schedule; + _work_schedule_feature.data = schedule; + _features[6] = &_work_schedule_feature; + } + lilv_node_free(worker_schedule); + _impl->instance = lilv_plugin_instantiate(plugin, rate, _features); _impl->name = lilv_plugin_get_name(plugin); _impl->author = lilv_plugin_get_author_name(plugin); @@ -220,6 +261,8 @@ LV2Plugin::init(void* c_plugin, framecnt_t rate) _data_access_extension_data.extension_data = _impl->instance->lv2_descriptor->extension_data; _data_access_feature.data = &_data_access_extension_data; + _impl->work_iface = (LV2_Worker_Interface*)extension_data(LV2_WORKER__interface); + if (lilv_plugin_has_feature(plugin, _world.lv2_inPlaceBroken)) { error << string_compose( _("LV2: \"%1\" cannot be used, since it cannot do inplace processing"), @@ -369,6 +412,10 @@ LV2Plugin::~LV2Plugin () lilv_node_free(_impl->name); lilv_node_free(_impl->author); + free(_features); + free(_make_path_feature.data); + free(_work_schedule_feature.data); + delete _to_ui; delete _from_ui; @@ -850,6 +897,7 @@ LV2Plugin::write_to_ui(uint32_t index, uint32_t size, uint8_t* body) { + std::cerr << "WRITE TO UI" << std::endl; write_to(_to_ui, index, protocol, size, body); } @@ -888,6 +936,19 @@ LV2Plugin::emit_to_ui(void* controller, UIMessageSink sink) } void +LV2Plugin::work(uint32_t size, const void* data) +{ + _impl->work_iface->work( + _impl->instance->lv2_handle, work_respond, _impl->worker, size, data); +} + +void +LV2Plugin::work_response(uint32_t size, const void* data) +{ + _impl->work_iface->work_response(_impl->instance->lv2_handle, size, data); +} + +void LV2Plugin::set_insert_info(const PluginInsert* insert) { _insert_id = insert->id(); @@ -1297,6 +1358,13 @@ LV2Plugin::run(pframes_t nframes) } lilv_instance_run(_impl->instance, nframes); + + if (_impl->work_iface) { + _impl->worker->emit_responses(); + if (_impl->work_iface->end_run) { + _impl->work_iface->end_run(_impl->instance->lv2_handle); + } + } } void diff --git a/libs/ardour/worker.cc b/libs/ardour/worker.cc new file mode 100644 index 0000000000..c108f653c4 --- /dev/null +++ b/libs/ardour/worker.cc @@ -0,0 +1,115 @@ +/* + Copyright (C) 2012 Paul Davis + Author: David Robillard + + This program 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 program 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include <stdlib.h> + +#include "ardour/worker.h" +#include "pbd/error.h" + +namespace ARDOUR { + +Worker::Worker(Workee* workee, uint32_t ring_size) + : _workee(workee) + , _thread(Glib::Thread::create(sigc::mem_fun(*this, &Worker::run), true)) + , _requests(new RingBuffer<uint8_t>(ring_size)) + , _responses(new RingBuffer<uint8_t>(ring_size)) + , _response((uint8_t*)malloc(ring_size)) + , _sem(0) + , _exit(false) +{} + +Worker::~Worker() +{ + _exit = true; + _sem.post(); + _thread->join(); +} + +bool +Worker::schedule(uint32_t size, const void* data) +{ + if (_requests->write((const uint8_t*)&size, sizeof(size)) != sizeof(size)) { + return false; + } + if (_requests->write((const uint8_t*)data, size) != size) { + return false; // FIXME: corruption + } + _sem.post(); + return true; +} + +bool +Worker::respond(uint32_t size, const void* data) +{ + if (_responses->write((const uint8_t*)&size, sizeof(size)) != sizeof(size)) { + return false; + } + if (_responses->write((const uint8_t*)data, size) != size) { + return false; // FIXME: corruption + } + return true; +} + +void +Worker::emit_responses() +{ + uint32_t read_space = _responses->read_space(); + uint32_t size = 0; + while (read_space > sizeof(size)) { + _responses->read((uint8_t*)&size, sizeof(size)); + _responses->read(_response, size); + _workee->work_response(size, _response); + read_space -= sizeof(size) + size; + } +} + +void +Worker::run() +{ + void* buf = NULL; + size_t buf_size = 0; + while (true) { + _sem.wait(); + if (_exit) { + return; + } + + uint32_t size = 0; + if (_requests->read((uint8_t*)&size, sizeof(size)) < sizeof(size)) { + PBD::error << "Worker: Error reading size from request ring" + << endmsg; + continue; + } + + if (size > buf_size) { + buf = realloc(buf, size); + buf_size = size; + } + + if (_requests->read((uint8_t*)buf, size) < size) { + PBD::error << "Worker: Error reading body from request ring" + << endmsg; + continue; // TODO: This is probably fatal + } + + _workee->work(size, buf); + } +} + +} // namespace ARDOUR diff --git a/libs/ardour/wscript b/libs/ardour/wscript index ab04fde80b..b12ebf593d 100644 --- a/libs/ardour/wscript +++ b/libs/ardour/wscript @@ -218,6 +218,7 @@ libardour_sources = [ 'user_bundle.cc', 'utils.cc', 'version.cc', + 'worker.cc', ] def flac_supported(): diff --git a/libs/pbd/pbd/semaphore.h b/libs/pbd/pbd/semaphore.h new file mode 100644 index 0000000000..f1b07ea4f5 --- /dev/null +++ b/libs/pbd/pbd/semaphore.h @@ -0,0 +1,198 @@ +/* + Copyright (C) 2012 Paul Davis + Author: David Robillard + + This program 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 program 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef __pbd_semaphore_h__ +#define __pbd_semaphore_h__ + +#ifdef __APPLE__ +# include <mach/mach.h> +#elif defined(_WIN32) +# include <windows.h> +#else +# include <semaphore.h> +# include <errno.h> +#endif + +#include "pbd/failed_constructor.h" + +namespace PBD { + +/** + Unnamed (process local) counting semaphore. + + The civilized person's synchronisation primitive. A counting semaphore is + an integer which is always non-negative, so, an attempted decrement (or + "wait") will block if the value is 0, until another thread does an increment + (or "post"). + + At least on Lignux, the main advantage of this is that it is fast and the + only safe way to reliably signal from a real-time audio thread. The + counting semantics also complement ringbuffers of events nicely. +*/ +class Semaphore +{ +public: + /** + Create a new semaphore. + + Chances are you want 1 wait() per 1 post(), an initial value of 0. + */ + inline Semaphore(unsigned initial); + + inline ~Semaphore(); + + /** Post/Increment/Signal */ + inline void post(); + + /** Wait/Decrement. Returns false on error. */ + inline bool wait(); + + /** Attempt Wait/Decrement. Returns true iff a decrement occurred. */ + inline bool try_wait(); + +private: +#if defined(__APPLE__) + semaphore_t _sem; // sem_t is a worthless broken mess on OSX +#elif defined(_WIN32) + HANDLE _sem; // types are overrated anyway +#else + sem_t _sem; +#endif +}; + +#ifdef __APPLE__ + +inline +Semaphore::Semaphore(unsigned initial) +{ + if (semaphore_create(mach_task_self(), &sem->sem, SYNC_POLICY_FIFO, 0)) { + throw failed_constructor(); + } +} + +inline +Semaphore::~Semaphore() +{ + semaphore_destroy(mach_task_self(), _sem); +} + +inline void +Semaphore::post() +{ + semaphore_signal(_sem); +} + +inline bool +Semaphore::wait() +{ + if (semaphore_wait(_sem) != KERN_SUCCESS) { + return false; + } + return true; +} + +inline bool +Semaphore::try_wait() +{ + const mach_timespec_t zero = { 0, 0 }; + return semaphore_timedwait(_sem, zero) == KERN_SUCCESS; +} + +#elif defined(_WIN32) + +inline +Semaphore::Semaphore(unsigned initial) +{ + if (!(_sem = CreateSemaphore(NULL, initial, LONG_MAX, NULL))) { + throw failed_constructor(); + } +} + +inline +Semaphore::~Semaphore() +{ + CloseHandle(_sem); +} + +inline void +Semaphore::post() +{ + ReleaseSemaphore(_sem, 1, NULL); +} + +inline bool +Semaphore::wait() +{ + if (WaitForSingleObject(_sem, INFINITE) != WAIT_OBJECT_0) { + return false; + } + return true; +} + +inline bool +Semaphore::try_wait() +{ + return WaitForSingleObject(sem->sem, 0) == WAIT_OBJECT_0; +} + +#else /* !defined(__APPLE__) && !defined(_WIN32) */ + +Semaphore::Semaphore(unsigned initial) +{ + if (sem_init(&_sem, 0, initial)) { + throw failed_constructor(); + } +} + +inline +Semaphore::~Semaphore() +{ + sem_destroy(&_sem); +} + +inline void +Semaphore::post() +{ + sem_post(&_sem); +} + +inline bool +Semaphore::wait() +{ + while (sem_wait(&_sem)) { + if (errno != EINTR) { + return false; // We are all doomed + } + /* Otherwise, interrupted (rare/weird), so try again. */ + } + + return true; +} + +inline bool +Semaphore::try_wait() +{ + return (sem_trywait(&_sem) == 0); +} + +#endif + +} // namespace PBD + +#endif /* __pbd_semaphore_h__ */ |