diff options
author | David Robillard <d@drobilla.net> | 2011-04-20 16:26:40 +0000 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2011-04-20 16:26:40 +0000 |
commit | 138a87e915ad3aff184730415105f94c874174bf (patch) | |
tree | 0d942bdddfdbcc3d969b4fce5592e770ab851b86 /src/server | |
parent | 1f1758f4dda0ddaf01c0b1d3a756f9db8ddc979d (diff) | |
download | ingen-138a87e915ad3aff184730415105f94c874174bf.tar.gz ingen-138a87e915ad3aff184730415105f94c874174bf.tar.bz2 ingen-138a87e915ad3aff184730415105f94c874174bf.zip |
Rename Ingen::Engine to Ingen::Server (hopefully avoid odd name clases and fix #675).
git-svn-id: http://svn.drobilla.net/lad/trunk/ingen@3184 a436a847-0d15-0410-975c-d299462d15a1
Diffstat (limited to 'src/server')
139 files changed, 17645 insertions, 0 deletions
diff --git a/src/server/AudioBuffer.cpp b/src/server/AudioBuffer.cpp new file mode 100644 index 00000000..f255ec2f --- /dev/null +++ b/src/server/AudioBuffer.cpp @@ -0,0 +1,207 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <cassert> +#include "raul/log.hpp" +#include "raul/SharedPtr.hpp" +#include "lv2/lv2plug.in/ns/ext/atom/atom.h" +#include "ingen-config.h" +#include "AudioBuffer.hpp" +#include "ProcessContext.hpp" +#include "LV2Features.hpp" + +using namespace std; +using namespace Raul; + +/* TODO: Be sure these functions are vectorized by GCC when its vectorizer + * stops sucking. Probably a good idea to inline them as well */ + +namespace Ingen { +namespace Server { + +AudioBuffer::AudioBuffer(BufferFactory& bufs, PortType type, size_t size) + : ObjectBuffer(bufs, size) + , _state(OK) + , _set_value(0) + , _set_time(0) +{ + assert(size >= sizeof(LV2_Atom) + sizeof(Sample)); + assert(this->size() >= size); + assert(data()); + _type = type; + + // Control port / Single float object + if (type == PortType::CONTROL) { + atom()->type = 0;//map->float_type; + + // Audio port / Vector of float + } else { + assert(type == PortType::AUDIO); + atom()->type = 0;//map->vector_type; + LV2_Atom_Vector* body = (LV2_Atom_Vector*)atom()->body; + body->elem_count = size / sizeof(Sample); + body->elem_type = 0;//map->float_type; + } + /*debug << "Created Audio Buffer" << endl + << "\tobject @ " << (void*)atom() << endl + << "\tbody @ " << (void*)atom()->body + << "\t(offset " << (char*)atom()->body - (char*)atom() << ")" << endl + << "\tdata @ " << (void*)data() + << "\t(offset " << (char*)data() - (char*)atom() << ")" + << endl;*/ + + clear(); +} + +void +AudioBuffer::resize(size_t size) +{ + if (_type == PortType::AUDIO) { + ObjectBuffer::resize(size + sizeof(LV2_Atom_Vector)); + vector()->elem_count = size / sizeof(Sample); + } + clear(); +} + +/** Empty (ie zero) the buffer. + */ +void +AudioBuffer::clear() +{ + assert(nframes() != 0); + set_block(0, 0, nframes() - 1); + _state = OK; +} + +/** Set value of buffer to @a val after @a start_sample. + * + * The Buffer will handle setting the intial portion of the buffer to the + * value on the next cycle automatically (if @a start_sample is > 0), as + * long as pre_process() is called every cycle. + */ +void +AudioBuffer::set_value(Sample val, FrameTime cycle_start, FrameTime time) +{ + if (is_control()) + time = cycle_start; + + const FrameTime offset = time - cycle_start; + assert(nframes() != 0); + assert(offset <= nframes()); + + if (offset < nframes()) { + set_block(val, offset, nframes() - 1); + + if (offset == 0) + _state = OK; + else + _state = HALF_SET_CYCLE_1; + } // else trigger at very end of block + + _set_time = time; + _set_value = val; +} + +/** Set a block of buffer to @a val. + * + * @a start_sample and @a end_sample define the inclusive range to be set. + */ +void +AudioBuffer::set_block(Sample val, size_t start_offset, size_t end_offset) +{ + assert(end_offset >= start_offset); + assert(end_offset < nframes()); + + Sample* const buf = data(); + assert(buf); + + for (size_t i = start_offset; i <= end_offset; ++i) + buf[i] = val; +} + +/** Copy a block of @a src into buffer. + * + * @a start_sample and @a end_sample define the inclusive range to be set. + * This function only copies the same range in one buffer to another. + */ +void +AudioBuffer::copy(const Sample* src, size_t start_sample, size_t end_sample) +{ + assert(end_sample >= start_sample); + assert(nframes() != 0); + + Sample* const buf = data(); + assert(buf); + + const size_t copy_end = std::min(end_sample, (size_t)nframes() - 1); + for (size_t i = start_sample; i <= copy_end; ++i) + buf[i] = src[i]; +} + +void +AudioBuffer::copy(Context& context, const Buffer* src) +{ + const AudioBuffer* src_abuf = dynamic_cast<const AudioBuffer*>(src); + if (!src_abuf) { + clear(); + return; + } + + // Control => Control + if (src_abuf->is_control() == is_control()) { + ObjectBuffer::copy(context, src); + + // Audio => Audio + } else if (!src_abuf->is_control() && !is_control()) { + copy(src_abuf->data(), + context.offset(), context.offset() + context.nframes() - 1); + + // Audio => Control + } else if (!src_abuf->is_control() && is_control()) { + data()[0] = src_abuf->data()[context.offset()]; + + // Control => Audio + } else if (src_abuf->is_control() && !is_control()) { + data()[context.offset()] = src_abuf->data()[0]; + + // Control => Audio or Audio => Control + } else { + set_block(src_abuf->data()[0], 0, nframes()); + } +} + +void +AudioBuffer::prepare_read(Context& context) +{ + assert(nframes() != 0); + switch (_state) { + case HALF_SET_CYCLE_1: + if (context.start() > _set_time) + _state = HALF_SET_CYCLE_2; + break; + case HALF_SET_CYCLE_2: + set_block(_set_value, 0, nframes() - 1); + _state = OK; + break; + default: + break; + } +} + +} // namespace Server +} // namespace Ingen diff --git a/src/server/AudioBuffer.hpp b/src/server/AudioBuffer.hpp new file mode 100644 index 00000000..4a7869e2 --- /dev/null +++ b/src/server/AudioBuffer.hpp @@ -0,0 +1,109 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_AUDIOBUFFER_HPP +#define INGEN_ENGINE_AUDIOBUFFER_HPP + +#include <cstddef> +#include <cassert> +#include <boost/utility.hpp> +#include "types.hpp" +#include "ObjectBuffer.hpp" +#include "Context.hpp" + +using namespace std; + +namespace Ingen { +namespace Server { + +class AudioBuffer : public ObjectBuffer +{ +public: + AudioBuffer(BufferFactory& bufs, PortType type, size_t capacity); + + void clear(); + + void set_value(Sample val, FrameTime cycle_start, FrameTime time); + void set_block(Sample val, size_t start_offset, size_t end_offset); + void copy(const Sample* src, size_t start_sample, size_t end_sample); + void copy(Context& context, const Buffer* src); + void accumulate(Context& context, const AudioBuffer* src); + + inline bool is_control() const { return _type.symbol() == PortType::CONTROL; } + + inline Sample* data() const { + return (is_control()) + ? (Sample*)atom()->body + : (Sample*)(atom()->body + sizeof(LV2_Atom_Vector)); + } + + inline SampleCount nframes() const { + return (is_control()) + ? 1 + : (_size - sizeof(LV2_Atom) - sizeof(LV2_Atom_Vector)) / sizeof(Sample); + } + + inline Sample& value_at(size_t offset) const + { assert(offset < nframes()); return data()[offset]; } + + void prepare_read(Context& context); + void prepare_write(Context& context) {} + + void resize(size_t size); + +private: + enum State { OK, HALF_SET_CYCLE_1, HALF_SET_CYCLE_2 }; + + LV2_Atom_Vector* vector() { return(LV2_Atom_Vector*)atom()->body; } + + State _state; ///< State of buffer for setting values next cycle + Sample _set_value; ///< Value set by set_value (for completing the set next cycle) + FrameTime _set_time; ///< Time _set_value was set (to reset next cycle) +}; + +/** Accumulate a block of @a src into buffer. + */ +inline void +AudioBuffer::accumulate(Context& context, const AudioBuffer* const src) +{ + Sample* const buf = data(); + const Sample* const src_buf = src->data(); + + if (is_control()) { + if (src->is_control()) { // control => control + buf[0] += src_buf[0]; + } else { // audio => control + buf[0] += src_buf[context.offset()]; + } + } else { + const SampleCount end = context.offset() + context.nframes(); + if (src->is_control()) { // control => audio + for (SampleCount i = context.offset(); i < end; ++i) { + buf[i] += src_buf[0]; + } + } else { // audio => audio + for (SampleCount i = context.offset(); i < end; ++i) { + buf[i] += src_buf[i]; + } + } + } +} + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_AUDIOBUFFER_HPP diff --git a/src/server/Buffer.hpp b/src/server/Buffer.hpp new file mode 100644 index 00000000..e44915b3 --- /dev/null +++ b/src/server/Buffer.hpp @@ -0,0 +1,97 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_BUFFER_HPP +#define INGEN_ENGINE_BUFFER_HPP + +#include <cstddef> +#include <cassert> +#include <boost/utility.hpp> +#include <boost/intrusive_ptr.hpp> +#include "raul/Deletable.hpp" +#include "raul/SharedPtr.hpp" +#include "ingen/PortType.hpp" +#include "types.hpp" +#include "BufferFactory.hpp" + +namespace Ingen { +namespace Server { + +class Context; +class Engine; +class BufferFactory; + +class Buffer : public boost::noncopyable, public Raul::Deletable +{ +public: + Buffer(BufferFactory& bufs, PortType type, size_t size) + : _factory(bufs) + , _type(type) + , _size(size) + , _next(NULL) + , _refs(0) + {} + + /** Clear contents and reset state */ + virtual void clear() = 0; + + virtual void resize(size_t size) { _size = size; } + + virtual void* port_data(PortType port_type, SampleCount offset=0) = 0; + virtual const void* port_data(PortType port_type, SampleCount offset=0) const = 0; + + /** Rewind (ie reset read pointer), but leave contents unchanged */ + virtual void rewind() const {} + + virtual void copy(Context& context, const Buffer* src) = 0; + + virtual void prepare_read(Context& context) {} + virtual void prepare_write(Context& context) {} + + PortType type() const { return _type; } + size_t size() const { return _size; } + + inline void ref() { ++_refs; } + + inline void deref() { + assert(_refs > 0); + if ((--_refs) == 0) + _factory.recycle(this); + } + +protected: + BufferFactory& _factory; + PortType _type; + size_t _size; + + friend class BufferFactory; + virtual ~Buffer() {} + +private: + Buffer* _next; ///< Intrusive linked list for BufferFactory + size_t _refs; ///< Intrusive reference count for intrusive_ptr +}; + +} // namespace Server +} // namespace Ingen + +namespace boost { +inline void intrusive_ptr_add_ref(Ingen::Server::Buffer* b) { b->ref(); } +inline void intrusive_ptr_release(Ingen::Server::Buffer* b) { b->deref(); } +} + +#endif // INGEN_ENGINE_BUFFER_HPP diff --git a/src/server/BufferFactory.cpp b/src/server/BufferFactory.cpp new file mode 100644 index 00000000..f725079b --- /dev/null +++ b/src/server/BufferFactory.cpp @@ -0,0 +1,163 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <algorithm> +#include "raul/log.hpp" +#include "shared/LV2URIMap.hpp" +#include "AudioBuffer.hpp" +#include "EventBuffer.hpp" +#include "ObjectBuffer.hpp" +#include "BufferFactory.hpp" +#include "Engine.hpp" +#include "Driver.hpp" +#include "ThreadManager.hpp" + +using namespace Raul; + +namespace Ingen { +namespace Server { + +static const size_t EVENT_BYTES_PER_FRAME = 4; // FIXME + +BufferFactory::BufferFactory(Engine& engine, + SharedPtr<Ingen::Shared::LV2URIMap> uris) + : _engine(engine) + , _uris(uris) + , _silent_buffer(NULL) +{ + assert(_uris); +} + +BufferFactory::~BufferFactory() +{ + free_list(_free_audio.get()); + free_list(_free_control.get()); + free_list(_free_event.get()); + free_list(_free_object.get()); +} + +void +BufferFactory::free_list(Buffer* head) +{ + Buffer* next = head->_next; + delete head; + if (next) + free_list(next); +} + +void +BufferFactory::set_block_length(SampleCount block_length) +{ + _silent_buffer = create(PortType::AUDIO, audio_buffer_size(block_length)); +} + +size_t +BufferFactory::audio_buffer_size(SampleCount nframes) +{ + return sizeof(LV2_Atom) + sizeof(LV2_Atom_Vector) + (nframes * sizeof(float)); +} + +size_t +BufferFactory::default_buffer_size(PortType type) +{ + switch (type.symbol()) { + case PortType::AUDIO: + return audio_buffer_size(_engine.driver()->block_length()); + case PortType::CONTROL: + return sizeof(LV2_Atom) + sizeof(float); + case PortType::EVENTS: + return _engine.driver()->block_length() * EVENT_BYTES_PER_FRAME; + default: + return 1024; // Who knows + } +} + +BufferFactory::Ref +BufferFactory::get(PortType type, size_t size, bool force_create) +{ + Raul::AtomicPtr<Buffer>& head_ptr = free_list(type); + Buffer* try_head = NULL; + + if (!force_create) { + Buffer* next; + do { + try_head = head_ptr.get(); + if (!try_head) + break; + next = try_head->_next; + } while (!head_ptr.compare_and_exchange(try_head, next)); + } + + if (!try_head) { + if (!ThreadManager::thread_is(THREAD_PROCESS)) { + return create(type, size); + } else { + assert(false); + error << "Failed to obtain buffer" << endl; + return Ref(); + } + } + + try_head->_next = NULL; + return Ref(try_head); +} + +BufferFactory::Ref +BufferFactory::create(PortType type, size_t size) +{ + ThreadManager::assert_not_thread(THREAD_PROCESS); + + Buffer* buffer = NULL; + + if (size == 0) + size = default_buffer_size(type); + + if (type.is_control()) { + AudioBuffer* ret = new AudioBuffer(*this, type, size); + ret->atom()->type = _uris->atom_Vector.id; + ((LV2_Atom_Vector*)ret->atom()->body)->elem_type = _uris->atom_Float32.id; + buffer = ret; + } else if (type.is_audio()) { + AudioBuffer* ret = new AudioBuffer(*this, type, size); + ret->atom()->type = _uris->atom_Float32.id; + buffer = ret; + } else if (type.is_events()) { + buffer = new EventBuffer(*this, size); + } else if (type.is_value() || type.is_message()) { + buffer = new ObjectBuffer(*this, std::max(size, sizeof(LV2_Atom) + sizeof(void*))); + } else { + error << "Failed to create buffer of unknown type" << endl; + return Ref(); + } + + assert(buffer); + return Ref(buffer); +} + +void +BufferFactory::recycle(Buffer* buf) +{ + Raul::AtomicPtr<Buffer>& head_ptr = free_list(buf->type()); + Buffer* try_head; + do { + try_head = head_ptr.get(); + buf->_next = try_head; + } while (!head_ptr.compare_and_exchange(try_head, buf)); +} + +} // namespace Server +} // namespace Ingen diff --git a/src/server/BufferFactory.hpp b/src/server/BufferFactory.hpp new file mode 100644 index 00000000..58dc55af --- /dev/null +++ b/src/server/BufferFactory.hpp @@ -0,0 +1,92 @@ +/* This file is part of Ingen. + * Copyright 2009-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_BUFFERFACTORY_HPP +#define INGEN_ENGINE_BUFFERFACTORY_HPP + +#include <map> +#include <boost/intrusive_ptr.hpp> +#include "ingen/PortType.hpp" +#include "glibmm/thread.h" +#include "raul/RingBuffer.hpp" +#include "raul/AtomicPtr.hpp" +#include "types.hpp" + +namespace Ingen { + +namespace Shared { class LV2URIMap; } + +namespace Server { + +class Engine; +class Buffer; + +class BufferFactory { +public: + BufferFactory(Engine& engine, + SharedPtr<Ingen::Shared::LV2URIMap> uris); + + ~BufferFactory(); + + typedef boost::intrusive_ptr<Buffer> Ref; + + static size_t audio_buffer_size(SampleCount nframes); + size_t default_buffer_size(PortType type); + + Ref get(PortType type, size_t size=0, bool force_create=false); + + Ref silent_buffer() { return _silent_buffer; } + + void set_block_length(SampleCount block_length); + + Ingen::Shared::LV2URIMap& uris() { assert(_uris); return *_uris.get(); } + +private: + friend class Buffer; + void recycle(Buffer* buf); + + Ref create(PortType type, size_t size=0); + + inline Raul::AtomicPtr<Buffer>& free_list(PortType type) { + switch (type.symbol()) { + case PortType::AUDIO: return _free_audio; + case PortType::CONTROL: return _free_control; + case PortType::EVENTS: return _free_event; + case PortType::VALUE: + case PortType::MESSAGE: return _free_object; + default: throw; + } + } + + void free_list(Buffer* head); + + Raul::AtomicPtr<Buffer> _free_audio; + Raul::AtomicPtr<Buffer> _free_control; + Raul::AtomicPtr<Buffer> _free_event; + Raul::AtomicPtr<Buffer> _free_object; + + Glib::Mutex _mutex; + Engine& _engine; + SharedPtr<Ingen::Shared::LV2URIMap> _uris; + + Ref _silent_buffer; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_BUFFERFACTORY_HPP diff --git a/src/server/ClientBroadcaster.cpp b/src/server/ClientBroadcaster.cpp new file mode 100644 index 00000000..2931497a --- /dev/null +++ b/src/server/ClientBroadcaster.cpp @@ -0,0 +1,116 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <cassert> +#include <unistd.h> +#include "raul/log.hpp" +#include "ingen/ClientInterface.hpp" +#include "ClientBroadcaster.hpp" +#include "PluginImpl.hpp" +#include "ConnectionImpl.hpp" +#include "EngineStore.hpp" +#include "ObjectSender.hpp" +#include "util.hpp" + +#define LOG(s) s << "[ClientBroadcaster] " + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { + +/** Register a client to receive messages over the notification band. + */ +void +ClientBroadcaster::register_client(const URI& uri, ClientInterface* client) +{ + Clients::iterator i = _clients.find(uri); + + if (i == _clients.end()) { + _clients[uri] = client; + LOG(info) << "Registered client: " << uri << endl; + } else { + LOG(warn) << "Client already registered: " << uri << endl; + } +} + +/** Remove a client from the list of registered clients. + * + * @return true if client was found and removed. + */ +bool +ClientBroadcaster::unregister_client(const URI& uri) +{ + size_t erased = _clients.erase(uri); + + if (erased > 0) + LOG(info) << "Unregistered client: " << uri << endl; + else + LOG(warn) << "Failed to find client to unregister: " << uri << endl; + + return (erased > 0); +} + +/** Looks up the client with the given source @a uri (which is used as the + * unique identifier for registered clients). + */ +ClientInterface* +ClientBroadcaster::client(const URI& uri) +{ + Clients::iterator i = _clients.find(uri); + if (i != _clients.end()) { + return (*i).second; + } else { + return NULL; + } +} + +void +ClientBroadcaster::send_plugins(const NodeFactory::Plugins& plugins) +{ + for (Clients::const_iterator c = _clients.begin(); c != _clients.end(); ++c) + send_plugins_to((*c).second, plugins); +} + +void +ClientBroadcaster::send_plugins_to(ClientInterface* client, const NodeFactory::Plugins& plugins) +{ + client->bundle_begin(); + + for (NodeFactory::Plugins::const_iterator i = plugins.begin(); i != plugins.end(); ++i) { + const PluginImpl* const plugin = i->second; + client->put(plugin->uri(), plugin->properties()); + } + + client->bundle_end(); +} + +/** Send an object to all clients. + * + * @param o Object to send + * @param recursive If true send all children of object + */ +void +ClientBroadcaster::send_object(const GraphObjectImpl* o, bool recursive) +{ + for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + ObjectSender::send_object((*i).second, o, recursive); +} + +} // namespace Server +} // namespace Ingen diff --git a/src/server/ClientBroadcaster.hpp b/src/server/ClientBroadcaster.hpp new file mode 100644 index 00000000..5db6961d --- /dev/null +++ b/src/server/ClientBroadcaster.hpp @@ -0,0 +1,131 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_CLIENTBROADCASTER_HPP +#define INGEN_ENGINE_CLIENTBROADCASTER_HPP + +#include <string> +#include <list> +#include <map> +#include <pthread.h> +#include "raul/SharedPtr.hpp" +#include "ingen/ClientInterface.hpp" +#include "NodeFactory.hpp" + +using namespace std; + +namespace Ingen { +namespace Server { + +class GraphObjectImpl; +class NodeImpl; +class PortImpl; +class PluginImpl; +class PatchImpl; +class ConnectionImpl; + +/** Broadcaster for all clients. + * + * This is a ClientInterface that forwards all messages to all registered + * clients (for updating all clients on state changes in the engine). + * + * \ingroup engine + */ +class ClientBroadcaster : public ClientInterface +{ +public: + void register_client(const Raul::URI& uri, ClientInterface* client); + bool unregister_client(const Raul::URI& uri); + + ClientInterface* client(const Raul::URI& uri); + + void send_plugins(const NodeFactory::Plugins& plugin_list); + void send_plugins_to(ClientInterface*, const NodeFactory::Plugins& plugin_list); + + void send_object(const GraphObjectImpl* p, bool recursive); + +#define BROADCAST(msg, ...) \ + for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i) \ + (*i).second->msg(__VA_ARGS__) + + // CommonInterface + + void bundle_begin() { BROADCAST(bundle_begin); } + void bundle_end() { BROADCAST(bundle_end); } + + void put(const Raul::URI& uri, + const Resource::Properties& properties, + Resource::Graph ctx=Resource::DEFAULT) { + BROADCAST(put, uri, properties); + } + + void delta(const Raul::URI& uri, + const Resource::Properties& remove, + const Resource::Properties& add) { + BROADCAST(delta, uri, remove, add); + } + + void move(const Raul::Path& old_path, + const Raul::Path& new_path) { + BROADCAST(move, old_path, new_path); + } + + void del(const Raul::URI& uri) { + BROADCAST(del, uri); + } + + void connect(const Raul::Path& src_port_path, + const Raul::Path& dst_port_path) { + BROADCAST(connect, src_port_path, dst_port_path); + } + + void disconnect(const Raul::URI& src, + const Raul::URI& dst) { + BROADCAST(disconnect, src, dst); + } + + void disconnect_all(const Raul::Path& parent_patch_path, + const Raul::Path& path) { + BROADCAST(disconnect_all, parent_patch_path, path); + } + + void set_property(const Raul::URI& subject, + const Raul::URI& predicate, + const Raul::Atom& value) { + BROADCAST(set_property, subject, predicate, value); + } + + // ClientInterface + + Raul::URI uri() const { return "http://drobilla.net/ns/ingen#broadcaster"; } ///< N/A + + void response_ok(int32_t id) {} ///< N/A + void response_error(int32_t id, const std::string& msg) {} ///< N/A + + void error(const std::string& msg) { BROADCAST(error, msg); } + void activity(const Raul::Path& path) { BROADCAST(activity, path); } + +private: + typedef std::map<Raul::URI, ClientInterface*> Clients; + Clients _clients; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_CLIENTBROADCASTER_HPP + diff --git a/src/server/CompiledPatch.hpp b/src/server/CompiledPatch.hpp new file mode 100644 index 00000000..65ee9fad --- /dev/null +++ b/src/server/CompiledPatch.hpp @@ -0,0 +1,76 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_COMPILEDPATCH_HPP +#define INGEN_ENGINE_COMPILEDPATCH_HPP + +#include <vector> +#include "raul/List.hpp" +#include "raul/Deletable.hpp" +#include <boost/utility.hpp> + +namespace Ingen { +namespace Server { + +class ConnectionImpl; + +/** All information required about a node to execute it in an audio thread. + */ +struct CompiledNode { + CompiledNode(NodeImpl* n, size_t np, Raul::List<NodeImpl*>* d) + : _node(n), _n_providers(np) + { + // Copy to a vector for maximum iteration speed and cache optimization + // (Need to take a copy anyway) + + _dependants.reserve(d->size()); + for (Raul::List<NodeImpl*>::iterator i = d->begin(); i != d->end(); ++i) + _dependants.push_back(*i); + } + + NodeImpl* node() const { return _node; } + size_t n_providers() const { return _n_providers; } + const std::vector<NodeImpl*>& dependants() const { return _dependants; } + +private: + NodeImpl* _node; + size_t _n_providers; ///< Number of input ready signals to trigger run + std::vector<NodeImpl*> _dependants; ///< Nodes this one's output ports are connected to +}; + +/** A patch ``compiled'' into a flat structure with the correct order so + * the audio thread(s) can execute it without threading problems (since + * the preprocessor thread modifies the graph). + * + * The nodes contained here are sorted in the order they must be executed. + * The parallel processing algorithm guarantees no node will be executed + * before its providers, using this order as well as semaphores. + */ +struct CompiledPatch : public std::vector<CompiledNode> + , public Raul::Deletable + , public boost::noncopyable +{ + typedef std::vector<ConnectionImpl*> QueuedConnections; + + /** All (audio context => other context) connections */ + std::vector<ConnectionImpl*> queued_connections; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_COMPILEDPATCH_HPP diff --git a/src/server/ConnectionImpl.cpp b/src/server/ConnectionImpl.cpp new file mode 100644 index 00000000..1e339910 --- /dev/null +++ b/src/server/ConnectionImpl.cpp @@ -0,0 +1,153 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <algorithm> +#include "raul/log.hpp" +#include "raul/Maid.hpp" +#include "raul/IntrusivePtr.hpp" +#include "shared/LV2URIMap.hpp" +#include "AudioBuffer.hpp" +#include "BufferFactory.hpp" +#include "ConnectionImpl.hpp" +#include "Engine.hpp" +#include "EventBuffer.hpp" +#include "InputPort.hpp" +#include "MessageContext.hpp" +#include "OutputPort.hpp" +#include "PortImpl.hpp" +#include "ProcessContext.hpp" +#include "mix.hpp" +#include "util.hpp" + +namespace Ingen { +namespace Server { + +/** Constructor for a connection from a node's output port. + * + * This handles both polyphonic and monophonic nodes, transparently to the + * user (InputPort). + */ +ConnectionImpl::ConnectionImpl(BufferFactory& bufs, PortImpl* src_port, PortImpl* dst_port) + : _queue(NULL) + , _bufs(bufs) + , _src_port(src_port) + , _dst_port(dst_port) + , _pending_disconnection(false) +{ + assert(src_port); + assert(dst_port); + assert(src_port != dst_port); + assert(src_port->path() != dst_port->path()); + + if (must_queue()) + _queue = new Raul::RingBuffer(src_port->buffer_size() * 2); +} + +void +ConnectionImpl::dump() const +{ + debug << _src_port->path() << " -> " << _dst_port->path() + << (must_mix() ? " (MIX) " : " (DIRECT) ") + << (must_queue() ? " (QUEUE)" : " (NOQUEUE) ") + << "POLY: " << _src_port->poly() << " => " << _dst_port->poly() << endl; +} + +void +ConnectionImpl::get_sources(Context& context, uint32_t voice, + IntrusivePtr<Buffer>* srcs, uint32_t max_num_srcs, uint32_t& num_srcs) +{ + if (must_queue() && _queue->read_space() > 0) { + LV2_Atom obj; + _queue->peek(sizeof(LV2_Atom), &obj); + IntrusivePtr<Buffer> buf = context.engine().buffer_factory()->get( + dst_port()->buffer_type(), sizeof(LV2_Atom) + obj.size); + void* data = buf->port_data(PortType::MESSAGE, context.offset()); + _queue->full_read(sizeof(LV2_Atom) + obj.size, (LV2_Atom*)data); + srcs[num_srcs++] = buf; + } else if (must_mix()) { + // Mixing down voices: every src voice mixed into every dst voice + for (uint32_t v = 0; v < _src_port->poly(); ++v) { + assert(num_srcs < max_num_srcs); + srcs[num_srcs++] = _src_port->buffer(v).get(); + } + } else { + // Matching polyphony: each src voice mixed into corresponding dst voice + assert(_src_port->poly() == _dst_port->poly()); + assert(num_srcs < max_num_srcs); + srcs[num_srcs++] = _src_port->buffer(voice).get(); + } +} + +void +ConnectionImpl::queue(Context& context) +{ + if (!must_queue()) + return; + + IntrusivePtr<EventBuffer> src_buf = PtrCast<EventBuffer>(_src_port->buffer(0)); + if (!src_buf) { + error << "Queued connection but source is not an EventBuffer" << endl; + return; + } + + for (src_buf->rewind(); src_buf->is_valid(); src_buf->increment()) { + LV2_Event* ev = src_buf->get_event(); + LV2_Atom* obj = LV2_ATOM_FROM_EVENT(ev); + /*debug << _src_port->path() << " -> " << _dst_port->path() + << " QUEUE OBJECT TYPE " << obj->type << ":"; + for (size_t i = 0; i < obj->size; ++i) + debug << " " << std::hex << (int)obj->body[i]; + debug << endl;*/ + + _queue->write(sizeof(LV2_Atom) + obj->size, obj); + context.engine().message_context()->run(_dst_port->parent_node(), context.start() + ev->frames); + } +} + +bool +ConnectionImpl::can_connect(const OutputPort* src, const InputPort* dst) +{ + const Ingen::Shared::LV2URIMap& uris = src->bufs().uris(); + return ( + // (Audio | Control) => (Audio | Control) + ( (src->is_a(PortType::CONTROL) || src->is_a(PortType::AUDIO)) + && (dst->is_a(PortType::CONTROL) || dst->is_a(PortType::AUDIO))) + + // (Events | Message) => (Events | Message) + || ( (src->is_a(PortType::EVENTS) || src->is_a(PortType::MESSAGE)) + && (dst->is_a(PortType::EVENTS) || dst->is_a(PortType::MESSAGE))) + + // (Message | Value) => (Message | Value) + || ( (src->is_a(PortType::MESSAGE) || src->is_a(PortType::VALUE)) + && (dst->is_a(PortType::MESSAGE) || dst->is_a(PortType::VALUE))) + + // Control => atom:Float32 Value + || (src->is_a(PortType::CONTROL) && dst->supports(uris.atom_Float32)) + + // Audio => atom:Vector Value + || (src->is_a(PortType::AUDIO) && dst->supports(uris.atom_Vector)) + + // atom:Float32 Value => Control + || (src->supports(uris.atom_Float32) && dst->is_a(PortType::CONTROL)) + + // atom:Vector Value => Audio + || (src->supports(uris.atom_Vector) && dst->is_a(PortType::AUDIO))); +} + +} // namespace Server +} // namespace Ingen + diff --git a/src/server/ConnectionImpl.hpp b/src/server/ConnectionImpl.hpp new file mode 100644 index 00000000..ce30eaa4 --- /dev/null +++ b/src/server/ConnectionImpl.hpp @@ -0,0 +1,110 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_CONNECTIONIMPL_HPP +#define INGEN_ENGINE_CONNECTIONIMPL_HPP + +#include <cstdlib> +#include <boost/utility.hpp> +#include "raul/log.hpp" +#include "raul/Deletable.hpp" +#include "raul/IntrusivePtr.hpp" +#include "ingen/PortType.hpp" +#include "ingen/Connection.hpp" +#include "lv2/lv2plug.in/ns/ext/atom/atom.h" +#include "PortImpl.hpp" + +using namespace std; + +namespace Ingen { +namespace Server { + +class PortImpl; +class OutputPort; +class InputPort; +class Buffer; +class BufferFactory; + +/** Represents a single inbound connection for an InputPort. + * + * This can be a group of ports (ie coming from a polyphonic Node) or + * a single Port. This class exists basically as an abstraction of mixing + * down polyphonic inputs, so InputPort can just deal with mixing down + * multiple connections (oblivious to the polyphonic situation of the + * connection itself). + * + * \ingroup engine + */ +class ConnectionImpl : public Raul::Deletable, public Connection +{ +public: + ConnectionImpl(BufferFactory& bufs, PortImpl* src_port, PortImpl* dst_port); + + PortImpl* src_port() const { return _src_port; } + PortImpl* dst_port() const { return _dst_port; } + + const Raul::Path src_port_path() const { return _src_port->path(); } + const Raul::Path dst_port_path() const { return _dst_port->path(); } + + /** Used by some (recursive) events to prevent double disconnections */ + bool pending_disconnection() { return _pending_disconnection; } + void pending_disconnection(bool b) { _pending_disconnection = b; } + + void queue(Context& context); + + void get_sources(Context& context, uint32_t voice, + IntrusivePtr<Buffer>* srcs, uint32_t max_num_srcs, uint32_t& num_srcs); + + /** Get the buffer for a particular voice. + * A Connection is smart - it knows the destination port requesting the + * buffer, and will return accordingly (e.g. the same buffer for every + * voice in a mono->poly connection). + */ + inline BufferFactory::Ref buffer(uint32_t voice) const { + assert(!must_mix()); + assert(!must_queue()); + assert(_src_port->poly() == 1 || _src_port->poly() > voice); + if (_src_port->poly() == 1) { + return _src_port->buffer(0); + } else { + return _src_port->buffer(voice); + } + } + + /** Returns true if this connection must mix down voices into a local buffer */ + inline bool must_mix() const { return _src_port->poly() > _dst_port->poly(); } + + /** Returns true if this connection crosses contexts and must buffer */ + inline bool must_queue() const { return _src_port->context() != _dst_port->context(); } + + static bool can_connect(const OutputPort* src, const InputPort* dst); + +protected: + void dump() const; + + Raul::RingBuffer* _queue; + + BufferFactory& _bufs; + PortImpl* const _src_port; + PortImpl* const _dst_port; + bool _pending_disconnection; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_CONNECTIONIMPL_HPP diff --git a/src/server/Context.hpp b/src/server/Context.hpp new file mode 100644 index 00000000..d2d1b11f --- /dev/null +++ b/src/server/Context.hpp @@ -0,0 +1,107 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_CONTEXT_HPP +#define INGEN_ENGINE_CONTEXT_HPP + +#include "shared/World.hpp" + +#include "Engine.hpp" +#include "EventSink.hpp" +#include "types.hpp" + +namespace Ingen { +namespace Server { + +class Engine; + +/** Graph execution context. + * + * This is used to pass whatever information a GraphObject might need to + * process; such as the current time, a sink for generated events, etc. + * + * Note the logical distinction between nframes (jack relative) and start/end + * (timeline relative). If transport speed != 1.0, then end-start != nframes + * (though currently this is never the case, it may be if ingen incorporates + * tempo and varispeed). + * + * \ingroup engine + */ +class Context +{ +public: + enum ID { + AUDIO, + MESSAGE + }; + + Context(Engine& engine, ID id) + : _engine(engine) + , _id(id) + , _event_sink(engine, engine.event_queue_size()) + , _start(0) + , _end(0) + , _nframes(0) + , _offset(0) + , _realtime(true) + {} + + virtual ~Context() {} + + ID id() const { return _id; } + + void locate(FrameTime s, SampleCount nframes, SampleCount offset) { + _start = s; + _end = s + nframes; + _nframes = nframes; + _offset = offset; + } + + void locate(const Context& other) { + _start = other._start; + _end = other._end; + _nframes = other._nframes; + _offset = other._offset; + } + + inline Engine& engine() const { return _engine; } + inline FrameTime start() const { return _start; } + inline FrameTime end() const { return _end; } + inline SampleCount nframes() const { return _nframes; } + inline SampleCount offset() const { return _offset; } + inline bool realtime() const { return _realtime; } + + inline const EventSink& event_sink() const { return _event_sink; } + inline EventSink& event_sink() { return _event_sink; } + +protected: + Engine& _engine; ///< Engine we're running in + ID _id; ///< Fast ID for this context + + EventSink _event_sink; ///< Sink for events generated in a realtime context + FrameTime _start; ///< Start frame of this cycle, timeline relative + FrameTime _end; ///< End frame of this cycle, timeline relative + SampleCount _nframes; ///< Length of this cycle in frames + SampleCount _offset; ///< Start offset relative to start of driver buffers + bool _realtime; ///< True iff context is hard realtime +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_CONTEXT_HPP + diff --git a/src/server/ControlBindings.cpp b/src/server/ControlBindings.cpp new file mode 100644 index 00000000..6c2cf09c --- /dev/null +++ b/src/server/ControlBindings.cpp @@ -0,0 +1,384 @@ +/* This file is part of Ingen. + * Copyright 2009-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <math.h> +#include "raul/log.hpp" +#include "raul/midi_events.h" +#include "shared/LV2URIMap.hpp" +#include "shared/World.hpp" +#include "events/SendPortValue.hpp" +#include "events/SendBinding.hpp" +#include "AudioBuffer.hpp" +#include "ControlBindings.hpp" +#include "Engine.hpp" +#include "EventBuffer.hpp" +#include "PortImpl.hpp" +#include "ProcessContext.hpp" +#include "ThreadManager.hpp" + +#define LOG(s) s << "[ControlBindings] " + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { + +ControlBindings::ControlBindings(Engine& engine) + : _engine(engine) + , _learn_port(NULL) + , _bindings(new Bindings()) + , _feedback(new EventBuffer(*_engine.buffer_factory(), 1024)) // FIXME: size +{ +} + +ControlBindings::~ControlBindings() +{ + delete _feedback; +} + +ControlBindings::Key +ControlBindings::port_binding(PortImpl* port) +{ + const Ingen::Shared::LV2URIMap& uris = *_engine.world()->uris().get(); + const Raul::Atom& binding = port->get_property(uris.ingen_controlBinding); + Key key; + if (binding.type() == Atom::DICT) { + const Atom::DictValue& dict = binding.get_dict(); + Atom::DictValue::const_iterator t = dict.find(uris.rdf_type); + Atom::DictValue::const_iterator n; + if (t == dict.end()) { + return key; + } else if (t->second == uris.midi_Bender) { + key = Key(MIDI_BENDER); + } else if (t->second == uris.midi_ChannelPressure) { + key = Key(MIDI_CHANNEL_PRESSURE); + } else if (t->second == uris.midi_Controller) { + if ((n = dict.find(uris.midi_controllerNumber)) != dict.end()) + key = Key(MIDI_CC, n->second.get_int32()); + } else if (t->second == uris.midi_Note) { + if ((n = dict.find(uris.midi_noteNumber)) != dict.end()) + key = Key(MIDI_NOTE, n->second.get_int32()); + } + } + return key; +} + +ControlBindings::Key +ControlBindings::midi_event_key(uint16_t size, uint8_t* buf, uint16_t& value) +{ + switch (buf[0] & 0xF0) { + case MIDI_CMD_CONTROL: + value = static_cast<const int8_t>(buf[2]); + return Key(MIDI_CC, static_cast<const int8_t>(buf[1])); + case MIDI_CMD_BENDER: + value = (static_cast<int8_t>(buf[2]) << 7) + static_cast<int8_t>(buf[1]); + return Key(MIDI_BENDER); + case MIDI_CMD_CHANNEL_PRESSURE: + value = static_cast<const int8_t>(buf[1]); + return Key(MIDI_CHANNEL_PRESSURE); + case MIDI_CMD_NOTE_ON: + value = 1.0f; + return Key(MIDI_NOTE, static_cast<const int8_t>(buf[1])); + default: + return Key(); + } +} + +void +ControlBindings::port_binding_changed(ProcessContext& context, PortImpl* port) +{ + Key key = port_binding(port); + if (key) + _bindings->insert(make_pair(key, port)); +} + +void +ControlBindings::port_value_changed(ProcessContext& context, PortImpl* port) +{ + const Ingen::Shared::LV2URIMap& uris = *_engine.world()->uris().get(); + Key key = port_binding(port); + if (key) { + int16_t value = port_value_to_control(port, key.type); + uint16_t size = 0; + uint8_t buf[4]; + switch (key.type) { + case MIDI_CC: + size = 3; + buf[0] = MIDI_CMD_CONTROL; + buf[1] = key.num; + buf[2] = static_cast<int8_t>(value); + break; + case MIDI_CHANNEL_PRESSURE: + size = 2; + buf[0] = MIDI_CMD_CHANNEL_PRESSURE; + buf[1] = static_cast<int8_t>(value); + break; + case MIDI_BENDER: + size = 3; + buf[0] = MIDI_CMD_BENDER; + buf[1] = (value & 0x007F); + buf[2] = (value & 0x7F00) >> 7; + break; + case MIDI_NOTE: + size = 3; + if (value == 1) + buf[0] = MIDI_CMD_NOTE_ON; + else if (value == 0) + buf[0] = MIDI_CMD_NOTE_OFF; + buf[1] = key.num; + buf[2] = 0x64; // MIDI spec default + break; + default: + break; + } + if (size > 0) { + _feedback->append(0, 0, + uris.global_to_event(uris.midi_MidiEvent.id).second, + size, buf); + } + } +} + +void +ControlBindings::learn(PortImpl* port) +{ + ThreadManager::assert_thread(THREAD_PRE_PROCESS); + _learn_port = port; +} + +Raul::Atom +ControlBindings::control_to_port_value(PortImpl* port, Type type, int16_t value) +{ + const Ingen::Shared::LV2URIMap& uris = *_engine.world()->uris().get(); + + // TODO: cache these to avoid the lookup + float min = port->get_property(uris.lv2_minimum).get_float(); + float max = port->get_property(uris.lv2_maximum).get_float(); + bool toggled = port->has_property(uris.lv2_portProperty, uris.lv2_toggled); + + float normal = 0.0f; + switch (type) { + case MIDI_CC: + case MIDI_CHANNEL_PRESSURE: + normal = (float)value / 127.0f; + break; + case MIDI_BENDER: + normal = (float)value / 16383.0f; + break; + case MIDI_NOTE: + normal = (value == 0.0f) ? 0.0f : 1.0f; + break; + default: + break; + } + + float scaled_value = normal * (max - min) + min; + if (toggled) + scaled_value = (scaled_value < 0.5) ? 0.0 : 1.0; + + return Raul::Atom(scaled_value); +} + +int16_t +ControlBindings::port_value_to_control(PortImpl* port, Type type) +{ + if (port->value().type() != Atom::FLOAT) + return 0; + + const Ingen::Shared::LV2URIMap& uris = *_engine.world()->uris().get(); + + // TODO: cache these to avoid the lookup + float min = port->get_property(uris.lv2_minimum).get_float(); + float max = port->get_property(uris.lv2_maximum).get_float(); + //bool toggled = port->has_property(uris.lv2_portProperty, uris.lv2_toggled); + float value = port->value().get_float(); + float normal = (value - min) / (max - min); + + if (normal < 0.0f) { + warn << "Value " << value << " (normal " << normal << ") for " + << port->path() << " out of range" << endl; + normal = 0.0f; + } + + if (normal > 1.0f) { + warn << "Value " << value << " (normal " << normal << ") for " + << port->path() << " out of range" << endl; + normal = 1.0f; + } + + switch (type) { + case MIDI_CC: + case MIDI_CHANNEL_PRESSURE: + return lrintf(normal * 127.0f); + case MIDI_BENDER: + return lrintf(normal * 16383.0f); + case MIDI_NOTE: + return (value > 0.0f) ? 1 : 0; + default: + return 0; + } +} + +void +ControlBindings::set_port_value(ProcessContext& context, PortImpl* port, Type type, int16_t value) +{ + const Raul::Atom port_value(control_to_port_value(port, type, value)); + port->set_value(port_value); + + assert(port_value.type() == Atom::FLOAT); + assert(dynamic_cast<AudioBuffer*>(port->buffer(0).get())); + + for (uint32_t v = 0; v < port->poly(); ++v) + reinterpret_cast<AudioBuffer*>(port->buffer(v).get())->set_value( + port_value.get_float(), context.start(), context.start()); + + const Events::SendPortValue ev(context.engine(), context.start(), port, true, 0, port_value); + context.event_sink().write(sizeof(ev), &ev); +} + +bool +ControlBindings::bind(ProcessContext& context, Key key) +{ + const Ingen::Shared::LV2URIMap& uris = *context.engine().world()->uris().get(); + assert(_learn_port); + if (key.type == MIDI_NOTE) { + bool toggled = _learn_port->has_property(uris.lv2_portProperty, uris.lv2_toggled); + if (!toggled) + return false; + } + + _bindings->insert(make_pair(key, _learn_port)); + + const Events::SendBinding ev(context.engine(), context.start(), _learn_port, key.type, key.num); + context.event_sink().write(sizeof(ev), &ev); + + _learn_port = NULL; + return true; +} + +SharedPtr<ControlBindings::Bindings> +ControlBindings::remove(const Raul::Path& path) +{ + ThreadManager::assert_thread(THREAD_PRE_PROCESS); + + SharedPtr<Bindings> old_bindings(_bindings); + SharedPtr<Bindings> copy(new Bindings(*_bindings.get())); + + for (Bindings::iterator i = copy->begin(); i != copy->end();) { + Bindings::iterator next = i; + ++next; + + if (i->second->path() == path || i->second->path().is_child_of(path)) + copy->erase(i); + + i = next; + } + + _bindings = copy; + return old_bindings; +} + +SharedPtr<ControlBindings::Bindings> +ControlBindings::remove(PortImpl* port) +{ + ThreadManager::assert_thread(THREAD_PRE_PROCESS); + + SharedPtr<Bindings> old_bindings(_bindings); + SharedPtr<Bindings> copy(new Bindings(*_bindings.get())); + + for (Bindings::iterator i = copy->begin(); i != copy->end();) { + Bindings::iterator next = i; + ++next; + + if (i->second == port) + copy->erase(i); + + i = next; + } + + _bindings = copy; + return old_bindings; +} + +void +ControlBindings::pre_process(ProcessContext& context, EventBuffer* buffer) +{ + uint32_t frames = 0; + uint32_t subframes = 0; + uint16_t type = 0; + uint16_t size = 0; + uint8_t* buf = NULL; + uint16_t value = 0; + + SharedPtr<Bindings> bindings = _bindings; + _feedback->clear(); + + const Ingen::Shared::LV2URIMap& uris = *context.engine().world()->uris().get(); + + // TODO: cache + const uint32_t midi_event_type = uris.global_to_event(uris.midi_MidiEvent.id).second; + + // Learn from input if necessary + if (_learn_port) { + for (buffer->rewind(); + buffer->get_event(&frames, &subframes, &type, &size, &buf); + buffer->increment()) { + if (type != midi_event_type) + continue; + + const Key key = midi_event_key(size, buf, value); + if (key && bind(context, key)) + break; + } + } + + // If bindings are empty, no sense reading input + if (bindings->empty()) + return; + + // Read input and apply control values + for (buffer->rewind(); + buffer->get_event(&frames, &subframes, &type, &size, &buf); + buffer->increment()) { + if (type != midi_event_type) + continue; + + const Key key = midi_event_key(size, buf, value); + if (!key) + continue; + + Bindings::const_iterator i = bindings->find(key); + if (i == bindings->end()) + continue; + + set_port_value(context, i->second, key.type, value); + } +} + +void +ControlBindings::post_process(ProcessContext& context, EventBuffer* buffer) +{ + if (_feedback->event_count() > 0) { + // TODO: merge buffer's existing contents (anything send to it in the patch) + _feedback->rewind(); + buffer->copy(context, _feedback); + } +} + +} // namespace Server +} // namespace Ingen diff --git a/src/server/ControlBindings.hpp b/src/server/ControlBindings.hpp new file mode 100644 index 00000000..92eee7c7 --- /dev/null +++ b/src/server/ControlBindings.hpp @@ -0,0 +1,102 @@ +/* This file is part of Ingen. + * Copyright 2009-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_CONTROLBINDINGS_HPP +#define INGEN_ENGINE_CONTROLBINDINGS_HPP + +#include <stdint.h> +#include <map> +#include "raul/SharedPtr.hpp" +#include "raul/Path.hpp" +#include "shared/LV2URIMap.hpp" +#include "BufferFactory.hpp" + +namespace Ingen { +namespace Server { + +class Engine; +class ProcessContext; +class EventBuffer; +class PortImpl; + +class ControlBindings { +public: + enum Type { + NULL_CONTROL, + MIDI_BENDER, + MIDI_CC, + MIDI_RPN, + MIDI_NRPN, + MIDI_CHANNEL_PRESSURE, + MIDI_NOTE + }; + + struct Key { + Key(Type t=NULL_CONTROL, int16_t n=0) : type(t), num(n) {} + inline bool operator<(const Key& other) const { + return (type == other.type) ? (num < other.num) : (type < other.type); + } + inline operator bool() const { return type != NULL_CONTROL; } + Type type; + int16_t num; + }; + + typedef std::map<Key, PortImpl*> Bindings; + + explicit ControlBindings(Engine& engine); + ~ControlBindings(); + + void learn(PortImpl* port); + + void port_binding_changed(ProcessContext& context, PortImpl* port); + void port_value_changed(ProcessContext& context, PortImpl* port); + void pre_process(ProcessContext& context, EventBuffer* control_in); + void post_process(ProcessContext& context, EventBuffer* control_out); + + /** Remove all bindings for @a path or children of @a path. + * The caller must safely drop the returned reference in the + * post-processing thread after at least one process thread has run. + */ + SharedPtr<Bindings> remove(const Raul::Path& path); + + /** Remove binding for a particular port. + * The caller must safely drop the returned reference in the + * post-processing thread after at least one process thread has run. + */ + SharedPtr<Bindings> remove(PortImpl* port); + +private: + Key port_binding(PortImpl* port); + Key midi_event_key(uint16_t size, uint8_t* buf, uint16_t& value); + + void set_port_value(ProcessContext& context, PortImpl* port, Type type, int16_t value); + bool bind(ProcessContext& context, Key key); + + Raul::Atom control_to_port_value(PortImpl* port, Type type, int16_t value); + int16_t port_value_to_control(PortImpl* port, Type type); + + Engine& _engine; + PortImpl* _learn_port; + + SharedPtr<Bindings> _bindings; + EventBuffer* _feedback; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_CONTROLBINDINGS_HPP diff --git a/src/server/Driver.hpp b/src/server/Driver.hpp new file mode 100644 index 00000000..f9416e3e --- /dev/null +++ b/src/server/Driver.hpp @@ -0,0 +1,123 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_DRIVER_HPP +#define INGEN_ENGINE_DRIVER_HPP + +#include <string> +#include <boost/utility.hpp> +#include "raul/Deletable.hpp" +#include "ingen/PortType.hpp" +#include "ingen/EventType.hpp" +#include "DuplexPort.hpp" + +namespace Raul { class Path; } + +namespace Ingen { +namespace Server { + +class DuplexPort; +class ProcessContext; + +/** Representation of a "system" (eg outside Ingen) port. + * + * This is the class through which the rest of the engine manages everything + * related to driver ports. Derived classes are expected to have a pointer to + * their driver (to be able to perform the operation necessary). + * + * \ingroup engine + */ +class DriverPort : boost::noncopyable, public Raul::Deletable { +public: + virtual ~DriverPort() {} + + /** Set the name of the system port according to new path */ + virtual void move(const Raul::Path& path) = 0; + + /** Create system port */ + virtual void create() = 0; + + /** Destroy system port */ + virtual void destroy() = 0; + + bool is_input() const { return _patch_port->is_input(); } + DuplexPort* patch_port() const { return _patch_port; } + +protected: + explicit DriverPort(DuplexPort* port) : _patch_port(port) {} + + DuplexPort* _patch_port; +}; + +/** Driver abstract base class. + * + * A Driver is, from the perspective of GraphObjects (nodes, patches, ports) an + * interface for managing system ports. An implementation of Driver basically + * needs to manage DriverPorts, and handle writing/reading data to/from them. + * + * \ingroup engine + */ +class Driver : boost::noncopyable { +public: + virtual ~Driver() {} + + /** Activate driver (begin processing graph and events). */ + virtual void activate() {} + + /** Deactivate driver (stop processing graph and events). */ + virtual void deactivate() {} + + /** Create a port ready to be inserted with add_input (non realtime). + * May return NULL if the Driver can not create the port for some reason. + */ + virtual DriverPort* create_port(DuplexPort* patch_port) = 0; + + /** Return the DriverPort for a particular path, iff one exists. */ + virtual DriverPort* driver_port(const Raul::Path& path) = 0; + + /** Add a system visible port (e.g. a port on the root patch). */ + virtual void add_port(DriverPort* port) = 0; + + /** Remove a system visible port. */ + virtual Raul::Deletable* remove_port(const Raul::Path& path, + DriverPort** port=NULL) = 0; + + /** Return true iff this driver supports the given type of I/O */ + virtual bool supports(PortType port_type, + EventType event_type) = 0; + + virtual void set_root_patch(PatchImpl* patch) = 0; + virtual PatchImpl* root_patch() = 0; + + /** Return the audio buffer size in frames */ + virtual SampleCount block_length() const = 0; + + /** Return the sample rate in Hz */ + virtual SampleCount sample_rate() const = 0; + + /** Return the current frame time (running counter) */ + virtual SampleCount frame_time() const = 0; + + virtual bool is_realtime() const = 0; + + virtual ProcessContext& context() = 0; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_DRIVER_HPP diff --git a/src/server/DuplexPort.cpp b/src/server/DuplexPort.cpp new file mode 100644 index 00000000..773aab37 --- /dev/null +++ b/src/server/DuplexPort.cpp @@ -0,0 +1,99 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <cstdlib> +#include <cassert> +#include <string> + +#include "shared/LV2URIMap.hpp" + +#include "ConnectionImpl.hpp" +#include "DuplexPort.hpp" +#include "EventBuffer.hpp" +#include "NodeImpl.hpp" +#include "OutputPort.hpp" +#include "ProcessContext.hpp" +#include "util.hpp" + +using namespace std; + +namespace Ingen { +namespace Server { + +DuplexPort::DuplexPort( + BufferFactory& bufs, + NodeImpl* parent, + const string& name, + uint32_t index, + bool polyphonic, + uint32_t poly, + PortType type, + const Raul::Atom& value, + size_t buffer_size, + bool is_output) + : PortImpl(bufs, parent, name, index, poly, type, value, buffer_size) + , InputPort(bufs, parent, name, index, poly, type, value, buffer_size) + , OutputPort(bufs, parent, name, index, poly, type, value, buffer_size) + , _is_output(is_output) +{ + assert(PortImpl::_parent == parent); + set_property(bufs.uris().ingen_polyphonic, polyphonic); +} + +bool +DuplexPort::get_buffers(BufferFactory& bufs, Raul::Array<BufferFactory::Ref>* buffers, uint32_t poly) +{ + if (_is_output) + return InputPort::get_buffers(bufs, buffers, poly); + else + return OutputPort::get_buffers(bufs, buffers, poly); +} + +/** Prepare for the execution of parent patch */ +void +DuplexPort::pre_process(Context& context) +{ + // If we're a patch output, we're an input from the internal perspective. + // Prepare buffers for write (so plugins can deliver to them) + if (_is_output) { + for (uint32_t v = 0; v < _poly; ++v) + _buffers->at(v)->prepare_write(context); + + // If we're a patch input, were an output from the internal perspective. + // Do whatever a normal node's input port does to prepare input for reading. + } else { + InputPort::pre_process(context); + } +} + +/** Finalize after the execution of parent patch (deliver outputs) */ +void +DuplexPort::post_process(Context& context) +{ + // If we're a patch output, we're an input from the internal perspective. + // Mix down input delivered by plugins so output (external perspective) is ready. + if (_is_output) { + InputPort::pre_process(context); + + if (_broadcast) + broadcast_value(context, false); + } +} + +} // namespace Server +} // namespace Ingen + diff --git a/src/server/DuplexPort.hpp b/src/server/DuplexPort.hpp new file mode 100644 index 00000000..ed00e072 --- /dev/null +++ b/src/server/DuplexPort.hpp @@ -0,0 +1,70 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_DUPLEXPORT_HPP +#define INGEN_ENGINE_DUPLEXPORT_HPP + +#include <string> +#include "Buffer.hpp" +#include "InputPort.hpp" +#include "OutputPort.hpp" + +namespace Ingen { +namespace Server { + +class NodeImpl; + +/** A duplex port (which is both an InputPort and an OutputPort) + * + * This is used for Patch ports, since they need to appear as both an input + * and an output port based on context. Eg. a patch output appears as an + * input inside the patch, so nodes inside the patch can feed it data. + * + * \ingroup engine + */ +class DuplexPort : public InputPort, public OutputPort +{ +public: + DuplexPort(BufferFactory& bufs, + NodeImpl* parent, + const std::string& name, + uint32_t index, + bool polyphonic, + uint32_t poly, + PortType type, + const Raul::Atom& value, + size_t buffer_size, + bool is_output); + + virtual ~DuplexPort() {} + + bool get_buffers(BufferFactory& bufs, Raul::Array<BufferFactory::Ref>* buffers, uint32_t poly); + + void pre_process(Context& context); + void post_process(Context& context); + + bool is_input() const { return !_is_output; } + bool is_output() const { return _is_output; } + +protected: + bool _is_output; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_DUPLEXPORT_HPP diff --git a/src/server/Engine.cpp b/src/server/Engine.cpp new file mode 100644 index 00000000..4a4a9c0f --- /dev/null +++ b/src/server/Engine.cpp @@ -0,0 +1,225 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <cassert> +#include <sys/mman.h> +#include <unistd.h> +#include "raul/log.hpp" +#include "raul/Deletable.hpp" +#include "raul/Maid.hpp" +#include "raul/SharedPtr.hpp" +#include "lv2/lv2plug.in/ns/ext/uri-map/uri-map.h" +#include "ingen/EventType.hpp" +#include "events/CreatePatch.hpp" +#include "events/CreatePort.hpp" +#include "shared/World.hpp" +#include "shared/LV2Features.hpp" +#include "shared/LV2URIMap.hpp" +#include "shared/Store.hpp" +#include "BufferFactory.hpp" +#include "ClientBroadcaster.hpp" +#include "ControlBindings.hpp" +#include "Driver.hpp" +#include "Engine.hpp" +#include "EngineStore.hpp" +#include "Event.hpp" +#include "EventSource.hpp" +#include "MessageContext.hpp" +#include "NodeFactory.hpp" +#include "PatchImpl.hpp" +#include "PostProcessor.hpp" +#include "ProcessContext.hpp" +#include "QueuedEngineInterface.hpp" +#include "ThreadManager.hpp" + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { + +bool ThreadManager::single_threaded = true; + +Engine::Engine(Ingen::Shared::World* a_world) + : _world(a_world) + , _broadcaster(new ClientBroadcaster()) + , _buffer_factory(new BufferFactory(*this, a_world->uris())) + , _control_bindings(new ControlBindings(*this)) + , _maid(new Raul::Maid(event_queue_size())) + , _message_context(new MessageContext(*this)) + , _node_factory(new NodeFactory(a_world)) + , _post_processor(new PostProcessor(*this, event_queue_size())) +{ + if (a_world->store()) { + assert(PtrCast<EngineStore>(a_world->store())); + } else { + a_world->set_store(SharedPtr<Ingen::Shared::Store>(new EngineStore())); + } +} + +Engine::~Engine() +{ + deactivate(); + + SharedPtr<EngineStore> store = engine_store(); + if (store) + for (EngineStore::iterator i = store->begin(); i != store->end(); ++i) + if ( ! PtrCast<GraphObjectImpl>(i->second)->parent() ) + i->second.reset(); + + delete _maid; + delete _post_processor; + delete _node_factory; + delete _broadcaster; + + munlockall(); +} + +SharedPtr<EngineStore> +Engine::engine_store() const +{ + return PtrCast<EngineStore>(_world->store()); +} + +size_t +Engine::event_queue_size() const +{ + return world()->conf()->option("queue-size").get_int32(); +} + +void +Engine::quit() +{ + _quit_flag = true; +} + +bool +Engine::main_iteration() +{ + _post_processor->process(); + _maid->cleanup(); + return !_quit_flag; +} + +void +Engine::add_event_source(SharedPtr<EventSource> source) +{ + _event_sources.insert(source); +} + +void +Engine::set_driver(SharedPtr<Driver> driver) +{ + _driver = driver; +} + +static void +execute_and_delete_event(ProcessContext& context, QueuedEvent* ev) +{ + ev->pre_process(); + ev->execute(context); + ev->post_process(); + delete ev; +} + +bool +Engine::activate() +{ + assert(_driver); + ThreadManager::single_threaded = true; + + _buffer_factory->set_block_length(_driver->block_length()); + + _message_context->Thread::start(); + + const Ingen::Shared::LV2URIMap& uris = *world()->uris().get(); + + // Create root patch + PatchImpl* root_patch = _driver->root_patch(); + if (!root_patch) { + root_patch = new PatchImpl(*this, "root", 1, NULL, _driver->sample_rate(), 1); + root_patch->set_property(uris.rdf_type, + Resource::Property(uris.ingen_Patch, Resource::INTERNAL)); + root_patch->set_property(uris.ingen_polyphony, + Resource::Property(Raul::Atom(int32_t(1)), + Resource::INTERNAL)); + root_patch->activate(*_buffer_factory); + _world->store()->add(root_patch); + root_patch->compiled_patch(root_patch->compile()); + _driver->set_root_patch(root_patch); + + ProcessContext context(*this); + + Resource::Properties control_properties; + control_properties.insert(make_pair(uris.lv2_name, "Control")); + control_properties.insert(make_pair(uris.rdf_type, uris.ev_EventPort)); + + // Add control input + Resource::Properties in_properties(control_properties); + in_properties.insert(make_pair(uris.rdf_type, uris.lv2_InputPort)); + in_properties.insert(make_pair(uris.lv2_index, 0)); + in_properties.insert(make_pair(uris.ingenui_canvas_x, + Resource::Property(32.0f, Resource::EXTERNAL))); + in_properties.insert(make_pair(uris.ingenui_canvas_y, + Resource::Property(32.0f, Resource::EXTERNAL))); + + execute_and_delete_event(context, new Events::CreatePort( + *this, SharedPtr<Request>(), 0, + "/control_in", uris.ev_EventPort, false, in_properties)); + + // Add control out + Resource::Properties out_properties(control_properties); + out_properties.insert(make_pair(uris.rdf_type, uris.lv2_OutputPort)); + out_properties.insert(make_pair(uris.lv2_index, 1)); + out_properties.insert(make_pair(uris.ingenui_canvas_x, + Resource::Property(128.0f, Resource::EXTERNAL))); + out_properties.insert(make_pair(uris.ingenui_canvas_y, + Resource::Property(32.0f, Resource::EXTERNAL))); + + execute_and_delete_event(context, new Events::CreatePort( + *this, SharedPtr<Request>(), 0, + "/control_out", uris.ev_EventPort, true, out_properties)); + } + + _driver->activate(); + root_patch->enable(); + + ThreadManager::single_threaded = false; + + return true; +} + +void +Engine::deactivate() +{ + _driver->deactivate(); + _driver->root_patch()->deactivate(); + + ThreadManager::single_threaded = true; +} + +void +Engine::process_events(ProcessContext& context) +{ + ThreadManager::assert_thread(THREAD_PROCESS); + + for (EventSources::iterator i = _event_sources.begin(); i != _event_sources.end(); ++i) + (*i)->process(*_post_processor, context); +} + +} // namespace Server +} // namespace Ingen diff --git a/src/server/Engine.hpp b/src/server/Engine.hpp new file mode 100644 index 00000000..b3cf6688 --- /dev/null +++ b/src/server/Engine.hpp @@ -0,0 +1,115 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_ENGINE_HPP +#define INGEN_ENGINE_ENGINE_HPP + +#include <set> +#include <vector> + +#include <boost/utility.hpp> + +#include "raul/SharedPtr.hpp" + +#include "ingen/EngineBase.hpp" + +namespace Raul { class Maid; } + +namespace Ingen { + +namespace Shared { class World; } + +namespace Server { + +class BufferFactory; +class ClientBroadcaster; +class ControlBindings; +class Driver; +class EngineStore; +class EventSource; +class MessageContext; +class NodeFactory; +class PostProcessor; +class ProcessContext; + +/** + The engine which executes the process graph. + + This is a simple class that provides pointers to the various components + that make up the engine implementation. In processes with a local engine, + it can be accessed via the Ingen::Shared::World. + + @ingroup engine +*/ +class Engine : public boost::noncopyable, public EngineBase +{ +public: + explicit Engine(Ingen::Shared::World* world); + + virtual ~Engine(); + + virtual bool activate(); + + virtual void deactivate(); + + virtual void quit(); + + virtual bool main_iteration(); + + void set_driver(SharedPtr<Driver> driver); + + void add_event_source(SharedPtr<EventSource> source); + + void process_events(ProcessContext& context); + + Ingen::Shared::World* world() const { return _world; } + + ClientBroadcaster* broadcaster() const { return _broadcaster; } + BufferFactory* buffer_factory() const { return _buffer_factory; } + ControlBindings* control_bindings() const { return _control_bindings; } + Driver* driver() const { return _driver.get(); } + Raul::Maid* maid() const { return _maid; } + MessageContext* message_context() const { return _message_context; } + NodeFactory* node_factory() const { return _node_factory; } + PostProcessor* post_processor() const { return _post_processor; } + + SharedPtr<EngineStore> engine_store() const; + + size_t event_queue_size() const; + +private: + Ingen::Shared::World* _world; + + ClientBroadcaster* _broadcaster; + BufferFactory* _buffer_factory; + ControlBindings* _control_bindings; + SharedPtr<Driver> _driver; + Raul::Maid* _maid; + MessageContext* _message_context; + NodeFactory* _node_factory; + PostProcessor* _post_processor; + + typedef std::set< SharedPtr<EventSource> > EventSources; + EventSources _event_sources; + + bool _quit_flag; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_ENGINE_HPP diff --git a/src/server/EngineStore.cpp b/src/server/EngineStore.cpp new file mode 100644 index 00000000..ee0dc933 --- /dev/null +++ b/src/server/EngineStore.cpp @@ -0,0 +1,160 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <utility> +#include <vector> +#include "raul/log.hpp" +#include "raul/List.hpp" +#include "raul/PathTable.hpp" +#include "raul/TableImpl.hpp" +#include "EngineStore.hpp" +#include "PatchImpl.hpp" +#include "NodeImpl.hpp" +#include "PortImpl.hpp" +#include "ThreadManager.hpp" + +#define LOG(s) s << "[EngineStore] " + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { + +/** Find the Patch at the given path. + */ +PatchImpl* +EngineStore::find_patch(const Path& path) +{ + GraphObjectImpl* const object = find_object(path); + return dynamic_cast<PatchImpl*>(object); +} + +/** Find the Node at the given path. + */ +NodeImpl* +EngineStore::find_node(const Path& path) +{ + GraphObjectImpl* const object = find_object(path); + return dynamic_cast<NodeImpl*>(object); +} + +/** Find the Port at the given path. + */ +PortImpl* +EngineStore::find_port(const Path& path) +{ + GraphObjectImpl* const object = find_object(path); + return dynamic_cast<PortImpl*>(object); +} + +/** Find the Object at the given path. + */ +GraphObjectImpl* +EngineStore::find_object(const Path& path) +{ + iterator i = find(path); + return ((i == end()) ? NULL : dynamic_cast<GraphObjectImpl*>(i->second.get())); +} + +/** Add an object to the store. Not realtime safe. + */ +void +EngineStore::add(GraphObject* obj) +{ + ThreadManager::assert_thread(THREAD_PRE_PROCESS); + Store::add(obj); +} + +/** Add a family of objects to the store. Not realtime safe. + */ +void +EngineStore::add(const Objects& table) +{ + ThreadManager::assert_thread(THREAD_PRE_PROCESS); + cram(table); +} + +/** Remove an object from the store. + * + * Returned is a vector containing all descendants of the object removed + * including the object itself, in lexicographically sorted order by Path. + */ +SharedPtr<EngineStore::Objects> +EngineStore::remove(const Path& path) +{ + return remove(find(path)); +} + +/** Remove an object from the store. + * + * Returned is a vector containing all descendants of the object removed + * including the object itself, in lexicographically sorted order by Path. + */ +SharedPtr<EngineStore::Objects> +EngineStore::remove(iterator object) +{ + ThreadManager::assert_thread(THREAD_PRE_PROCESS); + + if (object != end()) { + iterator descendants_end = find_descendants_end(object); + SharedPtr<Objects> removed = yank(object, descendants_end); + + return removed; + + } else { + LOG(warn) << "Removing " << object->first << " failed." << endl; + return SharedPtr<EngineStore>(); + } +} + +/** Remove all children of an object from the store. + * + * Returned is a vector containing all descendants of the object removed + * in lexicographically sorted order by Path. + */ +SharedPtr<EngineStore::Objects> +EngineStore::remove_children(const Path& path) +{ + return remove_children(find(path)); +} + +/** Remove all children of an object from the store. + * + * Returned is a vector containing all descendants of the object removed + * in lexicographically sorted order by Path. + */ +SharedPtr<EngineStore::Objects> +EngineStore::remove_children(iterator object) +{ + if (object != end()) { + iterator descendants_end = find_descendants_end(object); + if (descendants_end != object) { + iterator first_child = object; + ++first_child; + return yank(first_child, descendants_end); + } + } else { + LOG(warn) << "Removing children of " << object->first << " failed." << endl; + return SharedPtr<EngineStore::Objects>(); + } + + return SharedPtr<EngineStore::Objects>(); +} + +} // namespace Server +} // namespace Ingen diff --git a/src/server/EngineStore.hpp b/src/server/EngineStore.hpp new file mode 100644 index 00000000..ba8dd000 --- /dev/null +++ b/src/server/EngineStore.hpp @@ -0,0 +1,65 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_OBJECTSTORE_HPP +#define INGEN_ENGINE_OBJECTSTORE_HPP + +#include "raul/SharedPtr.hpp" + +#include "shared/Store.hpp" + +namespace Ingen { + +class GraphObject; + +namespace Server { + +class PatchImpl; +class NodeImpl; +class PortImpl; +class GraphObjectImpl; + +/** Storage for all GraphObjects (tree of GraphObject's sorted by path). + * + * All looking up in pre_process() methods (and anything else that isn't in-band + * with the audio thread) should use this (to read and modify the GraphObject + * tree). + * + * Searching with find*() is fast (O(log(n)) binary search on contiguous + * memory) and realtime safe, but modification (add or remove) are neither. + */ +class EngineStore : public Ingen::Shared::Store +{ +public: + PatchImpl* find_patch(const Raul::Path& path); + NodeImpl* find_node(const Raul::Path& path); + PortImpl* find_port(const Raul::Path& path); + GraphObjectImpl* find_object(const Raul::Path& path); + + void add(Ingen::GraphObject* o); + void add(const Objects& family); + + SharedPtr<Objects> remove(const Raul::Path& path); + SharedPtr<Objects> remove(Objects::iterator i); + SharedPtr<Objects> remove_children(const Raul::Path& path); + SharedPtr<Objects> remove_children(Objects::iterator i); +}; + +} // namespace Server +} // namespace Ingen + +#endif // OBJECTSTORE diff --git a/src/server/Event.cpp b/src/server/Event.cpp new file mode 100644 index 00000000..f967bfac --- /dev/null +++ b/src/server/Event.cpp @@ -0,0 +1,56 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 "Driver.hpp" +#include "Engine.hpp" +#include "Event.hpp" +#include "ProcessContext.hpp" +#include "ThreadManager.hpp" + +/*! \page methods Method Documentation + * + * <p>All changes in Ingen (both engine and client) occur as a result of + * a small set of methods defined in terms of RDF and matching the + * HTTP and WebDAV standards as closely as possible.</p> + */ + +namespace Ingen { +namespace Server { + +void +Event::execute(ProcessContext& context) +{ + ThreadManager::assert_thread(THREAD_PROCESS); + assert(!_executed); + assert(_time <= context.end()); + + // Missed the event, jitter, damnit. + if (_time < context.start()) + _time = context.start(); + + _executed = true; +} + +void +Event::post_process() +{ + ThreadManager::assert_not_thread(THREAD_PROCESS); +} + +} // namespace Server +} // namespace Ingen + diff --git a/src/server/Event.hpp b/src/server/Event.hpp new file mode 100644 index 00000000..af39bfa4 --- /dev/null +++ b/src/server/Event.hpp @@ -0,0 +1,80 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_EVENT_HPP +#define INGEN_ENGINE_EVENT_HPP + +#include <cassert> +#include "raul/SharedPtr.hpp" +#include "raul/Deletable.hpp" +#include "raul/Path.hpp" +#include "types.hpp" + +namespace Ingen { +namespace Server { + +class Engine; +class Request; +class ProcessContext; + +/** Base class for all events (both realtime and QueuedEvent). + * + * This is for time-critical events like note ons. There is no non-realtime + * pre-execute method as in QueuedEvent's, any lookups etc need to be done in the + * realtime execute() method. + * + * QueuedEvent extends this class with a pre_process() method for any work that needs + * to be done before processing in the realtime audio thread. + * + * \ingroup engine + */ +class Event : public Raul::Deletable +{ +public: + virtual ~Event() {} + + /** Execute this event in the audio thread (MUST be realtime safe). */ + virtual void execute(ProcessContext& context); + + /** Perform any actions after execution (ie send replies to commands) + * (no realtime requirements). */ + virtual void post_process(); + + inline SampleCount time() const { return _time; } + + int error() { return _error; } + +protected: + Event(Engine& engine, SharedPtr<Request> request, FrameTime time) + : _engine(engine) + , _request(request) + , _time(time) + , _error(0) // success + , _executed(false) + {} + + Engine& _engine; + SharedPtr<Request> _request; + FrameTime _time; + int _error; + bool _executed; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_EVENT_HPP diff --git a/src/server/EventBuffer.cpp b/src/server/EventBuffer.cpp new file mode 100644 index 00000000..1e8713ba --- /dev/null +++ b/src/server/EventBuffer.cpp @@ -0,0 +1,215 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#define __STDC_LIMIT_MACROS 1 +#include <stdint.h> +#include "raul/log.hpp" +#include "lv2/lv2plug.in/ns/ext/event/event.h" +#include "lv2/lv2plug.in/ns/ext/event/event-helpers.h" +#include "ingen-config.h" +#include "EventBuffer.hpp" +#include "ProcessContext.hpp" + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { + +/** Allocate a new event buffer. + * \a capacity is in bytes (not number of events). + */ +EventBuffer::EventBuffer(BufferFactory& bufs, size_t capacity) + : Buffer(bufs, PortType(PortType::EVENTS), capacity) + , _latest_frames(0) + , _latest_subframes(0) +{ + if (capacity > UINT32_MAX) { + error << "Event buffer size " << capacity << " too large, aborting." << endl; + throw std::bad_alloc(); + } + +#ifdef HAVE_POSIX_MEMALIGN + int ret = posix_memalign((void**)&_data, 16, sizeof(LV2_Event_Buffer) + capacity); +#else + _data = (LV2_Event_Buffer*)malloc(sizeof(LV2_Event_Buffer) + capacity); + int ret = (_data != NULL) ? 0 : -1; +#endif + + if (ret != 0) { + error << "Failed to allocate event buffer. Aborting." << endl; + exit(EXIT_FAILURE); + } + + _data->header_size = sizeof(LV2_Event_Buffer); + _data->data = reinterpret_cast<uint8_t*>(_data + _data->header_size); + _data->stamp_type = 0; + _data->event_count = 0; + _data->capacity = (uint32_t)capacity; + _data->size = 0; + + clear(); +} + +EventBuffer::~EventBuffer() +{ + free(_data); +} + +void +EventBuffer::prepare_read(Context& context) +{ + rewind(); +} + +void +EventBuffer::prepare_write(Context& context) +{ + if (context.offset() == 0) + clear(); +} + +void +EventBuffer::copy(Context& context, const Buffer* src_buf) +{ + const EventBuffer* src = dynamic_cast<const EventBuffer*>(src_buf); + if (src->_data == _data) + return; + + assert(src->_data->header_size == _data->header_size); + assert(capacity() >= _data->header_size + src->_data->size); + + rewind(); + + memcpy(_data, src->_data, _data->header_size + src->_data->size); + + _iter = src->_iter; + _iter.buf = _data; + + _latest_frames = src->_latest_frames; + _latest_subframes = src->_latest_subframes; + + assert(event_count() == src->event_count()); +} + +/** Increment the read position by one event. + * + * \return true if increment was successful, or false if end of buffer reached. + */ +bool +EventBuffer::increment() const +{ + if (lv2_event_is_valid(&_iter)) { + lv2_event_increment(&_iter); + return true; + } else { + return false; + } +} + +/** \return true iff the cursor is valid (ie get_event is safe) + */ +bool +EventBuffer::is_valid() const +{ + return lv2_event_is_valid(&_iter); +} + +/** Read an event from the current position in the buffer + * + * \return true if read was successful, or false if end of buffer reached + */ +bool +EventBuffer::get_event(uint32_t* frames, + uint32_t* subframes, + uint16_t* type, + uint16_t* size, + uint8_t** data) const +{ + if (lv2_event_is_valid(&_iter)) { + LV2_Event* ev = lv2_event_get(&_iter, data); + *frames = ev->frames; + *subframes = ev->subframes; + *type = ev->type; + *size = ev->size; + return true; + } else { + return false; + } +} + +/** Get the object currently pointed to, or NULL if invalid. + */ +LV2_Atom* +EventBuffer::get_atom() const +{ + if (lv2_event_is_valid(&_iter)) { + uint8_t* data; + LV2_Event* ev = lv2_event_get(&_iter, &data); + return LV2_ATOM_FROM_EVENT(ev); + } + return NULL; +} + +/** Get the event currently pointed to, or NULL if invalid. + */ +LV2_Event* +EventBuffer::get_event() const +{ + if (lv2_event_is_valid(&_iter)) { + uint8_t* data; + return lv2_event_get(&_iter, &data); + } + return NULL; +} + +/** Append an event to the buffer. + * + * \a timestamp must be >= the latest event in the buffer. + * + * \return true on success + */ +bool +EventBuffer::append(uint32_t frames, + uint32_t subframes, + uint16_t type, + uint16_t size, + const uint8_t* data) +{ +#ifndef NDEBUG + if (lv2_event_is_valid(&_iter)) { + LV2_Event* last_event = lv2_event_get(&_iter, NULL); + assert(last_event->frames < frames + || (last_event->frames == frames && last_event->subframes <= subframes)); + } +#endif + + /*debug << "Appending event type " << type << ", size " << size + << " @ " << frames << "." << subframes << endl;*/ + + if (!lv2_event_write(&_iter, frames, subframes, type, size, data)) { + error << "Failed to write event." << endl; + return false; + } else { + _latest_frames = frames; + _latest_subframes = subframes; + return true; + } +} + +} // namespace Server +} // namespace Ingen diff --git a/src/server/EventBuffer.hpp b/src/server/EventBuffer.hpp new file mode 100644 index 00000000..de67ae0c --- /dev/null +++ b/src/server/EventBuffer.hpp @@ -0,0 +1,86 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_EVENTBUFFER_HPP +#define INGEN_ENGINE_EVENTBUFFER_HPP + +#include "lv2/lv2plug.in/ns/ext/atom/atom.h" +#include "lv2/lv2plug.in/ns/ext/event/event.h" +#include "lv2/lv2plug.in/ns/ext/event/event-helpers.h" +#include "ingen/PortType.hpp" +#include "Buffer.hpp" + +namespace Ingen { +namespace Server { + +class EventBuffer : public Buffer { +public: + EventBuffer(BufferFactory& bufs, size_t capacity); + ~EventBuffer(); + + void* port_data(PortType port_type, SampleCount offset=0) { return _data; } + const void* port_data(PortType port_type, SampleCount offset=0) const { return _data; } + + inline void rewind() const { lv2_event_begin(&_iter, _data); } + + inline void clear() { + _latest_frames = 0; + _latest_subframes = 0; + _data->event_count = 0; + _data->size = 0; + rewind(); + } + + void prepare_read(Context& context); + void prepare_write(Context& context); + + void copy(Context& context, const Buffer* src); + + inline size_t event_count() const { return _data->event_count; } + inline uint32_t capacity() const { return _data->capacity; } + inline uint32_t latest_frames() const { return _latest_frames; } + inline uint32_t latest_subframes() const { return _latest_subframes; } + + bool increment() const; + bool is_valid() const; + + bool get_event(uint32_t* frames, + uint32_t* subframes, + uint16_t* type, + uint16_t* size, + uint8_t** data) const; + + LV2_Atom* get_atom() const; + LV2_Event* get_event() const; + + bool append(uint32_t frames, + uint32_t subframes, + uint16_t type, + uint16_t size, + const uint8_t* data); + +private: + LV2_Event_Buffer* _data; ///< Contents + mutable LV2_Event_Iterator _iter; ///< Iterator into _data + uint32_t _latest_frames; ///< Latest time of all events (frames) + uint32_t _latest_subframes; ///< Latest time of all events (subframes) +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_EVENTBUFFER_HPP diff --git a/src/server/EventSink.cpp b/src/server/EventSink.cpp new file mode 100644 index 00000000..bb81f3ec --- /dev/null +++ b/src/server/EventSink.cpp @@ -0,0 +1,62 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 "EventSink.hpp" +#include "PortImpl.hpp" + +using namespace std; + +namespace Ingen { +namespace Server { + +/** \a size is not size_t because an event will never be even remotely close + * to UINT32_MAX in size, so uint32_t saves wasted space on 64-bit. + */ +bool +EventSink::write(uint32_t size, const Event* ev) +{ + if (size > _events.write_space()) + return false; + + _events.write(sizeof(uint32_t), (uint8_t*)&size); + _events.write(size, (uint8_t*)ev); + + return true; +} + +/** Read the next event into event_buffer. + * + * \a event_buffer can be casted to Event* and virtual methods called. + */ +bool +EventSink::read(uint32_t event_buffer_size, uint8_t* event_buffer) +{ + uint32_t read_size; + bool success = _events.full_read(sizeof(uint32_t), (uint8_t*)&read_size); + if (!success) + return false; + + assert(read_size <= event_buffer_size); + + if (read_size > 0) + return _events.full_read(read_size, event_buffer); + else + return false; +} + +} // namespace Server +} // namespace Ingen diff --git a/src/server/EventSink.hpp b/src/server/EventSink.hpp new file mode 100644 index 00000000..bbc3c324 --- /dev/null +++ b/src/server/EventSink.hpp @@ -0,0 +1,59 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_EVENTSINK_HPP +#define INGEN_ENGINE_EVENTSINK_HPP + +#include <stdint.h> +#include <list> +#include <utility> +#include "raul/RingBuffer.hpp" + +namespace Ingen { +namespace Server { + +class PortImpl; +class Engine; +class Event; + +/** Sink for events generated in the audio thread. + * + * Implemented as a flat ringbuffer of events, which are constructed directly + * in the ringbuffer rather than allocated on the heap (in order to make + * writing realtime safe). + * + * \ingroup engine + */ +class EventSink +{ +public: + EventSink(Engine& engine, size_t capacity) : _engine(engine), _events(capacity) {} + + bool write(uint32_t size, const Event* ev); + + bool read(uint32_t event_buffer_size, uint8_t* event_buffer); + +private: + Engine& _engine; + Raul::RingBuffer _events; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_EVENTSINK_HPP + diff --git a/src/server/EventSource.cpp b/src/server/EventSource.cpp new file mode 100644 index 00000000..273a4693 --- /dev/null +++ b/src/server/EventSource.cpp @@ -0,0 +1,127 @@ +/* This file is part of Ingen. + * Copyright 2008-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <sys/mman.h> +#include "EventSource.hpp" +#include "QueuedEvent.hpp" +#include "PostProcessor.hpp" +#include "ThreadManager.hpp" +#include "ProcessContext.hpp" + +using namespace std; + +namespace Ingen { +namespace Server { + +EventSource::EventSource(size_t queue_size) + : _blocking_semaphore(0) +{ + Thread::set_context(THREAD_PRE_PROCESS); + set_name("EventSource"); +} + +EventSource::~EventSource() +{ + Thread::stop(); +} + +/** Push an unprepared event onto the queue. + */ +void +EventSource::push_queued(QueuedEvent* const ev) +{ + assert(!ev->is_prepared()); + Raul::List<Event*>::Node* node = new Raul::List<Event*>::Node(ev); + _events.push_back(node); + if (_prepared_back.get() == NULL) + _prepared_back = node; + + whip(); +} + +/** Process all events for a cycle. + * + * Executed events will be pushed to @a dest. + */ +void +EventSource::process(PostProcessor& dest, ProcessContext& context, bool limit) +{ + ThreadManager::assert_thread(THREAD_PROCESS); + + if (_events.empty()) + return; + + /* Limit the maximum number of queued events to process per cycle. This + * makes the process callback (more) realtime-safe by preventing being + * choked by events coming in faster than they can be processed. + * FIXME: test this and figure out a good value */ + const size_t MAX_QUEUED_EVENTS = context.nframes() / 32; + + size_t num_events_processed = 0; + + Raul::List<Event*>::Node* head = _events.head(); + Raul::List<Event*>::Node* tail = head; + + if (!head) + return; + + QueuedEvent* ev = (QueuedEvent*)head->elem(); + + while (ev && ev->is_prepared() && ev->time() < context.end()) { + ev->execute(context); + tail = head; + head = head->next(); + ++num_events_processed; + if (limit && num_events_processed > MAX_QUEUED_EVENTS) + break; + ev = (head ? (QueuedEvent*)head->elem() : NULL); + } + + if (num_events_processed > 0) { + Raul::List<Event*> front; + _events.chop_front(front, num_events_processed, tail); + dest.append(&front); + } +} + +/** Pre-process a single event */ +void +EventSource::_whipped() +{ + Raul::List<Event*>::Node* pb = _prepared_back.get(); + if (!pb) + return; + + QueuedEvent* const ev = (QueuedEvent*)pb->elem(); + assert(ev); + + assert(!ev->is_prepared()); + ev->pre_process(); + assert(ev->is_prepared()); + + assert(_prepared_back.get() == pb); + _prepared_back = pb->next(); + + // If event was blocking, wait for event to being run through the + // process thread before preparing the next event + if (ev->is_blocking()) + _blocking_semaphore.wait(); +} + +} // namespace Server +} // namespace Ingen + diff --git a/src/server/EventSource.hpp b/src/server/EventSource.hpp new file mode 100644 index 00000000..19d87ed9 --- /dev/null +++ b/src/server/EventSource.hpp @@ -0,0 +1,73 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_EVENTSOURCE_HPP +#define INGEN_ENGINE_EVENTSOURCE_HPP + +#include "raul/Semaphore.hpp" +#include "raul/Slave.hpp" +#include "raul/List.hpp" + +namespace Ingen { +namespace Server { + +class Event; +class QueuedEvent; +class PostProcessor; +class ProcessContext; + +/** Source for events to run in the audio thread. + * + * The Driver gets events from an EventSource in the process callback + * (realtime audio thread) and executes them, then they are sent to the + * PostProcessor and finalised (post-processing thread). + */ +class EventSource : protected Raul::Slave +{ +public: + explicit EventSource(size_t queue_size); + virtual ~EventSource(); + + void process(PostProcessor& dest, ProcessContext& context, bool limit=true); + + bool empty() { return _events.empty(); } + + /** Signal that a blocking event is finished. + * + * This MUST be called by blocking events in their post_process() method + * to resume pre-processing of events. + */ + inline void unblock() { _blocking_semaphore.post(); } + +protected: + void push_queued(QueuedEvent* const ev); + + inline bool unprepared_events() { return (_prepared_back.get() != NULL); } + + virtual void _whipped(); ///< Prepare 1 event + +private: + Raul::List<Event*> _events; + Raul::AtomicPtr<Raul::List<Event*>::Node> _prepared_back; + Raul::Semaphore _blocking_semaphore; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_EVENTSOURCE_HPP + diff --git a/src/server/GraphObjectImpl.cpp b/src/server/GraphObjectImpl.cpp new file mode 100644 index 00000000..334a8caa --- /dev/null +++ b/src/server/GraphObjectImpl.cpp @@ -0,0 +1,73 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <string> + +#include "GraphObjectImpl.hpp" +#include "PatchImpl.hpp" +#include "EngineStore.hpp" + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { + +GraphObjectImpl::GraphObjectImpl(Ingen::Shared::LV2URIMap& uris, + GraphObjectImpl* parent, + const Symbol& symbol) + : ResourceImpl(uris, parent ? parent->path().child(symbol) : Raul::Path::root()) + , _parent(parent) + , _path(parent ? parent->path().child(symbol) : "/") + , _symbol(symbol) +{ +} + +void +GraphObjectImpl::add_meta_property(const Raul::URI& key, const Atom& value) +{ + add_property(key, Resource::Property(value, Resource::INTERNAL)); +} + +void +GraphObjectImpl::set_meta_property(const Raul::URI& key, const Atom& value) +{ + set_property(key, Resource::Property(value, Resource::INTERNAL)); +} + +const Atom& +GraphObjectImpl::get_property(const Raul::URI& key) const +{ + static const Atom null_atom; + Resource::Properties::const_iterator i = properties().find(key); + return (i != properties().end()) ? i->second : null_atom; +} + +PatchImpl* +GraphObjectImpl::parent_patch() const +{ + return dynamic_cast<PatchImpl*>((NodeImpl*)_parent); +} + +SharedPtr<GraphObject> +GraphObjectImpl::find_child(const std::string& name) const +{ + throw; +} + +} // namespace Server +} // namespace Ingen diff --git a/src/server/GraphObjectImpl.hpp b/src/server/GraphObjectImpl.hpp new file mode 100644 index 00000000..6c08c9c4 --- /dev/null +++ b/src/server/GraphObjectImpl.hpp @@ -0,0 +1,113 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_GRAPHOBJECTIMPL_HPP +#define INGEN_ENGINE_GRAPHOBJECTIMPL_HPP + +#include <string> +#include <map> +#include <cstddef> +#include <cassert> +#include "raul/Deletable.hpp" +#include "raul/Path.hpp" +#include "raul/SharedPtr.hpp" +#include "ingen/GraphObject.hpp" +#include "shared/ResourceImpl.hpp" + +namespace Raul { class Maid; } + +namespace Ingen { + +namespace Shared { class LV2URIMap; } + +namespace Server { + +class PatchImpl; +class Context; +class ProcessContext; +class BufferFactory; + +/** An object on the audio graph - Patch, Node, Port, etc. + * + * Each of these is a Raul::Deletable and so can be deleted in a realtime safe + * way from anywhere, and they all have a map of variable for clients to store + * arbitrary values in (which the engine puts no significance to whatsoever). + * + * \ingroup engine + */ +class GraphObjectImpl : virtual public GraphObject + , public Ingen::Shared::ResourceImpl +{ +public: + virtual ~GraphObjectImpl() {} + + const Raul::URI& uri() const { return _path; } + const Raul::Symbol& symbol() const { return _symbol; } + + GraphObject* graph_parent() const { return _parent; } + GraphObjectImpl* parent() const { return _parent; } + + //virtual void process(ProcessContext& context) = 0; + + /** Rename */ + virtual void set_path(const Raul::Path& new_path) { + _path = new_path; + _symbol = new_path.symbol(); + } + + const Raul::Atom& get_property(const Raul::URI& key) const; + void add_meta_property(const Raul::URI& key, const Raul::Atom& value); + void set_meta_property(const Raul::URI& key, const Raul::Atom& value); + + /** The Patch this object is a child of. */ + virtual PatchImpl* parent_patch() const; + + /** Raul::Path is dynamically generated from parent to ease renaming */ + const Raul::Path& path() const { return _path; } + + SharedPtr<GraphObject> find_child(const std::string& name) const; + + /** Prepare for a new (external) polyphony value. + * + * Preprocessor thread, poly is actually applied by apply_poly. + * \return true on success. + */ + virtual bool prepare_poly(BufferFactory& bufs, uint32_t poly) = 0; + + /** Apply a new (external) polyphony value. + * + * Audio thread. + * + * \param poly Must be <= the most recent value passed to prepare_poly. + * \param maid Any objects no longer needed will be pushed to this + */ + virtual bool apply_poly(Raul::Maid& maid, uint32_t poly) = 0; + +protected: + GraphObjectImpl(Ingen::Shared::LV2URIMap& uris, + GraphObjectImpl* parent, + const Raul::Symbol& symbol); + + GraphObjectImpl* _parent; + Raul::Path _path; + Raul::Symbol _symbol; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_GRAPHOBJECTIMPL_HPP diff --git a/src/server/HTTPClientSender.cpp b/src/server/HTTPClientSender.cpp new file mode 100644 index 00000000..1334e6e7 --- /dev/null +++ b/src/server/HTTPClientSender.cpp @@ -0,0 +1,146 @@ +/* This file is part of Ingen. + * Copyright 2008-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <string> +#include <libsoup/soup.h> +#include "raul/log.hpp" +#include "raul/Atom.hpp" +#include "raul/AtomRDF.hpp" +#include "serialisation/Serialiser.hpp" +#include "shared/World.hpp" +#include "HTTPClientSender.hpp" +#include "Engine.hpp" + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { + +void +HTTPClientSender::response_ok(int32_t id) +{ +} + +void +HTTPClientSender::response_error(int32_t id, const std::string& msg) +{ + warn << "HTTP Error " << id << " (" << msg << ")" << endl; +} + +void +HTTPClientSender::error(const std::string& msg) +{ + warn << "HTTP send error " << msg << endl; +} + +void +HTTPClientSender::put(const URI& uri, + const Resource::Properties& properties, + Resource::Graph ctx) +{ + const std::string request_uri = (Raul::Path::is_path(uri)) + ? _url + "/patch" + uri.substr(uri.find("/")) + : uri.str(); + + + Sord::Model model(*_engine.world()->rdf_world()); + for (Resource::Properties::const_iterator i = properties.begin(); + i != properties.end(); ++i) + model.add_statement( + Sord::URI(*_engine.world()->rdf_world(), request_uri), + AtomRDF::atom_to_node(model, i->first.str()), + AtomRDF::atom_to_node(model, i->second)); + + const string str = model.write_to_string("turtle"); + send_chunk(str); +} + +void +HTTPClientSender::delta(const URI& uri, + const Resource::Properties& remove, + const Resource::Properties& add) +{ +} + +void +HTTPClientSender::del(const URI& uri) +{ + send_chunk(string("<").append(uri.str()).append("> a <http://www.w3.org/2002/07/owl#Nothing> .")); +} + +void +HTTPClientSender::connect(const Path& src_path, const Path& dst_path) +{ + const string msg = string( + "@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n" + "@prefix ingen: <http://drobilla.net/ns/ingen#> .\n").append( + "<> ingen:connection [\n" + "\tingen:destination <").append(dst_path.str()).append("> ;\n" + "\tingen:source <").append(src_path.str()).append(">\n] .\n"); + send_chunk(msg); +} + +void +HTTPClientSender::disconnect(const URI& src, + const URI& dst) +{ +} + +void +HTTPClientSender::disconnect_all(const Raul::Path& parent_patch_path, + const Raul::Path& path) +{ +} + +void +HTTPClientSender::set_property(const URI& subject, const URI& key, const Atom& value) +{ +#if 0 + Sord::Node node = AtomRDF::atom_to_node(*_engine.world()->rdf_world(), value); + const string msg = string( + "@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n" + "@prefix ingen: <http://drobilla.net/ns/ingen#> .\n" + "@prefix ingenui: <http://drobilla.net/ns/ingenuity#> .\n").append( + subject.str()).append("> ingen:property [\n" + "rdf:predicate ").append(key.str()).append(" ;\n" + "rdf:value ").append(node.to_string()).append("\n] .\n"); + send_chunk(msg); +#endif +} + +void +HTTPClientSender::activity(const Path& path) +{ + const string msg = string( + "@prefix ingen: <http://drobilla.net/ns/ingen#> .\n\n<").append( + path.str()).append("> ingen:activity true .\n"); + send_chunk(msg); +} + +void +HTTPClientSender::move(const Path& old_path, const Path& new_path) +{ + string msg = string( + "@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n" + "@prefix ingen: <http://drobilla.net/ns/ingen#> .\n\n<").append( + old_path.str()).append("> rdf:subject <").append(new_path.str()).append("> .\n"); + send_chunk(msg); +} + +} // namespace Server +} // namespace Ingen diff --git a/src/server/HTTPClientSender.hpp b/src/server/HTTPClientSender.hpp new file mode 100644 index 00000000..63842c3a --- /dev/null +++ b/src/server/HTTPClientSender.hpp @@ -0,0 +1,107 @@ +/* This file is part of Ingen. + * Copyright 2008-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_HTTPCLIENTSENDER_HPP +#define INGEN_ENGINE_HTTPCLIENTSENDER_HPP + +#include <cassert> +#include <string> +#include <pthread.h> +#include "raul/Thread.hpp" +#include "ingen/ClientInterface.hpp" +#include "shared/HTTPSender.hpp" + +namespace Ingen { + +class ServerInterface; + +namespace Server { + +class Engine; + +/** Implements ClientInterface for HTTP clients. + * Sends changes as RDF deltas over an HTTP stream + * (a single message with chunked encoding response). + * + * \ingroup engine + */ +class HTTPClientSender + : public ClientInterface + , public Ingen::Shared::HTTPSender +{ +public: + explicit HTTPClientSender(Engine& engine) + : _engine(engine) + , _enabled(true) + {} + + bool enabled() const { return _enabled; } + + void enable() { _enabled = true; } + void disable() { _enabled = false; } + + void bundle_begin() { HTTPSender::bundle_begin(); } + void bundle_end() { HTTPSender::bundle_end(); } + + Raul::URI uri() const { return "http://example.org/"; } + + /* *** ClientInterface Implementation Below *** */ + + void response_ok(int32_t id); + void response_error(int32_t id, const std::string& msg); + + void error(const std::string& msg); + + virtual void put(const Raul::URI& path, + const Resource::Properties& properties, + Resource::Graph ctx); + + virtual void delta(const Raul::URI& path, + const Resource::Properties& remove, + const Resource::Properties& add); + + virtual void del(const Raul::URI& uri); + + virtual void move(const Raul::Path& old_path, + const Raul::Path& new_path); + + virtual void connect(const Raul::Path& src_port_path, + const Raul::Path& dst_port_path); + + virtual void disconnect(const Raul::URI& src, + const Raul::URI& dst); + + virtual void disconnect_all(const Raul::Path& parent_patch_path, + const Raul::Path& path); + + virtual void set_property(const Raul::URI& subject_path, + const Raul::URI& predicate, + const Raul::Atom& value); + + virtual void activity(const Raul::Path& path); + +private: + Engine& _engine; + std::string _url; + bool _enabled; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_HTTPCLIENTSENDER_HPP + diff --git a/src/server/HTTPEngineReceiver.cpp b/src/server/HTTPEngineReceiver.cpp new file mode 100644 index 00000000..b027a6b3 --- /dev/null +++ b/src/server/HTTPEngineReceiver.cpp @@ -0,0 +1,230 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <cstdio> +#include <cstdlib> +#include <string> + +#include <boost/format.hpp> + +#include <libsoup/soup.h> + +#include "raul/SharedPtr.hpp" +#include "raul/log.hpp" + +#include "ingen/ClientInterface.hpp" +#include "shared/Module.hpp" +#include "serialisation/Parser.hpp" +#include "serialisation/Serialiser.hpp" + +#include "ClientBroadcaster.hpp" +#include "Engine.hpp" +#include "EngineStore.hpp" +#include "EventSource.hpp" +#include "HTTPClientSender.hpp" +#include "HTTPEngineReceiver.hpp" +#include "ThreadManager.hpp" + +#define LOG(s) s << "[HTTPEngineReceiver] " + +using namespace std; +using namespace Raul; + +namespace Ingen { + +using namespace Serialisation; + +namespace Server { + +HTTPEngineReceiver::HTTPEngineReceiver(Engine& engine, uint16_t port) + : QueuedEngineInterface(engine, 64) // FIXME + , _server(soup_server_new(SOUP_SERVER_PORT, port, NULL)) +{ + _receive_thread = new ReceiveThread(*this); + + soup_server_add_handler(_server, NULL, message_callback, this, NULL); + + LOG(info) << "Started HTTP server on port " << soup_server_get_port(_server) << endl; + + if (!engine.world()->parser() || !engine.world()->serialiser()) + engine.world()->load_module("serialisation"); + + Thread::set_name("HTTPEngineReceiver"); + start(); + _receive_thread->set_name("HTTPEngineReceiver Listener"); + _receive_thread->start(); +} + +HTTPEngineReceiver::~HTTPEngineReceiver() +{ + _receive_thread->stop(); + stop(); + delete _receive_thread; + + if (_server) { + soup_server_quit(_server); + _server = NULL; + } +} + +void +HTTPEngineReceiver::message_callback(SoupServer* server, + SoupMessage* msg, + const char* path_str, + GHashTable* query, + SoupClientContext* client, + void* data) +{ + HTTPEngineReceiver* me = (HTTPEngineReceiver*)data; + + using namespace Ingen::Shared; + + SharedPtr<Store> store = me->_engine.world()->store(); + if (!store) { + soup_message_set_status(msg, SOUP_STATUS_INTERNAL_SERVER_ERROR); + return; + } + + string path = path_str; + if (path[path.length() - 1] == '/') { + path = path.substr(0, path.length()-1); + } + + SharedPtr<Serialiser> serialiser = me->_engine.world()->serialiser(); + + const string base_uri = "path:/"; + const char* mime_type = "text/plain"; + + // Special GET paths + if (msg->method == SOUP_METHOD_GET) { + if (path == Path::root().str() || path.empty()) { + const string r = string("@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n") + .append("\n<> rdfs:seeAlso <plugins> ;") + .append("\n rdfs:seeAlso <stream> ;") + .append("\n rdfs:seeAlso <patch> ."); + soup_message_set_status(msg, SOUP_STATUS_OK); + soup_message_set_response(msg, mime_type, SOUP_MEMORY_COPY, r.c_str(), r.length()); + return; + + } else if (msg->method == SOUP_METHOD_GET && path.substr(0, 8) == "/plugins") { + // FIXME: kludge + #if 0 + me->get("ingen:plugins"); + me->_receive_thread->whip(); + + serialiser->start_to_string("/", base_uri); + for (NodeFactory::Plugins::const_iterator p = me->_engine.node_factory()->plugins().begin(); + p != me->_engine.node_factory()->plugins().end(); ++p) + serialiser->serialise_plugin(*(Shared::Plugin*)p->second); + const string r = serialiser->finish(); + soup_message_set_status(msg, SOUP_STATUS_OK); + soup_message_set_response(msg, mime_type, SOUP_MEMORY_COPY, r.c_str(), r.length()); + #endif + return; + + } else if (path.substr(0, 6) == "/patch") { + path = '/' + path.substr(6); + if (path.substr(0, 2) == "//") + path = path.substr(1); + + } else if (path.substr(0, 7) == "/stream") { + HTTPClientSender* client = new HTTPClientSender(me->_engine); + me->register_client(client); + + // Respond with port number of stream for client + const int port = client->listen_port(); + char buf[32]; + snprintf(buf, sizeof(buf), "%d", port); + soup_message_set_status(msg, SOUP_STATUS_OK); + soup_message_set_response(msg, mime_type, SOUP_MEMORY_COPY, buf, strlen(buf)); + return; + } + } + + if (!Path::is_valid(path)) { + LOG(error) << "Bad HTTP path: " << path << endl; + soup_message_set_status(msg, SOUP_STATUS_BAD_REQUEST); + const string& err = (boost::format("Bad path: %1%") % path).str(); + soup_message_set_response(msg, "text/plain", SOUP_MEMORY_COPY, + err.c_str(), err.length()); + return; + } + + if (msg->method == SOUP_METHOD_GET) { + Glib::RWLock::ReaderLock lock(store->lock()); + + // Find object + Store::const_iterator start = store->find(path); + if (start == store->end()) { + soup_message_set_status(msg, SOUP_STATUS_NOT_FOUND); + const string& err = (boost::format("No such object: %1%") % path).str(); + soup_message_set_response(msg, "text/plain", SOUP_MEMORY_COPY, + err.c_str(), err.length()); + return; + } + + // Get serialiser + SharedPtr<Serialiser> serialiser = me->_engine.world()->serialiser(); + if (!serialiser) { + soup_message_set_status(msg, SOUP_STATUS_INTERNAL_SERVER_ERROR); + soup_message_set_response(msg, "text/plain", SOUP_MEMORY_STATIC, + "No serialiser available\n", 24); + return; + } + + // Serialise object + const string response = serialiser->to_string(start->second, + "http://localhost:16180/patch", GraphObject::Properties()); + + soup_message_set_status(msg, SOUP_STATUS_OK); + soup_message_set_response(msg, mime_type, SOUP_MEMORY_COPY, + response.c_str(), response.length()); + + } else if (msg->method == SOUP_METHOD_PUT) { + Glib::RWLock::WriterLock lock(store->lock()); + + // Get parser + SharedPtr<Parser> parser = me->_engine.world()->parser(); + if (!parser) { + soup_message_set_status(msg, SOUP_STATUS_INTERNAL_SERVER_ERROR); + return; + } + + parser->parse_string(me->_engine.world(), me, msg->request_body->data, base_uri); + soup_message_set_status(msg, SOUP_STATUS_OK); + + } else if (msg->method == SOUP_METHOD_DELETE) { + me->del(path); + soup_message_set_status(msg, SOUP_STATUS_OK); + + } else { + soup_message_set_status(msg, SOUP_STATUS_NOT_IMPLEMENTED); + } +} + +/** Override the semaphore driven _run method of QueuedEngineInterface + * to wait on HTTP requests and process them immediately in this thread. + */ +void +HTTPEngineReceiver::ReceiveThread::_run() +{ + soup_server_run(_receiver._server); +} + +} // namespace Server +} // namespace Ingen + diff --git a/src/server/HTTPEngineReceiver.hpp b/src/server/HTTPEngineReceiver.hpp new file mode 100644 index 00000000..c261d0f1 --- /dev/null +++ b/src/server/HTTPEngineReceiver.hpp @@ -0,0 +1,64 @@ +/* This file is part of Ingen. + * Copyright 2008-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_HTTPENGINERECEIVER_HPP +#define INGEN_ENGINE_HTTPENGINERECEIVER_HPP + +#include <stdint.h> + +#include <string> + +#include "QueuedEngineInterface.hpp" + +typedef struct _SoupServer SoupServer; +typedef struct _SoupMessage SoupMessage; +typedef struct SoupClientContext SoupClientContext; + +namespace Ingen { +namespace Server { + +class HTTPEngineReceiver : public QueuedEngineInterface +{ +public: + HTTPEngineReceiver(Engine& engine, uint16_t port); + ~HTTPEngineReceiver(); + +private: + struct ReceiveThread : public Raul::Thread { + explicit ReceiveThread(HTTPEngineReceiver& receiver) : _receiver(receiver) {} + virtual void _run(); + virtual void whip() { + while (_receiver.unprepared_events()) + _receiver.whip(); + } + private: + HTTPEngineReceiver& _receiver; + }; + + friend class ReceiveThread; + + static void message_callback(SoupServer* server, SoupMessage* msg, const char* path, + GHashTable *query, SoupClientContext* client, void* data); + + ReceiveThread* _receive_thread; + SoupServer* _server; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_HTTPENGINERECEIVER_HPP diff --git a/src/server/InputPort.cpp b/src/server/InputPort.cpp new file mode 100644 index 00000000..a79f808b --- /dev/null +++ b/src/server/InputPort.cpp @@ -0,0 +1,229 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 "InputPort.hpp" +#include <cstdlib> +#include <cassert> +#include "ingen/Patch.hpp" +#include "shared/LV2URIMap.hpp" +#include "AudioBuffer.hpp" +#include "BufferFactory.hpp" +#include "ConnectionImpl.hpp" +#include "EventBuffer.hpp" +#include "NodeImpl.hpp" +#include "OutputPort.hpp" +#include "ProcessContext.hpp" +#include "ThreadManager.hpp" +#include "mix.hpp" +#include "util.hpp" + +using namespace std; + +namespace Ingen { +namespace Server { + +InputPort::InputPort(BufferFactory& bufs, + NodeImpl* parent, + const Raul::Symbol& symbol, + uint32_t index, + uint32_t poly, + PortType type, + const Raul::Atom& value, + size_t buffer_size) + : PortImpl(bufs, parent, symbol, index, poly, type, value, buffer_size) + , _num_connections(0) +{ + const Ingen::Shared::LV2URIMap& uris = bufs.uris(); + + if (!dynamic_cast<Patch*>(parent)) + add_property(uris.rdf_type, uris.lv2_InputPort); + + // Set default control range + if (type == PortType::CONTROL) { + set_property(uris.lv2_minimum, 0.0f); + set_property(uris.lv2_maximum, 1.0f); + } +} + +bool +InputPort::apply_poly(Maid& maid, uint32_t poly) +{ + bool ret = PortImpl::apply_poly(maid, poly); + if (!ret) + poly = 1; + + assert(_buffers->size() >= poly); + + return true; +} + +/** Set \a buffers appropriately if this port has \a num_connections connections. + * \return true iff buffers are locally owned by the port + */ +bool +InputPort::get_buffers(BufferFactory& bufs, + Raul::Array<BufferFactory::Ref>* buffers, + uint32_t poly) +{ + size_t num_connections = (ThreadManager::thread_is(THREAD_PROCESS)) + ? _connections.size() : _num_connections; + + if (buffer_type() == PortType::AUDIO && num_connections == 0) { + // Audio input with no connections, use shared zero buffer + for (uint32_t v = 0; v < poly; ++v) + buffers->at(v) = bufs.silent_buffer(); + return false; + + } else if (num_connections == 1) { + if (ThreadManager::thread_is(THREAD_PROCESS)) { + if (!_connections.front()->must_mix() && !_connections.front()->must_queue()) { + // Single non-mixing conneciton, use buffers directly + for (uint32_t v = 0; v < poly; ++v) + buffers->at(v) = _connections.front()->buffer(v); + return false; + } + } + } + + // Otherwise, allocate local buffers + for (uint32_t v = 0; v < poly; ++v) { + buffers->at(v) = _bufs.get(buffer_type(), _buffer_size); + buffers->at(v)->clear(); + } + return true; +} + +/** Add a connection. Realtime safe. + * + * The buffer of this port will be set directly to the connection's buffer + * if there is only one connection, since no copying/mixing needs to take place. + * + * Note that setup_buffers must be called after this before the change + * will audibly take effect. + */ +void +InputPort::add_connection(Connections::Node* const c) +{ + ThreadManager::assert_thread(THREAD_PROCESS); + + _connections.push_back(c); + + // Automatically broadcast connected control inputs + if (is_a(PortType::CONTROL)) + _broadcast = true; +} + +/** Remove a connection. Realtime safe. + * + * Note that setup_buffers must be called after this before the change + * will audibly take effect. + */ +InputPort::Connections::Node* +InputPort::remove_connection(ProcessContext& context, const OutputPort* src_port) +{ + ThreadManager::assert_thread(THREAD_PROCESS); + + Connections::Node* connection = NULL; + for (Connections::iterator i = _connections.begin(); i != _connections.end();) { + Connections::iterator next = i; + ++next; + + if ((*i)->src_port() == src_port) { + connection = _connections.erase(i); + break; + } + + i = next; + } + + if ( ! connection) { + error << "[InputPort::remove_connection] Connection not found!" << endl; + return NULL; + } + + // Turn off broadcasting if we're no longer connected + if (is_a(PortType::CONTROL) && _connections.size() == 0) + _broadcast = false; + + return connection; +} + +/** Prepare buffer for access, mixing if necessary. Realtime safe. + */ +void +InputPort::pre_process(Context& context) +{ + // If value has been set (e.g. events pushed) by the user, don't smash it + if (_set_by_user) + return; + + uint32_t max_num_srcs = 0; + for (Connections::iterator c = _connections.begin(); c != _connections.end(); ++c) + max_num_srcs += (*c)->src_port()->poly(); + + IntrusivePtr<Buffer> srcs[max_num_srcs]; + + if (_connections.empty()) { + for (uint32_t v = 0; v < _poly; ++v) { + buffer(v)->prepare_read(context); + } + } else if (direct_connect()) { + for (uint32_t v = 0; v < _poly; ++v) { + _buffers->at(v) = _connections.front()->buffer(v); + _buffers->at(v)->prepare_read(context); + } + } else { + for (uint32_t v = 0; v < _poly; ++v) { + uint32_t num_srcs = 0; + for (Connections::iterator c = _connections.begin(); c != _connections.end(); ++c) + (*c)->get_sources(context, v, srcs, max_num_srcs, num_srcs); + + mix(context, buffer(v).get(), srcs, num_srcs); + buffer(v)->prepare_read(context); + } + } + + if (_broadcast) + broadcast_value(context, false); +} + +void +InputPort::post_process(Context& context) +{ + if (_set_by_user) { + if (buffer_type() == PortType::EVENTS) { + // Clear events received via a SetPortValue + for (uint32_t v = 0; v < _poly; ++v) { + buffer(v)->clear(); + } + } + _set_by_user = false; + } +} + +bool +InputPort::direct_connect() const +{ + return (context() == Context::AUDIO) + && _connections.size() == 1 + && !_connections.front()->must_mix() + && !_connections.front()->must_queue(); +} + +} // namespace Server +} // namespace Ingen + diff --git a/src/server/InputPort.hpp b/src/server/InputPort.hpp new file mode 100644 index 00000000..0ecd5317 --- /dev/null +++ b/src/server/InputPort.hpp @@ -0,0 +1,93 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_INPUTPORT_HPP +#define INGEN_ENGINE_INPUTPORT_HPP + +#include <string> +#include <cstdlib> +#include <cassert> +#include "raul/List.hpp" +#include "raul/SharedPtr.hpp" +#include "PortImpl.hpp" + +namespace Ingen { +namespace Server { + +class ConnectionImpl; +class Context; +class NodeImpl; +class OutputPort; +class ProcessContext; + +/** An input port on a Node or Patch. + * + * All ports have a Buffer, but the actual contents (data) of that buffer may be + * set directly to the incoming connection's buffer if there's only one inbound + * connection, to eliminate the need to copy/mix. + * + * If a port has multiple connections, they will be mixed down into the local + * buffer and it will be used. + * + * \ingroup engine + */ +class InputPort : virtual public PortImpl +{ +public: + InputPort(BufferFactory& bufs, + NodeImpl* parent, + const Raul::Symbol& symbol, + uint32_t index, + uint32_t poly, + PortType type, + const Raul::Atom& value, + size_t buffer_size=0); + + virtual ~InputPort() {} + + typedef Raul::List< SharedPtr<ConnectionImpl> > Connections; + + void add_connection(Connections::Node* c); + Connections::Node* remove_connection(ProcessContext& context, const OutputPort* src_port); + + bool apply_poly(Raul::Maid& maid, uint32_t poly); + + bool get_buffers(BufferFactory& bufs, + Raul::Array<BufferFactory::Ref>* buffers, + uint32_t poly); + + void pre_process(Context& context); + void post_process(Context& context); + + size_t num_connections() const { return _num_connections; } ///< Pre-process thread + void increment_num_connections() { ++_num_connections; } + void decrement_num_connections() { --_num_connections; } + + bool is_input() const { return true; } + bool is_output() const { return false; } + + bool direct_connect() const; + +protected: + size_t _num_connections; ///< Pre-process thread + Connections _connections; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_INPUTPORT_HPP diff --git a/src/server/InternalPlugin.cpp b/src/server/InternalPlugin.cpp new file mode 100644 index 00000000..bcf30a47 --- /dev/null +++ b/src/server/InternalPlugin.cpp @@ -0,0 +1,72 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <cassert> +#include <string> +#include "shared/LV2URIMap.hpp" +#include "internals/Controller.hpp" +#include "internals/Delay.hpp" +#include "internals/Note.hpp" +#include "internals/Trigger.hpp" +#include "Driver.hpp" +#include "Engine.hpp" +#include "InternalPlugin.hpp" + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { + +using namespace Internals; + +InternalPlugin::InternalPlugin(Shared::LV2URIMap& uris, + const std::string& uri, const std::string& symbol) + : PluginImpl(uris, Plugin::Internal, uri) + , _symbol(symbol) +{ + set_property(uris.rdf_type, uris.ingen_Internal); +} + +NodeImpl* +InternalPlugin::instantiate(BufferFactory& bufs, + const string& name, + bool polyphonic, + PatchImpl* parent, + Engine& engine) +{ + assert(_type == Internal); + + const SampleCount srate = engine.driver()->sample_rate(); + + const string uri_str = uri().str(); + + if (uri_str == NS_INTERNALS "Controller") { + return new ControllerNode(this, bufs, name, polyphonic, parent, srate); + } else if (uri_str == NS_INTERNALS "Delay") { + return new DelayNode(this, bufs, name, polyphonic, parent, srate); + } else if (uri_str == NS_INTERNALS "Note") { + return new NoteNode(this, bufs, name, polyphonic, parent, srate); + } else if (uri_str == NS_INTERNALS "Trigger") { + return new TriggerNode(this, bufs, name, polyphonic, parent, srate); + } else { + return NULL; + } +} + +} // namespace Server +} // namespace Ingen diff --git a/src/server/InternalPlugin.hpp b/src/server/InternalPlugin.hpp new file mode 100644 index 00000000..be1df1d0 --- /dev/null +++ b/src/server/InternalPlugin.hpp @@ -0,0 +1,63 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_INTERNALPLUGIN_HPP +#define INGEN_ENGINE_INTERNALPLUGIN_HPP + +#include "ingen-config.h" + +#include <cstdlib> +#include <string> + +#include <boost/utility.hpp> +#include <glibmm/module.h> + +#include "PluginImpl.hpp" + +#define NS_INTERNALS "http://drobilla.net/ns/ingen-internals#" + +namespace Ingen { +namespace Server { + +class NodeImpl; +class BufferFactory; + +/** Implementation of an Internal plugin. + */ +class InternalPlugin : public PluginImpl +{ +public: + InternalPlugin(Shared::LV2URIMap& uris, + const std::string& uri, const std::string& symbol); + + NodeImpl* instantiate(BufferFactory& bufs, + const std::string& name, + bool polyphonic, + PatchImpl* parent, + Engine& engine); + + const std::string symbol() const { return _symbol; } + +private: + const std::string _symbol; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_INTERNALPLUGIN_HPP + diff --git a/src/server/JackDriver.cpp b/src/server/JackDriver.cpp new file mode 100644 index 00000000..e78c33af --- /dev/null +++ b/src/server/JackDriver.cpp @@ -0,0 +1,561 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 "ingen-config.h" + +#include <cstdlib> +#include <string> + +#include <jack/midiport.h> +#ifdef INGEN_JACK_SESSION +#include <jack/session.h> +#include <boost/format.hpp> +#include "serialisation/Serialiser.hpp" +#endif + +#include "raul/log.hpp" +#include "raul/List.hpp" + +#include "lv2/lv2plug.in/ns/ext/event/event.h" + +#include "AudioBuffer.hpp" +#include "ControlBindings.hpp" +#include "DuplexPort.hpp" +#include "Engine.hpp" +#include "Event.hpp" +#include "EventBuffer.hpp" +#include "EventSource.hpp" +#include "JackDriver.hpp" +#include "MessageContext.hpp" +#include "PatchImpl.hpp" +#include "PortImpl.hpp" +#include "PostProcessor.hpp" +#include "ProcessSlave.hpp" +#include "QueuedEvent.hpp" +#include "ThreadManager.hpp" +#include "shared/World.hpp" +#include "shared/LV2Features.hpp" +#include "shared/LV2URIMap.hpp" +#include "util.hpp" + +#define LOG(s) s << "[JackDriver] " + +using namespace std; +using namespace Raul; + +typedef jack_default_audio_sample_t jack_sample_t; + +namespace Ingen { +namespace Server { + +//// JackPort //// + +JackPort::JackPort(JackDriver* driver, DuplexPort* patch_port) + : DriverPort(patch_port) + , Raul::List<JackPort*>::Node(this) + , _driver(driver) + , _jack_port(NULL) +{ + patch_port->setup_buffers(*driver->_engine.buffer_factory(), patch_port->poly()); + create(); +} + +JackPort::~JackPort() +{ + assert(_jack_port == NULL); +} + +void +JackPort::create() +{ + _jack_port = jack_port_register( + _driver->jack_client(), + ingen_jack_port_name(_patch_port->path()).c_str(), + (_patch_port->buffer_type() == PortType::AUDIO) + ? JACK_DEFAULT_AUDIO_TYPE : JACK_DEFAULT_MIDI_TYPE, + (_patch_port->is_input()) + ? JackPortIsInput : JackPortIsOutput, + 0); + + if (_jack_port == NULL) { + error << "[JackPort] Failed to register port " << _patch_port->path() << endl; + throw JackDriver::PortRegistrationFailedException(); + } +} + +void +JackPort::destroy() +{ + assert(_jack_port); + if (jack_port_unregister(_driver->jack_client(), _jack_port)) + error << "[JackPort] Unable to unregister port" << endl; + _jack_port = NULL; +} + +void +JackPort::move(const Raul::Path& path) +{ + jack_port_set_name(_jack_port, ingen_jack_port_name(path).c_str()); +} + +void +JackPort::pre_process(ProcessContext& context) +{ + if (!is_input()) + return; + + const SampleCount nframes = context.nframes(); + + if (_patch_port->buffer_type() == PortType::AUDIO) { + jack_sample_t* jack_buf = (jack_sample_t*)jack_port_get_buffer(_jack_port, nframes); + AudioBuffer* patch_buf = (AudioBuffer*)_patch_port->buffer(0).get(); + + patch_buf->copy(jack_buf, 0, nframes - 1); + + } else if (_patch_port->buffer_type() == PortType::EVENTS) { + void* jack_buf = jack_port_get_buffer(_jack_port, nframes); + EventBuffer* patch_buf = (EventBuffer*)_patch_port->buffer(0).get(); + + const jack_nframes_t event_count = jack_midi_get_event_count(jack_buf); + + patch_buf->prepare_write(context); + + // Copy events from Jack port buffer into patch port buffer + for (jack_nframes_t i = 0; i < event_count; ++i) { + jack_midi_event_t ev; + jack_midi_event_get(&ev, jack_buf, i); + + if (!patch_buf->append(ev.time, 0, + _driver->_midi_event_type, + ev.size, ev.buffer)) + LOG(warn) << "Failed to write MIDI to port buffer, event(s) lost!" << endl; + } + } +} + +void +JackPort::post_process(ProcessContext& context) +{ + if (is_input()) + return; + + const SampleCount nframes = context.nframes(); + + if (_patch_port->buffer_type() == PortType::AUDIO) { + jack_sample_t* jack_buf = (jack_sample_t*)jack_port_get_buffer(_jack_port, nframes); + AudioBuffer* patch_buf = (AudioBuffer*)_patch_port->buffer(0).get(); + + memcpy(jack_buf, patch_buf->data(), nframes * sizeof(Sample)); + + } else if (_patch_port->buffer_type() == PortType::EVENTS) { + void* jack_buf = jack_port_get_buffer(_jack_port, context.nframes()); + EventBuffer* patch_buf = (EventBuffer*)_patch_port->buffer(0).get(); + + patch_buf->prepare_read(context); + jack_midi_clear_buffer(jack_buf); + + uint32_t frames = 0; + uint32_t subframes = 0; + uint16_t type = 0; + uint16_t size = 0; + uint8_t* data = NULL; + + // Copy events from Jack port buffer into patch port buffer + for (patch_buf->rewind(); patch_buf->is_valid(); patch_buf->increment()) { + patch_buf->get_event(&frames, &subframes, &type, &size, &data); + jack_midi_event_write(jack_buf, frames, data, size); + } + } +} + +//// JackDriver //// + +JackDriver::JackDriver(Engine& engine) + : _engine(engine) + , _jack_thread(NULL) + , _sem(0) + , _flag(0) + , _client(NULL) + , _block_length(0) + , _sample_rate(0) + , _is_activated(false) + , _process_context(engine) + , _root_patch(NULL) +{ + _midi_event_type = _engine.world()->uris()->uri_to_id( + LV2_EVENT_URI, "http://lv2plug.in/ns/ext/midi#MidiEvent"); +} + +JackDriver::~JackDriver() +{ + deactivate(); + + if (_client) + jack_client_close(_client); +} + +bool +JackDriver::supports(PortType port_type, EventType event_type) +{ + return (port_type == PortType::AUDIO + || (port_type == PortType::EVENTS && event_type == EventType::MIDI)); +} + +bool +JackDriver::attach(const std::string& server_name, + const std::string& client_name, + void* jack_client) +{ + assert(!_client); + if (!jack_client) { + #ifdef INGEN_JACK_SESSION + const std::string uuid = _engine.world()->jack_uuid(); + if (!uuid.empty()) { + _client = jack_client_open(client_name.c_str(), + JackSessionID, NULL, + uuid.c_str()); + LOG(info) << "Connected to JACK server as client `" + << client_name.c_str() << "' UUID `" << uuid << "'" << endl; + } + #endif + + // Try supplied server name + if (!_client && !server_name.empty()) { + if ((_client = jack_client_open(client_name.c_str(), + JackServerName, NULL, + server_name.c_str()))) { + LOG(info) << "Connected to JACK server `" << server_name << "'" << endl; + } + } + + // Either server name not specified, or supplied server name does not exist + // Connect to default server + if (!_client) { + if ((_client = jack_client_open(client_name.c_str(), JackNullOption, NULL))) + LOG(info) << "Connected to default JACK server" << endl; + } + + // Still failed + if (!_client) { + LOG(error) << "Unable to connect to Jack" << endl; + return false; + } + } else { + _client = (jack_client_t*)jack_client; + } + + _block_length = jack_get_buffer_size(_client); + _sample_rate = jack_get_sample_rate(_client); + + jack_on_shutdown(_client, shutdown_cb, this); + + jack_set_thread_init_callback(_client, thread_init_cb, this); + jack_set_sample_rate_callback(_client, sample_rate_cb, this); + jack_set_buffer_size_callback(_client, block_length_cb, this); +#ifdef INGEN_JACK_SESSION + jack_set_session_callback(_client, session_cb, this); +#endif + + for (Raul::List<JackPort*>::iterator i = _ports.begin(); i != _ports.end(); ++i) + (*i)->create(); + + return true; +} + +void +JackDriver::activate() +{ + Shared::World* world = _engine.world(); + + if (_is_activated) { + LOG(warn) << "Jack driver already activated." << endl; + return; + } + + if (!_client) + attach(world->conf()->option("jack-server").get_string(), + world->conf()->option("jack-client").get_string(), NULL); + + jack_set_process_callback(_client, process_cb, this); + + _is_activated = true; + + _process_context.activate(world->conf()->option("parallelism").get_int32(), + is_realtime()); + + if (jack_activate(_client)) { + LOG(error) << "Could not activate Jack client, aborting." << endl; + exit(EXIT_FAILURE); + } else { + LOG(info) << "Activated Jack client." << endl; + } +} + +void +JackDriver::deactivate() +{ + if (_is_activated) { + _flag = 1; + _is_activated = false; + _sem.wait(); + + for (Raul::List<JackPort*>::iterator i = _ports.begin(); i != _ports.end(); ++i) + (*i)->destroy(); + + if (_client) { + jack_deactivate(_client); + jack_client_close(_client); + _client = NULL; + } + + _jack_thread->stop(); + LOG(info) << "Deactivated Jack client" << endl; + } +} + +/** Add a Jack port. + * + * Realtime safe, this is to be called at the beginning of a process cycle to + * insert (and actually begin using) a new port. + * + * See create_port() and remove_port(). + */ +void +JackDriver::add_port(DriverPort* port) +{ + ThreadManager::assert_thread(THREAD_PROCESS); + assert(dynamic_cast<JackPort*>(port)); + _ports.push_back((JackPort*)port); +} + +/** Remove a Jack port. + * + * Realtime safe. This is to be called at the beginning of a process cycle to + * remove the port from the lists read by the audio thread, so the port + * will no longer be used and can be removed afterwards. + * + * It is the callers responsibility to delete the returned port. + */ +Raul::Deletable* +JackDriver::remove_port(const Path& path, DriverPort** port) +{ + ThreadManager::assert_thread(THREAD_PROCESS); + + for (Raul::List<JackPort*>::iterator i = _ports.begin(); i != _ports.end(); ++i) { + if ((*i)->patch_port()->path() == path) { + Raul::List<JackPort*>::Node* node = _ports.erase(i); + if (port) + *port = node->elem(); + return node; + } + } + + LOG(warn) << "Unable to find port " << path << endl; + return NULL; +} + +DriverPort* +JackDriver::port(const Path& path) +{ + for (Raul::List<JackPort*>::iterator i = _ports.begin(); i != _ports.end(); ++i) + if ((*i)->patch_port()->path() == path) + return (*i); + + return NULL; +} + +DriverPort* +JackDriver::create_port(DuplexPort* patch_port) +{ + try { + if (patch_port->buffer_type() == PortType::AUDIO + || patch_port->buffer_type() == PortType::EVENTS) + return new JackPort(this, patch_port); + else + return NULL; + } catch (...) { + return NULL; + } +} + +DriverPort* +JackDriver::driver_port(const Path& path) +{ + ThreadManager::assert_thread(THREAD_PROCESS); + + for (Raul::List<JackPort*>::iterator i = _ports.begin(); i != _ports.end(); ++i) + if ((*i)->patch_port()->path() == path) + return (*i); + + return NULL; +} + +/**** Jack Callbacks ****/ + +/** Jack process callback, drives entire audio thread. + * + * \callgraph + */ +int +JackDriver::_process_cb(jack_nframes_t nframes) +{ + if (nframes == 0 || ! _is_activated) { + if (_flag == 1) + _sem.post(); + return 0; + } + + // FIXME: all of this time stuff is screwy + + // FIXME: support nframes != buffer_size, even though that never damn well happens + //assert(nframes == _block_length); + + // Note that Jack can not call this function for a cycle, if overloaded + const jack_nframes_t start_of_current_cycle = jack_last_frame_time(_client); + + _transport_state = jack_transport_query(_client, &_position); + + _process_context.locate(start_of_current_cycle, nframes, 0); + + for (ProcessContext::Slaves::iterator i = _process_context.slaves().begin(); + i != _process_context.slaves().end(); ++i) { + (*i)->context().locate(start_of_current_cycle, nframes, 0); + } + + // Read input + for (Raul::List<JackPort*>::iterator i = _ports.begin(); i != _ports.end(); ++i) + (*i)->pre_process(_process_context); + + // Apply control bindings to input + _engine.control_bindings()->pre_process(_process_context, + PtrCast<EventBuffer>(_root_patch->port_impl(0)->buffer(0)).get()); + + _engine.post_processor()->set_end_time(_process_context.end()); + + // Process events that came in during the last cycle + // (Aiming for jitter-free 1 block event latency, ideally) + _engine.process_events(_process_context); + + // Run root patch + if (_root_patch) { + _root_patch->process(_process_context); +#if 0 + static const SampleCount control_block_size = nframes / 2; + for (jack_nframes_t i = 0; i < nframes; i += control_block_size) { + const SampleCount block_size = (i + control_block_size < nframes) + ? control_block_size + : nframes - i; + _process_context.locate(start_of_current_cycle + i, block_size, i); + _root_patch->process(_process_context); + } +#endif + } + + // Emit control binding feedback + _engine.control_bindings()->post_process(_process_context, + PtrCast<EventBuffer>(_root_patch->port_impl(1)->buffer(0)).get()); + + // Signal message context to run if necessary + if (_engine.message_context()->has_requests()) + _engine.message_context()->signal(_process_context); + + // Write output + for (Raul::List<JackPort*>::iterator i = _ports.begin(); i != _ports.end(); ++i) + (*i)->post_process(_process_context); + + return 0; +} + +void +JackDriver::_thread_init_cb() +{ + // Initialize thread specific data + _jack_thread = Thread::create_for_this_thread("Jack"); + assert(&Thread::get() == _jack_thread); + _jack_thread->set_context(THREAD_PROCESS); + ThreadManager::assert_thread(THREAD_PROCESS); +} + +void +JackDriver::_shutdown_cb() +{ + LOG(info) << "Jack shutdown. Exiting." << endl; + _is_activated = false; + delete _jack_thread; + _jack_thread = NULL; + _client = NULL; +} + +int +JackDriver::_sample_rate_cb(jack_nframes_t nframes) +{ + if (_is_activated) { + LOG(error) << "On-the-fly sample rate changing not supported (yet). Aborting." << endl; + exit(EXIT_FAILURE); + } else { + _sample_rate = nframes; + } + return 0; +} + +int +JackDriver::_block_length_cb(jack_nframes_t nframes) +{ + if (_root_patch) { + _block_length = nframes; + _root_patch->set_buffer_size(context(), *_engine.buffer_factory(), PortType::AUDIO, + _engine.buffer_factory()->audio_buffer_size(nframes)); + } + return 0; +} + +#ifdef INGEN_JACK_SESSION +void +JackDriver::_session_cb(jack_session_event_t* event) +{ + LOG(info) << "Jack session save to " << event->session_dir << endl; + + const string cmd = (boost::format("ingen -eg -n %1% -u %2% -l ${SESSION_DIR}") + % jack_get_client_name(_client) + % event->client_uuid).str(); + + SharedPtr<Serialisation::Serialiser> serialiser = _engine.world()->serialiser(); + if (serialiser) { + SharedPtr<Patch> root(_engine.driver()->root_patch(), NullDeleter<Patch>); + serialiser->write_bundle(root, string("file://") + event->session_dir); + } + + event->command_line = strdup(cmd.c_str()); + jack_session_reply(_client, event); + + switch (event->type) { + case JackSessionSave: + break; + case JackSessionSaveAndQuit: + LOG(warn) << "Jack session quit" << endl; + _engine.quit(); + break; + case JackSessionSaveTemplate: + break; + } + + jack_session_event_free(event); +} +#endif + +} // namespace Server +} // namespace Ingen diff --git a/src/server/JackDriver.hpp b/src/server/JackDriver.hpp new file mode 100644 index 00000000..5439c95c --- /dev/null +++ b/src/server/JackDriver.hpp @@ -0,0 +1,183 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_JACKAUDIODRIVER_HPP +#define INGEN_ENGINE_JACKAUDIODRIVER_HPP + +#include "ingen-config.h" + +#include <string> + +#include <jack/jack.h> +#include <jack/transport.h> +#ifdef INGEN_JACK_SESSION +#include <jack/session.h> +#endif + +#include "raul/AtomicInt.hpp" +#include "raul/List.hpp" +#include "raul/Semaphore.hpp" +#include "raul/Thread.hpp" + +#include "Buffer.hpp" +#include "Driver.hpp" +#include "ProcessContext.hpp" + +namespace Raul { class Path; } + +namespace Ingen { +namespace Server { + +class Engine; +class PatchImpl; +class PortImpl; +class DuplexPort; +class JackDriver; + +/** Used internally by JackDriver to represent a Jack port. + * + * A Jack port always has a one-to-one association with a Patch port. + */ +class JackPort : public DriverPort, public Raul::List<JackPort*>::Node +{ +public: + JackPort(JackDriver* driver, DuplexPort* patch_port); + ~JackPort(); + + void create(); + void destroy(); + + void move(const Raul::Path& path); + + void pre_process(ProcessContext& context); + void post_process(ProcessContext& context); + + jack_port_t* jack_port() const { return _jack_port; } + +private: + JackDriver* _driver; + jack_port_t* _jack_port; +}; + +/** The Jack Driver. + * + * The process callback here drives the entire audio thread by "pulling" + * events from queues, processing them, running the patches, and passing + * events along to the PostProcessor. + * + * \ingroup engine + */ +class JackDriver : public Driver +{ +public: + explicit JackDriver(Engine& engine); + ~JackDriver(); + + bool supports(PortType port_type, EventType event_type); + + bool attach(const std::string& server_name, + const std::string& client_name, + void* jack_client); + + void activate(); + void deactivate(); + void enable(); + void disable(); + + DriverPort* port(const Raul::Path& path); + DriverPort* create_port(DuplexPort* patch_port); + + void add_port(DriverPort* port); + DriverPort* driver_port(const Raul::Path& path); + + Raul::Deletable* remove_port(const Raul::Path& path, DriverPort** port=NULL); + + PatchImpl* root_patch() { return _root_patch; } + void set_root_patch(PatchImpl* patch) { _root_patch = patch; } + + ProcessContext& context() { return _process_context; } + + /** Transport state for this frame. + * Intended to only be called from the audio thread. */ + inline const jack_position_t* position() { return &_position; } + inline jack_transport_state_t transport_state() { return _transport_state; } + + bool is_realtime() const { return jack_is_realtime(_client); } + + jack_client_t* jack_client() const { return _client; } + SampleCount block_length() const { return _block_length; } + SampleCount sample_rate() const { return _sample_rate; } + + inline SampleCount frame_time() const { return _client ? jack_frame_time(_client) : 0; } + + class PortRegistrationFailedException : public std::exception {}; + +private: + friend class JackPort; + + // Static JACK callbacks which call the non-static callbacks (methods) + inline static void thread_init_cb(void* const jack_driver) { + return ((JackDriver*)jack_driver)->_thread_init_cb(); + } + inline static void shutdown_cb(void* const jack_driver) { + return ((JackDriver*)jack_driver)->_shutdown_cb(); + } + inline static int process_cb(jack_nframes_t nframes, void* const jack_driver) { + return ((JackDriver*)jack_driver)->_process_cb(nframes); + } + inline static int block_length_cb(jack_nframes_t nframes, void* const jack_driver) { + return ((JackDriver*)jack_driver)->_block_length_cb(nframes); + } + inline static int sample_rate_cb(jack_nframes_t nframes, void* const jack_driver) { + return ((JackDriver*)jack_driver)->_sample_rate_cb(nframes); + } +#ifdef INGEN_JACK_SESSION + inline static void session_cb(jack_session_event_t* event, void* jack_driver) { + ((JackDriver*)jack_driver)->_session_cb(event); + } +#endif + + // Non static callbacks (methods) + void _thread_init_cb(); + void _shutdown_cb(); + int _process_cb(jack_nframes_t nframes); + int _block_length_cb(jack_nframes_t nframes); + int _sample_rate_cb(jack_nframes_t nframes); +#ifdef INGEN_JACK_SESSION + void _session_cb(jack_session_event_t* event); +#endif + + Engine& _engine; + Raul::Thread* _jack_thread; + Raul::Semaphore _sem; + Raul::AtomicInt _flag; + jack_client_t* _client; + jack_nframes_t _block_length; + jack_nframes_t _sample_rate; + uint32_t _midi_event_type; + bool _is_activated; + jack_position_t _position; + jack_transport_state_t _transport_state; + Raul::List<JackPort*> _ports; + ProcessContext _process_context; + PatchImpl* _root_patch; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_JACKAUDIODRIVER_HPP diff --git a/src/server/LV2BlobFeature.hpp b/src/server/LV2BlobFeature.hpp new file mode 100644 index 00000000..0e892219 --- /dev/null +++ b/src/server/LV2BlobFeature.hpp @@ -0,0 +1,66 @@ +/* This file is part of Ingen. + * Copyright 2009-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_LV2BLOBFEATURE_HPP +#define INGEN_ENGINE_LV2BLOBFEATURE_HPP + +#include "shared/LV2Features.hpp" + +namespace Ingen { +namespace Server { + +struct BlobFeature : public Ingen::Shared::LV2Features::Feature { + BlobFeature() { + LV2_Blob_Support* data = (LV2_Blob_Support*)malloc(sizeof(LV2_Blob_Support)); + data->data = NULL; + data->ref_size = sizeof(LV2_Blob); + data->ref_get = &ref_get; + data->ref_copy = &ref_copy; + data->ref_reset = &ref_reset; + data->blob_new = &blob_new; + _feature.URI = LV2_BLOB_SUPPORT_URI; + _feature.data = data; + } + + static LV2_Blob ref_get(LV2_Blob_Support_Data data, + LV2_Atom_Reference* ref) { return 0; } + + static void ref_copy(LV2_Blob_Support_Data data, + LV2_Atom_Reference* dst, + LV2_Atom_Reference* src) {} + + static void ref_reset(LV2_Blob_Support_Data data, + LV2_Atom_Reference* ref) {} + + static void blob_new(LV2_Blob_Support_Data data, + LV2_Atom_Reference* reference, + LV2_Blob_Destroy destroy, + uint32_t type, + size_t size) {} + + SharedPtr<LV2_Feature> feature(Shared::World*, Node*) { + return SharedPtr<LV2_Feature>(&_feature, NullDeleter<LV2_Feature>); + } + +private: + LV2_Feature _feature; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_LV2BLOBFEATURE_HPP diff --git a/src/server/LV2EventFeature.hpp b/src/server/LV2EventFeature.hpp new file mode 100644 index 00000000..5e2f1f31 --- /dev/null +++ b/src/server/LV2EventFeature.hpp @@ -0,0 +1,54 @@ +/* This file is part of Ingen. + * Copyright 2009-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_LV2EVENTFEATURE_HPP +#define INGEN_ENGINE_LV2EVENTFEATURE_HPP + +#include "lv2/lv2plug.in/ns/ext/event/event.h" +#include "shared/LV2Features.hpp" + +namespace Ingen { +namespace Server { + +struct EventFeature : public Ingen::Shared::LV2Features::Feature { + EventFeature() { + LV2_Event_Feature* data = (LV2_Event_Feature*)malloc(sizeof(LV2_Event_Feature)); + data->lv2_event_ref = &event_ref; + data->lv2_event_unref = &event_unref; + data->callback_data = this; + _feature.URI = LV2_EVENT_URI; + _feature.data = data; + } + + static uint32_t event_ref(LV2_Event_Callback_Data callback_data, + LV2_Event* event) { return 0; } + + static uint32_t event_unref(LV2_Event_Callback_Data callback_data, + LV2_Event* event) { return 0; } + + SharedPtr<LV2_Feature> feature(Shared::World*, Node*) { + return SharedPtr<LV2_Feature>(&_feature, NullDeleter<LV2_Feature>); + } + +private: + LV2_Feature _feature; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_LV2EVENTFEATURE_HPP diff --git a/src/server/LV2Info.cpp b/src/server/LV2Info.cpp new file mode 100644 index 00000000..6ce5792b --- /dev/null +++ b/src/server/LV2Info.cpp @@ -0,0 +1,77 @@ +/* This file is part of Ingen. + * Copyright 2008-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#define __STDC_LIMIT_MACROS 1 + +#include <stdint.h> + +#include <cassert> + +#include "lv2/lv2plug.in/ns/ext/atom/atom.h" +#include "lv2/lv2plug.in/ns/ext/contexts/contexts.h" + +#include "shared/World.hpp" + +#include "LV2BlobFeature.hpp" +#include "LV2EventFeature.hpp" +#include "LV2Features.hpp" +#include "LV2Info.hpp" +#include "LV2RequestRunFeature.hpp" +#include "LV2ResizeFeature.hpp" + +using namespace std; + +namespace Ingen { +namespace Server { + +LV2Info::LV2Info(Ingen::Shared::World* world) + : input_class(slv2_value_new_uri(world->slv2_world(), SLV2_PORT_CLASS_INPUT)) + , output_class(slv2_value_new_uri(world->slv2_world(), SLV2_PORT_CLASS_OUTPUT)) + , control_class(slv2_value_new_uri(world->slv2_world(), SLV2_PORT_CLASS_CONTROL)) + , audio_class(slv2_value_new_uri(world->slv2_world(), SLV2_PORT_CLASS_AUDIO)) + , event_class(slv2_value_new_uri(world->slv2_world(), SLV2_PORT_CLASS_EVENT)) + , value_port_class(slv2_value_new_uri(world->slv2_world(), + "http://lv2plug.in/ns/ext/atom#ValuePort")) + , message_port_class(slv2_value_new_uri(world->slv2_world(), + "http://lv2plug.in/ns/ext/atom#MessagePort")) + , _world(world) +{ + assert(world); + + world->lv2_features()->add_feature(LV2_EVENT_URI, + SharedPtr<Shared::LV2Features::Feature>(new EventFeature())); + world->lv2_features()->add_feature(LV2_BLOB_SUPPORT_URI, + SharedPtr<Shared::LV2Features::Feature>(new BlobFeature())); + world->lv2_features()->add_feature(LV2_RESIZE_PORT_URI, + SharedPtr<Shared::LV2Features::Feature>(new ResizeFeature())); + world->lv2_features()->add_feature(LV2_CONTEXTS_URI "#RequestRunFeature", + SharedPtr<Shared::LV2Features::Feature>(new RequestRunFeature())); +} + +LV2Info::~LV2Info() +{ + slv2_value_free(input_class); + slv2_value_free(output_class); + slv2_value_free(control_class); + slv2_value_free(audio_class); + slv2_value_free(event_class); + slv2_value_free(value_port_class); + slv2_value_free(message_port_class); +} + +} // namespace Server +} // namespace Ingen diff --git a/src/server/LV2Info.hpp b/src/server/LV2Info.hpp new file mode 100644 index 00000000..de252d82 --- /dev/null +++ b/src/server/LV2Info.hpp @@ -0,0 +1,62 @@ +/* This file is part of Ingen. + * Copyright 2008-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_LV2INFO_HPP +#define INGEN_ENGINE_LV2INFO_HPP + +#include "ingen-config.h" +#ifndef HAVE_SLV2 +#error "This file requires SLV2, but HAVE_SLV2 is not defined. Please report." +#endif + +#include <map> +#include <string> +#include "slv2/slv2.h" +#include "shared/World.hpp" + +namespace Ingen { + +class Node; + +namespace Server { + +/** Stuff that may need to be passed to an LV2 plugin (i.e. LV2 features). + */ +class LV2Info { +public: + explicit LV2Info(Ingen::Shared::World* world); + ~LV2Info(); + + SLV2Value input_class; + SLV2Value output_class; + SLV2Value control_class; + SLV2Value audio_class; + SLV2Value event_class; + SLV2Value value_port_class; + SLV2Value message_port_class; + + Ingen::Shared::World& world() { return *_world; } + SLV2World lv2_world() { return _world->slv2_world(); } + +private: + Ingen::Shared::World* _world; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_LV2INFO_HPP diff --git a/src/server/LV2Node.cpp b/src/server/LV2Node.cpp new file mode 100644 index 00000000..26f1e918 --- /dev/null +++ b/src/server/LV2Node.cpp @@ -0,0 +1,410 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <float.h> +#include <stdint.h> + +#include <cassert> +#include <cmath> +#include <string> + +#include "raul/log.hpp" +#include "raul/Maid.hpp" +#include "raul/Array.hpp" + +#include "AudioBuffer.hpp" +#include "EventBuffer.hpp" +#include "InputPort.hpp" +#include "LV2Node.hpp" +#include "LV2Plugin.hpp" +#include "LV2URIMap.hpp" +#include "MessageContext.hpp" +#include "OutputPort.hpp" +#include "ProcessContext.hpp" + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { + +/** Partially construct a LV2Node. + * + * Object is not usable until instantiate() is called with success. + * (It _will_ crash!) + */ +LV2Node::LV2Node(LV2Plugin* plugin, + const string& name, + bool polyphonic, + PatchImpl* parent, + SampleRate srate) + : NodeImpl(plugin, name, polyphonic, parent, srate) + , _lv2_plugin(plugin) + , _instances(NULL) + , _prepared_instances(NULL) + , _message_funcs(NULL) +{ + assert(_lv2_plugin); +} + +LV2Node::~LV2Node() +{ + delete _instances; +} + +bool +LV2Node::prepare_poly(BufferFactory& bufs, uint32_t poly) +{ + if (!_polyphonic) + poly = 1; + + NodeImpl::prepare_poly(bufs, poly); + + if (_polyphony == poly) + return true; + + SharedPtr<LV2Info> info = _lv2_plugin->lv2_info(); + _prepared_instances = new Instances(poly, *_instances, SharedPtr<void>()); + for (uint32_t i = _polyphony; i < _prepared_instances->size(); ++i) { + _prepared_instances->at(i) = SharedPtr<void>( + slv2_plugin_instantiate( + _lv2_plugin->slv2_plugin(), _srate, _features->array()), + slv2_instance_free); + + if (!_prepared_instances->at(i)) { + error << "Failed to instantiate plugin" << endl; + return false; + } + + // Initialize the values of new ports + for (uint32_t j = 0; j < num_ports(); ++j) { + PortImpl* const port = _ports->at(j); + Buffer* const buffer = port->prepared_buffer(i).get(); + if (buffer) { + if (port->is_a(PortType::CONTROL)) { + ((AudioBuffer*)buffer)->set_value(port->value().get_float(), 0, 0); + } else { + buffer->clear(); + } + } + } + + if (_activated) + slv2_instance_activate((SLV2Instance)(*_prepared_instances)[i].get()); + } + + return true; +} + +bool +LV2Node::apply_poly(Raul::Maid& maid, uint32_t poly) +{ + if (!_polyphonic) + poly = 1; + + if (_prepared_instances) { + maid.push(_instances); + _instances = _prepared_instances; + _prepared_instances = NULL; + } + assert(poly <= _instances->size()); + + return NodeImpl::apply_poly(maid, poly); +} + +/** Instantiate self from LV2 plugin descriptor. + * + * Implemented as a seperate function (rather than in the constructor) to + * allow graceful error-catching of broken plugins. + * + * Returns whether or not plugin was successfully instantiated. If return + * value is false, this object may not be used. + */ +bool +LV2Node::instantiate(BufferFactory& bufs) +{ + const Ingen::Shared::LV2URIMap& uris = bufs.uris(); + SharedPtr<LV2Info> info = _lv2_plugin->lv2_info(); + SLV2Plugin plug = _lv2_plugin->slv2_plugin(); + + uint32_t num_ports = slv2_plugin_get_num_ports(plug); + assert(num_ports > 0); + + _ports = new Raul::Array<PortImpl*>(num_ports, NULL); + _instances = new Instances(_polyphony, SharedPtr<void>()); + + _features = info->world().lv2_features()->lv2_features(&info->world(), this); + + uint32_t port_buffer_size = 0; + SLV2Value ctx_ext_uri = slv2_value_new_uri(info->lv2_world(), + LV2_CONTEXTS_URI "#MessageContext"); + + for (uint32_t i = 0; i < _polyphony; ++i) { + (*_instances)[i] = SharedPtr<void>( + slv2_plugin_instantiate(plug, _srate, _features->array()), + slv2_instance_free); + + if (!instance(i)) { + error << "Failed to instantiate plugin " << _lv2_plugin->uri() + << " voice " << i << endl; + return false; + } + + if (!slv2_plugin_has_feature(plug, ctx_ext_uri)) + continue; + + const void* ctx_ext = slv2_instance_get_extension_data( + instance(i), LV2_CONTEXTS_URI "#MessageContext"); + + if (i == 0 && ctx_ext) { + assert(!_message_funcs); + _message_funcs = (LV2_Contexts_MessageContext*)ctx_ext; + } + } + + slv2_value_free(ctx_ext_uri); + + string port_name; + Path port_path; + + PortImpl* port = NULL; + bool ret = true; + + float* min_values = new float[num_ports]; + float* max_values = new float[num_ports]; + float* def_values = new float[num_ports]; + slv2_plugin_get_port_ranges_float(plug, min_values, max_values, def_values); + + SLV2Value context_pred = slv2_value_new_uri(info->lv2_world(), + "http://lv2plug.in/ns/ext/contexts#context"); + + SLV2Value default_pred = slv2_value_new_uri(info->lv2_world(), + "http://lv2plug.in/ns/lv2core#default"); + + SLV2Value min_size_pred = slv2_value_new_uri(info->lv2_world(), + "http://lv2plug.in/ns/ext/resize-port#minimumSize"); + + SLV2Value port_property_pred = slv2_value_new_uri(info->lv2_world(), + "http://lv2plug.in/ns/lv2core#portProperty"); + + SLV2Value supports_pred = slv2_value_new_uri(info->lv2_world(), + "http://lv2plug.in/ns/ext/atom#supports"); + + //SLV2Value as_large_as_pred = slv2_value_new_uri(info->lv2_world(), + // "http://lv2plug.in/ns/ext/resize-port#asLargeAs"); + + for (uint32_t j = 0; j < num_ports; ++j) { + SLV2Port id = slv2_plugin_get_port_by_index(plug, j); + + // LV2 port symbols are guaranteed to be unique, valid C identifiers + port_name = slv2_value_as_string(slv2_port_get_symbol(plug, id)); + + if (!Symbol::is_valid(port_name)) { + error << "Plugin " << _lv2_plugin->uri() << " port " << j + << " has illegal symbol `" << port_name << "'" << endl; + ret = false; + break; + } + + assert(port_name.find('/') == string::npos); + + port_path = path().child(port_name); + + Raul::Atom val; + PortType data_type = PortType::UNKNOWN; + if (slv2_port_is_a(plug, id, info->control_class)) { + data_type = PortType::CONTROL; + } else if (slv2_port_is_a(plug, id, info->audio_class)) { + data_type = PortType::AUDIO; + } else if (slv2_port_is_a(plug, id, info->event_class)) { + data_type = PortType::EVENTS; + } else if (slv2_port_is_a(plug, id, info->value_port_class)) { + data_type = PortType::VALUE; + } else if (slv2_port_is_a(plug, id, info->message_port_class)) { + data_type = PortType::MESSAGE; + } + + port_buffer_size = bufs.default_buffer_size(data_type); + + if (data_type == PortType::VALUE || data_type == PortType::MESSAGE) { + // Get default value, and its length + SLV2Values defaults = slv2_port_get_value(plug, id, default_pred); + SLV2_FOREACH(i, defaults) { + SLV2Value d = slv2_values_get(defaults, i); + if (slv2_value_is_string(d)) { + const char* str_val = slv2_value_as_string(d); + const size_t str_val_len = strlen(str_val); + val = str_val; + port_buffer_size = str_val_len; + } + } + + // Get minimum size, if set in data + SLV2Values sizes = slv2_port_get_value(plug, id, min_size_pred); + SLV2_FOREACH(i, sizes) { + SLV2Value d = slv2_values_get(sizes, i); + if (slv2_value_is_int(d)) { + size_t size_val = slv2_value_as_int(d); + port_buffer_size = size_val; + } + } + } + + enum { UNKNOWN, INPUT, OUTPUT } direction = UNKNOWN; + if (slv2_port_is_a(plug, id, info->input_class)) { + direction = INPUT; + } else if (slv2_port_is_a(plug, id, info->output_class)) { + direction = OUTPUT; + } + + if (data_type == PortType::UNKNOWN || direction == UNKNOWN) { + warn << "Unknown type or direction for port `" << port_name << "'" << endl; + ret = false; + break; + } + + if (val.type() == Atom::NIL) + val = isnan(def_values[j]) ? 0.0f : def_values[j]; + + if (direction == INPUT) + port = new InputPort(bufs, this, port_name, j, _polyphony, data_type, val); + else + port = new OutputPort(bufs, this, port_name, j, _polyphony, data_type, val); + + if (direction == INPUT && data_type == PortType::CONTROL) { + port->set_value(val); + if (!isnan(min_values[j])) { + port->set_property(uris.lv2_minimum, min_values[j]); + } + if (!isnan(max_values[j])) { + port->set_property(uris.lv2_maximum, max_values[j]); + } + } + + // Set lv2:portProperty properties + SLV2Values properties = slv2_port_get_value(plug, id, port_property_pred); + SLV2_FOREACH(i, properties) { + SLV2Value p = slv2_values_get(properties, i); + if (slv2_value_is_uri(p)) { + port->set_property(uris.lv2_portProperty, Raul::URI(slv2_value_as_uri(p))); + } + } + + // Set atom:supports properties + SLV2Values types = slv2_port_get_value(plug, id, supports_pred); + SLV2_FOREACH(i, types) { + SLV2Value type = slv2_values_get(types, i); + if (slv2_value_is_uri(type)) { + port->add_property(uris.atom_supports, Raul::URI(slv2_value_as_uri(type))); + } + } + + SLV2Values contexts = slv2_port_get_value(plug, id, context_pred); + SLV2_FOREACH(i, contexts) { + SLV2Value c = slv2_values_get(contexts, i); + const char* context = slv2_value_as_string(c); + if (!strcmp(LV2_CONTEXTS_URI "#MessageContext", context)) { + if (!_message_funcs) { + warn << _lv2_plugin->uri() + << " has a message port, but no context extension data." << endl; + } + port->set_context(Context::MESSAGE); + } else { + warn << _lv2_plugin->uri() << " port " << i << " has unknown context " + << slv2_value_as_string(c) + << endl; + } + } + + _ports->at(j) = port; + } + + if (!ret) { + delete _ports; + _ports = NULL; + delete _instances; + _instances = NULL; + } + + delete[] min_values; + delete[] max_values; + delete[] def_values; + slv2_value_free(context_pred); + slv2_value_free(default_pred); + slv2_value_free(min_size_pred); + slv2_value_free(port_property_pred); + + return ret; +} + +void +LV2Node::activate(BufferFactory& bufs) +{ + NodeImpl::activate(bufs); + + for (uint32_t i = 0; i < _polyphony; ++i) + slv2_instance_activate(instance(i)); +} + +void +LV2Node::deactivate() +{ + NodeImpl::deactivate(); + + for (uint32_t i = 0; i < _polyphony; ++i) + slv2_instance_deactivate(instance(i)); +} + +void +LV2Node::message_run(MessageContext& context) +{ + for (size_t i = 0; i < num_ports(); ++i) { + PortImpl* const port = _ports->at(i); + if (port->context() == Context::MESSAGE) + port->pre_process(context); + } + + if (!_valid_ports) + _valid_ports = calloc(num_ports() / 8, 1); + + if (_message_funcs) + (*_message_funcs->run)(instance(0)->lv2_handle, _valid_ports, _valid_ports); +} + +void +LV2Node::process(ProcessContext& context) +{ + NodeImpl::pre_process(context); + + for (uint32_t i = 0; i < _polyphony; ++i) + slv2_instance_run(instance(i), context.nframes()); + + NodeImpl::post_process(context); +} + +void +LV2Node::set_port_buffer(uint32_t voice, uint32_t port_num, + IntrusivePtr<Buffer> buf, SampleCount offset) +{ + NodeImpl::set_port_buffer(voice, port_num, buf, offset); + slv2_instance_connect_port(instance(voice), port_num, + buf ? buf->port_data(_ports->at(port_num)->buffer_type(), offset) : NULL); +} + +} // namespace Server +} // namespace Ingen + diff --git a/src/server/LV2Node.hpp b/src/server/LV2Node.hpp new file mode 100644 index 00000000..4702d24d --- /dev/null +++ b/src/server/LV2Node.hpp @@ -0,0 +1,82 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_LV2NODE_HPP +#define INGEN_ENGINE_LV2NODE_HPP + +#include <string> +#include "slv2/slv2.h" +#include "raul/IntrusivePtr.hpp" +#include "lv2/lv2plug.in/ns/ext/contexts/contexts.h" +#include "types.hpp" +#include "NodeImpl.hpp" +#include "LV2Features.hpp" + +namespace Ingen { +namespace Server { + +class LV2Plugin; + +/** An instance of a LV2 plugin. + * + * \ingroup engine + */ +class LV2Node : public NodeImpl +{ +public: + LV2Node(LV2Plugin* plugin, + const std::string& name, + bool polyphonic, + PatchImpl* parent, + SampleRate srate); + + ~LV2Node(); + + bool instantiate(BufferFactory& bufs); + + bool prepare_poly(BufferFactory& bufs, uint32_t poly); + bool apply_poly(Raul::Maid& maid, uint32_t poly); + + void activate(BufferFactory& bufs); + void deactivate(); + + void message_run(MessageContext& context); + + void process(ProcessContext& context); + + void set_port_buffer(uint32_t voice, uint32_t port_num, + IntrusivePtr<Buffer> buf, SampleCount offset); + +protected: + inline SLV2Instance instance(uint32_t voice) { return (SLV2Instance)(*_instances)[voice].get(); } + + typedef Raul::Array< SharedPtr<void> > Instances; + + LV2Plugin* _lv2_plugin; + Instances* _instances; + Instances* _prepared_instances; + + LV2_Contexts_MessageContext* _message_funcs; + + SharedPtr<Ingen::Shared::LV2Features::FeatureArray> _features; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_LV2NODE_HPP + diff --git a/src/server/LV2Plugin.cpp b/src/server/LV2Plugin.cpp new file mode 100644 index 00000000..ed25d1ac --- /dev/null +++ b/src/server/LV2Plugin.cpp @@ -0,0 +1,110 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <cassert> +#include <string> +#include <glibmm.h> + +#include "sord/sordmm.hpp" + +#include "shared/LV2URIMap.hpp" +#include "Driver.hpp" +#include "Engine.hpp" +#include "LV2Node.hpp" +#include "LV2Plugin.hpp" +#include "NodeImpl.hpp" + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { + +LV2Plugin::LV2Plugin(SharedPtr<LV2Info> lv2_info, const std::string& uri) + : PluginImpl(*lv2_info->world().uris().get(), Plugin::LV2, uri) + , _slv2_plugin(NULL) + , _lv2_info(lv2_info) +{ + set_property(_uris.rdf_type, _uris.lv2_Plugin); +} + +const string +LV2Plugin::symbol() const +{ + string working = uri().str(); + if (working[working.length()-1] == '/') + working = working.substr(0, working.length()-1); + + while (working.length() > 0) { + size_t last_slash = working.find_last_of("/"); + const string symbol = working.substr(last_slash+1); + if ( (symbol[0] >= 'a' && symbol[0] <= 'z') + || (symbol[0] >= 'A' && symbol[0] <= 'Z') ) + return Path::nameify(symbol); + else + working = working.substr(0, last_slash); + } + + return "lv2_symbol"; +} + +NodeImpl* +LV2Plugin::instantiate(BufferFactory& bufs, + const string& name, + bool polyphonic, + PatchImpl* parent, + Engine& engine) +{ + const SampleCount srate = engine.driver()->sample_rate(); + + load(); // FIXME: unload at some point + + LV2Node* n = new LV2Node(this, name, polyphonic, parent, srate); + + if ( ! n->instantiate(bufs) ) { + delete n; + n = NULL; + } + + return n; +} + +void +LV2Plugin::slv2_plugin(SLV2Plugin p) +{ + _slv2_plugin = p; +} + +const std::string& +LV2Plugin::library_path() const +{ + static const std::string empty_string; + if (_library_path.empty()) { + SLV2Value v = slv2_plugin_get_library_uri(_slv2_plugin); + if (v) { + _library_path = slv2_uri_to_path(slv2_value_as_uri(v)); + } else { + warn << uri() << " has no library path" << endl; + return empty_string; + } + } + + return _library_path; +} + +} // namespace Server +} // namespace Ingen diff --git a/src/server/LV2Plugin.hpp b/src/server/LV2Plugin.hpp new file mode 100644 index 00000000..e4360398 --- /dev/null +++ b/src/server/LV2Plugin.hpp @@ -0,0 +1,76 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_LV2PLUGIN_HPP +#define INGEN_ENGINE_LV2PLUGIN_HPP + +#include "ingen-config.h" + +#ifndef HAVE_SLV2 +#error "This file requires SLV2, but HAVE_SLV2 is not defined. Please report." +#endif + +#include <cstdlib> +#include <string> + +#include <glibmm/module.h> +#include <boost/utility.hpp> + +#include "slv2/slv2.h" +#include "raul/SharedPtr.hpp" + +#include "PluginImpl.hpp" +#include "LV2Info.hpp" + +namespace Ingen { +namespace Server { + +class PatchImpl; +class NodeImpl; + +/** Implementation of an LV2 plugin (loaded shared library). + */ +class LV2Plugin : public PluginImpl +{ +public: + LV2Plugin(SharedPtr<LV2Info> lv2_info, const std::string& uri); + + NodeImpl* instantiate(BufferFactory& bufs, + const std::string& name, + bool polyphonic, + PatchImpl* parent, + Engine& engine); + + const std::string symbol() const; + + SharedPtr<LV2Info> lv2_info() const { return _lv2_info; } + + const std::string& library_path() const; + + SLV2Plugin slv2_plugin() const { return _slv2_plugin; } + void slv2_plugin(SLV2Plugin p); + +private: + SLV2Plugin _slv2_plugin; + SharedPtr<LV2Info> _lv2_info; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_LV2PLUGIN_HPP + diff --git a/src/server/LV2RequestRunFeature.hpp b/src/server/LV2RequestRunFeature.hpp new file mode 100644 index 00000000..88010bb9 --- /dev/null +++ b/src/server/LV2RequestRunFeature.hpp @@ -0,0 +1,84 @@ +/* This file is part of Ingen. + * Copyright 2010-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_LV2_REQUEST_RUN_FEATURE_HPP +#define INGEN_ENGINE_LV2_REQUEST_RUN_FEATURE_HPP + +#include "lv2/lv2plug.in/ns/ext/contexts/contexts.h" + +#include "raul/log.hpp" + +#include "shared/LV2Features.hpp" + +#include "Driver.hpp" +#include "Engine.hpp" +#include "MessageContext.hpp" +#include "NodeImpl.hpp" +#include "PortImpl.hpp" + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { + +struct RequestRunFeature : public Ingen::Shared::LV2Features::Feature { + struct Data { + inline Data(Shared::World* w, Node* n) : world(w), node(n) {} + Shared::World* world; + Node* node; + }; + + static void request_run(LV2_Contexts_Request_Run_Data data_ptr, + uint32_t context_uri) { + Data* data = reinterpret_cast<Data*>(data_ptr); + if (!data->world->local_engine()) + return; + + Engine* engine = (Engine*)data->world->local_engine().get(); + engine->message_context()->run( + dynamic_cast<NodeImpl*>(data->node), + engine->driver()->frame_time()); + } + + static void delete_feature(LV2_Feature* feature) { + delete (Data*)feature->data; + free(feature); + } + + SharedPtr<LV2_Feature> feature(Shared::World* world, Node* n) { + NodeImpl* node = dynamic_cast<NodeImpl*>(n); + if (!node) + return SharedPtr<LV2_Feature>(); + + typedef LV2_Contexts_Request_Run_Feature Feature; + Feature* data = (Feature*)malloc(sizeof(Feature)); + data->data = new Data(world, n); + data->request_run = &request_run; + + LV2_Feature* f = (LV2_Feature*)malloc(sizeof(LV2_Feature)); + f->URI = LV2_CONTEXTS_URI "#RequestRunFeature"; + f->data = data; + + return SharedPtr<LV2_Feature>(f, &delete_feature); + } +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_LV2_REQUEST_RUN_FEATURE_HPP diff --git a/src/server/LV2ResizeFeature.hpp b/src/server/LV2ResizeFeature.hpp new file mode 100644 index 00000000..095796a7 --- /dev/null +++ b/src/server/LV2ResizeFeature.hpp @@ -0,0 +1,73 @@ +/* This file is part of Ingen. + * Copyright 2009-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_LV2RESIZEFEATURE_HPP +#define INGEN_ENGINE_LV2RESIZEFEATURE_HPP + +#include "raul/log.hpp" +#include "lv2/lv2plug.in/ns/ext/resize-port/resize-port.h" +#include "shared/LV2Features.hpp" +#include "NodeImpl.hpp" +#include "PortImpl.hpp" + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { + +struct ResizeFeature : public Ingen::Shared::LV2Features::Feature { + static bool resize_port(LV2_Resize_Port_Feature_Data data, + uint32_t index, + size_t size) { + NodeImpl* node = (NodeImpl*)data; + PortImpl* port = node->port_impl(index); + switch (port->context()) { + case Context::MESSAGE: + port->buffer(0)->resize(size); + port->connect_buffers(); + return true; + default: + // TODO: Implement realtime allocator and support this in audio thread + return false; + } + } + + static void delete_feature(LV2_Feature* feature) { + free(feature->data); + free(feature); + } + + SharedPtr<LV2_Feature> feature(Shared::World* w, Node* n) { + NodeImpl* node = dynamic_cast<NodeImpl*>(n); + if (!node) + return SharedPtr<LV2_Feature>(); + LV2_Resize_Port_Feature* data + = (LV2_Resize_Port_Feature*)malloc(sizeof(LV2_Resize_Port_Feature)); + data->data = node; + data->resize_port = &resize_port; + LV2_Feature* f = (LV2_Feature*)malloc(sizeof(LV2_Feature)); + f->URI = LV2_RESIZE_PORT_URI; + f->data = data; + return SharedPtr<LV2_Feature>(f, &delete_feature); + } +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_LV2RESIZEFEATURE_HPP diff --git a/src/server/MessageContext.cpp b/src/server/MessageContext.cpp new file mode 100644 index 00000000..0ac385bd --- /dev/null +++ b/src/server/MessageContext.cpp @@ -0,0 +1,126 @@ +/* This file is part of Ingen. + * Copyright 2008-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <algorithm> +#include "lv2/lv2plug.in/ns/ext/contexts/contexts.h" +#include "raul/log.hpp" +#include "ConnectionImpl.hpp" +#include "Engine.hpp" +#include "MessageContext.hpp" +#include "NodeImpl.hpp" +#include "PatchImpl.hpp" +#include "PortImpl.hpp" +#include "ProcessContext.hpp" +#include "ThreadManager.hpp" + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { + +void +MessageContext::run(NodeImpl* node, FrameTime time) +{ + if (ThreadManager::thread_is(THREAD_PROCESS)) { + const Request r(time, node); + _requests.write(sizeof(Request), &r); + // signal() will be called at the end of this process cycle + } else { + assert(node); + Glib::Mutex::Lock lock(_mutex); + _non_rt_request = Request(time, node); + _sem.post(); + _cond.wait(_mutex); + } +} + +void +MessageContext::_run() +{ + Request req; + + while (true) { + _sem.wait(); + + // Enqueue a request from the pre-process thread + { + Glib::Mutex::Lock lock(_mutex); + const Request req = _non_rt_request; + if (req.node) { + _queue.insert(req); + _end_time = std::max(_end_time, req.time); + _cond.broadcast(); // Notify caller we got the message + } + } + + // Enqueue (and thereby sort) requests from audio thread + while (has_requests()) { + _requests.full_read(sizeof(Request), &req); + if (req.node) { + _queue.insert(req); + } else { + _end_time = req.time; + break; + } + } + + // Run events in time increasing order + // Note that executing nodes may insert further events into the queue + while (!_queue.empty()) { + const Request req = *_queue.begin(); + + // Break if all events during this cycle have been consumed + // (the queue may contain generated events with later times) + if (req.time > _end_time) { + break; + } + + _queue.erase(_queue.begin()); + execute(req); + } + } +} + +void +MessageContext::execute(const Request& req) +{ + NodeImpl* node = req.node; + node->message_run(*this); + + void* valid_ports = node->valid_ports(); + PatchImpl* patch = node->parent_patch(); + + for (uint32_t i = 0; i < node->num_ports(); ++i) { + PortImpl* p = node->port_impl(i); + if (p->is_output() && p->context() == Context::MESSAGE && + lv2_contexts_port_is_valid(valid_ports, i)) { + PatchImpl::Connections& wires = patch->connections(); + for (PatchImpl::Connections::iterator c = wires.begin(); c != wires.end(); ++c) { + ConnectionImpl* ci = (ConnectionImpl*)c->second.get(); + if (ci->src_port() == p && ci->dst_port()->context() == Context::MESSAGE) { + _queue.insert(Request(req.time, ci->dst_port()->parent_node())); + } + } + } + } + + node->reset_valid_ports(); +} + +} // namespace Server +} // namespace Ingen diff --git a/src/server/MessageContext.hpp b/src/server/MessageContext.hpp new file mode 100644 index 00000000..d3bcfed6 --- /dev/null +++ b/src/server/MessageContext.hpp @@ -0,0 +1,114 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_MESSAGECONTEXT_HPP +#define INGEN_ENGINE_MESSAGECONTEXT_HPP + +#include <set> +#include <glibmm/thread.h> +#include "raul/Thread.hpp" +#include "raul/Semaphore.hpp" +#include "raul/AtomicPtr.hpp" +#include "lv2/lv2plug.in/ns/ext/atom/atom.h" +#include "Context.hpp" +#include "ProcessContext.hpp" +#include "ThreadManager.hpp" + +namespace Ingen { +namespace Server { + +class NodeImpl; + +/** Context of a message_run() call. + * + * The message context is a non-hard-realtime thread used to execute things + * that can take too long to execute in an audio thread, and do sloppy timed + * event propagation and scheduling. Interface to plugins via the + * LV2 contexts extension. + * + * \ingroup engine + */ +class MessageContext : public Context, public Raul::Thread +{ +public: + explicit MessageContext(Engine& engine) + : Context(engine, MESSAGE) + , Raul::Thread("MessageContext") + , _sem(0) + , _requests(engine.event_queue_size()) + , _end_time(0) + { + Thread::set_context(THREAD_MESSAGE); + } + + /** Schedule a message context run at a certain time. + * Safe to call from either process thread or pre-process thread. + */ + void run(NodeImpl* node, FrameTime time); + +protected: + struct Request { + Request(FrameTime t=0, NodeImpl* n=0) : time(t), node(n) {} + FrameTime time; + NodeImpl* node; + }; + +public: + /** Signal the end of a cycle that has produced messages. + * AUDIO THREAD ONLY. + */ + inline void signal(ProcessContext& context) { + ThreadManager::assert_thread(THREAD_PROCESS); + const Request cycle_end_request(context.end(), NULL); + _requests.write(sizeof(Request), &cycle_end_request); + _sem.post(); + } + + /** Return true iff requests are pending. Safe from any thread. */ + inline bool has_requests() const { + return _requests.read_space() >= sizeof(Request); + } + +protected: + /** Thread run method (wait for and execute requests from process thread */ + void _run(); + + /** Execute a request (possibly enqueueing more requests) */ + void execute(const Request& req); + + Raul::Semaphore _sem; + Raul::RingBuffer _requests; + Glib::Mutex _mutex; + Glib::Cond _cond; + Request _non_rt_request; + + struct RequestEarlier { + bool operator()(const Request& r1, const Request& r2) { + return r1.time < r2.time; + } + }; + + typedef std::set<Request, RequestEarlier> Queue; + Queue _queue; + FrameTime _end_time; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_MESSAGECONTEXT_HPP + diff --git a/src/server/NodeFactory.cpp b/src/server/NodeFactory.cpp new file mode 100644 index 00000000..9c6fc47c --- /dev/null +++ b/src/server/NodeFactory.cpp @@ -0,0 +1,148 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <cstdlib> +#include <string> +#include <pthread.h> +#include <dirent.h> +#include <float.h> +#include <cmath> +#include <glibmm/miscutils.h> +#include "sord/sordmm.hpp" +#include "raul/log.hpp" +#include "ingen-config.h" +#include "shared/World.hpp" +#include "internals/Controller.hpp" +#include "internals/Delay.hpp" +#include "internals/Note.hpp" +#include "internals/Trigger.hpp" +#include "Engine.hpp" +#include "InternalPlugin.hpp" +#include "NodeFactory.hpp" +#include "PatchImpl.hpp" +#include "ThreadManager.hpp" +#ifdef HAVE_SLV2 +#include "slv2/slv2.h" +#include "LV2Plugin.hpp" +#include "LV2Node.hpp" +#endif + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { + +using namespace Internals; + +NodeFactory::NodeFactory(Ingen::Shared::World* world) + : _world(world) + , _has_loaded(false) +#ifdef HAVE_SLV2 + , _lv2_info(new LV2Info(world)) +#endif +{ +} + +NodeFactory::~NodeFactory() +{ + for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i) + delete i->second; + + _plugins.clear(); +} + +const NodeFactory::Plugins& +NodeFactory::plugins() +{ + if (!_has_loaded) { + // TODO: Plugin list refreshing + load_plugins(); + } + return _plugins; +} + +PluginImpl* +NodeFactory::plugin(const Raul::URI& uri) +{ + const Plugins::const_iterator i = _plugins.find(uri); + return ((i != _plugins.end()) ? i->second : NULL); +} + +void +NodeFactory::load_plugins() +{ + ThreadManager::assert_thread(THREAD_PRE_PROCESS); + + // Only load if we havn't already, so every client connecting doesn't cause + // this (expensive!) stuff to happen. Not the best solution - would be nice + // if clients could refresh plugins list for whatever reason :/ + if (!_has_loaded) { + _plugins.clear(); // FIXME: assert empty? + + load_internal_plugins(); + +#ifdef HAVE_SLV2 + load_lv2_plugins(); +#endif + + _has_loaded = true; + } +} + +void +NodeFactory::load_internal_plugins() +{ + Ingen::Shared::LV2URIMap& uris = *_world->uris().get(); + InternalPlugin* controller_plug = ControllerNode::internal_plugin(uris); + _plugins.insert(make_pair(controller_plug->uri(), controller_plug)); + + InternalPlugin* delay_plug = DelayNode::internal_plugin(uris); + _plugins.insert(make_pair(delay_plug->uri(), delay_plug)); + + InternalPlugin* note_plug = NoteNode::internal_plugin(uris); + _plugins.insert(make_pair(note_plug->uri(), note_plug)); + + InternalPlugin* trigger_plug = TriggerNode::internal_plugin(uris); + _plugins.insert(make_pair(trigger_plug->uri(), trigger_plug)); +} + +#ifdef HAVE_SLV2 +/** Loads information about all LV2 plugins into internal plugin database. + */ +void +NodeFactory::load_lv2_plugins() +{ + SLV2Plugins plugins = slv2_world_get_all_plugins(_world->slv2_world()); + + SLV2_FOREACH(i, plugins) { + SLV2Plugin lv2_plug = slv2_plugins_get(plugins, i); + + const string uri(slv2_value_as_uri(slv2_plugin_get_uri(lv2_plug))); + + assert(_plugins.find(uri) == _plugins.end()); + + LV2Plugin* const plugin = new LV2Plugin(_lv2_info, uri); + + plugin->slv2_plugin(lv2_plug); + _plugins.insert(make_pair(uri, plugin)); + } +} +#endif // HAVE_SLV2 + +} // namespace Server +} // namespace Ingen diff --git a/src/server/NodeFactory.hpp b/src/server/NodeFactory.hpp new file mode 100644 index 00000000..f4e4ea41 --- /dev/null +++ b/src/server/NodeFactory.hpp @@ -0,0 +1,76 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_NODEFACTORY_HPP +#define INGEN_ENGINE_NODEFACTORY_HPP + +#include <list> +#include <map> +#include <string> +#include <pthread.h> +#include <glibmm/module.h> +#include "raul/SharedPtr.hpp" +#include "raul/URI.hpp" +#include "shared/World.hpp" +#include "ingen-config.h" + +namespace Ingen { +namespace Server { + +class NodeImpl; +class PatchImpl; +class PluginImpl; +#ifdef HAVE_SLV2 +class LV2Info; +#endif + +/** Discovers and loads plugin libraries. + * + * \ingroup engine + */ +class NodeFactory +{ +public: + explicit NodeFactory(Ingen::Shared::World* world); + ~NodeFactory(); + + void load_plugins(); + + typedef std::map<Raul::URI, PluginImpl*> Plugins; + const Plugins& plugins(); + + PluginImpl* plugin(const Raul::URI& uri); + +private: +#ifdef HAVE_SLV2 + void load_lv2_plugins(); +#endif + + void load_internal_plugins(); + + Plugins _plugins; + Ingen::Shared::World* _world; + bool _has_loaded; +#ifdef HAVE_SLV2 + SharedPtr<LV2Info> _lv2_info; +#endif +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_NODEFACTORY_HPP diff --git a/src/server/NodeImpl.cpp b/src/server/NodeImpl.cpp new file mode 100644 index 00000000..eab31716 --- /dev/null +++ b/src/server/NodeImpl.cpp @@ -0,0 +1,264 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <cassert> +#include <stdint.h> +#include "lv2/lv2plug.in/ns/ext/contexts/contexts.h" +#include "raul/List.hpp" +#include "raul/Array.hpp" +#include "util.hpp" +#include "AudioBuffer.hpp" +#include "ClientBroadcaster.hpp" +#include "EngineStore.hpp" +#include "NodeImpl.hpp" +#include "PatchImpl.hpp" +#include "PluginImpl.hpp" +#include "PortImpl.hpp" +#include "ThreadManager.hpp" + +using namespace std; + +namespace Ingen { +namespace Server { + +NodeImpl::NodeImpl(PluginImpl* plugin, const Raul::Symbol& symbol, bool polyphonic, PatchImpl* parent, SampleRate srate) + : GraphObjectImpl(plugin->uris(), parent, symbol) + , _plugin(plugin) + , _polyphonic(polyphonic) + , _polyphony((polyphonic && parent) ? parent->internal_poly() : 1) + , _srate(srate) + , _valid_ports(NULL) + , _input_ready(1) + , _process_lock(0) + , _n_inputs_ready(0) + , _ports(NULL) + , _providers(new Raul::List<NodeImpl*>()) + , _dependants(new Raul::List<NodeImpl*>()) + , _activated(false) + , _traversed(false) +{ + assert(_plugin); + assert(_polyphony > 0); + assert(_parent == NULL || (_polyphony == parent->internal_poly() || _polyphony == 1)); +} + +NodeImpl::~NodeImpl() +{ + if (_activated) + deactivate(); + + delete _providers; + delete _dependants; + delete _ports; + + free(_valid_ports); +} + +Port* +NodeImpl::port(uint32_t index) const +{ + return (*_ports)[index]; +} + +const Plugin* +NodeImpl::plugin() const +{ + return _plugin; +} + +void +NodeImpl::activate(BufferFactory& bufs) +{ + ThreadManager::assert_thread(THREAD_PRE_PROCESS); + assert(!_activated); + _activated = true; + + for (uint32_t p = 0; p < num_ports(); ++p) { + PortImpl* const port = _ports->at(p); + port->setup_buffers(bufs, port->poly()); + port->connect_buffers(); + for (uint32_t v = 0; v < _polyphony; ++v) { + if (!port->buffer(v)) + continue; + if (port->is_a(PortType::CONTROL)) + ((AudioBuffer*)port->buffer(v).get())->set_value(port->value().get_float(), 0, 0); + else + port->buffer(v)->clear(); + } + } +} + +void +NodeImpl::deactivate() +{ + assert(_activated); + _activated = false; + for (uint32_t i = 0; i < _polyphony; ++i) { + for (unsigned long j = 0; j < num_ports(); ++j) { + PortImpl* const port = _ports->at(j); + if (port->is_output() && port->buffer(i)) + port->buffer(i)->clear(); + } + } +} + +bool +NodeImpl::prepare_poly(BufferFactory& bufs, uint32_t poly) +{ + ThreadManager::assert_thread(THREAD_PRE_PROCESS); + + if (!_polyphonic) + poly = 1; + + if (_ports) + for (size_t i = 0; i < _ports->size(); ++i) + _ports->at(i)->prepare_poly(bufs, poly); + + return true; +} + +bool +NodeImpl::apply_poly(Raul::Maid& maid, uint32_t poly) +{ + ThreadManager::assert_thread(THREAD_PROCESS); + + if (!_polyphonic) + poly = 1; + + _polyphony = poly; + + if (_ports) + for (size_t i = 0; i < num_ports(); ++i) + _ports->at(i)->apply_poly(maid, poly); + + return true; +} + +void +NodeImpl::set_buffer_size(Context& context, BufferFactory& bufs, PortType type, size_t size) +{ + if (_ports) + for (size_t i = 0; i < _ports->size(); ++i) + if (_ports->at(i)->buffer_type() == type && _ports->at(i)->context() == context.id()) + _ports->at(i)->set_buffer_size(context, bufs, size); +} + +void +NodeImpl::reset_input_ready() +{ + _n_inputs_ready = 0; + _process_lock = 0; + _input_ready.reset(0); +} + +bool +NodeImpl::process_lock() +{ + return _process_lock.compare_and_exchange(0, 1); +} + +void +NodeImpl::process_unlock() +{ + _process_lock = 0; +} + +void +NodeImpl::wait_for_input(size_t num_providers) +{ + ThreadManager::assert_thread(THREAD_PROCESS); + assert(_process_lock.get() == 1); + + while ((unsigned)_n_inputs_ready.get() < num_providers) + _input_ready.wait(); +} + +void +NodeImpl::signal_input_ready() +{ + ThreadManager::assert_thread(THREAD_PROCESS); + ++_n_inputs_ready; + _input_ready.post(); +} + +/** Prepare to run a cycle (in the audio thread) + */ +void +NodeImpl::pre_process(Context& context) +{ + ThreadManager::assert_thread(THREAD_PROCESS); + + // Mix down input ports + for (uint32_t i = 0; i < num_ports(); ++i) { + PortImpl* const port = _ports->at(i); + if (port->context() == Context::AUDIO) { + port->pre_process(context); + port->connect_buffers(context.offset()); + } + } +} + +/** Prepare to run a cycle (in the audio thread) + */ +void +NodeImpl::post_process(Context& context) +{ + ThreadManager::assert_thread(THREAD_PROCESS); + + // Write output ports + for (size_t i = 0; _ports && i < _ports->size(); ++i) { + PortImpl* const port = _ports->at(i); + if (port->context() == Context::AUDIO) + _ports->at(i)->post_process(context); + } +} + +/** Flag a port as set (for message context) + */ +void +NodeImpl::set_port_valid(uint32_t port_index) +{ + // Allocate enough space for one bit per port + if (!_valid_ports) + _valid_ports = calloc(num_ports() / 8, 1); + lv2_contexts_set_port_valid(_valid_ports, port_index); +} + +void* +NodeImpl::valid_ports() +{ + return _valid_ports; +} + +void +NodeImpl::reset_valid_ports() +{ + if (_valid_ports) + memset(_valid_ports, '\0', num_ports() / 8); +} + +void +NodeImpl::set_port_buffer(uint32_t voice, uint32_t port_num, + BufferFactory::Ref buf, SampleCount offset) +{ + /*std::cout << path() << " set port " << port_num << " voice " << voice + << " buffer " << buf << " offset " << offset << std::endl;*/ +} + +} // namespace Server +} // namespace Ingen + diff --git a/src/server/NodeImpl.hpp b/src/server/NodeImpl.hpp new file mode 100644 index 00000000..3263727a --- /dev/null +++ b/src/server/NodeImpl.hpp @@ -0,0 +1,223 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_NODEIMPL_HPP +#define INGEN_ENGINE_NODEIMPL_HPP + +#include <string> +#include "raul/Array.hpp" +#include "raul/AtomicInt.hpp" +#include "raul/IntrusivePtr.hpp" +#include "raul/Semaphore.hpp" +#include "ingen/Node.hpp" +#include "GraphObjectImpl.hpp" +#include "types.hpp" + +namespace Raul { template <typename T> class List; class Maid; } + +namespace Ingen { + +class Plugin; +class Port; + +namespace Server { + +class Buffer; +class BufferFactory; +class Context; +class MessageContext; +class PatchImpl; +class PluginImpl; +class PortImpl; +class ProcessContext; + +/** A Node (or "module") in a Patch (which is also a Node). + * + * A Node is a unit with input/output ports, a process() method, and some other + * things. + * + * \ingroup engine + */ +class NodeImpl : public GraphObjectImpl, virtual public Node +{ +public: + NodeImpl(PluginImpl* plugin, + const Raul::Symbol& symbol, + bool poly, + PatchImpl* parent, + SampleRate rate); + + virtual ~NodeImpl(); + + /** Activate this Node. + * + * This function must be called in a non-realtime thread before it is + * inserted in to a patch. Any non-realtime actions that need to be + * done before the Node is ready for use should be done here. + */ + virtual void activate(BufferFactory& bufs); + + /** Deactivate this Node. + * + * This function must be called in a non-realtime thread after the + * node has been removed from its patch (i.e. processing is finished). + */ + virtual void deactivate(); + + /** Return true iff this node is activated */ + bool activated() { return _activated; } + + /** Parallelism: Reset flags for start of a new cycle. */ + virtual void reset_input_ready(); + + /** Parallelism: Claim this node (to wait on its input). + * Only one thread will ever take this lock on a particular Node. + * \return true if lock was aquired, false otherwise + */ + virtual bool process_lock(); + + /** Parallelism: Unclaim this node (let someone else wait on its input). + * Only a thread which successfully called process_lock may call this. + */ + virtual void process_unlock(); + + /** Parallelism: Wait for signal that input is ready. + * Only a thread which successfully called process_lock may call this. + */ + virtual void wait_for_input(size_t num_providers); + + /** Parallelism: Signal that input is ready. Realtime safe. + * Calling this will wake up the thread which blocked on wait_for_input + * if there is one, and otherwise cause it to return true the next call. + * \return true if lock was aquired and input is ready, false otherwise + */ + virtual void signal_input_ready(); + + /** Parallelism: Return the number of providers that have signalled. */ + virtual unsigned n_inputs_ready() const { return _n_inputs_ready.get(); } + + /** Learn the next incoming MIDI event (for internals) */ + virtual void learn() {} + + /** Run the node for one instant in the message thread. */ + virtual void message_run(MessageContext& context) {} + + /** Flag a port as valid (for message context) */ + virtual void set_port_valid(uint32_t index); + + /** Return a bit vector of which ports are valid */ + virtual void* valid_ports(); + + /** Clear all bits in valid_ports() */ + virtual void reset_valid_ports(); + + /** Do whatever needs doing in the process thread before process() is called */ + virtual void pre_process(Context& context); + + /** Run the node for @a nframes input/output. + * + * @a start and @a end are transport times: end is not redundant in the case + * of varispeed, where end-start != nframes. + */ + virtual void process(ProcessContext& context) = 0; + + /** Do whatever needs doing in the process thread after process() is called */ + virtual void post_process(Context& context); + + /** Set the buffer of a port to a given buffer (e.g. connect plugin to buffer) */ + virtual void set_port_buffer( + uint32_t voice, + uint32_t port_num, + IntrusivePtr<Buffer> buf, + SampleCount offset); + + virtual Port* port(uint32_t index) const; + virtual PortImpl* port_impl(uint32_t index) const { return (*_ports)[index]; } + + /** Nodes that are connected to this Node's inputs. + * (This Node depends on them) + */ + Raul::List<NodeImpl*>* providers() { return _providers; } + void providers(Raul::List<NodeImpl*>* l) { _providers = l; } + + /** Nodes are are connected to this Node's outputs. + * (They depend on this Node) + */ + Raul::List<NodeImpl*>* dependants() { return _dependants; } + void dependants(Raul::List<NodeImpl*>* l) { _dependants = l; } + + /** Flag node as polyphonic. + * + * Note this will not actually allocate voices etc., prepare_poly + * and apply_poly must be called after this function to truly make + * a node polyphonic. + */ + virtual void set_polyphonic(bool p) { _polyphonic = p; } + + virtual bool prepare_poly(BufferFactory& bufs, uint32_t poly); + virtual bool apply_poly(Raul::Maid& maid, uint32_t poly); + + /** Information about the Plugin this Node is an instance of. + * Not the best name - not all nodes come from plugins (ie Patch) + */ + virtual PluginImpl* plugin_impl() const { return _plugin; } + + /** Information about the Plugin this Node is an instance of. + * Not the best name - not all nodes come from plugins (ie Patch) + */ + virtual const Plugin* plugin() const; + + virtual void plugin(PluginImpl* pi) { _plugin = pi; } + + virtual void set_buffer_size(Context& context, BufferFactory& bufs, + PortType type, size_t size); + + /** The Patch this Node belongs to. */ + inline PatchImpl* parent_patch() const { return (PatchImpl*)_parent; } + + SampleRate sample_rate() const { return _srate; } + virtual uint32_t num_ports() const { return _ports ? _ports->size() : 0; } + virtual uint32_t polyphony() const { return _polyphony; } + + /** Used by the process order finding algorithm (ie during connections) */ + bool traversed() const { return _traversed; } + void traversed(bool b) { _traversed = b; } + +protected: + PluginImpl* _plugin; + + bool _polyphonic; + uint32_t _polyphony; + SampleRate _srate; + + void* _valid_ports; ///< Valid port flags for message context + + Raul::Semaphore _input_ready; ///< Parallelism: input ready signal + Raul::AtomicInt _process_lock; ///< Parallelism: Waiting on inputs 'lock' + Raul::AtomicInt _n_inputs_ready; ///< Parallelism: # input ready signals this cycle + Raul::Array<PortImpl*>* _ports; ///< Access in audio thread only + Raul::List<NodeImpl*>* _providers; ///< Nodes connected to this one's input ports + Raul::List<NodeImpl*>* _dependants; ///< Nodes this one's output ports are connected to + + bool _activated; + bool _traversed; ///< Flag for process order algorithm +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_NODEIMPL_HPP diff --git a/src/server/OSCClientSender.cpp b/src/server/OSCClientSender.cpp new file mode 100644 index 00000000..df2a8f3a --- /dev/null +++ b/src/server/OSCClientSender.cpp @@ -0,0 +1,247 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <unistd.h> + +#include <cassert> +#include <string> + +#include "raul/log.hpp" +#include "raul/AtomLiblo.hpp" + +#include "EngineStore.hpp" +#include "NodeImpl.hpp" +#include "OSCClientSender.hpp" +#include "PatchImpl.hpp" + +#include "PluginImpl.hpp" +#include "PortImpl.hpp" +#include "ingen/ClientInterface.hpp" +#include "util.hpp" + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { + +/** @page client_osc_namespace Client OSC Namespace Documentation + * + * <p>These are the commands the client recognizes. All monitoring of + * changes in the engine happens via these commands.</p> + */ + +/** @page client_osc_namespace + * <h2>/ok</h2> + * @arg @p response-id :: Integer + * + * @par + * Successful response to some command. + */ +void +OSCClientSender::response_ok(int32_t id) +{ + if (!_enabled) + return; + + + + if (lo_send(_address, "/ok", "i", id, LO_ARGS_END) < 0) { + Raul::error << "Unable to send OK " << id << "! (" + << lo_address_errstr(_address) << ")" << endl; + } +} + +/** @page client_osc_namespace + * <h2>/error</h2> + * @arg @p response-id :: Integer + * @arg @p message :: String + * + * @par + * Unsuccessful response to some command. + */ +void +OSCClientSender::response_error(int32_t id, const std::string& msg) +{ + if (!_enabled) + return; + + if (lo_send(_address, "/error", "is", id, msg.c_str(), LO_ARGS_END) < 0) { + Raul::error << "Unable to send error " << id << "! (" + << lo_address_errstr(_address) << ")" << endl; + } +} + +/** @page client_osc_namespace + * <h2>/error</h2> + * @arg @p message :: String + * + * @par + * Notification that an error has occurred. This is for notification of errors + * that aren't a direct response to a user command, i.e. "unexpected" errors. + */ +void +OSCClientSender::error(const std::string& msg) +{ + send("/error", "s", msg.c_str(), LO_ARGS_END); +} + +/** @page client_osc_namespace + * <h2>/put</h2> + * @arg @p path :: String + * @arg @p predicate :: URI String + * @arg @p value + * @arg @p ... + * + * @par + * PUT a set of properties to a path. + */ +void +OSCClientSender::put(const Raul::URI& path, + const Resource::Properties& properties, + Resource::Graph ctx) +{ + typedef Resource::Properties::const_iterator iterator; + lo_message m = lo_message_new(); + lo_message_add_string(m, path.c_str()); + for (iterator i = properties.begin(); i != properties.end(); ++i) { + lo_message_add_string(m, i->first.c_str()); + Raul::AtomLiblo::lo_message_add_atom(m, i->second); + } + send_message("/put", m); +} + +void +OSCClientSender::delta(const Raul::URI& path, + const Resource::Properties& remove, + const Resource::Properties& add) +{ + warn << "FIXME: OSC DELTA" << endl; +} + +/** @page client_osc_namespace + * <h2>/move</h2> + * @arg @p old-path :: String + * @arg @p new-path :: String + * + * @par + * MOVE an object to a new path. + * The new path will have the same parent as the old path. + */ +void +OSCClientSender::move(const Path& old_path, const Path& new_path) +{ + send("/move", "ss", old_path.c_str(), new_path.c_str(), LO_ARGS_END); +} + +/** @page client_osc_namespace + * <h2>/delete</h2> + * @arg @p path :: String + * + * @par + * DELETE an object. + */ +void +OSCClientSender::del(const URI& uri) +{ + send("/delete", "s", uri.c_str(), LO_ARGS_END); +} + +/** @page client_osc_namespace + * <h2>/connect</h2> + * @arg @p src-path :: String + * @arg @p dst-path :: String + * + * @par + * Notification a new connection has been made. + */ +void +OSCClientSender::connect(const Path& src_port_path, + const Path& dst_port_path) +{ + send("/connect", "ss", src_port_path.c_str(), dst_port_path.c_str(), LO_ARGS_END); +} + +/** @page client_osc_namespace + * <h2>/disconnect</h2> + * @arg @p src-path :: String + * @arg @p dst-path :: String + * + * @par + * Notification a connection has been unmade. + */ +void +OSCClientSender::disconnect(const URI& src, + const URI& dst) +{ + send("/disconnect", "ss", src.c_str(), dst.c_str(), LO_ARGS_END); +} + +/** @page client_osc_namespace + * <h2>/disconnect_all</h2> + * @arg @p parent-patch-path :: String + * @arg @p path :: String + * + * @par + * Notification all connections to an object have been disconnected. + */ +void +OSCClientSender::disconnect_all(const Raul::Path& parent_patch_path, + const Raul::Path& path) +{ + send("/disconnect_all", "ss", parent_patch_path.c_str(), path.c_str(), LO_ARGS_END); +} + +/** @page client_osc_namespace + * <h2>/set_property</h2> + * @arg @p path :: String + * @arg @p key :: URI String + * @arg @p value + * + * @par + * Notification of a property. + */ +void +OSCClientSender::set_property(const URI& path, + const URI& key, + const Atom& value) +{ + lo_message m = lo_message_new(); + lo_message_add_string(m, path.c_str()); + lo_message_add_string(m, key.c_str()); + AtomLiblo::lo_message_add_atom(m, value); + send_message("/set_property", m); +} + +/** @page client_osc_namespace + * <h2>/activity</h2> + * @arg @p path :: String + * + * @par + * Notification of "activity" (e.g. port message blinkenlights). + */ +void +OSCClientSender::activity(const Path& path) +{ + if (!_enabled) + return; + + lo_send(_address, "/activity", "s", path.c_str(), LO_ARGS_END); +} + +} // namespace Server +} // namespace Ingen diff --git a/src/server/OSCClientSender.hpp b/src/server/OSCClientSender.hpp new file mode 100644 index 00000000..eb052bc7 --- /dev/null +++ b/src/server/OSCClientSender.hpp @@ -0,0 +1,108 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_OSCCLIENTSENDER_HPP +#define INGEN_ENGINE_OSCCLIENTSENDER_HPP + +#include <cassert> +#include <string> +#include <lo/lo.h> +#include <pthread.h> +#include "ingen/ClientInterface.hpp" +#include "ingen/GraphObject.hpp" +#include "shared/OSCSender.hpp" + +namespace Ingen { + +class ServerInterface; + +namespace Server { + + +/** Implements ClientInterface for OSC clients (sends OSC messages). + * + * \ingroup engine + */ +class OSCClientSender : public ClientInterface, + public Ingen::Shared::OSCSender +{ +public: + explicit OSCClientSender(const Raul::URI& url, + size_t max_packet_size) + : Shared::OSCSender(max_packet_size) + , _url(url) + { + _address = lo_address_new_from_url(url.c_str()); + } + + virtual ~OSCClientSender() + { lo_address_free(_address); } + + bool enabled() const { return _enabled; } + + void enable() { _enabled = true; } + void disable() { _enabled = false; } + + void bundle_begin() { OSCSender::bundle_begin(); } + void bundle_end() { OSCSender::bundle_end(); } + + Raul::URI uri() const { return _url; } + + /* *** ClientInterface Implementation Below *** */ + + void response_ok(int32_t id); + void response_error(int32_t id, const std::string& msg); + + void error(const std::string& msg); + + virtual void put(const Raul::URI& path, + const Resource::Properties& properties, + Resource::Graph ctx=Resource::DEFAULT); + + virtual void delta(const Raul::URI& path, + const Resource::Properties& remove, + const Resource::Properties& add); + + virtual void del(const Raul::URI& uri); + + virtual void move(const Raul::Path& old_path, + const Raul::Path& new_path); + + virtual void connect(const Raul::Path& src_port_path, + const Raul::Path& dst_port_path); + + virtual void disconnect(const Raul::URI& src, + const Raul::URI& dst); + + virtual void disconnect_all(const Raul::Path& parent_patch_path, + const Raul::Path& path); + + virtual void set_property(const Raul::URI& subject, + const Raul::URI& predicate, + const Raul::Atom& value); + + virtual void activity(const Raul::Path& path); + +private: + Raul::URI _url; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_OSCCLIENTSENDER_HPP + diff --git a/src/server/OSCEngineReceiver.cpp b/src/server/OSCEngineReceiver.cpp new file mode 100644 index 00000000..635856da --- /dev/null +++ b/src/server/OSCEngineReceiver.cpp @@ -0,0 +1,586 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +//#define ENABLE_AVAHI 1 + +#include <stdlib.h> +#include <stdio.h> + +#include <string> + +#include <lo/lo.h> + +#include "raul/AtomLiblo.hpp" +#include "raul/Path.hpp" +#include "raul/SharedPtr.hpp" +#include "raul/log.hpp" + +#include "ingen-config.h" +#include "ingen/ClientInterface.hpp" + +#include "ClientBroadcaster.hpp" +#include "Engine.hpp" +#include "EventSource.hpp" +#include "OSCClientSender.hpp" +#include "OSCEngineReceiver.hpp" +#include "ThreadManager.hpp" + +#define LOG(s) s << "[OSCEngineReceiver] " + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { + +/** @page engine_osc_namespace Server OSC Namespace Documentation + * + * <p>These are the commands the engine recognizes. A client can control every + * aspect of the engine entirely with these commands.</p> + * + * <p>All commands on this page are in the "control band". If a client needs to + * know about the state of the engine, it must listen to the "notification band". + * See the "Client OSC Namespace Documentation" for details.</p> + */ + +OSCEngineReceiver::OSCEngineReceiver(Engine& engine, size_t queue_size, uint16_t port) + : QueuedEngineInterface(engine, queue_size) // FIXME + , _server(NULL) +{ + _receive_thread = new ReceiveThread(*this); + + char port_str[6]; + snprintf(port_str, sizeof(port_str), "%u", port); + + _server = lo_server_new(port_str, error_cb); +#ifdef ENABLE_AVAHI + lo_server_avahi_init(_server, "ingen"); +#endif + + if (_server == NULL) { + LOG(error) << "Could not start OSC server. Aborting." << endl; + exit(EXIT_FAILURE); + } else { + char* lo_url = lo_server_get_url(_server); + LOG(info) << "Started OSC server at " << lo_url << endl; + free(lo_url); + } + +#ifdef RAUL_LOG_DEBUG + lo_server_add_method(_server, NULL, NULL, generic_cb, NULL); +#endif + + // Set response address for this message. + // It's important this is first and returns nonzero. + lo_server_add_method(_server, NULL, NULL, set_response_address_cb, this); + +#ifdef LIBLO_BUNDLES + lo_server_add_bundle_handlers(_server, bundle_start_cb, bundle_end_cb, this); +#endif + + // Commands + lo_server_add_method(_server, "/ping", "i", ping_cb, this); + lo_server_add_method(_server, "/ping_queued", "i", ping_slow_cb, this); + lo_server_add_method(_server, "/register_client", "i", register_client_cb, this); + lo_server_add_method(_server, "/unregister_client", "i", unregister_client_cb, this); + lo_server_add_method(_server, "/put", NULL, put_cb, this); + lo_server_add_method(_server, "/move", "iss", move_cb, this); + lo_server_add_method(_server, "/delete", "is", del_cb, this); + lo_server_add_method(_server, "/connect", "iss", connect_cb, this); + lo_server_add_method(_server, "/disconnect", "iss", disconnect_cb, this); + lo_server_add_method(_server, "/disconnect_all", "iss", disconnect_all_cb, this); + lo_server_add_method(_server, "/note_on", "isii", note_on_cb, this); + lo_server_add_method(_server, "/note_off", "isi", note_off_cb, this); + lo_server_add_method(_server, "/all_notes_off", "isi", all_notes_off_cb, this); + lo_server_add_method(_server, "/learn", "is", learn_cb, this); + lo_server_add_method(_server, "/set_property", NULL, set_property_cb, this); + + // Queries + lo_server_add_method(_server, "/request_property", "iss", request_property_cb, this); + lo_server_add_method(_server, "/get", "is", get_cb, this); + + lo_server_add_method(_server, NULL, NULL, unknown_cb, NULL); + + Thread::set_name("OSCEngineReceiver"); + start(); + _receive_thread->set_name("OSCEngineReceiver Listener"); + _receive_thread->start(); + _receive_thread->set_scheduling(SCHED_FIFO, 5); +} + +OSCEngineReceiver::~OSCEngineReceiver() +{ + _receive_thread->stop(); + stop(); + delete _receive_thread; + + if (_server != NULL) { +#ifdef ENABLE_AVAHI + lo_server_avahi_free(_server); +#endif + lo_server_free(_server); + _server = NULL; + } +} + +/** Override the semaphore driven _run method of QueuedEngineInterface + * to wait on OSC messages and prepare them right away in the same thread. + */ +void +OSCEngineReceiver::ReceiveThread::_run() +{ + Thread::get().set_context(THREAD_PRE_PROCESS); + + /* get a timestamp here and stamp all the events with the same time so + * they all get executed in the same cycle */ + + while (true) { + // Wait on a message and enqueue it + lo_server_recv(_receiver._server); + + // Enqueue every other message that is here "now" + // (would this provide truly atomic bundles?) + while (lo_server_recv_noblock(_receiver._server, 0) > 0) + if (_receiver.unprepared_events()) + _receiver.whip(); + + // No more unprepared events + } +} + +/** Create a new request for this message, if necessary. + * + * This is based on the fact that the current request is stored in a ref + * counted pointer, and events just take a reference to that. Thus, events + * may delete their request if we've since switched to a new one, or the + * same one can stay around and serve a series of events. + * Hooray for reference counting. + * + * If this message came from the same source as the last message, no allocation + * of requests or lo_addresses or any of it needs to be done. Unfortunately + * the only way to check is by comparing URLs, because liblo addresses suck. + * Lack of a fast liblo address comparison really sucks here, in any case. + */ +int +OSCEngineReceiver::set_response_address_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg, void* user_data) +{ + OSCEngineReceiver* const me = reinterpret_cast<OSCEngineReceiver*>(user_data); + + if (argc < 1 || types[0] != 'i') // Not a valid Ingen message + return 0; // Save liblo the trouble + + const int32_t id = argv[0]->i; + + const lo_address addr = lo_message_get_source(msg); + char* const url = lo_address_get_url(addr); + + const SharedPtr<Request> r = me->_request; + + /* Different address than last time, have to do a lookup */ + if (!r || !r->client() || strcmp(url, r->client()->uri().c_str())) { + ClientInterface* client = me->_engine.broadcaster()->client(url); + if (client) + me->_request = SharedPtr<Request>(new Request(me, client, id)); + else + me->_request = SharedPtr<Request>(new Request(me)); + } + + if (id != -1) { + me->set_next_response_id(id); + } else { + me->disable_responses(); + } + + free(url); + + // If this returns 0 no OSC commands will work + return 1; +} + +#ifdef LIBLO_BUNDLES +int +OSCEngineReceiver::_bundle_start_cb(lo_timetag time) +{ + info << "BUNDLE START" << endl; + return 0; +} + +int +OSCEngineReceiver::_bundle_end_cb() +{ + info << "BUNDLE END" << endl; + return 0; +} +#endif + +void +OSCEngineReceiver::error_cb(int num, const char* msg, const char* path) +{ + error << "liblo server error" << num; + if (path) { + error << " for path `" << path << "'"; + } + error << " (" << msg << ")" << endl; +} + +/** @page engine_osc_namespace + * <h2>/ping</h2> + * @arg @p response-id :: Integer + */ +int +OSCEngineReceiver::_ping_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const lo_address addr = lo_message_get_source(msg); + if (lo_send(addr, "/ok", "i", argv[0]->i) < 0) + warn << "Unable to send response (" << lo_address_errstr(addr) << ")" << endl; + return 0; +} + +/** @page engine_osc_namespace + * <h2>/ping_queued</h2> + * @arg @p response-id :: Integer + * + * @par + * Reply to sender with a successful response after going through the + * event queue. This is useful for checking if the engine is actually active, + * or for sending after several events as a sentinel and wait on it's response, + * to know when all previous events have finished processing. + */ +int +OSCEngineReceiver::_ping_slow_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + ping(); + return 0; +} + +/** @page engine_osc_namespace + * <h2>/register_client</h2> + * @arg @p response-id :: Integer + * + * @par + * Register a new client with the engine. The incoming address will be + * used for the new registered client. + */ +int +OSCEngineReceiver::_register_client_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + lo_address addr = lo_message_get_source(msg); + + char* const url = lo_address_get_url(addr); + ClientInterface* client = new OSCClientSender( + (const char*)url, + _engine.world()->conf()->option("packet-size").get_int32()); + register_client(client); + free(url); + + return 0; +} + +/** @page engine_osc_namespace + * <h2>/unregister_client</h2> + * @arg @p response-id :: Integer + * + * @par + * Unregister a client. + */ +int +OSCEngineReceiver::_unregister_client_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + lo_address addr = lo_message_get_source(msg); + + char* url = lo_address_get_url(addr); + unregister_client(url); + free(url); + + return 0; +} + +/** @page engine_osc_namespace + * <h2>/get</h2> + * @arg @p response-id :: Integer + * @arg @p uri :: URI String + * + * @par + * Request all properties of an object. + */ +int +OSCEngineReceiver::_get_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + get(&argv[1]->s); + return 0; +} + +/** @page engine_osc_namespace + * <h2>/put</h2> + * @arg @p response-id :: Integer + * @arg @p path :: String + * @arg @p predicate :: URI String + * @arg @p value + * @arg @p ... + * + * @par + * PUT a set of properties to a path. + */ +int +OSCEngineReceiver::_put_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* obj_path = &argv[1]->s; + Resource::Properties prop; + for (int i = 2; i < argc-1; i += 2) + prop.insert(make_pair(&argv[i]->s, AtomLiblo::lo_arg_to_atom(types[i+1], argv[i+1]))); + put(obj_path, prop); + return 0; +} + +/** @page engine_osc_namespace + * <h2>/move</h2> + * @arg @p response-id :: Integer + * @arg @p old-path :: String + * @arg @p new-path :: String + * + * @par + * MOVE an object to a new path. + */ +int +OSCEngineReceiver::_move_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* old_path = &argv[1]->s; + const char* new_path = &argv[2]->s; + + move(old_path, new_path); + return 0; +} + +/** @page engine_osc_namespace + * <h2>/delete</h2> + * @arg @p response-id :: Integer + * @arg @p path :: String + * + * @par + * DELETE an object. + */ +int +OSCEngineReceiver::_del_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* uri = &argv[1]->s; + + del(uri); + return 0; +} + +/** @page engine_osc_namespace + * <h2>/connect</h2> + * @arg @p response-id :: Integer + * @arg @p src-port-path :: String + * @arg @p dst-port-path :: String + * + * @par + * Connect two ports (which must be in the same patch). + */ +int +OSCEngineReceiver::_connect_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* src_port_path = &argv[1]->s; + const char* dst_port_path = &argv[2]->s; + + connect(src_port_path, dst_port_path); + return 0; +} + +/** @page engine_osc_namespace + * <h2>/disconnect</h2> + * @arg @p response-id :: Integer + * @arg @p src-port-path :: String + * @arg @p dst-port-path :: String + * + * @par + * Disconnect two ports. + */ +int +OSCEngineReceiver::_disconnect_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* src_port_path = &argv[1]->s; + const char* dst_port_path = &argv[2]->s; + + disconnect(src_port_path, dst_port_path); + return 0; +} + +/** @page engine_osc_namespace + * <h2>/disconnect_all</h2> + * @arg @p response-id :: Integer + * @arg @p patch-path :: String + * @arg @p object-path :: String + * + * @par + * Disconnect all connections to/from a node/port. + */ +int +OSCEngineReceiver::_disconnect_all_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* patch_path = &argv[1]->s; + const char* object_path = &argv[2]->s; + + disconnect_all(patch_path, object_path); + return 0; +} + +/** @page engine_osc_namespace + * <h2>/note_on</h2> + * @arg @p response-id :: Integer + * @arg @p node-path :: String + * @arg @p note-num (int) + * @arg @p velocity (int) + * + * @par + * Trigger a note-on, just as if it came from MIDI. + */ +int +OSCEngineReceiver::_note_on_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + /* + const char* node_path = &argv[1]->s; + const uint8_t note_num = argv[2]->i; + const uint8_t velocity = argv[3]->i; + */ + warn << "TODO: OSC note on" << endl; + //note_on(node_path, note_num, velocity); + return 0; +} + +/** @page engine_osc_namespace + * <h2>/note_off</h2> + * @arg @p response-id :: Integer + * @arg @p node-path :: String + * @arg @p note-num :: Integer + * + * @par + * Trigger a note-off, just as if it came from MIDI. + */ +int +OSCEngineReceiver::_note_off_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + /* + const char* patch_path = &argv[1]->s; + const uint8_t note_num = argv[2]->i; + */ + warn << "TODO: OSC note off" << endl; + //note_off(patch_path, note_num); + return 0; +} + +/** @page engine_osc_namespace + * <h2>/all_notes_off</h2> + * @arg @p response-id :: Integer + * @arg @p patch-path :: String + * + * @par + * Trigger a note-off for all voices, just as if it came from MIDI. + */ +int +OSCEngineReceiver::_all_notes_off_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + /* + + const char* patch_path = &argv[1]->s; + */ + warn << "TODO: OSC all notes off" << endl; + //all_notes_off(patch_path); + return 0; +} + +/** @page engine_osc_namespace + * <h2>/set_property</h2> + * @arg @p response-id :: Integer + * @arg @p uri :: URI String + * @arg @p key :: URI String + * @arg @p value :: String + * + * @par + * Set a property on a graph object. + */ +int +OSCEngineReceiver::_set_property_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + if (argc != 4 || types[0] != 'i' || types[1] != 's' || types[2] != 's') + return 1; + + const char* object_path = &argv[1]->s; + const char* key = &argv[2]->s; + + Raul::Atom value = Raul::AtomLiblo::lo_arg_to_atom(types[3], argv[3]); + + set_property(object_path, key, value); + return 0; +} + +/** @page engine_osc_namespace + * <h2>/request_property</h2> + * @arg @p response-id :: Integer + * @arg @p uri :: URI String + * @arg @p key :: URI String + * + * Request the value of a property on an object. + */ +int +OSCEngineReceiver::_request_property_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* object_path = &argv[1]->s; + const char* key = &argv[2]->s; + + request_property(object_path, key); + return 0; +} + +// Static Callbacks // + + +// Display incoming OSC messages (for debugging purposes) +int +OSCEngineReceiver::generic_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg, void* user_data) +{ + printf("[OSCEngineReceiver] %s (%s)\t", path, types); + + for (int i=0; i < argc; ++i) { + lo_arg_pp(lo_type(types[i]), argv[i]); + printf("\t"); + } + printf("\n"); + + return 1; // not handled +} + +int +OSCEngineReceiver::unknown_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg, void* user_data) +{ + const lo_address addr = lo_message_get_source(msg); + char* const url = lo_address_get_url(addr); + + warn << "Unknown OSC command " << path << " (" << types << ") " + << "received from " << url << endl; + + string error_msg = "Unknown command: "; + error_msg.append(path).append(" ").append(types); + + lo_send(addr, "/error", "s", error_msg.c_str(), LO_ARGS_END); + free(url); + + return 0; +} + +} // namespace Server +} // namespace Ingen diff --git a/src/server/OSCEngineReceiver.hpp b/src/server/OSCEngineReceiver.hpp new file mode 100644 index 00000000..8a76fa01 --- /dev/null +++ b/src/server/OSCEngineReceiver.hpp @@ -0,0 +1,118 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_OSCENGINERECEIVER_HPP +#define INGEN_ENGINE_OSCENGINERECEIVER_HPP + +#include <stdint.h> +#include <lo/lo.h> +#include "QueuedEngineInterface.hpp" +#include "Request.hpp" +#include "ingen-config.h" + +namespace Ingen { +namespace Server { + +class JackDriver; +class NodeFactory; +class PatchImpl; + +/* Some boilerplate killing macros... */ +#define LO_HANDLER_ARGS const char* path, const char* types, lo_arg** argv, int argc, lo_message msg + +/* Defines a static handler to be passed to lo_add_method, which is a trivial + * wrapper around a non-static method that does the real work. Makes a whoole + * lot of ugly boiler plate go away */ +#define LO_HANDLER(name) \ +int _##name##_cb (LO_HANDLER_ARGS);\ +inline static int name##_cb(LO_HANDLER_ARGS, void* myself)\ +{ return ((OSCEngineReceiver*)myself)->_##name##_cb(path, types, argv, argc, msg); } + +/* FIXME: Make this receive and preprocess in the same thread? */ + +/** Receives OSC messages from liblo. + * + * This inherits from QueuedEngineInterface and calls it's own functions + * via OSC. It's not actually a directly callable ServerInterface (it's + * callable via OSC...) so it should be implemented-as-a (privately inherit) + * QueuedEngineInterface, but it needs to be public so it's an EventSource + * the Driver can use. This probably should be fixed somehow.. + * + * \ingroup engine + */ +class OSCEngineReceiver : public QueuedEngineInterface +{ +public: + OSCEngineReceiver(Engine& engine, size_t queue_size, uint16_t port); + ~OSCEngineReceiver(); + +private: + struct ReceiveThread : public Raul::Thread { + explicit ReceiveThread(OSCEngineReceiver& receiver) : _receiver(receiver) {} + virtual void _run(); + private: + OSCEngineReceiver& _receiver; + }; + + friend class ReceiveThread; + + ReceiveThread* _receive_thread; + +#ifdef LIBLO_BUNDLES + static int bundle_start_cb(lo_timetag time, void* myself) { + return ((OSCEngineReceiver*)myself)->_bundle_start_cb(time); + } + static int bundle_end_cb(void* myself) { + return ((OSCEngineReceiver*)myself)->_bundle_end_cb(); + } + + int _bundle_start_cb(lo_timetag time); + int _bundle_end_cb(); +#endif + + static void error_cb(int num, const char* msg, const char* path); + static int set_response_address_cb(LO_HANDLER_ARGS, void* myself); + static int generic_cb(LO_HANDLER_ARGS, void* myself); + static int unknown_cb(LO_HANDLER_ARGS, void* myself); + + LO_HANDLER(quit); + LO_HANDLER(ping); + LO_HANDLER(ping_slow); + LO_HANDLER(register_client); + LO_HANDLER(unregister_client); + LO_HANDLER(get); + LO_HANDLER(put); + LO_HANDLER(move); + LO_HANDLER(del); + LO_HANDLER(connect); + LO_HANDLER(disconnect); + LO_HANDLER(disconnect_all); + LO_HANDLER(note_on); + LO_HANDLER(note_off); + LO_HANDLER(all_notes_off); + LO_HANDLER(learn); + LO_HANDLER(set_property); + LO_HANDLER(property_set); + LO_HANDLER(request_property); + + lo_server _server; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_OSCENGINERECEIVER_HPP diff --git a/src/server/ObjectBuffer.cpp b/src/server/ObjectBuffer.cpp new file mode 100644 index 00000000..d09fbb96 --- /dev/null +++ b/src/server/ObjectBuffer.cpp @@ -0,0 +1,147 @@ +/* This file is part of Ingen. + * Copyright 2009-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#define __STDC_LIMIT_MACROS 1 +#include <string.h> +#include <stdint.h> +#include <algorithm> +#include "raul/log.hpp" +#include "lv2/lv2plug.in/ns/ext/uri-map/uri-map.h" +#include "ingen-config.h" +#include "shared/LV2Features.hpp" +#include "shared/LV2URIMap.hpp" +#include "ObjectBuffer.hpp" +#include "Engine.hpp" + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { + +/** Allocate a new object buffer. + * \a capacity is in bytes, including LV2_Atom header + */ +ObjectBuffer::ObjectBuffer(BufferFactory& bufs, size_t capacity) + : Buffer(bufs, PortType(PortType::VALUE), capacity) +{ + capacity += sizeof(LV2_Atom); + +#ifdef HAVE_POSIX_MEMALIGN + const int ret = posix_memalign((void**)&_buf, 16, capacity); +#else + _buf = (LV2_Atom*)malloc(capacity); + const int ret = (_buf != NULL) ? 0 : -1; +#endif + + if (ret != 0) { + error << "Failed to allocate object buffer. Aborting." << endl; + exit(EXIT_FAILURE); + } + + clear(); +} + +ObjectBuffer::~ObjectBuffer() +{ + free(_buf); +} + +void +ObjectBuffer::clear() +{ + // null + _buf->type = 0; + _buf->size = 0; +} + +void +ObjectBuffer::copy(Context& context, const Buffer* src_buf) +{ + const ObjectBuffer* src = dynamic_cast<const ObjectBuffer*>(src_buf); + if (!src || src == this || src->_buf == _buf) + return; + + // Copy only if src is a POD object that fits + if (src->_buf->type != 0 && src_buf->size() <= size()) + memcpy(_buf, src->_buf, sizeof(LV2_Atom) + src_buf->size()); +} + +void +ObjectBuffer::resize(size_t size) +{ + const uint32_t contents_size = sizeof(LV2_Atom) + _buf->size; + + _buf = (LV2_Atom*)realloc(_buf, sizeof(LV2_Atom) + size); + _size = size + sizeof(LV2_Atom); + + // If we shrunk and chopped the current contents, clear corrupt data + if (size < contents_size) + clear(); +} + +void* +ObjectBuffer::port_data(PortType port_type, SampleCount offset) +{ + switch (port_type.symbol()) { + case PortType::CONTROL: + case PortType::AUDIO: + switch (_type.symbol()) { + case PortType::CONTROL: + return (float*)atom()->body; + case PortType::AUDIO: + return (float*)((LV2_Atom_Vector*)atom()->body)->elems + offset; + default: + warn << "Audio data requested from non-audio buffer" << endl; + return NULL; + } + break; + default: + return _buf; + } +} + +const void* +ObjectBuffer::port_data(PortType port_type, SampleCount offset) const +{ + switch (port_type.symbol()) { + case PortType::CONTROL: + case PortType::AUDIO: + switch (_type.symbol()) { + case PortType::CONTROL: + return (float*)atom()->body; + case PortType::AUDIO: + return (float*)((LV2_Atom_Vector*)atom()->body)->elems + offset; + default: + warn << "Audio data requested from non-audio buffer" << endl; + return NULL; + } + break; + default: + return _buf; + } +} + +void +ObjectBuffer::prepare_write(Context& context) +{ + _buf->size = _size - sizeof(LV2_Atom); +} + +} // namespace Server +} // namespace Ingen + diff --git a/src/server/ObjectBuffer.hpp b/src/server/ObjectBuffer.hpp new file mode 100644 index 00000000..11481ad1 --- /dev/null +++ b/src/server/ObjectBuffer.hpp @@ -0,0 +1,56 @@ +/* This file is part of Ingen. + * Copyright 2009-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_OBJECTBUFFER_HPP +#define INGEN_ENGINE_OBJECTBUFFER_HPP + +#include "lv2/lv2plug.in/ns/ext/atom/atom.h" +#include "ingen/PortType.hpp" +#include "Buffer.hpp" + +namespace Ingen { +namespace Server { + +class Context; + +class ObjectBuffer : public Buffer { +public: + ObjectBuffer(BufferFactory& bufs, size_t capacity); + ~ObjectBuffer(); + + void clear(); + + void* port_data(PortType port_type, SampleCount offset); + const void* port_data(PortType port_type, SampleCount offset) const; + + void prepare_write(Context& context); + + void copy(Context& context, const Buffer* src); + + void resize(size_t size); + + LV2_Atom* atom() { return _buf; } + const LV2_Atom* atom() const { return _buf; } + +private: + LV2_Atom* _buf; ///< Contents +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_OBJECTBUFFER_HPP diff --git a/src/server/ObjectSender.cpp b/src/server/ObjectSender.cpp new file mode 100644 index 00000000..22aff285 --- /dev/null +++ b/src/server/ObjectSender.cpp @@ -0,0 +1,149 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 "ObjectSender.hpp" +#include "ingen/ClientInterface.hpp" +#include "shared/LV2URIMap.hpp" +#include "EngineStore.hpp" +#include "PatchImpl.hpp" +#include "NodeImpl.hpp" +#include "PortImpl.hpp" +#include "ConnectionImpl.hpp" +#include "NodeFactory.hpp" +#include "ingen/PortType.hpp" +#include "AudioBuffer.hpp" + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { + +void +ObjectSender::send_object(ClientInterface* client, + const GraphObjectImpl* object, + bool recursive) +{ + const PatchImpl* patch = dynamic_cast<const PatchImpl*>(object); + if (patch) { + send_patch(client, patch, recursive); + return; + } + + const NodeImpl* node = dynamic_cast<const NodeImpl*>(object); + if (node) { + send_node(client, node, recursive); + return; + } + + const PortImpl* port = dynamic_cast<const PortImpl*>(object); + if (port) { + send_port(client, port); + return; + } +} + +void +ObjectSender::send_patch(ClientInterface* client, const PatchImpl* patch, bool recursive, bool bundle) +{ + if (bundle) + client->bundle_begin(); + + client->put(patch->path(), + patch->properties(Resource::INTERNAL), + Resource::INTERNAL); + + client->put(patch->path(), + patch->properties(Resource::EXTERNAL), + Resource::EXTERNAL); + + if (recursive) { + // Send nodes + for (List<NodeImpl*>::const_iterator j = patch->nodes().begin(); + j != patch->nodes().end(); ++j) { + const NodeImpl* const node = (*j); + send_node(client, node, true, false); + } + + // Send ports + for (uint32_t i=0; i < patch->num_ports(); ++i) { + PortImpl* const port = patch->port_impl(i); + send_port(client, port, false); + } + + // Send connections + for (PatchImpl::Connections::const_iterator j = patch->connections().begin(); + j != patch->connections().end(); ++j) + client->connect(j->second->src_port_path(), j->second->dst_port_path()); + } + + if (bundle) + client->bundle_end(); +} + +/** Sends a node or a patch */ +void +ObjectSender::send_node(ClientInterface* client, const NodeImpl* node, bool recursive, bool bundle) +{ + PluginImpl* const plugin = node->plugin_impl(); + + if (plugin->type() == Plugin::Patch) { + send_patch(client, (PatchImpl*)node, recursive); + return; + } + + if (plugin->uri().length() == 0) { + error << "Node " << node->path() << "'s plugin has no URI! Not sending." << endl; + return; + } + + if (bundle) + client->bundle_begin(); + + client->put(node->path(), node->properties()); + + if (recursive) { + // Send ports + for (size_t j=0; j < node->num_ports(); ++j) + send_port(client, node->port_impl(j), false); + } + + if (bundle) + client->bundle_end(); +} + +void +ObjectSender::send_port(ClientInterface* client, const PortImpl* port, bool bundle) +{ + assert(port); + + if (bundle) + client->bundle_begin(); + + client->put(port->path(), port->properties()); + + // Send control value + if (port->is_a(PortType::CONTROL)) + client->set_property(port->path(), port->bufs().uris().ingen_value, port->value()); + + if (bundle) + client->bundle_end(); +} + +} // namespace Server +} // namespace Ingen + diff --git a/src/server/ObjectSender.hpp b/src/server/ObjectSender.hpp new file mode 100644 index 00000000..a743c6d5 --- /dev/null +++ b/src/server/ObjectSender.hpp @@ -0,0 +1,66 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_OBJECTSENDER_HPP +#define INGEN_ENGINE_OBJECTSENDER_HPP + +namespace Ingen { + +class ClientInterface; + +namespace Server { + +class GraphObjectImpl; +class PatchImpl; +class NodeImpl; +class PortImpl; +class PluginImpl; + +/** Utility class for sending GraphObjects to clients through ClientInterface. + * + * While ClientInterface is the direct low level message-based interface + * (protocol), this is used from the engine to easily send proper Objects + * with these messages (which is done in a few different parts of the code). + * + * Basically a serialiser, except to calls on ClientInterface rather than + * eg a byte stream. + */ +class ObjectSender { +public: + static void send_object(ClientInterface* client, + const GraphObjectImpl* object, + bool recursive); + +private: + static void send_patch(ClientInterface* client, + const PatchImpl* patch, + bool recursive, + bool bundle=true); + static void send_node(ClientInterface* client, + const NodeImpl* node, + bool recursive, + bool bundle=true); + static void send_port(ClientInterface* client, + const PortImpl* port, + bool bundle=true); +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_OBJECTSENDER_HPP + diff --git a/src/server/OutputPort.cpp b/src/server/OutputPort.cpp new file mode 100644 index 00000000..487832c3 --- /dev/null +++ b/src/server/OutputPort.cpp @@ -0,0 +1,76 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 "ingen/Patch.hpp" +#include "shared/LV2URIMap.hpp" +#include "Buffer.hpp" +#include "NodeImpl.hpp" +#include "OutputPort.hpp" +#include "ProcessContext.hpp" + +using namespace std; + +namespace Ingen { +namespace Server { + +OutputPort::OutputPort(BufferFactory& bufs, + NodeImpl* parent, + const Raul::Symbol& symbol, + uint32_t index, + uint32_t poly, + PortType type, + const Raul::Atom& value, + size_t buffer_size) + : PortImpl(bufs, parent, symbol, index, poly, type, value, buffer_size) +{ + if (!dynamic_cast<Patch*>(parent)) + add_property(bufs.uris().rdf_type, bufs.uris().lv2_OutputPort); + + if (type == PortType::CONTROL) + _broadcast = true; + + setup_buffers(bufs, poly); +} + +bool +OutputPort::get_buffers(BufferFactory& bufs, Raul::Array<BufferFactory::Ref>* buffers, uint32_t poly) +{ + for (uint32_t v = 0; v < poly; ++v) + buffers->at(v) = bufs.get(buffer_type(), _buffer_size); + + return true; +} + +void +OutputPort::pre_process(Context& context) +{ + for (uint32_t v = 0; v < _poly; ++v) + _buffers->at(v)->prepare_write(context); +} + +void +OutputPort::post_process(Context& context) +{ + for (uint32_t v = 0; v < _poly; ++v) + _buffers->at(v)->prepare_read(context); + + if (_broadcast) + broadcast_value(context, false); +} + +} // namespace Server +} // namespace Ingen diff --git a/src/server/OutputPort.hpp b/src/server/OutputPort.hpp new file mode 100644 index 00000000..f16a5ff7 --- /dev/null +++ b/src/server/OutputPort.hpp @@ -0,0 +1,65 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_OUTPUTPORT_HPP +#define INGEN_ENGINE_OUTPUTPORT_HPP + +#include <string> +#include <cstdlib> +#include "PortImpl.hpp" + +namespace Ingen { +namespace Server { + +/** An output port. + * + * Output ports always have a locally allocated buffer, and buffer() will + * always return that buffer. (This is very different from InputPort) + * + * This class actually adds no functionality to Port whatsoever right now, + * it will in the future when more advanced port types exist, and it makes + * things clearer throughout the engine. + * + * \ingroup engine + */ +class OutputPort : virtual public PortImpl +{ +public: + OutputPort(BufferFactory& bufs, + NodeImpl* parent, + const Raul::Symbol& symbol, + uint32_t index, + uint32_t poly, + PortType type, + const Raul::Atom& value, + size_t buffer_size=0); + + bool get_buffers(BufferFactory& bufs, Raul::Array<BufferFactory::Ref>* buffers, uint32_t poly); + + void pre_process(Context& context); + void post_process(Context& context); + + virtual ~OutputPort() {} + + bool is_input() const { return false; } + bool is_output() const { return true; } +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_OUTPUTPORT_HPP diff --git a/src/server/PatchImpl.cpp b/src/server/PatchImpl.cpp new file mode 100644 index 00000000..72545b1b --- /dev/null +++ b/src/server/PatchImpl.cpp @@ -0,0 +1,470 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <cassert> +#include <cmath> +#include <string> +#include "raul/log.hpp" +#include "shared/World.hpp" +#include "shared/LV2URIMap.hpp" +#include "ThreadManager.hpp" +#include "NodeImpl.hpp" +#include "PatchImpl.hpp" +#include "PatchPlugin.hpp" +#include "PortImpl.hpp" +#include "ConnectionImpl.hpp" +#include "DuplexPort.hpp" +#include "Engine.hpp" +#include "ProcessSlave.hpp" +#include "Driver.hpp" +#include "ingen-config.h" + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { + +PatchImpl::PatchImpl(Engine& engine, + const Raul::Symbol& symbol, + uint32_t poly, + PatchImpl* parent, + SampleRate srate, + uint32_t internal_poly) + : NodeImpl(new PatchPlugin(*engine.world()->uris().get(), + engine.world()->uris()->ingen_Patch.c_str(), "patch", "Ingen Patch"), + symbol, poly, parent, srate) + , _engine(engine) + , _internal_poly(internal_poly) + , _compiled_patch(NULL) + , _process(false) +{ + assert(internal_poly >= 1); +} + +PatchImpl::~PatchImpl() +{ + assert(!_activated); + + delete _compiled_patch; + delete _plugin; +} + +void +PatchImpl::activate(BufferFactory& bufs) +{ + NodeImpl::activate(bufs); + + for (List<NodeImpl*>::iterator i = _nodes.begin(); i != _nodes.end(); ++i) + (*i)->activate(bufs); + + assert(_activated); +} + +void +PatchImpl::deactivate() +{ + if (_activated) { + NodeImpl::deactivate(); + + for (List<NodeImpl*>::iterator i = _nodes.begin(); i != _nodes.end(); ++i) { + if ((*i)->activated()) + (*i)->deactivate(); + assert(!(*i)->activated()); + } + } + assert(!_activated); +} + +void +PatchImpl::disable() +{ + ThreadManager::assert_thread(THREAD_PROCESS); + + _process = false; + + for (List<PortImpl*>::iterator i = _output_ports.begin(); i != _output_ports.end(); ++i) + if ((*i)->context() == Context::AUDIO) + (*i)->clear_buffers(); +} + +bool +PatchImpl::prepare_internal_poly(BufferFactory& bufs, uint32_t poly) +{ + ThreadManager::assert_thread(THREAD_PRE_PROCESS); + + // TODO: Subpatch dynamic polyphony (i.e. changing port polyphony) + + for (List<NodeImpl*>::iterator i = _nodes.begin(); i != _nodes.end(); ++i) + (*i)->prepare_poly(bufs, poly); + + for (List<NodeImpl*>::iterator i = _nodes.begin(); i != _nodes.end(); ++i) + for (uint32_t j = 0; j < (*i)->num_ports(); ++j) + (*i)->port_impl(j)->prepare_poly_buffers(bufs); + + return true; +} + +bool +PatchImpl::apply_internal_poly(ProcessContext& context, BufferFactory& bufs, Raul::Maid& maid, uint32_t poly) +{ + ThreadManager::assert_thread(THREAD_PROCESS); + + // TODO: Subpatch dynamic polyphony (i.e. changing port polyphony) + + for (List<NodeImpl*>::iterator i = _nodes.begin(); i != _nodes.end(); ++i) + (*i)->apply_poly(maid, poly); + + for (List<NodeImpl*>::iterator i = _nodes.begin(); i != _nodes.end(); ++i) { + for (uint32_t j = 0; j < (*i)->num_ports(); ++j) { + PortImpl* const port = (*i)->port_impl(j); + if (port->is_input() && dynamic_cast<InputPort*>(port)->direct_connect()) + port->setup_buffers(bufs, port->poly()); + port->connect_buffers(context.offset()); + } + } + + const bool polyphonic = parent_patch() && (poly == parent_patch()->internal_poly()); + for (List<PortImpl*>::iterator i = _output_ports.begin(); i != _output_ports.end(); ++i) + (*i)->setup_buffers(bufs, polyphonic ? poly : 1); + + _internal_poly = poly; + + return true; +} + +/** Run the patch for the specified number of frames. + * + * Calls all Nodes in (roughly, if parallel) the order _compiled_patch specifies. + */ +void +PatchImpl::process(ProcessContext& context) +{ + if (!_process) + return; + + NodeImpl::pre_process(context); + + // Run all nodes + if (_compiled_patch && _compiled_patch->size() > 0) { + if (context.slaves().size() > 0) { + process_parallel(context); + } else { + process_single(context); + } + } + + // Queue any cross-context connections + for (CompiledPatch::QueuedConnections::iterator i = _compiled_patch->queued_connections.begin(); + i != _compiled_patch->queued_connections.end(); ++i) { + (*i)->queue(context); + } + + NodeImpl::post_process(context); +} + +void +PatchImpl::process_parallel(ProcessContext& context) +{ + size_t n_slaves = context.slaves().size(); + + CompiledPatch* const cp = _compiled_patch; + + /* Start p-1 slaves */ + + if (n_slaves >= cp->size()) + n_slaves = cp->size()-1; + + if (n_slaves > 0) { + for (size_t i = 0; i < cp->size(); ++i) + (*cp)[i].node()->reset_input_ready(); + + for (size_t i = 0; i < n_slaves; ++i) + context.slaves()[i]->whip(cp, i+1, context); + } + + /* Process ourself until everything is done + * This is analogous to ProcessSlave::_whipped(), but this is the master + * (i.e. what the main Jack process thread calls). Where ProcessSlave + * waits on input, this just skips the node and tries the next, to avoid + * waiting in the Jack thread which pisses Jack off. + */ + + size_t index = 0; + size_t num_finished = 0; // Number of consecutive finished nodes hit + + while (num_finished < cp->size()) { + CompiledNode& n = (*cp)[index]; + + if (n.node()->process_lock()) { + if (n.node()->n_inputs_ready() == n.n_providers()) { + n.node()->process(context); + + /* Signal dependants their input is ready */ + for (uint32_t i = 0; i < n.dependants().size(); ++i) + n.dependants()[i]->signal_input_ready(); + + ++num_finished; + } else { + n.node()->process_unlock(); + num_finished = 0; + } + } else { + if (n.node()->n_inputs_ready() == n.n_providers()) + ++num_finished; + else + num_finished = 0; + } + + index = (index + 1) % cp->size(); + } + + /* Tell slaves we're done in case we beat them, and pray they're + * really done by the start of next cycle. + * FIXME: This probably breaks (race) at extremely small nframes where + * ingen is the majority of the DSP load. + */ + for (uint32_t i = 0; i < n_slaves; ++i) + context.slaves()[i]->finish(); +} + +void +PatchImpl::process_single(ProcessContext& context) +{ + for (size_t i = 0; i < _compiled_patch->size(); ++i) + (*_compiled_patch)[i].node()->process(context); +} + +void +PatchImpl::set_buffer_size(Context& context, BufferFactory& bufs, PortType type, size_t size) +{ + NodeImpl::set_buffer_size(context, bufs, type, size); + + for (size_t i = 0; i < _compiled_patch->size(); ++i) + (*_compiled_patch)[i].node()->set_buffer_size(context, bufs, type, size); +} + +// Patch specific stuff + +/** Add a node. + * Preprocessing thread only. + */ +void +PatchImpl::add_node(List<NodeImpl*>::Node* ln) +{ + ThreadManager::assert_thread(THREAD_PRE_PROCESS); + assert(ln != NULL); + assert(ln->elem() != NULL); + assert(ln->elem()->parent_patch() == this); + //assert(ln->elem()->polyphony() == _internal_poly); + + _nodes.push_back(ln); +} + +/** Remove a node. + * Preprocessing thread only. + */ +PatchImpl::Nodes::Node* +PatchImpl::remove_node(const Raul::Symbol& symbol) +{ + ThreadManager::assert_thread(THREAD_PRE_PROCESS); + for (List<NodeImpl*>::iterator i = _nodes.begin(); i != _nodes.end(); ++i) + if ((*i)->symbol() == symbol) + return _nodes.erase(i); + + return NULL; +} + +void +PatchImpl::add_connection(SharedPtr<ConnectionImpl> c) +{ + ThreadManager::assert_thread(THREAD_PRE_PROCESS); + _connections.insert(make_pair(make_pair(c->src_port(), c->dst_port()), c)); +} + +/** Remove a connection. + * Preprocessing thread only. + */ +SharedPtr<ConnectionImpl> +PatchImpl::remove_connection(const PortImpl* src_port, const PortImpl* dst_port) +{ + ThreadManager::assert_thread(THREAD_PRE_PROCESS); + Connections::iterator i = _connections.find(make_pair(src_port, dst_port)); + if (i != _connections.end()) { + SharedPtr<ConnectionImpl> c = PtrCast<ConnectionImpl>(i->second); + _connections.erase(i); + return c; + } else { + error << "[PatchImpl::remove_connection] Connection not found" << endl; + return SharedPtr<ConnectionImpl>(); + } +} + +bool +PatchImpl::has_connection(const PortImpl* src_port, const PortImpl* dst_port) const +{ + ThreadManager::assert_thread(THREAD_PRE_PROCESS); + Connections::const_iterator i = _connections.find(make_pair(src_port, dst_port)); + return (i != _connections.end()); +} + +uint32_t +PatchImpl::num_ports() const +{ + if (ThreadManager::thread_is(THREAD_PROCESS)) + return NodeImpl::num_ports(); + else + return _input_ports.size() + _output_ports.size(); +} + +/** Create a port. Not realtime safe. + */ +PortImpl* +PatchImpl::create_port(BufferFactory& bufs, const string& name, PortType type, size_t buffer_size, bool is_output, bool polyphonic) +{ + if (type == PortType::UNKNOWN) { + error << "[PatchImpl::create_port] Unknown port type " << type.uri() << endl; + return NULL; + } + + assert( !(type == PortType::UNKNOWN) ); + + return new DuplexPort(bufs, this, name, num_ports(), polyphonic, _polyphony, + type, Raul::Atom(), buffer_size, is_output); +} + +/** Remove port from ports list used in pre-processing thread. + * + * Port is not removed from ports array for process thread (which could be + * simultaneously running). + * + * Realtime safe. Preprocessing thread only. + */ +List<PortImpl*>::Node* +PatchImpl::remove_port(const string& symbol) +{ + ThreadManager::assert_thread(THREAD_PRE_PROCESS); + + bool found = false; + List<PortImpl*>::Node* ret = NULL; + for (List<PortImpl*>::iterator i = _input_ports.begin(); i != _input_ports.end(); ++i) { + if ((*i)->symbol() == symbol) { + ret = _input_ports.erase(i); + found = true; + break; + } + } + + if (!found) + for (List<PortImpl*>::iterator i = _output_ports.begin(); i != _output_ports.end(); ++i) { + if ((*i)->symbol() == symbol) { + ret = _output_ports.erase(i); + found = true; + break; + } + } + + if ( ! found) + error << "[PatchImpl::remove_port] Port not found!" << endl; + + return ret; +} + +/** Remove all ports from ports list used in pre-processing thread. + * + * Ports are not removed from ports array for process thread (which could be + * simultaneously running). Returned is a (inputs, outputs) pair. + * + * Realtime safe. Preprocessing thread only. + */ +void +PatchImpl::clear_ports() +{ + ThreadManager::assert_thread(THREAD_PRE_PROCESS); + + _input_ports.clear(); + _output_ports.clear(); +} + +Raul::Array<PortImpl*>* +PatchImpl::build_ports_array() const +{ + ThreadManager::assert_thread(THREAD_PRE_PROCESS); + + Raul::Array<PortImpl*>* const result = new Raul::Array<PortImpl*>(_input_ports.size() + _output_ports.size()); + + size_t i = 0; + + for (List<PortImpl*>::const_iterator p = _input_ports.begin(); p != _input_ports.end(); ++p,++i) + result->at(i) = *p; + + for (List<PortImpl*>::const_iterator p = _output_ports.begin(); p != _output_ports.end(); ++p,++i) + result->at(i) = *p; + + return result; +} + +/** Find the process order for this Patch. + * + * The process order is a flat list that the patch will execute in order + * when it's run() method is called. Return value is a newly allocated list + * which the caller is reponsible to delete. Note that this function does + * NOT actually set the process order, it is returned so it can be inserted + * at the beginning of an audio cycle (by various Events). + * + * Not realtime safe. + */ +CompiledPatch* +PatchImpl::compile() const +{ + ThreadManager::assert_thread(THREAD_PRE_PROCESS); + + CompiledPatch* const compiled_patch = new CompiledPatch();//_nodes.size()); + + for (Nodes::const_iterator i = _nodes.begin(); i != _nodes.end(); ++i) + (*i)->traversed(false); + + for (Nodes::const_iterator i = _nodes.begin(); i != _nodes.end(); ++i) { + NodeImpl* const node = (*i); + // Either a sink or connected to our output ports: + if ( ( ! node->traversed()) && node->dependants()->size() == 0) + compile_recursive(node, compiled_patch); + } + + // Traverse any nodes we didn't hit yet + for (Nodes::const_iterator i = _nodes.begin(); i != _nodes.end(); ++i) { + NodeImpl* const node = (*i); + if ( ! node->traversed()) + compile_recursive(node, compiled_patch); + } + + // Add any queued connections that must be run after a cycle + for (Connections::const_iterator i = _connections.begin(); i != _connections.end(); ++i) { + SharedPtr<ConnectionImpl> c = PtrCast<ConnectionImpl>(i->second); + if (c->src_port()->context() == Context::AUDIO && + c->dst_port()->context() == Context::MESSAGE) { + compiled_patch->queued_connections.push_back(c.get()); + } + } + + assert(compiled_patch->size() == _nodes.size()); + + return compiled_patch; +} + +} // namespace Server +} // namespace Ingen diff --git a/src/server/PatchImpl.hpp b/src/server/PatchImpl.hpp new file mode 100644 index 00000000..20ff59a6 --- /dev/null +++ b/src/server/PatchImpl.hpp @@ -0,0 +1,165 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_PATCHIMPL_HPP +#define INGEN_ENGINE_PATCHIMPL_HPP + +#include <cstdlib> +#include <string> +#include "raul/List.hpp" +#include "ingen/PortType.hpp" +#include "ingen/Patch.hpp" +#include "NodeImpl.hpp" +#include "PluginImpl.hpp" +#include "CompiledPatch.hpp" + +namespace Ingen { + +class Connection; + +namespace Server { + +class CompiledPatch; +class ConnectionImpl; +class Context; +class Engine; +class ProcessContext; + +/** A group of nodes in a graph, possibly polyphonic. + * + * Note that this is also a Node, just one which contains Nodes. + * Therefore infinite subpatching is possible, of polyphonic + * patches of polyphonic nodes etc. etc. + * + * \ingroup engine + */ +class PatchImpl : public NodeImpl, public Patch +{ +public: + PatchImpl(Engine& engine, + const Raul::Symbol& symbol, + uint32_t poly, + PatchImpl* parent, + SampleRate srate, + uint32_t local_poly); + + virtual ~PatchImpl(); + + void activate(BufferFactory& bufs); + void deactivate(); + + void process(ProcessContext& context); + + void set_buffer_size(Context& context, BufferFactory& bufs, PortType type, size_t size); + + /** Prepare for a new (internal) polyphony value. + * + * Preprocessor thread, poly is actually applied by apply_internal_poly. + * \return true on success. + */ + bool prepare_internal_poly(BufferFactory& bufs, uint32_t poly); + + /** Apply a new (internal) polyphony value. + * + * Audio thread. + * + * \param context Process context + * \param bufs New set of buffers + * \param poly Must be < the most recent value passed to prepare_internal_poly. + * \param maid Any objects no longer needed will be pushed to this + */ + bool apply_internal_poly(ProcessContext& context, BufferFactory& bufs, Raul::Maid& maid, uint32_t poly); + + // Patch specific stuff not inherited from Node + + typedef Raul::List<NodeImpl*> Nodes; + + void add_node(Nodes::Node* tn); + Nodes::Node* remove_node(const Raul::Symbol& symbol); + + Nodes& nodes() { return _nodes; } + Connections& connections() { return _connections; } + + const Nodes& nodes() const { return _nodes; } + const Connections& connections() const { return _connections; } + + uint32_t num_ports() const; + + PortImpl* create_port(BufferFactory& bufs, const std::string& name, PortType type, size_t buffer_size, bool is_output, bool polyphonic); + void add_input(Raul::List<PortImpl*>::Node* port) { _input_ports.push_back(port); } ///< Preprocesser thread + void add_output(Raul::List<PortImpl*>::Node* port) { _output_ports.push_back(port); } ///< Preprocessor thread + Raul::List<PortImpl*>::Node* remove_port(const std::string& name); + void clear_ports(); + + void add_connection(SharedPtr<ConnectionImpl> c); + + SharedPtr<ConnectionImpl> remove_connection(const PortImpl* src_port, const PortImpl* dst_port); + + bool has_connection(const PortImpl* src_port, const PortImpl* dst_port) const; + + CompiledPatch* compiled_patch() { return _compiled_patch; } + void compiled_patch(CompiledPatch* cp) { _compiled_patch = cp; } + + Raul::Array<PortImpl*>* external_ports() { return _ports; } + void external_ports(Raul::Array<PortImpl*>* pa) { _ports = pa; } + + CompiledPatch* compile() const; + Raul::Array<PortImpl*>* build_ports_array() const; + + /** Whether to run this patch's DSP bits in the audio thread */ + bool enabled() const { return _process; } + void enable() { _process = true; } + void disable(); + + uint32_t internal_poly() const { return _internal_poly; } + +private: + inline void compile_recursive(NodeImpl* n, CompiledPatch* output) const; + void process_parallel(ProcessContext& context); + void process_single(ProcessContext& context); + + Engine& _engine; + uint32_t _internal_poly; + CompiledPatch* _compiled_patch; ///< Accessed in audio thread only + Connections _connections; ///< Accessed in preprocessing thread only + Raul::List<PortImpl*> _input_ports; ///< Accessed in preprocessing thread only + Raul::List<PortImpl*> _output_ports; ///< Accessed in preprocessing thread only + Nodes _nodes; ///< Accessed in preprocessing thread only + bool _process; +}; + +/** Private helper for compile */ +inline void +PatchImpl::compile_recursive(NodeImpl* n, CompiledPatch* output) const +{ + if (n == NULL || n->traversed()) + return; + + n->traversed(true); + assert(output != NULL); + + for (Raul::List<NodeImpl*>::iterator i = n->providers()->begin(); i != n->providers()->end(); ++i) + if ( ! (*i)->traversed() ) + compile_recursive((*i), output); + + output->push_back(CompiledNode(n, n->providers()->size(), n->dependants())); +} + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_PATCHIMPL_HPP diff --git a/src/server/PatchPlugin.hpp b/src/server/PatchPlugin.hpp new file mode 100644 index 00000000..72885c7d --- /dev/null +++ b/src/server/PatchPlugin.hpp @@ -0,0 +1,67 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_PATCHPLUGIN_HPP +#define INGEN_ENGINE_PATCHPLUGIN_HPP + +#include "ingen-config.h" + +#include <string> +#include "PluginImpl.hpp" + +namespace Ingen { +namespace Server { + +class NodeImpl; + +/** Implementation of a Patch plugin. + * + * Patches don't actually work like this yet... + */ +class PatchPlugin : public PluginImpl +{ +public: + PatchPlugin( + Shared::LV2URIMap& uris, + const std::string& uri, + const std::string& symbol, + const std::string& name) + : PluginImpl(uris, Plugin::Patch, uri) + {} + + NodeImpl* instantiate(BufferFactory& bufs, + const std::string& name, + bool polyphonic, + PatchImpl* parent, + Engine& engine) + { + return NULL; + } + + const std::string symbol() const { return "patch"; } + const std::string name() const { return "Ingen Patch"; } + +private: + const std::string _symbol; + const std::string _name; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_PATCHPLUGIN_HPP + diff --git a/src/server/PluginImpl.cpp b/src/server/PluginImpl.cpp new file mode 100644 index 00000000..4eb2b078 --- /dev/null +++ b/src/server/PluginImpl.cpp @@ -0,0 +1,50 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 "raul/log.hpp" +#include "PluginImpl.hpp" + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { + +void +PluginImpl::load() +{ + if (!_module) { + debug << "Loading plugin library " << _library_path << endl; + _module = new Glib::Module(_library_path, Glib::MODULE_BIND_LOCAL); + if (!(*_module)) + delete _module; + } +} + +void +PluginImpl::unload() +{ + if (_module) { + debug << "Unloading plugin library " << _library_path << endl; + delete _module; + _module = NULL; + } +} + +} // namespace Server +} // namespace Ingen + diff --git a/src/server/PluginImpl.hpp b/src/server/PluginImpl.hpp new file mode 100644 index 00000000..cea1145d --- /dev/null +++ b/src/server/PluginImpl.hpp @@ -0,0 +1,88 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_PLUGINIMPL_HPP +#define INGEN_ENGINE_PLUGINIMPL_HPP + +#include <cstdlib> +#include <string> + +#include <boost/utility.hpp> +#include <glibmm/module.h> + +#include "ingen/Plugin.hpp" +#include "shared/ResourceImpl.hpp" + +namespace Ingen { + +namespace Shared { class LV2URIMap; } + +namespace Server { + +class PatchImpl; +class NodeImpl; +class Engine; +class BufferFactory; + +/** Implementation of a plugin (internal code, or a loaded shared library). + * + * Conceptually, a Node is an instance of this. + */ +class PluginImpl : public Plugin + , public Ingen::Shared::ResourceImpl + , public boost::noncopyable +{ +public: + PluginImpl(Ingen::Shared::LV2URIMap& uris, + Type type, + const std::string& uri, + const std::string library_path = "") + : ResourceImpl(uris, uri) + , _type(type) + , _library_path(library_path) + , _module(NULL) + {} + + virtual NodeImpl* instantiate(BufferFactory& bufs, + const std::string& name, + bool polyphonic, + PatchImpl* parent, + Engine& engine) = 0; + + virtual const std::string symbol() const = 0; + + virtual const std::string& library_path() const { return _library_path; } + + void load(); + void unload(); + + Plugin::Type type() const { return _type; } + void type(Plugin::Type t) { _type = t; } + Glib::Module* module() const { return _module; } + void module(Glib::Module* module) { _module = module; } + +protected: + Plugin::Type _type; + mutable std::string _library_path; + Glib::Module* _module; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_PLUGINIMPL_HPP + diff --git a/src/server/PortImpl.cpp b/src/server/PortImpl.cpp new file mode 100644 index 00000000..a448916e --- /dev/null +++ b/src/server/PortImpl.cpp @@ -0,0 +1,251 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 "raul/Array.hpp" +#include "raul/Maid.hpp" +#include "shared/LV2URIMap.hpp" +#include "lv2/lv2plug.in/ns/ext/contexts/contexts.h" +#include "ingen/PortType.hpp" +#include "events/SendPortValue.hpp" +#include "events/SendPortActivity.hpp" +#include "AudioBuffer.hpp" +#include "BufferFactory.hpp" +#include "Engine.hpp" +#include "EventBuffer.hpp" +#include "LV2Atom.hpp" +#include "NodeImpl.hpp" +#include "ObjectBuffer.hpp" +#include "PortImpl.hpp" +#include "ThreadManager.hpp" + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { + +PortImpl::PortImpl(BufferFactory& bufs, + NodeImpl* const node, + const Raul::Symbol& name, + uint32_t index, + uint32_t poly, + PortType type, + const Atom& value, + size_t buffer_size) + : GraphObjectImpl(bufs.uris(), node, name) + , _bufs(bufs) + , _index(index) + , _poly(poly) + , _buffer_size(buffer_size) + , _buffer_type(type) + , _value(value) + , _broadcast(false) + , _set_by_user(false) + , _last_broadcasted_value(value) + , _context(Context::AUDIO) + , _buffers(new Array<BufferFactory::Ref>(static_cast<size_t>(poly))) + , _prepared_buffers(NULL) +{ + _types.insert(type); + assert(node != NULL); + assert(_poly > 0); + + if (_buffer_size == 0) + _buffer_size = bufs.default_buffer_size(type); + + const Ingen::Shared::LV2URIMap& uris = bufs.uris(); + add_property(uris.rdf_type, type.uri()); + set_property(uris.lv2_index, Atom((int32_t)index)); + set_context(_context); + + if (type == PortType::EVENTS) + _broadcast = true; // send activity blips +} + +PortImpl::~PortImpl() +{ + delete _buffers; +} + +bool +PortImpl::supports(const Raul::URI& value_type) const +{ + return has_property(_bufs.uris().atom_supports, value_type); +} + +Raul::Array<BufferFactory::Ref>* +PortImpl::set_buffers(Raul::Array<BufferFactory::Ref>* buffers) +{ + ThreadManager::assert_thread(THREAD_PROCESS); + + Raul::Array<BufferFactory::Ref>* ret = NULL; + if (buffers != _buffers) { + ret = _buffers; + _buffers = buffers; + } + + connect_buffers(); + + return ret; +} + +bool +PortImpl::prepare_poly(BufferFactory& bufs, uint32_t poly) +{ + ThreadManager::assert_thread(THREAD_PRE_PROCESS); + if (buffer_type() != PortType::CONTROL && buffer_type() != PortType::AUDIO) + return false; + + if (_poly == poly) + return true; + + if (_prepared_buffers && _prepared_buffers->size() != poly) { + delete _prepared_buffers; + _prepared_buffers = NULL; + } + + if (!_prepared_buffers) + _prepared_buffers = new Array<BufferFactory::Ref>(poly, *_buffers, NULL); + + return true; +} + +void +PortImpl::prepare_poly_buffers(BufferFactory& bufs) +{ + if (_prepared_buffers) + get_buffers(bufs, _prepared_buffers, _prepared_buffers->size()); +} + +bool +PortImpl::apply_poly(Maid& maid, uint32_t poly) +{ + ThreadManager::assert_thread(THREAD_PROCESS); + if (buffer_type() != PortType::CONTROL && buffer_type() != PortType::AUDIO) + return false; + + if (!_prepared_buffers) + return true; + + assert(poly == _prepared_buffers->size()); + + _poly = poly; + + // Apply a new set of buffers from a preceding call to prepare_poly + maid.push(set_buffers(_prepared_buffers)); + assert(_buffers == _prepared_buffers); + _prepared_buffers = NULL; + + if (is_a(PortType::CONTROL)) + for (uint32_t v = 0; v < _poly; ++v) + if (_buffers->at(v)) + boost::static_pointer_cast<AudioBuffer>(_buffers->at(v))->set_value( + _value.get_float(), 0, 0); + + assert(_buffers->size() >= poly); + assert(this->poly() == poly); + assert(!_prepared_buffers); + + return true; +} + +void +PortImpl::set_buffer_size(Context& context, BufferFactory& bufs, size_t size) +{ + _buffer_size = size; + + for (uint32_t v = 0; v < _poly; ++v) + _buffers->at(v)->resize(size); + + connect_buffers(); +} + +void +PortImpl::connect_buffers(SampleCount offset) +{ + for (uint32_t v = 0; v < _poly; ++v) + PortImpl::parent_node()->set_port_buffer(v, _index, buffer(v), offset); +} + +void +PortImpl::recycle_buffers() +{ + for (uint32_t v = 0; v < _poly; ++v) + _buffers->at(v) = NULL; +} + +void +PortImpl::clear_buffers() +{ + for (uint32_t v = 0; v < _poly; ++v) + buffer(v)->clear(); +} + +void +PortImpl::broadcast_value(Context& context, bool force) +{ + Raul::Atom val; + switch (buffer_type().symbol()) { + case PortType::UNKNOWN: + break; + case PortType::AUDIO: + case PortType::CONTROL: + val = ((AudioBuffer*)buffer(0).get())->value_at(0); + break; + case PortType::EVENTS: + if (((EventBuffer*)buffer(0).get())->event_count() > 0) { + const Events::SendPortActivity ev(context.engine(), context.start(), this); + context.event_sink().write(sizeof(ev), &ev); + } + break; + case PortType::VALUE: + case PortType::MESSAGE: + Ingen::Shared::LV2Atom::to_atom(_bufs.uris(), ((ObjectBuffer*)buffer(0).get())->atom(), val); + break; + } + + if (val.is_valid() && (force || val != _last_broadcasted_value)) { + _last_broadcasted_value = val; + const Events::SendPortValue ev(context.engine(), context.start(), this, true, 0, val); + context.event_sink().write(sizeof(ev), &ev); + } +} + +void +PortImpl::set_context(Context::ID c) +{ + const Ingen::Shared::LV2URIMap& uris = _bufs.uris(); + _context = c; + switch (c) { + case Context::AUDIO: + remove_property(uris.ctx_context, uris.wildcard); + break; + case Context::MESSAGE: + set_property(uris.ctx_context, uris.ctx_MessageContext); + break; + } +} + +PortType +PortImpl::buffer_type() const +{ + // TODO: multiple types + return *_types.begin(); +} + +} // namespace Server +} // namespace Ingen diff --git a/src/server/PortImpl.hpp b/src/server/PortImpl.hpp new file mode 100644 index 00000000..b3621f13 --- /dev/null +++ b/src/server/PortImpl.hpp @@ -0,0 +1,174 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_PORTIMPL_HPP +#define INGEN_ENGINE_PORTIMPL_HPP + +#include <cstdlib> +#include <string> +#include <set> +#include "raul/Array.hpp" +#include "raul/Atom.hpp" +#include "ingen/Port.hpp" +#include "types.hpp" +#include "GraphObjectImpl.hpp" +#include "ingen/PortType.hpp" +#include "Buffer.hpp" +#include "Context.hpp" + +namespace Raul { class Maid; } + +namespace Ingen { +namespace Server { + +class NodeImpl; +class Buffer; +class BufferFactory; + +/** A port on a Node. + * + * This is a non-template abstract base class, which basically exists so + * things can pass around Port pointers and not have to worry about type, + * templates, etc. + * + * \ingroup engine + */ +class PortImpl : public GraphObjectImpl, public Port +{ +public: + ~PortImpl(); + + /** A port's parent is always a node, so static cast should be safe */ + NodeImpl* parent_node() const { return (NodeImpl*)_parent; } + + /** Set the buffers array for this port. + * + * Audio thread. Returned value must be freed by caller. + * \a buffers must be poly() long + */ + Raul::Array<BufferFactory::Ref>* set_buffers(Raul::Array<BufferFactory::Ref>* buffers); + + /** Prepare for a new (external) polyphony value. + * + * Preprocessor thread, poly is actually applied by apply_poly. + */ + virtual bool prepare_poly(BufferFactory& bufs, uint32_t poly); + + virtual void prepare_poly_buffers(BufferFactory& bufs); + + /** Apply a new polyphony value. + * + * Audio thread. + * \a poly Must be < the most recent value passed to prepare_poly. + */ + virtual bool apply_poly(Raul::Maid& maid, uint32_t poly); + + const Raul::Atom& value() const { return _value; } + void set_value(const Raul::Atom& v) { _value = v; } + + inline BufferFactory::Ref buffer(uint32_t voice) const { + return _buffers->at((_poly == 1) ? 0 : voice); + } + inline BufferFactory::Ref prepared_buffer(uint32_t voice) const { + return _prepared_buffers->at(voice); + } + + /** Called once per process cycle */ + virtual void pre_process(Context& context) = 0; + virtual void post_process(Context& context) = 0; + + /** Empty buffer contents completely (ie silence) */ + virtual void clear_buffers(); + + virtual bool get_buffers(BufferFactory& bufs, + Raul::Array<BufferFactory::Ref>* buffers, + uint32_t poly) = 0; + + void setup_buffers(BufferFactory& bufs, uint32_t poly) { + get_buffers(bufs, _buffers, poly); + } + + virtual void connect_buffers(SampleCount offset=0); + virtual void recycle_buffers(); + + virtual bool is_input() const = 0; + virtual bool is_output() const = 0; + + uint32_t index() const { return _index; } + + const PortTypes& types() const { return _types; } + + PortType buffer_type() const; + + bool supports(const Raul::URI& value_type) const; + + size_t buffer_size() const { return _buffer_size; } + + uint32_t poly() const { + return _poly; + } + uint32_t prepared_poly() const { + return (_prepared_buffers) ? _prepared_buffers->size() : 1; + } + + void set_buffer_size(Context& context, BufferFactory& bufs, size_t size); + void set_buffer_type(PortType type); + + void broadcast(bool b) { _broadcast = b; } + bool broadcast() { return _broadcast; } + + void broadcast_value(Context& context, bool force=false); + + void raise_set_by_user_flag() { _set_by_user = true; } + + Context::ID context() const { return _context; } + void set_context(Context::ID c); + + BufferFactory& bufs() const { return _bufs; } + +protected: + PortImpl(BufferFactory& bufs, + NodeImpl* node, + const Raul::Symbol& name, + uint32_t index, + uint32_t poly, + PortType type, + const Raul::Atom& value, + size_t buffer_size); + + BufferFactory& _bufs; + uint32_t _index; + uint32_t _poly; + uint32_t _buffer_size; + PortType _buffer_type; + std::set<PortType> _types; + Raul::Atom _value; + bool _broadcast; + bool _set_by_user; + Raul::Atom _last_broadcasted_value; + + Context::ID _context; + Raul::Array<BufferFactory::Ref>* _buffers; + + // Dynamic polyphony + Raul::Array<BufferFactory::Ref>* _prepared_buffers; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_PORTIMPL_HPP diff --git a/src/server/PostProcessor.cpp b/src/server/PostProcessor.cpp new file mode 100644 index 00000000..25252a16 --- /dev/null +++ b/src/server/PostProcessor.cpp @@ -0,0 +1,92 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <cassert> +#include <pthread.h> +#include "raul/log.hpp" +#include "raul/SRSWQueue.hpp" +#include "events/SendPortValue.hpp" +#include "Event.hpp" +#include "PostProcessor.hpp" +#include "Engine.hpp" +#include "Driver.hpp" +#include "ProcessContext.hpp" + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { + +PostProcessor::PostProcessor(Engine& engine, size_t queue_size) + : _engine(engine) + , _max_time(0) + , _events(queue_size) + , _event_buffer_size(sizeof(Events::SendPortValue)) // FIXME: make generic + , _event_buffer((uint8_t*)malloc(_event_buffer_size)) +{ +} + +PostProcessor::~PostProcessor() +{ + free(_event_buffer); +} + +void +PostProcessor::process() +{ + const FrameTime end_time = _max_time.get(); + + /* FIXME: The order here is a bit tricky: if the normal events are + * processed first, then a deleted port may still have a pending + * broadcast event which will segfault if executed afterwards. + * If it's the other way around, broadcasts will be sent for + * ports the client doesn't even know about yet... */ + + /* FIXME: process events from all threads if parallel */ + + /* Process audio thread generated events */ + while (true) { + Driver* driver = _engine.driver(); + if (driver && driver->context().event_sink().read(_event_buffer_size, _event_buffer)) { + if (((Event*)_event_buffer)->time() > end_time) { + warn << "Lost event with time " + << ((Event*)_event_buffer)->time() << " > " << end_time << endl; + break; + } + ((Event*)_event_buffer)->post_process(); + } else { + break; + } + } + + /* Process normal events */ + Raul::List<Event*>::Node* n = _events.head(); + while (n) { + if (n->elem()->time() > end_time) + break; + Raul::List<Event*>::Node* next = n->next(); + n->elem()->post_process(); + _events.erase(_events.begin()); + delete n->elem(); + delete n; + n = next; + } +} + +} // namespace Server +} // namespace Ingen diff --git a/src/server/PostProcessor.hpp b/src/server/PostProcessor.hpp new file mode 100644 index 00000000..a48c89f8 --- /dev/null +++ b/src/server/PostProcessor.hpp @@ -0,0 +1,68 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_POSTPROCESSOR_HPP +#define INGEN_ENGINE_POSTPROCESSOR_HPP + +#include <pthread.h> +#include "raul/SRSWQueue.hpp" +#include "raul/List.hpp" + +namespace Ingen { +namespace Server { + +class Event; +class Engine; + +/** Processor for Events after leaving the audio thread. + * + * The audio thread pushes events to this when it is done with them (which + * is realtime-safe), which signals the processing thread through a semaphore + * to handle the event and pass it on to the Maid. + * + * Update: This is all run from main_iteration now to solve scripting + * thread issues. Not sure if this is permanent/ideal or not... + * + * \ingroup engine + */ +class PostProcessor +{ +public: + PostProcessor(Engine& engine, size_t queue_size); + ~PostProcessor(); + + /** Push a list of events on to the process queue, realtime-safe, not thread-safe. */ + inline void append(Raul::List<Event*>* l) { _events.append(*l); } + + /** Post-process and delete all pending events */ + void process(); + + /** Set the latest event time that should be post-processed */ + void set_end_time(FrameTime time) { _max_time = time; } + +private: + Engine& _engine; + Raul::AtomicInt _max_time; + Raul::List<Event*> _events; + uint32_t _event_buffer_size; + uint8_t* _event_buffer; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_POSTPROCESSOR_HPP diff --git a/src/server/ProcessContext.cpp b/src/server/ProcessContext.cpp new file mode 100644 index 00000000..4e098b8b --- /dev/null +++ b/src/server/ProcessContext.cpp @@ -0,0 +1,38 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 "ProcessContext.hpp" +#include "ProcessSlave.hpp" + +namespace Ingen { +namespace Server { + +void +ProcessContext::activate(uint32_t parallelism, bool sched_rt) +{ + for (uint32_t i = 0; i < _slaves.size(); ++i) { + delete _slaves[i]; + } + _slaves.clear(); + _slaves.reserve(parallelism); + for (uint32_t i = 0; i < parallelism - 1; ++i) { + _slaves.push_back(new ProcessSlave(_engine, sched_rt)); + } +} + +} // namespace Server +} // namespace Ingen diff --git a/src/server/ProcessContext.hpp b/src/server/ProcessContext.hpp new file mode 100644 index 00000000..339b32f8 --- /dev/null +++ b/src/server/ProcessContext.hpp @@ -0,0 +1,54 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_PROCESSCONTEXT_HPP +#define INGEN_ENGINE_PROCESSCONTEXT_HPP + +#include <vector> + +#include "Context.hpp" +#include "EventSink.hpp" +#include "types.hpp" + +namespace Ingen { +namespace Server { + +class ProcessSlave; + +/** Context of a process() call (the audio context). + * \ingroup engine + */ +class ProcessContext : public Context +{ +public: + explicit ProcessContext(Engine& engine) : Context(engine, AUDIO) {} + + typedef std::vector<ProcessSlave*> Slaves; + + const Slaves& slaves() const { return _slaves; } + Slaves& slaves() { return _slaves; } + + void activate(uint32_t parallelism, bool sched_rt); + +private: + Slaves _slaves; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_PROCESSCONTEXT_HPP diff --git a/src/server/ProcessSlave.cpp b/src/server/ProcessSlave.cpp new file mode 100644 index 00000000..8d60dab3 --- /dev/null +++ b/src/server/ProcessSlave.cpp @@ -0,0 +1,73 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 "ProcessSlave.hpp" +#include "NodeImpl.hpp" +#include "CompiledPatch.hpp" + +using namespace std; + +namespace Ingen { +namespace Server { + +uint32_t ProcessSlave::_next_id = 0; + +void +ProcessSlave::_whipped() +{ + assert(_compiled_patch); + CompiledPatch* const cp = _compiled_patch; + + /* Iterate over all nodes attempting to run immediately or block then run, + * until we've been through the entire array without getting a lock, + * and thus are finished this cycle. + */ + + size_t num_finished = 0; // Number of consecutive finished nodes hit + + while (_state == STATE_RUNNING) { + + CompiledNode& n = (*cp)[_index]; + + if (n.node()->process_lock()) { + + n.node()->wait_for_input(n.n_providers()); + + n.node()->process(*_context); + + /* Signal dependants their input is ready */ + for (size_t i=0; i < n.dependants().size(); ++i) + n.dependants()[i]->signal_input_ready(); + + num_finished = 1; + } else { + ++num_finished; + } + + _index = (_index + 1) % cp->size(); + + if (num_finished >= cp->size()) + break; + } + + _index = 0; + _compiled_patch = NULL; + _state = STATE_FINISHED; +} + +} // namespace Server +} // namespace Ingen diff --git a/src/server/ProcessSlave.hpp b/src/server/ProcessSlave.hpp new file mode 100644 index 00000000..4fb4b9d5 --- /dev/null +++ b/src/server/ProcessSlave.hpp @@ -0,0 +1,104 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_PROCESSSLAVE_HPP +#define INGEN_ENGINE_PROCESSSLAVE_HPP + +#include <sstream> + +#include "raul/Array.hpp" +#include "raul/AtomicInt.hpp" +#include "raul/Slave.hpp" + +#include "Driver.hpp" +#include "Engine.hpp" +#include "ProcessContext.hpp" + +namespace Ingen { +namespace Server { + +class NodeImpl; +class CompiledPatch; + +class ProcessSlave : protected Raul::Slave { +public: + ProcessSlave(Engine& engine, bool realtime) + : _engine(engine) + , _id(_next_id++) + , _index(0) + , _state(STATE_FINISHED) + , _compiled_patch(NULL) + , _context(NULL) + { + std::stringstream ss; + ss << "Process Slave "; + ss << _id; + set_name(ss.str()); + + start(); + + if (realtime) + set_scheduling(SCHED_FIFO, 40); + } + + ~ProcessSlave() { + stop(); + } + + inline void whip(CompiledPatch* compiled_patch, + uint32_t start_index, + ProcessContext& context) + { + assert(_state == STATE_FINISHED); + _index = start_index; + _state = STATE_RUNNING; + _compiled_patch = compiled_patch; + _context = &context; + + Raul::Slave::whip(); + } + + inline void finish() { + while (_state.get() != STATE_FINISHED) + _state.compare_and_exchange(STATE_RUNNING, STATE_FINISH_SIGNALLED); + } + + inline uint32_t id() const { return _id; } + inline const ProcessContext& context() const { return _engine.driver()->context(); } + inline ProcessContext& context() { return _engine.driver()->context(); } + +private: + void _whipped(); + + static uint32_t _next_id; + + static const int STATE_RUNNING = 0; + static const int STATE_FINISH_SIGNALLED = 1; + static const int STATE_FINISHED = 2; + + Engine& _engine; + uint32_t _id; + uint32_t _index; + Raul::AtomicInt _state; + CompiledPatch* _compiled_patch; + ProcessContext* _context; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_PROCESSSLAVE_HPP diff --git a/src/server/QueuedEngineInterface.cpp b/src/server/QueuedEngineInterface.cpp new file mode 100644 index 00000000..5ef9335f --- /dev/null +++ b/src/server/QueuedEngineInterface.cpp @@ -0,0 +1,223 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <string> + +#include "raul/log.hpp" + +#include "Driver.hpp" +#include "Engine.hpp" +#include "EventSource.hpp" +#include "QueuedEngineInterface.hpp" +#include "events.hpp" + +#define LOG(s) s << "[QueuedEngineInterface] " + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { + +QueuedEngineInterface::QueuedEngineInterface(Engine& engine, size_t queue_size) + : EventSource(queue_size) + , _request(new Request(this, NULL, 0)) + , _engine(engine) + , _in_bundle(false) +{ + start(); +} + + +QueuedEngineInterface::~QueuedEngineInterface() +{ + stop(); +} + +SampleCount +QueuedEngineInterface::now() const +{ + // Exactly one cycle latency (some could run ASAP if we get lucky, but not always, and a slight + // constant latency is far better than jittery lower (average) latency + if (_engine.driver()) + return _engine.driver()->frame_time() + _engine.driver()->block_length(); + else + return 0; +} + +void +QueuedEngineInterface::set_next_response_id(int32_t id) +{ + if (_request) + _request->set_id(id); +} + +void +QueuedEngineInterface::disable_responses() +{ + _request->set_client(NULL); + _request->set_id(0); +} + +/* *** ServerInterface implementation below here *** */ + +void +QueuedEngineInterface::register_client(ClientInterface* client) +{ + push_queued(new Events::RegisterClient(_engine, _request, now(), client->uri(), client)); + if (!_request) { + _request = SharedPtr<Request>(new Request(this, client, 1)); + } else { + _request->set_id(1); + _request->set_client(client); + } +} + +void +QueuedEngineInterface::unregister_client(const URI& uri) +{ + push_queued(new Events::UnregisterClient(_engine, _request, now(), uri)); + if (_request && _request->client() && _request->client()->uri() == uri) { + _request->set_id(0); + _request->set_client(NULL); + } +} + +// Bundle commands + +void +QueuedEngineInterface::bundle_begin() +{ + _in_bundle = true; +} + +void +QueuedEngineInterface::bundle_end() +{ + _in_bundle = false; +} + +// Object commands + +void +QueuedEngineInterface::put(const URI& uri, + const Resource::Properties& properties, + const Resource::Graph ctx) +{ + push_queued(new Events::SetMetadata(_engine, _request, now(), true, ctx, uri, properties)); +} + +void +QueuedEngineInterface::delta(const URI& uri, + const Resource::Properties& remove, + const Resource::Properties& add) +{ + push_queued(new Events::SetMetadata(_engine, _request, now(), false, Resource::DEFAULT, uri, add, remove)); +} + +void +QueuedEngineInterface::move(const Path& old_path, + const Path& new_path) +{ + push_queued(new Events::Move(_engine, _request, now(), old_path, new_path)); +} + +void +QueuedEngineInterface::del(const URI& uri) +{ + if (uri == "ingen:engine") { + _request->respond_ok(); + _engine.quit(); + } else { + push_queued(new Events::Delete(_engine, _request, now(), uri)); + } +} + +void +QueuedEngineInterface::connect(const Path& src_port_path, + const Path& dst_port_path) +{ + push_queued(new Events::Connect(_engine, _request, now(), src_port_path, dst_port_path)); + +} + +void +QueuedEngineInterface::disconnect(const URI& src, + const URI& dst) +{ + if (!Path::is_path(src) && !Path::is_path(dst)) { + std::cerr << "Bad disconnect request " << src << " => " << dst << std::endl; + return; + } + + push_queued(new Events::Disconnect(_engine, _request, now(), + Path(src.str()), Path(dst.str()))); +} + +void +QueuedEngineInterface::disconnect_all(const Path& patch_path, + const Path& path) +{ + push_queued(new Events::DisconnectAll(_engine, _request, now(), patch_path, path)); +} + +void +QueuedEngineInterface::set_property(const URI& uri, + const URI& predicate, + const Atom& value) +{ + if (uri == "ingen:engine" && predicate == "ingen:enabled" + && value.type() == Atom::BOOL) { + if (value.get_bool()) { + _engine.activate(); + push_queued(new Events::Ping(_engine, _request, now())); + } else { + push_queued(new Events::Deactivate(_engine, _request, now())); + } + } else { + Resource::Properties remove; + remove.insert(make_pair(predicate, _engine.world()->uris()->wildcard)); + Resource::Properties add; + add.insert(make_pair(predicate, value)); + push_queued(new Events::SetMetadata( + _engine, _request, now(), false, Resource::DEFAULT, + uri, add, remove)); + } +} + +// Requests // + +void +QueuedEngineInterface::ping() +{ + push_queued(new Events::Ping(_engine, _request, now())); +} + +void +QueuedEngineInterface::get(const URI& uri) +{ + push_queued(new Events::Get(_engine, _request, now(), uri)); +} + +void +QueuedEngineInterface::request_property(const URI& uri, const URI& key) +{ + push_queued(new Events::RequestMetadata(_engine, _request, now(), Resource::DEFAULT, uri, key)); +} + +} // namespace Server +} // namespace Ingen diff --git a/src/server/QueuedEngineInterface.hpp b/src/server/QueuedEngineInterface.hpp new file mode 100644 index 00000000..3bd77013 --- /dev/null +++ b/src/server/QueuedEngineInterface.hpp @@ -0,0 +1,116 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_QUEUEDENGINEINTERFACE_HPP +#define INGEN_ENGINE_QUEUEDENGINEINTERFACE_HPP + +#include <inttypes.h> +#include <string> +#include <memory> +#include "raul/SharedPtr.hpp" +#include "ingen/ClientInterface.hpp" +#include "ingen/ServerInterface.hpp" +#include "ingen/Resource.hpp" +#include "EventSource.hpp" +#include "Request.hpp" +#include "types.hpp" + +namespace Ingen { +namespace Server { + +class Engine; + +/** A queued (preprocessed) event source / interface. + * + * This is the bridge between the ServerInterface presented to the client, and + * the EventSource that needs to be presented to the Driver. + * + * Responses occur through the event mechanism (which notified clients in + * event post_process methods) and are related to an event by an integer ID. + * If you do not register a request, you have no way of knowing if your calls + * are successful. + */ +class QueuedEngineInterface : public EventSource, + public ServerInterface +{ +public: + QueuedEngineInterface(Engine& engine, size_t queue_size); + virtual ~QueuedEngineInterface(); + + Raul::URI uri() const { return "http://drobilla.net/ns/ingen#internal"; } + + void set_next_response_id(int32_t id); + + // Client registration + virtual void register_client(ClientInterface* client); + virtual void unregister_client(const Raul::URI& uri); + + // Bundles + virtual void bundle_begin(); + virtual void bundle_end(); + + // CommonInterface object commands + + virtual void put(const Raul::URI& path, + const Resource::Properties& properties, + const Resource::Graph g=Resource::DEFAULT); + + virtual void delta(const Raul::URI& path, + const Resource::Properties& remove, + const Resource::Properties& add); + + virtual void move(const Raul::Path& old_path, + const Raul::Path& new_path); + + virtual void connect(const Raul::Path& src_port_path, + const Raul::Path& dst_port_path); + + virtual void disconnect(const Raul::URI& src, + const Raul::URI& dst); + + virtual void set_property(const Raul::URI& subject_path, + const Raul::URI& predicate, + const Raul::Atom& value); + + virtual void del(const Raul::URI& uri); + + // ServerInterface object commands + + virtual void disconnect_all(const Raul::Path& parent_patch_path, + const Raul::Path& path); + + // Requests + virtual void ping(); + virtual void get(const Raul::URI& uri); + virtual void request_property(const Raul::URI& object_path, + const Raul::URI& key); + +protected: + virtual void disable_responses(); + + SharedPtr<Request> _request; ///< NULL if responding disabled + Engine& _engine; + bool _in_bundle; ///< True iff a bundle is currently being received + +private: + SampleCount now() const; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_QUEUEDENGINEINTERFACE_HPP diff --git a/src/server/QueuedEvent.cpp b/src/server/QueuedEvent.cpp new file mode 100644 index 00000000..ac3c60af --- /dev/null +++ b/src/server/QueuedEvent.cpp @@ -0,0 +1,49 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 "QueuedEvent.hpp" +#include "ThreadManager.hpp" +#include "ProcessContext.hpp" + +namespace Ingen { +namespace Server { + +void +QueuedEvent::pre_process() +{ + ThreadManager::assert_thread(THREAD_PRE_PROCESS); + assert(_pre_processed == false); + _pre_processed = true; +} + +void +QueuedEvent::execute(ProcessContext& context) +{ + assert(_pre_processed); + assert(_time <= context.end()); + + // Didn't prepare in time. QueuedEvents aren't (necessarily) sample accurate + // so just run at the beginning of this cycle + if (_time <= context.start()) + _time = context.start(); + + Event::execute(context); +} + +} // namespace Server +} // namespace Ingen + diff --git a/src/server/QueuedEvent.hpp b/src/server/QueuedEvent.hpp new file mode 100644 index 00000000..20229272 --- /dev/null +++ b/src/server/QueuedEvent.hpp @@ -0,0 +1,77 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_QUEUEDEVENT_HPP +#define INGEN_ENGINE_QUEUEDEVENT_HPP + +#include "Event.hpp" + +namespace Ingen { +namespace Server { + +/** An Event with a not-time-critical preprocessing stage. + * + * These events are events that aren't able to be executed immediately by the + * Jack thread (because they allocate memory or whatever). They are pushed + * on to the QueuedEventQueue where they are preprocessed then pushed on + * to the realtime Event Queue when they are ready. + * + * Lookups for these events should go in the pre_process() method, since they are + * not time critical and shouldn't waste time in the audio thread doing + * lookups they can do beforehand. (This applies for any expensive operation that + * could be done before the execute() method). + * + * \ingroup engine + */ +class QueuedEvent : public Event +{ +public: + /** Process this event into a realtime-suitable event. */ + virtual void pre_process(); + + virtual void execute(ProcessContext& context); + + /** True iff this event blocks the prepare phase of other events. */ + bool is_blocking() { return _blocking; } + + bool is_prepared() { return _pre_processed; } + +protected: + QueuedEvent(Engine& engine, + SharedPtr<Request> request, + FrameTime time, + bool blocking=false) + : Event(engine, request, time) + , _pre_processed(false) + , _blocking(blocking) + {} + + // NULL event base (for internal events only!) + explicit QueuedEvent(Engine& engine) + : Event(engine, SharedPtr<Request>(), 0) + , _pre_processed(false) + , _blocking(false) + {} + + bool _pre_processed; + bool _blocking; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_QUEUEDEVENT_HPP diff --git a/src/server/Request.hpp b/src/server/Request.hpp new file mode 100644 index 00000000..5b91798d --- /dev/null +++ b/src/server/Request.hpp @@ -0,0 +1,81 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_REQUEST_HPP +#define INGEN_ENGINE_REQUEST_HPP + +#include <inttypes.h> +#include <string> +#include "ingen/ClientInterface.hpp" +#include "EventSource.hpp" + +namespace Ingen { +namespace Server { + +/** Record of a request (used to respond to clients). + * + * This is a glorified std::pair<ClientInterface*, int32_t> for replying + * to numbered messages from a client. + * + * For responses that involve several messages, the response will come first + * followed by the messages (eg object notifications, values, errors, etc.) + * in a bundle (or "transfer" if too large). + */ +class Request +{ +public: + Request(EventSource* source=0, + ClientInterface* client=0, + int32_t id=1) + : _source(source) + , _client(client) + , _id(id) + {} + + EventSource* source() { return _source; } + int32_t id() const { return _id; } + void set_id(int32_t id) { _id = id; } + + ClientInterface* client() const { return _client; } + void set_client(ClientInterface* client) { _client = client; } + + void unblock() { + if (_source) + _source->unblock(); + } + + void respond_ok() { + if (_client) + _client->response_ok(_id); + } + + void respond_error(const std::string& msg) { + if (_client) + _client->response_error(_id, msg); + } + +private: + EventSource* _source; + ClientInterface* _client; + int32_t _id; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_REQUEST_HPP + diff --git a/src/server/ThreadManager.hpp b/src/server/ThreadManager.hpp new file mode 100644 index 00000000..db705278 --- /dev/null +++ b/src/server/ThreadManager.hpp @@ -0,0 +1,56 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_THREADMANAGER_HPP +#define INGEN_ENGINE_THREADMANAGER_HPP + +#include <cassert> +#include "raul/Thread.hpp" + +namespace Ingen { +namespace Server { + +enum ThreadID { + THREAD_PRE_PROCESS, + THREAD_PROCESS, + THREAD_MESSAGE, +}; + +class ThreadManager { +public: + inline static bool thread_is(ThreadID id) { + return Raul::Thread::get().is_context(id); + } + + inline static void assert_thread(ThreadID id) { + assert(single_threaded || Raul::Thread::get().is_context(id)); + } + + inline static void assert_not_thread(ThreadID id) { + assert(single_threaded || !Raul::Thread::get().is_context(id)); + } + + /** Set to true during initialisation so ensure_thread doesn't fail. + * Defined in Engine.cpp + */ + static bool single_threaded; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_THREADMANAGER_HPP diff --git a/src/server/events.hpp b/src/server/events.hpp new file mode 100644 index 00000000..7ddcfe52 --- /dev/null +++ b/src/server/events.hpp @@ -0,0 +1,41 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_EVENTS_HPP +#define INGEN_ENGINE_EVENTS_HPP + +#include "ingen-config.h" + +#include "events/Connect.hpp" +#include "events/CreateNode.hpp" +#include "events/CreatePatch.hpp" +#include "events/CreatePort.hpp" +#include "events/Deactivate.hpp" +#include "events/Delete.hpp" +#include "events/Disconnect.hpp" +#include "events/DisconnectAll.hpp" +#include "events/Get.hpp" +#include "events/Move.hpp" +#include "events/Ping.hpp" +#include "events/RegisterClient.hpp" +#include "events/RequestMetadata.hpp" +#include "events/SetMetadata.hpp" +#include "events/SetPortValue.hpp" +#include "events/UnregisterClient.hpp" + +#endif // INGEN_ENGINE_EVENTS_HPP + diff --git a/src/server/events/Connect.cpp b/src/server/events/Connect.cpp new file mode 100644 index 00000000..e0f09a3d --- /dev/null +++ b/src/server/events/Connect.cpp @@ -0,0 +1,204 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <string> +#include <boost/format.hpp> +#include "raul/Maid.hpp" +#include "raul/Path.hpp" +#include "ClientBroadcaster.hpp" +#include "Connect.hpp" +#include "ConnectionImpl.hpp" +#include "DuplexPort.hpp" +#include "Engine.hpp" +#include "EngineStore.hpp" +#include "InputPort.hpp" +#include "OutputPort.hpp" +#include "PatchImpl.hpp" +#include "PortImpl.hpp" +#include "ProcessContext.hpp" +#include "Request.hpp" +#include "types.hpp" + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { +namespace Events { + +Connect::Connect(Engine& engine, SharedPtr<Request> request, SampleCount timestamp, const Path& src_port_path, const Path& dst_port_path) + : QueuedEvent(engine, request, timestamp) + , _src_port_path(src_port_path) + , _dst_port_path(dst_port_path) + , _patch(NULL) + , _src_output_port(NULL) + , _dst_input_port(NULL) + , _compiled_patch(NULL) + , _port_listnode(NULL) + , _buffers(NULL) +{ +} + +void +Connect::pre_process() +{ + PortImpl* src_port = _engine.engine_store()->find_port(_src_port_path); + PortImpl* dst_port = _engine.engine_store()->find_port(_dst_port_path); + if (!src_port || !dst_port) { + _error = PORT_NOT_FOUND; + QueuedEvent::pre_process(); + return; + } + + _dst_input_port = dynamic_cast<InputPort*>(dst_port); + _src_output_port = dynamic_cast<OutputPort*>(src_port); + if (!_dst_input_port || !_src_output_port) { + _error = DIRECTION_MISMATCH; + QueuedEvent::pre_process(); + return; + } + + NodeImpl* const src_node = src_port->parent_node(); + NodeImpl* const dst_node = dst_port->parent_node(); + if (!src_node || !dst_node) { + _error = PARENTS_NOT_FOUND; + QueuedEvent::pre_process(); + return; + } + + if (src_node->parent() != dst_node->parent() + && src_node != dst_node->parent() + && src_node->parent() != dst_node) { + _error = PARENT_PATCH_DIFFERENT; + QueuedEvent::pre_process(); + return; + } + + if (!ConnectionImpl::can_connect(_src_output_port, _dst_input_port)) { + _error = TYPE_MISMATCH; + QueuedEvent::pre_process(); + return; + } + + // Connection to a patch port from inside the patch + if (src_node->parent_patch() != dst_node->parent_patch()) { + assert(src_node->parent() == dst_node || dst_node->parent() == src_node); + if (src_node->parent() == dst_node) + _patch = dynamic_cast<PatchImpl*>(dst_node); + else + _patch = dynamic_cast<PatchImpl*>(src_node); + + // Connection from a patch input to a patch output (pass through) + } else if (src_node == dst_node && dynamic_cast<PatchImpl*>(src_node)) { + _patch = dynamic_cast<PatchImpl*>(src_node); + + // Normal connection between nodes with the same parent + } else { + _patch = src_node->parent_patch(); + } + + if (_patch->has_connection(_src_output_port, _dst_input_port)) { + _error = ALREADY_CONNECTED; + QueuedEvent::pre_process(); + return; + } + + _connection = SharedPtr<ConnectionImpl>( + new ConnectionImpl(*_engine.buffer_factory(), _src_output_port, _dst_input_port)); + + _port_listnode = new InputPort::Connections::Node(_connection); + + // Need to be careful about patch port connections here and adding a node's + // parent as a dependant/provider, or adding a patch as it's own provider... + if (src_node != dst_node && src_node->parent() == dst_node->parent()) { + dst_node->providers()->push_back(new Raul::List<NodeImpl*>::Node(src_node)); + src_node->dependants()->push_back(new Raul::List<NodeImpl*>::Node(dst_node)); + } + + _patch->add_connection(_connection); + _dst_input_port->increment_num_connections(); + + /*if ((_dst_input_port->num_connections() == 1 + && (_connection->must_mix() || _connection->must_queue())) + || _dst_input_port->num_connections() == 2) {*/ + _buffers = new Raul::Array<BufferFactory::Ref>(_dst_input_port->poly()); + _dst_input_port->get_buffers(*_engine.buffer_factory(), + _buffers, _dst_input_port->poly()); + //} + + if (_patch->enabled()) + _compiled_patch = _patch->compile(); + + QueuedEvent::pre_process(); +} + +void +Connect::execute(ProcessContext& context) +{ + QueuedEvent::execute(context); + + if (_error == NO_ERROR) { + // This must be inserted here, since they're actually used by the audio thread + _dst_input_port->add_connection(_port_listnode); + assert(_buffers); + //if (_buffers) + _engine.maid()->push(_dst_input_port->set_buffers(_buffers)); + //else + // _dst_input_port->setup_buffers(*_engine.buffer_factory(), _dst_input_port->poly()); + _dst_input_port->connect_buffers(); + _engine.maid()->push(_patch->compiled_patch()); + _patch->compiled_patch(_compiled_patch); + } +} + +void +Connect::post_process() +{ + std::ostringstream ss; + if (_error == NO_ERROR) { + _request->respond_ok(); + _engine.broadcaster()->connect(_src_port_path, _dst_port_path); + return; + } + + ss << boost::format("Unable to make connection %1% -> %2% (") + % _src_port_path.chop_scheme() % _dst_port_path.chop_scheme(); + + switch (_error) { + case PARENT_PATCH_DIFFERENT: + ss << "Ports have mismatched parents"; break; + case PORT_NOT_FOUND: + ss << "Port not found"; break; + case TYPE_MISMATCH: + ss << "Type mismatch"; break; + case DIRECTION_MISMATCH: + ss << "Direction mismatch"; break; + case ALREADY_CONNECTED: + ss << "Already connected"; break; + case PARENTS_NOT_FOUND: + ss << "Parents not found"; break; + default: + ss << "Unknown error"; + } + ss << ")"; + _request->respond_error(ss.str()); +} + +} // namespace Server +} // namespace Ingen +} // namespace Events + diff --git a/src/server/events/Connect.hpp b/src/server/events/Connect.hpp new file mode 100644 index 00000000..1cc98729 --- /dev/null +++ b/src/server/events/Connect.hpp @@ -0,0 +1,88 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_EVENTS_CONNECT_HPP +#define INGEN_EVENTS_CONNECT_HPP + +#include "raul/Path.hpp" +#include "QueuedEvent.hpp" +#include "PatchImpl.hpp" +#include "InputPort.hpp" +#include "types.hpp" + +namespace Raul { + template <typename T> class ListNode; + template <typename T> class Array; +} + +namespace Ingen { +namespace Server { + +class PatchImpl; +class NodeImpl; +class ConnectionImpl; +class PortImpl; +class InputPort; +class OutputPort; +class CompiledPatch; + +namespace Events { + +/** Make a Connection between two Ports. + * + * \ingroup engine + */ +class Connect : public QueuedEvent +{ +public: + Connect(Engine& engine, SharedPtr<Request> request, SampleCount timestamp, const Raul::Path& src_port_path, const Raul::Path& dst_port_path); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + enum ErrorType { + NO_ERROR, + PARENT_PATCH_DIFFERENT, + PORT_NOT_FOUND, + TYPE_MISMATCH, + DIRECTION_MISMATCH, + ALREADY_CONNECTED, + PARENTS_NOT_FOUND + }; + + Raul::Path _src_port_path; + Raul::Path _dst_port_path; + + PatchImpl* _patch; + OutputPort* _src_output_port; + InputPort* _dst_input_port; + + CompiledPatch* _compiled_patch; ///< New process order for Patch + + SharedPtr<ConnectionImpl> _connection; + InputPort::Connections::Node* _port_listnode; + + Raul::Array<BufferFactory::Ref>* _buffers; +}; + +} // namespace Server +} // namespace Ingen +} // namespace Events + +#endif // INGEN_EVENTS_CONNECT_HPP diff --git a/src/server/events/CreateNode.cpp b/src/server/events/CreateNode.cpp new file mode 100644 index 00000000..01d4f285 --- /dev/null +++ b/src/server/events/CreateNode.cpp @@ -0,0 +1,146 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 "raul/log.hpp" +#include "raul/Maid.hpp" +#include "raul/Path.hpp" +#include "sord/sordmm.hpp" +#include "shared/LV2URIMap.hpp" +#include "CreateNode.hpp" +#include "Request.hpp" +#include "PatchImpl.hpp" +#include "NodeImpl.hpp" +#include "PluginImpl.hpp" +#include "Engine.hpp" +#include "PatchImpl.hpp" +#include "NodeFactory.hpp" +#include "ClientBroadcaster.hpp" +#include "EngineStore.hpp" +#include "PortImpl.hpp" +#include "Driver.hpp" + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { +namespace Events { + +CreateNode::CreateNode( + Engine& engine, + SharedPtr<Request> request, + SampleCount timestamp, + const Path& path, + const URI& plugin_uri, + const Resource::Properties& properties) + : QueuedEvent(engine, request, timestamp) + , _path(path) + , _plugin_uri(plugin_uri) + , _patch(NULL) + , _plugin(NULL) + , _node(NULL) + , _compiled_patch(NULL) + , _node_already_exists(false) + , _polyphonic(false) + , _properties(properties) +{ + const Resource::Properties::const_iterator p = properties.find( + engine.world()->uris()->ingen_polyphonic); + if (p != properties.end() && p->second.type() == Raul::Atom::BOOL + && p->second.get_bool()) + _polyphonic = true; +} + +void +CreateNode::pre_process() +{ + if (_engine.engine_store()->find_object(_path) != NULL) { + _node_already_exists = true; + QueuedEvent::pre_process(); + return; + } + + _patch = _engine.engine_store()->find_patch(_path.parent()); + _plugin = _engine.node_factory()->plugin(_plugin_uri.str()); + + if (_patch && _plugin) { + + _node = _plugin->instantiate(*_engine.buffer_factory(), _path.symbol(), _polyphonic, _patch, _engine); + + if (_node != NULL) { + _node->properties().insert(_properties.begin(), _properties.end()); + _node->activate(*_engine.buffer_factory()); + + // This can be done here because the audio thread doesn't touch the + // node tree - just the process order array + _patch->add_node(new PatchImpl::Nodes::Node(_node)); + _engine.engine_store()->add(_node); + + // FIXME: not really necessary to build process order since it's not connected, + // just append to the list + if (_patch->enabled()) + _compiled_patch = _patch->compile(); + } + } + + if (!_node) + _error = 1; + + QueuedEvent::pre_process(); +} + +void +CreateNode::execute(ProcessContext& context) +{ + QueuedEvent::execute(context); + + if (_node) { + _engine.maid()->push(_patch->compiled_patch()); + _patch->compiled_patch(_compiled_patch); + } +} + +void +CreateNode::post_process() +{ + if (!_request) + return; + + string msg; + if (_node_already_exists) { + msg = string("Could not create node - ").append(_path.str());// + " already exists."; + _request->respond_error(msg); + } else if (_patch == NULL) { + msg = "Could not find patch '" + _path.parent().str() +"' to add node."; + _request->respond_error(msg); + } else if (_plugin == NULL) { + msg = "Unable to load node "; + msg += _path.str() + " (you're missing the plugin " + _plugin_uri.str() + ")"; + _request->respond_error(msg); + } else if (_node == NULL) { + msg = "Failed to instantiate plugin " + _plugin_uri.str(); + _request->respond_error(msg); + } else { + _request->respond_ok(); + _engine.broadcaster()->send_object(_node, true); // yes, send ports + } +} + +} // namespace Server +} // namespace Ingen +} // namespace Events + diff --git a/src/server/events/CreateNode.hpp b/src/server/events/CreateNode.hpp new file mode 100644 index 00000000..bbaf830b --- /dev/null +++ b/src/server/events/CreateNode.hpp @@ -0,0 +1,71 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_EVENTS_CREATENODE_HPP +#define INGEN_EVENTS_CREATENODE_HPP + +#include <string> +#include "QueuedEvent.hpp" +#include "ingen/Resource.hpp" + +namespace Ingen { +namespace Server { + +class PatchImpl; +class PluginImpl; +class NodeImpl; +class CompiledPatch; + +namespace Events { + +/** An event to load a Node and insert it into a Patch. + * + * \ingroup engine + */ +class CreateNode : public QueuedEvent +{ +public: + CreateNode( + Engine& engine, + SharedPtr<Request> request, + SampleCount timestamp, + const Raul::Path& node_path, + const Raul::URI& plugin_uri, + const Resource::Properties& properties); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + Raul::Path _path; + Raul::URI _plugin_uri; + PatchImpl* _patch; + PluginImpl* _plugin; + NodeImpl* _node; + CompiledPatch* _compiled_patch; ///< Patch's new process order + bool _node_already_exists; + bool _polyphonic; + + Resource::Properties _properties; +}; + +} // namespace Server +} // namespace Ingen +} // namespace Events + +#endif // INGEN_EVENTS_CREATENODE_HPP diff --git a/src/server/events/CreatePatch.cpp b/src/server/events/CreatePatch.cpp new file mode 100644 index 00000000..a5c75adf --- /dev/null +++ b/src/server/events/CreatePatch.cpp @@ -0,0 +1,164 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 "raul/Maid.hpp" +#include "raul/Path.hpp" +#include "shared/LV2URIMap.hpp" +#include "events/CreatePatch.hpp" +#include "Request.hpp" +#include "PatchImpl.hpp" +#include "NodeImpl.hpp" +#include "PluginImpl.hpp" +#include "Engine.hpp" +#include "ClientBroadcaster.hpp" +#include "Driver.hpp" +#include "EngineStore.hpp" + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { +namespace Events { + +CreatePatch::CreatePatch( + Engine& engine, + SharedPtr<Request> request, + SampleCount timestamp, + const Raul::Path& path, + int poly, + const Resource::Properties& properties) + : QueuedEvent(engine, request, timestamp) + , _path(path) + , _patch(NULL) + , _parent(NULL) + , _compiled_patch(NULL) + , _poly(poly) + , _properties(properties) +{ +} + +void +CreatePatch::pre_process() +{ + if (_path.is_root() || _engine.engine_store()->find_object(_path) != NULL) { + _error = OBJECT_EXISTS; + QueuedEvent::pre_process(); + return; + } + + if (_poly < 1) { + _error = INVALID_POLY; + QueuedEvent::pre_process(); + return; + } + + const Path& path = (const Path&)_path; + + _parent = _engine.engine_store()->find_patch(path.parent()); + if (_parent == NULL) { + _error = PARENT_NOT_FOUND; + QueuedEvent::pre_process(); + return; + } + + uint32_t poly = 1; + if (_parent != NULL && _poly > 1 && _poly == static_cast<int>(_parent->internal_poly())) + poly = _poly; + + const Ingen::Shared::LV2URIMap& uris = *_engine.world()->uris().get(); + + _patch = new PatchImpl(_engine, path.symbol(), poly, _parent, + _engine.driver()->sample_rate(), _poly); + _patch->properties().insert(_properties.begin(), _properties.end()); + _patch->add_property(uris.rdf_type, uris.ingen_Patch); + _patch->add_property(uris.rdf_type, + Resource::Property(uris.ingen_Node, Resource::EXTERNAL)); + + if (_parent != NULL) { + _parent->add_node(new PatchImpl::Nodes::Node(_patch)); + + if (_parent->enabled()) + _compiled_patch = _parent->compile(); + } + + _patch->activate(*_engine.buffer_factory()); + + // Insert into EngineStore + //_patch->add_to_store(_engine.engine_store()); + _engine.engine_store()->add(_patch); + + QueuedEvent::pre_process(); +} + +void +CreatePatch::execute(ProcessContext& context) +{ + QueuedEvent::execute(context); + + if (_patch) { + if (!_parent) { + assert(_path.is_root()); + assert(_patch->parent_patch() == NULL); + _engine.driver()->set_root_patch(_patch); + } else { + assert(_parent); + assert(!_path.is_root()); + _engine.maid()->push(_parent->compiled_patch()); + _parent->compiled_patch(_compiled_patch); + } + } +} + +void +CreatePatch::post_process() +{ + string msg; + if (_request) { + switch (_error) { + case NO_ERROR: + _request->respond_ok(); + // Don't send ports/nodes that have been added since prepare() + // (otherwise they would be sent twice) + _engine.broadcaster()->send_object(_patch, false); + break; + case OBJECT_EXISTS: + _request->respond_ok(); + /*string msg = "Unable to create patch: "; + msg.append(_path).append(" already exists."); + _request->respond_error(msg);*/ + break; + case PARENT_NOT_FOUND: + msg = "Unable to create patch: Parent "; + msg.append(Path(_path).parent().str()).append(" not found."); + _request->respond_error(msg); + break; + case INVALID_POLY: + msg = "Unable to create patch "; + msg.append(_path.str()).append(": ").append("Invalid polyphony requested."); + _request->respond_error(msg); + break; + default: + _request->respond_error("Unable to load patch."); + } + } +} + +} // namespace Server +} // namespace Ingen +} // namespace Events + diff --git a/src/server/events/CreatePatch.hpp b/src/server/events/CreatePatch.hpp new file mode 100644 index 00000000..e3afde5f --- /dev/null +++ b/src/server/events/CreatePatch.hpp @@ -0,0 +1,67 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_EVENTS_CREATEPATCH_HPP +#define INGEN_EVENTS_CREATEPATCH_HPP + +#include "QueuedEvent.hpp" +#include "ingen/Resource.hpp" + +namespace Ingen { +namespace Server { + +class PatchImpl; +class CompiledPatch; + +namespace Events { + +/** Creates a new Patch. + * + * \ingroup engine + */ +class CreatePatch : public QueuedEvent +{ +public: + CreatePatch( + Engine& engine, + SharedPtr<Request> request, + SampleCount timestamp, + const Raul::Path& path, + int poly, + const Resource::Properties& properties); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + enum ErrorType { NO_ERROR, OBJECT_EXISTS, PARENT_NOT_FOUND, INVALID_POLY }; + + const Raul::Path _path; + PatchImpl* _patch; + PatchImpl* _parent; + CompiledPatch* _compiled_patch; + int _poly; + + Resource::Properties _properties; +}; + +} // namespace Server +} // namespace Ingen +} // namespace Events + +#endif // INGEN_EVENTS_CREATEPATCH_HPP diff --git a/src/server/events/CreatePort.cpp b/src/server/events/CreatePort.cpp new file mode 100644 index 00000000..ba46a35d --- /dev/null +++ b/src/server/events/CreatePort.cpp @@ -0,0 +1,192 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 "raul/Array.hpp" +#include "raul/Atom.hpp" +#include "raul/List.hpp" +#include "raul/Maid.hpp" +#include "raul/Path.hpp" +#include "shared/LV2URIMap.hpp" +#include "ClientBroadcaster.hpp" +#include "ControlBindings.hpp" +#include "CreatePort.hpp" +#include "Driver.hpp" +#include "DuplexPort.hpp" +#include "Engine.hpp" +#include "EngineStore.hpp" +#include "PatchImpl.hpp" +#include "PatchImpl.hpp" +#include "PluginImpl.hpp" +#include "PortImpl.hpp" +#include "Request.hpp" + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { +namespace Events { + +CreatePort::CreatePort( + Engine& engine, + SharedPtr<Request> request, + SampleCount timestamp, + const Raul::Path& path, + const Raul::URI& type, + bool is_output, + const Resource::Properties& properties) + : QueuedEvent(engine, request, timestamp, bool(request)) + , _path(path) + , _type(type) + , _is_output(is_output) + , _data_type(type) + , _patch(NULL) + , _patch_port(NULL) + , _driver_port(NULL) + , _properties(properties) +{ + /* This is blocking because of the two different sets of Patch ports, the array used in the + * audio thread (inherited from NodeImpl), and the arrays used in the pre processor thread. + * If two add port events arrive in the same cycle and the second pre processes before the + * first executes, bad things happen (ports are lost). + * + * TODO: fix this using RCU? + */ + + if (_data_type == PortType::UNKNOWN) + _error = UNKNOWN_TYPE; +} + +void +CreatePort::pre_process() +{ + if (_error == UNKNOWN_TYPE || _engine.engine_store()->find_object(_path)) { + QueuedEvent::pre_process(); + return; + } + + _patch = _engine.engine_store()->find_patch(_path.parent()); + + const Ingen::Shared::LV2URIMap& uris = *_engine.world()->uris().get(); + + if (_patch != NULL) { + assert(_patch->path() == _path.parent()); + + size_t buffer_size = _engine.buffer_factory()->default_buffer_size(_data_type); + + const uint32_t old_num_ports = (_patch->external_ports()) + ? _patch->external_ports()->size() + : 0; + + Resource::Properties::const_iterator index_i = _properties.find(uris.lv2_index); + if (index_i == _properties.end()) { + index_i = _properties.insert(make_pair(uris.lv2_index, (int)old_num_ports)); + } else if (index_i->second.type() != Atom::INT + || index_i->second.get_int32() != static_cast<int32_t>(old_num_ports)) { + QueuedEvent::pre_process(); + _error = BAD_INDEX; + return; + } + + Resource::Properties::const_iterator poly_i = _properties.find(uris.ingen_polyphonic); + bool polyphonic = (poly_i != _properties.end() && poly_i->second.type() == Atom::BOOL + && poly_i->second.get_bool()); + + _patch_port = _patch->create_port(*_engine.buffer_factory(), _path.symbol(), _data_type, buffer_size, _is_output, polyphonic); + + _patch_port->properties().insert(_properties.begin(), _properties.end()); + + assert(index_i->second == Atom((int)_patch_port->index())); + + if (_patch_port) { + + if (_is_output) + _patch->add_output(new Raul::List<PortImpl*>::Node(_patch_port)); + else + _patch->add_input(new Raul::List<PortImpl*>::Node(_patch_port)); + + if (_patch->external_ports()) + _ports_array = new Raul::Array<PortImpl*>(old_num_ports + 1, *_patch->external_ports(), NULL); + else + _ports_array = new Raul::Array<PortImpl*>(old_num_ports + 1, NULL); + + _ports_array->at(old_num_ports) = _patch_port; + _engine.engine_store()->add(_patch_port); + + if (!_patch->parent()) + _driver_port = _engine.driver()->create_port( + dynamic_cast<DuplexPort*>(_patch_port)); + + assert(_ports_array->size() == _patch->num_ports()); + + } else { + _error = CREATION_FAILED; + } + } + QueuedEvent::pre_process(); +} + +void +CreatePort::execute(ProcessContext& context) +{ + QueuedEvent::execute(context); + + if (_patch_port) { + _engine.maid()->push(_patch->external_ports()); + _patch->external_ports(_ports_array); + _engine.control_bindings()->port_binding_changed(context, _patch_port); + } + + if (_driver_port) { + _engine.driver()->add_port(_driver_port); + } + + if (_request) + _request->unblock(); +} + +void +CreatePort::post_process() +{ + if (!_request) + return; + + string msg; + switch (_error) { + case NO_ERROR: + _request->respond_ok(); + _engine.broadcaster()->send_object(_patch_port, true); + break; + case BAD_INDEX: + msg = string("Could not create port ") + _path.str() + " (Illegal index given)"; + _request->respond_error(msg); + break; + case UNKNOWN_TYPE: + msg = string("Could not create port ") + _path.str() + " (Unknown type)"; + _request->respond_error(msg); + break; + case CREATION_FAILED: + msg = string("Could not create port ") + _path.str() + " (Creation failed)"; + _request->respond_error(msg); + break; + } +} + +} // namespace Server +} // namespace Ingen +} // namespace Events + diff --git a/src/server/events/CreatePort.hpp b/src/server/events/CreatePort.hpp new file mode 100644 index 00000000..ae44e2f1 --- /dev/null +++ b/src/server/events/CreatePort.hpp @@ -0,0 +1,81 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_EVENTS_CREATEPORT_HPP +#define INGEN_EVENTS_CREATEPORT_HPP + +#include "QueuedEvent.hpp" +#include "raul/Path.hpp" +#include "raul/Array.hpp" +#include "ingen/PortType.hpp" +#include "ingen/Resource.hpp" + +namespace Ingen { +namespace Server { + +class PatchImpl; +class PortImpl; +class DriverPort; + +namespace Events { + +/** An event to add a Port to a Patch. + * + * \ingroup engine + */ +class CreatePort : public QueuedEvent +{ +public: + CreatePort( + Engine& engine, + SharedPtr<Request> request, + SampleCount timestamp, + const Raul::Path& path, + const Raul::URI& type, + bool is_output, + const Resource::Properties& properties); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + enum ErrorType { + NO_ERROR, + UNKNOWN_TYPE, + BAD_INDEX, + CREATION_FAILED + }; + + Raul::Path _path; + Raul::URI _type; + bool _is_output; + PortType _data_type; + PatchImpl* _patch; + PortImpl* _patch_port; + Raul::Array<PortImpl*>* _ports_array; ///< New (external) ports array for Patch + DriverPort* _driver_port; ///< Driver (eg Jack) port if this is a toplevel port + bool _succeeded; + + Resource::Properties _properties; +}; + +} // namespace Server +} // namespace Ingen +} // namespace Events + +#endif // INGEN_EVENTS_CREATEPORT_HPP diff --git a/src/server/events/Deactivate.hpp b/src/server/events/Deactivate.hpp new file mode 100644 index 00000000..779ba54c --- /dev/null +++ b/src/server/events/Deactivate.hpp @@ -0,0 +1,49 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_EVENTS_DEACTIVATE_HPP +#define INGEN_EVENTS_DEACTIVATE_HPP + +#include "QueuedEvent.hpp" +#include "Engine.hpp" + +namespace Ingen { +namespace Server { +namespace Events { + +/** Deactivates the engine. + * + * \ingroup engine + */ +class Deactivate : public QueuedEvent +{ +public: + Deactivate(Engine& engine, SharedPtr<Request> request, SampleCount timestamp) + : QueuedEvent(engine, request, timestamp) + {} + + void post_process() { + _request->respond_ok(); + _engine.deactivate(); + } +}; + +} // namespace Server +} // namespace Ingen +} // namespace Events + +#endif // INGEN_EVENTS_DEACTIVATE_HPP diff --git a/src/server/events/Delete.cpp b/src/server/events/Delete.cpp new file mode 100644 index 00000000..b1dc1558 --- /dev/null +++ b/src/server/events/Delete.cpp @@ -0,0 +1,213 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 "raul/Maid.hpp" +#include "raul/Path.hpp" +#include "ClientBroadcaster.hpp" +#include "ControlBindings.hpp" +#include "Delete.hpp" +#include "DisconnectAll.hpp" +#include "Driver.hpp" +#include "Engine.hpp" +#include "EngineStore.hpp" +#include "NodeImpl.hpp" +#include "PatchImpl.hpp" +#include "PluginImpl.hpp" +#include "PortImpl.hpp" +#include "Request.hpp" + +using namespace std; + +namespace Ingen { +namespace Server { +namespace Events { + +Delete::Delete(Engine& engine, + SharedPtr<Request> request, + FrameTime time, + const Raul::URI& uri) + : QueuedEvent(engine, request, time, true) + , _uri(uri) + , _store_iterator(engine.engine_store()->end()) + , _garbage(NULL) + , _driver_port(NULL) + , _patch_node_listnode(NULL) + , _patch_port_listnode(NULL) + , _ports_array(NULL) + , _compiled_patch(NULL) + , _disconnect_event(NULL) +{ + assert(request); + assert(request->source()); + + if (Raul::Path::is_path(uri)) + _path = Raul::Path(uri.str()); +} + +Delete::~Delete() +{ + delete _disconnect_event; +} + +void +Delete::pre_process() +{ + if (_path.is_root() || _path == "path:/control_in" || _path == "path:/control_out") { + QueuedEvent::pre_process(); + return; + } + + _removed_bindings = _engine.control_bindings()->remove(_path); + + _store_iterator = _engine.engine_store()->find(_path); + + if (_store_iterator != _engine.engine_store()->end()) { + _node = PtrCast<NodeImpl>(_store_iterator->second); + + if (!_node) + _port = PtrCast<PortImpl>(_store_iterator->second); + } + + if (_store_iterator != _engine.engine_store()->end()) { + _removed_table = _engine.engine_store()->remove(_store_iterator); + } + + if (_node && !_path.is_root()) { + assert(_node->parent_patch()); + _patch_node_listnode = _node->parent_patch()->remove_node(_path.symbol()); + if (_patch_node_listnode) { + assert(_patch_node_listnode->elem() == _node.get()); + + _disconnect_event = new DisconnectAll(_engine, _node->parent_patch(), _node.get()); + _disconnect_event->pre_process(); + + if (_node->parent_patch()->enabled()) { + // FIXME: is this called multiple times? + _compiled_patch = _node->parent_patch()->compile(); +#ifndef NDEBUG + // Be sure node is removed from process order, so it can be deleted + for (size_t i=0; i < _compiled_patch->size(); ++i) { + assert(_compiled_patch->at(i).node() != _node.get()); + // FIXME: check providers/dependants too + } +#endif + } + } + } else if (_port) { + assert(_port->parent_patch()); + _patch_port_listnode = _port->parent_patch()->remove_port(_path.symbol()); + if (_patch_port_listnode) { + assert(_patch_port_listnode->elem() == _port.get()); + + _disconnect_event = new DisconnectAll(_engine, _port->parent_patch(), _port.get()); + _disconnect_event->pre_process(); + + if (_port->parent_patch()->enabled()) { + // FIXME: is this called multiple times? + _compiled_patch = _port->parent_patch()->compile(); + _ports_array = _port->parent_patch()->build_ports_array(); + assert(_ports_array->size() == _port->parent_patch()->num_ports()); + } + } + + } + + QueuedEvent::pre_process(); +} + +void +Delete::execute(ProcessContext& context) +{ + QueuedEvent::execute(context); + + PatchImpl* parent_patch = NULL; + + if (_patch_node_listnode) { + assert(_node); + + if (_disconnect_event) + _disconnect_event->execute(context); + + parent_patch = _node->parent_patch(); + + } else if (_patch_port_listnode) { + assert(_port); + + if (_disconnect_event) + _disconnect_event->execute(context); + + parent_patch = _port->parent_patch(); + + _engine.maid()->push(_port->parent_patch()->external_ports()); + _port->parent_patch()->external_ports(_ports_array); + + if ( ! _port->parent_patch()->parent()) + _garbage = _engine.driver()->remove_port(_port->path(), &_driver_port); + } + + if (parent_patch) { + _engine.maid()->push(parent_patch->compiled_patch()); + parent_patch->compiled_patch(_compiled_patch); + } + + _request->unblock(); +} + +void +Delete::post_process() +{ + _removed_bindings.reset(); + + if (!Raul::Path::is_path(_uri) + || _path.is_root() || _path == "path:/control_in" || _path == "path:/control_out") { + // XXX: Just ignore? + //_request->respond_error(_path.chop_scheme() + " can not be deleted"); + } else if (!_node && !_port) { + string msg = string("Could not find object ") + _path.chop_scheme() + " to delete"; + _request->respond_error(msg); + } else if (_patch_node_listnode) { + assert(_node); + _node->deactivate(); + _request->respond_ok(); + _engine.broadcaster()->bundle_begin(); + if (_disconnect_event) + _disconnect_event->post_process(); + _engine.broadcaster()->del(_path); + _engine.broadcaster()->bundle_end(); + _engine.maid()->push(_patch_node_listnode); + } else if (_patch_port_listnode) { + assert(_port); + _request->respond_ok(); + _engine.broadcaster()->bundle_begin(); + if (_disconnect_event) + _disconnect_event->post_process(); + _engine.broadcaster()->del(_path); + _engine.broadcaster()->bundle_end(); + _engine.maid()->push(_patch_port_listnode); + } else { + _request->respond_error("Unable to delete object " + _path.chop_scheme()); + } + + if (_driver_port) + _driver_port->destroy(); + + _engine.maid()->push(_garbage); +} + +} // namespace Server +} // namespace Ingen +} // namespace Events diff --git a/src/server/events/Delete.hpp b/src/server/events/Delete.hpp new file mode 100644 index 00000000..ba256d87 --- /dev/null +++ b/src/server/events/Delete.hpp @@ -0,0 +1,95 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_EVENTS_DELETE_HPP +#define INGEN_EVENTS_DELETE_HPP + +#include "QueuedEvent.hpp" +#include "EngineStore.hpp" +#include "PatchImpl.hpp" +#include "ControlBindings.hpp" + +namespace Raul { + template<typename T> class Array; + template<typename T> class ListNode; +} + +namespace Ingen { +namespace Server { + +class GraphObjectImpl; +class NodeImpl; +class PortImpl; +class DriverPort; +class CompiledPatch; + +namespace Events { + +class DisconnectAll; + +/** \page methods + * <h2>DELETE</h2> + * As per WebDAV (RFC4918 S9.6). + * + * Remove an object from the engine and destroy it. + * + * \li All properties of the object are lost + * \li All references to the object are lost (e.g. the parent's reference to + * this child is lost, any connections to the object are removed, etc.) + */ + +/** DELETE a graph object (see \ref methods). + * \ingroup engine + */ +class Delete : public QueuedEvent +{ +public: + Delete(Engine& engine, + SharedPtr<Request> request, + FrameTime timestamp, + const Raul::URI& uri); + + ~Delete(); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + Raul::URI _uri; + Raul::Path _path; + EngineStore::iterator _store_iterator; + SharedPtr<NodeImpl> _node; ///< Non-NULL iff a node + SharedPtr<PortImpl> _port; ///< Non-NULL iff a port + Raul::Deletable* _garbage; + DriverPort* _driver_port; + PatchImpl::Nodes::Node* _patch_node_listnode; + Raul::List<PortImpl*>::Node* _patch_port_listnode; + Raul::Array<PortImpl*>* _ports_array; ///< New (external) ports for Patch + CompiledPatch* _compiled_patch; ///< Patch's new process order + DisconnectAll* _disconnect_event; + + SharedPtr<ControlBindings::Bindings> _removed_bindings; + + SharedPtr< Raul::Table<Raul::Path, SharedPtr<GraphObject> > > _removed_table; +}; + +} // namespace Server +} // namespace Ingen +} // namespace Events + +#endif // INGEN_EVENTS_DELETE_HPP diff --git a/src/server/events/Disconnect.cpp b/src/server/events/Disconnect.cpp new file mode 100644 index 00000000..e9374648 --- /dev/null +++ b/src/server/events/Disconnect.cpp @@ -0,0 +1,269 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 "raul/log.hpp" +#include "raul/Maid.hpp" +#include "raul/Path.hpp" +#include "events/Disconnect.hpp" +#include "AudioBuffer.hpp" +#include "ClientBroadcaster.hpp" +#include "ConnectionImpl.hpp" +#include "DuplexPort.hpp" +#include "Engine.hpp" +#include "EngineStore.hpp" +#include "InputPort.hpp" +#include "OutputPort.hpp" +#include "PatchImpl.hpp" +#include "PortImpl.hpp" +#include "ProcessContext.hpp" +#include "Request.hpp" +#include "ThreadManager.hpp" + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { +namespace Events { + +Disconnect::Disconnect( + Engine& engine, + SharedPtr<Request> request, + SampleCount timestamp, + const Raul::Path& src_port_path, + const Raul::Path& dst_port_path) + : QueuedEvent(engine, request, timestamp) + , _src_port_path(src_port_path) + , _dst_port_path(dst_port_path) + , _patch(NULL) + , _src_port(NULL) + , _dst_port(NULL) + , _impl(NULL) + , _compiled_patch(NULL) +{ +} + +Disconnect::Impl::Impl(Engine& e, + PatchImpl* patch, + OutputPort* s, + InputPort* d) + : _engine(e) + , _src_output_port(s) + , _dst_input_port(d) + , _patch(patch) + , _connection(patch->remove_connection(_src_output_port, _dst_input_port)) + , _buffers(NULL) +{ + ThreadManager::assert_thread(THREAD_PRE_PROCESS); + + NodeImpl* const src_node = _src_output_port->parent_node(); + NodeImpl* const dst_node = _dst_input_port->parent_node(); + + for (Raul::List<NodeImpl*>::iterator i = dst_node->providers()->begin(); + i != dst_node->providers()->end(); ++i) { + if ((*i) == src_node) { + delete dst_node->providers()->erase(i); + break; + } + } + + for (Raul::List<NodeImpl*>::iterator i = src_node->dependants()->begin(); + i != src_node->dependants()->end(); ++i) { + if ((*i) == dst_node) { + delete src_node->dependants()->erase(i); + break; + } + } + + _dst_input_port->decrement_num_connections(); + + if (_dst_input_port->num_connections() == 0) { + _buffers = new Raul::Array<BufferFactory::Ref>(_dst_input_port->poly()); + _dst_input_port->get_buffers(*_engine.buffer_factory(), + _buffers, _dst_input_port->poly()); + + const bool is_control = _dst_input_port->is_a(PortType::CONTROL); + const float value = is_control ? _dst_input_port->value().get_float() : 0; + for (uint32_t i = 0; i < _buffers->size(); ++i) { + if (is_control) { + PtrCast<AudioBuffer>(_buffers->at(i))->set_value(value, 0, 0); + } else { + _buffers->at(i)->clear(); + } + } + } + + _connection->pending_disconnection(true); +} + +void +Disconnect::pre_process() +{ + if (_src_port_path.parent().parent() != _dst_port_path.parent().parent() + && _src_port_path.parent() != _dst_port_path.parent().parent() + && _src_port_path.parent().parent() != _dst_port_path.parent()) { + _error = PARENT_PATCH_DIFFERENT; + QueuedEvent::pre_process(); + return; + } + + _src_port = _engine.engine_store()->find_port(_src_port_path); + _dst_port = _engine.engine_store()->find_port(_dst_port_path); + + if (_src_port == NULL || _dst_port == NULL) { + _error = PORT_NOT_FOUND; + QueuedEvent::pre_process(); + return; + } + + NodeImpl* const src_node = _src_port->parent_node(); + NodeImpl* const dst_node = _dst_port->parent_node(); + + // Connection to a patch port from inside the patch + if (src_node->parent_patch() != dst_node->parent_patch()) { + + assert(src_node->parent() == dst_node || dst_node->parent() == src_node); + if (src_node->parent() == dst_node) + _patch = dynamic_cast<PatchImpl*>(dst_node); + else + _patch = dynamic_cast<PatchImpl*>(src_node); + + // Connection from a patch input to a patch output (pass through) + } else if (src_node == dst_node && dynamic_cast<PatchImpl*>(src_node)) { + _patch = dynamic_cast<PatchImpl*>(src_node); + + // Normal connection between nodes with the same parent + } else { + _patch = src_node->parent_patch(); + } + + assert(_patch); + + if (!_patch->has_connection(_src_port, _dst_port)) { + _error = NOT_CONNECTED; + QueuedEvent::pre_process(); + return; + } + + if (src_node == NULL || dst_node == NULL) { + _error = PARENTS_NOT_FOUND; + QueuedEvent::pre_process(); + return; + } + + _impl = new Impl(_engine, + _patch, + dynamic_cast<OutputPort*>(_src_port), + dynamic_cast<InputPort*>(_dst_port)); + + if (_patch->enabled()) + _compiled_patch = _patch->compile(); + + QueuedEvent::pre_process(); +} + +bool +Disconnect::Impl::execute(ProcessContext& context, bool set_dst_buffers) +{ + ThreadManager::assert_thread(THREAD_PROCESS); + + InputPort::Connections::Node* const port_connections_node + = _dst_input_port->remove_connection(context, _src_output_port); + if (!port_connections_node) { + return false; + } + + if (set_dst_buffers) { + if (_buffers) { + _engine.maid()->push(_dst_input_port->set_buffers(_buffers)); + } else { + _dst_input_port->setup_buffers(*_engine.buffer_factory(), + _dst_input_port->poly()); + } + _dst_input_port->connect_buffers(); + } else { + _dst_input_port->recycle_buffers(); + } + + assert(_connection); + assert(port_connections_node->elem() == _connection); + + _engine.maid()->push(port_connections_node); + return true; +} + +void +Disconnect::execute(ProcessContext& context) +{ + QueuedEvent::execute(context); + + if (_error == NO_ERROR) { + if (!_impl->execute(context, true)) { + _error = CONNECTION_NOT_FOUND; + return; + } + + _engine.maid()->push(_patch->compiled_patch()); + _patch->compiled_patch(_compiled_patch); + } +} + +void +Disconnect::post_process() +{ + if (_error == NO_ERROR) { + if (_request) + _request->respond_ok(); + _engine.broadcaster()->disconnect(_src_port->path(), _dst_port->path()); + } else { + string msg("Unable to disconnect "); + msg.append(_src_port_path.str() + " => " + _dst_port_path.str()); + msg.append(" ("); + switch (_error) { + case PARENT_PATCH_DIFFERENT: + msg.append("Ports exist in different patches"); + break; + case PORT_NOT_FOUND: + msg.append("Port not found"); + break; + case TYPE_MISMATCH: + msg.append("Ports have incompatible types"); + break; + case NOT_CONNECTED: + msg.append("Ports are not connected"); + break; + case PARENTS_NOT_FOUND: + msg.append("Parent node not found"); + break; + case CONNECTION_NOT_FOUND: + msg.append("Connection not found"); + break; + default: + break; + } + msg.append(")"); + if (_request) + _request->respond_error(msg); + } + + delete _impl; +} + +} // namespace Server +} // namespace Ingen +} // namespace Events + diff --git a/src/server/events/Disconnect.hpp b/src/server/events/Disconnect.hpp new file mode 100644 index 00000000..a553fe79 --- /dev/null +++ b/src/server/events/Disconnect.hpp @@ -0,0 +1,106 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_EVENTS_DISCONNECT_HPP +#define INGEN_EVENTS_DISCONNECT_HPP + +#include "raul/Path.hpp" +#include "QueuedEvent.hpp" +#include "types.hpp" +#include "PatchImpl.hpp" +#include "BufferFactory.hpp" + +namespace Raul { + template <typename T> class ListNode; + template <typename T> class Array; +} + +namespace Ingen { +namespace Server { + +class CompiledPatch; +class InputPort; +class OutputPort; +class PortImpl; + +namespace Events { + +/** Make a Connection between two Ports. + * + * \ingroup engine + */ +class Disconnect : public QueuedEvent +{ +public: + Disconnect( + Engine& engine, + SharedPtr<Request> request, + SampleCount timestamp, + const Raul::Path& src_port_path, + const Raul::Path& dst_port_path); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + + class Impl { + public: + Impl(Engine& e, + PatchImpl* patch, + OutputPort* s, + InputPort* d); + + bool execute(ProcessContext& context, bool set_dst_buffers); + + InputPort* dst_port() { return _dst_input_port; } + + private: + Engine& _engine; + OutputPort* _src_output_port; + InputPort* _dst_input_port; + PatchImpl* _patch; + SharedPtr<ConnectionImpl> _connection; + Raul::Array<BufferFactory::Ref>* _buffers; + }; + +private: + enum ErrorType { + NO_ERROR, + PARENT_PATCH_DIFFERENT, + PORT_NOT_FOUND, + TYPE_MISMATCH, + NOT_CONNECTED, + PARENTS_NOT_FOUND, + CONNECTION_NOT_FOUND + }; + + const Raul::Path _src_port_path; + const Raul::Path _dst_port_path; + + PatchImpl* _patch; + PortImpl* _src_port; + PortImpl* _dst_port; + + Impl* _impl; + CompiledPatch* _compiled_patch; +}; + +} // namespace Events +} // namespace Server +} // namespace Ingen + +#endif // INGEN_EVENTS_DISCONNECT_HPP diff --git a/src/server/events/DisconnectAll.cpp b/src/server/events/DisconnectAll.cpp new file mode 100644 index 00000000..dd694810 --- /dev/null +++ b/src/server/events/DisconnectAll.cpp @@ -0,0 +1,189 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <boost/format.hpp> + +#include "raul/Array.hpp" +#include "raul/Maid.hpp" +#include "raul/Path.hpp" + +#include "ClientBroadcaster.hpp" +#include "ConnectionImpl.hpp" +#include "Engine.hpp" +#include "EngineStore.hpp" +#include "InputPort.hpp" +#include "NodeImpl.hpp" +#include "OutputPort.hpp" +#include "PatchImpl.hpp" +#include "PortImpl.hpp" +#include "Request.hpp" +#include "events/Disconnect.hpp" +#include "events/DisconnectAll.hpp" +#include "util.hpp" + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { +namespace Events { + +DisconnectAll::DisconnectAll(Engine& engine, SharedPtr<Request> request, SampleCount timestamp, const Path& parent_path, const Path& node_path) + : QueuedEvent(engine, request, timestamp) + , _parent_path(parent_path) + , _path(node_path) + , _parent(NULL) + , _node(NULL) + , _port(NULL) + , _compiled_patch(NULL) + , _deleting(false) +{ +} + +/** Internal version for use by other events. + */ +DisconnectAll::DisconnectAll(Engine& engine, PatchImpl* parent, GraphObjectImpl* object) + : QueuedEvent(engine) + , _parent_path(parent->path()) + , _path(object->path()) + , _parent(parent) + , _node(dynamic_cast<NodeImpl*>(object)) + , _port(dynamic_cast<PortImpl*>(object)) + , _compiled_patch(NULL) + , _deleting(true) +{ +} + +DisconnectAll::~DisconnectAll() +{ + for (Impls::iterator i = _impls.begin(); i != _impls.end(); ++i) + delete (*i); +} + +void +DisconnectAll::maybe_remove_connection(ConnectionImpl* c) +{ + if (c->pending_disconnection()) + return; + + OutputPort* src = dynamic_cast<OutputPort*>(c->src_port()); + InputPort* dst = dynamic_cast<InputPort*>(c->dst_port()); + + if (_node) { + if (src->parent_node() == _node || dst->parent_node() == _node) { + _impls.push_back(new Disconnect::Impl(_engine, _parent, src, dst)); + } + } else { + assert(_port); + if (src == _port || dst == _port) { + _impls.push_back(new Disconnect::Impl(_engine, _parent, src, dst)); + } + } +} + +void +DisconnectAll::pre_process() +{ + if (!_deleting) { + _parent = _engine.engine_store()->find_patch(_parent_path); + + if (_parent == NULL) { + _error = PARENT_NOT_FOUND; + QueuedEvent::pre_process(); + return; + } + + GraphObjectImpl* object = _engine.engine_store()->find_object(_path); + + if (object == NULL) { + _error = OBJECT_NOT_FOUND; + QueuedEvent::pre_process(); + return; + } + + if (object->parent_patch() != _parent && object->parent()->parent_patch() != _parent) { + _error = INVALID_PARENT_PATH; + QueuedEvent::pre_process(); + return; + } + + // Only one of these will succeed + _node = dynamic_cast<NodeImpl*>(object); + _port = dynamic_cast<PortImpl*>(object); + + assert((_node || _port) && !(_node && _port)); + } + + for (Patch::Connections::const_iterator i = _parent->connections().begin(); + i != _parent->connections().end(); ++i) { + maybe_remove_connection((ConnectionImpl*)i->second.get()); + } + + if (!_deleting && _parent->enabled()) + _compiled_patch = _parent->compile(); + + QueuedEvent::pre_process(); +} + +void +DisconnectAll::execute(ProcessContext& context) +{ + QueuedEvent::execute(context); + + if (_error == NO_ERROR) { + for (Impls::iterator i = _impls.begin(); i != _impls.end(); ++i) { + (*i)->execute(context, + !_deleting || ((*i)->dst_port()->parent_node() != _node)); + } + } + + _engine.maid()->push(_parent->compiled_patch()); + _parent->compiled_patch(_compiled_patch); +} + +void +DisconnectAll::post_process() +{ + if (_error == NO_ERROR) { + if (_request) + _request->respond_ok(); + _engine.broadcaster()->disconnect_all(_parent_path, _path); + } else { + if (_request) { + boost::format fmt("Unable to disconnect %1% (%2%)"); + fmt % _path; + switch (_error) { + case INVALID_PARENT_PATH: + fmt % string("Invalid parent path: ").append(_parent_path.str()); + break; + case PARENT_NOT_FOUND: + fmt % string("Unable to find parent: ").append(_parent_path.str()); + break; + case OBJECT_NOT_FOUND: + fmt % string("Unable to find object"); + default: + break; + } + _request->respond_error(fmt.str()); + } + } +} + +} // namespace Server +} // namespace Ingen +} // namespace Events + diff --git a/src/server/events/DisconnectAll.hpp b/src/server/events/DisconnectAll.hpp new file mode 100644 index 00000000..d53d1114 --- /dev/null +++ b/src/server/events/DisconnectAll.hpp @@ -0,0 +1,93 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_EVENTS_DISCONNECTALL_HPP +#define INGEN_EVENTS_DISCONNECTALL_HPP + +#include <list> + +#include "raul/Path.hpp" + +#include "Disconnect.hpp" +#include "QueuedEvent.hpp" + +namespace Ingen { +namespace Server { + +class CompiledPatch; +class NodeImpl; +class PatchImpl; +class PortImpl; + +namespace Events { + +class Disconnect; + +/** An event to disconnect all connections to a Node. + * + * \ingroup engine + */ +class DisconnectAll : public QueuedEvent +{ +public: + DisconnectAll( + Engine& engine, + SharedPtr<Request> request, + SampleCount timestamp, + const Raul::Path& parent, + const Raul::Path& object); + + DisconnectAll( + Engine& engine, + PatchImpl* parent, + GraphObjectImpl* object); + + ~DisconnectAll(); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + enum ErrorType { + NO_ERROR, + INVALID_PARENT_PATH, + PARENT_NOT_FOUND, + OBJECT_NOT_FOUND, + }; + + void maybe_remove_connection(ConnectionImpl* c); + + Raul::Path _parent_path; + Raul::Path _path; + PatchImpl* _parent; + NodeImpl* _node; + PortImpl* _port; + + typedef std::list<Disconnect::Impl*> Impls; + Impls _impls; + + CompiledPatch* _compiled_patch; ///< New process order for Patch + + bool _deleting; +}; + +} // namespace Server +} // namespace Ingen +} // namespace Events + +#endif // INGEN_EVENTS_DISCONNECTALL_HPP diff --git a/src/server/events/Get.cpp b/src/server/events/Get.cpp new file mode 100644 index 00000000..058f0b63 --- /dev/null +++ b/src/server/events/Get.cpp @@ -0,0 +1,84 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 "Get.hpp" +#include "ingen/ClientInterface.hpp" +#include "Request.hpp" +#include "Engine.hpp" +#include "EngineStore.hpp" +#include "ClientBroadcaster.hpp" +#include "PatchImpl.hpp" +#include "NodeImpl.hpp" +#include "PortImpl.hpp" +#include "ObjectSender.hpp" +#include "ProcessContext.hpp" + +using namespace Raul; + +namespace Ingen { +namespace Server { +namespace Events { + +Get::Get( + Engine& engine, + SharedPtr<Request> request, + SampleCount timestamp, + const URI& uri) + : QueuedEvent(engine, request, timestamp) + , _uri(uri) + , _object(NULL) + , _plugin(NULL) +{ +} + +void +Get::pre_process() +{ + if (_uri == "ingen:plugins") { + _plugins = _engine.node_factory()->plugins(); + } else if (Path::is_valid(_uri.str())) { + _object = _engine.engine_store()->find_object(Path(_uri.str())); + } else { + _plugin = _engine.node_factory()->plugin(_uri); + } + + QueuedEvent::pre_process(); +} + +void +Get::post_process() +{ + if (_uri == "ingen:plugins") { + _request->respond_ok(); + _engine.broadcaster()->send_plugins_to(_request->client(), _plugins); + } else if (!_object && !_plugin) { + _request->respond_error("Unable to find object requested."); + } else if (_request->client()) { + _request->respond_ok(); + if (_object) + ObjectSender::send_object(_request->client(), _object, true); + else if (_plugin) + _request->client()->put(_uri, _plugin->properties()); + } else { + _request->respond_error("Unable to find client to send object."); + } +} + +} // namespace Server +} // namespace Ingen +} // namespace Events + diff --git a/src/server/events/Get.hpp b/src/server/events/Get.hpp new file mode 100644 index 00000000..ed68e3c0 --- /dev/null +++ b/src/server/events/Get.hpp @@ -0,0 +1,60 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_EVENTS_GET_HPP +#define INGEN_EVENTS_GET_HPP + +#include "QueuedEvent.hpp" +#include "NodeFactory.hpp" +#include "types.hpp" + +namespace Ingen { +namespace Server { + +class GraphObjectImpl; +class PluginImpl; + +namespace Events { + +/** A request from a client to send an object. + * + * \ingroup engine + */ +class Get : public QueuedEvent +{ +public: + Get( + Engine& engine, + SharedPtr<Request> request, + SampleCount timestamp, + const Raul::URI& uri); + + void pre_process(); + void post_process(); + +private: + const Raul::URI _uri; + GraphObjectImpl* _object; + const PluginImpl* _plugin; + NodeFactory::Plugins _plugins; +}; + +} // namespace Server +} // namespace Ingen +} // namespace Events + +#endif // INGEN_EVENTS_GET_HPP diff --git a/src/server/events/Move.cpp b/src/server/events/Move.cpp new file mode 100644 index 00000000..2e006b8c --- /dev/null +++ b/src/server/events/Move.cpp @@ -0,0 +1,130 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 "raul/Path.hpp" +#include "events/Move.hpp" +#include "ClientBroadcaster.hpp" +#include "Engine.hpp" +#include "NodeImpl.hpp" +#include "EngineStore.hpp" +#include "PatchImpl.hpp" +#include "Request.hpp" +#include "Driver.hpp" + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { +namespace Events { + +Move::Move(Engine& engine, SharedPtr<Request> request, SampleCount timestamp, const Path& path, const Path& new_path) + : QueuedEvent(engine, request, timestamp) + , _old_path(path) + , _new_path(new_path) + , _parent_patch(NULL) + , _store_iterator(engine.engine_store()->end()) +{ +} + +Move::~Move() +{ +} + +void +Move::pre_process() +{ + if (!_old_path.parent().is_parent_of(_new_path)) { + _error = PARENT_DIFFERS; + QueuedEvent::pre_process(); + return; + } + _store_iterator = _engine.engine_store()->find(_old_path); + if (_store_iterator == _engine.engine_store()->end()) { + _error = OBJECT_NOT_FOUND; + QueuedEvent::pre_process(); + return; + } + + if (_engine.engine_store()->find_object(_new_path)) { + _error = OBJECT_EXISTS; + QueuedEvent::pre_process(); + return; + } + + SharedPtr< Table<Path, SharedPtr<GraphObject> > > removed + = _engine.engine_store()->remove(_store_iterator); + + assert(removed->size() > 0); + + for (Table<Path, SharedPtr<GraphObject> >::iterator i = removed->begin(); i != removed->end(); ++i) { + const Path& child_old_path = i->first; + assert(Path::descendant_comparator(_old_path, child_old_path)); + + Path child_new_path; + if (child_old_path == _old_path) + child_new_path = _new_path; + else + child_new_path = Path(_new_path).base() + child_old_path.substr(_old_path.length()+1); + + PtrCast<GraphObjectImpl>(i->second)->set_path(child_new_path); + i->first = child_new_path; + } + + _engine.engine_store()->add(*removed.get()); + + QueuedEvent::pre_process(); +} + +void +Move::execute(ProcessContext& context) +{ + QueuedEvent::execute(context); + + SharedPtr<PortImpl> port = PtrCast<PortImpl>(_store_iterator->second); + if (port && port->parent()->parent() == NULL) { + DriverPort* driver_port = _engine.driver()->driver_port(_new_path); + if (driver_port) + driver_port->move(_new_path); + } +} + +void +Move::post_process() +{ + string msg = "Unable to rename object - "; + + if (_error == NO_ERROR) { + _request->respond_ok(); + _engine.broadcaster()->move(_old_path, _new_path); + } else { + if (_error == OBJECT_EXISTS) + msg.append("Object already exists at ").append(_new_path.str()); + else if (_error == OBJECT_NOT_FOUND) + msg.append("Could not find object ").append(_old_path.str()); + else if (_error == OBJECT_NOT_RENAMABLE) + msg.append(_old_path.str()).append(" is not renamable"); + else if (_error == PARENT_DIFFERS) + msg.append(_new_path.str()).append(" is a child of a different patch"); + + _request->respond_error(msg); + } +} + +} // namespace Server +} // namespace Ingen +} // namespace Events diff --git a/src/server/events/Move.hpp b/src/server/events/Move.hpp new file mode 100644 index 00000000..4286f583 --- /dev/null +++ b/src/server/events/Move.hpp @@ -0,0 +1,79 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_EVENTS_MOVE_HPP +#define INGEN_EVENTS_MOVE_HPP + +#include "raul/Path.hpp" +#include "QueuedEvent.hpp" +#include "EngineStore.hpp" + +namespace Ingen { +namespace Server { + +class PatchImpl; + +namespace Events { + +/** \page methods + * <h2>MOVE</h2> + * As per WebDAV (RFC4918 S9.9). + * + * Move an object from its current location and insert it at a new location + * in a single operation. + * + * MOVE to a path with a different parent is currently not supported. + */ + +/** MOVE a graph object to a new path (see \ref methods). + * \ingroup engine + */ +class Move : public QueuedEvent +{ +public: + Move( + Engine& engine, + SharedPtr<Request> request, + SampleCount timestamp, + const Raul::Path& old_path, + const Raul::Path& new_path); + ~Move(); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + enum ErrorType { + NO_ERROR, + OBJECT_NOT_FOUND, + OBJECT_EXISTS, + OBJECT_NOT_RENAMABLE, + PARENT_DIFFERS + }; + + Raul::Path _old_path; + Raul::Path _new_path; + PatchImpl* _parent_patch; + EngineStore::iterator _store_iterator; +}; + +} // namespace Server +} // namespace Ingen +} // namespace Events + +#endif // INGEN_EVENTS_MOVE_HPP diff --git a/src/server/events/Ping.hpp b/src/server/events/Ping.hpp new file mode 100644 index 00000000..2353e496 --- /dev/null +++ b/src/server/events/Ping.hpp @@ -0,0 +1,51 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_EVENTS_PING_HPP +#define INGEN_EVENTS_PING_HPP + +#include "QueuedEvent.hpp" +#include "types.hpp" +#include "Request.hpp" + +namespace Ingen { +namespace Server { + +class PortImpl; + +namespace Events { + +/** A ping that travels through the pre-processed event queue before responding + * (useful for the order guarantee). + * + * \ingroup engine + */ +class Ping : public QueuedEvent +{ +public: + Ping(Engine& engine, SharedPtr<Request> request, SampleCount timestamp) + : QueuedEvent(engine, request, timestamp) + {} + + void post_process() { _request->respond_ok(); } +}; + +} // namespace Server +} // namespace Ingen +} // namespace Events + +#endif // INGEN_EVENTS_PING_HPP diff --git a/src/server/events/RegisterClient.cpp b/src/server/events/RegisterClient.cpp new file mode 100644 index 00000000..71ec26bc --- /dev/null +++ b/src/server/events/RegisterClient.cpp @@ -0,0 +1,57 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 "Request.hpp" +#include "events/RegisterClient.hpp" +#include "Engine.hpp" +#include "ClientBroadcaster.hpp" + +using namespace Raul; + +namespace Ingen { +namespace Server { +namespace Events { + +RegisterClient::RegisterClient(Engine& engine, + SharedPtr<Request> request, + SampleCount timestamp, + const URI& uri, + ClientInterface* client) + : QueuedEvent(engine, request, timestamp) + , _uri(uri) + , _client(client) +{ +} + +void +RegisterClient::pre_process() +{ + _engine.broadcaster()->register_client(_uri, _client); + + QueuedEvent::pre_process(); +} + +void +RegisterClient::post_process() +{ + _request->respond_ok(); +} + +} // namespace Server +} // namespace Ingen +} // namespace Events + diff --git a/src/server/events/RegisterClient.hpp b/src/server/events/RegisterClient.hpp new file mode 100644 index 00000000..ec2d0809 --- /dev/null +++ b/src/server/events/RegisterClient.hpp @@ -0,0 +1,54 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_EVENTS_REGISTERCLIENT_HPP +#define INGEN_EVENTS_REGISTERCLIENT_HPP + +#include "raul/URI.hpp" +#include "ingen/ClientInterface.hpp" +#include "QueuedEvent.hpp" + +namespace Ingen { +namespace Server { +namespace Events { + +/** Registers a new client with the OSC system, so it can receive updates. + * + * \ingroup engine + */ +class RegisterClient : public QueuedEvent +{ +public: + RegisterClient(Engine& engine, + SharedPtr<Request> request, + SampleCount timestamp, + const Raul::URI& uri, + ClientInterface* client); + + void pre_process(); + void post_process(); + +private: + Raul::URI _uri; + ClientInterface* _client; +}; + +} // namespace Server +} // namespace Ingen +} // namespace Events + +#endif // INGEN_EVENTS_REGISTERCLIENT_HPP diff --git a/src/server/events/RequestMetadata.cpp b/src/server/events/RequestMetadata.cpp new file mode 100644 index 00000000..156fb51d --- /dev/null +++ b/src/server/events/RequestMetadata.cpp @@ -0,0 +1,137 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 "raul/IntrusivePtr.hpp" +#include "ingen/ClientInterface.hpp" +#include "events/RequestMetadata.hpp" +#include "shared/LV2Atom.hpp" +#include "shared/LV2URIMap.hpp" +#include "AudioBuffer.hpp" +#include "ClientBroadcaster.hpp" +#include "Engine.hpp" +#include "EngineStore.hpp" +#include "GraphObjectImpl.hpp" +#include "ObjectBuffer.hpp" +#include "PluginImpl.hpp" +#include "PortImpl.hpp" +#include "ProcessContext.hpp" +#include "Request.hpp" + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { +namespace Events { + +RequestMetadata::RequestMetadata(Engine& engine, + SharedPtr<Request> request, + SampleCount timestamp, + Resource::Graph ctx, + const URI& subject, + const URI& key) + : QueuedEvent(engine, request, timestamp) + , _special_type(NONE) + , _uri(subject) + , _key(key) + , _resource(0) + , _context(ctx) +{ +} + +void +RequestMetadata::pre_process() +{ + const bool is_object = Path::is_path(_uri); + if (_request->client()) { + if (is_object) + _resource = _engine.engine_store()->find_object(Path(_uri.str())); + else + _resource = _engine.node_factory()->plugin(_uri); + + if (!_resource) { + QueuedEvent::pre_process(); + return; + } + } + + GraphObjectImpl* obj = dynamic_cast<GraphObjectImpl*>(_resource); + if (obj) { + if (_key == _engine.world()->uris()->ingen_value) + _special_type = PORT_VALUE; + else + _value = obj->get_property(_key); + } else { + _value = _resource->get_property(_key); + } + + QueuedEvent::pre_process(); +} + +void +RequestMetadata::execute(ProcessContext& context) +{ + QueuedEvent::execute(context); + if (_special_type == PORT_VALUE) { + PortImpl* port = dynamic_cast<PortImpl*>(_resource); + if (port) { + IntrusivePtr<AudioBuffer> abuf = PtrCast<AudioBuffer>(port->buffer(0)); + if (abuf) { + _value = abuf->value_at(0); + } else { + IntrusivePtr<ObjectBuffer> obuf = PtrCast<ObjectBuffer>(port->buffer(0)); + if (obuf) { + Ingen::Shared::LV2Atom::to_atom(*_engine.world()->uris().get(), + obuf->atom(), + _value); + } + } + } else { + _resource = 0; + } + } +} + +void +RequestMetadata::post_process() +{ + if (_request->client()) { + if (_special_type == PORT_VALUE) { + if (_resource) { + _request->respond_ok(); + _request->client()->set_property(_uri.str(), + _engine.world()->uris()->ingen_value, _value); + } else { + const string msg = "Get value for non-port " + _uri.str(); + _request->respond_error(msg); + } + } else if (!_resource) { + const string msg = "Unable to find subject " + _uri.str(); + _request->respond_error(msg); + } else { + _request->respond_ok(); + _request->client()->set_property(_uri, _key, _value); + } + } else { + _request->respond_error("Unknown client"); + } +} + +} // namespace Server +} // namespace Ingen +} // namespace Events + diff --git a/src/server/events/RequestMetadata.hpp b/src/server/events/RequestMetadata.hpp new file mode 100644 index 00000000..3f8311ef --- /dev/null +++ b/src/server/events/RequestMetadata.hpp @@ -0,0 +1,79 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_EVENTS_REQUESTMETADATA_HPP +#define INGEN_EVENTS_REQUESTMETADATA_HPP + +#include "raul/Atom.hpp" +#include "raul/URI.hpp" + +#include "QueuedEvent.hpp" + +namespace Ingen { + +namespace Shared { class ResourceImpl; } + +namespace Server { + +class GraphObjectImpl; + +namespace Events { + +/** \page methods + * <h2>GET</h2> + * As per HTTP (RFC2616 S9.3). + * + * Get the description of a graph object. + */ + +/** GET an object (see \ref methods). + * + * \ingroup engine + */ +class RequestMetadata : public QueuedEvent +{ +public: + RequestMetadata(Engine& engine, + SharedPtr<Request> request, + SampleCount timestamp, + Resource::Graph context, + const Raul::URI& subject, + const Raul::URI& key); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + enum ErrorType { NO_ERROR, NOT_FOUND }; + enum { + NONE, + PORT_VALUE + } _special_type; + + Raul::URI _uri; + Raul::URI _key; + Raul::Atom _value; + Ingen::Shared::ResourceImpl* _resource; + Resource::Graph _context; +}; + +} // namespace Server +} // namespace Ingen +} // namespace Events + +#endif // INGEN_EVENTS_REQUESTMETADATA_HPP diff --git a/src/server/events/SendBinding.cpp b/src/server/events/SendBinding.cpp new file mode 100644 index 00000000..ebfa21dd --- /dev/null +++ b/src/server/events/SendBinding.cpp @@ -0,0 +1,55 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <sstream> +#include "events/SendBinding.hpp" +#include "shared/LV2URIMap.hpp" +#include "Engine.hpp" +#include "PortImpl.hpp" +#include "ClientBroadcaster.hpp" + +using namespace std; + +namespace Ingen { +namespace Server { +namespace Events { + +void +SendBinding::post_process() +{ + const Ingen::Shared::LV2URIMap& uris = *_engine.world()->uris().get(); + Raul::Atom::DictValue dict; + if (_type == ControlBindings::MIDI_CC) { + dict[uris.rdf_type] = uris.midi_Controller; + dict[uris.midi_controllerNumber] = _num; + } else if (_type == ControlBindings::MIDI_BENDER) { + dict[uris.rdf_type] = uris.midi_Bender; + } else if (_type == ControlBindings::MIDI_CHANNEL_PRESSURE) { + dict[uris.rdf_type] = uris.midi_ChannelPressure; + } else if (_type == ControlBindings::MIDI_NOTE) { + dict[uris.rdf_type] = uris.midi_Note; + dict[uris.midi_noteNumber] = _num; + } + // TODO: other event types + _port->set_property(uris.ingen_controlBinding, dict); // FIXME: thread unsafe + _engine.broadcaster()->set_property(_port->path(), uris.ingen_controlBinding, dict); +} + +} // namespace Server +} // namespace Ingen +} // namespace Events + diff --git a/src/server/events/SendBinding.hpp b/src/server/events/SendBinding.hpp new file mode 100644 index 00000000..f3727ece --- /dev/null +++ b/src/server/events/SendBinding.hpp @@ -0,0 +1,86 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_EVENTS_SENDBINDING_HPP +#define INGEN_EVENTS_SENDBINDING_HPP + +#include "server/Event.hpp" +#include "server/ControlBindings.hpp" +#include "server/types.hpp" + +namespace Ingen { +namespace Server { + +class PortImpl; + +namespace Events { + +/** A special event used internally to send control bindings from the audio thread. + * + * See SendPortValue documentation for details. + * + * \ingroup engine + */ +class SendBinding : public Event +{ +public: + inline SendBinding( + Engine& engine, + SampleCount timestamp, + PortImpl* port, + ControlBindings::Type type, + int16_t num) + : Event(engine, SharedPtr<Request>(), timestamp) + , _port(port) + , _type(type) + , _num(num) + { + assert(_port); + switch (type) { + case ControlBindings::MIDI_CC: + assert(num >= 0 && num < 128); + break; + case ControlBindings::MIDI_RPN: + assert(num >= 0 && num < 16384); + break; + case ControlBindings::MIDI_NRPN: + assert(num >= 0 && num < 16384); + default: + break; + } + } + + inline SendBinding& operator=(const SendBinding& ev) { + _port = ev._port; + _type = ev._type; + _num = ev._num; + return *this; + } + + void post_process(); + +private: + PortImpl* _port; + ControlBindings::Type _type; + int16_t _num; +}; + +} // namespace Server +} // namespace Ingen +} // namespace Events + +#endif // INGEN_EVENTS_SENDBINDING_HPP diff --git a/src/server/events/SendPortActivity.cpp b/src/server/events/SendPortActivity.cpp new file mode 100644 index 00000000..8ff960e2 --- /dev/null +++ b/src/server/events/SendPortActivity.cpp @@ -0,0 +1,36 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 "events/SendPortActivity.hpp" +#include "Engine.hpp" +#include "PortImpl.hpp" +#include "ClientBroadcaster.hpp" + +namespace Ingen { +namespace Server { +namespace Events { + +void +SendPortActivity::post_process() +{ + _engine.broadcaster()->activity(_port->path()); +} + +} // namespace Server +} // namespace Ingen +} // namespace Events + diff --git a/src/server/events/SendPortActivity.hpp b/src/server/events/SendPortActivity.hpp new file mode 100644 index 00000000..dd74a68a --- /dev/null +++ b/src/server/events/SendPortActivity.hpp @@ -0,0 +1,69 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_EVENTS_SENDPORTACTIVITY_HPP +#define INGEN_EVENTS_SENDPORTACTIVITY_HPP + +#include "Event.hpp" +#include "types.hpp" + +namespace Ingen { +namespace Server { + +class PortImpl; + +namespace Events { + +/** A special event used internally to send port activity notification (e.g. + * MIDI event activity) from the audio thread. + * + * This is created in the audio thread (directly in a ringbuffer, not new'd) + * for processing in the post processing thread (unlike normal events which + * are created in the pre-processor thread then run through the audio + * thread). This event's job is done entirely in post_process. + * + * This only really makes sense for message ports. + * + * \ingroup engine + */ +class SendPortActivity : public Event +{ +public: + inline SendPortActivity(Engine& engine, + SampleCount timestamp, + PortImpl* port) + : Event(engine, SharedPtr<Request>(), timestamp) + , _port(port) + { + } + + inline SendPortActivity& operator=(const SendPortActivity& ev) { + _port = ev._port; + return *this; + } + + void post_process(); + +private: + PortImpl* _port; +}; + +} // namespace Server +} // namespace Ingen +} // namespace Events + +#endif // INGEN_EVENTS_SENDPORTACTIVITY_HPP diff --git a/src/server/events/SendPortValue.cpp b/src/server/events/SendPortValue.cpp new file mode 100644 index 00000000..1364b692 --- /dev/null +++ b/src/server/events/SendPortValue.cpp @@ -0,0 +1,42 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <sstream> +#include "events/SendPortValue.hpp" +#include "shared/LV2URIMap.hpp" +#include "Engine.hpp" +#include "PortImpl.hpp" +#include "ClientBroadcaster.hpp" + +using namespace std; + +namespace Ingen { +namespace Server { +namespace Events { + +void +SendPortValue::post_process() +{ + _engine.broadcaster()->set_property( + _port->path(), + _engine.world()->uris()->ingen_value, _value); +} + +} // namespace Server +} // namespace Ingen +} // namespace Events + diff --git a/src/server/events/SendPortValue.hpp b/src/server/events/SendPortValue.hpp new file mode 100644 index 00000000..4465ef00 --- /dev/null +++ b/src/server/events/SendPortValue.hpp @@ -0,0 +1,82 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_EVENTS_SENDPORTVALUE_HPP +#define INGEN_EVENTS_SENDPORTVALUE_HPP + +#include "raul/Atom.hpp" +#include "server/Event.hpp" +#include "server/types.hpp" + +namespace Ingen { +namespace Server { + +class PortImpl; + +namespace Events { + +/** A special event used internally to send port values from the audio thread. + * + * This is created in the audio thread (using in-place new on a preallocated + * buffer) for processing in the post processing thread (unlike normal events + * which are created in the pre-processor thread then run through the audio + * thread). This event's job is done entirely in post_process. + * + * This only works for control ports right now. + * + * \ingroup engine + */ +class SendPortValue : public Event +{ +public: + inline SendPortValue( + Engine& engine, + SampleCount timestamp, + PortImpl* port, + bool omni, + uint32_t voice_num, + const Raul::Atom& value) + : Event(engine, SharedPtr<Request>(), timestamp) + , _port(port) + , _omni(omni) + , _voice_num(voice_num) + , _value(value) + { + } + + inline SendPortValue& operator=(const SendPortValue& ev) { + _port = ev._port; + _omni = ev._omni; + _voice_num = ev._voice_num; + _value = ev._value; + return *this; + } + + void post_process(); + +private: + PortImpl* _port; + bool _omni; + uint32_t _voice_num; + Raul::Atom _value; +}; + +} // namespace Server +} // namespace Ingen +} // namespace Events + +#endif // INGEN_EVENTS_SENDPORTVALUE_HPP diff --git a/src/server/events/SetMetadata.cpp b/src/server/events/SetMetadata.cpp new file mode 100644 index 00000000..1812ad91 --- /dev/null +++ b/src/server/events/SetMetadata.cpp @@ -0,0 +1,380 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <string> +#include <boost/format.hpp> +#include "raul/log.hpp" +#include "raul/Maid.hpp" +#include "ingen/PortType.hpp" +#include "shared/LV2URIMap.hpp" +#include "ClientBroadcaster.hpp" +#include "ControlBindings.hpp" +#include "CreateNode.hpp" +#include "CreatePatch.hpp" +#include "CreatePort.hpp" +#include "Driver.hpp" +#include "Engine.hpp" +#include "EngineStore.hpp" +#include "GraphObjectImpl.hpp" +#include "PatchImpl.hpp" +#include "PluginImpl.hpp" +#include "PortImpl.hpp" +#include "Request.hpp" +#include "SetMetadata.hpp" +#include "SetPortValue.hpp" + +#define LOG(s) s << "[SetMetadata] " + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { +namespace Events { + +typedef Resource::Properties Properties; + +SetMetadata::SetMetadata( + Engine& engine, + SharedPtr<Request> request, + SampleCount timestamp, + bool create, + Resource::Graph context, + const URI& subject, + const Properties& properties, + const Properties& remove) + : QueuedEvent(engine, request, timestamp, false) + , _create_event(NULL) + , _subject(subject) + , _properties(properties) + , _remove(remove) + , _object(NULL) + , _patch(NULL) + , _compiled_patch(NULL) + , _create(create) + , _context(context) +{ + if (context != Resource::DEFAULT) { + Resource::set_context(_properties, context); + } + + /* + LOG(info) << "Set " << subject << " : " << context << " {" << endl; + typedef Resource::Properties::const_iterator iterator; + for (iterator i = properties.begin(); i != properties.end(); ++i) + LOG(info) << " " << i->first << " = " << i->second << " :: " << i->second.type() << endl; + LOG(info) << "}" << endl; + + LOG(info) << "Unset " << subject << " {" << endl; + typedef Resource::Properties::const_iterator iterator; + for (iterator i = remove.begin(); i != remove.end(); ++i) + LOG(info) << " " << i->first << " = " << i->second << " :: " << i->second.type() << endl; + LOG(info) << "}" << endl; + */ +} + +SetMetadata::~SetMetadata() +{ + for (SetEvents::iterator i = _set_events.begin(); i != _set_events.end(); ++i) + delete *i; + + delete _create_event; +} + +void +SetMetadata::pre_process() +{ + typedef Properties::const_iterator iterator; + + const bool is_graph_object = Path::is_path(_subject); + + _object = is_graph_object + ? _engine.engine_store()->find_object(Path(_subject.str())) + : static_cast<Shared::ResourceImpl*>(_engine.node_factory()->plugin(_subject)); + + if (!_object && (!is_graph_object || !_create)) { + _error = NOT_FOUND; + QueuedEvent::pre_process(); + return; + } + + const Ingen::Shared::LV2URIMap& uris = *_engine.world()->uris().get(); + + if (is_graph_object && !_object) { + Path path(_subject.str()); + bool is_patch = false, is_node = false, is_port = false, is_output = false; + PortType data_type(PortType::UNKNOWN); + Shared::ResourceImpl::type(uris, _properties, is_patch, is_node, is_port, is_output, data_type); + + // Create a separate request without a source so EventSource isn't unblocked twice + SharedPtr<Request> sub_request(new Request(NULL, _request->client(), _request->id())); + + if (is_patch) { + uint32_t poly = 1; + iterator p = _properties.find(uris.ingen_polyphony); + if (p != _properties.end() && p->second.is_valid() && p->second.type() == Atom::INT) + poly = p->second.get_int32(); + _create_event = new CreatePatch(_engine, sub_request, _time, + path, poly, _properties); + } else if (is_node) { + const iterator p = _properties.find(uris.rdf_instanceOf); + _create_event = new CreateNode(_engine, sub_request, _time, + path, p->second.get_uri(), _properties); + } else if (is_port) { + _blocking = bool(_request); + _create_event = new CreatePort(_engine, sub_request, _time, + path, data_type.uri(), is_output, _properties); + } + if (_create_event) + _create_event->pre_process(); + else + _error = BAD_OBJECT_TYPE; + QueuedEvent::pre_process(); + return; + } + + _types.reserve(_properties.size()); + + GraphObjectImpl* obj = dynamic_cast<GraphObjectImpl*>(_object); + +#if 0 + // If we're replacing (i.e. this is a PUT, not a POST), first remove all properties + // with keys we will later set. This must be done first so a PUT with several properties + // of the same predicate (e.g. rdf:type) retains the multiple values. Only previously + // existing properties should be replaced + if (_replace) + for (Properties::iterator p = _properties.begin(); p != _properties.end(); ++p) + obj->properties().erase(p->first); +#endif + + for (Properties::const_iterator p = _remove.begin(); p != _remove.end(); ++p) { + const Raul::URI& key = p->first; + const Raul::Atom& value = p->second; + if (key == uris.ingen_controlBinding && value == uris.wildcard) { + PortImpl* port = dynamic_cast<PortImpl*>(_object); + if (port) + _old_bindings = _engine.control_bindings()->remove(port); + } + _object->remove_property(key, value); + } + + for (Properties::iterator p = _properties.begin(); p != _properties.end(); ++p) { + const Raul::URI& key = p->first; + const Raul::Atom& value = p->second; + SpecialType op = NONE; + if (obj) { + Resource& resource = *obj; + resource.add_property(key, value); + + PortImpl* port = dynamic_cast<PortImpl*>(_object); + if (port) { + if (key == uris.ingen_broadcast) { + if (value.type() == Atom::BOOL) { + op = ENABLE_BROADCAST; + } else { + _error = BAD_VALUE_TYPE; + } + } else if (key == uris.ingen_value) { + SetPortValue* ev = new SetPortValue(_engine, _request, _time, port, value); + ev->pre_process(); + _set_events.push_back(ev); + } else if (key == uris.ingen_controlBinding) { + if (port->is_a(PortType::CONTROL)) { + if (value == uris.wildcard) { + _engine.control_bindings()->learn(port); + } else if (value.type() == Atom::DICT) { + op = CONTROL_BINDING; + } else { + _error = BAD_VALUE_TYPE; + } + } else { + _error = BAD_OBJECT_TYPE; + } + } + } else if ((_patch = dynamic_cast<PatchImpl*>(_object))) { + if (key == uris.ingen_enabled) { + if (value.type() == Atom::BOOL) { + op = ENABLE; + // FIXME: defer this until all other metadata has been processed + if (value.get_bool() && !_patch->enabled()) + _compiled_patch = _patch->compile(); + } else { + _error = BAD_VALUE_TYPE; + } + } else if (key == uris.ingen_polyphony) { + if (value.type() == Atom::INT) { + op = POLYPHONY; + _blocking = true; + _patch->prepare_internal_poly(*_engine.buffer_factory(), value.get_int32()); + } else { + _error = BAD_VALUE_TYPE; + } + } + } else if (key == uris.ingen_polyphonic) { + PatchImpl* parent = dynamic_cast<PatchImpl*>(obj->parent()); + if (parent) { + if (value.type() == Atom::BOOL) { + op = POLYPHONIC; + _blocking = true; + obj->set_property(key, value.get_bool()); + NodeImpl* node = dynamic_cast<NodeImpl*>(obj); + if (node) + node->set_polyphonic(value.get_bool()); + if (value.get_bool()) { + obj->prepare_poly(*_engine.buffer_factory(), parent->internal_poly()); + } else { + obj->prepare_poly(*_engine.buffer_factory(), 1); + } + } else { + _error = BAD_VALUE_TYPE; + } + } else { + _error = BAD_OBJECT_TYPE; + } + } + } + + if (_error != NO_ERROR) { + _error_predicate += key.str(); + break; + } + + _types.push_back(op); + } + + QueuedEvent::pre_process(); +} + +void +SetMetadata::execute(ProcessContext& context) +{ + if (_error != NO_ERROR) { + QueuedEvent::execute(context); + return; + } + + if (_create_event) { + QueuedEvent::execute(context); + _create_event->execute(context); + if (_blocking) + _request->unblock(); + return; + } + + for (SetEvents::iterator i = _set_events.begin(); i != _set_events.end(); ++i) + (*i)->execute(context); + + GraphObjectImpl* const object = dynamic_cast<GraphObjectImpl*>(_object); + NodeImpl* const node = dynamic_cast<NodeImpl*>(_object); + PortImpl* const port = dynamic_cast<PortImpl*>(_object); + + std::vector<SpecialType>::const_iterator t = _types.begin(); + for (Properties::const_iterator p = _properties.begin(); p != _properties.end(); ++p, ++t) { + const Raul::Atom& value = p->second; + switch (*t) { + case ENABLE_BROADCAST: + if (port) + port->broadcast(value.get_bool()); + break; + case ENABLE: + if (value.get_bool()) { + if (_compiled_patch) { + _engine.maid()->push(_patch->compiled_patch()); + _patch->compiled_patch(_compiled_patch); + } + _patch->enable(); + } else { + _patch->disable(); + } + break; + case POLYPHONIC: + { + PatchImpl* parent = reinterpret_cast<PatchImpl*>(object->parent()); + if (value.get_bool()) + object->apply_poly(*_engine.maid(), parent->internal_poly()); + else + object->apply_poly(*_engine.maid(), 1); + } + break; + case POLYPHONY: + if (_patch->internal_poly() != static_cast<uint32_t>(value.get_int32()) && + !_patch->apply_internal_poly(_engine.driver()->context(), + *_engine.buffer_factory(), + *_engine.maid(), value.get_int32())) { + _error = INTERNAL; + } + break; + case CONTROL_BINDING: + if (port) { + _engine.control_bindings()->port_binding_changed(context, port); + } else if (node) { + if (node->plugin_impl()->type() == Plugin::Internal) { + node->learn(); + } + } + break; + case NONE: + break; + } + } + + QueuedEvent::execute(context); + + if (_blocking) + _request->unblock(); +} + +void +SetMetadata::post_process() +{ + for (SetEvents::iterator i = _set_events.begin(); i != _set_events.end(); ++i) + (*i)->post_process(); + + switch (_error) { + case NO_ERROR: + if (_create_event) + _create_event->post_process(); + else + _request->respond_ok(); + if (_create) + _engine.broadcaster()->put(_subject, _properties, _context); + else + _engine.broadcaster()->delta(_subject, _remove, _properties); + break; + case NOT_FOUND: + _request->respond_error((boost::format( + "Unable to find object `%1%'") % _subject).str()); + break; + case INTERNAL: + _request->respond_error("Internal error"); + break; + case BAD_OBJECT_TYPE: + _request->respond_error((boost::format( + "Bad type for object `%1%'") % _subject).str()); + break; + case BAD_VALUE_TYPE: + _request->respond_error((boost::format( + "Bad metadata value type for subject `%1%' predicate `%2%'") + % _subject % _error_predicate).str()); + break; + } +} + +} // namespace Server +} // namespace Ingen +} // namespace Events + diff --git a/src/server/events/SetMetadata.hpp b/src/server/events/SetMetadata.hpp new file mode 100644 index 00000000..59856730 --- /dev/null +++ b/src/server/events/SetMetadata.hpp @@ -0,0 +1,124 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_EVENTS_SETMETADATA_HPP +#define INGEN_EVENTS_SETMETADATA_HPP + +#include <vector> +#include "raul/URI.hpp" +#include "shared/ResourceImpl.hpp" +#include "QueuedEvent.hpp" + +namespace Ingen { +namespace Server { + +class GraphObjectImpl; +class PatchImpl; +class CompiledPatch; + +namespace Events { + +/** \page methods + * <h2>POST</h2> + * As per HTTP (RFC2616 S9.5). + * + * Append properties to a graph object. + * + * An object can have several properties with a single predicate. + * POST appends properties without modifying or removing existing properties. + */ + +/** \page methods + * <h2>PUT</h2> + * As per HTTP (RFC2616 S9.6). + * + * Set properties of a graph object, or create an object. + * + * An object can have several properties with a single predicate. + * \li If the object does not yet exist, the message must contain sufficient + * information to create the object (e.g. known rdf:type properties, etc.) + * \li If the object does exist, a PUT removes all existing object properties + * with predicates that match any property in the message, then adds all + * properties from the message. + */ + +class SetPortValue; + +/** Set properties of a graph object. + * \ingroup engine + */ +class SetMetadata : public QueuedEvent +{ +public: + SetMetadata( + Engine& engine, + SharedPtr<Request> request, + SampleCount timestamp, + bool create, + Resource::Graph context, + const Raul::URI& subject, + const Resource::Properties& properties, + const Resource::Properties& remove = Resource::Properties()); + + ~SetMetadata(); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + enum ErrorType { + NO_ERROR, + NOT_FOUND, + INTERNAL, + BAD_OBJECT_TYPE, + BAD_VALUE_TYPE + }; + + enum SpecialType { + NONE, + ENABLE, + ENABLE_BROADCAST, + POLYPHONY, + POLYPHONIC, + CONTROL_BINDING + }; + + typedef std::vector<SetPortValue*> SetEvents; + + QueuedEvent* _create_event; + SetEvents _set_events; + std::vector<SpecialType> _types; + std::vector<SpecialType> _remove_types; + Raul::URI _subject; + Resource::Properties _properties; + Resource::Properties _remove; + Ingen::Shared::ResourceImpl* _object; + PatchImpl* _patch; + CompiledPatch* _compiled_patch; + std::string _error_predicate; + bool _create; + Resource::Graph _context; + + SharedPtr<ControlBindings::Bindings> _old_bindings; +}; + +} // namespace Server +} // namespace Ingen +} // namespace Events + +#endif // INGEN_EVENTS_SETMETADATA_HPP diff --git a/src/server/events/SetPortValue.cpp b/src/server/events/SetPortValue.cpp new file mode 100644 index 00000000..4af14d44 --- /dev/null +++ b/src/server/events/SetPortValue.cpp @@ -0,0 +1,223 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <sstream> +#include "raul/log.hpp" +#include "lv2/lv2plug.in/ns/ext/event/event.h" +#include "shared/LV2URIMap.hpp" +#include "shared/LV2Features.hpp" +#include "shared/LV2Atom.hpp" +#include "shared/World.hpp" +#include "AudioBuffer.hpp" +#include "ClientBroadcaster.hpp" +#include "ControlBindings.hpp" +#include "Driver.hpp" +#include "Engine.hpp" +#include "EngineStore.hpp" +#include "EventBuffer.hpp" +#include "MessageContext.hpp" +#include "NodeImpl.hpp" +#include "ObjectBuffer.hpp" +#include "PortImpl.hpp" +#include "ProcessContext.hpp" +#include "ProcessContext.hpp" +#include "Request.hpp" +#include "SetPortValue.hpp" + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { +namespace Events { + +SetPortValue::SetPortValue(Engine& engine, + SharedPtr<Request> request, + bool queued, + SampleCount timestamp, + const Raul::Path& port_path, + const Raul::Atom& value) + : QueuedEvent(engine, request, timestamp) + , _queued(queued) + , _port_path(port_path) + , _value(value) + , _port(NULL) +{ +} + +/** Internal */ +SetPortValue::SetPortValue(Engine& engine, + SharedPtr<Request> request, + SampleCount timestamp, + PortImpl* port, + const Raul::Atom& value) + : QueuedEvent(engine, request, timestamp) + , _queued(false) + , _port_path(port->path()) + , _value(value) + , _port(port) +{ +} + +SetPortValue::~SetPortValue() +{ +} + +void +SetPortValue::pre_process() +{ + if (_queued) { + if (_port == NULL) + _port = _engine.engine_store()->find_port(_port_path); + if (_port == NULL) + _error = PORT_NOT_FOUND; + } + + // Port is a message context port, set its value and + // call the plugin's message run function once + if (_port && _port->context() == Context::MESSAGE) { + apply(*_engine.message_context()); + _port->parent_node()->set_port_valid(_port->index()); + _engine.message_context()->run(_port->parent_node(), + _engine.driver()->frame_time() + _engine.driver()->block_length()); + } + + if (_port) { + _port->set_value(_value); + _port->set_property(_engine.world()->uris()->ingen_value, _value); + } + + QueuedEvent::pre_process(); +} + +void +SetPortValue::execute(ProcessContext& context) +{ + Event::execute(context); + assert(_time >= context.start() && _time <= context.end()); + + if (_port && _port->context() == Context::MESSAGE) + return; + + apply(context); + _engine.control_bindings()->port_value_changed(context, _port); +} + +void +SetPortValue::apply(Context& context) +{ + uint32_t start = context.start(); + if (_error == NO_ERROR && !_port) + _port = _engine.engine_store()->find_port(_port_path); + + if (!_port) { + if (_error == NO_ERROR) + _error = PORT_NOT_FOUND; + /*} else if (_port->buffer(0)->capacity() < _data_size) { + _error = NO_SPACE;*/ + } else { + Buffer* const buf = _port->buffer(0).get(); + AudioBuffer* const abuf = dynamic_cast<AudioBuffer*>(buf); + if (abuf) { + if (_value.type() != Atom::FLOAT) { + _error = TYPE_MISMATCH; + return; + } + + for (uint32_t v = 0; v < _port->poly(); ++v) { + ((AudioBuffer*)_port->buffer(v).get())->set_value( + _value.get_float(), start, _time); + } + return; + } + + Ingen::Shared::LV2URIMap& uris = *_engine.world()->uris().get(); + + EventBuffer* const ebuf = dynamic_cast<EventBuffer*>(buf); + if (ebuf && _value.type() == Atom::BLOB) { + const uint32_t frames = std::max(uint32_t(_time - start), ebuf->latest_frames()); + + // Size 0 event, pass it along to the plugin as a typed but empty event + if (_value.data_size() == 0) { + const uint32_t type_id = uris.uri_to_id(NULL, _value.get_blob_type()); + ebuf->append(frames, 0, type_id, 0, NULL); + _port->raise_set_by_user_flag(); + return; + + } else if (!strcmp(_value.get_blob_type(), + "http://lv2plug.in/ns/ext/midi#MidiEvent")) { + ebuf->prepare_write(context); + ebuf->append(frames, 0, + uris.global_to_event(uris.midi_MidiEvent.id).second, + _value.data_size(), + (const uint8_t*)_value.get_blob()); + _port->raise_set_by_user_flag(); + return; + } + } + + ObjectBuffer* const obuf = dynamic_cast<ObjectBuffer*>(buf); + if (obuf) { + obuf->atom()->size = obuf->size() - sizeof(LV2_Atom); + if (Ingen::Shared::LV2Atom::from_atom(uris, _value, obuf->atom())) { + debug << "Converted atom " << _value << " :: " << obuf->atom()->type + << " * " << obuf->atom()->size << " @ " << obuf->atom() << endl; + return; + } else { + warn << "Failed to convert atom to LV2 object" << endl; + } + } + + warn << "Unknown value type " << (int)_value.type() << endl; + } +} + +void +SetPortValue::post_process() +{ + string msg; + std::ostringstream ss; + switch (_error) { + case NO_ERROR: + assert(_port != NULL); + _request->respond_ok(); + _engine.broadcaster()->set_property(_port_path, + _engine.world()->uris()->ingen_value, _value); + break; + case TYPE_MISMATCH: + ss << "Illegal value type " << _value.type() + << " for port " << _port_path << endl; + _request->respond_error(ss.str()); + break; + case PORT_NOT_FOUND: + msg = "Unable to find port "; + msg.append(_port_path.str()).append(" to set value"); + _request->respond_error(msg); + break; + case NO_SPACE: + ss << "Attempt to write " << _value.data_size() << " bytes to " + << _port_path.str() << ", with capacity " + << _port->buffer_size() << endl; + _request->respond_error(ss.str()); + break; + } +} + +} // namespace Server +} // namespace Ingen +} // namespace Events + diff --git a/src/server/events/SetPortValue.hpp b/src/server/events/SetPortValue.hpp new file mode 100644 index 00000000..6f3cde87 --- /dev/null +++ b/src/server/events/SetPortValue.hpp @@ -0,0 +1,83 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_EVENTS_SETPORTVALUE_HPP +#define INGEN_EVENTS_SETPORTVALUE_HPP + +#include "raul/Atom.hpp" +#include "QueuedEvent.hpp" +#include "types.hpp" + +namespace Ingen { +namespace Server { + +class PortImpl; + +namespace Events { + +/** An event to change the value of a port. + * + * This event can either be queued or immediate, depending on the queued + * parameter passed to the constructor. It must be passed to the appropriate + * place (ie queued event passed to the event queue and non-queued event + * processed in the audio thread) or nasty things will happen. + * + * \ingroup engine + */ +class SetPortValue : public QueuedEvent +{ +public: + SetPortValue(Engine& engine, + SharedPtr<Request> request, + bool queued, + SampleCount timestamp, + const Raul::Path& port_path, + const Raul::Atom& value); + + SetPortValue(Engine& engine, + SharedPtr<Request> request, + SampleCount timestamp, + PortImpl* port, + const Raul::Atom& value); + + ~SetPortValue(); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + enum ErrorType { + NO_ERROR, + PORT_NOT_FOUND, + NO_SPACE, + TYPE_MISMATCH + }; + + void apply(Context& context); + + bool _queued; + const Raul::Path _port_path; + const Raul::Atom _value; + PortImpl* _port; +}; + +} // namespace Server +} // namespace Ingen +} // namespace Events + +#endif // INGEN_EVENTS_SETPORTVALUE_HPP diff --git a/src/server/events/UnregisterClient.cpp b/src/server/events/UnregisterClient.cpp new file mode 100644 index 00000000..b87bfa81 --- /dev/null +++ b/src/server/events/UnregisterClient.cpp @@ -0,0 +1,48 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 "ingen/ClientInterface.hpp" +#include "Request.hpp" +#include "UnregisterClient.hpp" +#include "Engine.hpp" +#include "ClientBroadcaster.hpp" + +using namespace Raul; + +namespace Ingen { +namespace Server { +namespace Events { + +UnregisterClient::UnregisterClient(Engine& engine, SharedPtr<Request> request, SampleCount timestamp, const URI& uri) + : QueuedEvent(engine, request, timestamp) + , _uri(uri) +{ +} + +void +UnregisterClient::post_process() +{ + if (_engine.broadcaster()->unregister_client(_uri)) + _request->respond_ok(); + else + _request->respond_error("Unable to unregister client"); +} + +} // namespace Server +} // namespace Ingen +} // namespace Events + diff --git a/src/server/events/UnregisterClient.hpp b/src/server/events/UnregisterClient.hpp new file mode 100644 index 00000000..eefc6318 --- /dev/null +++ b/src/server/events/UnregisterClient.hpp @@ -0,0 +1,50 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_EVENTS_UNREGISTERCLIENT_HPP +#define INGEN_EVENTS_UNREGISTERCLIENT_HPP + +#include "QueuedEvent.hpp" +#include "raul/URI.hpp" + +namespace Ingen { +namespace Server { +namespace Events { + +/** Unregisters an OSC client so it no longer receives notifications. + * + * \ingroup engine + */ +class UnregisterClient : public QueuedEvent +{ +public: + UnregisterClient(Engine& engine, + SharedPtr<Request> request, + SampleCount timestamp, + const Raul::URI& uri); + + void post_process(); + +private: + Raul::URI _uri; +}; + +} // namespace Server +} // namespace Ingen +} // namespace Events + +#endif // INGEN_EVENTS_UNREGISTERCLIENT_HPP diff --git a/src/server/ingen_engine.cpp b/src/server/ingen_engine.cpp new file mode 100644 index 00000000..568267cd --- /dev/null +++ b/src/server/ingen_engine.cpp @@ -0,0 +1,48 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 "shared/Module.hpp" +#include "shared/World.hpp" +#include "Engine.hpp" +#include "QueuedEngineInterface.hpp" +#include "util.hpp" + +using namespace Ingen; + +struct IngenEngineModule : public Ingen::Shared::Module { + virtual void load(Ingen::Shared::World* world) { + Server::set_denormal_flags(); + SharedPtr<Server::Engine> engine(new Server::Engine(world)); + world->set_local_engine(engine); + SharedPtr<Server::QueuedEngineInterface> interface( + new Server::QueuedEngineInterface(*engine.get(), + engine->event_queue_size())); + world->set_engine(interface); + engine->add_event_source(interface); + assert(world->local_engine() == engine); + } +}; + +extern "C" { + +Ingen::Shared::Module* +ingen_module_load() +{ + return new IngenEngineModule(); +} + +} // extern "C" diff --git a/src/server/ingen_http.cpp b/src/server/ingen_http.cpp new file mode 100644 index 00000000..d205b175 --- /dev/null +++ b/src/server/ingen_http.cpp @@ -0,0 +1,45 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 "shared/Module.hpp" +#include "shared/World.hpp" +#include "HTTPEngineReceiver.hpp" +#include "Engine.hpp" + +using namespace std; +using namespace Ingen; + +struct IngenHTTPModule : public Ingen::Shared::Module { + void load(Ingen::Shared::World* world) { + Server::Engine* engine = (Server::Engine*)world->local_engine().get(); + SharedPtr<Server::HTTPEngineReceiver> interface( + new Server::HTTPEngineReceiver( + *engine, + world->conf()->option("engine-port").get_int32())); + engine->add_event_source(interface); + } +}; + +extern "C" { + +Ingen::Shared::Module* +ingen_module_load() +{ + return new IngenHTTPModule(); +} + +} // extern "C" diff --git a/src/server/ingen_jack.cpp b/src/server/ingen_jack.cpp new file mode 100644 index 00000000..ff760f1c --- /dev/null +++ b/src/server/ingen_jack.cpp @@ -0,0 +1,45 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 "shared/Module.hpp" +#include "shared/World.hpp" +#include "JackDriver.hpp" +#include "Engine.hpp" + +using namespace std; +using namespace Ingen; + +struct IngenJackModule : public Ingen::Shared::Module { + void load(Ingen::Shared::World* world) { + Server::JackDriver* driver = new Server::JackDriver( + *(Server::Engine*)world->local_engine().get()); + driver->attach(world->conf()->option("jack-server").get_string(), + world->conf()->option("jack-client").get_string(), NULL); + ((Server::Engine*)world->local_engine().get())->set_driver( + SharedPtr<Server::Driver>(driver)); + } +}; + +extern "C" { + +Ingen::Shared::Module* +ingen_module_load() +{ + return new IngenJackModule(); +} + +} // extern "C" 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" diff --git a/src/server/ingen_osc.cpp b/src/server/ingen_osc.cpp new file mode 100644 index 00000000..bf0f09f8 --- /dev/null +++ b/src/server/ingen_osc.cpp @@ -0,0 +1,46 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 "shared/Module.hpp" +#include "shared/World.hpp" +#include "OSCEngineReceiver.hpp" +#include "Engine.hpp" + +using namespace std; +using namespace Ingen; + +struct IngenOSCModule : public Ingen::Shared::Module { + void load(Ingen::Shared::World* world) { + Server::Engine* engine = (Server::Engine*)world->local_engine().get(); + SharedPtr<Server::OSCEngineReceiver> interface( + new Server::OSCEngineReceiver( + *engine, + engine->event_queue_size(), + world->conf()->option("engine-port").get_int32())); + engine->add_event_source(interface); + } +}; + +extern "C" { + +Ingen::Shared::Module* +ingen_module_load() +{ + return new IngenOSCModule(); +} + +} // extern "C" diff --git a/src/server/internals/Controller.cpp b/src/server/internals/Controller.cpp new file mode 100644 index 00000000..3d477de6 --- /dev/null +++ b/src/server/internals/Controller.cpp @@ -0,0 +1,147 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <math.h> +#include "raul/midi_events.h" +#include "shared/LV2URIMap.hpp" +#include "internals/Controller.hpp" +#include "PostProcessor.hpp" +#include "events/SendPortValue.hpp" +#include "InputPort.hpp" +#include "OutputPort.hpp" +#include "InternalPlugin.hpp" +#include "AudioBuffer.hpp" +#include "ProcessContext.hpp" +#include "EventBuffer.hpp" +#include "util.hpp" + +using namespace std; + +namespace Ingen { +namespace Server { +namespace Internals { + +InternalPlugin* ControllerNode::internal_plugin(Shared::LV2URIMap& uris) { + return new InternalPlugin(uris, NS_INTERNALS "Controller", "controller"); +} + +ControllerNode::ControllerNode(InternalPlugin* plugin, + BufferFactory& bufs, + const string& path, + bool polyphonic, + PatchImpl* parent, + SampleRate srate) + : NodeImpl(plugin, path, false, parent, srate) + , _learning(false) +{ + const Ingen::Shared::LV2URIMap& uris = bufs.uris(); + _ports = new Raul::Array<PortImpl*>(6); + + _midi_in_port = new InputPort(bufs, this, "input", 0, 1, PortType::EVENTS, Raul::Atom()); + _midi_in_port->set_property(uris.lv2_name, "Input"); + _ports->at(0) = _midi_in_port; + + _param_port = new InputPort(bufs, this, "controller", 1, 1, PortType::CONTROL, 0.0f); + _param_port->set_property(uris.lv2_minimum, 0.0f); + _param_port->set_property(uris.lv2_maximum, 127.0f); + _param_port->set_property(uris.lv2_integer, true); + _param_port->set_property(uris.lv2_name, "Controller"); + _ports->at(1) = _param_port; + + _log_port = new InputPort(bufs, this, "logarithmic", 2, 1, PortType::CONTROL, 0.0f); + _log_port->set_property(uris.lv2_portProperty, uris.lv2_toggled); + _log_port->set_property(uris.lv2_name, "Logarithmic"); + _ports->at(2) = _log_port; + + _min_port = new InputPort(bufs, this, "minimum", 3, 1, PortType::CONTROL, 0.0f); + _min_port->set_property(uris.lv2_name, "Minimum"); + _ports->at(3) = _min_port; + + _max_port = new InputPort(bufs, this, "maximum", 4, 1, PortType::CONTROL, 1.0f); + _max_port->set_property(uris.lv2_name, "Maximum"); + _ports->at(4) = _max_port; + + _audio_port = new OutputPort(bufs, this, "ar_output", 5, 1, PortType::AUDIO, 0.0f); + _audio_port->set_property(uris.lv2_name, "Output"); + _ports->at(5) = _audio_port; +} + +void +ControllerNode::process(ProcessContext& context) +{ + NodeImpl::pre_process(context); + + uint32_t frames = 0; + uint32_t subframes = 0; + uint16_t type = 0; + uint16_t size = 0; + uint8_t* buf = NULL; + + EventBuffer* const midi_in = (EventBuffer*)_midi_in_port->buffer(0).get(); + + midi_in->rewind(); + + while (midi_in->get_event(&frames, &subframes, &type, &size, &buf)) { + // FIXME: type + if (size >= 3 && (buf[0] & 0xF0) == MIDI_CMD_CONTROL) + control(context, buf[1], buf[2], frames + context.start()); + + midi_in->increment(); + } + + NodeImpl::post_process(context); +} + +void +ControllerNode::control(ProcessContext& context, uint8_t control_num, uint8_t val, FrameTime time) +{ + Sample scaled_value; + + const Sample nval = (val / 127.0f); // normalized [0, 1] + + if (_learning) { + _param_port->set_value(control_num); + ((AudioBuffer*)_param_port->buffer(0).get())->set_value( + (float)control_num, context.start(), context.end()); + _param_port->broadcast_value(context, true); + _learning = false; + } + + const Sample min_port_val = ((AudioBuffer*)_min_port->buffer(0).get())->value_at(0); + const Sample max_port_val = ((AudioBuffer*)_max_port->buffer(0).get())->value_at(0); + const Sample log_port_val = ((AudioBuffer*)_log_port->buffer(0).get())->value_at(0); + + if (log_port_val > 0.0f) { + // haaaaack, stupid negatives and logarithms + Sample log_offset = 0; + if (min_port_val < 0) + log_offset = fabs(min_port_val); + const Sample min = log(min_port_val + 1 + log_offset); + const Sample max = log(max_port_val + 1 + log_offset); + scaled_value = expf(nval * (max - min) + min) - 1 - log_offset; + } else { + scaled_value = ((nval) * (max_port_val - min_port_val)) + min_port_val; + } + + if (control_num == ((AudioBuffer*)_param_port->buffer(0).get())->value_at(0)) + ((AudioBuffer*)_audio_port->buffer(0).get())->set_value(scaled_value, context.start(), time); +} + +} // namespace Internals +} // namespace Server +} // namespace Ingen + diff --git a/src/server/internals/Controller.hpp b/src/server/internals/Controller.hpp new file mode 100644 index 00000000..30ef663f --- /dev/null +++ b/src/server/internals/Controller.hpp @@ -0,0 +1,74 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_INTERNALS_CONTROLLER_HPP +#define INGEN_INTERNALS_CONTROLLER_HPP + +#include <string> +#include "NodeImpl.hpp" + +namespace Ingen { +namespace Server { + +class InputPort; +class OutputPort; +class InternalPlugin; + +namespace Internals { + +/** MIDI control input node. + * + * Creating one of these nodes is how a user makes "MIDI Bindings". Note that + * this node will always be monophonic, the poly parameter is ignored. + * + * \ingroup engine + */ +class ControllerNode : public NodeImpl +{ +public: + ControllerNode( + InternalPlugin* plugin, + BufferFactory& bufs, + const std::string& path, + bool polyphonic, + PatchImpl* parent, + SampleRate srate); + + void process(ProcessContext& context); + + void control(ProcessContext& context, uint8_t control_num, uint8_t val, FrameTime time); + + void learn() { _learning = true; } + + static InternalPlugin* internal_plugin(Shared::LV2URIMap& uris); + +private: + bool _learning; + + InputPort* _midi_in_port; + InputPort* _param_port; + InputPort* _log_port; + InputPort* _min_port; + InputPort* _max_port; + OutputPort* _audio_port; +}; + +} // namespace Server +} // namespace Ingen +} // namespace Internals + +#endif // INGEN_INTERNALS_CONTROLLER_HPP diff --git a/src/server/internals/Delay.cpp b/src/server/internals/Delay.cpp new file mode 100644 index 00000000..5c2758da --- /dev/null +++ b/src/server/internals/Delay.cpp @@ -0,0 +1,208 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <cmath> +#include <limits.h> +#include "raul/log.hpp" +#include "raul/Array.hpp" +#include "raul/Maid.hpp" +#include "raul/midi_events.h" +#include "shared/LV2URIMap.hpp" +#include "internals/Delay.hpp" +#include "AudioBuffer.hpp" +#include "Driver.hpp" +#include "EventBuffer.hpp" +#include "InputPort.hpp" +#include "InternalPlugin.hpp" +#include "OutputPort.hpp" +#include "PatchImpl.hpp" +#include "ProcessContext.hpp" +#include "ingen-config.h" +#include "util.hpp" + +#define LOG(s) s << "[DelayNode] " + +#define CALC_DELAY(delaytime) \ + (f_clamp (delaytime * (float)sample_rate, 1.0f, (float)(buffer_mask + 1))) + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { +namespace Internals { + +static const float MAX_DELAY_SECONDS = 8.0f; + +InternalPlugin* DelayNode::internal_plugin(Shared::LV2URIMap& uris) { + return new InternalPlugin(uris, NS_INTERNALS "Delay", "delay"); +} + +DelayNode::DelayNode( + InternalPlugin* plugin, + BufferFactory& bufs, + const std::string& path, + bool polyphonic, + PatchImpl* parent, + SampleRate srate) + : NodeImpl(plugin, path, polyphonic, parent, srate) + , _buffer(0) + , _buffer_length(0) + , _buffer_mask(0) + , _write_phase(0) +{ + const Ingen::Shared::LV2URIMap& uris = bufs.uris(); + _ports = new Raul::Array<PortImpl*>(3); + + const float default_delay = 1.0f; + _last_delay_time = default_delay; + _delay_samples = default_delay; + + _delay_port = new InputPort(bufs, this, "delay", 1, _polyphony, PortType::CONTROL, default_delay); + _delay_port->set_property(uris.lv2_name, "Delay"); + _delay_port->set_property(uris.lv2_default, default_delay); + _delay_port->set_property(uris.lv2_minimum, (float)(1.0/(double)srate)); + _delay_port->set_property(uris.lv2_maximum, MAX_DELAY_SECONDS); + _ports->at(0) = _delay_port; + + _in_port = new InputPort(bufs, this, "in", 0, 1, PortType::AUDIO, 0.0f); + _in_port->set_property(uris.lv2_name, "Input"); + _ports->at(1) = _in_port; + + _out_port = new OutputPort(bufs, this, "out", 0, 1, PortType::AUDIO, 0.0f); + _out_port->set_property(uris.lv2_name, "Output"); + _ports->at(2) = _out_port; + + //_buffer = bufs.get(PortType::AUDIO, bufs.audio_buffer_size(buffer_length_frames), true); + +} + +DelayNode::~DelayNode() +{ + //_buffer.reset(); + free(_buffer); +} + +void +DelayNode::activate(BufferFactory& bufs) +{ + NodeImpl::activate(bufs); + const SampleCount min_size = MAX_DELAY_SECONDS * _srate; + + // Smallest power of two larger than min_size + SampleCount size = 1; + while (size < min_size) + size <<= 1; + + _buffer = (float*)calloc(size, sizeof(float)); + _buffer_mask = size - 1; + _buffer_length = size; + //_buffer->clear(); + _write_phase = 0; +} + +static inline float f_clamp(float x, float a, float b) +{ + const float x1 = fabs(x - a); + const float x2 = fabs(x - b); + + x = x1 + a + b; + x -= x2; + x *= 0.5; + + return x; +} + +static inline float cube_interp(const float fr, const float inm1, const float + in, const float inp1, const float inp2) +{ + return in + 0.5f * fr * (inp1 - inm1 + + fr * (4.0f * inp1 + 2.0f * inm1 - 5.0f * in - inp2 + + fr * (3.0f * (in - inp1) - inm1 + inp2))); +} + +void +DelayNode::process(ProcessContext& context) +{ + AudioBuffer* const delay_buf = (AudioBuffer*)_delay_port->buffer(0).get(); + AudioBuffer* const in_buf = (AudioBuffer*)_in_port->buffer(0).get(); + AudioBuffer* const out_buf = (AudioBuffer*)_out_port->buffer(0).get(); + + NodeImpl::pre_process(context); + + DelayNode* plugin_data = this; + + const float* const in = in_buf->data(); + float* const out = out_buf->data(); + const float delay_time = delay_buf->data()[0]; + const uint32_t buffer_mask = plugin_data->_buffer_mask; + const unsigned int sample_rate = plugin_data->_srate; + float delay_samples = plugin_data->_delay_samples; + long write_phase = plugin_data->_write_phase; + const uint32_t sample_count = context.nframes(); + + if (write_phase == 0) { + _last_delay_time = delay_time; + _delay_samples = delay_samples = CALC_DELAY(delay_time); + } + + if (delay_time == _last_delay_time) { + const long idelay_samples = (long)delay_samples; + const float frac = delay_samples - idelay_samples; + + for (uint32_t i = 0; i < sample_count; i++) { + long read_phase = write_phase - (long)delay_samples; + const float read = cube_interp(frac, + buffer_at(read_phase - 1), + buffer_at(read_phase), + buffer_at(read_phase + 1), + buffer_at(read_phase + 2)); + buffer_at(write_phase++) = in[i]; + out[i] = read; + } + } else { + const float next_delay_samples = CALC_DELAY(delay_time); + const float delay_samples_slope = (next_delay_samples - delay_samples) / sample_count; + + for (uint32_t i = 0; i < sample_count; i++) { + delay_samples += delay_samples_slope; + write_phase++; + const long read_phase = write_phase - (long)delay_samples; + const long idelay_samples = (long)delay_samples; + const float frac = delay_samples - idelay_samples; + const float read = cube_interp(frac, + buffer_at(read_phase - 1), + buffer_at(read_phase), + buffer_at(read_phase + 1), + buffer_at(read_phase + 2)); + buffer_at(write_phase) = in[i]; + out[i] = read; + } + + _last_delay_time = delay_time; + _delay_samples = delay_samples; + } + + _write_phase = write_phase; + + NodeImpl::post_process(context); +} + +} // namespace Internals +} // namespace Server +} // namespace Ingen + diff --git a/src/server/internals/Delay.hpp b/src/server/internals/Delay.hpp new file mode 100644 index 00000000..7456d7cc --- /dev/null +++ b/src/server/internals/Delay.hpp @@ -0,0 +1,78 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_INTERNALS_DELAY_HPP +#define INGEN_INTERNALS_DELAY_HPP + +#include <string> +#include <math.h> +#include "types.hpp" +#include "NodeImpl.hpp" + +namespace Ingen { +namespace Server { + +class InputPort; +class OutputPort; +class InternalPlugin; +class BufferFactory; + +namespace Internals { + +class DelayNode : public NodeImpl +{ +public: + DelayNode( + InternalPlugin* plugin, + BufferFactory& bufs, + const std::string& path, + bool polyphonic, + PatchImpl* parent, + SampleRate srate); + + ~DelayNode(); + + void activate(BufferFactory& bufs); + + void process(ProcessContext& context); + + static InternalPlugin* internal_plugin(Shared::LV2URIMap& uris); + + float delay_samples() const { return _delay_samples; } + +private: + inline float& buffer_at(long phase) const { return _buffer[phase & _buffer_mask]; } + + InputPort* _delay_port; + InputPort* _in_port; + OutputPort* _out_port; + + typedef long Phase; + + float* _buffer; + uint32_t _buffer_length; + uint32_t _buffer_mask; + Phase _write_phase; + float _last_delay_time; + float _delay_samples; +}; + +} // namespace Server +} // namespace Ingen +} // namespace Internals + +#endif // INGEN_INTERNALS_DELAY_HPP diff --git a/src/server/internals/Note.cpp b/src/server/internals/Note.cpp new file mode 100644 index 00000000..f28dacc1 --- /dev/null +++ b/src/server/internals/Note.cpp @@ -0,0 +1,416 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <cmath> +#include "raul/log.hpp" +#include "raul/Array.hpp" +#include "raul/Maid.hpp" +#include "raul/midi_events.h" +#include "shared/LV2URIMap.hpp" +#include "internals/Note.hpp" +#include "AudioBuffer.hpp" +#include "Driver.hpp" +#include "EventBuffer.hpp" +#include "InputPort.hpp" +#include "InternalPlugin.hpp" +#include "OutputPort.hpp" +#include "PatchImpl.hpp" +#include "ProcessContext.hpp" +#include "util.hpp" +#include "ingen-config.h" + +#define LOG(s) s << "[NoteNode] " + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { +namespace Internals { + +InternalPlugin* NoteNode::internal_plugin(Shared::LV2URIMap& uris) { + return new InternalPlugin(uris, NS_INTERNALS "Note", "note"); +} + +NoteNode::NoteNode( + InternalPlugin* plugin, + BufferFactory& bufs, + const std::string& path, + bool polyphonic, + PatchImpl* parent, + SampleRate srate) + : NodeImpl(plugin, path, polyphonic, parent, srate) + , _voices(new Raul::Array<Voice>(_polyphony)) + , _prepared_voices(NULL) + , _sustain(false) +{ + const Ingen::Shared::LV2URIMap& uris = bufs.uris(); + _ports = new Raul::Array<PortImpl*>(5); + + _midi_in_port = new InputPort(bufs, this, "input", 0, 1, PortType::EVENTS, Raul::Atom()); + _midi_in_port->set_property(uris.lv2_name, "Input"); + _ports->at(0) = _midi_in_port; + + _freq_port = new OutputPort(bufs, this, "frequency", 1, _polyphony, PortType::AUDIO, 440.0f); + _freq_port->set_property(uris.lv2_name, "Frequency"); + _ports->at(1) = _freq_port; + + _vel_port = new OutputPort(bufs, this, "velocity", 2, _polyphony, PortType::AUDIO, 0.0f); + _vel_port->set_property(uris.lv2_minimum, 0.0f); + _vel_port->set_property(uris.lv2_maximum, 1.0f); + _vel_port->set_property(uris.lv2_name, "Velocity"); + _ports->at(2) = _vel_port; + + _gate_port = new OutputPort(bufs, this, "gate", 3, _polyphony, PortType::AUDIO, 0.0f); + _gate_port->set_property(uris.lv2_portProperty, uris.lv2_toggled); + _gate_port->set_property(uris.lv2_name, "Gate"); + _ports->at(3) = _gate_port; + + _trig_port = new OutputPort(bufs, this, "trigger", 4, _polyphony, PortType::AUDIO, 0.0f); + _trig_port->set_property(uris.lv2_portProperty, uris.lv2_toggled); + _trig_port->set_property(uris.lv2_name, "Trigger"); + _ports->at(4) = _trig_port; +} + +NoteNode::~NoteNode() +{ + delete _voices; +} + +bool +NoteNode::prepare_poly(BufferFactory& bufs, uint32_t poly) +{ + if (!_polyphonic) + return true; + + NodeImpl::prepare_poly(bufs, poly); + + if (_prepared_voices && poly <= _prepared_voices->size()) + return true; + + _prepared_voices = new Raul::Array<Voice>(poly, *_voices, Voice()); + + return true; +} + +bool +NoteNode::apply_poly(Raul::Maid& maid, uint32_t poly) +{ + if (!NodeImpl::apply_poly(maid, poly)) + return false; + + if (_prepared_voices) { + assert(_polyphony <= _prepared_voices->size()); + maid.push(_voices); + _voices = _prepared_voices; + _prepared_voices = NULL; + } + assert(_polyphony <= _voices->size()); + + return true; +} + +void +NoteNode::process(ProcessContext& context) +{ + EventBuffer* const midi_in = (EventBuffer*)_midi_in_port->buffer(0).get(); + NodeImpl::pre_process(context); + + uint32_t frames = 0; + uint32_t subframes = 0; + uint16_t type = 0; + uint16_t size = 0; + uint8_t* buf = NULL; + + midi_in->rewind(); + + if (midi_in->event_count() > 0) + for (midi_in->rewind(); midi_in->get_event(&frames, &subframes, &type, &size, &buf); + midi_in->increment()) { + +#ifdef RAUL_LOG_DEBUG + LOG(debug) << "EVENT TYPE " << type << " @ " << frames << "." << subframes << ": "; + for (uint16_t i = 0; i < size; ++i) + debug << (int)((char)buf[i]) << " "; + debug << endl; +#endif + + if (frames < context.offset()) + continue; + if (frames > context.nframes()) + break; + + const FrameTime time = context.start() + (FrameTime)frames; + + if (size >= 3) { + switch (buf[0] & 0xF0) { + case MIDI_CMD_NOTE_ON: + if (buf[2] == 0) + note_off(context, buf[1], time); + else + note_on(context, buf[1], buf[2], time); + break; + case MIDI_CMD_NOTE_OFF: + note_off(context, buf[1], time); + break; + case MIDI_CMD_CONTROL: + switch (buf[1]) { + case MIDI_CTL_ALL_NOTES_OFF: + case MIDI_CTL_ALL_SOUNDS_OFF: + all_notes_off(context, time); + break; + case MIDI_CTL_SUSTAIN: + if (buf[2] > 63) + sustain_on(context, time); + else + sustain_off(context, time); + break; + case MIDI_CMD_BENDER: + // ? + break; + default: + //warn << "Ignored controller " << buf[1] << endl; + break; + } + break; + default: + //fprintf(stderr, "Unknown (size %d) MIDI event %X\n", size, buf[0]); + break; + } + } else { + //fprintf(stderr, "Unknown (size %d) MIDI event %X\n", size, buf[0]); + } + } + + NodeImpl::post_process(context); +} + +void +NoteNode::note_on(ProcessContext& context, uint8_t note_num, uint8_t velocity, FrameTime time) +{ + assert(time >= context.start() && time <= context.end()); + assert(note_num <= 127); + + Key* key = &_keys[note_num]; + Voice* voice = NULL; + uint32_t voice_num = 0; + + if (key->state != Key::OFF) { +#ifdef RAUL_LOG_DEBUG + LOG(debug) << "Double midi note received" << endl; +#endif + return; + } + + // Look for free voices + for (uint32_t i=0; i < _polyphony; ++i) { + if ((*_voices)[i].state == Voice::Voice::FREE) { + voice = &(*_voices)[i]; + voice_num = i; + break; + } + } + + // If we didn't find a free one, steal the oldest + if (voice == NULL) { + voice_num = 0; + voice = &(*_voices)[0]; + FrameTime oldest_time = (*_voices)[0].time; + for (uint32_t i=1; i < _polyphony; ++i) { + if ((*_voices)[i].time < oldest_time) { + voice = &(*_voices)[i]; + voice_num = i; + oldest_time = voice->time; + } + } + } + assert(voice != NULL); + assert(voice == &(*_voices)[voice_num]); + +#ifdef RAUL_LOG_DEBUG + LOG(debug) << "Note " << (int)note_num << " on @ " << time + << ". Voice " << voice_num << " / " << _polyphony << endl; +#endif + + // Update stolen key, if applicable + if (voice->state == Voice::Voice::ACTIVE) { + assert(_keys[voice->note].state == Key::ON_ASSIGNED); + assert(_keys[voice->note].voice == voice_num); + _keys[voice->note].state = Key::Key::ON_UNASSIGNED; +#ifdef RAUL_LOG_DEBUG + LOG(debug) << "Stole voice " << voice_num << endl; +#endif + } + + // Store key information for later reallocation on note off + key->state = Key::Key::ON_ASSIGNED; + key->voice = voice_num; + key->time = time; + + // Trigger voice + voice->state = Voice::Voice::ACTIVE; + voice->note = note_num; + voice->time = time; + + assert(_keys[voice->note].state == Key::Key::ON_ASSIGNED); + assert(_keys[voice->note].voice == voice_num); + + ((AudioBuffer*)_freq_port->buffer(voice_num).get())->set_value( + note_to_freq(note_num), context.start(), time); + ((AudioBuffer*)_vel_port->buffer(voice_num).get())->set_value( + velocity/127.0, context.start(), time); + ((AudioBuffer*)_gate_port->buffer(voice_num).get())->set_value( + 1.0f, context.start(), time); + + // trigger (one sample) + ((AudioBuffer*)_trig_port->buffer(voice_num).get())->set_value( + 1.0f, context.start(), time); + ((AudioBuffer*)_trig_port->buffer(voice_num).get())->set_value( + 0.0f, context.start(), time + 1); + + assert(key->state == Key::Key::ON_ASSIGNED); + assert(voice->state == Voice::Voice::ACTIVE); + assert(key->voice == voice_num); + assert((*_voices)[key->voice].note == note_num); +} + +void +NoteNode::note_off(ProcessContext& context, uint8_t note_num, FrameTime time) +{ + assert(time >= context.start() && time <= context.end()); + + Key* key = &_keys[note_num]; + +#ifdef RAUL_LOG_DEBUG + debug << "Note " << (int)note_num << " off @ " << time << endl; +#endif + + if (key->state == Key::ON_ASSIGNED) { + // Assigned key, turn off voice and key + if ((*_voices)[key->voice].state == Voice::ACTIVE) { + assert((*_voices)[key->voice].note == note_num); + + if ( ! _sustain) { +#ifdef RAUL_LOG_DEBUG + debug << "Free voice " << key->voice << endl; +#endif + free_voice(context, key->voice, time); + } else { +#ifdef RAUL_LOG_DEBUG + debug << "Hold voice " << key->voice << endl; +#endif + (*_voices)[key->voice].state = Voice::HOLDING; + } + + } else { +#ifdef RAUL_LOG_DEBUG + debug << "WARNING: Assigned key, but voice not active" << endl; +#endif + } + } + + key->state = Key::OFF; +} + +void +NoteNode::free_voice(ProcessContext& context, uint32_t voice, FrameTime time) +{ + assert(time >= context.start() && time <= context.end()); + + // Find a key to reassign to the freed voice (the newest, if there is one) + Key* replace_key = NULL; + uint8_t replace_key_num = 0; + + for (uint8_t i = 0; i <= 127; ++i) { + if (_keys[i].state == Key::ON_UNASSIGNED) { + if (replace_key == NULL || _keys[i].time > replace_key->time) { + replace_key = &_keys[i]; + replace_key_num = i; + } + } + } + + if (replace_key != NULL) { // Found a key to assign to freed voice + assert(&_keys[replace_key_num] == replace_key); + assert(replace_key->state == Key::ON_UNASSIGNED); + + // Change the freq but leave the gate high and don't retrigger + ((AudioBuffer*)_freq_port->buffer(voice).get())->set_value(note_to_freq(replace_key_num), context.start(), time); + + replace_key->state = Key::ON_ASSIGNED; + replace_key->voice = voice; + _keys[(*_voices)[voice].note].state = Key::ON_UNASSIGNED; + (*_voices)[voice].note = replace_key_num; + (*_voices)[voice].state = Voice::ACTIVE; + } else { + // No new note for voice, deactivate (set gate low) +#ifdef RAUL_LOG_DEBUG + LOG(debug) << "Note off: key " << (*_voices)[voice].note << " voice " << voice << endl; +#endif + ((AudioBuffer*)_gate_port->buffer(voice).get())->set_value(0.0f, context.start(), time); + (*_voices)[voice].state = Voice::FREE; + } +} + +void +NoteNode::all_notes_off(ProcessContext& context, FrameTime time) +{ + assert(time >= context.start() && time <= context.end()); + +#ifdef RAUL_LOG_DEBUG + LOG(debug) << "All notes off @ " << time << endl; +#endif + + // FIXME: set all keys to Key::OFF? + + for (uint32_t i = 0; i < _polyphony; ++i) { + ((AudioBuffer*)_gate_port->buffer(i).get())->set_value(0.0f, context.start(), time); + (*_voices)[i].state = Voice::FREE; + } +} + +float +NoteNode::note_to_freq(int num) +{ + static const float A4 = 440.0f; + if (num >= 0 && num <= 119) + return A4 * powf(2.0f, (float)(num - 57.0f) / 12.0f); + return 1.0f; // Frequency of zero causes numerical problems... +} + +void +NoteNode::sustain_on(ProcessContext& context, FrameTime time) +{ + _sustain = true; +} + +void +NoteNode::sustain_off(ProcessContext& context, FrameTime time) +{ + assert(time >= context.start() && time <= context.end()); + + _sustain = false; + + for (uint32_t i=0; i < _polyphony; ++i) + if ((*_voices)[i].state == Voice::HOLDING) + free_voice(context, i, time); +} + +} // namespace Internals +} // namespace Server +} // namespace Ingen + diff --git a/src/server/internals/Note.hpp b/src/server/internals/Note.hpp new file mode 100644 index 00000000..d20baf13 --- /dev/null +++ b/src/server/internals/Note.hpp @@ -0,0 +1,101 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_INTERNALS_NOTE_HPP +#define INGEN_INTERNALS_NOTE_HPP + +#include <string> +#include "types.hpp" +#include "NodeImpl.hpp" + +namespace Ingen { +namespace Server { + +class InputPort; +class OutputPort; +class InternalPlugin; + +namespace Internals { + +/** MIDI note input node. + * + * For pitched instruments like keyboard, etc. + * + * \ingroup engine + */ +class NoteNode : public NodeImpl +{ +public: + NoteNode( + InternalPlugin* plugin, + BufferFactory& bufs, + const std::string& path, + bool polyphonic, + PatchImpl* parent, + SampleRate srate); + + ~NoteNode(); + + bool prepare_poly(BufferFactory& bufs, uint32_t poly); + bool apply_poly(Raul::Maid& maid, uint32_t poly); + + void process(ProcessContext& context); + + void note_on(ProcessContext& context, uint8_t note_num, uint8_t velocity, FrameTime time); + void note_off(ProcessContext& context, uint8_t note_num, FrameTime time); + void all_notes_off(ProcessContext& context, FrameTime time); + + void sustain_on(ProcessContext& context, FrameTime time); + void sustain_off(ProcessContext& context, FrameTime time); + + static InternalPlugin* internal_plugin(Shared::LV2URIMap& uris); + +private: + /** Key, one for each key on the keyboard */ + struct Key { + enum State { OFF, ON_ASSIGNED, ON_UNASSIGNED }; + Key() : state(OFF), voice(0), time(0) {} + State state; uint32_t voice; SampleCount time; + }; + + /** Voice, one of these always exists for each voice */ + struct Voice { + enum State { FREE, ACTIVE, HOLDING }; + Voice() : state(FREE), note(0), time(0) {} + State state; uint8_t note; SampleCount time; + }; + + float note_to_freq(int num); + void free_voice(ProcessContext& context, uint32_t voice, FrameTime time); + + Raul::Array<Voice>* _voices; + Raul::Array<Voice>* _prepared_voices; + Key _keys[128]; + bool _sustain; ///< Whether or not hold pedal is depressed + + InputPort* _midi_in_port; + OutputPort* _freq_port; + OutputPort* _vel_port; + OutputPort* _gate_port; + OutputPort* _trig_port; +}; + +} // namespace Server +} // namespace Ingen +} // namespace Internals + +#endif // INGEN_INTERNALS_NOTE_HPP diff --git a/src/server/internals/Trigger.cpp b/src/server/internals/Trigger.cpp new file mode 100644 index 00000000..d062fa13 --- /dev/null +++ b/src/server/internals/Trigger.cpp @@ -0,0 +1,168 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 <cmath> +#include "raul/log.hpp" +#include "raul/midi_events.h" +#include "shared/LV2URIMap.hpp" +#include "internals/Trigger.hpp" +#include "AudioBuffer.hpp" +#include "EventBuffer.hpp" +#include "InputPort.hpp" +#include "InternalPlugin.hpp" +#include "OutputPort.hpp" +#include "ProcessContext.hpp" +#include "util.hpp" +#include "ingen-config.h" + +#define LOG(s) s << "[TriggerNode] " + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { +namespace Internals { + +InternalPlugin* TriggerNode::internal_plugin(Shared::LV2URIMap& uris) { + return new InternalPlugin(uris, NS_INTERNALS "Trigger", "trigger"); +} + +TriggerNode::TriggerNode( + InternalPlugin* plugin, + BufferFactory& bufs, + const std::string& path, + bool polyphonic, + PatchImpl* parent, + SampleRate srate) + : NodeImpl(plugin, path, false, parent, srate) + , _learning(false) +{ + const Ingen::Shared::LV2URIMap& uris = bufs.uris(); + _ports = new Raul::Array<PortImpl*>(5); + + _midi_in_port = new InputPort(bufs, this, "input", 0, 1, PortType::EVENTS, Raul::Atom()); + _midi_in_port->set_property(uris.lv2_name, "Input"); + _ports->at(0) = _midi_in_port; + + _note_port = new InputPort(bufs, this, "note", 1, 1, PortType::CONTROL, 60.0f); + _note_port->set_property(uris.lv2_minimum, 0.0f); + _note_port->set_property(uris.lv2_maximum, 127.0f); + _note_port->set_property(uris.lv2_integer, true); + _note_port->set_property(uris.lv2_name, "Note"); + _ports->at(1) = _note_port; + + _gate_port = new OutputPort(bufs, this, "gate", 2, 1, PortType::AUDIO, 0.0f); + _gate_port->set_property(uris.lv2_portProperty, uris.lv2_toggled); + _gate_port->set_property(uris.lv2_name, "Gate"); + _ports->at(2) = _gate_port; + + _trig_port = new OutputPort(bufs, this, "trigger", 3, 1, PortType::AUDIO, 0.0f); + _trig_port->set_property(uris.lv2_portProperty, uris.lv2_toggled); + _trig_port->set_property(uris.lv2_name, "Trigger"); + _ports->at(3) = _trig_port; + + _vel_port = new OutputPort(bufs, this, "velocity", 4, 1, PortType::AUDIO, 0.0f); + _vel_port->set_property(uris.lv2_minimum, 0.0f); + _vel_port->set_property(uris.lv2_maximum, 1.0f); + _vel_port->set_property(uris.lv2_name, "Velocity"); + _ports->at(4) = _vel_port; +} + +void +TriggerNode::process(ProcessContext& context) +{ + NodeImpl::pre_process(context); + + uint32_t frames = 0; + uint32_t subframes = 0; + uint16_t type = 0; + uint16_t size = 0; + uint8_t* buf = NULL; + + EventBuffer* const midi_in = (EventBuffer*)_midi_in_port->buffer(0).get(); + + midi_in->rewind(); + + while (midi_in->get_event(&frames, &subframes, &type, &size, &buf)) { + const FrameTime time = context.start() + (FrameTime)frames; + + if (size >= 3) { + switch (buf[0] & 0xF0) { + case MIDI_CMD_NOTE_ON: + if (buf[2] == 0) + note_off(context, buf[1], time); + else + note_on(context, buf[1], buf[2], time); + break; + case MIDI_CMD_NOTE_OFF: + note_off(context, buf[1], time); + break; + case MIDI_CMD_CONTROL: + if (buf[1] == MIDI_CTL_ALL_NOTES_OFF + || buf[1] == MIDI_CTL_ALL_SOUNDS_OFF) + ((AudioBuffer*)_gate_port->buffer(0).get())->set_value(0.0f, context.start(), time); + default: + break; + } + } + + midi_in->increment(); + } + + NodeImpl::post_process(context); +} + +void +TriggerNode::note_on(ProcessContext& context, uint8_t note_num, uint8_t velocity, FrameTime time) +{ + assert(time >= context.start() && time <= context.end()); + + if (_learning) { + _note_port->set_value(note_num); + ((AudioBuffer*)_note_port->buffer(0).get())->set_value( + (float)note_num, context.start(), context.end()); + _note_port->broadcast_value(context, true); + _learning = false; + } + +#ifdef RAUL_LOG_DEBUG + LOG(debug) << path() << " note " << (int)note_num << " on @ " << time << endl; +#endif + + Sample filter_note = ((AudioBuffer*)_note_port->buffer(0).get())->value_at(0); + if (filter_note >= 0.0 && filter_note < 127.0 && (note_num == (uint8_t)filter_note)) { + ((AudioBuffer*)_gate_port->buffer(0).get())->set_value(1.0f, context.start(), time); + ((AudioBuffer*)_trig_port->buffer(0).get())->set_value(1.0f, context.start(), time); + ((AudioBuffer*)_trig_port->buffer(0).get())->set_value(0.0f, context.start(), time + 1); + ((AudioBuffer*)_vel_port->buffer(0).get())->set_value(velocity / 127.0f, context.start(), time); + assert(((AudioBuffer*)_trig_port->buffer(0).get())->data()[time - context.start()] == 1.0f); + } +} + +void +TriggerNode::note_off(ProcessContext& context, uint8_t note_num, FrameTime time) +{ + assert(time >= context.start() && time <= context.end()); + + if (note_num == lrintf(((AudioBuffer*)_note_port->buffer(0).get())->value_at(0))) + ((AudioBuffer*)_gate_port->buffer(0).get())->set_value(0.0f, context.start(), time); +} + +} // namespace Internals +} // namespace Server +} // namespace Ingen diff --git a/src/server/internals/Trigger.hpp b/src/server/internals/Trigger.hpp new file mode 100644 index 00000000..92b85345 --- /dev/null +++ b/src/server/internals/Trigger.hpp @@ -0,0 +1,77 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_INTERNALS_TRIGGER_HPP +#define INGEN_INTERNALS_TRIGGER_HPP + +#include <string> +#include "NodeImpl.hpp" + +namespace Ingen { +namespace Server { + +class InputPort; +class OutputPort; +class InternalPlugin; + +namespace Internals { + +/** MIDI trigger input node. + * + * Just has a gate, for drums etc. A control port is used to select + * which note number is responded to. + * + * Note that this node is always monophonic, the poly parameter is ignored. + * (Should that change?) + * + * \ingroup engine + */ +class TriggerNode : public NodeImpl +{ +public: + TriggerNode( + InternalPlugin* plugin, + BufferFactory& bufs, + const std::string& path, + bool polyphonic, + PatchImpl* parent, + SampleRate srate); + + void process(ProcessContext& context); + + void note_on(ProcessContext& context, uint8_t note_num, uint8_t velocity, FrameTime time); + void note_off(ProcessContext& context, uint8_t note_num, FrameTime time); + + void learn() { _learning = true; } + + static InternalPlugin* internal_plugin(Shared::LV2URIMap& uris); + +private: + bool _learning; + + InputPort* _midi_in_port; + InputPort* _note_port; + OutputPort* _gate_port; + OutputPort* _trig_port; + OutputPort* _vel_port; +}; + +} // namespace Server +} // namespace Ingen +} // namespace Internals + +#endif // INGEN_INTERNALS_TRIGGER_HPP diff --git a/src/server/mix.hpp b/src/server/mix.hpp new file mode 100644 index 00000000..4b062ed9 --- /dev/null +++ b/src/server/mix.hpp @@ -0,0 +1,91 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_MIX_HPP +#define INGEN_ENGINE_MIX_HPP + +#include "raul/log.hpp" +#include "ingen/PortType.hpp" +#include "Buffer.hpp" +#include "Context.hpp" + +using namespace Raul; + +namespace Ingen { +namespace Server { + +inline void +mix(Context& context, Buffer* dst, const IntrusivePtr<Buffer>* srcs, uint32_t num_srcs) +{ + using Ingen::PortType; + switch (dst->type().symbol()) { + case PortType::AUDIO: + case PortType::CONTROL: + // Copy the first source + dst->copy(context, srcs[0].get()); + + // Mix in the rest + for (uint32_t i = 1; i < num_srcs; ++i) { + assert(srcs[i]->type() == PortType::AUDIO || srcs[i]->type() == PortType::CONTROL); + ((AudioBuffer*)dst)->accumulate(context, (AudioBuffer*)srcs[i].get()); + } + + break; + + case PortType::EVENTS: + dst->clear(); + for (uint32_t i = 0; i < num_srcs; ++i) { + assert(srcs[i]->type() == PortType::EVENTS); + srcs[i]->rewind(); + } + + while (true) { + const EventBuffer* first = NULL; + for (uint32_t i = 0; i < num_srcs; ++i) { + const EventBuffer* const src = (const EventBuffer*)srcs[i].get(); + if (src->is_valid()) { + if (!first || src->get_event()->frames < first->get_event()->frames) + first = src; + } + } + if (first) { + const LV2_Event* const ev = first->get_event(); + ((EventBuffer*)dst)->append( + ev->frames, ev->subframes, ev->type, ev->size, + (const uint8_t*)ev + sizeof(LV2_Event)); + first->increment(); + } else { + break; + } + } + + dst->rewind(); + break; + + default: + if (num_srcs == 1) + dst->copy(context, srcs[0].get()); + else + error << "Mix of unsupported buffer types" << std::endl; + return; + } +} + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_MIX_HPP diff --git a/src/server/types.hpp b/src/server/types.hpp new file mode 100644 index 00000000..1c6b217e --- /dev/null +++ b/src/server/types.hpp @@ -0,0 +1,29 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_TYPES_HPP +#define INGEN_ENGINE_TYPES_HPP + +#include <stdint.h> +#include <cstddef> + +typedef float Sample; +typedef uint32_t SampleCount; +typedef uint32_t SampleRate; +typedef uint32_t FrameTime; + +#endif // INGEN_ENGINE_TYPES_HPP diff --git a/src/server/util.hpp b/src/server/util.hpp new file mode 100644 index 00000000..9aab55ac --- /dev/null +++ b/src/server/util.hpp @@ -0,0 +1,90 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * 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 General Public License for 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 + */ + +#ifndef INGEN_ENGINE_UTIL_HPP +#define INGEN_ENGINE_UTIL_HPP + +#include <cstdlib> +#include <string> + +#include "raul/log.hpp" +#include "raul/Path.hpp" + +#include "ingen-config.h" + +#include <fenv.h> +#ifdef __SSE__ +#include <xmmintrin.h> +#endif + +#ifdef USE_ASSEMBLY +# if SIZEOF_VOID_P==8 +# define cpuid(a,b,c,d,n) asm("xchgq %%rbx, %1; cpuid; xchgq %%rbx, %1": "=a" (a), "=r" (b), "=c" (c), "=d" (d) : "a" (n)); +# else +# define cpuid(a,b,c,d,n) asm("xchgl %%ebx, %1; cpuid; xchgl %%ebx, %1": "=a" (a), "=r" (b), "=c" (c), "=d" (d) : "a" (n)); +# endif +#endif + +namespace Ingen { +namespace Server { + +/** Set flags to disable denormal processing. + */ +inline void +set_denormal_flags() +{ +#ifdef USE_ASSEMBLY +#ifdef __SSE__ + unsigned long a, b, c, d0, d1; + int stepping, model, family, extfamily; + + cpuid(a,b,c,d1,1); + if (d1 & 1<<25) { /* It has SSE support */ + _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); + family = (a >> 8) & 0xf; + extfamily = (a >> 20) & 0xff; + model = (a >> 4) & 0xf; + stepping = a & 0xf; + cpuid(a,b,c,d0,0); + if (b == 0x756e6547) { /* It's an Intel */ + if (family == 15 && extfamily == 0 && model == 0 && stepping < 7) { + return; + } + } + if (d1 & 1<<26) { /* bit 26, SSE2 support */ + _mm_setcsr(_mm_getcsr() | 0x8040); // set DAZ and FZ bits of MXCSR + Raul::info << "Set SSE denormal fix flag" << endl; + } + } else { + Raul::warn << "This code has been built with SSE support, but your processor does" + << " not support the SSE instruction set, exiting." << std::endl; + exit(EXIT_FAILURE); + } +#endif +#endif +} + +static inline std::string +ingen_jack_port_name(const Raul::Path& path) +{ + return path.chop_start("/"); +} + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_UTIL_HPP diff --git a/src/server/wscript b/src/server/wscript new file mode 100644 index 00000000..708f38d2 --- /dev/null +++ b/src/server/wscript @@ -0,0 +1,128 @@ +#!/usr/bin/env python +from waflib.extras import autowaf as autowaf + +def build(bld): + # Headers + bld.install_files('${INCLUDEDIR}/ingen/server', bld.path.ant_glob('*.hpp')) + + core_source = ''' + AudioBuffer.cpp + BufferFactory.cpp + ClientBroadcaster.cpp + ConnectionImpl.cpp + ControlBindings.cpp + DuplexPort.cpp + Engine.cpp + EngineStore.cpp + Event.cpp + EventBuffer.cpp + EventSink.cpp + EventSource.cpp + GraphObjectImpl.cpp + InputPort.cpp + InternalPlugin.cpp + MessageContext.cpp + NodeFactory.cpp + NodeImpl.cpp + ObjectBuffer.cpp + ObjectSender.cpp + OutputPort.cpp + PatchImpl.cpp + PluginImpl.cpp + PortImpl.cpp + PostProcessor.cpp + ProcessContext.cpp + ProcessSlave.cpp + QueuedEngineInterface.cpp + QueuedEvent.cpp + events/Connect.cpp + events/CreateNode.cpp + events/CreatePatch.cpp + events/CreatePort.cpp + events/Delete.cpp + events/Disconnect.cpp + events/DisconnectAll.cpp + events/Get.cpp + events/Move.cpp + events/RegisterClient.cpp + events/RequestMetadata.cpp + events/SendBinding.cpp + events/SendPortActivity.cpp + events/SendPortValue.cpp + events/SetMetadata.cpp + events/SetPortValue.cpp + events/UnregisterClient.cpp + ingen_engine.cpp + internals/Controller.cpp + internals/Delay.cpp + internals/Note.cpp + internals/Trigger.cpp + ''' + + if bld.is_defined('HAVE_SLV2'): + core_source += ' LV2Info.cpp LV2Plugin.cpp LV2Node.cpp ' + + obj = bld(features = 'cxx cxxshlib') + obj.source = core_source + obj.export_includes = ['.'] + obj.includes = ['.', '..', '../..', '../../include'] + obj.name = 'libingen_server' + obj.target = 'ingen_server' + obj.install_path = '${LIBDIR}' + obj.use = 'libingen_shared' + core_libs = 'GLIBMM GTHREAD LV2CORE SLV2 RAUL SORD' + autowaf.use_lib(bld, obj, core_libs) + + if bld.is_defined('HAVE_SOUP'): + obj = bld(features = 'cxx cxxshlib') + obj.source = ''' + EventSource.cpp + QueuedEngineInterface.cpp + HTTPClientSender.cpp + HTTPEngineReceiver.cpp + ingen_http.cpp + ''' + obj.includes = ['.', '..', '../..', '../../include', '../server'] + obj.name = 'libingen_http' + obj.target = 'ingen_http' + obj.install_path = '${LIBDIR}' + autowaf.use_lib(bld, obj, core_libs + ' SOUP') + + if bld.is_defined('HAVE_LIBLO'): + obj = bld(features = 'cxx cxxshlib') + obj.source = ''' + EventSource.cpp + QueuedEngineInterface.cpp + OSCClientSender.cpp + OSCEngineReceiver.cpp + ingen_osc.cpp + ''' + obj.export_includes = ['.'] + obj.includes = ['.', '..', '../..', '../../include', '../server'] + obj.name = 'libingen_osc' + obj.target = 'ingen_osc' + obj.install_path = '${LIBDIR}' + autowaf.use_lib(bld, obj, core_libs + ' LIBLO') + + if bld.is_defined('HAVE_JACK'): + obj = bld(features = 'cxx cxxshlib') + obj.source = 'JackDriver.cpp ingen_jack.cpp' + obj.export_includes = ['.'] + obj.includes = ['.', '..', '../..', '../../include', '../server'] + obj.name = 'libingen_jack' + obj.target = 'ingen_jack' + obj.install_path = '${LIBDIR}' + obj.use = 'libingen_server' + autowaf.use_lib(bld, obj, core_libs + ' JACK') + + # Ingen LV2 wrapper + obj = bld(features = 'cxx cxxshlib') + obj.source = ' ingen_lv2.cpp ' + obj.export_includes = ['.'] + obj.includes = ['.', '..', '../..', '../../include'] + obj.name = 'libingen_lv2' + obj.target = 'ingen_lv2' + obj.install_path = '${LIBDIR}' + obj.use = 'libingen_shared' + obj.add_objects = 'libingen_server' + autowaf.use_lib(bld, obj, core_libs) |