summaryrefslogtreecommitdiffstats
path: root/src/server
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2011-04-20 16:26:40 +0000
committerDavid Robillard <d@drobilla.net>2011-04-20 16:26:40 +0000
commit138a87e915ad3aff184730415105f94c874174bf (patch)
tree0d942bdddfdbcc3d969b4fce5592e770ab851b86 /src/server
parent1f1758f4dda0ddaf01c0b1d3a756f9db8ddc979d (diff)
downloadingen-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')
-rw-r--r--src/server/AudioBuffer.cpp207
-rw-r--r--src/server/AudioBuffer.hpp109
-rw-r--r--src/server/Buffer.hpp97
-rw-r--r--src/server/BufferFactory.cpp163
-rw-r--r--src/server/BufferFactory.hpp92
-rw-r--r--src/server/ClientBroadcaster.cpp116
-rw-r--r--src/server/ClientBroadcaster.hpp131
-rw-r--r--src/server/CompiledPatch.hpp76
-rw-r--r--src/server/ConnectionImpl.cpp153
-rw-r--r--src/server/ConnectionImpl.hpp110
-rw-r--r--src/server/Context.hpp107
-rw-r--r--src/server/ControlBindings.cpp384
-rw-r--r--src/server/ControlBindings.hpp102
-rw-r--r--src/server/Driver.hpp123
-rw-r--r--src/server/DuplexPort.cpp99
-rw-r--r--src/server/DuplexPort.hpp70
-rw-r--r--src/server/Engine.cpp225
-rw-r--r--src/server/Engine.hpp115
-rw-r--r--src/server/EngineStore.cpp160
-rw-r--r--src/server/EngineStore.hpp65
-rw-r--r--src/server/Event.cpp56
-rw-r--r--src/server/Event.hpp80
-rw-r--r--src/server/EventBuffer.cpp215
-rw-r--r--src/server/EventBuffer.hpp86
-rw-r--r--src/server/EventSink.cpp62
-rw-r--r--src/server/EventSink.hpp59
-rw-r--r--src/server/EventSource.cpp127
-rw-r--r--src/server/EventSource.hpp73
-rw-r--r--src/server/GraphObjectImpl.cpp73
-rw-r--r--src/server/GraphObjectImpl.hpp113
-rw-r--r--src/server/HTTPClientSender.cpp146
-rw-r--r--src/server/HTTPClientSender.hpp107
-rw-r--r--src/server/HTTPEngineReceiver.cpp230
-rw-r--r--src/server/HTTPEngineReceiver.hpp64
-rw-r--r--src/server/InputPort.cpp229
-rw-r--r--src/server/InputPort.hpp93
-rw-r--r--src/server/InternalPlugin.cpp72
-rw-r--r--src/server/InternalPlugin.hpp63
-rw-r--r--src/server/JackDriver.cpp561
-rw-r--r--src/server/JackDriver.hpp183
-rw-r--r--src/server/LV2BlobFeature.hpp66
-rw-r--r--src/server/LV2EventFeature.hpp54
-rw-r--r--src/server/LV2Info.cpp77
-rw-r--r--src/server/LV2Info.hpp62
-rw-r--r--src/server/LV2Node.cpp410
-rw-r--r--src/server/LV2Node.hpp82
-rw-r--r--src/server/LV2Plugin.cpp110
-rw-r--r--src/server/LV2Plugin.hpp76
-rw-r--r--src/server/LV2RequestRunFeature.hpp84
-rw-r--r--src/server/LV2ResizeFeature.hpp73
-rw-r--r--src/server/MessageContext.cpp126
-rw-r--r--src/server/MessageContext.hpp114
-rw-r--r--src/server/NodeFactory.cpp148
-rw-r--r--src/server/NodeFactory.hpp76
-rw-r--r--src/server/NodeImpl.cpp264
-rw-r--r--src/server/NodeImpl.hpp223
-rw-r--r--src/server/OSCClientSender.cpp247
-rw-r--r--src/server/OSCClientSender.hpp108
-rw-r--r--src/server/OSCEngineReceiver.cpp586
-rw-r--r--src/server/OSCEngineReceiver.hpp118
-rw-r--r--src/server/ObjectBuffer.cpp147
-rw-r--r--src/server/ObjectBuffer.hpp56
-rw-r--r--src/server/ObjectSender.cpp149
-rw-r--r--src/server/ObjectSender.hpp66
-rw-r--r--src/server/OutputPort.cpp76
-rw-r--r--src/server/OutputPort.hpp65
-rw-r--r--src/server/PatchImpl.cpp470
-rw-r--r--src/server/PatchImpl.hpp165
-rw-r--r--src/server/PatchPlugin.hpp67
-rw-r--r--src/server/PluginImpl.cpp50
-rw-r--r--src/server/PluginImpl.hpp88
-rw-r--r--src/server/PortImpl.cpp251
-rw-r--r--src/server/PortImpl.hpp174
-rw-r--r--src/server/PostProcessor.cpp92
-rw-r--r--src/server/PostProcessor.hpp68
-rw-r--r--src/server/ProcessContext.cpp38
-rw-r--r--src/server/ProcessContext.hpp54
-rw-r--r--src/server/ProcessSlave.cpp73
-rw-r--r--src/server/ProcessSlave.hpp104
-rw-r--r--src/server/QueuedEngineInterface.cpp223
-rw-r--r--src/server/QueuedEngineInterface.hpp116
-rw-r--r--src/server/QueuedEvent.cpp49
-rw-r--r--src/server/QueuedEvent.hpp77
-rw-r--r--src/server/Request.hpp81
-rw-r--r--src/server/ThreadManager.hpp56
-rw-r--r--src/server/events.hpp41
-rw-r--r--src/server/events/Connect.cpp204
-rw-r--r--src/server/events/Connect.hpp88
-rw-r--r--src/server/events/CreateNode.cpp146
-rw-r--r--src/server/events/CreateNode.hpp71
-rw-r--r--src/server/events/CreatePatch.cpp164
-rw-r--r--src/server/events/CreatePatch.hpp67
-rw-r--r--src/server/events/CreatePort.cpp192
-rw-r--r--src/server/events/CreatePort.hpp81
-rw-r--r--src/server/events/Deactivate.hpp49
-rw-r--r--src/server/events/Delete.cpp213
-rw-r--r--src/server/events/Delete.hpp95
-rw-r--r--src/server/events/Disconnect.cpp269
-rw-r--r--src/server/events/Disconnect.hpp106
-rw-r--r--src/server/events/DisconnectAll.cpp189
-rw-r--r--src/server/events/DisconnectAll.hpp93
-rw-r--r--src/server/events/Get.cpp84
-rw-r--r--src/server/events/Get.hpp60
-rw-r--r--src/server/events/Move.cpp130
-rw-r--r--src/server/events/Move.hpp79
-rw-r--r--src/server/events/Ping.hpp51
-rw-r--r--src/server/events/RegisterClient.cpp57
-rw-r--r--src/server/events/RegisterClient.hpp54
-rw-r--r--src/server/events/RequestMetadata.cpp137
-rw-r--r--src/server/events/RequestMetadata.hpp79
-rw-r--r--src/server/events/SendBinding.cpp55
-rw-r--r--src/server/events/SendBinding.hpp86
-rw-r--r--src/server/events/SendPortActivity.cpp36
-rw-r--r--src/server/events/SendPortActivity.hpp69
-rw-r--r--src/server/events/SendPortValue.cpp42
-rw-r--r--src/server/events/SendPortValue.hpp82
-rw-r--r--src/server/events/SetMetadata.cpp380
-rw-r--r--src/server/events/SetMetadata.hpp124
-rw-r--r--src/server/events/SetPortValue.cpp223
-rw-r--r--src/server/events/SetPortValue.hpp83
-rw-r--r--src/server/events/UnregisterClient.cpp48
-rw-r--r--src/server/events/UnregisterClient.hpp50
-rw-r--r--src/server/ingen_engine.cpp48
-rw-r--r--src/server/ingen_http.cpp45
-rw-r--r--src/server/ingen_jack.cpp45
-rw-r--r--src/server/ingen_lv2.cpp422
-rw-r--r--src/server/ingen_osc.cpp46
-rw-r--r--src/server/internals/Controller.cpp147
-rw-r--r--src/server/internals/Controller.hpp74
-rw-r--r--src/server/internals/Delay.cpp208
-rw-r--r--src/server/internals/Delay.hpp78
-rw-r--r--src/server/internals/Note.cpp416
-rw-r--r--src/server/internals/Note.hpp101
-rw-r--r--src/server/internals/Trigger.cpp168
-rw-r--r--src/server/internals/Trigger.hpp77
-rw-r--r--src/server/mix.hpp91
-rw-r--r--src/server/types.hpp29
-rw-r--r--src/server/util.hpp90
-rw-r--r--src/server/wscript128
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)