summaryrefslogtreecommitdiffstats
path: root/src/engine
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2008-09-30 16:50:21 +0000
committerDavid Robillard <d@drobilla.net>2008-09-30 16:50:21 +0000
commit93850c202de8b073a1ce1dd8bd246d407bce4e2f (patch)
tree6910b135bf4eff12de1af116cef73f6e9c107cd0 /src/engine
parenta8bf5272d096de73507d2eab47f282c345f4ca8a (diff)
downloadingen-93850c202de8b073a1ce1dd8bd246d407bce4e2f.tar.gz
ingen-93850c202de8b073a1ce1dd8bd246d407bce4e2f.tar.bz2
ingen-93850c202de8b073a1ce1dd8bd246d407bce4e2f.zip
Flatten ingen source directory heirarchy a bit.
git-svn-id: http://svn.drobilla.net/lad/trunk/ingen@1551 a436a847-0d15-0410-975c-d299462d15a1
Diffstat (limited to 'src/engine')
-rw-r--r--src/engine/AudioBuffer.cpp299
-rw-r--r--src/engine/AudioBuffer.hpp84
-rw-r--r--src/engine/AudioDriver.hpp62
-rw-r--r--src/engine/Buffer.cpp38
-rw-r--r--src/engine/Buffer.hpp76
-rw-r--r--src/engine/ClientBroadcaster.cpp271
-rw-r--r--src/engine/ClientBroadcaster.hpp97
-rw-r--r--src/engine/CompiledPatch.hpp83
-rw-r--r--src/engine/ConnectionImpl.cpp185
-rw-r--r--src/engine/ConnectionImpl.hpp101
-rw-r--r--src/engine/Context.hpp51
-rw-r--r--src/engine/Driver.hpp98
-rw-r--r--src/engine/DuplexPort.cpp108
-rw-r--r--src/engine/DuplexPort.hpp68
-rw-r--r--src/engine/Engine.cpp317
-rw-r--r--src/engine/Engine.hpp130
-rw-r--r--src/engine/EngineStore.cpp182
-rw-r--r--src/engine/EngineStore.hpp68
-rw-r--r--src/engine/Event.cpp49
-rw-r--r--src/engine/Event.hpp75
-rw-r--r--src/engine/EventBuffer.cpp318
-rw-r--r--src/engine/EventBuffer.hpp104
-rw-r--r--src/engine/EventSink.cpp74
-rw-r--r--src/engine/EventSink.hpp64
-rw-r--r--src/engine/EventSource.hpp60
-rw-r--r--src/engine/GraphObjectImpl.cpp39
-rw-r--r--src/engine/GraphObjectImpl.hpp132
-rw-r--r--src/engine/HTTPEngineReceiver.cpp207
-rw-r--r--src/engine/HTTPEngineReceiver.hpp59
-rw-r--r--src/engine/InputPort.cpp297
-rw-r--r--src/engine/InputPort.hpp88
-rw-r--r--src/engine/InternalPlugin.cpp55
-rw-r--r--src/engine/InternalPlugin.hpp74
-rw-r--r--src/engine/JackAudioDriver.cpp384
-rw-r--r--src/engine/JackAudioDriver.hpp185
-rw-r--r--src/engine/JackMidiDriver.cpp267
-rw-r--r--src/engine/JackMidiDriver.hpp113
-rw-r--r--src/engine/LADSPANode.cpp373
-rw-r--r--src/engine/LADSPANode.hpp74
-rw-r--r--src/engine/LADSPAPlugin.cpp79
-rw-r--r--src/engine/LADSPAPlugin.hpp78
-rw-r--r--src/engine/LV2Info.cpp70
-rw-r--r--src/engine/LV2Info.hpp66
-rw-r--r--src/engine/LV2Node.cpp305
-rw-r--r--src/engine/LV2Node.hpp75
-rw-r--r--src/engine/LV2Plugin.cpp94
-rw-r--r--src/engine/LV2Plugin.hpp84
-rw-r--r--src/engine/Makefile.am205
-rw-r--r--src/engine/MessageContext.cpp32
-rw-r--r--src/engine/MessageContext.hpp51
-rw-r--r--src/engine/MidiControlNode.cpp141
-rw-r--r--src/engine/MidiControlNode.hpp65
-rw-r--r--src/engine/MidiDriver.hpp100
-rw-r--r--src/engine/MidiNoteNode.cpp390
-rw-r--r--src/engine/MidiNoteNode.hpp88
-rw-r--r--src/engine/MidiTriggerNode.cpp135
-rw-r--r--src/engine/MidiTriggerNode.hpp61
-rw-r--r--src/engine/NodeBase.cpp230
-rw-r--r--src/engine/NodeBase.hpp132
-rw-r--r--src/engine/NodeFactory.cpp285
-rw-r--r--src/engine/NodeFactory.hpp92
-rw-r--r--src/engine/NodeImpl.hpp170
-rw-r--r--src/engine/OSCClientSender.cpp358
-rw-r--r--src/engine/OSCClientSender.hpp136
-rw-r--r--src/engine/OSCDriver.hpp82
-rw-r--r--src/engine/OSCEngineReceiver.cpp884
-rw-r--r--src/engine/OSCEngineReceiver.hpp129
-rw-r--r--src/engine/ObjectSender.cpp150
-rw-r--r--src/engine/ObjectSender.hpp57
-rw-r--r--src/engine/OmInProcess.cpp66
-rw-r--r--src/engine/OutputPort.cpp62
-rw-r--r--src/engine/OutputPort.hpp63
-rw-r--r--src/engine/PatchImpl.cpp481
-rw-r--r--src/engine/PatchImpl.hpp168
-rw-r--r--src/engine/PatchPlugin.hpp64
-rw-r--r--src/engine/PluginImpl.cpp54
-rw-r--r--src/engine/PluginImpl.hpp106
-rw-r--r--src/engine/PortImpl.cpp190
-rw-r--r--src/engine/PortImpl.hpp147
-rw-r--r--src/engine/PostProcessor.cpp73
-rw-r--r--src/engine/PostProcessor.hpp73
-rw-r--r--src/engine/ProcessContext.hpp71
-rw-r--r--src/engine/ProcessSlave.cpp75
-rw-r--r--src/engine/ProcessSlave.hpp100
-rw-r--r--src/engine/QueuedEngineInterface.cpp370
-rw-r--r--src/engine/QueuedEngineInterface.hpp170
-rw-r--r--src/engine/QueuedEvent.cpp50
-rw-r--r--src/engine/QueuedEvent.hpp83
-rw-r--r--src/engine/QueuedEventSource.cpp183
-rw-r--r--src/engine/QueuedEventSource.hpp128
-rw-r--r--src/engine/Responder.hpp71
-rw-r--r--src/engine/ThreadManager.hpp43
-rw-r--r--src/engine/TransportNode.cpp154
-rw-r--r--src/engine/TransportNode.hpp45
-rw-r--r--src/engine/engine.cpp56
-rw-r--r--src/engine/engine.hpp42
-rw-r--r--src/engine/events.hpp53
-rw-r--r--src/engine/events/AllNotesOffEvent.cpp71
-rw-r--r--src/engine/events/AllNotesOffEvent.hpp51
-rw-r--r--src/engine/events/ClearPatchEvent.cpp133
-rw-r--r--src/engine/events/ClearPatchEvent.hpp65
-rw-r--r--src/engine/events/ConnectionEvent.cpp202
-rw-r--r--src/engine/events/ConnectionEvent.hpp92
-rw-r--r--src/engine/events/CreateNodeEvent.cpp151
-rw-r--r--src/engine/events/CreateNodeEvent.hpp81
-rw-r--r--src/engine/events/CreatePatchEvent.cpp157
-rw-r--r--src/engine/events/CreatePatchEvent.hpp64
-rw-r--r--src/engine/events/CreatePortEvent.cpp173
-rw-r--r--src/engine/events/CreatePortEvent.hpp72
-rw-r--r--src/engine/events/DeactivateEvent.cpp54
-rw-r--r--src/engine/events/DeactivateEvent.hpp43
-rw-r--r--src/engine/events/DestroyEvent.cpp201
-rw-r--r--src/engine/events/DestroyEvent.hpp77
-rw-r--r--src/engine/events/DisablePortMonitoringEvent.cpp87
-rw-r--r--src/engine/events/DisablePortMonitoringEvent.hpp58
-rw-r--r--src/engine/events/DisconnectAllEvent.cpp183
-rw-r--r--src/engine/events/DisconnectAllEvent.hpp79
-rw-r--r--src/engine/events/DisconnectionEvent.cpp213
-rw-r--r--src/engine/events/DisconnectionEvent.hpp90
-rw-r--r--src/engine/events/EnablePatchEvent.cpp86
-rw-r--r--src/engine/events/EnablePatchEvent.hpp63
-rw-r--r--src/engine/events/LoadPluginsEvent.cpp56
-rw-r--r--src/engine/events/LoadPluginsEvent.hpp46
-rw-r--r--src/engine/events/Makefile.am63
-rw-r--r--src/engine/events/MidiLearnEvent.cpp89
-rw-r--r--src/engine/events/MidiLearnEvent.hpp85
-rw-r--r--src/engine/events/NoteEvent.cpp102
-rw-r--r--src/engine/events/NoteEvent.hpp68
-rw-r--r--src/engine/events/PingQueuedEvent.hpp48
-rw-r--r--src/engine/events/RegisterClientEvent.cpp55
-rw-r--r--src/engine/events/RegisterClientEvent.hpp55
-rw-r--r--src/engine/events/RenameEvent.cpp152
-rw-r--r--src/engine/events/RenameEvent.hpp64
-rw-r--r--src/engine/events/RequestAllObjectsEvent.cpp59
-rw-r--r--src/engine/events/RequestAllObjectsEvent.hpp48
-rw-r--r--src/engine/events/RequestMetadataEvent.cpp85
-rw-r--r--src/engine/events/RequestMetadataEvent.hpp62
-rw-r--r--src/engine/events/RequestObjectEvent.cpp98
-rw-r--r--src/engine/events/RequestObjectEvent.hpp55
-rw-r--r--src/engine/events/RequestPluginEvent.cpp79
-rw-r--r--src/engine/events/RequestPluginEvent.hpp53
-rw-r--r--src/engine/events/RequestPluginsEvent.cpp57
-rw-r--r--src/engine/events/RequestPluginsEvent.hpp47
-rw-r--r--src/engine/events/RequestPortValueEvent.cpp81
-rw-r--r--src/engine/events/RequestPortValueEvent.hpp56
-rw-r--r--src/engine/events/SendPortActivityEvent.cpp34
-rw-r--r--src/engine/events/SendPortActivityEvent.hpp67
-rw-r--r--src/engine/events/SendPortValueEvent.cpp43
-rw-r--r--src/engine/events/SendPortValueEvent.hpp78
-rw-r--r--src/engine/events/SetMetadataEvent.cpp114
-rw-r--r--src/engine/events/SetMetadataEvent.hpp65
-rw-r--r--src/engine/events/SetPolyphonicEvent.cpp82
-rw-r--r--src/engine/events/SetPolyphonicEvent.hpp56
-rw-r--r--src/engine/events/SetPolyphonyEvent.cpp79
-rw-r--r--src/engine/events/SetPolyphonyEvent.hpp56
-rw-r--r--src/engine/events/SetPortValueEvent.cpp224
-rw-r--r--src/engine/events/SetPortValueEvent.hpp80
-rw-r--r--src/engine/events/UnregisterClientEvent.cpp45
-rw-r--r--src/engine/events/UnregisterClientEvent.hpp54
-rw-r--r--src/engine/jack_compat.h56
l---------src/engine/lv2_contexts.h1
-rw-r--r--src/engine/tuning.hpp39
-rw-r--r--src/engine/types.hpp33
-rw-r--r--src/engine/util.hpp78
-rw-r--r--src/engine/wscript73
165 files changed, 19040 insertions, 0 deletions
diff --git a/src/engine/AudioBuffer.cpp b/src/engine/AudioBuffer.cpp
new file mode 100644
index 00000000..098103c0
--- /dev/null
+++ b/src/engine/AudioBuffer.cpp
@@ -0,0 +1,299 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 <iostream>
+#include <cassert>
+#include <stdlib.h>
+#include "AudioBuffer.hpp"
+
+using namespace std;
+
+/* TODO: Be sure these functions are vectorized by GCC when it's vectorizer
+ * stops sucking. Probably a good idea to inline them as well */
+
+namespace Ingen {
+
+
+AudioBuffer::AudioBuffer(size_t size)
+ : Buffer((size == 1) ? DataType::CONTROL : DataType::AUDIO, size)
+ , _data(NULL)
+ , _local_data(NULL)
+ , _size(size)
+ , _filled_size(0)
+ , _state(OK)
+ , _set_value(0)
+ , _set_time(0)
+{
+ assert(_size > 0);
+ allocate();
+ assert(data());
+}
+
+
+void
+AudioBuffer::resize(size_t size)
+{
+ _size = size;
+
+ Sample* const old_data = _data;
+
+ const bool using_local_data = (_data == _local_data);
+
+ deallocate();
+
+ const int ret = posix_memalign((void**)&_local_data, 16, _size * sizeof(Sample));
+ if (ret != 0) {
+ cerr << "[Buffer] Failed to allocate buffer. Aborting." << endl;
+ exit(EXIT_FAILURE);
+ }
+
+ assert(ret == 0);
+ assert(_local_data);
+
+ if (using_local_data)
+ _data = _local_data;
+ else
+ _data = old_data;
+
+ set_block(0, 0, _size-1);
+}
+
+
+/** Allocate and use a locally managed buffer (data).
+ */
+void
+AudioBuffer::allocate()
+{
+ assert(!_joined_buf);
+ assert(_local_data == NULL);
+ assert(_size > 0);
+
+ const int ret = posix_memalign((void**)&_local_data, 16, _size * sizeof(Sample));
+ if (ret != 0) {
+ cerr << "[Buffer] Failed to allocate buffer. Aborting." << endl;
+ exit(EXIT_FAILURE);
+ }
+
+ assert(ret == 0);
+ assert(_local_data);
+
+ _data = _local_data;
+
+ set_block(0, 0, _size-1);
+}
+
+
+/** Free locally allocated buffer.
+ */
+void
+AudioBuffer::deallocate()
+{
+ assert(!_joined_buf);
+ free(_local_data);
+ _local_data = NULL;
+ _data = NULL;
+}
+
+
+/** Empty (ie zero) the buffer.
+ */
+void
+AudioBuffer::clear()
+{
+ set_block(0, 0, _size-1);
+ _state = OK;
+ _filled_size = 0;
+}
+
+
+/** 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 (_size == 1)
+ time = cycle_start;
+
+ FrameTime offset = time - cycle_start;
+ assert(offset <= _size);
+
+ if (offset < _size) {
+ set_block(val, offset, _size - 1);
+
+ if (offset > 0)
+ _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 < _size);
+
+ Sample* const buf = data();
+ assert(buf);
+
+ for (size_t i = start_offset; i <= end_offset; ++i)
+ buf[i] = val;
+}
+
+
+/** Scale a block of buffer by @a val.
+ *
+ * @a start_sample and @a end_sample define the inclusive range to be set.
+ */
+void
+AudioBuffer::scale(Sample val, size_t start_sample, size_t end_sample)
+{
+ assert(end_sample >= start_sample);
+ assert(end_sample < _size);
+
+ Sample* const buf = data();
+ assert(buf);
+
+ for (size_t i=start_sample; i <= end_sample; ++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 Buffer* src, size_t start_sample, size_t end_sample)
+{
+ assert(end_sample >= start_sample);
+ assert(end_sample < _size);
+ assert(src);
+ assert(src->type() == DataType::CONTROL || DataType::AUDIO);
+
+ Sample* const buf = data();
+ assert(buf);
+
+ const Sample* const src_buf = ((AudioBuffer*)src)->data();
+ assert(src_buf);
+
+ for (size_t i=start_sample; i <= end_sample; ++i)
+ buf[i] = src_buf[i];
+}
+
+
+/** Accumulate a block of @a src into @a dst.
+ *
+ * @a start_sample and @a end_sample define the inclusive range to be accumulated.
+ * This function only adds the same range in one buffer to another.
+ */
+void
+AudioBuffer::accumulate(const AudioBuffer* const src, size_t start_sample, size_t end_sample)
+{
+ assert(end_sample >= start_sample);
+ assert(end_sample < _size);
+ assert(src);
+
+ Sample* const buf = data();
+ assert(buf);
+
+ const Sample* const src_buf = src->data();
+ assert(src_buf);
+
+ for (size_t i=start_sample; i <= end_sample; ++i)
+ buf[i] += src_buf[i];
+
+}
+
+
+/** Use another buffer's data instead of the local one.
+ *
+ * This buffer will essentially be identical to @a buf after this call.
+ */
+bool
+AudioBuffer::join(Buffer* buf)
+{
+ AudioBuffer* abuf = dynamic_cast<AudioBuffer*>(buf);
+ if (!abuf)
+ return false;
+
+ assert(abuf->size() >= _size);
+
+ _joined_buf = abuf;
+ _filled_size = abuf->filled_size();
+
+ assert(_filled_size <= _size);
+
+ return true;
+}
+
+
+void
+AudioBuffer::unjoin()
+{
+ _joined_buf = NULL;
+ _data = _local_data;
+}
+
+
+void
+AudioBuffer::prepare_read(FrameTime start, SampleCount nframes)
+{
+ // FIXME: nframes parameter doesn't actually work,
+ // writing starts from 0 every time
+ assert(_size == 1 || nframes == _size);
+
+ switch (_state) {
+ case HALF_SET_CYCLE_1:
+ if (start > _set_time)
+ _state = HALF_SET_CYCLE_2;
+ break;
+ case HALF_SET_CYCLE_2:
+ set_block(_set_value, 0, _size-1);
+ _state = OK;
+ break;
+ default:
+ break;
+ }
+}
+
+
+/** Set the buffer (data) used.
+ *
+ * This is only to be used by Drivers (to provide zero-copy processing).
+ */
+void
+AudioBuffer::set_data(Sample* buf)
+{
+ assert(buf);
+ assert(!_joined_buf);
+ _data = buf;
+}
+
+
+} // namespace Ingen
diff --git a/src/engine/AudioBuffer.hpp b/src/engine/AudioBuffer.hpp
new file mode 100644
index 00000000..513c188f
--- /dev/null
+++ b/src/engine/AudioBuffer.hpp
@@ -0,0 +1,84 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 AUDIOBUFFER_H
+#define AUDIOBUFFER_H
+
+#include <cstddef>
+#include <cassert>
+#include <boost/utility.hpp>
+#include "types.hpp"
+#include "Buffer.hpp"
+
+namespace Ingen {
+
+
+class AudioBuffer : public Buffer
+{
+public:
+ AudioBuffer(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 scale(Sample val, size_t start_sample, size_t end_sample);
+ void copy(const Buffer* src, size_t start_sample, size_t end_sample);
+ void accumulate(const AudioBuffer* src, size_t start_sample, size_t end_sample);
+
+ bool join(Buffer* buf);
+ void unjoin();
+
+ /** For driver use only!! */
+ void set_data(Sample* data);
+
+ inline const void* raw_data() const { return _data; }
+ inline void* raw_data() { return _data; }
+
+ inline Sample* data() const { return _data; }
+
+ inline Sample& value_at(size_t offset) const
+ { assert(offset < _size); return data()[offset]; }
+
+ void prepare_read(FrameTime start, SampleCount nframes);
+ void prepare_write(FrameTime start, SampleCount nframes) {}
+
+ void rewind() const {}
+ void resize(size_t size);
+
+ void filled_size(size_t size) { _filled_size = size; }
+ size_t filled_size() const { return _filled_size; }
+ size_t size() const { return _size; }
+
+private:
+ enum State { OK, HALF_SET_CYCLE_1, HALF_SET_CYCLE_2 };
+
+ void allocate();
+ void deallocate();
+
+ Sample* _data; ///< Used data pointer (probably same as _local_data)
+ Sample* _local_data; ///< Locally allocated buffer (possibly unused if joined or set_data used)
+ size_t _size; ///< Allocated buffer size
+ size_t _filled_size; ///< Usable buffer size (for MIDI ports etc)
+ State _state; ///< State of buffer for setting values next cycle
+ Sample _set_value; ///< Value set by @ref set (may need to be set next cycle)
+ FrameTime _set_time; ///< Time _set_value was set (to reset next cycle)
+};
+
+
+} // namespace Ingen
+
+#endif // AUDIOBUFFER_H
diff --git a/src/engine/AudioDriver.hpp b/src/engine/AudioDriver.hpp
new file mode 100644
index 00000000..6b5ea8dc
--- /dev/null
+++ b/src/engine/AudioDriver.hpp
@@ -0,0 +1,62 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 AUDIODRIVER_H
+#define AUDIODRIVER_H
+
+#include <raul/List.hpp>
+#include <raul/Path.hpp>
+#include "Driver.hpp"
+#include "types.hpp"
+#include "interface/DataType.hpp"
+
+namespace Ingen {
+
+class PatchImpl;
+class AudioDriver;
+class PortImpl;
+class ProcessContext;
+
+
+/** Audio driver abstract base class.
+ *
+ * \ingroup engine
+ */
+class AudioDriver : public Driver
+{
+public:
+ AudioDriver() : Driver(DataType::AUDIO) {}
+
+ virtual void set_root_patch(PatchImpl* patch) = 0;
+ virtual PatchImpl* root_patch() = 0;
+
+ virtual void add_port(DriverPort* port) = 0;
+ virtual DriverPort* remove_port(const Raul::Path& path) = 0;
+
+ virtual SampleCount buffer_size() const = 0;
+ virtual SampleCount sample_rate() const = 0;
+ virtual SampleCount frame_time() const = 0;
+
+ virtual bool is_realtime() const = 0;
+
+ virtual ProcessContext& context() = 0;
+};
+
+
+} // namespace Ingen
+
+#endif // AUDIODRIVER_H
diff --git a/src/engine/Buffer.cpp b/src/engine/Buffer.cpp
new file mode 100644
index 00000000..d019146c
--- /dev/null
+++ b/src/engine/Buffer.cpp
@@ -0,0 +1,38 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "AudioBuffer.hpp"
+#include "EventBuffer.hpp"
+
+namespace Ingen {
+
+
+Buffer*
+Buffer::create(DataType type, size_t size)
+{
+ if (type.is_control())
+ return new AudioBuffer(1);
+ else if (type.is_audio())
+ return new AudioBuffer(size);
+ else if (type.is_event())
+ return new EventBuffer(size);
+ else
+ throw;
+}
+
+
+} // namespace Ingen
diff --git a/src/engine/Buffer.hpp b/src/engine/Buffer.hpp
new file mode 100644
index 00000000..e388e2e8
--- /dev/null
+++ b/src/engine/Buffer.hpp
@@ -0,0 +1,76 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 BUFFER_H
+#define BUFFER_H
+
+#include <cstddef>
+#include <cassert>
+#include <boost/utility.hpp>
+#include <raul/Deletable.hpp>
+#include "types.hpp"
+#include "interface/DataType.hpp"
+
+namespace Ingen {
+
+
+class Buffer : public boost::noncopyable, public Raul::Deletable
+{
+public:
+ Buffer(Shared::DataType type, size_t size)
+ : _type(type)
+ , _size(size)
+ , _joined_buf(NULL)
+ {}
+
+ static Buffer* create(Shared::DataType type, size_t size);
+
+ /** Clear contents and reset state */
+ virtual void clear() = 0;
+
+ virtual void* raw_data() = 0;
+ virtual const void* raw_data() const = 0;
+
+ /** Rewind (ie reset read pointer), but leave contents unchanged */
+ virtual void rewind() const = 0;
+
+ virtual void prepare_read(FrameTime start, SampleCount nframes) = 0;
+ virtual void prepare_write(FrameTime start, SampleCount nframes) = 0;
+
+ bool is_joined() const { return (_joined_buf != NULL); }
+ Buffer* joined_buffer() const { return _joined_buf; }
+
+ virtual bool join(Buffer* buf) = 0;
+ virtual void unjoin() = 0;
+
+ virtual void copy(const Buffer* src, size_t start_sample, size_t end_sample) = 0;
+
+ virtual void resize(size_t size) { _size = size; }
+
+ Shared::DataType type() const { return _type; }
+ size_t size() const { return _size; }
+
+protected:
+ Shared::DataType _type;
+ size_t _size;
+ Buffer* _joined_buf;
+};
+
+
+} // namespace Ingen
+
+#endif // BUFFER_H
diff --git a/src/engine/ClientBroadcaster.cpp b/src/engine/ClientBroadcaster.cpp
new file mode 100644
index 00000000..d754f072
--- /dev/null
+++ b/src/engine/ClientBroadcaster.cpp
@@ -0,0 +1,271 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 <iostream>
+#include <unistd.h>
+#include "interface/ClientInterface.hpp"
+#include "ClientBroadcaster.hpp"
+#include "EngineStore.hpp"
+#include "NodeFactory.hpp"
+#include "util.hpp"
+#include "PatchImpl.hpp"
+#include "NodeImpl.hpp"
+#include "PluginImpl.hpp"
+#include "PortImpl.hpp"
+#include "ConnectionImpl.hpp"
+#include "AudioDriver.hpp"
+#include "ObjectSender.hpp"
+#include "OSCClientSender.hpp"
+
+using namespace std;
+using Ingen::Shared::ClientInterface;
+
+namespace Ingen {
+
+
+/** Register a client to receive messages over the notification band.
+ */
+void
+ClientBroadcaster::register_client(const string& uri, ClientInterface* client)
+{
+ Clients::iterator i = _clients.find(uri);
+
+ if (i == _clients.end()) {
+ _clients[uri] = client;
+ cout << "[ClientBroadcaster] Registered client: " << uri << endl;
+ } else {
+ cout << "[ClientBroadcaster] 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 string& uri)
+{
+ size_t erased = _clients.erase(uri);
+
+ if (erased > 0)
+ cout << "Unregistered client: " << uri << endl;
+ else
+ cout << "Failed to find client to unregister: " << uri << endl;
+
+ return (erased > 0);
+}
+
+
+
+/** Looks up the client with the given @a source address (which is used as the
+ * unique identifier for registered clients).
+ *
+ * (A responder is passed to remove the dependency on liblo addresses in request
+ * events, in anticipation of libom and multiple ways of responding to clients).
+ */
+ClientInterface*
+ClientBroadcaster::client(const string& uri)
+{
+ Clients::iterator i = _clients.find(uri);
+ if (i != _clients.end()) {
+ return (*i).second;
+ } else {
+ return NULL;
+ }
+}
+
+
+void
+ClientBroadcaster::bundle_begin()
+{
+ for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i)
+ (*i).second->bundle_begin();
+}
+
+
+void
+ClientBroadcaster::bundle_end()
+{
+ for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i)
+ (*i).second->bundle_end();
+}
+
+
+void
+ClientBroadcaster::send_error(const string& msg)
+{
+ for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i)
+ (*i).second->error(msg);
+}
+
+void
+ClientBroadcaster::send_plugins_to(ClientInterface* client, const NodeFactory::Plugins& plugins)
+{
+ client->transfer_begin();
+
+ for (NodeFactory::Plugins::const_iterator i = plugins.begin(); i != plugins.end(); ++i) {
+ const PluginImpl* const plugin = i->second;
+ client->new_plugin(plugin->uri(), plugin->type_uri(), plugin->symbol(), plugin->name());
+ }
+
+ client->transfer_end();
+}
+
+
+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_node(const NodeImpl* node, bool recursive)
+{
+ for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i)
+ ObjectSender::send_node((*i).second, node, recursive);
+}
+
+
+void
+ClientBroadcaster::send_port(const PortImpl* port)
+{
+ for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i)
+ ObjectSender::send_port((*i).second, port);
+}
+
+
+void
+ClientBroadcaster::send_destroyed(const string& path)
+{
+ assert(path != "/");
+ for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i)
+ (*i).second->destroy(path);
+}
+
+
+void
+ClientBroadcaster::send_patch_cleared(const string& patch_path)
+{
+ for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i)
+ (*i).second->patch_cleared(patch_path);
+}
+
+void
+ClientBroadcaster::send_connection(const SharedPtr<const ConnectionImpl> c)
+{
+ for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i)
+ (*i).second->connect(c->src_port()->path(), c->dst_port()->path());
+}
+
+
+void
+ClientBroadcaster::send_disconnection(const string& src_port_path, const string& dst_port_path)
+{
+ for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i)
+ (*i).second->disconnect(src_port_path, dst_port_path);
+}
+
+
+/** Send notification of a variable update.
+ *
+ * Like control changes, does not send update to client that set the variable, if applicable.
+ */
+void
+ClientBroadcaster::send_variable_change(const string& node_path, const string& key, const Atom& value)
+{
+ for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i)
+ (*i).second->set_variable(node_path, key, value);
+}
+
+
+/** Send notification of a property update.
+ *
+ * Like control changes, does not send update to client that set the property, if applicable.
+ */
+void
+ClientBroadcaster::send_property_change(const string& node_path, const string& key, const Atom& value)
+{
+ for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i)
+ (*i).second->set_property(node_path, key, value);
+}
+
+
+/** Send notification of a control change.
+ *
+ * If responder is specified, the notification will not be send to the address of
+ * that responder (to avoid sending redundant information back to clients and
+ * forcing clients to ignore things to avoid feedback loops etc).
+ */
+void
+ClientBroadcaster::send_port_value(const string& port_path, const Raul::Atom& value)
+{
+ for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i)
+ (*i).second->set_port_value(port_path, value);
+}
+
+
+void
+ClientBroadcaster::send_port_activity(const string& port_path)
+{
+ for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i)
+ (*i).second->port_activity(port_path);
+}
+
+
+void
+ClientBroadcaster::send_program_add(const string& node_path, int bank, int program, const string& name)
+{
+ for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i)
+ (*i).second->program_add(node_path, bank, program, name);
+}
+
+
+void
+ClientBroadcaster::send_program_remove(const string& node_path, int bank, int program)
+{
+ for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i)
+ (*i).second->program_remove(node_path, bank, program);
+}
+
+
+/** Send a patch.
+ *
+ * Sends all objects underneath Patch - contained Nodes, etc.
+ */
+void
+ClientBroadcaster::send_patch(const PatchImpl* p, bool recursive)
+{
+ for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i)
+ ObjectSender::send_patch((*i).second, p, recursive);
+}
+
+
+/** Sends notification of an GraphObject's renaming
+ */
+void
+ClientBroadcaster::send_rename(const string& old_path, const string& new_path)
+{
+ for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i)
+ (*i).second->object_renamed(old_path, new_path);
+}
+
+
+} // namespace Ingen
diff --git a/src/engine/ClientBroadcaster.hpp b/src/engine/ClientBroadcaster.hpp
new file mode 100644
index 00000000..b0963610
--- /dev/null
+++ b/src/engine/ClientBroadcaster.hpp
@@ -0,0 +1,97 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 CLIENTBROADCASTER_H
+#define CLIENTBROADCASTER_H
+
+#include <string>
+#include <list>
+#include <map>
+#include <lo/lo.h>
+#include <pthread.h>
+#include <raul/SharedPtr.hpp>
+#include "interface/ClientInterface.hpp"
+#include "types.hpp"
+#include "NodeFactory.hpp"
+
+using std::string;
+
+namespace Ingen {
+
+class NodeImpl;
+class PortImpl;
+class PluginImpl;
+class PatchImpl;
+class ConnectionImpl;
+using Shared::ClientInterface;
+
+
+/** Broadcaster for all clients.
+ *
+ * This sends messages to all client simultaneously through the opaque
+ * ClientInterface. The clients may be OSC driver, in process, theoretically
+ * anything that implements ClientInterface.
+ *
+ * This also serves as the database of all registered clients.
+ *
+ * \ingroup engine
+ */
+class ClientBroadcaster
+{
+public:
+ void register_client(const string& uri, ClientInterface* client);
+ bool unregister_client(const string& uri);
+
+ ClientInterface* client(const string& uri);
+
+ //void send_client_registration(const string& url, int client_id);
+
+ void bundle_begin();
+ void bundle_end();
+
+ // Error that isn't the direct result of a request
+ void send_error(const string& msg);
+
+ void send_plugins(const NodeFactory::Plugins& plugin_list);
+ void send_patch(const PatchImpl* p, bool recursive);
+ void send_node(const NodeImpl* node, bool recursive);
+ void send_port(const PortImpl* port);
+ void send_destroyed(const string& path);
+ void send_patch_cleared(const string& patch_path);
+ void send_connection(const SharedPtr<const ConnectionImpl> connection);
+ void send_disconnection(const string& src_port_path, const string& dst_port_path);
+ void send_rename(const string& old_path, const string& new_path);
+ void send_variable_change(const string& node_path, const string& key, const Raul::Atom& value);
+ void send_property_change(const string& node_path, const string& key, const Raul::Atom& value);
+ void send_port_value(const string& port_path, const Raul::Atom& value);
+ void send_port_activity(const string& port_path);
+ void send_program_add(const string& node_path, int bank, int program, const string& name);
+ void send_program_remove(const string& node_path, int bank, int program);
+
+ void send_plugins_to(ClientInterface*, const NodeFactory::Plugins& plugin_list);
+
+private:
+ typedef std::map<string, ClientInterface*> Clients;
+ Clients _clients;
+};
+
+
+
+} // namespace Ingen
+
+#endif // CLIENTBROADCASTER_H
+
diff --git a/src/engine/CompiledPatch.hpp b/src/engine/CompiledPatch.hpp
new file mode 100644
index 00000000..9ca1d6e9
--- /dev/null
+++ b/src/engine/CompiledPatch.hpp
@@ -0,0 +1,83 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 COMPILED_PATCH_HPP
+#define COMPILED_PATCH_HPP
+
+#include <iostream>
+#include <vector>
+#include <raul/List.hpp>
+#include <raul/Deletable.hpp>
+#include <boost/utility.hpp>
+
+using Raul::List;
+
+using namespace std;
+
+namespace Ingen {
+
+
+/** All information required about a node to execute it in an audio thread.
+ */
+struct CompiledNode {
+ CompiledNode(NodeImpl* n, size_t np, 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 (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 vector<NodeImpl*>& dependants() const { return _dependants; }
+
+private:
+ NodeImpl* _node;
+ size_t _n_providers; ///< Number of input ready signals to trigger run
+ vector<NodeImpl*> _dependants; ///< Nodes this one's output ports are connected to
+};
+
+
+/** A patch and a set of connections, "compiled" into a flat structure with
+ * the correct order so the audio thread(s) can execute it without
+ * threading problems (since the preprocessor thread fiddles with other
+ * things).
+ *
+ * Currently objects still have some 'heavyweight' connection state, but
+ * eventually this should be the only place a particular set of connections
+ * in a patch is stored, so various "connection presets" can be switched
+ * in a realtime safe way.
+ *
+ * The nodes contained here are sorted in the order they must be executed.
+ * The parallel processing algorithm guarantees no node will be executed
+ * before it's providers, using this order as well as semaphores.
+ */
+struct CompiledPatch : public std::vector<CompiledNode>
+ , public Raul::Deletable
+ , public boost::noncopyable {
+ /*CompiledPatch() : std::vector<CompiledNode>() {}
+ CompiledPatch(size_t reserve) : std::vector<CompiledNode>(reserve) {}*/
+};
+
+
+} // namespace Ingen
+
+#endif // COMPILED_PATCH_HPP
diff --git a/src/engine/ConnectionImpl.cpp b/src/engine/ConnectionImpl.cpp
new file mode 100644
index 00000000..d226b4dd
--- /dev/null
+++ b/src/engine/ConnectionImpl.cpp
@@ -0,0 +1,185 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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/Maid.hpp>
+#include "util.hpp"
+#include "ConnectionImpl.hpp"
+#include "NodeImpl.hpp"
+#include "PortImpl.hpp"
+#include "AudioBuffer.hpp"
+#include "ProcessContext.hpp"
+
+#include <iostream>
+using namespace std;
+
+namespace Ingen {
+
+
+/** Constructor for a connection from a node's output port.
+ *
+ * This handles both polyphonic and monophonic nodes, transparently to the
+ * user (InputPort).
+ */
+ConnectionImpl::ConnectionImpl(PortImpl* src_port, PortImpl* dst_port)
+ : _src_port(src_port)
+ , _dst_port(dst_port)
+ , _local_buffer(NULL)
+ , _buffer_size(dst_port->buffer_size())
+ /*, _must_mix( (src_port->poly() != dst_port->poly())
+ || (src_port->buffer(0)->size() < dst_port->buffer(0)->size()) )*/
+ , _must_mix( (src_port->polyphonic() && (! dst_port->polyphonic()))
+ || (src_port->poly() != dst_port->poly() )
+ || (src_port->buffer(0)->size() < dst_port->buffer(0)->size()) )
+ , _pending_disconnection(false)
+{
+ assert(src_port);
+ assert(dst_port);
+ assert(src_port != dst_port);
+ assert(src_port->path() != dst_port->path());
+ assert(src_port->type() == dst_port->type()
+ || ( (src_port->type() == DataType::CONTROL || src_port->type() == DataType::AUDIO)
+ && (dst_port->type() == DataType::CONTROL || dst_port->type() == DataType::AUDIO) ));
+
+ /*assert((src_port->parent_node()->poly() == dst_port->parent_node()->poly())
+ || (src_port->parent_node()->poly() == 1 || dst_port->parent_node()->poly() == 1));*/
+
+ if (type() == DataType::EVENT)
+ _must_mix = false; // FIXME: kludge
+
+ if (_must_mix)
+ _local_buffer = Buffer::create(dst_port->type(), dst_port->buffer(0)->size());
+
+ /* FIXME: 1->1 connections with a destination with fixed buffers copies unecessarily */
+ //cerr << src_port->path() << " -> " << dst_port->path() << " must mix: " << _must_mix << endl;
+}
+
+
+ConnectionImpl::~ConnectionImpl()
+{
+ delete _local_buffer;
+}
+
+
+void
+ConnectionImpl::set_buffer_size(size_t size)
+{
+ if (_must_mix) {
+ assert(_local_buffer);
+ delete _local_buffer;
+
+ _local_buffer = Buffer::create(_dst_port->type(), _dst_port->buffer(0)->size());
+ }
+
+ _buffer_size = size;
+}
+
+
+void
+ConnectionImpl::prepare_poly(uint32_t poly)
+{
+ _src_port->prepare_poly(poly);
+
+ if (type() == DataType::CONTROL || type() == DataType::AUDIO)
+ _must_mix = (poly > 1) && (
+ (_src_port->poly() != _dst_port->poly())
+ || (_src_port->polyphonic() && !_dst_port->polyphonic())
+ || (_src_port->parent()->polyphonic() && !_dst_port->parent()->polyphonic()) );
+
+ /*cerr << src_port()->path() << " * " << src_port()->poly()
+ << " -> " << dst_port()->path() << " * " << dst_port()->poly()
+ << "\t\tmust mix: " << _must_mix << " at poly " << poly << endl;*/
+
+ if (_must_mix && ! _local_buffer)
+ _local_buffer = Buffer::create(_dst_port->type(), _dst_port->buffer(0)->size());
+}
+
+
+void
+ConnectionImpl::apply_poly(Raul::Maid& maid, uint32_t poly)
+{
+ _src_port->apply_poly(maid, poly);
+ if (poly == 1 && _local_buffer && !_must_mix) {
+ maid.push(_local_buffer);
+ _local_buffer = NULL;
+ }
+}
+
+
+void
+ConnectionImpl::process(ProcessContext& context)
+{
+ // FIXME: nframes parameter not used
+ assert(_buffer_size == 1 || _buffer_size == context.nframes());
+
+ /* Thought: A poly output port can be connected to multiple mono input
+ * ports, which means this mix down would have to happen many times.
+ * Adding a method to OutputPort that mixes down all it's outputs into
+ * a buffer (if it hasn't been done already this cycle) and returns that
+ * would avoid having to mix multiple times. Probably not a very common
+ * case, but it would be faster anyway. */
+
+ /*cerr << src_port()->path() << " * " << src_port()->poly()
+ << " -> " << dst_port()->path() << " * " << dst_port()->poly()
+ << "\t\tmust mix: " << _must_mix << endl;*/
+
+ if (_must_mix && (type() == DataType::CONTROL || type() == DataType::AUDIO)) {
+
+ const AudioBuffer* const src_buffer = (AudioBuffer*)src_port()->buffer(0);
+ AudioBuffer* mix_buf = (AudioBuffer*)_local_buffer;
+
+ assert(mix_buf);
+
+ const size_t copy_size = std::min(src_buffer->size(), mix_buf->size());
+
+ // Copy src buffer to start of mix buffer
+ mix_buf->copy((AudioBuffer*)src_port()->buffer(0), 0, copy_size-1);
+
+ // Write last value of src buffer to remainder of dst buffer, if necessary
+ if (copy_size < mix_buf->size())
+ mix_buf->set_block(src_buffer->value_at(copy_size-1), copy_size, mix_buf->size()-1);
+
+ // Accumulate the source's voices into local buffer starting at the second
+ // voice (buffer is already set to first voice above)
+ for (uint32_t j=1; j < src_port()->poly(); ++j) {
+ mix_buf->accumulate((AudioBuffer*)src_port()->buffer(j), 0, copy_size-1);
+ }
+
+ // Find the summed value and write it to the remainder of dst buffer
+ if (copy_size < mix_buf->size()) {
+ float src_value = src_buffer->value_at(copy_size-1);
+ for (uint32_t j=1; j < src_port()->poly(); ++j)
+ src_value += ((AudioBuffer*)src_port()->buffer(j))->value_at(copy_size-1);
+
+ mix_buf->set_block(src_value, copy_size, mix_buf->size()-1);
+ }
+
+ // Scale the buffer down.
+ if (src_port()->poly() > 1)
+ mix_buf->scale(1.0f/(float)src_port()->poly(), 0, _buffer_size-1);
+
+ } else if (_must_mix && type() == DataType::EVENT) {
+
+ std::cerr << "WARNING: No event mixing." << std::endl;
+
+ }
+
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/ConnectionImpl.hpp b/src/engine/ConnectionImpl.hpp
new file mode 100644
index 00000000..b3da1b54
--- /dev/null
+++ b/src/engine/ConnectionImpl.hpp
@@ -0,0 +1,101 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 CONNECTIONIMPL_H
+#define CONNECTIONIMPL_H
+
+#include <cstdlib>
+#include <boost/utility.hpp>
+#include <raul/Deletable.hpp>
+#include "interface/DataType.hpp"
+#include "interface/Connection.hpp"
+#include "PortImpl.hpp"
+#include "types.hpp"
+
+namespace Ingen {
+
+class PortImpl;
+class Buffer;
+
+
+/** 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 Shared::Connection
+{
+public:
+ ConnectionImpl(PortImpl* src_port, PortImpl* dst_port);
+ virtual ~ConnectionImpl();
+
+ 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 process(ProcessContext& context);
+
+ /** Get the buffer for a particular voice.
+ * A Connection is smart - it knows the destination port requesting the
+ * buffer, and will return accordingly (ie the same buffer for every voice
+ * in a mono->poly connection).
+ */
+ inline Buffer* buffer(size_t voice) const;
+
+ void set_buffer_size(size_t size);
+ void prepare_poly(uint32_t poly);
+ void apply_poly(Raul::Maid& maid, uint32_t poly);
+
+ DataType type() const { return _src_port->type(); }
+
+protected:
+ PortImpl* const _src_port;
+ PortImpl* const _dst_port;
+ Buffer* _local_buffer;
+ size_t _buffer_size;
+ bool _must_mix;
+ bool _pending_disconnection;
+};
+
+
+inline Buffer*
+ConnectionImpl::buffer(size_t voice) const
+{
+ if (_must_mix) {
+ return _local_buffer;
+ } else if ( ! _src_port->polyphonic()) {
+ return _src_port->buffer(0);
+ } else {
+ return _src_port->buffer(voice);
+ }
+}
+
+
+} // namespace Ingen
+
+#endif // CONNECTIONIMPL_H
diff --git a/src/engine/Context.hpp b/src/engine/Context.hpp
new file mode 100644
index 00000000..2e000fb1
--- /dev/null
+++ b/src/engine/Context.hpp
@@ -0,0 +1,51 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 CONTEXT_H
+#define CONTEXT_H
+
+namespace Ingen {
+
+class Engine;
+
+class Context
+{
+public:
+ enum ID {
+ AUDIO,
+ MESSAGE
+ };
+
+ Context(Engine& engine, ID id)
+ : _id(id)
+ , _engine(engine)
+ {}
+
+ virtual ~Context() {}
+
+ inline Engine& engine() const { return _engine; }
+
+protected:
+ ID _id; ///< Fast ID for this context
+ Engine& _engine; ///< Engine we're running in
+};
+
+
+} // namespace Ingen
+
+#endif // CONTEXT_H
+
diff --git a/src/engine/Driver.hpp b/src/engine/Driver.hpp
new file mode 100644
index 00000000..590b66b5
--- /dev/null
+++ b/src/engine/Driver.hpp
@@ -0,0 +1,98 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 DRIVER_H
+#define DRIVER_H
+
+#include <string>
+#include <boost/utility.hpp>
+#include <raul/Path.hpp>
+#include "interface/DataType.hpp"
+#include "DuplexPort.hpp"
+
+namespace Ingen {
+
+class DuplexPort;
+
+
+/** 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:
+ virtual ~DriverPort() {}
+
+ /** Set the name of the system port */
+ virtual void set_name(const std::string& name) = 0;
+
+ bool is_input() const { return _patch_port->is_input(); }
+ DuplexPort* patch_port() const { return _patch_port; }
+
+protected:
+ /** is_input from the perspective outside of ingen */
+ 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:
+ Driver(DataType type)
+ : _type(type)
+ {}
+
+ virtual ~Driver() {}
+
+ virtual void activate() = 0;
+ virtual void deactivate() = 0;
+
+ virtual bool is_activated() const = 0;
+
+ /** Create a port ready to be inserted with add_input (non realtime).
+ *
+ * May return NULL if the Driver can not drive the port for some reason.
+ */
+ virtual DriverPort* create_port(DuplexPort* patch_port) = 0;
+
+ virtual DriverPort* driver_port(const Raul::Path& path) = 0;
+
+ virtual void add_port(DriverPort* port) = 0;
+ virtual DriverPort* remove_port(const Raul::Path& path) = 0;
+
+protected:
+ DataType _type;
+};
+
+
+} // namespace Ingen
+
+#endif // DRIVER_H
diff --git a/src/engine/DuplexPort.cpp b/src/engine/DuplexPort.cpp
new file mode 100644
index 00000000..31bfbe09
--- /dev/null
+++ b/src/engine/DuplexPort.cpp
@@ -0,0 +1,108 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 <iostream>
+#include <cstdlib>
+#include <cassert>
+#include "util.hpp"
+#include "DuplexPort.hpp"
+#include "ConnectionImpl.hpp"
+#include "OutputPort.hpp"
+#include "NodeImpl.hpp"
+#include "ProcessContext.hpp"
+#include "EventBuffer.hpp"
+
+using namespace std;
+
+namespace Ingen {
+
+
+DuplexPort::DuplexPort(NodeImpl* parent, const string& name, uint32_t index, uint32_t poly, DataType type, const Atom& value, size_t buffer_size, bool is_output)
+ : PortImpl(parent, name, index, poly, type, value, buffer_size)
+ , InputPort(parent, name, index, poly, type, value, buffer_size)
+ , OutputPort(parent, name, index, poly, type, value, buffer_size)
+ , _is_output(is_output)
+{
+ assert(PortImpl::_parent == parent);
+}
+
+
+void
+DuplexPort::pre_process(ProcessContext& context)
+{
+ // <BrainHurt>
+
+ /*cerr << endl << "{ duplex pre" << endl;
+ cerr << path() << " duplex pre: fixed buffers: " << fixed_buffers() << endl;
+ cerr << path() << " duplex pre: buffer: " << buffer(0) << endl;
+ cerr << path() << " duplex pre: is_output: " << _is_output << " { " << endl;*/
+
+ /*if (type() == DataType::EVENT)
+ for (uint32_t i=0; i < _poly; ++i)
+ cerr << path() << " (" << buffer(i) << ") # events: "
+ << ((EventBuffer*)buffer(i))->event_count()
+ << ", joined: " << _buffers->at(i)->is_joined() << endl;*/
+
+ if (_is_output) {
+
+ for (uint32_t i=0; i < _poly; ++i)
+ if (!_buffers->at(i)->is_joined())
+ _buffers->at(i)->prepare_write(context.start(), context.nframes());
+
+ } else {
+
+ for (uint32_t i=0; i < _poly; ++i)
+ _buffers->at(i)->prepare_read(context.start(), context.nframes());
+
+ broadcast(context);
+ }
+
+ //cerr << "} duplex pre " << path() << endl;
+
+ // </BrainHurt>
+}
+
+
+void
+DuplexPort::post_process(ProcessContext& context)
+{
+ // <BrainHurt>
+
+ /*cerr << endl << "{ duplex post" << endl;
+ cerr << path() << " duplex post: fixed buffers: " << fixed_buffers() << endl;
+ cerr << path() << " duplex post: buffer: " << buffer(0) << endl;
+ cerr << path() << " duplex post: is_output: " << _is_output << " { " << endl;
+
+ if (type() == DataType::EVENT)
+ for (uint32_t i=0; i < _poly; ++i)
+ cerr << path() << " (" << buffer(i) << ") # events: "
+ << ((EventBuffer*)buffer(i))->event_count()
+ << ", joined: " << _buffers->at(i)->is_joined() << endl;*/
+
+ if (_is_output) {
+ InputPort::pre_process(context); // Mix down inputs
+ broadcast(context);
+ }
+
+ //cerr << "} duplex post " << path() << endl;
+
+ // </BrainHurt>
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/DuplexPort.hpp b/src/engine/DuplexPort.hpp
new file mode 100644
index 00000000..43d202a9
--- /dev/null
+++ b/src/engine/DuplexPort.hpp
@@ -0,0 +1,68 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 DUPLEXPORT_H
+#define DUPLEXPORT_H
+
+#include <string>
+#include <raul/Array.hpp>
+#include "types.hpp"
+#include "Buffer.hpp"
+#include "InputPort.hpp"
+#include "OutputPort.hpp"
+
+namespace Ingen {
+
+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(NodeImpl* parent,
+ const std::string& name,
+ uint32_t index,
+ uint32_t poly,
+ DataType type,
+ const Atom& value,
+ size_t buffer_size,
+ bool is_output);
+
+ virtual ~DuplexPort() {}
+
+ void pre_process(ProcessContext& context);
+ void post_process(ProcessContext& context);
+
+ bool is_input() const { return !_is_output; }
+ bool is_output() const { return _is_output; }
+
+protected:
+ bool _is_output;
+};
+
+
+} // namespace Ingen
+
+#endif // DUPLEXPORT_H
diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp
new file mode 100644
index 00000000..a08a37ca
--- /dev/null
+++ b/src/engine/Engine.cpp
@@ -0,0 +1,317 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 <iostream>
+#include <unistd.h>
+#include <raul/Deletable.hpp>
+#include <raul/Maid.hpp>
+#include <raul/SharedPtr.hpp>
+#include "Engine.hpp"
+#include CONFIG_H_PATH
+#include "tuning.hpp"
+#include "Event.hpp"
+#include "common/interface/EventType.hpp"
+#include "shared/Store.hpp"
+#include "JackAudioDriver.hpp"
+#include "NodeFactory.hpp"
+#include "ClientBroadcaster.hpp"
+#include "PatchImpl.hpp"
+#include "EngineStore.hpp"
+#include "MidiDriver.hpp"
+#include "OSCDriver.hpp"
+#include "QueuedEventSource.hpp"
+#include "PostProcessor.hpp"
+#include "CreatePatchEvent.hpp"
+#include "EnablePatchEvent.hpp"
+#include "OSCEngineReceiver.hpp"
+#ifdef HAVE_SOUP
+#include "HTTPEngineReceiver.hpp"
+#endif
+#include "PostProcessor.hpp"
+#include "ProcessSlave.hpp"
+#include "ProcessContext.hpp"
+#include "MessageContext.hpp"
+#include "ThreadManager.hpp"
+#ifdef HAVE_JACK_MIDI
+#include "JackMidiDriver.hpp"
+#endif
+using namespace std;
+
+namespace Ingen {
+
+
+Engine::Engine(Ingen::Shared::World* world)
+ : _world(world)
+ , _midi_driver(NULL)
+ , _osc_driver(NULL)
+ , _maid(new Raul::Maid(maid_queue_size))
+ , _post_processor(new PostProcessor(*this, /**_maid, */post_processor_queue_size))
+ , _broadcaster(new ClientBroadcaster())
+ , _node_factory(new NodeFactory(world))
+ , _message_context(new MessageContext(*this))
+ , _quit_flag(false)
+ , _activated(false)
+{
+ if (world->store) {
+ assert(PtrCast<EngineStore>(world->store));
+ } else {
+ world->store = SharedPtr<Store>(new EngineStore());
+ }
+}
+
+
+Engine::~Engine()
+{
+ deactivate();
+
+ for (EngineStore::iterator i = engine_store()->begin();
+ i != engine_store()->end(); ++i) {
+ if ( ! PtrCast<GraphObjectImpl>(i->second)->parent() )
+ i->second.reset();
+ }
+
+ delete _broadcaster;
+ delete _node_factory;
+ delete _osc_driver;
+ delete _post_processor;
+ //delete _lash_driver;
+
+ delete _maid;
+
+ munlockall();
+}
+
+
+SharedPtr<EngineStore>
+Engine::engine_store() const
+{
+ return PtrCast<EngineStore>(_world->store);
+}
+
+
+Driver*
+Engine::driver(DataType type, EventType event_type)
+{
+ if (type == DataType::AUDIO) {
+ return _audio_driver.get();
+ } else if (type == DataType::EVENT) {
+ if (event_type == EventType::MIDI) {
+ return _midi_driver;
+ } else if (event_type == EventType::OSC) {
+ return _osc_driver;
+ }
+ }
+
+ return NULL;
+}
+
+
+int
+Engine::main()
+{
+ Thread::get().set_context(THREAD_POST_PROCESS);
+
+ // Loop until quit flag is set (by OSCReceiver)
+ while ( ! _quit_flag) {
+ nanosleep(&main_rate, NULL);
+ main_iteration();
+ }
+ cout << "[Main] Done main loop." << endl;
+
+ deactivate();
+
+ return 0;
+}
+
+
+/** Run one iteration of the main loop.
+ *
+ * NOT realtime safe (this is where deletion actually occurs)
+ */
+bool
+Engine::main_iteration()
+{
+/*#ifdef HAVE_LASH
+ // Process any pending LASH events
+ if (lash_driver->enabled())
+ lash_driver->process_events();
+#endif*/
+ // Run the maid (garbage collector)
+ _post_processor->process();
+ _maid->cleanup();
+
+ return !_quit_flag;
+}
+
+
+void
+Engine::start_jack_driver()
+{
+ if ( ! _audio_driver)
+ _audio_driver = SharedPtr<AudioDriver>(new JackAudioDriver(*this));
+ else
+ cerr << "[Engine::start_jack_driver] Audio driver already running" << endl;
+}
+
+
+void
+Engine::start_osc_driver(int port)
+{
+ if (_event_source) {
+ cerr << "WARNING: Replacing event source" << endl;
+ _event_source.reset();
+ }
+
+ _event_source = SharedPtr<EventSource>(new OSCEngineReceiver(
+ *this, pre_processor_queue_size, port));
+}
+
+
+void
+Engine::start_http_driver(int port)
+{
+#ifdef HAVE_SOUP
+ // FIXE: leak
+ HTTPEngineReceiver* server = new HTTPEngineReceiver(*this, port);
+ server->activate();
+#endif
+}
+
+
+SharedPtr<QueuedEngineInterface>
+Engine::new_queued_interface()
+{
+ if (_event_source) {
+ cerr << "WARNING: Replacing event source" << endl;
+ _event_source.reset();
+ }
+
+ SharedPtr<QueuedEngineInterface> result(new QueuedEngineInterface(
+ *this, Ingen::event_queue_size, Ingen::event_queue_size));
+
+ _event_source = result;
+
+ return result;
+}
+
+/*
+void
+Engine::set_event_source(SharedPtr<EventSource> source)
+{
+ if (_event_source)
+ cerr << "Warning: Dropped event source (engine interface)" << endl;
+
+ _event_source = source;
+}
+*/
+
+
+bool
+Engine::activate(size_t parallelism)
+{
+ if (_activated)
+ return false;
+
+ assert(_audio_driver);
+ assert(_event_source);
+
+#ifdef HAVE_JACK_MIDI
+ _midi_driver = new JackMidiDriver(((JackAudioDriver*)_audio_driver.get())->jack_client());
+#else
+ _midi_driver = new DummyMidiDriver();
+#endif
+
+ _event_source->activate();
+
+ // Create root patch
+
+ PatchImpl* root_patch = new PatchImpl(*this, "", 1, NULL,
+ _audio_driver->sample_rate(), _audio_driver->buffer_size(), 1);
+ root_patch->activate();
+ _world->store->add(root_patch);
+ root_patch->compiled_patch(root_patch->compile());
+
+ assert(_audio_driver->root_patch() == NULL);
+ _audio_driver->set_root_patch(root_patch);
+
+ _audio_driver->activate();
+
+ _process_slaves.clear();
+ _process_slaves.reserve(parallelism);
+ for (size_t i=0; i < parallelism - 1; ++i)
+ _process_slaves.push_back(new ProcessSlave(*this, _audio_driver->is_realtime()));
+
+ root_patch->enable();
+
+ //_post_processor->start();
+
+ _activated = true;
+
+ return true;
+}
+
+
+void
+Engine::deactivate()
+{
+ if (!_activated)
+ return;
+
+ _event_source->deactivate();
+
+ /*for (Tree<GraphObject*>::iterator i = _engine_store->objects().begin();
+ i != _engine_store->objects().end(); ++i)
+ if ((*i)->as_node() != NULL && (*i)->as_node()->parent() == NULL)
+ (*i)->as_node()->deactivate();*/
+
+ if (_midi_driver != NULL) {
+ _midi_driver->deactivate();
+ delete _midi_driver;
+ _midi_driver = NULL;
+ }
+
+ _audio_driver->deactivate();
+
+ _audio_driver->root_patch()->deactivate();
+
+ for (size_t i=0; i < _process_slaves.size(); ++i) {
+ delete _process_slaves[i];
+ }
+
+ _process_slaves.clear();
+
+ // Finalize any lingering events (unlikely)
+ _post_processor->process();
+
+ _audio_driver.reset();
+ _event_source.reset();
+
+ _activated = false;
+}
+
+
+void
+Engine::process_events(ProcessContext& context)
+{
+ if (_event_source)
+ _event_source->process(*_post_processor, context);
+}
+
+
+} // namespace Ingen
diff --git a/src/engine/Engine.hpp b/src/engine/Engine.hpp
new file mode 100644
index 00000000..fce826db
--- /dev/null
+++ b/src/engine/Engine.hpp
@@ -0,0 +1,130 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 ENGINE_H
+#define ENGINE_H
+
+#include CONFIG_H_PATH
+#include <cassert>
+#include <vector>
+#include <boost/utility.hpp>
+#include <raul/SharedPtr.hpp>
+#include "module/global.hpp"
+#include "interface/DataType.hpp"
+#include "interface/EventType.hpp"
+
+template<typename T> class Queue;
+
+namespace Raul { class Maid; }
+
+namespace Ingen {
+
+class AudioDriver;
+class MidiDriver;
+class OSCDriver;
+class NodeFactory;
+class ClientBroadcaster;
+class EngineStore;
+class EventSource;
+class PostProcessor;
+class Event;
+class QueuedEvent;
+class QueuedEngineInterface;
+class Driver;
+class ProcessSlave;
+class ProcessContext;
+class MessageContext;
+
+
+/** The main class for the Engine.
+ *
+ * This is a (GoF) facade for the engine. Pointers to all components are
+ * available for more advanced control than this facade allows.
+ *
+ * \ingroup engine
+ */
+class Engine : boost::noncopyable
+{
+public:
+ Engine(Ingen::Shared::World* world);
+
+ virtual ~Engine();
+
+ virtual int main();
+ virtual bool main_iteration();
+
+ /** Set the quit flag that should kill all threads and exit cleanly.
+ * Note that it will take some time. */
+ virtual void quit() { _quit_flag = true; }
+
+ virtual void start_jack_driver();
+ virtual void start_osc_driver(int port);
+ virtual void start_http_driver(int port);
+
+ virtual SharedPtr<QueuedEngineInterface> new_queued_interface();
+
+ virtual bool activate(size_t parallelism);
+ virtual void deactivate();
+
+ void process_events(ProcessContext& context);
+
+ virtual bool activated() { return _activated; }
+
+ Raul::Maid* maid() const { return _maid; }
+ EventSource* event_source() const { return _event_source.get(); }
+ AudioDriver* audio_driver() const { return _audio_driver.get(); }
+ MidiDriver* midi_driver() const { return _midi_driver; }
+ OSCDriver* osc_driver() const { return _osc_driver; }
+ PostProcessor* post_processor() const { return _post_processor; }
+ ClientBroadcaster* broadcaster() const { return _broadcaster; }
+ NodeFactory* node_factory() const { return _node_factory; }
+ MessageContext* message_context() const { return _message_context; }
+
+ SharedPtr<EngineStore> engine_store() const;
+
+ /** Return the active driver for the given type */
+ Driver* driver(DataType type, EventType event_type);
+
+ Ingen::Shared::World* world() { return _world; }
+
+ typedef std::vector<ProcessSlave*> ProcessSlaves;
+ inline const ProcessSlaves& process_slaves() const { return _process_slaves; }
+ inline ProcessSlaves& process_slaves() { return _process_slaves; }
+
+private:
+ ProcessSlaves _process_slaves;
+
+ Ingen::Shared::World* _world;
+
+ SharedPtr<EventSource> _event_source;
+ SharedPtr<AudioDriver> _audio_driver;
+ MidiDriver* _midi_driver;
+ OSCDriver* _osc_driver;
+ Raul::Maid* _maid;
+ PostProcessor* _post_processor;
+ ClientBroadcaster* _broadcaster;
+ NodeFactory* _node_factory;
+ MessageContext* _message_context;
+
+ bool _quit_flag;
+ bool _activated;
+};
+
+
+} // namespace Ingen
+
+#endif // ENGINE_H
diff --git a/src/engine/EngineStore.cpp b/src/engine/EngineStore.cpp
new file mode 100644
index 00000000..9fcd3806
--- /dev/null
+++ b/src/engine/EngineStore.cpp
@@ -0,0 +1,182 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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/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"
+
+using namespace std;
+using namespace Raul;
+
+namespace Ingen {
+
+
+/** 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)
+{
+ GraphObjectImpl* o = dynamic_cast<GraphObjectImpl*>(obj);
+ assert(o);
+
+ assert(ThreadManager::current_thread_id() == THREAD_PRE_PROCESS);
+
+ Store::add(obj);
+}
+
+
+/** Add a family of objects to the store. Not realtime safe.
+ */
+void
+EngineStore::add(const Objects& table)
+{
+ assert(ThreadManager::current_thread_id() == THREAD_PRE_PROCESS);
+
+ //cerr << "[EngineStore] Adding " << o[0].second->path() << endl;
+ cram(table);
+
+ /*cerr << "[EngineStore] Adding Table:" << endl;
+ for (const_iterator i = table.begin(); i != table.end(); ++i) {
+ cerr << i->first << " = " << i->second->path() << endl;
+ }*/
+}
+
+
+/** 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)
+{
+ assert(ThreadManager::current_thread_id() == THREAD_PRE_PROCESS);
+
+ if (object != end()) {
+ iterator descendants_end = find_descendants_end(object);
+ //cout << "[EngineStore] Removing " << object->first << " {" << endl;
+ SharedPtr<Objects> removed = yank(object, descendants_end);
+ /*for (iterator i = removed->begin(); i != removed->end(); ++i) {
+ cout << "\t" << i->first << endl;
+ }
+ cout << "}" << endl;*/
+
+ return removed;
+
+ } else {
+ cerr << "[EngineStore] WARNING: 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 {
+ cerr << "[EngineStore] WARNING: Removing children of " << object->first << " failed." << endl;
+ return SharedPtr<EngineStore::Objects>();
+ }
+
+ return SharedPtr<EngineStore::Objects>();
+}
+
+
+} // namespace Ingen
diff --git a/src/engine/EngineStore.hpp b/src/engine/EngineStore.hpp
new file mode 100644
index 00000000..ad656aeb
--- /dev/null
+++ b/src/engine/EngineStore.hpp
@@ -0,0 +1,68 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 OBJECTSTORE_H
+#define OBJECTSTORE_H
+
+#include <string>
+#include <raul/PathTable.hpp>
+#include <raul/SharedPtr.hpp>
+#include "shared/Store.hpp"
+
+using std::string;
+using namespace Raul;
+
+namespace Ingen {
+
+namespace Shared { class GraphObject; }
+
+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 Shared::Store
+{
+public:
+ PatchImpl* find_patch(const Path& path);
+ NodeImpl* find_node(const Path& path);
+ PortImpl* find_port(const Path& path);
+ GraphObjectImpl* find_object(const Path& path);
+
+ void add(Shared::GraphObject* o);
+ void add(const Objects& family);
+
+ SharedPtr<Objects> remove(const Path& path);
+ SharedPtr<Objects> remove(Objects::iterator i);
+ SharedPtr<Objects> remove_children(const Path& path);
+ SharedPtr<Objects> remove_children(Objects::iterator i);
+};
+
+
+} // namespace Ingen
+
+#endif // OBJECTSTORE
diff --git a/src/engine/Event.cpp b/src/engine/Event.cpp
new file mode 100644
index 00000000..8e5c33da
--- /dev/null
+++ b/src/engine/Event.cpp
@@ -0,0 +1,49 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "Event.hpp"
+#include "ThreadManager.hpp"
+#include "ProcessContext.hpp"
+
+namespace Ingen {
+
+
+void
+Event::execute(ProcessContext& context)
+{
+ assert(ThreadManager::current_thread_id() == 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()
+{
+ // FIXME: Not true witn monolithic GUI/engine
+ //assert(ThreadManager::current_thread_id() == THREAD_POST_PROCESS);
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/Event.hpp b/src/engine/Event.hpp
new file mode 100644
index 00000000..5860da2a
--- /dev/null
+++ b/src/engine/Event.hpp
@@ -0,0 +1,75 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 EVENT_H
+#define EVENT_H
+
+#include <cassert>
+#include <raul/SharedPtr.hpp>
+#include <raul/Deletable.hpp>
+#include "types.hpp"
+
+namespace Ingen {
+
+class Engine;
+class Responder;
+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; }
+
+protected:
+ Event(Engine& engine, SharedPtr<Responder> responder, FrameTime time)
+ : _engine(engine)
+ , _responder(responder)
+ , _time(time)
+ , _executed(false)
+ {}
+
+ Engine& _engine;
+ SharedPtr<Responder> _responder;
+ FrameTime _time;
+ bool _executed;
+};
+
+
+} // namespace Ingen
+
+#endif // EVENT_H
diff --git a/src/engine/EventBuffer.cpp b/src/engine/EventBuffer.cpp
new file mode 100644
index 00000000..e8584597
--- /dev/null
+++ b/src/engine/EventBuffer.cpp
@@ -0,0 +1,318 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 <iostream>
+#include "EventBuffer.hpp"
+#include "lv2ext/lv2_event.h"
+#include "lv2ext/lv2_event_helpers.h"
+
+using namespace std;
+
+namespace Ingen {
+
+
+/** Allocate a new event buffer.
+ * \a capacity is in bytes (not number of events).
+ */
+EventBuffer::EventBuffer(size_t capacity)
+ : Buffer(DataType(DataType::EVENT), capacity)
+ , _latest_frames(0)
+ , _latest_subframes(0)
+{
+ if (capacity > UINT32_MAX) {
+ cerr << "Event buffer size " << capacity << " too large, aborting." << endl;
+ throw std::bad_alloc();
+ }
+
+ int ret = posix_memalign((void**)&_local_buf, 16, sizeof(LV2_Event_Buffer) + capacity);
+ if (ret) {
+ cerr << "Failed to allocate event buffer. Aborting." << endl;
+ exit(EXIT_FAILURE);
+ }
+
+ _local_buf->event_count = 0;
+ _local_buf->capacity = (uint32_t)capacity;
+ _local_buf->size = 0;
+ _local_buf->data = reinterpret_cast<uint8_t*>(_local_buf + 1);
+ _buf = _local_buf;
+
+ reset(0);
+
+ //cerr << "Creating MIDI Buffer " << _buf << ", capacity = " << _buf->capacity << endl;
+}
+
+
+EventBuffer::~EventBuffer()
+{
+ free(_local_buf);
+}
+
+
+/** Use another buffer's data instead of the local one.
+ *
+ * This buffer will essentially be identical to @a buf after this call.
+ */
+bool
+EventBuffer::join(Buffer* buf)
+{
+ EventBuffer* mbuf = dynamic_cast<EventBuffer*>(buf);
+ if (mbuf) {
+ _buf = mbuf->local_data();
+ _joined_buf = mbuf;
+ _iter = mbuf->_iter;
+ _iter.buf = _buf;
+ return false;
+ } else {
+ return false;
+ }
+
+ //assert(mbuf->size() == _size);
+
+ _joined_buf = mbuf;
+
+ return true;
+}
+
+
+void
+EventBuffer::unjoin()
+{
+ _joined_buf = NULL;
+ _buf = _local_buf;
+ reset(_this_nframes);
+}
+
+
+void
+EventBuffer::prepare_read(FrameTime start, SampleCount nframes)
+{
+ //cerr << "\t" << this << " prepare_read: " << event_count() << endl;
+ rewind();
+ _this_nframes = nframes;
+}
+
+
+void
+EventBuffer::prepare_write(FrameTime start, SampleCount nframes)
+{
+ //cerr << "\t" << this << " prepare_write: " << event_count() << endl;
+ reset(nframes);
+}
+
+/** FIXME: parameters ignored */
+void
+EventBuffer::copy(const Buffer* src_buf, size_t start_sample, size_t end_sample)
+{
+ const EventBuffer* src = dynamic_cast<const EventBuffer*>(src_buf);
+ assert(src);
+ assert(_buf->capacity >= src->_buf->capacity);
+
+ clear();
+ src->rewind();
+
+ memcpy(_buf, src->_buf, src->_buf->size);
+}
+
+
+/** 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);
+}
+
+
+/** Append an event to the buffer.
+ *
+ * \a timestamp must be >= the latest event in the buffer,
+ * and < this_nframes()
+ *
+ * \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
+
+ /*cout << "Appending event type " << type << ", size " << size
+ << " @ " << frames << "." << subframes << endl;*/
+
+ const bool ret = lv2_event_write(&_iter, frames, subframes, type, size, data);
+
+ if (!ret)
+ cerr << "ERROR: Failed to write event." << endl;
+
+ _latest_frames = frames;
+ _latest_subframes = subframes;
+
+ return ret;
+}
+
+
+/** Append a buffer of events to the buffer.
+ *
+ * \a timestamp must be >= the latest event in the buffer,
+ * and < this_nframes()
+ *
+ * \return true on success
+ */
+bool
+EventBuffer::append(const LV2_Event_Buffer* buf)
+{
+ uint8_t** data;
+ bool ret = true;
+
+ LV2_Event_Iterator iter;
+ for (lv2_event_begin(&iter, _buf); lv2_event_is_valid(&iter); lv2_event_increment(&iter)) {
+ LV2_Event* ev = lv2_event_get(&iter, data);
+
+#ifndef NDEBUG
+ assert((ev->frames > _latest_frames)
+ || (ev->frames == _latest_frames
+ && ev->subframes >= _latest_subframes));
+#endif
+
+ if (!(ret = append(ev->frames, ev->subframes, ev->type, ev->size, *data))) {
+ cerr << "ERROR: Failed to write event." << endl;
+ break;
+ }
+
+ _latest_frames = ev->frames;
+ _latest_subframes = ev->subframes;
+ }
+
+ return ret;
+}
+
+
+/** 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;
+ }
+}
+
+
+/** Clear, and merge \a a and \a b into this buffer.
+ *
+ * FIXME: This is slow.
+ *
+ * \return true if complete merge was successful
+ */
+bool
+EventBuffer::merge(const EventBuffer& a, const EventBuffer& b)
+{
+ // Die if a merge isn't necessary as it's expensive
+ assert(a.size() > 0 && b.size() > 0);
+
+ reset(_this_nframes);
+
+ a.rewind();
+ b.rewind();
+
+#if 0
+ uint32_t a_frames;
+ uint32_t a_subframes;
+ uint16_t a_type;
+ uint16_t a_size;
+ uint8_t* a_data;
+
+ uint32_t b_frames;
+ uint32_t b_subframes;
+ uint16_t b_type;
+ uint16_t b_size;
+ uint8_t* b_data;
+#endif
+
+ cout << "FIXME: merge" << endl;
+#if 0
+ a.get_event(&a_frames, &a_subframes, &a_type, &a_size, &a_data);
+ b.get_event(&b_frames, &b_subframes, &b_type, &b_size, &b_data);
+
+ while (true) {
+ if (a_data && (!b_data || (a_time < b_time))) {
+ append(a_time, a_size, a_data);
+ if (a.increment())
+ a.get_event(&a_time, &a_size, &a_data);
+ else
+ a_data = NULL;
+ } else if (b_data) {
+ append(b_time, b_size, b_data);
+ if (b.increment())
+ b.get_event(&b_time, &b_size, &b_data);
+ else
+ b_data = NULL;
+ } else {
+ break;
+ }
+ }
+
+ _latest_stamp = max(a_time, b_time);
+#endif
+
+ return true;
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/EventBuffer.hpp b/src/engine/EventBuffer.hpp
new file mode 100644
index 00000000..0c80d452
--- /dev/null
+++ b/src/engine/EventBuffer.hpp
@@ -0,0 +1,104 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 EVENTBUFFER_H
+#define EVENTBUFFER_H
+
+#include <iostream>
+#include <lv2ext/lv2_event.h>
+#include <lv2ext/lv2_event_helpers.h>
+#include "Buffer.hpp"
+#include "interface/DataType.hpp"
+
+namespace Ingen {
+
+
+class EventBuffer : public Buffer {
+public:
+ EventBuffer(size_t capacity);
+
+ ~EventBuffer();
+
+ void prepare_read(FrameTime start, SampleCount nframes);
+ void prepare_write(FrameTime start, SampleCount nframes);
+
+ bool join(Buffer* buf);
+ void unjoin();
+
+ inline uint32_t this_nframes() const { return _this_nframes; }
+ inline uint32_t event_count() const { return _buf->event_count; }
+
+ inline void* raw_data() { return _buf; }
+ inline const void* raw_data() const { return _buf; }
+
+ inline LV2_Event_Buffer* local_data() { return _local_buf; }
+ inline const LV2_Event_Buffer* local_data() const { return _local_buf; }
+
+ inline LV2_Event_Buffer* data() { return _buf; }
+ inline const LV2_Event_Buffer* data() const { return _buf; }
+
+ void copy(const Buffer* src, size_t start_sample, size_t end_sample);
+
+ inline void rewind() const { lv2_event_begin(&_iter, _buf); }
+ inline void clear() { reset(_this_nframes); }
+ inline void reset(SampleCount nframes) {
+ //std::cerr << this << " reset" << std::endl;
+ _this_nframes = nframes;
+ _latest_frames = 0;
+ _latest_subframes = 0;
+ _buf->event_count = 0;
+ _buf->size = 0;
+ rewind();
+ }
+
+ bool increment() const;
+ bool is_valid() const;
+
+ uint32_t latest_frames() const { return _latest_frames; }
+ uint32_t latest_subframes() const { return _latest_subframes; }
+
+ bool get_event(uint32_t* frames,
+ uint32_t* subframes,
+ uint16_t* type,
+ uint16_t* size,
+ uint8_t** data) const;
+
+ bool append(uint32_t frames,
+ uint32_t subframes,
+ uint16_t type,
+ uint16_t size,
+ const uint8_t* data);
+
+ bool append(const LV2_Event_Buffer* buf);
+
+ bool merge(const EventBuffer& a, const EventBuffer& b);
+
+private:
+ LV2_Event_Buffer* _buf; ///< Contents (maybe belong to _joined_buf)
+ LV2_Event_Buffer* _local_buf; ///< Local contents
+
+ mutable LV2_Event_Iterator _iter; ///< Iterator into _buf
+
+ uint32_t _latest_frames; ///< Latest time of all events (frames)
+ uint32_t _latest_subframes; ///< Latest time of all events (subframes)
+ uint32_t _this_nframes; ///< Current cycle nframes
+};
+
+
+} // namespace Ingen
+
+#endif // EVENTBUFFER_H
diff --git a/src/engine/EventSink.cpp b/src/engine/EventSink.cpp
new file mode 100644
index 00000000..6c775d4c
--- /dev/null
+++ b/src/engine/EventSink.cpp
@@ -0,0 +1,74 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 <iostream>
+#include "events/SendPortValueEvent.hpp"
+#include "EventSink.hpp"
+#include "PortImpl.hpp"
+
+using namespace std;
+
+namespace Ingen {
+
+#if 0
+void
+EventSink::control_change(Port* port, FrameTime time, float val)
+{
+ //cerr << "CONTROL CHANGE: " << port->path() << " == " << val << endl;
+ SendPortValueEvent ev(_engine, time, port, false, 0, val);
+ _events.write(sizeof(ev), (uchar*)&ev);
+}
+#endif
+
+/** \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 Ingen
diff --git a/src/engine/EventSink.hpp b/src/engine/EventSink.hpp
new file mode 100644
index 00000000..9e937d1a
--- /dev/null
+++ b/src/engine/EventSink.hpp
@@ -0,0 +1,64 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 EVENTSINK_H
+#define EVENTSINK_H
+
+#include <list>
+#include <utility>
+#include <raul/RingBuffer.hpp>
+#include "events/SendPortValueEvent.hpp"
+#include "types.hpp"
+
+namespace Ingen {
+
+class PortImpl;
+class Engine;
+class SendPortValueEvent;
+
+
+/** 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) {}
+
+ /* FIXME: Figure out variable sized event queues and make this a generic
+ * interface (ie don't add a method for every event type, crap..) */
+
+ bool write(uint32_t size, const Event* ev);
+
+ bool read(uint32_t event_buffer_size, uint8_t* event_buffer);
+
+private:
+ Engine& _engine;
+ Raul::RingBuffer<uchar> _events;
+};
+
+
+
+} // namespace Ingen
+
+#endif // EVENTSINK_H
+
diff --git a/src/engine/EventSource.hpp b/src/engine/EventSource.hpp
new file mode 100644
index 00000000..68532d16
--- /dev/null
+++ b/src/engine/EventSource.hpp
@@ -0,0 +1,60 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 EVENTSOURCE_H
+#define EVENTSOURCE_H
+
+#include "types.hpp"
+
+namespace Ingen {
+
+class Event;
+class QueuedEvent;
+class PostProcessor;
+
+
+/** Source for events to run in the audio thread.
+ *
+ * The AudioDriver 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).
+ *
+ * There are two distinct classes of events - "queued" and "stamped". Queued
+ * events are events that require non-realtime pre-processing before being
+ * executed in the process thread. Stamped events are timestamped realtime
+ * events that require no pre-processing and can be executed immediately
+ * (with sample accuracy).
+ */
+class EventSource
+{
+public:
+ virtual ~EventSource() {}
+
+ virtual void activate() = 0;
+ virtual void deactivate() = 0;
+
+ virtual void process(PostProcessor& dest, ProcessContext& context) = 0;
+
+protected:
+ size_t _capacity;
+};
+
+
+} // namespace Ingen
+
+#endif // EVENTSOURCE_H
+
diff --git a/src/engine/GraphObjectImpl.cpp b/src/engine/GraphObjectImpl.cpp
new file mode 100644
index 00000000..0e1abc57
--- /dev/null
+++ b/src/engine/GraphObjectImpl.cpp
@@ -0,0 +1,39 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "GraphObjectImpl.hpp"
+#include "PatchImpl.hpp"
+#include "EngineStore.hpp"
+
+namespace Ingen {
+
+
+PatchImpl*
+GraphObjectImpl::parent_patch() const
+{
+ return dynamic_cast<PatchImpl*>((NodeImpl*)_parent);
+}
+
+
+SharedPtr<GraphObject>
+GraphObjectImpl::find_child(const string& name) const
+{
+ throw;
+}
+
+
+} // namespace Ingen
diff --git a/src/engine/GraphObjectImpl.hpp b/src/engine/GraphObjectImpl.hpp
new file mode 100644
index 00000000..9b1f675d
--- /dev/null
+++ b/src/engine/GraphObjectImpl.hpp
@@ -0,0 +1,132 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 GRAPHOBJECTIMPL_H
+#define GRAPHOBJECTIMPL_H
+
+#include <string>
+#include <map>
+#include <cstddef>
+#include <cassert>
+#include <raul/Deletable.hpp>
+#include <raul/Path.hpp>
+#include <raul/Atom.hpp>
+#include "interface/GraphObject.hpp"
+#include "types.hpp"
+
+using Raul::Atom;
+using Raul::Path;
+using Raul::Symbol;
+
+namespace Raul { class Maid; }
+
+namespace Ingen {
+
+class PatchImpl;
+class ProcessContext;
+
+
+/** 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 Ingen::Shared::GraphObject
+{
+public:
+ virtual ~GraphObjectImpl() {}
+
+ bool polyphonic() const { return _polyphonic; }
+ virtual bool set_polyphonic(Raul::Maid& maid, bool p) { _polyphonic = p; return true; }
+
+ GraphObject* graph_parent() const { return _parent; }
+
+ inline GraphObjectImpl* parent() const { return _parent; }
+ const Symbol symbol() const { return _name; }
+
+ virtual void process(ProcessContext& context) = 0;
+
+ /** Rename */
+ virtual void set_path(const Path& new_path) {
+ assert(new_path.parent() == path().parent());
+ _name = new_path.name();
+ assert(_name.find("/") == std::string::npos);
+ }
+
+ void set_variable(const std::string& key, const Atom& value)
+ { _variables[key] = value; }
+
+ void set_property(const std::string& key, const Atom& value)
+ { _properties[key] = value; }
+
+ const Atom& get_variable(const std::string& key) {
+ static Atom null_atom;
+ Variables::iterator i = _variables.find(key);
+ return (i != _variables.end()) ? (*i).second : null_atom;
+ }
+
+ const Atom& get_property(const std::string& key) {
+ static Atom null_atom;
+ Properties::iterator i = _properties.find(key);
+ return (i != _properties.end()) ? (*i).second : null_atom;
+ }
+
+ const Variables& variables() const { return _variables; }
+ const Properties& properties() const { return _properties; }
+ Variables& variables() { return _variables; }
+ Properties& properties() { return _properties; }
+
+ /** The Patch this object is a child of. */
+ virtual PatchImpl* parent_patch() const;
+
+ /** Path is dynamically generated from parent to ease renaming */
+ const Path path() const {
+ if (_parent == NULL)
+ return Path(std::string("/").append(_name));
+ else if (_parent->path() == "/")
+ return Path(std::string("/").append(_name));
+ else
+ return Path(_parent->path() +"/"+ _name);
+ }
+
+ SharedPtr<GraphObject> find_child(const std::string& name) const;
+
+protected:
+ GraphObjectImpl(GraphObjectImpl* parent, const std::string& name, bool polyphonic=false)
+ : _parent(parent), _name(name), _polyphonic(polyphonic)
+ {
+ assert(parent == NULL || _name.length() > 0);
+ assert(_name.find("/") == std::string::npos);
+ assert(path().find("//") == std::string::npos);
+ }
+
+ GraphObjectImpl* _parent;
+ std::string _name;
+ bool _polyphonic;
+
+private:
+ Variables _variables;
+ Properties _properties;
+};
+
+
+} // namespace Ingen
+
+#endif // GRAPHOBJECTIMPL_H
diff --git a/src/engine/HTTPEngineReceiver.cpp b/src/engine/HTTPEngineReceiver.cpp
new file mode 100644
index 00000000..1b21e184
--- /dev/null
+++ b/src/engine/HTTPEngineReceiver.cpp
@@ -0,0 +1,207 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 <iostream>
+#include <cstdlib>
+#include <cstdio>
+#include <string>
+#include <boost/format.hpp>
+#include "types.hpp"
+#include <raul/SharedPtr.hpp>
+#include <raul/AtomLiblo.hpp>
+#include "interface/ClientInterface.hpp"
+#include "module/Module.hpp"
+#include "serialisation/serialisation.hpp"
+#include "serialisation/Serialiser.hpp"
+#include "serialisation/Parser.hpp"
+#include "engine/ThreadManager.hpp"
+#include "HTTPEngineReceiver.hpp"
+#include "QueuedEventSource.hpp"
+#include "ClientBroadcaster.hpp"
+#include "EngineStore.hpp"
+
+using namespace std;
+using namespace Ingen::Shared;
+
+namespace Ingen {
+
+
+HTTPEngineReceiver::HTTPEngineReceiver(Engine& engine, uint16_t port)
+ : QueuedEngineInterface(engine, 2, 2)
+ , _server(soup_server_new(SOUP_SERVER_PORT, port, NULL))
+{
+ _receive_thread = new ReceiveThread(*this);
+
+ soup_server_add_handler(_server, NULL, message_callback, this, NULL);
+
+ cout << "Started HTTP server on port " << soup_server_get_port(_server) << endl;
+ Thread::set_name("HTTP receiver");
+
+ if (!engine.world()->serialisation_module)
+ engine.world()->serialisation_module = Ingen::Shared::load_module("ingen_serialisation");
+
+ if (engine.world()->serialisation_module) {
+ if (!engine.world()->serialiser)
+ engine.world()->serialiser = SharedPtr<Serialiser>(
+ Ingen::Serialisation::new_serialiser(engine.world(), engine.engine_store()));
+
+ if (!engine.world()->parser)
+ engine.world()->parser = SharedPtr<Parser>(
+ Ingen::Serialisation::new_parser());
+ } else {
+ cerr << "WARNING: Failed to load ingen_serialisation module, HTTP disabled." << endl;
+ }
+}
+
+
+HTTPEngineReceiver::~HTTPEngineReceiver()
+{
+ deactivate();
+
+ if (_server != NULL) {
+ soup_server_quit(_server);
+ _server = NULL;
+ }
+}
+
+
+void
+HTTPEngineReceiver::activate()
+{
+ QueuedEventSource::activate();
+ _receive_thread->set_name("HTTP Receiver");
+ _receive_thread->start();
+}
+
+
+void
+HTTPEngineReceiver::deactivate()
+{
+ cout << "[HTTPEngineReceiver] Stopped HTTP listening thread" << endl;
+ _receive_thread->stop();
+ QueuedEventSource::deactivate();
+}
+
+
+void
+HTTPEngineReceiver::message_callback(SoupServer* server, SoupMessage* msg, const char* path,
+ GHashTable *query, SoupClientContext* client, void* data)
+{
+ HTTPEngineReceiver* me = (HTTPEngineReceiver*)data;
+
+ SharedPtr<Store> store = me->_engine.world()->store;
+ if (!store) {
+ soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ if (!Path::is_valid(path)) {
+ 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;
+ }
+
+#if 0
+ SoupMessageHeaders* in_head = msg->request_headers;
+ const char* str = soup_message_headers_get(in_head, "Accept");
+ cout << "Accept: " << str << endl;
+#endif
+
+ // Serialise object
+ const string response = serialiser->to_string(start->second,
+ "http://example.org", GraphObject::Variables());
+
+#if 0
+ FILE* xhtml_file = fopen("/home/dave/ingen_ui.xhtml", "r");
+ string response;
+ while (!feof(xhtml_file)) {
+ int c = fgetc(xhtml_file);
+ if (c != EOF)
+ response += (char)c;
+ }
+ fclose(xhtml_file);
+#endif
+
+ soup_message_set_status (msg, SOUP_STATUS_OK);
+ soup_message_set_response (msg, "text/plain", SOUP_MEMORY_COPY,
+ response.c_str(), response.length());
+
+ } else if (msg->method == SOUP_METHOD_PUT) {
+ Glib::RWLock::WriterLock lock(store->lock());
+
+ // Be sure object doesn't exist
+ Store::const_iterator start = store->find(path);
+ if (start != store->end()) {
+ soup_message_set_status (msg, SOUP_STATUS_CONFLICT);
+ return;
+ }
+
+ // Get parser
+ SharedPtr<Parser> parser = me->_engine.world()->parser;
+ if (!parser) {
+ soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ //cout << "POST: " << msg->request_body->data << endl;
+
+ // Load object
+ soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
+ } else if (msg->method == SOUP_METHOD_POST) {
+ //cout << "PUT: " << msg->request_body->data << endl;
+ soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
+ } 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 Ingen
diff --git a/src/engine/HTTPEngineReceiver.hpp b/src/engine/HTTPEngineReceiver.hpp
new file mode 100644
index 00000000..34c425b2
--- /dev/null
+++ b/src/engine/HTTPEngineReceiver.hpp
@@ -0,0 +1,59 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2008 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 HTTPENGINERECEIVER_H
+#define HTTPENGINERECEIVER_H
+
+#include CONFIG_H_PATH
+#include <string>
+#include <stdint.h>
+#include <libsoup/soup.h>
+#include <raul/SharedPtr.hpp>
+#include "QueuedEngineInterface.hpp"
+
+namespace Ingen {
+
+class HTTPEngineReceiver : public QueuedEngineInterface
+{
+public:
+ HTTPEngineReceiver(Engine& engine, uint16_t port);
+ ~HTTPEngineReceiver();
+
+ void activate();
+ void deactivate();
+
+private:
+ struct ReceiveThread : public Raul::Thread {
+ ReceiveThread(HTTPEngineReceiver& receiver) : _receiver(receiver) {}
+ virtual void _run();
+ 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 Ingen
+
+#endif // HTTPENGINERECEIVER_H
diff --git a/src/engine/InputPort.cpp b/src/engine/InputPort.cpp
new file mode 100644
index 00000000..cf5501ca
--- /dev/null
+++ b/src/engine/InputPort.cpp
@@ -0,0 +1,297 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 <iostream>
+#include <cstdlib>
+#include <cassert>
+#include "AudioBuffer.hpp"
+#include "EventBuffer.hpp"
+#include "ConnectionImpl.hpp"
+#include "OutputPort.hpp"
+#include "NodeImpl.hpp"
+#include "ProcessContext.hpp"
+#include "util.hpp"
+
+using namespace std;
+
+namespace Ingen {
+
+
+InputPort::InputPort(NodeImpl* parent,
+ const string& name,
+ uint32_t index,
+ uint32_t poly,
+ DataType type,
+ const Atom& value,
+ size_t buffer_size)
+ : PortImpl(parent, name, index, poly, type, value, buffer_size)
+{
+}
+
+
+void
+InputPort::set_buffer_size(size_t size)
+{
+ PortImpl::set_buffer_size(size);
+ assert(_buffer_size = size);
+
+ for (Connections::iterator c = _connections.begin(); c != _connections.end(); ++c)
+ ((ConnectionImpl*)c->get())->set_buffer_size(size);
+
+}
+
+
+bool
+InputPort::prepare_poly(uint32_t poly)
+{
+ PortImpl::prepare_poly(poly);
+
+ for (Connections::iterator c = _connections.begin(); c != _connections.end(); ++c)
+ ((ConnectionImpl*)c->get())->prepare_poly(poly);
+
+ connect_buffers();
+ return true;
+}
+
+
+bool
+InputPort::apply_poly(Raul::Maid& maid, uint32_t poly)
+{
+ if (!_polyphonic || !_parent->polyphonic())
+ return true;
+
+ for (Connections::iterator c = _connections.begin(); c != _connections.end(); ++c)
+ ((ConnectionImpl*)c->get())->apply_poly(maid, poly);
+
+ PortImpl::apply_poly(maid, poly);
+ assert(this->poly() == poly);
+
+ if (_connections.size() == 1) {
+ ConnectionImpl* c = _connections.begin()->get();
+ for (uint32_t i=0; i < _poly; ++i)
+ _buffers->at(i)->join(c->buffer(i));
+ }
+
+ connect_buffers();
+
+ 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 mixing needs to take place.
+ */
+void
+InputPort::add_connection(Connections::Node* const c)
+{
+ _connections.push_back(c);
+
+ bool modify_buffers = !_fixed_buffers;
+
+ if (modify_buffers) {
+ if (_connections.size() == 1) {
+ // Use buffer directly to avoid copying
+ for (uint32_t i=0; i < _poly; ++i) {
+ _buffers->at(i)->join(c->elem()->buffer(i));
+ }
+ } else if (_connections.size() == 2) {
+ // Used to directly use single connection buffer, now there's two
+ // so have to use local ones again and mix down
+ for (uint32_t i=0; i < _poly; ++i) {
+ _buffers->at(i)->unjoin();
+ }
+ }
+ PortImpl::connect_buffers();
+ }
+
+ // Automatically broadcast connected control inputs
+ if (_type == DataType::CONTROL)
+ _broadcast = true;
+}
+
+
+/** Remove a connection. Realtime safe.
+ */
+InputPort::Connections::Node*
+InputPort::remove_connection(const OutputPort* src_port)
+{
+ bool modify_buffers = !_fixed_buffers;
+
+ bool found = false;
+ Connections::Node* connection = NULL;
+ for (Connections::iterator i = _connections.begin(); i != _connections.end(); ++i) {
+ if ((*i)->src_port()->path() == src_port->path()) {
+ connection = _connections.erase(i);
+ found = true;
+ }
+ }
+
+ if ( ! found) {
+ cerr << "WARNING: [InputPort::remove_connection] Connection not found !" << endl;
+ exit(EXIT_FAILURE);
+ } else {
+ if (_connections.size() == 0) {
+ for (uint32_t i=0; i < _poly; ++i) {
+ // Use a local buffer
+ if (modify_buffers)
+ _buffers->at(i)->unjoin();
+ _buffers->at(i)->clear(); // Write silence
+ }
+ } else if (modify_buffers && _connections.size() == 1) {
+ // Share a buffer
+ for (uint32_t i=0; i < _poly; ++i) {
+ _buffers->at(i)->join((*_connections.begin())->buffer(i));
+ }
+ }
+ }
+
+ if (modify_buffers)
+ PortImpl::connect_buffers();
+
+ // Turn off broadcasting if we're not connected any more (FIXME: not quite right..)
+ if (_type == DataType::CONTROL && _connections.size() == 0)
+ _broadcast = false;
+
+ return connection;
+}
+
+
+/** Returns whether this port is connected to the passed port.
+ */
+/*bool
+InputPort::is_connected_to(const OutputPort* port) const
+{
+ for (Connections::const_iterator i = _connections.begin(); i != _connections.end(); ++i)
+ if ((*i)->src_port() == port)
+ return true;
+
+ return false;
+}*/
+
+
+/** Prepare buffer for access, mixing if necessary. Realtime safe.
+ * FIXME: nframes parameter not used,
+ */
+void
+InputPort::pre_process(ProcessContext& context)
+{
+ // If value has been set (e.g. events pushed) by the user,
+ // don't do anything this cycle to avoid smashing the value
+ if (_set_by_user)
+ return;
+
+ bool do_mixdown = true;
+
+ if (_connections.size() == 0) {
+ for (uint32_t i=0; i < _poly; ++i)
+ buffer(i)->prepare_read(context.start(), context.nframes());
+ return;
+ }
+
+ for (Connections::iterator c = _connections.begin(); c != _connections.end(); ++c)
+ (*c)->process(context);
+
+ if ( ! _fixed_buffers) {
+ // If only one connection, try to use buffer directly (zero copy)
+ if (_connections.size() == 1) {
+ for (uint32_t i=0; i < _poly; ++i) {
+ //cerr << path() << " joining to " << (*_connections.begin())->buffer(i) << endl;
+ _buffers->at(i)->join((*_connections.begin())->buffer(i));
+ }
+ do_mixdown = false;
+ }
+ connect_buffers();
+ } else {
+ do_mixdown = true;
+ }
+
+ for (uint32_t i=0; i < _poly; ++i)
+ buffer(i)->prepare_read(context.start(), context.nframes());
+
+ /*cerr << path() << " poly = " << _poly << ", mixdown: " << do_mixdown
+ << ", fixed buffers: " << _fixed_buffers << ", joined: " << _buffers->at(0)->is_joined()
+ << " to " << _buffers->at(0)->joined_buffer() << endl;*/
+
+ /*if (type() == DataType::EVENT)
+ for (uint32_t i=0; i < _poly; ++i)
+ cerr << path() << " (" << buffer(i) << ") # events: "
+ << ((EventBuffer*)buffer(i))->event_count()
+ << ", joined: " << _buffers->at(i)->is_joined() << endl;*/
+
+ if (!do_mixdown) {
+ /*#ifndef NDEBUG
+ for (uint32_t i=0; i < _poly; ++i)
+ assert(buffer(i) == (*_connections.begin())->buffer(i));
+ #endif*/
+ return;
+ }
+
+ if (_type == DataType::CONTROL || _type == DataType::AUDIO) {
+ for (uint32_t voice=0; voice < _poly; ++voice) {
+ // Copy first connection
+ buffer(voice)->copy(
+ (*_connections.begin())->buffer(voice), 0, _buffer_size-1);
+
+ // Accumulate the rest
+ if (_connections.size() > 1) {
+
+ Connections::iterator c = _connections.begin();
+
+ for (++c; c != _connections.end(); ++c)
+ ((AudioBuffer*)buffer(voice))->accumulate(
+ ((AudioBuffer*)(*c)->buffer(voice)), 0, _buffer_size-1);
+ }
+ }
+ } else {
+ assert(_poly == 1);
+
+ // FIXME
+ if (_connections.size() > 1)
+ cerr << "WARNING: MIDI mixing not implemented, only first connection used." << endl;
+
+ // Copy first connection
+ _buffers->at(0)->copy(
+ (*_connections.begin())->buffer(0), 0, _buffer_size-1);
+ }
+}
+
+
+void
+InputPort::post_process(ProcessContext& context)
+{
+ broadcast(context);
+
+ // Prepare for next cycle
+ for (uint32_t i=0; i < _poly; ++i)
+ buffer(i)->prepare_write(context.start(), context.nframes());
+
+ _set_by_user = false;
+
+ /*if (_broadcast && (_type == DataType::CONTROL)) {
+ const Sample value = ((AudioBuffer*)(*_buffers)[0])->value_at(0);
+
+ cerr << path() << " input post: buffer: " << buffer(0) << " value = "
+ << value << " (last " << _last_broadcasted_value << ")" <<endl;
+ }*/
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/InputPort.hpp b/src/engine/InputPort.hpp
new file mode 100644
index 00000000..2617882f
--- /dev/null
+++ b/src/engine/InputPort.hpp
@@ -0,0 +1,88 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 INPUTPORT_H
+#define INPUTPORT_H
+
+#include <string>
+#include <cstdlib>
+#include <cassert>
+#include <raul/List.hpp>
+#include <raul/SharedPtr.hpp>
+#include "PortImpl.hpp"
+using std::string;
+
+namespace Ingen {
+
+class ConnectionImpl;
+class OutputPort;
+class NodeImpl;
+
+
+/** 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(NodeImpl* parent,
+ const string& name,
+ uint32_t index,
+ uint32_t poly,
+ DataType type,
+ const Atom& value,
+ size_t buffer_size);
+
+ virtual ~InputPort() {}
+
+ typedef Raul::List< SharedPtr<ConnectionImpl> > Connections;
+
+ void add_connection(Connections::Node* c);
+ Connections::Node* remove_connection(const OutputPort* src_port);
+
+ const Connections& connections() { return _connections; }
+
+ bool prepare_poly(uint32_t poly);
+ bool apply_poly(Raul::Maid& maid, uint32_t poly);
+
+ void pre_process(ProcessContext& context);
+ void post_process(ProcessContext& context);
+
+ bool is_connected() const { return (_connections.size() > 0); }
+ //bool is_connected_to(const OutputPort* port) const;
+
+ bool is_input() const { return true; }
+ bool is_output() const { return false; }
+
+ virtual void set_buffer_size(size_t size);
+
+private:
+ Connections _connections;
+};
+
+
+} // namespace Ingen
+
+#endif // INPUTPORT_H
diff --git a/src/engine/InternalPlugin.cpp b/src/engine/InternalPlugin.cpp
new file mode 100644
index 00000000..1c6a92a5
--- /dev/null
+++ b/src/engine/InternalPlugin.cpp
@@ -0,0 +1,55 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "InternalPlugin.hpp"
+#include "MidiNoteNode.hpp"
+#include "MidiTriggerNode.hpp"
+#include "MidiControlNode.hpp"
+#include "TransportNode.hpp"
+#include "Engine.hpp"
+#include "AudioDriver.hpp"
+
+namespace Ingen {
+
+
+NodeImpl*
+InternalPlugin::instantiate(const string& name,
+ bool polyphonic,
+ Ingen::PatchImpl* parent,
+ Engine& engine)
+{
+ assert(_type == Internal);
+
+ SampleCount srate = engine.audio_driver()->sample_rate();
+ SampleCount buffer_size = engine.audio_driver()->buffer_size();
+
+ if (_uri == NS_INGEN "note_node") {
+ return new MidiNoteNode(name, polyphonic, parent, srate, buffer_size);
+ } else if (_uri == NS_INGEN "trigger_node") {
+ return new MidiTriggerNode(name, polyphonic, parent, srate, buffer_size);
+ } else if (_uri == NS_INGEN "control_node") {
+ return new MidiControlNode(name, polyphonic, parent, srate, buffer_size);
+ } else if (_uri == NS_INGEN "transport_node") {
+ return new TransportNode(name, polyphonic, parent, srate, buffer_size);
+ } else {
+ return NULL;
+ }
+}
+
+
+} // namespace Ingen
diff --git a/src/engine/InternalPlugin.hpp b/src/engine/InternalPlugin.hpp
new file mode 100644
index 00000000..c04c9015
--- /dev/null
+++ b/src/engine/InternalPlugin.hpp
@@ -0,0 +1,74 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 INTERNALPLUGIN_H
+#define INTERNALPLUGIN_H
+
+#include CONFIG_H_PATH
+
+#ifndef HAVE_SLV2
+#error "This file requires SLV2, but HAVE_SLV2 is not defined. Please report."
+#endif
+
+#include <cstdlib>
+#include <glibmm/module.h>
+#include <boost/utility.hpp>
+#include <dlfcn.h>
+#include <string>
+#include <iostream>
+#include <slv2/slv2.h>
+#include "types.hpp"
+#include "PluginImpl.hpp"
+
+#define NS_INGEN "http://drobilla.net/ns/ingen#"
+
+namespace Ingen {
+
+class NodeImpl;
+
+
+/** Implementation of an Internal plugin.
+ */
+class InternalPlugin : public PluginImpl
+{
+public:
+ InternalPlugin(const std::string& uri,
+ const std::string& symbol,
+ const std::string& name)
+ : PluginImpl(Plugin::Internal, uri)
+ , _symbol(symbol)
+ , _name(name)
+ {}
+
+ NodeImpl* instantiate(const std::string& name,
+ bool polyphonic,
+ Ingen::PatchImpl* parent,
+ Engine& engine);
+
+ const string symbol() const { return _symbol; }
+ const string name() const { return _name; }
+
+private:
+ const string _symbol;
+ const string _name;
+};
+
+
+} // namespace Ingen
+
+#endif // INTERNALPLUGIN_H
+
diff --git a/src/engine/JackAudioDriver.cpp b/src/engine/JackAudioDriver.cpp
new file mode 100644
index 00000000..eff04653
--- /dev/null
+++ b/src/engine/JackAudioDriver.cpp
@@ -0,0 +1,384 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "JackAudioDriver.hpp"
+#include CONFIG_H_PATH
+#include "tuning.hpp"
+#include <iostream>
+#include <cstdlib>
+#include <raul/List.hpp>
+#include "Engine.hpp"
+#include "util.hpp"
+#include "Event.hpp"
+#include "ThreadManager.hpp"
+#include "QueuedEvent.hpp"
+#include "EventSource.hpp"
+#include "PostProcessor.hpp"
+#include "NodeImpl.hpp"
+#include "PatchImpl.hpp"
+#include "PortImpl.hpp"
+#include "MidiDriver.hpp"
+#include "DuplexPort.hpp"
+#include "EventSource.hpp"
+#include "AudioBuffer.hpp"
+#include "ProcessSlave.hpp"
+
+using namespace std;
+
+namespace Ingen {
+
+
+//// JackAudioPort ////
+
+JackAudioPort::JackAudioPort(JackAudioDriver* driver, DuplexPort* patch_port)
+ : DriverPort(patch_port)
+ , Raul::List<JackAudioPort*>::Node(this)
+ , _driver(driver)
+ , _jack_port(NULL)
+ , _jack_buffer(NULL)
+{
+ assert(patch_port->poly() == 1);
+
+ _jack_port = jack_port_register(_driver->jack_client(),
+ patch_port->path().c_str(), JACK_DEFAULT_AUDIO_TYPE,
+ (patch_port->is_input()) ? JackPortIsInput : JackPortIsOutput,
+ 0);
+
+ if (_jack_port == NULL) {
+ cerr << "[JackAudioPort] ERROR: Failed to register port " << patch_port->path() << endl;
+ throw JackAudioDriver::PortRegistrationFailedException();
+ }
+
+ patch_port->buffer(0)->clear();
+ patch_port->fixed_buffers(true);
+}
+
+
+JackAudioPort::~JackAudioPort()
+{
+ jack_port_unregister(_driver->jack_client(), _jack_port);
+}
+
+
+void
+JackAudioPort::prepare_buffer(jack_nframes_t nframes)
+{
+ jack_sample_t* jack_buf = (jack_sample_t*)jack_port_get_buffer(_jack_port, nframes);
+
+ AudioBuffer* patch_buf = (AudioBuffer*)_patch_port->buffer(0);
+
+ //cerr << "[JACK] " << _patch_port->path() << " buffer: " << patch_buf << endl;
+
+ if (jack_buf != _jack_buffer) {
+ patch_buf->set_data(jack_buf);
+ _jack_buffer = jack_buf;
+ }
+
+ assert(patch_buf->data() == jack_buf);
+}
+
+
+//// JackAudioDriver ////
+
+JackAudioDriver::JackAudioDriver(Engine& engine,
+ std::string server_name,
+ jack_client_t* jack_client)
+ : _engine(engine)
+ , _jack_thread(NULL)
+ , _client(jack_client)
+ , _buffer_size(jack_client ? jack_get_buffer_size(jack_client) : 0)
+ , _sample_rate(jack_client ? jack_get_sample_rate(jack_client) : 0)
+ , _is_activated(false)
+ , _local_client(true) // FIXME
+ , _process_context(engine)
+ , _root_patch(NULL)
+{
+ if (!_client) {
+ // Try supplied server name
+ if (server_name != "") {
+ _client = jack_client_open("Ingen", JackServerName, NULL, server_name.c_str());
+ if (_client)
+ cerr << "[JackAudioDriver] 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) {
+ _client = jack_client_open("Ingen", JackNullOption, NULL);
+
+ if (_client)
+ cerr << "[JackAudioDriver] Connected to default JACK server." << endl;
+ }
+
+ // Still failed
+ if (!_client) {
+ cerr << "[JackAudioDriver] Unable to connect to Jack. Exiting." << endl;
+ exit(EXIT_FAILURE);
+ }
+
+ _buffer_size = 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, buffer_size_cb, this);
+}
+
+
+JackAudioDriver::~JackAudioDriver()
+{
+ deactivate();
+
+ if (_local_client)
+ jack_client_close(_client);
+}
+
+
+void
+JackAudioDriver::activate()
+{
+ if (_is_activated) {
+ cerr << "[JackAudioDriver] Jack driver already activated." << endl;
+ return;
+ }
+
+ jack_set_process_callback(_client, process_cb, this);
+
+ _is_activated = true;
+
+ if (jack_activate(_client)) {
+ cerr << "[JackAudioDriver] Could not activate Jack client, aborting." << endl;
+ exit(EXIT_FAILURE);
+ } else {
+ cout << "[JackAudioDriver] Activated Jack client." << endl;
+/*#ifdef HAVE_LASH
+ _engine.lash_driver()->set_jack_client_name(jack_client_get_name(_client));
+#endif*/
+ }
+}
+
+
+void
+JackAudioDriver::deactivate()
+{
+ if (_is_activated) {
+
+ //for (Raul::List<JackAudioPort*>::iterator i = _ports.begin(); i != _ports.end(); ++i)
+ // jack_port_unregister(_client, (*i)->jack_port());
+
+ jack_deactivate(_client);
+ _is_activated = false;
+
+ _ports.clear();
+
+ cout << "[JackAudioDriver] Deactivated Jack client." << endl;
+
+ //_engine.post_processor()->stop();
+ }
+}
+
+
+/** 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
+JackAudioDriver::add_port(DriverPort* port)
+{
+ assert(ThreadManager::current_thread_id() == THREAD_PROCESS);
+
+ assert(dynamic_cast<JackAudioPort*>(port));
+ _ports.push_back((JackAudioPort*)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.
+ */
+DriverPort*
+JackAudioDriver::remove_port(const Path& path)
+{
+ assert(ThreadManager::current_thread_id() == THREAD_PROCESS);
+
+ for (Raul::List<JackAudioPort*>::iterator i = _ports.begin(); i != _ports.end(); ++i)
+ if ((*i)->patch_port()->path() == path)
+ return _ports.erase(i)->elem(); // FIXME: LEAK
+
+ cerr << "[JackAudioDriver::remove_port] WARNING: Unable to find Jack port " << path << endl;
+ return NULL;
+}
+
+
+DriverPort*
+JackAudioDriver::port(const Path& path)
+{
+ for (Raul::List<JackAudioPort*>::iterator i = _ports.begin(); i != _ports.end(); ++i)
+ if ((*i)->patch_port()->path() == path)
+ return (*i);
+
+ return NULL;
+}
+
+
+DriverPort*
+JackAudioDriver::create_port(DuplexPort* patch_port)
+{
+ try {
+ if (patch_port->buffer_size() == _buffer_size)
+ return new JackAudioPort(this, patch_port);
+ else
+ return NULL;
+ } catch (...) {
+ return NULL;
+ }
+}
+
+
+DriverPort*
+JackAudioDriver::driver_port(const Path& path)
+{
+ assert(ThreadManager::current_thread_id() == THREAD_PROCESS);
+
+ for (Raul::List<JackAudioPort*>::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
+JackAudioDriver::_process_cb(jack_nframes_t nframes)
+{
+ if (nframes == 0 || ! _is_activated)
+ return 0;
+
+ // FIXME: all of this time stuff is screwy
+
+ // FIXME: support nframes != buffer_size, even though that never damn well happens
+ assert(nframes == _buffer_size);
+
+ // Jack can elect to not call this function for a cycle, if overloaded
+ // FIXME: this doesn't make sense, and the start time isn't used anyway
+ const jack_nframes_t start_of_current_cycle = jack_last_frame_time(_client);
+
+ const jack_nframes_t end_of_current_cycle = start_of_current_cycle + nframes;
+#ifndef NDEBUG
+ // FIXME: support changing cycle length
+ const jack_nframes_t start_of_last_cycle = start_of_current_cycle - nframes;
+ assert(start_of_current_cycle - start_of_last_cycle == nframes);
+#endif
+
+ _transport_state = jack_transport_query(_client, &_position);
+
+ _process_context.set_time_slice(nframes, start_of_current_cycle, end_of_current_cycle);
+
+ for (Engine::ProcessSlaves::iterator i = _engine.process_slaves().begin();
+ i != _engine.process_slaves().end(); ++i) {
+ (*i)->context().set_time_slice(nframes, start_of_current_cycle, end_of_current_cycle);
+ }
+
+ // Process events that came in during the last cycle
+ // (Aiming for jitter-free 1 block event latency, ideally)
+ _engine.process_events(_process_context);
+
+ // Set buffers of patch ports to Jack port buffers (zero-copy processing)
+ for (Raul::List<JackAudioPort*>::iterator i = _ports.begin(); i != _ports.end(); ++i) {
+ assert(*i);
+ (*i)->prepare_buffer(nframes);
+ }
+
+ assert(_engine.midi_driver());
+ _engine.midi_driver()->pre_process(_process_context);
+
+ // Run root patch
+ if (_root_patch)
+ _root_patch->process(_process_context);
+
+ _engine.midi_driver()->post_process(_process_context);
+
+ _engine.post_processor()->set_end_time(_process_context.end());
+
+ return 0;
+}
+
+
+void
+JackAudioDriver::_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);
+ assert(ThreadManager::current_thread_id() == THREAD_PROCESS);
+}
+
+void
+JackAudioDriver::_shutdown_cb()
+{
+ cout << "[JackAudioDriver] Jack shutdown. Exiting." << endl;
+ _engine.quit();
+ _jack_thread = NULL;
+}
+
+
+int
+JackAudioDriver::_sample_rate_cb(jack_nframes_t nframes)
+{
+ if (_is_activated) {
+ cerr << "[JackAudioDriver] On-the-fly sample rate changing not supported (yet). Aborting." << endl;
+ exit(EXIT_FAILURE);
+ } else {
+ _sample_rate = nframes;
+ }
+ return 0;
+}
+
+
+int
+JackAudioDriver::_buffer_size_cb(jack_nframes_t nframes)
+{
+ if (_root_patch) {
+ _root_patch->set_buffer_size(nframes);
+ _buffer_size = nframes;
+ }
+ return 0;
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/JackAudioDriver.hpp b/src/engine/JackAudioDriver.hpp
new file mode 100644
index 00000000..3beb775a
--- /dev/null
+++ b/src/engine/JackAudioDriver.hpp
@@ -0,0 +1,185 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 JACKAUDIODRIVER_H
+#define JACKAUDIODRIVER_H
+
+#include <jack/jack.h>
+#include <jack/transport.h>
+#include <raul/Thread.hpp>
+#include <raul/Path.hpp>
+#include <raul/List.hpp>
+#include "AudioDriver.hpp"
+#include "Buffer.hpp"
+#include "ProcessContext.hpp"
+
+namespace Ingen {
+
+class Engine;
+class PatchImpl;
+class PortImpl;
+class DuplexPort;
+class JackAudioDriver;
+typedef jack_default_audio_sample_t jack_sample_t;
+
+
+/** Used internally by JackAudioDriver to represent a Jack port.
+ *
+ * A Jack port always has a one-to-one association with a Patch port.
+ */
+class JackAudioPort : public DriverPort, public Raul::List<JackAudioPort*>::Node
+{
+public:
+ JackAudioPort(JackAudioDriver* driver, DuplexPort* patch_port);
+ ~JackAudioPort();
+
+ void set_name(const std::string& name) { jack_port_set_name(_jack_port, name.c_str()); };
+
+ void prepare_buffer(jack_nframes_t nframes);
+
+ jack_port_t* jack_port() const { return _jack_port; }
+
+private:
+ JackAudioDriver* _driver;
+ jack_port_t* _jack_port;
+ jack_sample_t* _jack_buffer; ///< Cached for output ports
+};
+
+
+
+/** The Jack AudioDriver.
+ *
+ * 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 JackAudioDriver : public AudioDriver
+{
+public:
+ JackAudioDriver(Engine& engine,
+ std::string server_name = "",
+ jack_client_t* jack_client = 0);
+
+ ~JackAudioDriver();
+
+ 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* remove_port(const Raul::Path& path);
+
+ DriverPort* driver_port(const Raul::Path& path);
+
+ 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 buffer_size() const { return _buffer_size; }
+ SampleCount sample_rate() const { return _sample_rate; }
+ bool is_activated() const { return _is_activated; }
+
+ inline SampleCount frame_time() const { return jack_frame_time(_client); }
+
+ class PortRegistrationFailedException : public std::exception {};
+
+private:
+ friend class JackAudioPort;
+
+ // These are the static versions of the callbacks, they call
+ // the non-static ones below
+ inline static void thread_init_cb(void* const jack_driver);
+ inline static void shutdown_cb(void* const jack_driver);
+ inline static int process_cb(jack_nframes_t nframes, void* const jack_driver);
+ inline static int buffer_size_cb(jack_nframes_t nframes, void* const jack_driver);
+ inline static int sample_rate_cb(jack_nframes_t nframes, void* const jack_driver);
+
+ // Non static callbacks
+ void _thread_init_cb();
+ void _shutdown_cb();
+ int _process_cb(jack_nframes_t nframes);
+ int _buffer_size_cb(jack_nframes_t nframes);
+ int _sample_rate_cb(jack_nframes_t nframes);
+
+ Engine& _engine;
+ Raul::Thread* _jack_thread;
+ jack_client_t* _client;
+ jack_nframes_t _buffer_size;
+ jack_nframes_t _sample_rate;
+ bool _is_activated;
+ bool _local_client; ///< Whether _client should be closed on destruction
+ jack_position_t _position;
+ jack_transport_state_t _transport_state;
+
+ Raul::List<JackAudioPort*> _ports;
+ ProcessContext _process_context;
+
+ PatchImpl* _root_patch;
+};
+
+
+inline int JackAudioDriver::process_cb(jack_nframes_t nframes, void* jack_driver)
+{
+ assert(jack_driver);
+ return ((JackAudioDriver*)jack_driver)->_process_cb(nframes);
+}
+
+inline void JackAudioDriver::thread_init_cb(void* jack_driver)
+{
+ assert(jack_driver);
+ return ((JackAudioDriver*)jack_driver)->_thread_init_cb();
+}
+
+inline void JackAudioDriver::shutdown_cb(void* jack_driver)
+{
+ assert(jack_driver);
+ return ((JackAudioDriver*)jack_driver)->_shutdown_cb();
+}
+
+
+inline int JackAudioDriver::buffer_size_cb(jack_nframes_t nframes, void* jack_driver)
+{
+ assert(jack_driver);
+ return ((JackAudioDriver*)jack_driver)->_buffer_size_cb(nframes);
+}
+
+
+inline int JackAudioDriver::sample_rate_cb(jack_nframes_t nframes, void* jack_driver)
+{
+ assert(jack_driver);
+ return ((JackAudioDriver*)jack_driver)->_sample_rate_cb(nframes);
+}
+
+
+} // namespace Ingen
+
+#endif // JACKAUDIODRIVER_H
diff --git a/src/engine/JackMidiDriver.cpp b/src/engine/JackMidiDriver.cpp
new file mode 100644
index 00000000..9e236541
--- /dev/null
+++ b/src/engine/JackMidiDriver.cpp
@@ -0,0 +1,267 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 <iostream>
+#include <cstdlib>
+#include <pthread.h>
+#include <raul/Maid.hpp>
+#include <raul/midi_events.h>
+#include "types.hpp"
+#include "JackMidiDriver.hpp"
+#include "JackAudioDriver.hpp"
+#include "ThreadManager.hpp"
+#include "AudioDriver.hpp"
+#include "EventBuffer.hpp"
+#include "DuplexPort.hpp"
+#include "ProcessContext.hpp"
+#include "jack_compat.h"
+using namespace std;
+
+namespace Ingen {
+
+
+//// JackMidiPort ////
+
+JackMidiPort::JackMidiPort(JackMidiDriver* driver, DuplexPort* patch_port)
+ : DriverPort(patch_port)
+ , Raul::List<JackMidiPort*>::Node(this)
+ , _driver(driver)
+ , _jack_port(NULL)
+{
+ assert(patch_port->poly() == 1);
+
+ _jack_port = jack_port_register(_driver->jack_client(),
+ patch_port->path().c_str(), JACK_DEFAULT_MIDI_TYPE,
+ (patch_port->is_input()) ? JackPortIsInput : JackPortIsOutput,
+ 0);
+
+ if (_jack_port == NULL) {
+ cerr << "[JackMidiPort] ERROR: Failed to register port " << patch_port->path() << endl;
+ throw JackAudioDriver::PortRegistrationFailedException();
+ }
+
+ patch_port->buffer(0)->clear();
+}
+
+
+JackMidiPort::~JackMidiPort()
+{
+ jack_port_unregister(_driver->jack_client(), _jack_port);
+}
+
+
+/** Prepare input for a block before a cycle is run, in the audio thread.
+ *
+ * This is simple since Jack MIDI is in-band with audio.
+ */
+void
+JackMidiPort::pre_process(ProcessContext& context)
+{
+ if ( ! is_input() )
+ return;
+
+ assert(_patch_port->poly() == 1);
+
+ EventBuffer* patch_buf = dynamic_cast<EventBuffer*>(_patch_port->buffer(0));
+ assert(patch_buf);
+
+ void* jack_buffer = jack_port_get_buffer(_jack_port, context.nframes());
+ const jack_nframes_t event_count = jack_midi_get_event_count(jack_buffer);
+
+ patch_buf->prepare_write(context.start(), context.nframes());
+
+ // 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_buffer, i);
+
+ // FIXME: type is hardcoded for now, we should get it from
+ // the type map instead
+ const bool success = patch_buf->append(ev.time, 0, 1, ev.size, ev.buffer);
+ if (!success)
+ cerr << "WARNING: Failed to write MIDI to port buffer, event(s) lost!" << endl;
+ }
+
+ //if (event_count)
+ // cerr << "Jack MIDI got " << event_count << " events." << endl;
+}
+
+
+/** Prepare output for a block after a cycle is run, in the audio thread.
+ *
+ * This is simple since Jack MIDI is in-band with audio.
+ */
+void
+JackMidiPort::post_process(ProcessContext& context)
+{
+ if (is_input())
+ return;
+
+ EventBuffer* patch_buf = dynamic_cast<EventBuffer*>(_patch_port->buffer(0));
+ void* jack_buf = jack_port_get_buffer(_jack_port, context.nframes());
+
+ assert(_patch_port->poly() == 1);
+ assert(patch_buf);
+
+ patch_buf->prepare_read(context.start(), context.nframes());
+ 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);
+ }
+
+ //if (event_count)
+ // cerr << "Jack MIDI wrote " << event_count << " events." << endl;
+}
+
+
+
+//// JackMidiDriver ////
+
+
+bool JackMidiDriver::_midi_thread_exit_flag = true;
+
+
+JackMidiDriver::JackMidiDriver(jack_client_t* client)
+ : _client(client)
+ , _is_activated(false)
+ , _is_enabled(false)
+{
+}
+
+
+JackMidiDriver::~JackMidiDriver()
+{
+ deactivate();
+}
+
+
+/** Launch and start the MIDI thread.
+ */
+void
+JackMidiDriver::activate()
+{
+ _is_activated = true;
+}
+
+
+/** Terminate the MIDI thread.
+ */
+void
+JackMidiDriver::deactivate()
+{
+ _is_activated = false;
+}
+
+
+/** Build flat arrays of events to be used as input for the given cycle.
+ */
+void
+JackMidiDriver::pre_process(ProcessContext& context)
+{
+ for (Raul::List<JackMidiPort*>::iterator i = _in_ports.begin(); i != _in_ports.end(); ++i)
+ (*i)->pre_process(context);
+}
+
+
+/** Write the output from any (top-level, exported) MIDI output ports to Jack ports.
+ */
+void
+JackMidiDriver::post_process(ProcessContext& context)
+{
+ for (Raul::List<JackMidiPort*>::iterator i = _out_ports.begin(); i != _out_ports.end(); ++i)
+ (*i)->post_process(context);
+}
+
+
+/** Add an Jack MIDI 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 new_port() and remove_port().
+ */
+void
+JackMidiDriver::add_port(DriverPort* port)
+{
+ assert(ThreadManager::current_thread_id() == THREAD_PROCESS);
+ assert(dynamic_cast<JackMidiPort*>(port));
+
+ if (port->is_input())
+ _in_ports.push_back((JackMidiPort*)port);
+ else
+ _out_ports.push_back((JackMidiPort*)port);
+}
+
+
+/** Remove an Jack MIDI 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.
+ */
+DriverPort*
+JackMidiDriver::remove_port(const Path& path)
+{
+ assert(ThreadManager::current_thread_id() == THREAD_PROCESS);
+
+ // FIXME: duplex?
+
+ for (Raul::List<JackMidiPort*>::iterator i = _in_ports.begin(); i != _in_ports.end(); ++i)
+ if ((*i)->patch_port()->path() == path)
+ return _in_ports.erase(i)->elem(); // FIXME: LEAK
+
+ for (Raul::List<JackMidiPort*>::iterator i = _out_ports.begin(); i != _out_ports.end(); ++i)
+ if ((*i)->patch_port()->path() == path)
+ return _out_ports.erase(i)->elem(); // FIXME: LEAK
+
+ cerr << "[JackMidiDriver::remove_input] WARNING: Unable to find Jack port " << path << endl;
+ return NULL;
+}
+
+
+DriverPort*
+JackMidiDriver::driver_port(const Path& path)
+{
+ assert(ThreadManager::current_thread_id() == THREAD_PROCESS);
+
+ // FIXME: duplex?
+
+ for (Raul::List<JackMidiPort*>::iterator i = _in_ports.begin(); i != _in_ports.end(); ++i)
+ if ((*i)->patch_port()->path() == path)
+ return (*i);
+
+ for (Raul::List<JackMidiPort*>::iterator i = _out_ports.begin(); i != _out_ports.end(); ++i)
+ if ((*i)->patch_port()->path() == path)
+ return (*i);
+
+ return NULL;
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/JackMidiDriver.hpp b/src/engine/JackMidiDriver.hpp
new file mode 100644
index 00000000..7889c4fa
--- /dev/null
+++ b/src/engine/JackMidiDriver.hpp
@@ -0,0 +1,113 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 JACKMIDIDRIVER_H
+#define JACKMIDIDRIVER_H
+
+#include <jack/jack.h>
+#include <jack/midiport.h>
+#include <raul/List.hpp>
+#include CONFIG_H_PATH
+#include "MidiDriver.hpp"
+
+namespace Ingen {
+
+class NodeImpl;
+class SetPortValueEvent;
+class JackMidiDriver;
+class DuplexPort;
+
+
+/** Representation of an JACK MIDI port.
+ *
+ * \ingroup engine
+ */
+class JackMidiPort : public DriverPort, public Raul::List<JackMidiPort*>::Node
+{
+public:
+ JackMidiPort(JackMidiDriver* driver, DuplexPort* port);
+ virtual ~JackMidiPort();
+
+ void pre_process(ProcessContext& context);
+ void post_process(ProcessContext& context);
+
+ void set_name(const std::string& name) { jack_port_set_name(_jack_port, name.c_str()); };
+
+private:
+ JackMidiDriver* _driver;
+ jack_port_t* _jack_port;
+};
+
+
+/** Jack MIDI driver.
+ *
+ * This driver reads Jack MIDI events and dispatches them to the appropriate
+ * JackMidiPort for processing.
+ *
+ * \ingroup engine
+ */
+class JackMidiDriver : public MidiDriver
+{
+public:
+ JackMidiDriver(jack_client_t* client);
+ ~JackMidiDriver();
+
+ void activate();
+ void deactivate();
+ void enable() { _is_enabled = true; }
+ void disable() { _is_enabled = false; }
+
+ bool is_activated() const { return _is_activated; }
+ bool is_enabled() const { return _is_enabled; }
+
+ void pre_process(ProcessContext& context);
+ void post_process(ProcessContext& context);
+
+ JackMidiPort* create_port(DuplexPort* patch_port)
+ { return new JackMidiPort(this, patch_port); }
+
+ void add_port(DriverPort* port);
+ DriverPort* remove_port(const Raul::Path& path);
+
+ DriverPort* driver_port(const Raul::Path& path);
+
+ jack_client_t* jack_client() { return _client; }
+
+private:
+ Raul::List<JackMidiPort*> _in_ports;
+ Raul::List<JackMidiPort*> _out_ports;
+
+ friend class JackMidiPort;
+
+ void add_output(Raul::List<JackMidiPort*>::Node* port);
+ Raul::List<JackMidiPort*>::Node* remove_output(JackMidiPort* port);
+
+ // MIDI thread
+ static void* process_midi_in(void* me);
+
+ jack_client_t* _client;
+ pthread_t _process_thread;
+ bool _is_activated;
+ bool _is_enabled;
+ static bool _midi_thread_exit_flag;
+};
+
+
+} // namespace Ingen
+
+
+#endif // JACKMIDIDRIVER_H
diff --git a/src/engine/LADSPANode.cpp b/src/engine/LADSPANode.cpp
new file mode 100644
index 00000000..22f5bbf3
--- /dev/null
+++ b/src/engine/LADSPANode.cpp
@@ -0,0 +1,373 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 <iostream>
+#include <cassert>
+#include <cmath>
+#include <stdint.h>
+#include <raul/Maid.hpp>
+#include <boost/optional.hpp>
+#include "LADSPANode.hpp"
+#include "AudioBuffer.hpp"
+#include "InputPort.hpp"
+#include "OutputPort.hpp"
+#include "PluginImpl.hpp"
+#include "ProcessContext.hpp"
+
+using namespace std;
+
+namespace Ingen {
+
+
+/** Partially construct a LADSPANode.
+ *
+ * Object is not usable until instantiate() is called with success.
+ * (It _will_ crash!)
+ */
+LADSPANode::LADSPANode(PluginImpl* plugin, const string& path, bool polyphonic, PatchImpl* parent, const LADSPA_Descriptor* descriptor, SampleRate srate, size_t buffer_size)
+ : NodeBase(plugin, path, polyphonic, parent, srate, buffer_size)
+ , _descriptor(descriptor)
+ , _instances(NULL)
+ , _prepared_instances(NULL)
+{
+ assert(_descriptor != NULL);
+}
+
+
+LADSPANode::~LADSPANode()
+{
+ for (uint32_t i=0; i < _polyphony; ++i)
+ _descriptor->cleanup((*_instances)[i]);
+
+ delete _instances;
+}
+
+
+bool
+LADSPANode::prepare_poly(uint32_t poly)
+{
+ NodeBase::prepare_poly(poly);
+
+ if ( (!_polyphonic)
+ || (_prepared_instances && poly <= _prepared_instances->size()) ) {
+ return true;
+ }
+
+ _prepared_instances = new Raul::Array<LADSPA_Handle>(poly, *_instances);
+ for (uint32_t i = _polyphony; i < _prepared_instances->size(); ++i) {
+ _prepared_instances->at(i) = _descriptor->instantiate(_descriptor, _srate);
+ if (_prepared_instances->at(i) == NULL) {
+ cerr << "Failed to instantiate plugin!" << endl;
+ return false;
+ }
+
+ if (_activated && _descriptor->activate)
+ _descriptor->activate(_prepared_instances->at(i));
+ }
+
+ return true;
+}
+
+
+bool
+LADSPANode::apply_poly(Raul::Maid& maid, uint32_t poly)
+{
+ if (!_polyphonic)
+ return true;
+
+ if (_prepared_instances) {
+ assert(poly <= _prepared_instances->size());
+ maid.push(_instances);
+ _instances = _prepared_instances;
+ _prepared_instances = NULL;
+ }
+
+ assert(poly <= _instances->size());
+ _polyphony = poly;
+
+ return NodeBase::apply_poly(maid, poly);
+}
+
+
+static string
+nameify_if_invalid(const string& name)
+{
+ if (Path::is_valid_name(name)) {
+ return name;
+ } else {
+ const string new_name = Path::nameify(name);
+ assert(Path::is_valid_name(new_name));
+ if (new_name != name)
+ cerr << "Symbol '" << new_name << "' generated from LADSPA name '" << name << endl;
+ return new_name;
+ }
+}
+
+
+/** Instantiate self from LADSPA 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
+LADSPANode::instantiate()
+{
+ if (!_ports)
+ _ports = new Raul::Array<PortImpl*>(_descriptor->PortCount);
+
+ _instances = new Raul::Array<LADSPA_Handle>(_polyphony, NULL);
+
+ size_t port_buffer_size = 0;
+
+ for (uint32_t i=0; i < _polyphony; ++i) {
+ (*_instances)[i] = _descriptor->instantiate(_descriptor, _srate);
+ if ((*_instances)[i] == NULL) {
+ cerr << "Failed to instantiate plugin!" << endl;
+ return false;
+ }
+ }
+
+ string port_name;
+ string port_path;
+
+ PortImpl* port = NULL;
+
+ for (size_t j=0; j < _descriptor->PortCount; ++j) {
+ port_name = nameify_if_invalid(_descriptor->PortNames[j]);
+
+ string::size_type slash_index;
+
+ // Name mangling, to guarantee port names are unique
+ for (size_t k=0; k < _descriptor->PortCount; ++k) {
+ assert(_descriptor->PortNames[k] != NULL);
+ if (k != j && port_name == _descriptor->PortNames[k]) { // clash
+ if (LADSPA_IS_PORT_CONTROL(_descriptor->PortDescriptors[j]))
+ port_name += "(CR)";
+ else
+ port_name += "(AR)";
+ }
+ // Replace all slashes with "-" (so they don't screw up paths)
+ while ((slash_index = port_name.find("/")) != string::npos)
+ port_name[slash_index] = '-';
+ }
+
+ /*if (_descriptor->PortNames[j] != port_name)
+ cerr << "NOTICE: Translated LADSPA port name: " <<
+ _descriptor->PortNames[j] << " -> " << port_name << endl;*/
+
+ port_path = path() + "/" + port_name;
+
+ DataType type = DataType::AUDIO;
+ port_buffer_size = _buffer_size;
+
+ if (LADSPA_IS_PORT_CONTROL(_descriptor->PortDescriptors[j])) {
+ type = DataType::CONTROL;
+ port_buffer_size = 1;
+ } else {
+ assert(LADSPA_IS_PORT_AUDIO(_descriptor->PortDescriptors[j]));
+ }
+ assert (LADSPA_IS_PORT_INPUT(_descriptor->PortDescriptors[j])
+ || LADSPA_IS_PORT_OUTPUT(_descriptor->PortDescriptors[j]));
+
+ boost::optional<Sample> default_val, min, max;
+ get_port_limits(j, default_val, min, max);
+
+ const float value = default_val ? default_val.get() : 0.0f;
+
+ if (LADSPA_IS_PORT_INPUT(_descriptor->PortDescriptors[j])) {
+ port = new InputPort(this, port_name, j, _polyphony, type, value, port_buffer_size);
+ _ports->at(j) = port;
+ } else if (LADSPA_IS_PORT_OUTPUT(_descriptor->PortDescriptors[j])) {
+ port = new OutputPort(this, port_name, j, _polyphony, type, value, port_buffer_size);
+ _ports->at(j) = port;
+ }
+
+ assert(port);
+ assert(_ports->at(j) == port);
+
+ // Work around broke-ass crackhead plugins
+ if (default_val && default_val.get() < min.get()) {
+ cerr << "WARNING: Broken LADSPA " << _descriptor->UniqueID
+ << ": Port default < minimum. Minimum adjusted." << endl;
+ min = default_val;
+ }
+
+ if (default_val && default_val.get() > max.get()) {
+ cerr << "WARNING: Broken LADSPA " << _descriptor->UniqueID
+ << ": Maximum adjusted." << endl;
+ max = default_val;
+ }
+
+ // Set initial/default value
+ if (port->buffer_size() == 1) {
+ for (uint32_t i=0; i < _polyphony; ++i)
+ ((AudioBuffer*)port->buffer(i))->set_value(value, 0, 0);
+ }
+
+ if (port->is_input() && port->buffer_size() == 1) {
+ if (min)
+ port->set_variable("ingen:minimum", min.get());
+ if (max)
+ port->set_variable("ingen:maximum", max.get());
+ if (default_val)
+ port->set_variable("ingen:default", default_val.get());
+ }
+ }
+
+ return true;
+}
+
+
+void
+LADSPANode::activate()
+{
+ NodeBase::activate();
+
+ for (uint32_t i=0; i < _polyphony; ++i) {
+ for (unsigned long j=0; j < _descriptor->PortCount; ++j) {
+ set_port_buffer(i, j, _ports->at(j)->buffer(i));
+ /* if (port->type() == DataType::FLOAT && port->buffer_size() == 1)
+ port->set_value(0.0f, 0); // FIXME
+ else if (port->type() == DataType::FLOAT && port->buffer_size() > 1)
+ port->set_value(0.0f, 0);*/
+ }
+ if (_descriptor->activate != NULL)
+ _descriptor->activate((*_instances)[i]);
+ }
+}
+
+
+void
+LADSPANode::deactivate()
+{
+ NodeBase::deactivate();
+
+ for (uint32_t i=0; i < _polyphony; ++i)
+ if (_descriptor->deactivate != NULL)
+ _descriptor->deactivate((*_instances)[i]);
+}
+
+
+void
+LADSPANode::process(ProcessContext& context)
+{
+ NodeBase::pre_process(context);
+
+ for (uint32_t i=0; i < _polyphony; ++i)
+ _descriptor->run((*_instances)[i], context.nframes());
+
+ NodeBase::post_process(context);
+}
+
+
+void
+LADSPANode::set_port_buffer(uint32_t voice, uint32_t port_num, Buffer* buf)
+{
+ assert(voice < _polyphony);
+
+ AudioBuffer* audio_buffer = dynamic_cast<AudioBuffer*>(buf);
+ assert(audio_buffer);
+
+ if (port_num < _descriptor->PortCount)
+ _descriptor->connect_port((*_instances)[voice], port_num,
+ audio_buffer->data());
+}
+
+
+void
+LADSPANode::get_port_limits(unsigned long port_index,
+ boost::optional<Sample>& normal,
+ boost::optional<Sample>& lower,
+ boost::optional<Sample>& upper)
+{
+ LADSPA_PortRangeHintDescriptor hint_descriptor = _descriptor->PortRangeHints[port_index].HintDescriptor;
+
+ /* set upper and lower, possibly adjusted to the sample rate */
+ if (LADSPA_IS_HINT_SAMPLE_RATE(hint_descriptor)) {
+ upper = _descriptor->PortRangeHints[port_index].UpperBound * _srate;
+ lower = _descriptor->PortRangeHints[port_index].LowerBound * _srate;
+ } else {
+ upper = _descriptor->PortRangeHints[port_index].UpperBound;
+ lower = _descriptor->PortRangeHints[port_index].LowerBound;
+ }
+
+ if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) {
+ /* FLT_EPSILON is defined as the different between 1.0 and the minimum
+ * float greater than 1.0. So, if lower is < FLT_EPSILON, it will be 1.0
+ * and the logarithmic control will have a base of 1 and thus not change
+ */
+ if (lower.get() < FLT_EPSILON) lower = FLT_EPSILON;
+ }
+
+
+ if (LADSPA_IS_HINT_HAS_DEFAULT(hint_descriptor)) {
+
+ if (LADSPA_IS_HINT_DEFAULT_MINIMUM(hint_descriptor)) {
+ normal = lower;
+ } else if (LADSPA_IS_HINT_DEFAULT_LOW(hint_descriptor)) {
+ assert(lower);
+ assert(upper);
+ if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) {
+ normal = exp(log(lower.get()) * 0.75 + log(upper.get()) * 0.25);
+ } else {
+ normal = lower.get() * 0.75 + upper.get() * 0.25;
+ }
+ } else if (LADSPA_IS_HINT_DEFAULT_MIDDLE(hint_descriptor)) {
+ assert(lower);
+ assert(upper);
+ if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) {
+ normal = exp(log(lower.get()) * 0.5 + log(upper.get()) * 0.5);
+ } else {
+ normal = lower.get() * 0.5 + upper.get() * 0.5;
+ }
+ } else if (LADSPA_IS_HINT_DEFAULT_HIGH(hint_descriptor)) {
+ assert(lower);
+ assert(upper);
+ if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) {
+ normal = exp(log(lower.get()) * 0.25 + log(upper.get()) * 0.75);
+ } else {
+ normal = lower.get() * 0.25 + upper.get() * 0.75;
+ }
+ } else if (LADSPA_IS_HINT_DEFAULT_MAXIMUM(hint_descriptor)) {
+ assert(upper);
+ normal = upper.get();
+ } else if (LADSPA_IS_HINT_DEFAULT_0(hint_descriptor)) {
+ normal = 0.0;
+ } else if (LADSPA_IS_HINT_DEFAULT_1(hint_descriptor)) {
+ normal = 1.0;
+ } else if (LADSPA_IS_HINT_DEFAULT_100(hint_descriptor)) {
+ normal = 100.0;
+ } else if (LADSPA_IS_HINT_DEFAULT_440(hint_descriptor)) {
+ normal = 440.0;
+ }
+ } else { // No default hint
+ if (LADSPA_IS_HINT_BOUNDED_BELOW(hint_descriptor)) {
+ assert(lower);
+ normal = lower.get();
+ } else if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint_descriptor)) {
+ assert(upper);
+ normal = upper.get();
+ }
+ }
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/LADSPANode.hpp b/src/engine/LADSPANode.hpp
new file mode 100644
index 00000000..53d7ad9e
--- /dev/null
+++ b/src/engine/LADSPANode.hpp
@@ -0,0 +1,74 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 LADSPANODE_H
+#define LADSPANODE_H
+
+#include <string>
+#include <ladspa.h>
+#include <boost/optional.hpp>
+#include "types.hpp"
+#include "NodeBase.hpp"
+#include "PluginImpl.hpp"
+
+namespace Ingen {
+
+
+/** An instance of a LADSPA plugin.
+ *
+ * \ingroup engine
+ */
+class LADSPANode : public NodeBase
+{
+public:
+ LADSPANode(PluginImpl* plugin,
+ const string& name,
+ bool polyphonic,
+ PatchImpl* parent,
+ const LADSPA_Descriptor* descriptor,
+ SampleRate srate,
+ size_t buffer_size);
+
+ ~LADSPANode();
+
+ bool instantiate();
+
+ bool prepare_poly(uint32_t poly);
+ bool apply_poly(Raul::Maid& maid, uint32_t poly);
+
+ void activate();
+ void deactivate();
+
+ void process(ProcessContext& context);
+
+ void set_port_buffer(uint32_t voice, uint32_t port_num, Buffer* buf);
+
+protected:
+ void get_port_limits(unsigned long port_index,
+ boost::optional<Sample>& default_value,
+ boost::optional<Sample>& lower_bound,
+ boost::optional<Sample>& upper_bound);
+
+ const LADSPA_Descriptor* _descriptor;
+ Raul::Array<LADSPA_Handle>* _instances;
+ Raul::Array<LADSPA_Handle>* _prepared_instances;
+};
+
+
+} // namespace Ingen
+
+#endif // LADSPANODE_H
diff --git a/src/engine/LADSPAPlugin.cpp b/src/engine/LADSPAPlugin.cpp
new file mode 100644
index 00000000..4a0b5c14
--- /dev/null
+++ b/src/engine/LADSPAPlugin.cpp
@@ -0,0 +1,79 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 <ladspa.h>
+#include <iostream>
+#include "LADSPAPlugin.hpp"
+#include "LADSPANode.hpp"
+#include "NodeImpl.hpp"
+#include "Engine.hpp"
+#include "AudioDriver.hpp"
+
+using namespace std;
+
+namespace Ingen {
+
+
+NodeImpl*
+LADSPAPlugin::instantiate(const string& name,
+ bool polyphonic,
+ Ingen::PatchImpl* parent,
+ Engine& engine)
+{
+ assert(_id != 0);
+
+ SampleCount srate = engine.audio_driver()->sample_rate();
+ SampleCount buffer_size = engine.audio_driver()->buffer_size();
+
+ LADSPA_Descriptor_Function df = NULL;
+ LADSPANode* n = NULL;
+
+ load(); // FIXME: unload at some point
+ assert(_module);
+ assert(*_module);
+
+ if (!_module->get_symbol("ladspa_descriptor", (void*&)df)) {
+ cerr << "Looks like this isn't a LADSPA plugin." << endl;
+ return NULL;
+ }
+
+ // Attempt to find the plugin in library
+ LADSPA_Descriptor* descriptor = NULL;
+ for (unsigned long i=0; (descriptor = (LADSPA_Descriptor*)df(i)) != NULL; ++i) {
+ if (descriptor->UniqueID == _id) {
+ break;
+ }
+ }
+
+ if (descriptor != NULL) {
+ n = new LADSPANode(this, name, polyphonic, parent, descriptor, srate, buffer_size);
+
+ if ( ! n->instantiate() ) {
+ delete n;
+ n = NULL;
+ }
+
+ } else {
+ cerr << "Could not find plugin \"" << _id << "\" in " << _library_path << endl;
+ }
+
+ return n;
+}
+
+
+} // namespace Ingen
diff --git a/src/engine/LADSPAPlugin.hpp b/src/engine/LADSPAPlugin.hpp
new file mode 100644
index 00000000..2414be7c
--- /dev/null
+++ b/src/engine/LADSPAPlugin.hpp
@@ -0,0 +1,78 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 LADSPAPLUGIN_H
+#define LADSPAPLUGIN_H
+
+#include CONFIG_H_PATH
+
+#include <cstdlib>
+#include <glibmm/module.h>
+#include <boost/utility.hpp>
+#include <dlfcn.h>
+#include <string>
+#include <iostream>
+#include <raul/Path.hpp>
+#include "types.hpp"
+#include "PluginImpl.hpp"
+
+namespace Ingen {
+
+class NodeImpl;
+
+
+/** Implementation of a LADSPA plugin (loaded shared library).
+ */
+class LADSPAPlugin : public PluginImpl
+{
+public:
+ LADSPAPlugin(const std::string& library_path,
+ const std::string& uri,
+ unsigned long id,
+ const string& label,
+ const string& name)
+ : PluginImpl(Plugin::LADSPA, uri, library_path)
+ , _id(id)
+ , _label(label)
+ , _name(name)
+ {}
+
+ NodeImpl* instantiate(const std::string& name,
+ bool polyphonic,
+ Ingen::PatchImpl* parent,
+ Engine& engine);
+
+ const std::string& label() const { return _label; }
+ unsigned long id() const { return _id; }
+ const string symbol() const { return Raul::Path::nameify(_label); }
+ const string name() const { return _name; }
+
+ const string library_name() const {
+ return _library_path.substr(_library_path.find_last_of("/")+1);
+ }
+
+private:
+ const unsigned long _id;
+ const std::string _label;
+ const std::string _name;
+};
+
+
+} // namespace Ingen
+
+#endif // LADSPAPLUGIN_H
+
diff --git a/src/engine/LV2Info.cpp b/src/engine/LV2Info.cpp
new file mode 100644
index 00000000..43dd014b
--- /dev/null
+++ b/src/engine/LV2Info.cpp
@@ -0,0 +1,70 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2008 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 <cassert>
+#include <iostream>
+#include <stdint.h>
+#include "LV2Info.hpp"
+#include <module/World.hpp>
+
+using namespace std;
+
+namespace Ingen {
+
+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))
+ , _world(world)
+{
+ assert(world);
+
+ LV2_Event_Feature* ev_data = (LV2_Event_Feature*)malloc(sizeof(LV2_Event_Feature));
+ ev_data->lv2_event_ref = &LV2Info::event_ref;
+ ev_data->lv2_event_unref = &LV2Info::event_ref;
+ ev_data->callback_data = this;
+ LV2_Feature* ev_feature = (LV2_Feature*)malloc(sizeof(LV2_Event_Feature));
+ ev_feature->URI = LV2_EVENT_URI;
+ ev_feature->data = ev_data;
+
+ world->lv2_features->add_feature(LV2_EVENT_URI, ev_feature, ev_data);
+}
+
+
+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);
+}
+
+
+uint32_t
+LV2Info::event_ref(LV2_Event_Callback_Data callback_data,
+ LV2_Event* event)
+{
+ return 0;
+}
+
+
+
+} // namespace Ingen
diff --git a/src/engine/LV2Info.hpp b/src/engine/LV2Info.hpp
new file mode 100644
index 00000000..f4859ac7
--- /dev/null
+++ b/src/engine/LV2Info.hpp
@@ -0,0 +1,66 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2008 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 LV2INFO_H
+#define LV2INFO_H
+
+#include CONFIG_H_PATH
+#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 "module/global.hpp"
+#include "module/World.hpp"
+#include "shared/LV2URIMap.hpp"
+#include "lv2ext/lv2_uri_map.h"
+#include "lv2ext/lv2_event.h"
+
+namespace Ingen {
+
+
+/** Stuff that may need to be passed to an LV2 plugin (i.e. LV2 features).
+ */
+class LV2Info : public Shared::LV2URIMap {
+public:
+ LV2Info(Ingen::Shared::World* world);
+ ~LV2Info();
+
+ SLV2Value input_class;
+ SLV2Value output_class;
+ SLV2Value control_class;
+ SLV2Value audio_class;
+ SLV2Value event_class;
+
+ Ingen::Shared::World& world() { return *_world; }
+ SLV2World lv2_world() { return _world->slv2_world; }
+
+ static uint32_t event_ref(LV2_Event_Callback_Data callback_data,
+ LV2_Event* event);
+
+ LV2_Feature** lv2_features() const { return _world->lv2_features->lv2_features(); }
+
+private:
+ Ingen::Shared::World* _world;
+};
+
+
+} // namespace Ingen
+
+#endif // LV2INFO_H
diff --git a/src/engine/LV2Node.cpp b/src/engine/LV2Node.cpp
new file mode 100644
index 00000000..a06cc55a
--- /dev/null
+++ b/src/engine/LV2Node.cpp
@@ -0,0 +1,305 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 <iostream>
+#include <cassert>
+#include <float.h>
+#include <stdint.h>
+#include <cmath>
+#include <raul/Maid.hpp>
+#include "AudioBuffer.hpp"
+#include "InputPort.hpp"
+#include "LV2Node.hpp"
+#include "LV2Plugin.hpp"
+#include "EventBuffer.hpp"
+#include "OutputPort.hpp"
+#include "ProcessContext.hpp"
+#include "lv2_contexts.h"
+
+using namespace std;
+
+namespace Ingen {
+
+
+/** 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,
+ size_t buffer_size)
+ : NodeBase(plugin, name, polyphonic, parent, srate, buffer_size)
+ , _lv2_plugin(plugin)
+ , _instances(NULL)
+ , _prepared_instances(NULL)
+ , _message_run(NULL)
+{
+ assert(_lv2_plugin);
+}
+
+
+LV2Node::~LV2Node()
+{
+ for (uint32_t i=0; i < _polyphony; ++i)
+ slv2_instance_free((*_instances)[i]);
+
+ delete _instances;
+}
+
+
+bool
+LV2Node::prepare_poly(uint32_t poly)
+{
+ NodeBase::prepare_poly(poly);
+
+ if ( (!_polyphonic)
+ || (_prepared_instances && poly <= _prepared_instances->size()) ) {
+ return true;
+ }
+
+ _prepared_instances = new Raul::Array<SLV2Instance>(poly, *_instances);
+ for (uint32_t i = _polyphony; i < _prepared_instances->size(); ++i) {
+ // FIXME: features array (in NodeFactory) must be passed!
+ _prepared_instances->at(i) = slv2_plugin_instantiate(
+ _lv2_plugin->slv2_plugin(), _srate, NULL);
+
+ if (_prepared_instances->at(i) == NULL) {
+ cerr << "Failed to instantiate plugin!" << endl;
+ return false;
+ }
+
+ if (_activated)
+ slv2_instance_activate(_prepared_instances->at(i));
+ }
+
+ return true;
+}
+
+
+bool
+LV2Node::apply_poly(Raul::Maid& maid, uint32_t poly)
+{
+ if (!_polyphonic)
+ return true;
+
+ if (_prepared_instances) {
+ assert(poly <= _prepared_instances->size());
+ maid.push(_instances);
+ _instances = _prepared_instances;
+ _prepared_instances = NULL;
+ }
+
+ assert(poly <= _instances->size());
+ _polyphony = poly;
+
+ return NodeBase::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()
+{
+ 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 Raul::Array<SLV2Instance>(_polyphony, NULL);
+
+ uint32_t port_buffer_size = 0;
+
+ for (uint32_t i=0; i < _polyphony; ++i) {
+ (*_instances)[i] = slv2_plugin_instantiate(plug, _srate, info->lv2_features());
+ if ((*_instances)[i] == NULL) {
+ cerr << "Failed to instantiate plugin!" << endl;
+ return false;
+ }
+
+ const void* ctx_ext = slv2_instance_get_extension_data(
+ (*_instances)[i], LV2_CONTEXT_MESSAGE);
+
+ if (ctx_ext) {
+ cerr << "HAS CONTEXT EXTENSION" << endl;
+ if (_message_run == NULL)
+ _message_run = new MessageRunFuncs(_polyphony, NULL);
+ LV2MessageContext* mc = (LV2MessageContext*)ctx_ext;
+ (*_message_run)[i] = mc->message_run;
+ }
+ }
+
+ string port_name;
+ string port_path;
+
+ PortImpl* port = NULL;
+
+ float* def_values = new float[num_ports];
+ slv2_plugin_get_port_ranges_float(plug, 0, 0, def_values);
+
+ for (uint32_t j=0; j < num_ports; ++j) {
+ SLV2Port id = slv2_plugin_get_port_by_index(plug, j);
+
+ // LV2 shortnames are guaranteed to be unique, valid C identifiers
+ port_name = slv2_value_as_string(slv2_port_get_symbol(plug, id));
+
+ assert(port_name.find("/") == string::npos);
+
+ port_path = path() + "/" + port_name;
+
+ DataType data_type = DataType::UNKNOWN;
+ if (slv2_port_is_a(plug, id, info->control_class)) {
+ data_type = DataType::CONTROL;
+ port_buffer_size = 1;
+ } else if (slv2_port_is_a(plug, id, info->audio_class)) {
+ data_type = DataType::AUDIO;
+ port_buffer_size = _buffer_size;
+ } else if (slv2_port_is_a(plug, id, info->event_class)) {
+ data_type = DataType::EVENT;
+ port_buffer_size = _buffer_size;
+ }
+
+ 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 == DataType::UNKNOWN || direction == UNKNOWN) {
+ delete _ports;
+ _ports = NULL;
+ delete _instances;
+ _instances = NULL;
+ return false;
+ }
+
+ // FIXME: need nice type preserving SLV2Value -> Raul::Atom conversion
+ float def = isnan(def_values[j]) ? 0.0f : def_values[j];
+ Atom defatm = def;
+
+ if (direction == INPUT)
+ port = new InputPort(this, port_name, j, _polyphony, data_type, defatm, port_buffer_size);
+ else
+ port = new OutputPort(this, port_name, j, _polyphony, data_type, defatm, port_buffer_size);
+
+ if (direction == INPUT && data_type == DataType::CONTROL)
+ ((AudioBuffer*)port->buffer(0))->set_value(def, 0, 0);
+
+ SLV2Value pred = slv2_value_new_uri(info->lv2_world(),
+ "http://lv2plug.in/ns/dev/contexts#context");
+ SLV2Values contexts = slv2_port_get_value(plug, id, pred);
+ for (uint32_t i = 0; i < slv2_values_size(contexts); ++i) {
+ SLV2Value c = slv2_values_get_at(contexts, i);
+ const char* context = slv2_value_as_string(c);
+ if (!strcmp("http://lv2plug.in/ns/dev/contexts#MessageContext", context)) {
+ cout << "MESSAGE CONTEXT!" << endl;
+ port->set_context(Context::MESSAGE);
+ } else {
+ cout << "UNKNOWN CONTEXT: "
+ << slv2_value_as_string(slv2_values_get_at(contexts, i))
+ << endl;
+ }
+ }
+
+ _ports->at(j) = port;
+ }
+
+ delete [] def_values;
+
+ return true;
+}
+
+
+void
+LV2Node::activate()
+{
+ NodeBase::activate();
+
+ for (uint32_t i=0; i < _polyphony; ++i) {
+ for (unsigned long j=0; j < num_ports(); ++j) {
+ PortImpl* const port = _ports->at(j);
+
+ set_port_buffer(i, j, port->buffer(i));
+
+ if (port->type() == DataType::CONTROL) {
+ ((AudioBuffer*)port->buffer(i))->set_value(port->value().get_float(), 0, 0);
+ } else if (port->type() == DataType::AUDIO) {
+ ((AudioBuffer*)port->buffer(i))->set_value(0.0f, 0, 0);
+ }
+ }
+ slv2_instance_activate((*_instances)[i]);
+ }
+}
+
+
+void
+LV2Node::deactivate()
+{
+ NodeBase::deactivate();
+
+ for (uint32_t i=0; i < _polyphony; ++i)
+ slv2_instance_deactivate((*_instances)[i]);
+}
+
+
+void
+LV2Node::message_process(MessageContext& context, uint32_t* output)
+{
+ // FIXME: voice
+ if (_message_run)
+ (*_message_run)[0]((*_instances)[0], output);
+
+ /* MESSAGE PROCESS */
+}
+
+
+void
+LV2Node::process(ProcessContext& context)
+{
+ NodeBase::pre_process(context);
+
+ for (uint32_t i=0; i < _polyphony; ++i)
+ slv2_instance_run((*_instances)[i], context.nframes());
+
+ NodeBase::post_process(context);
+}
+
+
+void
+LV2Node::set_port_buffer(uint32_t voice, uint32_t port_num, Buffer* buf)
+{
+ assert(voice < _polyphony);
+
+ slv2_instance_connect_port((*_instances)[voice], port_num, buf->raw_data());
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/LV2Node.hpp b/src/engine/LV2Node.hpp
new file mode 100644
index 00000000..3187005c
--- /dev/null
+++ b/src/engine/LV2Node.hpp
@@ -0,0 +1,75 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 LV2NODE_H
+#define LV2NODE_H
+
+#include <string>
+#include <slv2/slv2.h>
+#include "types.hpp"
+#include "NodeBase.hpp"
+
+namespace Ingen {
+
+class LV2Plugin;
+
+
+/** An instance of a LV2 plugin.
+ *
+ * \ingroup engine
+ */
+class LV2Node : public NodeBase
+{
+public:
+ LV2Node(LV2Plugin* plugin,
+ const string& name,
+ bool polyphonic,
+ PatchImpl* parent,
+ SampleRate srate,
+ size_t buffer_size);
+
+ ~LV2Node();
+
+ bool instantiate();
+
+ bool prepare_poly(uint32_t poly);
+ bool apply_poly(Raul::Maid& maid, uint32_t poly);
+
+ void activate();
+ void deactivate();
+
+ void message_process(MessageContext& context, uint32_t* output);
+
+ void process(ProcessContext& context);
+
+ void set_port_buffer(uint32_t voice, uint32_t port_num, Buffer* buf);
+
+protected:
+ LV2Plugin* _lv2_plugin;
+ Raul::Array<SLV2Instance>* _instances;
+ Raul::Array<SLV2Instance>* _prepared_instances;
+
+ typedef bool (*MessageRunFunc)(LV2_Handle, uint32_t*);
+ typedef Raul::Array<MessageRunFunc> MessageRunFuncs;
+ MessageRunFuncs* _message_run;
+};
+
+
+} // namespace Ingen
+
+#endif // LV2NODE_H
+
diff --git a/src/engine/LV2Plugin.cpp b/src/engine/LV2Plugin.cpp
new file mode 100644
index 00000000..90a3a6b8
--- /dev/null
+++ b/src/engine/LV2Plugin.cpp
@@ -0,0 +1,94 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 <glibmm.h>
+#include <redlandmm/World.hpp>
+#include "LV2Plugin.hpp"
+#include "LV2Node.hpp"
+#include "NodeImpl.hpp"
+#include "Engine.hpp"
+#include "AudioDriver.hpp"
+
+namespace Ingen {
+
+
+const string
+LV2Plugin::symbol() const
+{
+ string working = _uri;
+ 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";
+}
+
+
+const string
+LV2Plugin::name() const
+{
+ if (_name)
+ return slv2_value_as_string(_name);
+ else
+ return "(no name)";
+}
+
+
+NodeImpl*
+LV2Plugin::instantiate(const string& name,
+ bool polyphonic,
+ Ingen::PatchImpl* parent,
+ Engine& engine)
+{
+ SampleCount srate = engine.audio_driver()->sample_rate();
+ SampleCount buffer_size = engine.audio_driver()->buffer_size();
+
+ load(); // FIXME: unload at some point
+
+ Glib::Mutex::Lock lock(engine.world()->rdf_world->mutex());
+ LV2Node* n = new LV2Node(this, name, polyphonic, parent, srate, buffer_size);
+
+ if ( ! n->instantiate() ) {
+ delete n;
+ n = NULL;
+ }
+
+ return n;
+}
+
+
+void
+LV2Plugin::slv2_plugin(SLV2Plugin p)
+{
+ _slv2_plugin = p;
+ if (_name)
+ slv2_value_free(_name);
+ _name = slv2_plugin_get_name(_slv2_plugin);
+}
+
+
+} // namespace Ingen
diff --git a/src/engine/LV2Plugin.hpp b/src/engine/LV2Plugin.hpp
new file mode 100644
index 00000000..c9bc4ff9
--- /dev/null
+++ b/src/engine/LV2Plugin.hpp
@@ -0,0 +1,84 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 LV2PLUGIN_H
+#define LV2PLUGIN_H
+
+#include CONFIG_H_PATH
+
+#ifndef HAVE_SLV2
+#error "This file requires SLV2, but HAVE_SLV2 is not defined. Please report."
+#endif
+
+#include <cstdlib>
+#include <glibmm/module.h>
+#include <boost/utility.hpp>
+#include <dlfcn.h>
+#include <string>
+#include <iostream>
+#include <slv2/slv2.h>
+#include <raul/SharedPtr.hpp>
+#include "types.hpp"
+#include "PluginImpl.hpp"
+#include "LV2Info.hpp"
+
+using std::string;
+using Ingen::Shared::Plugin;
+
+
+namespace Ingen {
+
+class PatchImpl;
+class NodeImpl;
+
+
+/** Implementation of an LV2 plugin (loaded shared library).
+ */
+class LV2Plugin : public PluginImpl
+{
+public:
+ LV2Plugin(SharedPtr<LV2Info> lv2_info, const string& uri)
+ : PluginImpl(Plugin::LV2, uri)
+ , _name(NULL)
+ , _slv2_plugin(NULL)
+ , _lv2_info(lv2_info)
+ {}
+
+ NodeImpl* instantiate(const string& name,
+ bool polyphonic,
+ Ingen::PatchImpl* parent,
+ Engine& engine);
+
+ const string symbol() const;
+ const string name() const;
+
+ SharedPtr<LV2Info> lv2_info() const { return _lv2_info; }
+
+ SLV2Plugin slv2_plugin() const { return _slv2_plugin; }
+ void slv2_plugin(SLV2Plugin p);
+
+private:
+ SLV2Value _name;
+ SLV2Plugin _slv2_plugin;
+ SharedPtr<LV2Info> _lv2_info;
+};
+
+
+} // namespace Ingen
+
+#endif // LV2PLUGIN_H
+
diff --git a/src/engine/Makefile.am b/src/engine/Makefile.am
new file mode 100644
index 00000000..3c464de3
--- /dev/null
+++ b/src/engine/Makefile.am
@@ -0,0 +1,205 @@
+SUBDIRS = events
+
+MAINTAINERCLEANFILES = Makefile.in
+
+moduledir = $(libdir)/ingen
+
+module_LTLIBRARIES = libingen_engine.la
+libingen_engine_la_CXXFLAGS = \
+ @GLIBMM_CFLAGS@ \
+ @INGEN_CFLAGS@ \
+ @JACK_CFLAGS@ \
+ @LIBLO_CFLAGS@ \
+ @RAUL_CFLAGS@ \
+ @REDLANDMM_CFLAGS@ \
+ @SLV2_CFLAGS@ \
+ @SOUP_CFLAGS@
+
+libingen_engine_la_LDFLAGS = -no-undefined -module -avoid-version
+libingen_engine_la_LIBADD = \
+ ../shared/libingen_shared.la \
+ ../module/libingen_module.la \
+ @GLIBMM_LIBS@ \
+ @JACK_LIBS@ \
+ @LIBLO_LIBS@ \
+ @RAUL_LIBS@ \
+ @REDLANDMM_LIBS@ \
+ @SLV2_LIBS@ \
+ @SOUP_LIBS@
+
+AM_CFLAGS=-std=c99
+
+libingen_engine_la_SOURCES = \
+ AudioBuffer.cpp \
+ AudioBuffer.hpp \
+ AudioDriver.hpp \
+ Buffer.cpp \
+ Buffer.hpp \
+ ClientBroadcaster.cpp \
+ ClientBroadcaster.hpp \
+ CompiledPatch.hpp \
+ ConnectionImpl.cpp \
+ ConnectionImpl.hpp \
+ Driver.hpp \
+ DuplexPort.cpp \
+ DuplexPort.hpp \
+ Engine.cpp \
+ Engine.hpp \
+ EngineStore.cpp \
+ EngineStore.hpp \
+ Event.cpp \
+ Event.hpp \
+ EventBuffer.cpp \
+ EventBuffer.hpp \
+ EventSink.cpp \
+ EventSink.hpp \
+ EventSource.hpp \
+ GraphObjectImpl.cpp \
+ GraphObjectImpl.hpp \
+ InputPort.cpp \
+ InputPort.hpp \
+ InternalPlugin.cpp \
+ InternalPlugin.hpp \
+ JackAudioDriver.cpp \
+ JackAudioDriver.hpp \
+ JackMidiDriver.cpp \
+ JackMidiDriver.hpp \
+ LADSPAPlugin.cpp \
+ LADSPAPlugin.hpp \
+ LV2Info.cpp \
+ LV2Info.hpp \
+ LV2Plugin.cpp \
+ LV2Plugin.hpp \
+ MessageContext.cpp \
+ MessageContext.hpp \
+ MidiControlNode.cpp \
+ MidiControlNode.hpp \
+ MidiDriver.hpp \
+ MidiNoteNode.cpp \
+ MidiNoteNode.hpp \
+ MidiTriggerNode.cpp \
+ MidiTriggerNode.hpp \
+ NodeBase.cpp \
+ NodeBase.hpp \
+ NodeFactory.cpp \
+ NodeFactory.hpp \
+ NodeImpl.hpp \
+ OSCClientSender.cpp \
+ OSCClientSender.hpp \
+ OSCDriver.hpp \
+ OSCEngineReceiver.cpp \
+ OSCEngineReceiver.hpp \
+ ObjectSender.cpp \
+ ObjectSender.hpp \
+ OutputPort.cpp \
+ OutputPort.hpp \
+ PatchImpl.cpp \
+ PatchImpl.hpp \
+ PatchPlugin.hpp \
+ PluginImpl.cpp \
+ PluginImpl.hpp \
+ PortImpl.cpp \
+ PortImpl.hpp \
+ PostProcessor.cpp \
+ PostProcessor.hpp \
+ ProcessContext.hpp \
+ ProcessSlave.cpp \
+ ProcessSlave.hpp \
+ QueuedEngineInterface.cpp \
+ QueuedEngineInterface.hpp \
+ QueuedEvent.cpp \
+ QueuedEvent.hpp \
+ QueuedEventSource.cpp \
+ QueuedEventSource.hpp \
+ Responder.hpp \
+ ThreadManager.hpp \
+ TransportNode.cpp \
+ TransportNode.hpp \
+ engine.cpp \
+ engine.hpp \
+ events.hpp \
+ events/AllNotesOffEvent.cpp \
+ events/AllNotesOffEvent.hpp \
+ events/ClearPatchEvent.cpp \
+ events/ClearPatchEvent.hpp \
+ events/ConnectionEvent.cpp \
+ events/ConnectionEvent.hpp \
+ events/CreateNodeEvent.cpp \
+ events/CreateNodeEvent.hpp \
+ events/CreatePatchEvent.cpp \
+ events/CreatePatchEvent.hpp \
+ events/CreatePortEvent.cpp \
+ events/CreatePortEvent.hpp \
+ events/DeactivateEvent.cpp \
+ events/DeactivateEvent.hpp \
+ events/DestroyEvent.cpp \
+ events/DestroyEvent.hpp \
+ events/DisconnectAllEvent.cpp \
+ events/DisconnectAllEvent.hpp \
+ events/DisconnectionEvent.cpp \
+ events/DisconnectionEvent.hpp \
+ events/EnablePatchEvent.cpp \
+ events/EnablePatchEvent.hpp \
+ events/LoadPluginsEvent.cpp \
+ events/LoadPluginsEvent.hpp \
+ events/MidiLearnEvent.cpp \
+ events/MidiLearnEvent.hpp \
+ events/NoteEvent.cpp \
+ events/NoteEvent.hpp \
+ events/PingQueuedEvent.hpp \
+ events/RegisterClientEvent.cpp \
+ events/RegisterClientEvent.hpp \
+ events/RenameEvent.cpp \
+ events/RenameEvent.hpp \
+ events/RequestAllObjectsEvent.cpp \
+ events/RequestAllObjectsEvent.hpp \
+ events/RequestMetadataEvent.cpp \
+ events/RequestMetadataEvent.hpp \
+ events/RequestObjectEvent.cpp \
+ events/RequestObjectEvent.hpp \
+ events/RequestPluginEvent.cpp \
+ events/RequestPluginEvent.hpp \
+ events/RequestPluginsEvent.cpp \
+ events/RequestPluginsEvent.hpp \
+ events/RequestPortValueEvent.cpp \
+ events/RequestPortValueEvent.hpp \
+ events/SendPortActivityEvent.cpp \
+ events/SendPortActivityEvent.hpp \
+ events/SendPortValueEvent.cpp \
+ events/SendPortValueEvent.hpp \
+ events/SetMetadataEvent.cpp \
+ events/SetMetadataEvent.hpp \
+ events/SetPolyphonicEvent.cpp \
+ events/SetPolyphonicEvent.hpp \
+ events/SetPolyphonyEvent.cpp \
+ events/SetPolyphonyEvent.hpp \
+ events/SetPortValueEvent.cpp \
+ events/SetPortValueEvent.hpp \
+ events/UnregisterClientEvent.cpp \
+ events/UnregisterClientEvent.hpp \
+ jack_compat.h \
+ lv2_contexts.h \
+ tuning.hpp \
+ types.hpp \
+ util.hpp
+
+if WITH_LADSPA
+libingen_engine_la_SOURCES += \
+ LADSPANode.hpp \
+ LADSPANode.cpp
+endif
+
+if WITH_LV2
+libingen_engine_la_SOURCES += \
+ LV2Node.hpp \
+ LV2Node.cpp
+endif
+
+if WITH_SOUP
+libingen_engine_la_SOURCES += \
+ HTTPEngineReceiver.cpp \
+ HTTPEngineReceiver.hpp
+endif
+
+
+
diff --git a/src/engine/MessageContext.cpp b/src/engine/MessageContext.cpp
new file mode 100644
index 00000000..30f04b05
--- /dev/null
+++ b/src/engine/MessageContext.cpp
@@ -0,0 +1,32 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2008 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "MessageContext.hpp"
+#include "NodeImpl.hpp"
+
+namespace Ingen {
+
+void
+MessageContext::run(NodeImpl* node)
+{
+ uint32_t outputs;
+ node->message_process(*this, &outputs);
+
+ // Don't care what the plugin output, yet...
+}
+
+} // namespace Ingen
diff --git a/src/engine/MessageContext.hpp b/src/engine/MessageContext.hpp
new file mode 100644
index 00000000..31531521
--- /dev/null
+++ b/src/engine/MessageContext.hpp
@@ -0,0 +1,51 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007-2008 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 MESSAGECONTEXT_H
+#define MESSAGECONTEXT_H
+
+#include "EventSink.hpp"
+#include "Context.hpp"
+
+namespace Ingen {
+
+class NodeImpl;
+
+/** Context of a message_process() 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:
+ MessageContext(Engine& engine)
+ : Context(engine, MESSAGE)
+ {}
+
+ void run(NodeImpl* node);
+};
+
+
+} // namespace Ingen
+
+#endif // MESSAGECONTEXT_H
+
diff --git a/src/engine/MidiControlNode.cpp b/src/engine/MidiControlNode.cpp
new file mode 100644
index 00000000..bfe0e57e
--- /dev/null
+++ b/src/engine/MidiControlNode.cpp
@@ -0,0 +1,141 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "MidiControlNode.hpp"
+#include "PostProcessor.hpp"
+#include "MidiLearnEvent.hpp"
+#include "InputPort.hpp"
+#include "OutputPort.hpp"
+#include "InternalPlugin.hpp"
+#include "AudioBuffer.hpp"
+#include "ProcessContext.hpp"
+#include "EventBuffer.hpp"
+#include "util.hpp"
+
+namespace Ingen {
+
+
+MidiControlNode::MidiControlNode(const string& path,
+ bool polyphonic,
+ PatchImpl* parent,
+ SampleRate srate,
+ size_t buffer_size)
+ : NodeBase(new InternalPlugin(NS_INGEN "control_node", "controller", "MIDI Controller")
+ , path, false, parent, srate, buffer_size)
+ , _learning(false)
+{
+ _ports = new Raul::Array<PortImpl*>(6);
+
+ _midi_in_port = new InputPort(this, "input", 0, 1, DataType::EVENT, Atom(), _buffer_size);
+ _ports->at(0) = _midi_in_port;
+
+ _param_port = new InputPort(this, "controller", 1, 1, DataType::CONTROL, 0.0f, 1);
+ _param_port->set_variable("ingen:minimum", 0.0f);
+ _param_port->set_variable("ingen:maximum", 127.0f);
+ _param_port->set_variable("ingen:integer", true);
+ _ports->at(1) = _param_port;
+
+ _log_port = new InputPort(this, "logarithmic", 2, 1, DataType::CONTROL, 0.0f, 1);
+ _log_port->set_variable("ingen:toggled", true);
+ _ports->at(2) = _log_port;
+
+ _min_port = new InputPort(this, "minimum", 3, 1, DataType::CONTROL, 0.0f, 1);
+ _ports->at(3) = _min_port;
+
+ _max_port = new InputPort(this, "maximum", 4, 1, DataType::CONTROL, 1.0f, 1);
+ _ports->at(4) = _max_port;
+
+ _audio_port = new OutputPort(this, "ar_output", 5, 1, DataType::AUDIO, 0.0f, _buffer_size);
+ _ports->at(5) = _audio_port;
+}
+
+
+void
+MidiControlNode::process(ProcessContext& context)
+{
+ NodeBase::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);
+ assert(midi_in->this_nframes() == context.nframes());
+
+ 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();
+ }
+
+ NodeBase::post_process(context);
+}
+
+
+void
+MidiControlNode::control(ProcessContext& context, uchar control_num, uchar val, FrameTime time)
+{
+ assert(time - context.start() < _buffer_size);
+
+ Sample scaled_value;
+
+ const Sample nval = (val / 127.0f); // normalized [0, 1]
+
+ if (_learning) {
+ assert(false); // FIXME FIXME FIXME
+#if 0
+ assert(_learn_event != NULL);
+ _param_port->set_value(control_num, offset);
+ assert(_param_port->buffer(0)->value_at(0) == control_num);
+ _learn_event->set_value(control_num);
+ _learn_event->execute(offset);
+ //Engine::instance().post_processor()->push(_learn_event);
+ //Engine::instance().post_processor()->whip();
+ _learning = false;
+ _learn_event = NULL;
+#endif
+ }
+
+ const Sample min_port_val = ((AudioBuffer*)_min_port->buffer(0))->value_at(0);
+ const Sample max_port_val = ((AudioBuffer*)_max_port->buffer(0))->value_at(0);
+ const Sample log_port_val = ((AudioBuffer*)_log_port->buffer(0))->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))->value_at(0))
+ ((AudioBuffer*)_audio_port->buffer(0))->set_value(scaled_value, context.start(), time);
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/MidiControlNode.hpp b/src/engine/MidiControlNode.hpp
new file mode 100644
index 00000000..2f0496a3
--- /dev/null
+++ b/src/engine/MidiControlNode.hpp
@@ -0,0 +1,65 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 MIDICONTROLNODE_H
+#define MIDICONTROLNODE_H
+
+#include <string>
+#include "NodeBase.hpp"
+
+namespace Ingen {
+
+class MidiLearnResponseEvent;
+class InputPort;
+class OutputPort;
+
+
+/** 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 MidiControlNode : public NodeBase
+{
+public:
+ MidiControlNode(const std::string& path, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size);
+
+ void process(ProcessContext& context);
+
+ void control(ProcessContext& context, uchar control_num, uchar val, FrameTime time);
+
+ void learn(MidiLearnResponseEvent* ev) { _learning = true; _learn_event = ev; }
+
+private:
+ bool _learning;
+
+ InputPort* _midi_in_port;
+ InputPort* _param_port;
+ InputPort* _log_port;
+ InputPort* _min_port;
+ InputPort* _max_port;
+ OutputPort* _audio_port;
+
+ MidiLearnResponseEvent* _learn_event;
+};
+
+
+} // namespace Ingen
+
+#endif // MIDICONTROLNODE_H
diff --git a/src/engine/MidiDriver.hpp b/src/engine/MidiDriver.hpp
new file mode 100644
index 00000000..e268124b
--- /dev/null
+++ b/src/engine/MidiDriver.hpp
@@ -0,0 +1,100 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 MIDIDRIVER_H
+#define MIDIDRIVER_H
+
+#include <iostream>
+#include "types.hpp"
+#include "Driver.hpp"
+#include "EventBuffer.hpp"
+#include "ProcessContext.hpp"
+
+namespace Ingen {
+
+class ProcessContext;
+
+
+/** Midi driver abstract base class.
+ *
+ * \ingroup engine
+ */
+class MidiDriver : public Driver
+{
+public:
+ MidiDriver() : Driver(DataType::EVENT) {}
+
+ /** Prepare input for the specified (upcoming) cycle.
+ *
+ * Realtime safe, run in audio thread before executing the graph for a cycle.
+ */
+ virtual void pre_process(ProcessContext& context) = 0;
+
+ /** Prepare output for the specified (just completed) cycle.
+ *
+ * Realtime safe, run in audio thread after executing the graph for a cycle.
+ */
+ virtual void post_process(ProcessContext& context) = 0;
+};
+
+
+
+/** Dummy MIDIDriver.
+ *
+ * Not abstract, all functions are dummies. One of these will be allocated and
+ * "used" if no working MIDI driver is loaded. (Doing it this way as opposed to
+ * just making MidiDriver have dummy functions makes sure any existing MidiDriver
+ * derived class actually implements the required functions).
+ *
+ * \ingroup engine
+ */
+class DummyMidiDriver : public MidiDriver
+{
+public:
+ DummyMidiDriver() {
+ std::cout << "[DummyMidiDriver] Started Dummy MIDI driver." << std::endl;
+ }
+
+ ~DummyMidiDriver() {}
+
+ void activate() {}
+ void deactivate() {}
+
+ bool is_activated() const { return false; }
+ bool is_enabled() const { return false; }
+
+ void enable() {}
+ void disable() {}
+
+ DriverPort* create_port(DuplexPort* patch_port) { return NULL; }
+
+ DriverPort* driver_port(const Raul::Path& path) { return NULL; }
+
+ DriverPort* new_port(DuplexPort* patch_port) { return NULL; }
+
+ void add_port(DriverPort* port) {}
+ DriverPort* remove_port(const Raul::Path& path) { return NULL; }
+
+ void pre_process(ProcessContext& context) {}
+ void post_process(ProcessContext& context) {}
+};
+
+
+
+} // namespace Ingen
+
+#endif // MIDIDRIVER_H
diff --git a/src/engine/MidiNoteNode.cpp b/src/engine/MidiNoteNode.cpp
new file mode 100644
index 00000000..ae7a7f0e
--- /dev/null
+++ b/src/engine/MidiNoteNode.cpp
@@ -0,0 +1,390 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 <raul/midi_events.h>
+#include <cmath>
+#include <iostream>
+#include "AudioBuffer.hpp"
+#include "AudioDriver.hpp"
+#include "InputPort.hpp"
+#include "InternalPlugin.hpp"
+#include "EventBuffer.hpp"
+#include "MidiNoteNode.hpp"
+#include "OutputPort.hpp"
+#include "PatchImpl.hpp"
+#include "ProcessContext.hpp"
+#include "util.hpp"
+
+using namespace std;
+
+namespace Ingen {
+
+
+MidiNoteNode::MidiNoteNode(const string& path, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size)
+ : NodeBase(new InternalPlugin(NS_INGEN "note_node", "note", "MIDI Note"),
+ path, polyphonic, parent, srate, buffer_size)
+ , _voices(new Raul::Array<Voice>(_polyphony))
+ , _prepared_voices(NULL)
+ , _sustain(false)
+{
+ _ports = new Raul::Array<PortImpl*>(5);
+
+ _midi_in_port = new InputPort(this, "input", 0, 1, DataType::EVENT, Atom(), _buffer_size);
+ _ports->at(0) = _midi_in_port;
+
+ _freq_port = new OutputPort(this, "frequency", 1, _polyphony, DataType::AUDIO, 440.0f, _buffer_size);
+ _ports->at(1) = _freq_port;
+
+ _vel_port = new OutputPort(this, "velocity", 2, _polyphony, DataType::AUDIO, 0.0f, _buffer_size);
+ _vel_port->set_variable("ingen:minimum", 0.0f);
+ _vel_port->set_variable("ingen:maximum", 1.0f);
+ _ports->at(2) = _vel_port;
+
+ _gate_port = new OutputPort(this, "gate", 3, _polyphony, DataType::AUDIO, 0.0f, _buffer_size);
+ _gate_port->set_variable("ingen:toggled", true);
+ _ports->at(3) = _gate_port;
+
+ _trig_port = new OutputPort(this, "trigger", 4, _polyphony, DataType::AUDIO, 0.0f, _buffer_size);
+ _trig_port->set_variable("ingen:toggled", true);
+ _ports->at(4) = _trig_port;
+}
+
+
+MidiNoteNode::~MidiNoteNode()
+{
+ delete _voices;
+}
+
+
+bool
+MidiNoteNode::prepare_poly(uint32_t poly)
+{
+ if (!_polyphonic)
+ return true;
+
+ NodeBase::prepare_poly(poly);
+
+ if (_prepared_voices && poly <= _prepared_voices->size())
+ return true;
+
+ _prepared_voices = new Raul::Array<Voice>(poly, *_voices);
+
+ return true;
+}
+
+
+bool
+MidiNoteNode::apply_poly(Raul::Maid& maid, uint32_t poly)
+{
+ if (!_polyphonic)
+ return true;
+
+ NodeBase::apply_poly(maid, poly);
+
+ if (_prepared_voices) {
+ assert(poly <= _prepared_voices->size());
+ maid.push(_voices);
+ _voices = _prepared_voices;
+ _prepared_voices = NULL;
+ }
+
+ _polyphony = poly;
+ assert(_voices->size() >= _polyphony);
+
+ return true;
+}
+
+
+void
+MidiNoteNode::process(ProcessContext& context)
+{
+ NodeBase::pre_process(context);
+
+ uint32_t frames = 0;
+ uint32_t subframes = 0;
+ uint16_t type = 0;
+ uint16_t size = 0;
+ unsigned char* buf = NULL;
+
+ EventBuffer* const midi_in = (EventBuffer*)_midi_in_port->buffer(0);
+ assert(midi_in->this_nframes() == context.nframes());
+
+ //cerr << path() << " # input events: " << midi_in->event_count() << endl;
+
+ if (midi_in->event_count() > 0)
+ while (midi_in->get_event(&frames, &subframes, &type, &size, &buf)) {
+
+ /*cout << "EVENT TYPE " << type << " @ " << frames << "." << subframes << ": ";
+ for (uint16_t i = 0; i < size; ++i)
+ cout << (int)((char)buf[i]) << " ";
+ cout << endl;*/
+
+ 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:
+ //cerr << "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]);
+ }
+
+ if (midi_in->increment() == midi_in->this_nframes())
+ break;
+ }
+
+ NodeBase::post_process(context);
+}
+
+
+void
+MidiNoteNode::note_on(ProcessContext& context, uchar note_num, uchar velocity, FrameTime time)
+{
+ assert(time >= context.start() && time <= context.end());
+ assert(time - context.start() < _buffer_size);
+ assert(note_num <= 127);
+
+ Key* key = &_keys[note_num];
+ Voice* voice = NULL;
+ uint32_t voice_num = 0;
+
+ if (key->state != Key::OFF) {
+ //cerr << "[MidiNoteNode] Double midi note received" << endl;
+ 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];
+ jack_nframes_t 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]);
+
+ /*cerr << "[MidiNoteNode] Note " << (int)note_num << " on @ " << time
+ << ". Voice " << voice_num << " / " << _polyphony << endl;*/
+
+ // 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;
+ //cerr << "[MidiNoteNode] Stole voice " << voice_num << endl;
+ }
+
+ // 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))->set_value(note_to_freq(note_num), context.start(), time);
+ ((AudioBuffer*)_vel_port->buffer(voice_num))->set_value(velocity/127.0, context.start(), time);
+ ((AudioBuffer*)_gate_port->buffer(voice_num))->set_value(1.0f, context.start(), time);
+
+ // trigger (one sample)
+ ((AudioBuffer*)_trig_port->buffer(voice_num))->set_value(1.0f, context.start(), time);
+ ((AudioBuffer*)_trig_port->buffer(voice_num))->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
+MidiNoteNode::note_off(ProcessContext& context, uchar note_num, FrameTime time)
+{
+ assert(time >= context.start() && time <= context.end());
+ assert(time - context.start() < _buffer_size);
+
+ Key* key = &_keys[note_num];
+
+ //cerr << "[MidiNoteNode] Note " << (int)note_num << " off @ " << time << endl;
+
+ 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) {
+ //cerr << "... free voice " << key->voice << endl;
+ free_voice(context, key->voice, time);
+ } else {
+ //cerr << "... hold voice " << key->voice << endl;
+ (*_voices)[key->voice].state = Voice::HOLDING;
+ }
+
+ } else {
+#ifndef NDEBUG
+ cerr << "WARNING: Assigned key, but voice not active" << endl;
+#endif
+ }
+ }
+
+ key->state = Key::OFF;
+}
+
+
+void
+MidiNoteNode::free_voice(ProcessContext& context, uint32_t voice, FrameTime time)
+{
+ assert(time >= context.start() && time <= context.end());
+ assert(time - context.start() < _buffer_size);
+
+ // Find a key to reassign to the freed voice (the newest, if there is one)
+ Key* replace_key = NULL;
+ uchar replace_key_num = 0;
+
+ for (uchar 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))->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)
+ //cerr << "[MidiNoteNode] Note off. Key " << (int)note_num << ", Voice " << voice << " Killed" << endl;
+ ((AudioBuffer*)_gate_port->buffer(voice))->set_value(0.0f, context.start(), time);
+ (*_voices)[voice].state = Voice::FREE;
+ }
+}
+
+
+void
+MidiNoteNode::all_notes_off(ProcessContext& context, FrameTime time)
+{
+ assert(time >= context.start() && time <= context.end());
+ assert(time - context.start() < _buffer_size);
+
+ //cerr << "All notes off @ " << offset << endl;
+
+ // FIXME: set all keys to Key::OFF?
+
+ for (uint32_t i = 0; i < _polyphony; ++i) {
+ ((AudioBuffer*)_gate_port->buffer(i))->set_value(0.0f, context.start(), time);
+ (*_voices)[i].state = Voice::FREE;
+ }
+}
+
+
+float
+MidiNoteNode::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; // Some LADSPA plugins don't like freq=0
+}
+
+
+void
+MidiNoteNode::sustain_on(ProcessContext& context, FrameTime time)
+{
+ _sustain = true;
+}
+
+
+void
+MidiNoteNode::sustain_off(ProcessContext& context, FrameTime time)
+{
+ assert(time >= context.start() && time <= context.end());
+ assert(time - context.start() < _buffer_size);
+
+ _sustain = false;
+
+ for (uint32_t i=0; i < _polyphony; ++i)
+ if ((*_voices)[i].state == Voice::HOLDING)
+ free_voice(context, i, time);
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/MidiNoteNode.hpp b/src/engine/MidiNoteNode.hpp
new file mode 100644
index 00000000..eebcbda6
--- /dev/null
+++ b/src/engine/MidiNoteNode.hpp
@@ -0,0 +1,88 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 MIDINOTENODE_H
+#define MIDINOTENODE_H
+
+#include <string>
+#include "types.hpp"
+#include "NodeBase.hpp"
+
+namespace Ingen {
+
+class InputPort;
+class OutputPort;
+
+
+/** MIDI note input node.
+ *
+ * For pitched instruments like keyboard, etc.
+ *
+ * \ingroup engine
+ */
+class MidiNoteNode : public NodeBase
+{
+public:
+ MidiNoteNode(const std::string& path, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size);
+ ~MidiNoteNode();
+
+ bool prepare_poly(uint32_t poly);
+ bool apply_poly(Raul::Maid& maid, uint32_t poly);
+
+ void process(ProcessContext& context);
+
+ void note_on(ProcessContext& context, uchar note_num, uchar velocity, FrameTime time);
+ void note_off(ProcessContext& context, uchar 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);
+
+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) {}
+ State state; uchar 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 Ingen
+
+#endif // MIDINOTENODE_H
diff --git a/src/engine/MidiTriggerNode.cpp b/src/engine/MidiTriggerNode.cpp
new file mode 100644
index 00000000..aa2c272f
--- /dev/null
+++ b/src/engine/MidiTriggerNode.cpp
@@ -0,0 +1,135 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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/midi_events.h>
+#include "MidiTriggerNode.hpp"
+#include "AudioBuffer.hpp"
+#include "InputPort.hpp"
+#include "OutputPort.hpp"
+#include "InternalPlugin.hpp"
+#include "ProcessContext.hpp"
+#include "EventBuffer.hpp"
+#include "util.hpp"
+
+using namespace std;
+
+namespace Ingen {
+
+
+MidiTriggerNode::MidiTriggerNode(const string& path, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size)
+ : NodeBase(new InternalPlugin(NS_INGEN "trigger_node", "trigger", "MIDI Trigger"),
+ path, false, parent, srate, buffer_size)
+{
+ _ports = new Raul::Array<PortImpl*>(5);
+
+ _midi_in_port = new InputPort(this, "input", 0, 1, DataType::EVENT, Atom(), _buffer_size);
+ _ports->at(0) = _midi_in_port;
+
+ _note_port = new InputPort(this, "note", 1, 1, DataType::CONTROL, 60.0f, 1);
+ _note_port->set_variable("ingen:minimum", 0.0f);
+ _note_port->set_variable("ingen:maximum", 127.0f);
+ _note_port->set_variable("ingen:integer", true);
+ _ports->at(1) = _note_port;
+
+ _gate_port = new OutputPort(this, "gate", 2, 1, DataType::AUDIO, 0.0f, _buffer_size);
+ _ports->at(2) = _gate_port;
+
+ _trig_port = new OutputPort(this, "trigger", 3, 1, DataType::AUDIO, 0.0f, _buffer_size);
+ _ports->at(3) = _trig_port;
+
+ _vel_port = new OutputPort(this, "velocity", 4, 1, DataType::AUDIO, 0.0f, _buffer_size);
+ _ports->at(4) = _vel_port;
+}
+
+
+void
+MidiTriggerNode::process(ProcessContext& context)
+{
+ NodeBase::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);
+ assert(midi_in->this_nframes() == context.nframes());
+
+ 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))->set_value(0.0f, context.start(), time);
+ default:
+ break;
+ }
+ }
+
+ midi_in->increment();
+ }
+
+ NodeBase::post_process(context);
+}
+
+
+void
+MidiTriggerNode::note_on(ProcessContext& context, uchar note_num, uchar velocity, FrameTime time)
+{
+ assert(time >= context.start() && time <= context.end());
+ assert(time - context.start() < _buffer_size);
+
+ /*cerr << "[MidiTriggerNode] " << path() << " Note " << (int)note_num << " on @ " << time << endl;*/
+
+ Sample filter_note = ((AudioBuffer*)_note_port->buffer(0))->value_at(0);
+ if (filter_note >= 0.0 && filter_note < 127.0 && (note_num == (uchar)filter_note)) {
+ ((AudioBuffer*)_gate_port->buffer(0))->set_value(1.0f, context.start(), time);
+ ((AudioBuffer*)_trig_port->buffer(0))->set_value(1.0f, context.start(), time);
+ ((AudioBuffer*)_trig_port->buffer(0))->set_value(0.0f, context.start(), time + 1);
+ ((AudioBuffer*)_vel_port->buffer(0))->set_value(velocity / 127.0f, context.start(), time);
+ assert(((AudioBuffer*)_trig_port->buffer(0))->data()[time - context.start()] == 1.0f);
+ }
+}
+
+
+void
+MidiTriggerNode::note_off(ProcessContext& context, uchar note_num, FrameTime time)
+{
+ assert(time >= context.start() && time <= context.end());
+ assert(time - context.start() < _buffer_size);
+
+ if (note_num == lrintf(((AudioBuffer*)_note_port->buffer(0))->value_at(0)))
+ ((AudioBuffer*)_gate_port->buffer(0))->set_value(0.0f, context.start(), time);
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/MidiTriggerNode.hpp b/src/engine/MidiTriggerNode.hpp
new file mode 100644
index 00000000..ba96589b
--- /dev/null
+++ b/src/engine/MidiTriggerNode.hpp
@@ -0,0 +1,61 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 MIDITRIGGERNODE_H
+#define MIDITRIGGERNODE_H
+
+#include <string>
+#include "NodeBase.hpp"
+
+namespace Ingen {
+
+class InputPort;
+class OutputPort;
+
+
+/** 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 MidiTriggerNode : public NodeBase
+{
+public:
+ MidiTriggerNode(const std::string& path, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size);
+
+ void process(ProcessContext& context);
+
+ void note_on(ProcessContext& context, uchar note_num, uchar velocity, FrameTime time);
+ void note_off(ProcessContext& context, uchar note_num, FrameTime time);
+
+private:
+ InputPort* _midi_in_port;
+ InputPort* _note_port;
+ OutputPort* _gate_port;
+ OutputPort* _trig_port;
+ OutputPort* _vel_port;
+};
+
+
+} // namespace Ingen
+
+#endif // MIDITRIGGERNODE_H
diff --git a/src/engine/NodeBase.cpp b/src/engine/NodeBase.cpp
new file mode 100644
index 00000000..bb4f0e5c
--- /dev/null
+++ b/src/engine/NodeBase.cpp
@@ -0,0 +1,230 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "NodeBase.hpp"
+#include <cassert>
+#include <iostream>
+#include <stdint.h>
+#include <raul/List.hpp>
+#include <raul/Array.hpp>
+#include "util.hpp"
+#include "PluginImpl.hpp"
+#include "ClientBroadcaster.hpp"
+#include "PortImpl.hpp"
+#include "PatchImpl.hpp"
+#include "EngineStore.hpp"
+#include "ThreadManager.hpp"
+
+using namespace std;
+
+namespace Ingen {
+
+
+NodeBase::NodeBase(PluginImpl* plugin, const string& name, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size)
+ : NodeImpl(parent, name, polyphonic)
+ , _plugin(plugin)
+ , _polyphony((polyphonic && parent) ? parent->internal_polyphony() : 1)
+ , _srate(srate)
+ , _buffer_size(buffer_size)
+ , _activated(false)
+ , _traversed(false)
+ , _input_ready(1)
+ , _process_lock(0)
+ , _n_inputs_ready(0)
+ , _ports(NULL)
+ , _providers(new Raul::List<NodeImpl*>())
+ , _dependants(new Raul::List<NodeImpl*>())
+{
+ assert(_plugin);
+ assert(_polyphony > 0);
+ assert(_parent == NULL || (_polyphony == parent->internal_polyphony() || _polyphony == 1));
+}
+
+
+NodeBase::~NodeBase()
+{
+ if (_activated)
+ deactivate();
+
+ delete _providers;
+ delete _dependants;
+}
+
+
+Port*
+NodeBase::port(uint32_t index) const
+{
+ return (*_ports)[index];
+}
+
+
+const Plugin*
+NodeBase::plugin() const
+{
+ return _plugin;
+}
+
+
+void
+NodeBase::activate()
+{
+ assert(ThreadManager::current_thread_id() == THREAD_PRE_PROCESS);
+ assert(!_activated);
+ _activated = true;
+}
+
+
+void
+NodeBase::deactivate()
+{
+ // FIXME: Not true witn monolithic GUI/engine
+ //assert(ThreadManager::current_thread_id() == THREAD_POST_PROCESS);
+ assert(_activated);
+ _activated = false;
+}
+
+
+bool
+NodeBase::prepare_poly(uint32_t poly)
+{
+ assert(ThreadManager::current_thread_id() == THREAD_PRE_PROCESS);
+
+ if (!_polyphonic)
+ return true;
+
+ if (_ports)
+ for (size_t i=0; i < _ports->size(); ++i)
+ _ports->at(i)->prepare_poly(poly);
+
+ return true;
+}
+
+
+bool
+NodeBase::apply_poly(Raul::Maid& maid, uint32_t poly)
+{
+ assert(ThreadManager::current_thread_id() == THREAD_PROCESS);
+
+ if (!_polyphonic)
+ return true;
+
+ for (size_t i=0; i < num_ports(); ++i) {
+ _ports->at(i)->apply_poly(maid, poly);
+ assert(_ports->at(i)->poly() == poly);
+ }
+
+ for (uint32_t i=0; i < num_ports(); ++i)
+ for (uint32_t j=0; j < _polyphony; ++j)
+ set_port_buffer(j, i, _ports->at(i)->buffer(j));
+
+ return true;
+}
+
+
+void
+NodeBase::set_buffer_size(size_t size)
+{
+ assert(ThreadManager::current_thread_id() == THREAD_PROCESS);
+
+ _buffer_size = size;
+
+ if (_ports)
+ for (size_t i=0; i < _ports->size(); ++i)
+ _ports->at(i)->set_buffer_size(size);
+}
+
+
+void
+NodeBase::reset_input_ready()
+{
+ //cout << path() << " RESET" << endl;
+ _n_inputs_ready = 0;
+ _process_lock = 0;
+ _input_ready.reset(0);
+}
+
+
+bool
+NodeBase::process_lock()
+{
+ return _process_lock.compare_and_exchange(0, 1);
+}
+
+
+void
+NodeBase::process_unlock()
+{
+ _process_lock = 0;
+}
+
+
+void
+NodeBase::wait_for_input(size_t num_providers)
+{
+ assert(ThreadManager::current_thread_id() == THREAD_PROCESS);
+ assert(_process_lock.get() == 1);
+
+ while ((unsigned)_n_inputs_ready.get() < num_providers) {
+ //cout << path() << " WAITING " << _n_inputs_ready.get() << endl;
+ _input_ready.wait();
+ //cout << path() << " CAUGHT SIGNAL" << endl;
+ //++_n_inputs_ready;
+ }
+
+ //cout << path() << " READY" << endl;
+}
+
+
+void
+NodeBase::signal_input_ready()
+{
+ assert(ThreadManager::current_thread_id() == THREAD_PROCESS);
+ //cout << path() << " SIGNAL" << endl;
+ ++_n_inputs_ready;
+ _input_ready.post();
+}
+
+
+/** Prepare to run a cycle (in the audio thread)
+ */
+void
+NodeBase::pre_process(ProcessContext& context)
+{
+ assert(ThreadManager::current_thread_id() == THREAD_PROCESS);
+
+ // Mix down any ports with multiple inputs
+ for (size_t i=0; i < num_ports(); ++i)
+ _ports->at(i)->pre_process(context);
+}
+
+
+/** Prepare to run a cycle (in the audio thread)
+ */
+void
+NodeBase::post_process(ProcessContext& context)
+{
+ assert(ThreadManager::current_thread_id() == THREAD_PROCESS);
+
+ /* Write output ports */
+ if (_ports)
+ for (size_t i=0; i < _ports->size(); ++i)
+ _ports->at(i)->post_process(context);
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/NodeBase.hpp b/src/engine/NodeBase.hpp
new file mode 100644
index 00000000..e3710aa9
--- /dev/null
+++ b/src/engine/NodeBase.hpp
@@ -0,0 +1,132 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 NODEBASE_H
+#define NODEBASE_H
+
+#include "types.hpp"
+#include <string>
+#include <cstdlib>
+#include <raul/Semaphore.hpp>
+#include <raul/AtomicInt.hpp>
+#include "interface/Port.hpp"
+#include "NodeImpl.hpp"
+
+using std::string;
+
+namespace Ingen {
+
+class PluginImpl;
+class PatchImpl;
+class EngineStore;
+
+namespace Shared {
+ class ClientInterface;
+} using Shared::ClientInterface;
+
+
+/** Common implementation stuff for Node.
+ *
+ * Pretty much just attributes and getters/setters are here.
+ *
+ * \ingroup engine
+ */
+class NodeBase : public NodeImpl
+{
+public:
+ NodeBase(PluginImpl* plugin,
+ const string& name,
+ bool poly,
+ PatchImpl* parent,
+ SampleRate rate,
+ size_t buffer_size);
+
+ virtual ~NodeBase();
+
+ virtual void activate();
+ virtual void deactivate();
+ bool activated() { return _activated; }
+
+ virtual bool prepare_poly(uint32_t poly);
+ virtual bool apply_poly(Raul::Maid& maid, uint32_t poly);
+
+ virtual void reset_input_ready();
+ virtual bool process_lock();
+ virtual void process_unlock();
+ virtual void wait_for_input(size_t num_providers);
+ virtual unsigned n_inputs_ready() const { return _n_inputs_ready.get(); }
+
+ virtual void message_process(MessageContext& context, uint32_t* output) {}
+
+ virtual void pre_process(ProcessContext& context);
+ virtual void process(ProcessContext& context) = 0;
+ virtual void post_process(ProcessContext& context);
+
+ virtual void set_port_buffer(uint32_t voice, uint32_t port_num, Buffer* buf) {}
+
+ virtual void set_buffer_size(size_t size);
+
+ SampleRate sample_rate() const { return _srate; }
+ size_t buffer_size() const { return _buffer_size; }
+ uint32_t num_ports() const { return _ports ? _ports->size() : 0; }
+ uint32_t polyphony() const { return _polyphony; }
+ bool traversed() const { return _traversed; }
+ void traversed(bool b) { _traversed = b; }
+
+ virtual Port* port(uint32_t index) const;
+ virtual PortImpl* port_impl(uint32_t index) const { return (*_ports)[index]; }
+
+ /* These are NOT to be used in the audio thread!
+ * The providers and dependants in CompiledNode are for that
+ */
+
+ Raul::List<NodeImpl*>* providers() { return _providers; }
+ void providers(Raul::List<NodeImpl*>* l) { _providers = l; }
+
+ Raul::List<NodeImpl*>* dependants() { return _dependants; }
+ void dependants(Raul::List<NodeImpl*>* l) { _dependants = l; }
+
+ virtual const Plugin* plugin() const;
+ virtual PluginImpl* plugin_impl() const { return _plugin; }
+ virtual void plugin(PluginImpl* pi) { _plugin = pi; }
+
+ /** A node's parent is always a patch, so static cast should be safe */
+ inline PatchImpl* parent_patch() const { return (PatchImpl*)_parent; }
+
+protected:
+ virtual void signal_input_ready();
+
+ PluginImpl* _plugin;
+
+ uint32_t _polyphony;
+ SampleRate _srate;
+ size_t _buffer_size;
+ bool _activated;
+
+ bool _traversed; ///< Flag for process order algorithm
+ 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
+};
+
+
+} // namespace Ingen
+
+#endif // NODEBASE_H
diff --git a/src/engine/NodeFactory.cpp b/src/engine/NodeFactory.cpp
new file mode 100644
index 00000000..c868d067
--- /dev/null
+++ b/src/engine/NodeFactory.cpp
@@ -0,0 +1,285 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 CONFIG_H_PATH
+#include <cstdlib>
+#include <pthread.h>
+#include <dirent.h>
+#include <float.h>
+#include <cmath>
+#include <redlandmm/World.hpp>
+#include "module/World.hpp"
+#include "NodeFactory.hpp"
+#include "ThreadManager.hpp"
+#include "MidiNoteNode.hpp"
+#include "MidiTriggerNode.hpp"
+#include "MidiControlNode.hpp"
+#include "TransportNode.hpp"
+#include "PatchImpl.hpp"
+#include "InternalPlugin.hpp"
+#ifdef HAVE_LADSPA
+#include "LADSPANode.hpp"
+#include "LADSPAPlugin.hpp"
+#endif
+#ifdef HAVE_SLV2
+#include <slv2/slv2.h>
+#include "LV2Plugin.hpp"
+#include "LV2Node.hpp"
+#endif
+
+using namespace std;
+
+namespace Ingen {
+
+
+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)
+ if (i->second->type() != Plugin::Internal)
+ delete i->second;
+
+ _plugins.clear();
+}
+
+
+PluginImpl*
+NodeFactory::plugin(const string& uri)
+{
+ const Plugins::const_iterator i = _plugins.find(uri);
+ return ((i != _plugins.end()) ? i->second : NULL);
+}
+
+
+/** DEPRECATED: Find a plugin by type, lib, label.
+ *
+ * Slow. Evil. Do not use.
+ */
+PluginImpl*
+NodeFactory::plugin(const string& type, const string& lib, const string& label)
+{
+ if (type != "LADSPA" || lib == "" || label == "")
+ return NULL;
+
+#ifdef HAVE_LADSPA
+ for (Plugins::const_iterator i = _plugins.begin(); i != _plugins.end(); ++i) {
+ LADSPAPlugin* lp = dynamic_cast<LADSPAPlugin*>(i->second);
+ if (lp && lp->type_string() == type
+ && lp->library_name() == lib
+ && lp->label() == label)
+ return lp;
+ }
+#endif
+
+ cerr << "ERROR: Failed to find " << type << " plugin " << lib << " / " << label << endl;
+
+ return NULL;
+}
+
+
+void
+NodeFactory::load_plugins()
+{
+ assert(ThreadManager::current_thread_id() == THREAD_PRE_PROCESS);
+
+ _world->rdf_world->mutex().lock();
+
+ // 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
+
+#ifdef HAVE_LADSPA
+ load_ladspa_plugins();
+#endif
+
+ _has_loaded = true;
+ }
+
+ _world->rdf_world->mutex().unlock();
+
+ //cerr << "[NodeFactory] # Plugins: " << _plugins.size() << endl;
+}
+
+
+void
+NodeFactory::load_internal_plugins()
+{
+ // This is a touch gross...
+
+ PatchImpl* parent = new PatchImpl(*_world->local_engine, "dummy", 1, NULL, 1, 1, 1);
+
+ NodeImpl* n = NULL;
+ n = new MidiNoteNode("foo", 1, parent, 1, 1);
+ _plugins.insert(make_pair(n->plugin_impl()->uri(), n->plugin_impl()));
+ delete n;
+ n = new MidiTriggerNode("foo", 1, parent, 1, 1);
+ _plugins.insert(make_pair(n->plugin_impl()->uri(), n->plugin_impl()));
+ delete n;
+ n = new MidiControlNode("foo", 1, parent, 1, 1);
+ _plugins.insert(make_pair(n->plugin_impl()->uri(), n->plugin_impl()));
+ delete n;
+ n = new TransportNode("foo", 1, parent, 1, 1);
+ _plugins.insert(make_pair(n->plugin_impl()->uri(), n->plugin_impl()));
+ delete n;
+
+ delete parent;
+}
+
+
+#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);
+
+ //cerr << "[NodeFactory] Found " << slv2_plugins_size(plugins) << " LV2 plugins:" << endl;
+
+ for (unsigned i=0; i < slv2_plugins_size(plugins); ++i) {
+
+ SLV2Plugin lv2_plug = slv2_plugins_get_at(plugins, i);
+
+ const string uri(slv2_value_as_uri(slv2_plugin_get_uri(lv2_plug)));
+
+#ifndef NDEBUG
+ assert(_plugins.find(uri) == _plugins.end());
+#endif
+
+ LV2Plugin* const plugin = new LV2Plugin(_lv2_info, uri);
+
+ plugin->slv2_plugin(lv2_plug);
+ plugin->library_path(slv2_uri_to_path(slv2_value_as_uri(
+ slv2_plugin_get_library_uri(lv2_plug))));
+ _plugins.insert(make_pair(uri, plugin));
+ }
+
+ slv2_plugins_free(_world->slv2_world, plugins);
+}
+#endif // HAVE_SLV2
+
+
+#ifdef HAVE_LADSPA
+/** Loads information about all LADSPA plugins into internal plugin database.
+ */
+void
+NodeFactory::load_ladspa_plugins()
+{
+ char* env_ladspa_path = getenv("LADSPA_PATH");
+ string ladspa_path;
+ if (!env_ladspa_path) {
+ cerr << "[NodeFactory] LADSPA_PATH is empty. Assuming /usr/lib/ladspa:/usr/local/lib/ladspa:~/.ladspa" << endl;
+ ladspa_path = string("/usr/lib/ladspa:/usr/local/lib/ladspa:").append(
+ getenv("HOME")).append("/.ladspa");
+ } else {
+ ladspa_path = env_ladspa_path;
+ }
+
+ // Yep, this should use an sstream alright..
+ while (ladspa_path != "") {
+ const string dir = ladspa_path.substr(0, ladspa_path.find(':'));
+ if (ladspa_path.find(':') != string::npos)
+ ladspa_path = ladspa_path.substr(ladspa_path.find(':')+1);
+ else
+ ladspa_path = "";
+
+ DIR* pdir = opendir(dir.c_str());
+ if (pdir == NULL) {
+ //cerr << "[NodeFactory] Unreadable directory in LADSPA_PATH: " << dir.c_str() << endl;
+ continue;
+ }
+
+ struct dirent* pfile;
+ while ((pfile = readdir(pdir))) {
+
+ LADSPA_Descriptor_Function df = NULL;
+ LADSPA_Descriptor* descriptor = NULL;
+
+ if (!strcmp(pfile->d_name, ".") || !strcmp(pfile->d_name, ".."))
+ continue;
+
+ const string lib_path = dir +"/"+ pfile->d_name;
+
+ // Ignore stupid libtool files. Kludge alert.
+ if (lib_path.substr(lib_path.length()-3) == ".la") {
+ //cerr << "WARNING: Skipping stupid libtool file " << pfile->d_name << endl;
+ continue;
+ }
+
+ Glib::Module* plugin_library = new Glib::Module(lib_path, Glib::MODULE_BIND_LOCAL);
+ if (!plugin_library || !(*plugin_library)) {
+ cerr << "WARNING: Failed to load LADSPA library " << lib_path << endl;
+ continue;
+ }
+
+ bool found = plugin_library->get_symbol("ladspa_descriptor", (void*&)df);
+ if (!found || !df) {
+ cerr << "WARNING: Non-LADSPA library found in LADSPA path: " <<
+ lib_path << endl;
+ // Not a LADSPA plugin library
+ delete plugin_library;
+ continue;
+ }
+
+ for (unsigned long i=0; (descriptor = (LADSPA_Descriptor*)df(i)) != NULL; ++i) {
+ char id_str[11];
+ snprintf(id_str, 11, "%lu", descriptor->UniqueID);
+ const string uri = string("ladspa:").append(id_str);
+
+ const Plugins::const_iterator i = _plugins.find(uri);
+
+ if (i == _plugins.end()) {
+ LADSPAPlugin* plugin = new LADSPAPlugin(lib_path, uri,
+ descriptor->UniqueID,
+ descriptor->Label,
+ descriptor->Name);
+
+ _plugins.insert(make_pair(uri, plugin));
+
+ } else {
+ cerr << "Warning: Duplicate " << uri
+ << " - Using " << i->second->library_path()
+ << " over " << lib_path << endl;
+ }
+ }
+
+ delete plugin_library;
+ }
+ closedir(pdir);
+ }
+}
+#endif // HAVE_LADSPA
+
+
+} // namespace Ingen
diff --git a/src/engine/NodeFactory.hpp b/src/engine/NodeFactory.hpp
new file mode 100644
index 00000000..95194350
--- /dev/null
+++ b/src/engine/NodeFactory.hpp
@@ -0,0 +1,92 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 NODEFACTORY_H
+#define NODEFACTORY_H
+
+#include CONFIG_H_PATH
+#include "module/global.hpp"
+
+#include <list>
+#include <map>
+#include <string>
+#include <pthread.h>
+#include <glibmm/module.h>
+#ifdef HAVE_SLV2
+#include <slv2/slv2.h>
+#include "LV2Info.hpp"
+#endif
+#include "types.hpp"
+
+using std::string;
+
+namespace Ingen {
+
+class NodeImpl;
+class PatchImpl;
+class PluginImpl;
+
+
+/** Loads plugins and creates Nodes from them.
+ *
+ * NodeFactory's responsibility is to get enough information to allow the
+ * loading of a plugin possible (ie finding/opening shared libraries etc)
+ *
+ * The constructor of various Node types (ie LADSPANode) are responsible
+ * for actually creating a Node instance of the plugin.
+ *
+ * \ingroup engine
+ */
+class NodeFactory
+{
+public:
+ NodeFactory(Ingen::Shared::World* world);
+ ~NodeFactory();
+
+ void load_plugins();
+
+ typedef std::map<std::string,PluginImpl*> Plugins;
+ const Plugins& plugins() const { return _plugins; }
+
+ PluginImpl* plugin(const string& uri);
+
+ /** DEPRECATED */
+ PluginImpl* plugin(const string& type, const string& lib, const string& label);
+
+private:
+#ifdef HAVE_LADSPA
+ void load_ladspa_plugins();
+#endif
+
+#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 Ingen
+
+#endif // NODEFACTORY_H
diff --git a/src/engine/NodeImpl.hpp b/src/engine/NodeImpl.hpp
new file mode 100644
index 00000000..ac125c13
--- /dev/null
+++ b/src/engine/NodeImpl.hpp
@@ -0,0 +1,170 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 NODEIMPL_H
+#define NODEIMPL_H
+
+#include <string>
+#include <raul/Array.hpp>
+#include "interface/Node.hpp"
+#include "types.hpp"
+#include "GraphObjectImpl.hpp"
+
+namespace Raul { template <typename T> class List; class Maid; }
+
+namespace Ingen {
+
+namespace Shared { class Plugin; class Node; class Port; }
+
+using namespace Shared;
+
+class Buffer;
+class PluginImpl;
+class PatchImpl;
+class PortImpl;
+class MessageContext;
+
+
+/** 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.
+ *
+ * This is a pure abstract base class for any Node, it contains no
+ * implementation details/data whatsoever. This is the interface you need to
+ * implement to add a new Node type.
+ *
+ * \ingroup engine
+ */
+class NodeImpl : public GraphObjectImpl, virtual public Ingen::Shared::Node
+{
+public:
+ NodeImpl(GraphObjectImpl* parent, const std::string& name, bool poly)
+ : GraphObjectImpl(parent, name, poly)
+ {}
+
+ /** Activate this Node.
+ *
+ * This function will 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() = 0;
+ virtual void deactivate() = 0;
+ virtual bool activated() = 0;
+
+ /** Prepare for a new (external) polyphony value.
+ *
+ * Preprocessor thread, poly is actually applied by apply_poly.
+ * \return true on success.
+ */
+ virtual bool prepare_poly(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;
+
+ /** Parallelism: Reset flags for start of a new cycle.
+ */
+ virtual void reset_input_ready() = 0;
+
+ /** 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() = 0;
+
+ /** 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() = 0;
+
+ /** 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) = 0;
+
+ /** 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() = 0;
+
+ /** Parallelism: Return the number of providers that have signalled.
+ */
+ virtual unsigned n_inputs_ready() const = 0;
+
+ /** Run the node for one instant in the message thread.
+ */
+ virtual void message_process(MessageContext& context, uint32_t* output) = 0;
+
+ /** 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;
+
+ virtual void set_port_buffer(uint32_t voice, uint32_t port_num, Buffer* buf) = 0;
+
+ virtual uint32_t num_ports() const = 0;
+
+ virtual Port* port(uint32_t index) const = 0;
+ virtual PortImpl* port_impl(uint32_t index) const = 0;
+
+ /** Used by the process order finding algorithm (ie during connections) */
+ virtual bool traversed() const = 0;
+ virtual void traversed(bool b) = 0;
+
+ /** Nodes that are connected to this Node's inputs.
+ * (This Node depends on them)
+ */
+ virtual Raul::List<NodeImpl*>* providers() = 0;
+ virtual void providers(Raul::List<NodeImpl*>* l) = 0;
+
+ /** Nodes are are connected to this Node's outputs.
+ * (They depend on this Node)
+ */
+ virtual Raul::List<NodeImpl*>* dependants() = 0;
+ virtual void dependants(Raul::List<NodeImpl*>* l) = 0;
+
+ /** The Patch this Node belongs to. */
+ virtual PatchImpl* parent_patch() const = 0;
+
+ /** 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 = 0;
+
+ /** Information about the Plugin this Node is an instance of.
+ * Not the best name - not all nodes come from plugins (ie Patch)
+ */
+ virtual const Shared::Plugin* plugin() const = 0;
+
+ virtual void set_buffer_size(size_t size) = 0;
+};
+
+
+} // namespace Ingen
+
+#endif // NODEIMPL_H
diff --git a/src/engine/OSCClientSender.cpp b/src/engine/OSCClientSender.cpp
new file mode 100644
index 00000000..dca2e0ed
--- /dev/null
+++ b/src/engine/OSCClientSender.cpp
@@ -0,0 +1,358 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "OSCClientSender.hpp"
+#include <cassert>
+#include <iostream>
+#include <unistd.h>
+#include <raul/AtomLiblo.hpp>
+#include "EngineStore.hpp"
+#include "NodeFactory.hpp"
+#include "util.hpp"
+#include "PatchImpl.hpp"
+#include "NodeImpl.hpp"
+#include "PluginImpl.hpp"
+#include "PortImpl.hpp"
+#include "ConnectionImpl.hpp"
+#include "AudioDriver.hpp"
+#include "interface/ClientInterface.hpp"
+
+using namespace std;
+
+namespace Ingen {
+
+
+/*! \page client_osc_namespace Client OSC Namespace Documentation
+ *
+ * <p>NOTE: this comment doesn't really apply any longer.. sort of.
+ * but maybe it still should.. maybe. so it remains...</p>
+ *
+ * <p>These are all the messages sent from the engine to the client.
+ * Communication takes place over two distinct bands: control band and
+ * notification band.</p>
+ * <p>The control band is where clients send commands, and receive a simple
+ * response, either OK or an error.</p>
+ * <p>All notifications of engine state (ie new nodes) are sent over the
+ * notification band <em>which is seperate from the control band</em>. The
+ * reasoning behind this is that many clients may be connected at the same
+ * time - a client may receive notifications that are not a direct consequence
+ * of some message it sent.</p>
+ * <p>The notification band can be thought of as a stream of events representing
+ * the changing engine state. For example, It is possible for a client to send
+ * commands and receive aknowledgements, and not listen to the notification band
+ * at all; or (in the near future anyway) for a client to use UDP for the control
+ * band (for speed), and TCP for the notification band (for reliability and
+ * order guarantees).</p>
+ * \n\n
+ */
+
+
+/** \page client_osc_namespace
+ * \n
+ * <h2>Control Band</h2>
+ */
+
+/** \page client_osc_namespace
+ * <p> \b /ingen/ok - Respond to a successful user command
+ * \arg \b response-id (int) - Request ID this is a response to
+ * </p> \n \n
+ */
+void
+OSCClientSender::response_ok(int32_t id)
+{
+ if (!_enabled)
+ return;
+
+ if (lo_send(_address, "/ingen/ok", "i", id) < 0) {
+ cerr << "Unable to send ok " << id << "! ("
+ << lo_address_errstr(_address) << ")" << endl;
+ }
+}
+
+
+/** \page client_osc_namespace
+ * <p> \b /ingen/response - Respond to a user command
+ * \arg \b response-id (int) - Request ID this is a response to
+ * \arg \b message (string) - Error message (natural language text)
+ * </p> \n \n
+ */
+void
+OSCClientSender::response_error(int32_t id, const std::string& msg)
+{
+ if (!_enabled)
+ return;
+
+ if (lo_send(_address, "/ingen/error", "is", id, msg.c_str()) < 0) {
+ cerr << "Unable to send error " << id << "! ("
+ << lo_address_errstr(_address) << ")" << endl;
+ }
+}
+
+
+/** \page client_osc_namespace
+ * \n
+ * <h2>Notification Band</h2>
+ */
+
+
+/** \page client_osc_namespace
+ * <p> \b /ingen/error - Notification that an error has occurred
+ * \arg \b message (string) - Error message (natural language text) \n\n
+ * \li This is for notification of errors that aren't a direct response to a
+ * user command, ie "unexpected" errors.</p> \n \n
+ */
+void
+OSCClientSender::error(const std::string& msg)
+{
+ send("/ingen/error", "s", msg.c_str(), LO_ARGS_END);
+}
+
+
+/** \page client_osc_namespace
+ * <p> \b /ingen/new_node - Notification of a new node's creation.
+ * \arg \b plug-uri (string) - URI of the plugin new node is an instance of
+ * \arg \b path (string) - Path of the new node
+ * \arg \b polyphonic (boolean) - Node is polyphonic\n\n
+ * \li New nodes are sent as a bundle. The first message in the bundle will be
+ * this one (/ingen/new_node), followed by a series of /ingen/new_port commands,
+ * followed by /ingen/new_node_end. </p> \n \n
+ */
+void OSCClientSender::new_node(const std::string& node_path,
+ const std::string& plugin_uri)
+{
+ send("/ingen/new_node", "ss", node_path.c_str(), plugin_uri.c_str(), LO_ARGS_END);
+}
+
+
+
+/** \page client_osc_namespace
+ * <p> \b /ingen/new_port - Notification of a new port's creation.
+ * \arg \b path (string) - Path of new port
+ * \arg \b index (integer) - Index (or sort key) of port on parent
+ * \arg \b data-type (string) - Type of port (ingen:AudioPort, ingen:ControlPort, ingen:MIDIPort, or ingen:OSCPort)
+ * \arg \b direction ("is-output") (integer) - Direction of data flow (Input = 0, Output = 1)
+ *
+ * \li Note that in the event of loading a patch, this message could be
+ * followed immediately by a control change, meaning the default-value is
+ * not actually the current value of the port.
+ * \li The minimum and maximum values are suggestions only, they are not
+ * enforced in any way, and going outside them is perfectly fine. Also note
+ * that the port ranges in om_gtk are not these ones! Those ranges are set
+ * as variable.</p> \n \n
+ */
+void
+OSCClientSender::new_port(const std::string& path,
+ uint32_t index,
+ const std::string& data_type,
+ bool is_output)
+{
+ send("/ingen/new_port", "sisi", path.c_str(), index, data_type.c_str(), is_output, LO_ARGS_END);
+}
+
+
+/** \page client_osc_namespace
+ * <p> \b /ingen/destroyed - Notification an object has been destroyed
+ * \arg \b path (string) - Path of object (which no longer exists) </p> \n \n
+ */
+void
+OSCClientSender::destroy(const std::string& path)
+{
+ assert(path != "/");
+
+ send("/ingen/destroyed", "s", path.c_str(), LO_ARGS_END);
+}
+
+
+/** \page client_osc_namespace
+ * <p> \b /ingen/patch_cleared - Notification a patch has been cleared (all children destroyed)
+ * \arg \b path (string) - Path of patch (which is now empty)</p> \n \n
+ */
+void
+OSCClientSender::patch_cleared(const std::string& patch_path)
+{
+ send("/ingen/patch_cleared", "s", patch_path.c_str(), LO_ARGS_END);
+}
+
+
+/** \page client_osc_namespace
+ * <p> \b /ingen/new_connection - Notification a new connection has been made.
+ * \arg \b src-path (string) - Path of the source port
+ * \arg \b dst-path (string) - Path of the destination port</p> \n \n
+ */
+void
+OSCClientSender::connect(const std::string& src_port_path, const std::string& dst_port_path)
+{
+ send("/ingen/new_connection", "ss", src_port_path.c_str(), dst_port_path.c_str(), LO_ARGS_END);
+}
+
+
+/** \page client_osc_namespace
+ * <p> \b /ingen/disconnection - Notification a connection has been unmade.
+ * \arg \b src-path (string) - Path of the source port
+ * \arg \b dst-path (string) - Path of the destination port</p> \n \n
+ */
+void
+OSCClientSender::disconnect(const std::string& src_port_path, const std::string& dst_port_path)
+{
+ send("/ingen/disconnection", "ss", src_port_path.c_str(), dst_port_path.c_str(), LO_ARGS_END);
+}
+
+
+/** \page client_osc_namespace
+ * <p> \b /ingen/set_variable - Notification of a variable.
+ * \arg \b path (string) - Path of the object associated with variable (node, patch, or port)
+ * \arg \b key (string)
+ * \arg \b value (string)</p> \n \n
+ */
+void
+OSCClientSender::set_variable(const std::string& path, const std::string& 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());
+ Raul::AtomLiblo::lo_message_add_atom(m, value);
+ send_message("/ingen/set_variable", m);
+}
+
+
+/** \page client_osc_namespace
+ * <p> \b /ingen/set_property - Notification of a property.
+ * \arg \b path (string) - Path of the object associated with property (node, patch, or port)
+ * \arg \b key (string)
+ * \arg \b value (string)</p> \n \n
+ */
+void
+OSCClientSender::set_property(const std::string& path, const std::string& 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());
+ Raul::AtomLiblo::lo_message_add_atom(m, value);
+ send_message("/ingen/set_property", m);
+}
+
+
+/** \page client_osc_namespace
+ * <p> \b /ingen/set_port_value - Notification the value of a port has changed
+ * \arg \b path (string) - Path of port
+ * \arg \b value (any) - New value of port </p> \n \n
+ */
+void
+OSCClientSender::set_port_value(const std::string& port_path, const Raul::Atom& value)
+{
+ lo_message m = lo_message_new();
+ lo_message_add_string(m, port_path.c_str());
+ Raul::AtomLiblo::lo_message_add_atom(m, value);
+ send_message("/ingen/set_port_value", m);
+}
+
+
+/** \page client_osc_namespace
+ * <p> \b /ingen/set_port_value - Notification the value of a port has changed
+ * \arg \b path (string) - Path of port
+ * \arg \b voice (int) - Voice which is set to this value
+ * \arg \b value (any) - New value of port </p> \n \n
+ */
+void
+OSCClientSender::set_voice_value(const std::string& port_path, uint32_t voice, const Raul::Atom& value)
+{
+ lo_message m = lo_message_new();
+ lo_message_add_string(m, port_path.c_str());
+ Raul::AtomLiblo::lo_message_add_atom(m, value);
+ send_message("/ingen/set_port_value", m);
+}
+
+
+/** \page client_osc_namespace
+ * <p> \b /ingen/port_activity - Notification of activity for a port (e.g. MIDI messages)
+ * \arg \b path (string) - Path of port </p> \n \n
+ */
+void
+OSCClientSender::port_activity(const std::string& port_path)
+{
+ if (!_enabled)
+ return;
+
+ lo_send(_address, "/ingen/port_activity", "s", port_path.c_str(), LO_ARGS_END);
+}
+
+
+/** \page client_osc_namespace
+ * <p> \b /ingen/plugin - Notification of the existance of a plugin
+ * \arg \b uri (string) - URI of plugin (e.g. http://example.org/filtermatic)
+ * \arg \b type (string) - Type of plugin (e.g. "ingen:LV2Plugin")
+ * \arg \b symbol (string) - Valid symbol for plugin (default symbol for nodes) (e.g. "adsr")
+ * \arg \b name (string) - Descriptive human-readable name of plugin (e.g. "ADSR Envelope")
+ */
+void
+OSCClientSender::new_plugin(const std::string& uri,
+ const std::string& type_uri,
+ const std::string& symbol,
+ const std::string& name)
+{
+ lo_message m = lo_message_new();
+ lo_message_add_string(m, uri.c_str());
+ lo_message_add_string(m, type_uri.c_str());
+ lo_message_add_string(m, symbol.c_str());
+ lo_message_add_string(m, name.c_str());
+ send_message("/ingen/plugin", m);
+}
+
+
+/** \page client_osc_namespace
+ * <p> \b /ingen/new_patch - Notification of a new patch
+ * \arg \b path (string) - Path of new patch
+ * \arg \b poly (int) - Polyphony of new patch (\em not a boolean like new_node) </p> \n \n
+ */
+void
+OSCClientSender::new_patch(const std::string& path, uint32_t poly)
+{
+ send("/ingen/new_patch", "si", path.c_str(), poly, LO_ARGS_END);
+}
+
+
+/** \page client_osc_namespace
+ * <p> \b /ingen/object_renamed - Notification of an object's renaming
+ * \arg \b old-path (string) - Old path of object
+ * \arg \b new-path (string) - New path of object </p> \n \n
+ */
+void
+OSCClientSender::object_renamed(const std::string& old_path, const std::string& new_path)
+{
+ send("/ingen/object_renamed", "ss", old_path.c_str(), new_path.c_str(), LO_ARGS_END);
+}
+
+
+/** Sends information about a program associated with a node.
+ */
+void
+OSCClientSender::program_add(const std::string& node_path, uint32_t bank, uint32_t program, const std::string& name)
+{
+ send("/ingen/program_add", "siis",
+ node_path.c_str(), bank, program, name.c_str(), LO_ARGS_END);
+}
+
+
+void
+OSCClientSender::program_remove(const std::string& node_path, uint32_t bank, uint32_t program)
+{
+ send("/ingen/program_remove", "sii",
+ node_path.c_str(), bank, program, LO_ARGS_END);
+}
+
+
+} // namespace Ingen
diff --git a/src/engine/OSCClientSender.hpp b/src/engine/OSCClientSender.hpp
new file mode 100644
index 00000000..c8c8418f
--- /dev/null
+++ b/src/engine/OSCClientSender.hpp
@@ -0,0 +1,136 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 OSCCLIENTSENDER_H
+#define OSCCLIENTSENDER_H
+
+#include <cassert>
+#include <string>
+#include <iostream>
+#include <lo/lo.h>
+#include <pthread.h>
+#include "types.hpp"
+#include "interface/ClientInterface.hpp"
+#include "shared/OSCSender.hpp"
+
+namespace Ingen {
+
+namespace Shared { class EngineInterface; }
+
+
+/** Implements ClientInterface for OSC clients (sends OSC messages).
+ *
+ * \ingroup engine
+ */
+class OSCClientSender : public Shared::ClientInterface, public Shared::OSCSender
+{
+public:
+ OSCClientSender(const std::string& url)
+ : _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(); }
+ void transfer_begin() { OSCSender::transfer_begin(); }
+ void transfer_end() { OSCSender::transfer_end(); }
+
+ std::string uri() const { return lo_address_get_url(_address); }
+
+ void subscribe(Shared::EngineInterface* engine) { }
+
+ /* *** ClientInterface Implementation Below *** */
+
+ //void client_registration(const std::string& url, int client_id);
+
+ void response_ok(int32_t id);
+ void response_error(int32_t id, const std::string& msg);
+
+ void error(const std::string& msg);
+
+ virtual void new_plugin(const std::string& uri,
+ const std::string& type_uri,
+ const std::string& symbol,
+ const std::string& name);
+
+ virtual void new_patch(const std::string& path, uint32_t poly);
+
+ virtual void new_node(const std::string& path,
+ const std::string& plugin_uri);
+
+ virtual void new_port(const std::string& path,
+ uint32_t index,
+ const std::string& data_type,
+ bool is_output);
+
+ virtual void patch_cleared(const std::string& path);
+
+ virtual void destroy(const std::string& path);
+
+ virtual void object_renamed(const std::string& old_path,
+ const std::string& new_path);
+
+ virtual void connect(const std::string& src_port_path,
+ const std::string& dst_port_path);
+
+ virtual void disconnect(const std::string& src_port_path,
+ const std::string& dst_port_path);
+
+ virtual void set_variable(const std::string& subject_path,
+ const std::string& predicate,
+ const Raul::Atom& value);
+
+ virtual void set_property(const std::string& subject_path,
+ const std::string& predicate,
+ const Raul::Atom& value);
+
+ virtual void set_port_value(const std::string& port_path,
+ const Raul::Atom& value);
+
+ virtual void set_voice_value(const std::string& port_path,
+ uint32_t voice,
+ const Raul::Atom& value);
+
+ virtual void port_activity(const std::string& port_path);
+
+ virtual void program_add(const std::string& node_path,
+ uint32_t bank,
+ uint32_t program,
+ const std::string& program_name);
+
+ virtual void program_remove(const std::string& node_path,
+ uint32_t bank,
+ uint32_t program);
+
+private:
+ std::string _url;
+};
+
+
+} // namespace Ingen
+
+#endif // OSCCLIENTSENDER_H
+
diff --git a/src/engine/OSCDriver.hpp b/src/engine/OSCDriver.hpp
new file mode 100644
index 00000000..06125217
--- /dev/null
+++ b/src/engine/OSCDriver.hpp
@@ -0,0 +1,82 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 OSCDRIVER_H
+#define OSCDRIVER_H
+
+#include "types.hpp"
+#include "Driver.hpp"
+#include <iostream>
+
+namespace Ingen {
+
+
+/** OSC driver abstract base class.
+ *
+ * \ingroup engine
+ */
+class OSCDriver : public Driver
+{
+public:
+ OSCDriver() : Driver(DataType::EVENT) {}
+
+ /** Prepare events (however neccessary) for the specified block (realtime safe) */
+ virtual void prepare_block(const SampleCount block_start, const SampleCount block_end) = 0;
+};
+
+
+
+/** Dummy OSCDriver.
+ *
+ * Not abstract, all functions are dummies. One of these will be allocated and
+ * "used" if no working OSC driver is loaded. (Doing it this way as opposed to
+ * just making OSCDriver have dummy functions makes sure any existing OSCDriver
+ * derived class actually implements the required functions).
+ *
+ * \ingroup engine
+ */
+class DummyOSCDriver : public OSCDriver
+{
+public:
+ DummyOSCDriver() {
+ std::cout << "[DummyOSCDriver] Started Dummy OSC driver." << std::endl;
+ }
+
+ ~DummyOSCDriver() {}
+
+ void activate() {}
+ void deactivate() {}
+
+ bool is_activated() const { return false; }
+ bool is_enabled() const { return false; }
+
+ void enable() {}
+ void disable() {}
+
+ DriverPort* create_port(DuplexPort* patch_port) { return NULL; }
+
+ void add_port(DriverPort* port) {}
+ DriverPort* remove_port(const Raul::Path& path) { return NULL; }
+
+ void prepare_block(const SampleCount block_start, const SampleCount block_end) {}
+};
+
+
+
+} // namespace Ingen
+
+#endif // OSCDRIVER_H
diff --git a/src/engine/OSCEngineReceiver.cpp b/src/engine/OSCEngineReceiver.cpp
new file mode 100644
index 00000000..e4e2fec1
--- /dev/null
+++ b/src/engine/OSCEngineReceiver.cpp
@@ -0,0 +1,884 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 <iostream>
+#include <cstdlib>
+#include <string>
+//#define ENABLE_AVAHI 1
+#include <lo/lo.h>
+#include "types.hpp"
+#include <raul/SharedPtr.hpp>
+#include <raul/AtomLiblo.hpp>
+#include "interface/ClientInterface.hpp"
+#include "engine/ThreadManager.hpp"
+#include "OSCEngineReceiver.hpp"
+#include "QueuedEventSource.hpp"
+#include "OSCClientSender.hpp"
+#include "ClientBroadcaster.hpp"
+
+using namespace std;
+
+namespace Ingen {
+
+
+/*! \page engine_osc_namespace Engine 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>\n\n
+ */
+
+
+OSCEngineReceiver::OSCEngineReceiver(Engine& engine, size_t queue_size, uint16_t port)
+ : QueuedEngineInterface(engine, queue_size, queue_size) // FIXME
+ , _server(NULL)
+{
+ _receive_thread = new ReceiveThread(*this);
+
+ char port_str[6];
+ snprintf(port_str, 6, "%u", port);
+
+ _server = lo_server_new(port_str, error_cb);
+#ifdef ENABLE_AVAHI
+ lo_server_avahi_init(_server, "ingen");
+#endif
+
+ if (_server == NULL) {
+ cerr << "[OSC] Could not start OSC server. Aborting." << endl;
+ exit(EXIT_FAILURE);
+ } else {
+ char* lo_url = lo_server_get_url(_server);
+ cout << "[OSC] Started OSC server at " << lo_url << endl;
+ free(lo_url);
+ }
+
+ // For debugging, print all incoming OSC messages
+ //lo_server_add_method(_server, NULL, NULL, generic_cb, NULL);
+
+ // 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);
+
+ // Commands
+ lo_server_add_method(_server, "/ingen/ping", "i", ping_cb, this);
+ lo_server_add_method(_server, "/ingen/ping_queued", "i", ping_slow_cb, this);
+ lo_server_add_method(_server, "/ingen/quit", "i", quit_cb, this);
+ //lo_server_add_method(_server, "/ingen/register_client", "is", register_client_cb, this);
+ lo_server_add_method(_server, "/ingen/register_client", "i", register_client_cb, this);
+ lo_server_add_method(_server, "/ingen/unregister_client", "i", unregister_client_cb, this);
+ lo_server_add_method(_server, "/ingen/load_plugins", "i", load_plugins_cb, this);
+ lo_server_add_method(_server, "/ingen/activate", "i", engine_activate_cb, this);
+ lo_server_add_method(_server, "/ingen/deactivate", "i", engine_deactivate_cb, this);
+ lo_server_add_method(_server, "/ingen/new_patch", "isi", new_patch_cb, this);
+ lo_server_add_method(_server, "/ingen/clear_patch", "is", clear_patch_cb, this);
+ lo_server_add_method(_server, "/ingen/set_polyphony", "isi", set_polyphony_cb, this);
+ lo_server_add_method(_server, "/ingen/set_polyphonic", "isT", set_polyphonic_cb, this);
+ lo_server_add_method(_server, "/ingen/set_polyphonic", "isF", set_polyphonic_cb, this);
+ lo_server_add_method(_server, "/ingen/new_port", "issi", new_port_cb, this);
+ lo_server_add_method(_server, "/ingen/new_node", "issss", new_node_cb, this);
+ lo_server_add_method(_server, "/ingen/new_node", "issss", new_node_cb, this);
+ lo_server_add_method(_server, "/ingen/new_node", "iss", new_node_by_uri_cb, this);
+ lo_server_add_method(_server, "/ingen/new_node", "iss", new_node_by_uri_cb, this);
+ lo_server_add_method(_server, "/ingen/destroy", "is", destroy_cb, this);
+ lo_server_add_method(_server, "/ingen/rename", "iss", rename_cb, this);
+ lo_server_add_method(_server, "/ingen/connect", "iss", connect_cb, this);
+ lo_server_add_method(_server, "/ingen/disconnect", "iss", disconnect_cb, this);
+ lo_server_add_method(_server, "/ingen/disconnect_all", "iss", disconnect_all_cb, this);
+ lo_server_add_method(_server, "/ingen/set_port_value", NULL, set_port_value_cb, this);
+ lo_server_add_method(_server, "/ingen/note_on", "isii", note_on_cb, this);
+ lo_server_add_method(_server, "/ingen/note_off", "isi", note_off_cb, this);
+ lo_server_add_method(_server, "/ingen/all_notes_off", "isi", all_notes_off_cb, this);
+ lo_server_add_method(_server, "/ingen/midi_learn", "is", midi_learn_cb, this);
+ lo_server_add_method(_server, "/ingen/set_variable", NULL, variable_set_cb, this);
+ lo_server_add_method(_server, "/ingen/set_property", NULL, property_set_cb, this);
+
+ // Queries
+ lo_server_add_method(_server, "/ingen/request_variable", "iss", variable_get_cb, this);
+ lo_server_add_method(_server, "/ingen/request_plugin", "is", request_plugin_cb, this);
+ lo_server_add_method(_server, "/ingen/request_object", "is", request_object_cb, this);
+ lo_server_add_method(_server, "/ingen/request_port_value", "is", request_port_value_cb, this);
+ lo_server_add_method(_server, "/ingen/request_plugins", "i", request_plugins_cb, this);
+ lo_server_add_method(_server, "/ingen/request_all_objects", "i", request_all_objects_cb, this);
+
+ lo_server_add_method(_server, NULL, NULL, unknown_cb, NULL);
+
+ Thread::set_name("OSC Pre-Processor");
+}
+
+
+OSCEngineReceiver::~OSCEngineReceiver()
+{
+ deactivate();
+ stop();
+ _receive_thread->stop();
+ delete _receive_thread;
+
+ if (_server != NULL) {
+#ifdef ENABLE_AVAHI
+ lo_server_avahi_free(_server);
+#endif
+ lo_server_free(_server);
+ _server = NULL;
+ }
+}
+
+
+void
+OSCEngineReceiver::activate()
+{
+ QueuedEventSource::activate();
+ _receive_thread->set_name("OSC Receiver");
+ _receive_thread->start();
+ _receive_thread->set_scheduling(SCHED_FIFO, 5); // Jack default appears to be 10
+}
+
+
+void
+OSCEngineReceiver::deactivate()
+{
+ cout << "[OSCEngineReceiver] Stopped OSC listening thread" << endl;
+ _receive_thread->stop();
+ QueuedEventSource::deactivate();
+}
+
+
+/** 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) {
+ assert(_receiver._server);
+ /*if ( ! _server) {
+ cout << "[OSCEngineReceiver] Server is NULL, exiting" << endl;
+ break;
+ }*/
+
+ // 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 responder for this message, if necessary.
+ *
+ * This is based on the fact that the current responder is stored in a ref
+ * counted pointer, and events just take a reference to that. Thus, events
+ * may delete their responder 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 responders 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<Responder> r = me->_responder;
+
+ /* 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->_responder = SharedPtr<Responder>(new Responder(client, id));
+ else
+ me->_responder = SharedPtr<Responder>(new Responder());
+ }
+
+ if (id != -1) {
+ me->set_next_response_id(id);
+ } else {
+ me->disable_responses();
+ }
+
+ // If this returns 0 no OSC commands will work
+ return 1;
+}
+
+
+void
+OSCEngineReceiver::error_cb(int num, const char* msg, const char* path)
+{
+ cerr << "liblo server error " << num << " in path \"" << "\" - " << msg << endl;
+}
+
+
+/** \page engine_osc_namespace
+ * <p> \b /ingen/ping - Immediately sends a successful response to the given response id.
+ * \arg \b response-id (integer) </p> \n \n
+ */
+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, "/ingen/ok", "i", argv[0]->i) < 0)
+ cerr << "WARNING: Unable to send response: " << lo_address_errstr(addr) << endl;
+ return 0;
+}
+
+
+/** \page engine_osc_namespace
+ * <p> \b /ingen/ping_queued - Sends response after going through the event queue.
+ * \arg \b response-id (integer)
+ *
+ * \li See the documentation for /ingen/set_port_value_queued for an explanation of how
+ * this differs from /ingen/ping. This is useful to send after sending a large cluster of
+ * events as a sentinel and wait on it's response, to know when the events are all
+ * finished processing.</p> \n \n
+ */
+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
+ * <p> \b /ingen/quit - Terminates the engine.
+ * \arg \b response-id (integer)
+ *
+ * \li Note that there is NO order guarantees with this command at all. You could
+ * send 10 messages then quit, and the quit reply could come immediately and the
+ * 10 messages would never get executed. </p> \n \n
+ */
+int
+OSCEngineReceiver::_quit_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ quit();
+ return 0;
+}
+
+/** \page engine_osc_namespace
+ * <p> \b /ingen/register_client - Registers a new client with the engine
+ * \arg \b response-id (integer) \n\n
+ * \li The incoming address will be used for the new registered client. If you
+ * want to register a different specific address, use the URL version. </p> \n \n
+ */
+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);
+ register_client(client);
+ free(url);
+
+ return 0;
+}
+
+
+/** \page engine_osc_namespace
+ * <p> \b /ingen/unregister_client - Unregisters a client
+ * \arg \b response-id (integer) </p> \n \n
+ */
+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
+ * <p> \b /ingen/load_plugins - Locates all available plugins, making them available for use.
+ * \arg \b response-id (integer) </p> \n \n
+ */
+int
+OSCEngineReceiver::_load_plugins_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ load_plugins();
+ return 0;
+}
+
+
+/** \page engine_osc_namespace
+ * <p> \b /ingen/activate - Activate the engine (MIDI, audio, everything)
+ * \arg \b response-id (integer) </p>
+ *
+ * \li Note that you <b>must</b> send this message first if you want the engine to do
+ * anything at all - <em>including respond to your messages!</em> \n \n
+ */
+int
+OSCEngineReceiver::_engine_activate_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ QueuedEngineInterface::activate();
+ return 0;
+}
+
+
+/** \page engine_osc_namespace
+ * <p> \b /ingen/deactivate - Deactivate the engine completely.
+ * \arg \b response-id (integer) </p> \n \n
+ */
+int
+OSCEngineReceiver::_engine_deactivate_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ QueuedEngineInterface::deactivate();
+ return 0;
+}
+
+
+/** \page engine_osc_namespace
+ * <p> \b /ingen/new_patch - Creates a new, empty, toplevel patch.
+ * \arg \b response-id (integer)
+ * \arg \b patch-path (string) - Patch path (complete, ie /master/parent/new_patch)
+ * \arg \b poly (integer) - Patch's (internal) polyphony </p> \n \n
+ */
+int
+OSCEngineReceiver::_new_patch_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* patch_path = &argv[1]->s;
+ const int32_t poly = argv[2]->i;
+
+ new_patch(patch_path, poly);
+ return 0;
+}
+
+
+/** \page engine_osc_namespace
+ * <p> \b /ingen/rename - Rename an Object (only Nodes, for now)
+ * \arg \b response-id (integer)
+ * \arg \b path - Object's path
+ * \arg \b name - New name for object </p> \n \n
+ */
+int
+OSCEngineReceiver::_rename_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* object_path = &argv[1]->s;
+ const char* name = &argv[2]->s;
+
+ rename(object_path, name);
+ return 0;
+}
+
+
+/** \page engine_osc_namespace
+ * <p> \b /ingen/clear_patch - Remove all nodes from a patch
+ * \arg \b response-id (integer)
+ * \arg \b patch-path - Patch's path </p> \n \n
+ */
+int
+OSCEngineReceiver::_clear_patch_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* patch_path = &argv[1]->s;
+
+ clear_patch(patch_path);
+ return 0;
+}
+
+
+/** \page engine_osc_namespace
+ * <p> \b /ingen/set_polyphony - Set the polyphony of a patch
+ * \arg \b response-id (integer)
+ * \arg \b patch-path - Patch's path
+ * \arg \b poly (integer) </p> \n \n
+ */
+int
+OSCEngineReceiver::_set_polyphony_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* patch_path = &argv[1]->s;
+ const uint32_t poly = argv[2]->i;
+
+ set_polyphony(patch_path, poly);
+ return 0;
+}
+
+
+/** \page engine_osc_namespace
+ * <p> \b /ingen/set_polyphonic - Toggle a node's or port's polyphonic mode
+ * \arg \b response-id (integer)
+ * \arg \b path - Object's path
+ * \arg \b polyphonic (bool) </p> \n \n
+ */
+int
+OSCEngineReceiver::_set_polyphonic_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* object_path = &argv[1]->s;
+ bool polyphonic = (types[2] == 'T');
+
+ set_polyphonic(object_path, polyphonic);
+ return 0;
+}
+
+
+// FIXME: add index
+/** \page engine_osc_namespace
+ * <p> \b /ingen/new_port - Add a port into a given patch (load a plugin by URI)
+ * \arg \b response-id (integer)
+ * \arg \b path (string) - Full path of the new port (ie. /patch2/subpatch/newport)
+ * \arg \b data-type (string) - Type of port (ingen:AudioPort, ingen:ControlPort, ingen:MIDIPort, or ingen:OSCPort)
+ * \arg \b direction ("is-output") (integer) - Direction of data flow (Input = 0, Output = 1) </p> \n \n
+ */
+int
+OSCEngineReceiver::_new_port_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* port_path = &argv[1]->s;
+ const char* data_type = &argv[2]->s;
+ const int32_t direction = argv[3]->i;
+
+ new_port(port_path, 0, data_type, (direction == 1));
+ return 0;
+}
+
+/** \page engine_osc_namespace
+ * <p> \b /ingen/new_node - Add a node into a given patch (load a plugin by URI)
+ * \arg \b response-id (integer)
+ * \arg \b node-path (string) - Full path of the new node (ie. /patch2/subpatch/newnode)
+ * \arg \b plug-uri (string) - URI of the plugin to load \n \n
+ */
+int
+OSCEngineReceiver::_new_node_by_uri_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* node_path = &argv[1]->s;
+ const char* plug_uri = &argv[2]->s;
+
+ new_node(node_path, plug_uri);
+ return 0;
+}
+
+
+/** \page engine_osc_namespace
+ * <p> \b /ingen/new_node - Add a node into a given patch (load a plugin by libname, label) \b DEPRECATED
+ * \arg \b response-id (integer)
+ * \arg \b node-path (string) - Full path of the new node (ie. /patch2/subpatch/newnode)
+ * \arg \b type (string) - Plugin type ("LADSPA" or "Internal")
+ * \arg \b lib-name (string) - Name of library where plugin resides (eg "cmt.so")
+ * \arg \b plug-label (string) - Label (ID) of plugin (eg "sine_fcaa") \n \n
+ *
+ * \li This is only here to provide backwards compatibility for old patches that store LADSPA plugin
+ * references as libname, label. It is to be removed ASAP, don't use it.
+ * </p> \n \n
+ */
+int
+OSCEngineReceiver::_new_node_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* node_path = &argv[1]->s;
+ const char* type = &argv[2]->s;
+ const char* lib_name = &argv[3]->s;
+ const char* plug_label = &argv[4]->s;
+
+ new_node_deprecated(node_path, type, lib_name, plug_label);
+ return 0;
+}
+
+
+/** \page engine_osc_namespace
+ * <p> \b /ingen/destroy - Removes (destroys) a Patch or a Node
+ * \arg \b response-id (integer)
+ * \arg \b node-path (string) - Full path of the object </p> \n \n
+ */
+int
+OSCEngineReceiver::_destroy_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* node_path = &argv[1]->s;
+
+ destroy(node_path);
+ return 0;
+}
+
+
+/** \page engine_osc_namespace
+ * <p> \b /ingen/connect - Connects two ports (must be in the same patch)
+ * \arg \b response-id (integer)
+ * \arg \b src-port-path (string) - Full path of source port
+ * \arg \b dst-port-path (string) - Full path of destination port </p> \n \n
+ */
+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
+ * <p> \b /ingen/disconnect - Disconnects two ports.
+ * \arg \b response-id (integer)
+ * \arg \b src-port-path (string) - Full path of source port
+ * \arg \b dst-port-path (string) - Full path of destination port </p> \n \n
+ */
+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
+ * <p> \b /ingen/disconnect_all - Disconnect all connections to/from a node/port.
+ * \arg \b response-id (integer)
+ * \arg \b patch-path (string) - The (parent) patch in which to disconnect object. </p> \n \n
+ * \arg \b node-path (string) - Full path of object. </p> \n \n
+ */
+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
+ * <p> \b /ingen/set_port_value - Sets the value of a port for all voices (as a QueuedEvent)
+ * \arg \b response-id (integer)
+ * \arg \b port-path (string) - Name of port
+ * \arg \b value (float) - Value to set port to.</p> \n \n
+ */
+int
+OSCEngineReceiver::_set_port_value_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ if (argc < 3 || argc > 5 || strncmp(types, "is", 2))
+ return 1;
+
+ const char* port_path = &argv[1]->s;
+
+ using Raul::Atom;
+
+ if (!strcmp(types, "isf")) { // float, all voices
+ const float value = argv[2]->f;
+ set_port_value(port_path, Atom(value));
+ } else if (!strcmp(types, "isif")) { // float, specific voice
+ const float value = argv[3]->f;
+ set_voice_value(port_path, argv[2]->i, Atom(value));
+ } else if (!strcmp(types, "issb")) { // blob (event), all voices
+ const char* type = &argv[2]->s;
+ lo_blob b = argv[3];
+ size_t data_size = lo_blob_datasize(b);
+ void* data = lo_blob_dataptr(b);
+ set_port_value(port_path, Atom(type, data_size, data));
+ } else if (!strcmp(types, "isisb")) { // blob (event), specific voice
+ const char* type = &argv[3]->s;
+ lo_blob b = argv[4];
+ size_t data_size = lo_blob_datasize(b);
+ void* data = lo_blob_dataptr(b);
+ set_voice_value(port_path, argv[2]->i, Atom(type, data_size, data));
+ } else if (!strcmp(types, "issN")) { // empty event (type only), all voices
+ const char* type = &argv[2]->s;
+ set_port_value(port_path, Atom(type, 0, NULL));
+ } else if (!strcmp(types, "isisN")) { // empty event (type only), specific voice
+ const char* type = &argv[3]->s;
+ set_voice_value(port_path, argv[2]->i, Atom(type, 0, NULL));
+ } else {
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/** \page engine_osc_namespace
+ * <p> \b /ingen/note_on - Triggers a note-on, just as if it came from MIDI
+ * \arg \b response-id (integer)
+ * \arg \b node-path (string) - Patch of Node to trigger (must be a trigger or note node)
+ * \arg \b note-num (int) - MIDI style note number (0-127)
+ * \arg \b velocity (int) - MIDI style velocity (0-127)</p> \n \n
+ */
+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 uchar note_num = argv[2]->i;
+ const uchar velocity = argv[3]->i;
+ */
+ cerr << "FIXME: OSC note on\n";
+ //note_on(node_path, note_num, velocity);
+ return 0;
+}
+
+
+/** \page engine_osc_namespace
+ * <p> \b /ingen/note_off - Triggers a note-off, just as if it came from MIDI
+ * \arg \b response-id (integer)
+ * \arg \b node-path (string) - Patch of Node to trigger (must be a trigger or note node)
+ * \arg \b note-num (int) - MIDI style note number (0-127)</p> \n \n
+ */
+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 uchar note_num = argv[2]->i;
+ */
+ cerr << "FIXME: OSC note off\n";
+ //note_off(patch_path, note_num);
+ return 0;
+}
+
+
+/** \page engine_osc_namespace
+ * <p> \b /ingen/all_notes_off - Triggers a note-off for all voices, just as if it came from MIDI
+ * \arg \b response-id (integer)
+ * \arg \b patch-path (string) - Patch of patch to send event to </p> \n \n
+ */
+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;
+ */
+ cerr << "FIXME: OSC all notes off\n";
+ //all_notes_off(patch_path);
+ return 0;
+}
+
+
+/** \page engine_osc_namespace
+ * <p> \b /ingen/midi_learn - Initiate MIDI learn for a given (MIDI Control) Node
+ * \arg \b response-id (integer)
+ * \arg \b node-path (string) - Patch of the Node that should learn the next MIDI event.
+ *
+ * \li This of course will only do anything for MIDI control nodes. The node will learn the next MIDI
+ * event that arrives at it's MIDI input port - no behind the scenes voodoo happens here. It is planned
+ * that a plugin specification supporting arbitrary OSC commands for plugins will exist one day, and this
+ * method will go away completely. </p> \n \n
+ */
+int
+OSCEngineReceiver::_midi_learn_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* patch_path = &argv[1]->s;
+
+ midi_learn(patch_path);
+ return 0;
+}
+
+
+/** \page engine_osc_namespace
+ * <p> \b /ingen/set_variable - Set a variable, associated with a synth-space object (node, etc)
+ * \arg \b response-id (integer)
+ * \arg \b object-path (string) - Full path of object to associate variable with
+ * \arg \b key (string) - Key (index/predicate/ID) for new variable
+ * \arg \b value (string) - Value of new variable </p> \n \n
+ */
+int
+OSCEngineReceiver::_variable_set_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_variable(object_path, key, value);
+ return 0;
+}
+
+
+/** \page engine_osc_namespace
+ * <p> \b /ingen/set_property - Set an (RDF) property, associated with a synth-space object (node, etc)
+ * \arg \b response-id (integer)
+ * \arg \b object-path (string) - Full path of object to associate variable with
+ * \arg \b key (string) - URI/QName for predicate of this property (e.g. "ingen:enabled")
+ * \arg \b value (string) - Value of property </p> \n \n
+ */
+int
+OSCEngineReceiver::_property_set_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
+ * <p> \b /ingen/request_variable - Requests the engine send a piece of variable, associated with a synth-space object (node, etc)
+ * \arg \b response-id (integer)
+ * \arg \b object-path (string) - Full path of object variable is associated with
+ * \arg \b key (string) - Key (index) for piece of variable
+ *
+ * \li Reply will be sent to client registered with the source address of this message.</p> \n \n
+ */
+int
+OSCEngineReceiver::_variable_get_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_variable(object_path, key);
+ return 0;
+}
+
+
+/** \page engine_osc_namespace
+ * <p> \b /ingen/request_plugin - Requests the engine send the value of a port.
+ * \arg \b response-id (integer)
+ * \arg \b port-path (string) - Full path of port to send the value of \n\n
+ * \li Reply will be sent to client registered with the source address of this message.</p>\n\n
+ */
+int
+OSCEngineReceiver::_request_plugin_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* uri = &argv[1]->s;
+
+ request_plugin(uri);
+ return 0;
+}
+
+
+/** \page engine_osc_namespace
+ * <p> \b /ingen/request_object - Requests the engine send the value of a port.
+ * \arg \b response-id (integer)
+ * \arg \b port-path (string) - Full path of port to send the value of \n\n
+ * \li Reply will be sent to client registered with the source address of this message.</p>\n\n
+ */
+int
+OSCEngineReceiver::_request_object_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* object_path = &argv[1]->s;
+
+ request_object(object_path);
+ return 0;
+}
+
+
+/** \page engine_osc_namespace
+ * <p> \b /ingen/request_port_value - Requests the engine send the value of a port.
+ * \arg \b response-id (integer)
+ * \arg \b port-path (string) - Full path of port to send the value of \n\n
+ * \li Reply will be sent to client registered with the source address of this message.</p>\n\n
+ */
+int
+OSCEngineReceiver::_request_port_value_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* port_path = &argv[1]->s;
+
+ request_port_value(port_path);
+ return 0;
+}
+
+
+/** \page engine_osc_namespace
+ * <p> \b /ingen/request_plugins - Requests the engine send a list of all known plugins.
+ * \arg \b response-id (integer) \n\n
+ * \li Reply will be sent to client registered with the source address of this message.</p>\n\n
+ */
+int
+OSCEngineReceiver::_request_plugins_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ request_plugins();
+ return 0;
+}
+
+
+/** \page engine_osc_namespace
+ * <p> \b /ingen/request_all_objects - Requests the engine send information about \em all objects (patches, nodes, etc)
+ * \arg \b response-id (integer)\n\n
+ * \li Reply will be sent to client registered with the source address of this message.</p> \n \n
+ */
+int
+OSCEngineReceiver::_request_all_objects_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ request_all_objects();
+ 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("[OSCMsg] %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);
+
+ cerr << "Unknown command " << path << " (" << types << "), sending error.\n";
+
+ string error_msg = "Unknown command: ";
+ error_msg.append(path).append(" ").append(types);
+
+ OSCClientSender(url).error(error_msg);
+
+ return 0;
+}
+
+
+} // namespace Ingen
diff --git a/src/engine/OSCEngineReceiver.hpp b/src/engine/OSCEngineReceiver.hpp
new file mode 100644
index 00000000..c6e0bf59
--- /dev/null
+++ b/src/engine/OSCEngineReceiver.hpp
@@ -0,0 +1,129 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 OSCENGINERECEIVER_H
+#define OSCENGINERECEIVER_H
+
+#include CONFIG_H_PATH
+#include <string>
+#include <stdint.h>
+#include <lo/lo.h>
+#include <raul/SharedPtr.hpp>
+#include "QueuedEngineInterface.hpp"
+#include "Responder.hpp"
+using std::string;
+
+namespace Ingen {
+
+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 EngineInterface (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();
+
+ void activate();
+ void deactivate();
+
+private:
+ struct ReceiveThread : public Raul::Thread {
+ ReceiveThread(OSCEngineReceiver& receiver) : _receiver(receiver) {}
+ virtual void _run();
+ private:
+ OSCEngineReceiver& _receiver;
+ };
+
+ friend class ReceiveThread;
+
+ ReceiveThread* _receive_thread;
+
+ 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(load_plugins);
+ LO_HANDLER(engine_activate);
+ LO_HANDLER(engine_deactivate);
+ LO_HANDLER(new_patch);
+ LO_HANDLER(rename);
+ LO_HANDLER(new_port);
+ LO_HANDLER(new_node);
+ LO_HANDLER(new_node_by_uri);
+ LO_HANDLER(clear_patch);
+ LO_HANDLER(set_polyphony);
+ LO_HANDLER(set_polyphonic);
+ LO_HANDLER(destroy);
+ LO_HANDLER(connect);
+ LO_HANDLER(disconnect);
+ LO_HANDLER(disconnect_all);
+ LO_HANDLER(set_port_value);
+ LO_HANDLER(note_on);
+ LO_HANDLER(note_off);
+ LO_HANDLER(all_notes_off);
+ LO_HANDLER(midi_learn);
+ LO_HANDLER(variable_get);
+ LO_HANDLER(variable_set);
+ LO_HANDLER(property_set);
+ LO_HANDLER(request_plugin);
+ LO_HANDLER(request_object);
+ LO_HANDLER(request_port_value);
+ LO_HANDLER(request_variable);
+ LO_HANDLER(request_plugins);
+ LO_HANDLER(request_all_objects);
+
+ lo_server _server;
+};
+
+
+} // namespace Ingen
+
+#endif // OSCENGINERECEIVER_H
diff --git a/src/engine/ObjectSender.cpp b/src/engine/ObjectSender.cpp
new file mode 100644
index 00000000..688cea8e
--- /dev/null
+++ b/src/engine/ObjectSender.cpp
@@ -0,0 +1,150 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "interface/ClientInterface.hpp"
+#include "EngineStore.hpp"
+#include "PatchImpl.hpp"
+#include "NodeImpl.hpp"
+#include "PortImpl.hpp"
+#include "PortImpl.hpp"
+#include "ConnectionImpl.hpp"
+#include "NodeFactory.hpp"
+#include "interface/DataType.hpp"
+#include "AudioBuffer.hpp"
+
+namespace Ingen {
+
+
+void
+ObjectSender::send_patch(ClientInterface* client, const PatchImpl* patch, bool recursive)
+{
+ client->bundle_begin();
+
+ client->new_patch(patch->path(), patch->internal_polyphony());
+ client->set_property(patch->path(), "ingen:polyphonic", patch->polyphonic());
+
+ // Send variable
+ const GraphObjectImpl::Variables& data = patch->variables();
+ for (GraphObjectImpl::Variables::const_iterator j = data.begin(); j != data.end(); ++j)
+ client->set_variable(patch->path(), (*j).first, (*j).second);
+
+ client->set_property(patch->path(), "ingen:enabled", (bool)patch->enabled());
+
+ client->bundle_end();
+
+ 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);
+ }
+
+ // Send ports
+ for (uint32_t i=0; i < patch->num_ports(); ++i) {
+ PortImpl* const port = patch->port_impl(i);
+ send_port(client, port);
+ }
+
+ // Send connections
+ client->transfer_begin();
+ for (PatchImpl::Connections::const_iterator j = patch->connections().begin();
+ j != patch->connections().end(); ++j)
+ client->connect((*j)->src_port_path(), (*j)->dst_port_path());
+ client->transfer_end();
+ }
+}
+
+
+/** Sends a node or a patch */
+void
+ObjectSender::send_node(ClientInterface* client, const NodeImpl* node, bool recursive)
+{
+ PluginImpl* const plugin = node->plugin_impl();
+
+ assert(node->path().length() > 0);
+
+ if (plugin->type() == Plugin::Patch) {
+ send_patch(client, (PatchImpl*)node, recursive);
+ return;
+ }
+
+ if (plugin->uri().length() == 0) {
+ cerr << "Node " << node->path() << " plugin has no URI! Not sending." << endl;
+ return;
+ }
+
+ client->bundle_begin();
+
+ client->new_node(node->path(), node->plugin()->uri());
+ client->set_property(node->path(), "ingen:polyphonic", node->polyphonic());
+
+ // Send variables
+ const GraphObjectImpl::Variables& data = node->variables();
+ for (GraphObjectImpl::Variables::const_iterator j = data.begin(); j != data.end(); ++j)
+ client->set_variable(node->path(), (*j).first, (*j).second);
+
+ // Send properties
+ const GraphObjectImpl::Properties& prop = node->properties();
+ for (GraphObjectImpl::Properties::const_iterator j = prop.begin(); j != prop.end(); ++j)
+ client->set_property(node->path(), (*j).first, (*j).second);
+
+ client->bundle_end();
+
+ if (recursive) {
+ // Send ports
+ for (size_t j=0; j < node->num_ports(); ++j)
+ send_port(client, node->port_impl(j));
+ }
+}
+
+
+void
+ObjectSender::send_port(ClientInterface* client, const PortImpl* port)
+{
+ assert(port);
+
+ client->bundle_begin();
+
+ client->new_port(port->path(), port->index(), port->type().uri(), port->is_output());
+ client->set_property(port->path(), "ingen:polyphonic", port->polyphonic());
+
+ // Send variable
+ const GraphObjectImpl::Variables& data = port->variables();
+ for (GraphObjectImpl::Variables::const_iterator j = data.begin(); j != data.end(); ++j)
+ client->set_variable(port->path(), (*j).first, (*j).second);
+
+ // Send properties
+ const GraphObjectImpl::Properties& prop = port->properties();
+ for (GraphObjectImpl::Properties::const_iterator j = prop.begin(); j != prop.end(); ++j)
+ client->set_property(port->path(), (*j).first, (*j).second);
+
+ // Send control value
+ if (port->type() == DataType::CONTROL) {
+ const Sample value = dynamic_cast<const AudioBuffer*>(port->buffer(0))->value_at(0);
+ //cerr << port->path() << " sending default value " << default_value << endl;
+ client->set_port_value(port->path(), value);
+ }
+
+ client->bundle_end();
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/ObjectSender.hpp b/src/engine/ObjectSender.hpp
new file mode 100644
index 00000000..9b6eb000
--- /dev/null
+++ b/src/engine/ObjectSender.hpp
@@ -0,0 +1,57 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 OBJECTSENDER_H
+#define OBJECTSENDER_H
+
+#include <list>
+
+namespace Ingen {
+
+namespace Shared {
+ class ClientInterface;
+} using Shared::ClientInterface;
+
+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:
+
+ // FIXME: Make all object parameters const
+
+ static void send_patch(ClientInterface* client, const PatchImpl* patch, bool recursive);
+ static void send_node(ClientInterface* client, const NodeImpl* node, bool recursive);
+ static void send_port(ClientInterface* client, const PortImpl* port);
+};
+
+} // namespace Ingen
+
+#endif // OBJECTSENDER_H
+
diff --git a/src/engine/OmInProcess.cpp b/src/engine/OmInProcess.cpp
new file mode 100644
index 00000000..6a65cf38
--- /dev/null
+++ b/src/engine/OmInProcess.cpp
@@ -0,0 +1,66 @@
+/* This file is part of Ingen. Copyright (C) 2006 Mario Lang.
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 <pthread.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <memory.h>
+#include <jack/jack.h>
+#include "Engine.hpp"
+#include "OSCReceiver.hpp"
+#include "JackAudioDriver.hpp"
+
+extern "C"
+{
+ int jack_initialize(jack_client_t* client, const char* load_init);
+ void jack_finish(void* arg);
+}
+
+
+void*
+run_main(void* arg)
+{
+ Engine::instance().main();
+
+ // FIXME: cleanup
+
+ return 0;
+}
+
+
+pthread_t main_thread;
+
+
+int
+jack_initialize(jack_client_t* client, const char* load_init)
+{
+ if ((Ingen::om = new Engine(load_init, new Ingen::JackAudioDriver(client))) != NULL) {
+ pthread_create(&main_thread, NULL, run_main, NULL);
+ return 0; // Success
+ } else {
+ return 1;
+ }
+}
+
+
+void
+jack_finish(void* arg)
+{
+ void* ret;
+ Engine::instance().quit();
+ pthread_join(main_thread, &ret);
+}
+
diff --git a/src/engine/OutputPort.cpp b/src/engine/OutputPort.cpp
new file mode 100644
index 00000000..116d9a3e
--- /dev/null
+++ b/src/engine/OutputPort.cpp
@@ -0,0 +1,62 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 <iostream>
+#include "OutputPort.hpp"
+#include "Buffer.hpp"
+#include "ProcessContext.hpp"
+
+using namespace std;
+
+namespace Ingen {
+
+
+OutputPort::OutputPort(NodeImpl* parent,
+ const string& name,
+ uint32_t index,
+ uint32_t poly,
+ DataType type,
+ const Atom& value,
+ size_t buffer_size)
+ : PortImpl(parent, name, index, poly, type, value, buffer_size)
+{
+ if (type == DataType::CONTROL)
+ _broadcast = true;
+}
+
+
+void
+OutputPort::pre_process(ProcessContext& context)
+{
+ for (uint32_t i=0; i < _poly; ++i)
+ buffer(i)->prepare_write(context.start(), context.nframes());
+}
+
+
+void
+OutputPort::post_process(ProcessContext& context)
+{
+ for (uint32_t i=0; i < _poly; ++i)
+ buffer(i)->prepare_read(context.start(), context.nframes());
+
+ //cerr << path() << " output post: buffer: " << buffer(0) << endl;
+
+ broadcast(context);
+}
+
+
+} // namespace Ingen
diff --git a/src/engine/OutputPort.hpp b/src/engine/OutputPort.hpp
new file mode 100644
index 00000000..8d441b5c
--- /dev/null
+++ b/src/engine/OutputPort.hpp
@@ -0,0 +1,63 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 OUTPUTPORT_H
+#define OUTPUTPORT_H
+
+#include <string>
+#include <cstdlib>
+#include "PortImpl.hpp"
+#include "types.hpp"
+
+namespace Ingen {
+
+
+/** 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(NodeImpl* parent,
+ const std::string& name,
+ uint32_t index,
+ uint32_t poly,
+ DataType type,
+ const Atom& value,
+ size_t buffer_size);
+
+ void pre_process(ProcessContext& context);
+ void post_process(ProcessContext& context);
+
+ virtual ~OutputPort() {}
+
+ bool is_input() const { return false; }
+ bool is_output() const { return true; }
+};
+
+
+} // namespace Ingen
+
+#endif // OUTPUTPORT_H
diff --git a/src/engine/PatchImpl.cpp b/src/engine/PatchImpl.cpp
new file mode 100644
index 00000000..9f0ae701
--- /dev/null
+++ b/src/engine/PatchImpl.cpp
@@ -0,0 +1,481 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 <iostream>
+#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"
+
+using namespace std;
+
+namespace Ingen {
+
+
+PatchImpl::PatchImpl(Engine& engine, const string& path, uint32_t poly, PatchImpl* parent, SampleRate srate, size_t buffer_size, uint32_t internal_poly)
+ : NodeBase(new PatchPlugin("http://example.org/FIXME", "patch", "Ingen Patch"),
+ path, poly, parent, srate, buffer_size)
+ , _engine(engine)
+ , _internal_poly(internal_poly)
+ , _compiled_patch(NULL)
+ , _process(false)
+{
+ assert(internal_poly >= 1);
+}
+
+
+PatchImpl::~PatchImpl()
+{
+ assert(!_activated);
+
+ delete _compiled_patch;
+}
+
+
+void
+PatchImpl::activate()
+{
+ NodeBase::activate();
+
+ for (List<NodeImpl*>::iterator i = _nodes.begin(); i != _nodes.end(); ++i)
+ (*i)->activate();
+
+ assert(_activated);
+}
+
+
+void
+PatchImpl::deactivate()
+{
+ if (_activated) {
+
+ NodeBase::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()
+{
+ assert(ThreadManager::current_thread_id() == THREAD_PROCESS);
+
+ _process = false;
+
+ for (List<PortImpl*>::iterator i = _output_ports.begin(); i != _output_ports.end(); ++i)
+ (*i)->clear_buffers();
+}
+
+
+bool
+PatchImpl::prepare_internal_poly(uint32_t poly)
+{
+ assert(ThreadManager::current_thread_id() == THREAD_PRE_PROCESS);
+
+ /* TODO: ports? internal/external poly? */
+
+ for (List<NodeImpl*>::iterator i = _nodes.begin(); i != _nodes.end(); ++i)
+ (*i)->prepare_poly(poly);
+
+ for (Connections::iterator i = _connections.begin(); i != _connections.end(); ++i)
+ ((ConnectionImpl*)i->get())->prepare_poly(poly);
+
+ /* FIXME: Deal with failure */
+
+ return true;
+}
+
+
+bool
+PatchImpl::apply_internal_poly(Raul::Maid& maid, uint32_t poly)
+{
+ assert(ThreadManager::current_thread_id() == THREAD_PROCESS);
+
+ /* TODO: ports? internal/external poly? */
+
+ for (List<NodeImpl*>::iterator i = _nodes.begin(); i != _nodes.end(); ++i)
+ (*i)->apply_poly(maid, poly);
+
+ _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;
+
+ NodeBase::pre_process(context);
+
+ /*if (_ports)
+ for (size_t i=0; i < _ports->size(); ++i)
+ if (_ports->at(i)->is_input() && _ports->at(i)->type() == DataType::MIDI)
+ cerr << _ports->at(i)->path() << " "
+ << _ports->at(i)->buffer(0) << " # events: "
+ << ((MidiBuffer*)_ports->at(i)->buffer(0))->event_count() << endl;*/
+
+ /* Run */
+ if (_compiled_patch && _compiled_patch->size() > 0) {
+ if (_engine.process_slaves().size() > 0)
+ process_parallel(context);
+ else
+ process_single(context);
+ }
+
+ NodeBase::post_process(context);
+}
+
+
+void
+PatchImpl::process_parallel(ProcessContext& context)
+{
+ size_t n_slaves = _engine.process_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)
+ _engine.process_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 (size_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 (size_t i=0; i < n_slaves; ++i)
+ _engine.process_slaves()[i]->finish();
+}
+
+
+void
+PatchImpl::process_single(ProcessContext& context)
+{
+ CompiledPatch* const cp = _compiled_patch;
+
+ for (size_t i=0; i < cp->size(); ++i)
+ (*cp)[i].node()->process(context);
+}
+
+
+void
+PatchImpl::set_buffer_size(size_t size)
+{
+ NodeBase::set_buffer_size(size);
+ assert(_buffer_size == size);
+
+ CompiledPatch* const cp = _compiled_patch;
+
+ for (size_t i=0; i < cp->size(); ++i)
+ (*cp)[i].node()->set_buffer_size(size);
+}
+
+
+// Patch specific stuff
+
+
+/** Add a node.
+ * Preprocessing thread only.
+ */
+void
+PatchImpl::add_node(List<NodeImpl*>::Node* ln)
+{
+ assert(ThreadManager::current_thread_id() == 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 string& symbol)
+{
+ assert(ThreadManager::current_thread_id() == THREAD_PRE_PROCESS);
+ for (List<NodeImpl*>::iterator i = _nodes.begin(); i != _nodes.end(); ++i)
+ if ((*i)->symbol() == symbol)
+ return _nodes.erase(i);
+
+ return NULL;
+}
+
+
+/** Remove a connection.
+ * Preprocessing thread only.
+ */
+PatchImpl::Connections::Node*
+PatchImpl::remove_connection(const PortImpl* src_port, const PortImpl* dst_port)
+{
+ assert(ThreadManager::current_thread_id() == THREAD_PRE_PROCESS);
+ bool found = false;
+ Connections::Node* connection = NULL;
+ for (Connections::iterator i = _connections.begin(); i != _connections.end(); ++i) {
+ ConnectionImpl* const c = (ConnectionImpl*)i->get();
+ if (c->src_port() == src_port && c->dst_port() == dst_port) {
+ connection = _connections.erase(i);
+ found = true;
+ break;
+ }
+ }
+
+ if ( ! found)
+ cerr << "WARNING: [PatchImpl::remove_connection] Connection not found !" << endl;
+
+ return connection;
+}
+
+
+bool
+PatchImpl::has_connection(const PortImpl* src_port, const PortImpl* dst_port) const
+{
+ // FIXME: Doesn't scale
+ for (Connections::const_iterator i = _connections.begin(); i != _connections.end(); ++i) {
+ ConnectionImpl* const c = (ConnectionImpl*)i->get();
+ if (c->src_port() == src_port && c->dst_port() == dst_port)
+ return true;
+ }
+
+ return false;
+}
+
+
+uint32_t
+PatchImpl::num_ports() const
+{
+ ThreadID context = ThreadManager::current_thread_id();
+
+ if (context == THREAD_PROCESS)
+ return NodeBase::num_ports();
+ else
+ return _input_ports.size() + _output_ports.size();
+}
+
+
+/** Create a port. Not realtime safe.
+ */
+PortImpl*
+PatchImpl::create_port(const string& name, DataType type, size_t buffer_size, bool is_output)
+{
+ if (type == DataType::UNKNOWN) {
+ cerr << "[PatchImpl::create_port] Unknown port type " << type.uri() << endl;
+ return NULL;
+ }
+
+ assert( !(type == DataType::UNKNOWN) );
+
+ return new DuplexPort(this, name, num_ports(), _polyphony, type, 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)
+{
+ assert(ThreadManager::current_thread_id() == 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;
+ }
+ }
+
+ 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;
+ }
+ }
+
+ if ( ! found)
+ cerr << "WARNING: [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()
+{
+ assert(ThreadManager::current_thread_id() == THREAD_PROCESS);
+
+ _input_ports.clear();
+ _output_ports.clear();
+}
+
+
+Raul::Array<PortImpl*>*
+PatchImpl::build_ports_array() const
+{
+ assert(ThreadManager::current_thread_id() == 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
+{
+ assert(ThreadManager::current_thread_id() == THREAD_PRE_PROCESS);
+
+ //cerr << "*********** Building process order for " << path() << endl;
+
+ 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);
+ }
+
+ /*cerr << "----------------------------------------\n";
+ for (size_t i=0; i < process_order->size(); ++i) {
+ assert(process_order->at(i));
+ cerr << process_order->at(i)->path() << endl;
+ }
+ cerr << "----------------------------------------\n";*/
+
+ assert(compiled_patch->size() == _nodes.size());
+
+#ifndef NDEBUG
+ for (size_t i=0; i < compiled_patch->size(); ++i)
+ assert(compiled_patch->at(i).node());
+#endif
+
+ return compiled_patch;
+}
+
+
+} // namespace Ingen
diff --git a/src/engine/PatchImpl.hpp b/src/engine/PatchImpl.hpp
new file mode 100644
index 00000000..3629f6e5
--- /dev/null
+++ b/src/engine/PatchImpl.hpp
@@ -0,0 +1,168 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 PATCHIMPL_H
+#define PATCHIMPL_H
+
+#include <cstdlib>
+#include <string>
+#include <raul/List.hpp>
+#include <raul/SharedPtr.hpp>
+#include "interface/DataType.hpp"
+#include "interface/Patch.hpp"
+#include "NodeBase.hpp"
+#include "PluginImpl.hpp"
+#include "CompiledPatch.hpp"
+
+using std::string;
+
+template <typename T> class Array;
+using Raul::List;
+
+namespace Ingen {
+
+namespace Shared { class Connection; }
+
+class ConnectionImpl;
+class Engine;
+class CompiledPatch;
+
+
+/** 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 NodeBase, public Ingen::Shared::Patch
+{
+public:
+ PatchImpl(Engine& engine,
+ const string& name,
+ uint32_t poly,
+ PatchImpl* parent,
+ SampleRate srate,
+ size_t buffer_size,
+ uint32_t local_poly);
+
+ virtual ~PatchImpl();
+
+ void activate();
+ void deactivate();
+
+ void process(ProcessContext& context);
+
+ void set_buffer_size(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(uint32_t poly);
+
+ /** Apply a new (internal) polyphony value.
+ *
+ * Audio thread.
+ *
+ * \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(Raul::Maid& maid, uint32_t poly);
+
+ // Patch specific stuff not inherited from Node
+
+ typedef List<NodeImpl*> Nodes;
+
+ void add_node(Nodes::Node* tn);
+ Nodes::Node* remove_node(const string& name);
+
+ 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(const string& name, DataType type, size_t buffer_size, bool is_output);
+ void add_input(List<PortImpl*>::Node* port) { _input_ports.push_back(port); } ///< Preprocesser thread
+ void add_output(List<PortImpl*>::Node* port) { _output_ports.push_back(port); } ///< Preprocessor thread
+ List<PortImpl*>::Node* remove_port(const string& name);
+ void clear_ports();
+
+ void add_connection(Connections::Node* c) { _connections.push_back(c); }
+ Connections::Node* 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_polyphony() 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
+ List<PortImpl*> _input_ports; ///< Accessed in preprocessing thread only
+ 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 (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 Ingen
+
+#endif // PATCHIMPL_H
diff --git a/src/engine/PatchPlugin.hpp b/src/engine/PatchPlugin.hpp
new file mode 100644
index 00000000..a7334392
--- /dev/null
+++ b/src/engine/PatchPlugin.hpp
@@ -0,0 +1,64 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 PATCHPLUGIN_H
+#define PATCHPLUGIN_H
+
+#include CONFIG_H_PATH
+
+#include <string>
+#include "PluginImpl.hpp"
+
+namespace Ingen {
+
+class NodeImpl;
+
+
+/** Implementation of a Patch plugin.
+ *
+ * Patches don't actually work like this yet...
+ */
+class PatchPlugin : public PluginImpl
+{
+public:
+ PatchPlugin(const std::string& uri,
+ const std::string& symbol,
+ const std::string& name)
+ : PluginImpl(Plugin::Patch, uri)
+ {}
+
+ NodeImpl* instantiate(const std::string& name,
+ bool polyphonic,
+ Ingen::PatchImpl* parent,
+ Engine& engine)
+ {
+ return NULL;
+ }
+
+ const string symbol() const { return "patch"; }
+ const string name() const { return "Ingen Patch"; }
+
+private:
+ const string _symbol;
+ const string _name;
+};
+
+
+} // namespace Ingen
+
+#endif // PATCHPLUGIN_H
+
diff --git a/src/engine/PluginImpl.cpp b/src/engine/PluginImpl.cpp
new file mode 100644
index 00000000..215cf4ce
--- /dev/null
+++ b/src/engine/PluginImpl.cpp
@@ -0,0 +1,54 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 <iostream>
+#include "PluginImpl.hpp"
+#include "MidiNoteNode.hpp"
+#include "MidiTriggerNode.hpp"
+#include "MidiControlNode.hpp"
+#include "TransportNode.hpp"
+
+using namespace std;
+
+namespace Ingen {
+
+
+void
+PluginImpl::load()
+{
+ if (!_module) {
+ //cerr << "Loading " << _library_path << " library" << endl;
+ _module = new Glib::Module(_library_path, Glib::MODULE_BIND_LOCAL);
+ if (!(*_module))
+ delete _module;
+ }
+}
+
+
+void
+PluginImpl::unload()
+{
+ if (_module) {
+ //cerr << "Unloading " << _library_path << endl;
+ delete _module;
+ _module = NULL;
+ }
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/PluginImpl.hpp b/src/engine/PluginImpl.hpp
new file mode 100644
index 00000000..0301d942
--- /dev/null
+++ b/src/engine/PluginImpl.hpp
@@ -0,0 +1,106 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 PLUGINIMPL_H
+#define PLUGINIMPL_H
+
+#include CONFIG_H_PATH
+
+#include <cstdlib>
+#include <glibmm/module.h>
+#include <boost/utility.hpp>
+#include <dlfcn.h>
+#include <string>
+#include <iostream>
+#include "types.hpp"
+#include "interface/Plugin.hpp"
+
+using std::string;
+using Ingen::Shared::Plugin;
+
+namespace Ingen {
+
+class PatchImpl;
+class NodeImpl;
+class Engine;
+
+
+/** Implementation of a plugin (internal code, or a loaded shared library).
+ *
+ * Conceptually, a Node is an instance of this.
+ */
+class PluginImpl : public Ingen::Shared::Plugin, public boost::noncopyable
+{
+public:
+ PluginImpl(Type type, const string& uri, const string library_path="")
+ : _type(type)
+ , _uri(uri)
+ , _library_path(library_path)
+ , _module(NULL)
+ {}
+
+ virtual NodeImpl* instantiate(const std::string& name,
+ bool polyphonic,
+ Ingen::PatchImpl* parent,
+ Engine& engine) = 0;
+
+ virtual const string symbol() const = 0;
+ virtual const string name() const = 0;
+
+ const std::string& library_path() const { return _library_path; }
+ void library_path(const std::string& s) { _library_path = s;}
+
+ void load();
+ void unload();
+
+ const char* type_string() const {
+ if (_type == LADSPA) return "LADSPA";
+ else if (_type == LV2) return "LV2";
+ else if (_type == Internal) return "Internal";
+ else if (_type == Patch) return "Patch";
+ else return "";
+ }
+
+ const string type_uri() const {
+ return string("ingen:").append(type_string());
+ }
+
+ void set_type(const string& type_string) {
+ if (type_string == "LADSPA") _type = LADSPA;
+ else if (type_string == "LV2") _type = LV2;
+ else if (type_string == "Internal") _type = Internal;
+ else if (type_string == "Patch") _type = Patch;
+ }
+
+ Plugin::Type type() const { return _type; }
+ void type(Plugin::Type t) { _type = t; }
+ const string& uri() const { return _uri; }
+ Glib::Module* module() const { return _module; }
+ void module(Glib::Module* module) { _module = module; }
+
+protected:
+ Plugin::Type _type;
+ const string _uri;
+ string _library_path;
+ Glib::Module* _module;
+};
+
+
+} // namespace Ingen
+
+#endif // PLUGINIMPL_H
+
diff --git a/src/engine/PortImpl.cpp b/src/engine/PortImpl.cpp
new file mode 100644
index 00000000..3eac65ca
--- /dev/null
+++ b/src/engine/PortImpl.cpp
@@ -0,0 +1,190 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 <iostream>
+#include <raul/Array.hpp>
+#include <raul/Maid.hpp>
+#include "PortImpl.hpp"
+#include "NodeImpl.hpp"
+#include "interface/DataType.hpp"
+#include "AudioBuffer.hpp"
+#include "EventBuffer.hpp"
+#include "ProcessContext.hpp"
+#include "SendPortActivityEvent.hpp"
+
+using namespace std;
+
+namespace Ingen {
+
+
+PortImpl::PortImpl(NodeImpl* const node,
+ const string& name,
+ uint32_t index,
+ uint32_t poly,
+ DataType type,
+ const Atom& value,
+ size_t buffer_size)
+ : GraphObjectImpl(node, name, (type == DataType::AUDIO || type == DataType::CONTROL))
+ , _index(index)
+ , _poly(poly)
+ , _buffer_size(buffer_size)
+ , _type(type)
+ , _value(value)
+ , _fixed_buffers(false)
+ , _broadcast(false)
+ , _set_by_user(false)
+ , _last_broadcasted_value(_value.type() == Atom::FLOAT ? _value.get_float() : 0.0f) // default?
+ , _context(Context::AUDIO)
+ , _buffers(new Raul::Array<Buffer*>(poly))
+{
+ assert(node != NULL);
+ assert(_poly > 0);
+
+ allocate_buffers();
+ clear_buffers();
+
+ if (node->parent() == NULL)
+ _polyphonic = false;
+ else
+ _polyphonic = true;
+
+ if (type == DataType::EVENT)
+ _broadcast = true; // send activity blips
+
+ assert(_buffers->size() > 0);
+}
+
+
+PortImpl::~PortImpl()
+{
+ for (uint32_t i=0; i < _poly; ++i)
+ delete _buffers->at(i);
+
+ delete _buffers;
+}
+
+
+bool
+PortImpl::set_polyphonic(Raul::Maid& maid, bool p)
+{
+ if (_type == DataType::CONTROL || _type == DataType::AUDIO)
+ return GraphObjectImpl::set_polyphonic(maid, p);
+ else
+ return (!p);
+}
+
+
+bool
+PortImpl::prepare_poly(uint32_t poly)
+{
+ if (!_polyphonic || !_parent->polyphonic())
+ return true;
+
+ /* FIXME: poly never goes down, harsh on memory.. */
+ if (poly > _poly) {
+ _prepared_buffers = new Raul::Array<Buffer*>(poly, *_buffers);
+ for (uint32_t i = _poly; i < _prepared_buffers->size(); ++i)
+ _prepared_buffers->at(i) = Buffer::create(_type, _buffer_size);
+ }
+
+ return true;
+}
+
+
+bool
+PortImpl::apply_poly(Raul::Maid& maid, uint32_t poly)
+{
+ if (!_polyphonic || !_parent->polyphonic())
+ return true;
+
+ assert(poly <= _prepared_buffers->size());
+
+ // Apply a new set of buffers from a preceding call to prepare_poly
+ if (_prepared_buffers && _buffers != _prepared_buffers) {
+ maid.push(_buffers);
+ _buffers = _prepared_buffers;
+ }
+
+ _poly = poly;
+ assert(_buffers->size() >= poly);
+ assert(this->poly() == poly);
+
+ return true;
+}
+
+
+void
+PortImpl::allocate_buffers()
+{
+ _buffers->alloc(_poly);
+
+ for (uint32_t i=0; i < _poly; ++i)
+ _buffers->at(i) = Buffer::create(_type, _buffer_size);
+}
+
+
+void
+PortImpl::set_buffer_size(size_t size)
+{
+ _buffer_size = size;
+
+ for (uint32_t i=0; i < _poly; ++i)
+ _buffers->at(i)->resize(size);
+
+ connect_buffers();
+}
+
+
+void
+PortImpl::connect_buffers()
+{
+ for (uint32_t i=0; i < _poly; ++i)
+ PortImpl::parent_node()->set_port_buffer(i, _index, buffer(i));
+}
+
+
+void
+PortImpl::clear_buffers()
+{
+ for (uint32_t i=0; i < _poly; ++i)
+ buffer(i)->clear();
+}
+
+
+void
+PortImpl::broadcast(ProcessContext& context)
+{
+ if (_broadcast) {
+ if (_type == DataType::CONTROL || _type == DataType::AUDIO) {
+ const Sample value = ((AudioBuffer*)buffer(0))->value_at(0);
+ if (value != _last_broadcasted_value) {
+ const SendPortValueEvent ev(context.engine(), context.start(), this, false, 0, value);
+ context.event_sink().write(sizeof(ev), &ev);
+ _last_broadcasted_value = value;
+ }
+ } else if (_type == DataType::EVENT) {
+ if (((EventBuffer*)buffer(0))->event_count() > 0) {
+ const SendPortActivityEvent ev(context.engine(), context.start(), this);
+ context.event_sink().write(sizeof(ev), &ev);
+ }
+ }
+ }
+}
+
+
+
+} // namespace Ingen
diff --git a/src/engine/PortImpl.hpp b/src/engine/PortImpl.hpp
new file mode 100644
index 00000000..be96a910
--- /dev/null
+++ b/src/engine/PortImpl.hpp
@@ -0,0 +1,147 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 PORTIMPL_H
+#define PORTIMPL_H
+
+#include <cstdlib>
+#include <string>
+#include <raul/Array.hpp>
+#include "interface/Port.hpp"
+#include "types.hpp"
+#include "GraphObjectImpl.hpp"
+#include "interface/DataType.hpp"
+#include "Buffer.hpp"
+#include "Context.hpp"
+
+namespace Raul { class Maid; class Atom; }
+
+namespace Ingen {
+
+class NodeImpl;
+class Buffer;
+class ProcessContext;
+
+
+/** 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 Ingen::Shared::Port
+{
+public:
+ virtual ~PortImpl();
+
+ /** A port's parent is always a node, so static cast should be safe */
+ NodeImpl* parent_node() const { return (NodeImpl*)_parent; }
+
+ bool set_polyphonic(Raul::Maid& maid, bool p);
+
+ /** Prepare for a new (external) polyphony value.
+ *
+ * Preprocessor thread, poly is actually applied by apply_poly.
+ */
+ virtual bool prepare_poly(uint32_t poly);
+
+ /** Apply a new polyphony value.
+ *
+ * Audio thread.
+ *
+ * \param 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 Buffer* buffer(uint32_t voice) const {
+ Buffer* const buf = _buffers->at(voice);
+ if (buf->is_joined()) {
+ assert(buf->joined_buffer());
+ return buf->joined_buffer();
+ } else {
+ return buf;
+ }
+ }
+
+ /** Called once per process cycle */
+ virtual void pre_process(ProcessContext& context) = 0;
+ virtual void process(ProcessContext& context) {};
+ virtual void post_process(ProcessContext& context) = 0;
+
+ /** Empty buffer contents completely (ie silence) */
+ virtual void clear_buffers();
+
+ virtual bool is_input() const = 0;
+ virtual bool is_output() const = 0;
+
+ uint32_t index() const { return _index; }
+ uint32_t poly() const { return _poly; }
+ DataType type() const { return _type; }
+ size_t buffer_size() const { return _buffer_size; }
+
+ virtual void set_buffer_size(size_t size);
+
+ void fixed_buffers(bool b) { _fixed_buffers = b; }
+ bool fixed_buffers() { return _fixed_buffers; }
+
+ void broadcast(bool b) { _broadcast = b; }
+ bool broadcast() { return _broadcast; }
+
+ void raise_set_by_user_flag() { _set_by_user = true; }
+
+ Context::ID context() const { return _context; }
+ void set_context(Context::ID c) { _context = c; }
+
+protected:
+ PortImpl(NodeImpl* node,
+ const std::string& name,
+ uint32_t index,
+ uint32_t poly,
+ DataType type,
+ const Raul::Atom& value,
+ size_t buffer_size);
+
+ virtual void allocate_buffers();
+ virtual void connect_buffers();
+ virtual void broadcast(ProcessContext& context);
+
+ uint32_t _index;
+ uint32_t _poly;
+ uint32_t _buffer_size;
+ DataType _type;
+ Raul::Atom _value;
+ bool _fixed_buffers;
+ bool _broadcast;
+ bool _set_by_user;
+ Sample _last_broadcasted_value;
+
+ Context::ID _context;
+ Raul::Array<Buffer*>* _buffers;
+
+ // Dynamic polyphony
+ Raul::Array<Buffer*>* _prepared_buffers;
+};
+
+
+} // namespace Ingen
+
+#endif // PORTIMPL_H
diff --git a/src/engine/PostProcessor.cpp b/src/engine/PostProcessor.cpp
new file mode 100644
index 00000000..a50ca275
--- /dev/null
+++ b/src/engine/PostProcessor.cpp
@@ -0,0 +1,73 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 <iostream>
+#include <pthread.h>
+#include <raul/SRSWQueue.hpp>
+//#include <raul/Maid.hpp>
+#include "events/SendPortValueEvent.hpp"
+#include "Event.hpp"
+#include "PostProcessor.hpp"
+#include "Engine.hpp"
+#include "AudioDriver.hpp"
+#include "ProcessContext.hpp"
+
+using namespace std;
+
+namespace Ingen {
+
+
+PostProcessor::PostProcessor(Engine& engine, size_t queue_size)
+ : _engine(engine)
+ , _max_time(0)
+ , _events(queue_size)
+ , _event_buffer_size(sizeof(SendPortValueEvent)) // FIXME: make generic
+ , _event_buffer((uint8_t*)malloc(_event_buffer_size))
+{
+}
+
+
+void
+PostProcessor::process()
+{
+ const FrameTime end_time = _max_time.get();
+
+ /* Process any audio thread generated events */
+ /* FIXME: process events from all threads if parallel */
+
+ while (_engine.audio_driver()->context().event_sink().read(
+ _event_buffer_size, _event_buffer)) {
+ if (((Event*)_event_buffer)->time() > end_time)
+ break; // FIXME: loses event?
+ ((Event*)_event_buffer)->post_process();
+ }
+
+ /* Process normal events */
+ while ( ! _events.empty()) {
+ Event* const ev = _events.front();
+ if (ev->time() > end_time)
+ break;
+ _events.pop();
+ assert(ev);
+ ev->post_process();
+ delete ev;
+ }
+}
+
+
+} // namespace Ingen
diff --git a/src/engine/PostProcessor.hpp b/src/engine/PostProcessor.hpp
new file mode 100644
index 00000000..3d51136d
--- /dev/null
+++ b/src/engine/PostProcessor.hpp
@@ -0,0 +1,73 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 POSTPROCESSOR_H
+#define POSTPROCESSOR_H
+
+#include <pthread.h>
+#include "types.hpp"
+#include <raul/SRSWQueue.hpp>
+//#include <raul/Slave.hpp>
+
+//namespace Raul { class Maid; }
+
+namespace Ingen {
+
+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 Raul::Slave
+{
+public:
+ PostProcessor(Engine& engine, /*Raul::Maid& maid, */size_t queue_size);
+
+ /** Push an event on to the process queue, realtime-safe, not thread-safe. */
+ inline void push(Event* const ev) { _events.push(ev); }
+
+ /** 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::Maid& _maid;
+ Raul::SRSWQueue<Event*> _events;
+ uint32_t _event_buffer_size;
+ uint8_t* _event_buffer;
+
+ //virtual void _whipped();
+};
+
+
+} // namespace Ingen
+
+#endif // POSTPROCESSOR_H
diff --git a/src/engine/ProcessContext.hpp b/src/engine/ProcessContext.hpp
new file mode 100644
index 00000000..57677126
--- /dev/null
+++ b/src/engine/ProcessContext.hpp
@@ -0,0 +1,71 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 PROCESSCONTEXT_H
+#define PROCESSCONTEXT_H
+
+#include "EventSink.hpp"
+#include "Context.hpp"
+
+namespace Ingen {
+
+
+/** Context of a process() call.
+ *
+ * This is used to pass whatever information a GraphObject might need to
+ * process in the audio thread, e.g. the available thread pool, sink for
+ * events (generated in the audio thread, not user initiated events), etc.
+ *
+ * Note the distinction between nframes and start/end. If transport speed
+ * != 1.0, end-start != nframes (though currently that is never the case, it
+ * may be in the future with sequencerey things).
+ *
+ * \ingroup engine
+ */
+class ProcessContext : public Context
+{
+public:
+ ProcessContext(Engine& engine)
+ : Context(engine, AUDIO)
+ , _event_sink(engine, 1024) // FIXME: size?
+ {}
+
+ void set_time_slice(SampleCount nframes, FrameTime start, FrameTime end) {
+ _nframes = nframes;
+ _start = start;
+ _end = end;
+ }
+
+ inline SampleCount nframes() const { return _nframes; }
+ inline FrameTime start() const { return _start; }
+ inline FrameTime end() const { return _end; }
+ inline const EventSink& event_sink() const { return _event_sink; }
+ inline EventSink& event_sink() { return _event_sink; }
+
+private:
+ SampleCount _nframes; ///< Number of actual time (Jack) frames this cycle
+ FrameTime _start; ///< Start frame of this cycle, timeline relative
+ FrameTime _end; ///< End frame of this cycle, timeline relative
+ EventSink _event_sink; ///< Sink for events generated in the audio thread
+};
+
+
+
+} // namespace Ingen
+
+#endif // PROCESSCONTEXT_H
+
diff --git a/src/engine/ProcessSlave.cpp b/src/engine/ProcessSlave.cpp
new file mode 100644
index 00000000..c7c868e8
--- /dev/null
+++ b/src/engine/ProcessSlave.cpp
@@ -0,0 +1,75 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 <iostream>
+#include "ProcessSlave.hpp"
+#include "NodeImpl.hpp"
+#include "CompiledPatch.hpp"
+
+using namespace std;
+
+namespace Ingen {
+
+
+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(_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 Ingen
diff --git a/src/engine/ProcessSlave.hpp b/src/engine/ProcessSlave.hpp
new file mode 100644
index 00000000..40becd48
--- /dev/null
+++ b/src/engine/ProcessSlave.hpp
@@ -0,0 +1,100 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 PROCESS_SLAVE_HPP
+#define PROCESS_SLAVE_HPP
+
+#include CONFIG_H_PATH
+
+#include <sstream>
+#include <raul/Slave.hpp>
+#include <raul/Array.hpp>
+#include <raul/AtomicInt.hpp>
+#include "ProcessContext.hpp"
+#include "types.hpp"
+
+namespace Ingen {
+
+class NodeImpl;
+class CompiledPatch;
+
+
+class ProcessSlave : protected Raul::Slave {
+public:
+ ProcessSlave(Engine& engine, bool realtime)
+ : _id(_next_id++)
+ , _index(0)
+ , _state(STATE_FINISHED)
+ , _compiled_patch(NULL)
+ , _process_context(engine)
+ {
+ 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;
+ _process_context.set_time_slice(context.nframes(), context.start(), context.end());
+
+ 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 _process_context; }
+ inline ProcessContext& context() { return _process_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;
+
+ uint32_t _id;
+ uint32_t _index;
+ Raul::AtomicInt _state;
+ CompiledPatch* _compiled_patch;
+ ProcessContext _process_context;
+};
+
+
+} // namespace Ingen
+
+#endif // PROCESS_SLAVE_HPP
+
diff --git a/src/engine/QueuedEngineInterface.cpp b/src/engine/QueuedEngineInterface.cpp
new file mode 100644
index 00000000..962410b2
--- /dev/null
+++ b/src/engine/QueuedEngineInterface.cpp
@@ -0,0 +1,370 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 <iostream>
+#include "QueuedEngineInterface.hpp"
+#include CONFIG_H_PATH
+#include "QueuedEventSource.hpp"
+#include "events.hpp"
+#include "Engine.hpp"
+#include "AudioDriver.hpp"
+
+namespace Ingen {
+
+QueuedEngineInterface::QueuedEngineInterface(Engine& engine, size_t queued_size, size_t stamped_size)
+ : QueuedEventSource(queued_size, stamped_size)
+ , _responder(new Responder(NULL, 0))
+ , _engine(engine)
+ , _in_bundle(false)
+{
+}
+
+
+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
+ assert(_engine.audio_driver());
+ return _engine.audio_driver()->frame_time() + _engine.audio_driver()->buffer_size();
+}
+
+
+void
+QueuedEngineInterface::set_next_response_id(int32_t id)
+{
+ if (_responder)
+ _responder->set_id(id);
+}
+
+
+void
+QueuedEngineInterface::disable_responses()
+{
+ _responder->set_client(NULL);
+ _responder->set_id(0);
+}
+
+
+/* *** EngineInterface implementation below here *** */
+
+
+void
+QueuedEngineInterface::register_client(ClientInterface* client)
+{
+ push_queued(new RegisterClientEvent(_engine, _responder, now(), client->uri(), client));
+ if (!_responder) {
+ _responder = SharedPtr<Responder>(new Responder(client, 1));
+ } else {
+ _responder->set_id(1);
+ _responder->set_client(client);
+ }
+}
+
+
+void
+QueuedEngineInterface::unregister_client(const string& uri)
+{
+ push_queued(new UnregisterClientEvent(_engine, _responder, now(), uri));
+ if (_responder && _responder->client() && _responder->client()->uri() == uri) {
+ _responder->set_id(0);
+ _responder->set_client(NULL);
+ }
+}
+
+
+
+// Engine commands
+void
+QueuedEngineInterface::load_plugins()
+{
+ push_queued(new LoadPluginsEvent(_engine, _responder, now(), this));
+}
+
+
+void
+QueuedEngineInterface::activate()
+{
+ QueuedEventSource::activate();
+ push_queued(new PingQueuedEvent(_engine, _responder, now()));
+}
+
+
+void
+QueuedEngineInterface::deactivate()
+{
+ push_queued(new DeactivateEvent(_engine, _responder, now()));
+}
+
+
+void
+QueuedEngineInterface::quit()
+{
+ _responder->respond_ok();
+ _engine.quit();
+}
+
+
+// Bundle commands
+
+void
+QueuedEngineInterface::bundle_begin()
+{
+ _in_bundle = true;
+}
+
+
+void
+QueuedEngineInterface::bundle_end()
+{
+ _in_bundle = false;
+}
+
+
+// Object commands
+
+void
+QueuedEngineInterface::new_patch(const string& path,
+ uint32_t poly)
+{
+ push_queued(new CreatePatchEvent(_engine, _responder, now(), path, poly));
+}
+
+
+// FIXME: use index
+void QueuedEngineInterface::new_port(const string& path,
+ uint32_t index,
+ const string& data_type,
+ bool direction)
+{
+ push_queued(new CreatePortEvent(_engine, _responder, now(), path, data_type, direction, this));
+}
+
+
+void
+QueuedEngineInterface::new_node(const string& path,
+ const string& plugin_uri)
+{
+ push_queued(new CreateNodeEvent(_engine, _responder, now(),
+ path, plugin_uri, true)); // FIXME: polyphonic by default
+}
+
+
+void
+QueuedEngineInterface::new_node_deprecated(const string& path,
+ const string& plugin_type,
+ const string& plugin_lib,
+ const string& plugin_label)
+{
+ push_queued(new CreateNodeEvent(_engine, _responder, now(),
+ path, plugin_type, plugin_lib, plugin_label, true)); // FIXME: polyphonic by default
+}
+
+void
+QueuedEngineInterface::rename(const string& old_path,
+ const string& new_symbol)
+{
+ push_queued(new RenameEvent(_engine, _responder, now(), old_path, new_symbol));
+}
+
+
+void
+QueuedEngineInterface::destroy(const string& path)
+{
+ push_queued(new DestroyEvent(_engine, _responder, now(), this, path));
+}
+
+
+void
+QueuedEngineInterface::clear_patch(const string& patch_path)
+{
+ push_queued(new ClearPatchEvent(_engine, _responder, now(), this, patch_path));
+}
+
+
+void
+QueuedEngineInterface::set_polyphony(const string& patch_path, uint32_t poly)
+{
+ push_queued(new SetPolyphonyEvent(_engine, _responder, now(), this, patch_path, poly));
+}
+
+
+void
+QueuedEngineInterface::set_polyphonic(const string& path, bool poly)
+{
+ push_queued(new SetPolyphonicEvent(_engine, _responder, now(), this, path, poly));
+}
+
+
+void
+QueuedEngineInterface::connect(const string& src_port_path,
+ const string& dst_port_path)
+{
+ push_queued(new ConnectionEvent(_engine, _responder, now(), src_port_path, dst_port_path));
+
+}
+
+
+void
+QueuedEngineInterface::disconnect(const string& src_port_path,
+ const string& dst_port_path)
+{
+ push_queued(new DisconnectionEvent(_engine, _responder, now(), src_port_path, dst_port_path));
+}
+
+
+void
+QueuedEngineInterface::disconnect_all(const string& patch_path,
+ const string& node_path)
+{
+ push_queued(new DisconnectAllEvent(_engine, _responder, now(), patch_path, node_path));
+}
+
+
+void
+QueuedEngineInterface::set_port_value(const string& port_path,
+ const Raul::Atom& value)
+{
+ push_queued(new SetPortValueEvent(_engine, _responder, true, now(), port_path, value));
+}
+
+
+void
+QueuedEngineInterface::set_voice_value(const string& port_path,
+ uint32_t voice,
+ const Raul::Atom& value)
+{
+ push_queued(new SetPortValueEvent(_engine, _responder, true, now(), voice, port_path, value));
+}
+
+
+void
+QueuedEngineInterface::set_program(const string& node_path,
+ uint32_t bank,
+ uint32_t program)
+{
+ std::cerr << "FIXME: set program" << std::endl;
+}
+
+
+void
+QueuedEngineInterface::midi_learn(const string& node_path)
+{
+ push_queued(new MidiLearnEvent(_engine, _responder, now(), node_path));
+}
+
+
+void
+QueuedEngineInterface::set_variable(const string& path,
+ const string& predicate,
+ const Atom& value)
+{
+ push_queued(new SetMetadataEvent(_engine, _responder, now(), false, path, predicate, value));
+}
+
+
+void
+QueuedEngineInterface::set_property(const string& path,
+ const string& predicate,
+ const Atom& value)
+{
+ // FIXME: implement generically
+ if (predicate == "ingen:enabled") {
+ if (value.type() == Atom::BOOL) {
+ push_queued(new EnablePatchEvent(_engine, _responder, now(), path, value.get_bool()));
+ return;
+ }
+ } else if (predicate == "ingen:polyphonic") {
+ if (value.type() == Atom::BOOL) {
+ push_queued(new SetPolyphonicEvent(_engine, _responder, now(), this, path, value.get_bool()));
+ return;
+ }
+ } else if (predicate == "ingen:polyphony") {
+ if (value.type() == Atom::INT) {
+ push_queued(new SetPolyphonyEvent(_engine, _responder, now(), this, path, value.get_int32()));
+ return;
+ }
+ } else {
+ push_queued(new SetMetadataEvent(_engine, _responder, now(), true, path, predicate, value));
+ }
+}
+
+// Requests //
+
+void
+QueuedEngineInterface::ping()
+{
+ if (_engine.activated()) {
+ push_queued(new PingQueuedEvent(_engine, _responder, now()));
+ } else if (_responder) {
+ _responder->respond_ok();
+ }
+}
+
+
+void
+QueuedEngineInterface::request_plugin(const string& uri)
+{
+ push_queued(new RequestPluginEvent(_engine, _responder, now(), uri));
+}
+
+
+void
+QueuedEngineInterface::request_object(const string& path)
+{
+ push_queued(new RequestObjectEvent(_engine, _responder, now(), path));
+}
+
+
+void
+QueuedEngineInterface::request_port_value(const string& port_path)
+{
+ push_queued(new RequestPortValueEvent(_engine, _responder, now(), port_path));
+}
+
+
+void
+QueuedEngineInterface::request_variable(const string& object_path, const string& key)
+{
+ push_queued(new RequestMetadataEvent(_engine, _responder, now(), false, object_path, key));
+}
+
+
+void
+QueuedEngineInterface::request_property(const string& object_path, const string& key)
+{
+ push_queued(new RequestMetadataEvent(_engine, _responder, now(), true, object_path, key));
+}
+
+
+void
+QueuedEngineInterface::request_plugins()
+{
+ push_queued(new RequestPluginsEvent(_engine, _responder, now()));
+}
+
+
+void
+QueuedEngineInterface::request_all_objects()
+{
+ push_queued(new RequestAllObjectsEvent(_engine, _responder, now()));
+}
+
+
+} // namespace Ingen
+
+
diff --git a/src/engine/QueuedEngineInterface.hpp b/src/engine/QueuedEngineInterface.hpp
new file mode 100644
index 00000000..73c790c8
--- /dev/null
+++ b/src/engine/QueuedEngineInterface.hpp
@@ -0,0 +1,170 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 QUEUEDENGINEINTERFACE_H
+#define QUEUEDENGINEINTERFACE_H
+
+#include <inttypes.h>
+#include <string>
+#include <memory>
+#include <raul/SharedPtr.hpp>
+#include "interface/EngineInterface.hpp"
+#include "interface/ClientInterface.hpp"
+#include "Responder.hpp"
+#include "QueuedEventSource.hpp"
+#include "Engine.hpp"
+using std::string;
+
+namespace Ingen {
+
+using Shared::ClientInterface;
+using Shared::EngineInterface;
+class Engine;
+
+
+/** A queued (preprocessed) event source / interface.
+ *
+ * This is the bridge between the EngineInterface presented to the client, and
+ * the EventSource that needs to be presented to the AudioDriver.
+ *
+ * This is sort of a state machine, \ref set_responder sets the Responder that
+ * will be used to send the response from all future function calls. Stateless
+ * protocols like UDP/OSC can use this to set a different response address for
+ * each event (eg incoming UDP port), but engine/client interfaces that don't
+ * need to change an 'address' constantly can just set it once on initialisation.
+ * Blocking control interfaces can be made by setting a Responder which signals
+ * the caller when the 'response' is 'sent'.
+ *
+ * If you do not register a responder, you have no way of knowing if your calls
+ * are successful.
+ *
+ * FIXME: this isn't really "queued" entirely, since some events aren't queued
+ * events and get pushed directly into the realtime event queue. Should that
+ * be separated into a different interface/client?
+ */
+class QueuedEngineInterface : public QueuedEventSource, public EngineInterface
+{
+public:
+ QueuedEngineInterface(Engine& engine, size_t queued_size, size_t stamped_size);
+ virtual ~QueuedEngineInterface() {}
+
+ std::string uri() const { return "ingen:internal"; }
+
+ void set_next_response_id(int32_t id);
+
+ // Client registration
+ virtual void register_client(ClientInterface* client);
+ virtual void unregister_client(const string& uri);
+
+ // Engine commands
+ virtual void load_plugins();
+ virtual void activate();
+ virtual void deactivate();
+ virtual void quit();
+
+ // Bundles
+ virtual void bundle_begin();
+ virtual void bundle_end();
+
+ // Object commands
+
+ virtual void new_patch(const string& path,
+ uint32_t poly);
+
+ virtual void new_port(const string& path,
+ uint32_t index,
+ const string& data_type,
+ bool direction);
+
+ virtual void new_node(const string& path,
+ const string& plugin_uri);
+
+ /** FIXME: DEPRECATED, REMOVE */
+ virtual void new_node_deprecated(const string& path,
+ const string& plugin_type,
+ const string& lib_path,
+ const string& plug_label);
+
+ virtual void rename(const string& old_path,
+ const string& new_name);
+
+ virtual void destroy(const string& path);
+
+ virtual void clear_patch(const string& patch_path);
+
+ virtual void set_polyphony(const string& patch_path, uint32_t poly);
+
+ virtual void set_polyphonic(const string& path, bool poly);
+
+ virtual void connect(const string& src_port_path,
+ const string& dst_port_path);
+
+ virtual void disconnect(const string& src_port_path,
+ const string& dst_port_path);
+
+ virtual void disconnect_all(const string& patch_path,
+ const string& node_path);
+
+ virtual void set_port_value(const string& port_path,
+ const Raul::Atom& value);
+
+ virtual void set_voice_value(const string& port_path,
+ uint32_t voice,
+ const Raul::Atom& value);
+
+ virtual void set_program(const string& node_path,
+ uint32_t bank,
+ uint32_t program);
+
+ virtual void midi_learn(const string& node_path);
+
+ virtual void set_variable(const string& path,
+ const string& predicate,
+ const Raul::Atom& value);
+
+ virtual void set_property(const string& path,
+ const string& predicate,
+ const Raul::Atom& value);
+
+ // Requests //
+
+ virtual void ping();
+ virtual void request_plugin(const string& uri);
+ virtual void request_object(const string& path);
+ virtual void request_port_value(const string& port_path);
+ virtual void request_variable(const string& object_path, const string& key);
+ virtual void request_property(const string& object_path, const string& key);
+ virtual void request_plugins();
+ virtual void request_all_objects();
+
+protected:
+
+ virtual void disable_responses();
+
+ SharedPtr<Responder> _responder; ///< NULL if responding disabled
+ Engine& _engine;
+ bool _in_bundle; ///< True iff a bundle is currently being received
+
+private:
+ SampleCount now() const;
+};
+
+
+} // namespace Ingen
+
+#endif // QUEUEDENGINEINTERFACE_H
+
diff --git a/src/engine/QueuedEvent.cpp b/src/engine/QueuedEvent.cpp
new file mode 100644
index 00000000..8ed6fa02
--- /dev/null
+++ b/src/engine/QueuedEvent.cpp
@@ -0,0 +1,50 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 {
+
+
+void
+QueuedEvent::pre_process()
+{
+ assert(ThreadManager::current_thread_id() == 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 Ingen
+
diff --git a/src/engine/QueuedEvent.hpp b/src/engine/QueuedEvent.hpp
new file mode 100644
index 00000000..e616d269
--- /dev/null
+++ b/src/engine/QueuedEvent.hpp
@@ -0,0 +1,83 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 QUEUEDEVENT_H
+#define QUEUEDEVENT_H
+
+#include "Event.hpp"
+
+namespace Ingen {
+
+class QueuedEventSource;
+
+
+/** 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);
+
+ /** If this event blocks the prepare phase of other slow events */
+ bool is_blocking() { return _blocking; }
+
+ bool is_prepared() { return _pre_processed; }
+
+protected:
+ QueuedEvent(Engine& engine,
+ SharedPtr<Responder> responder,
+ FrameTime time,
+ bool blocking = false,
+ QueuedEventSource* source = NULL)
+ : Event(engine, responder, time)
+ , _pre_processed(false), _blocking(blocking), _source(source)
+ {
+ if (blocking)
+ assert(_source);
+ }
+
+ // NULL event base (for internal events only!)
+ QueuedEvent(Engine& engine)
+ : Event(engine, SharedPtr<Responder>(), 0)
+ , _pre_processed(false), _blocking(false), _source(NULL)
+ {}
+
+ bool _pre_processed;
+ bool _blocking;
+ QueuedEventSource* _source;
+};
+
+
+} // namespace Ingen
+
+#endif // QUEUEDEVENT_H
diff --git a/src/engine/QueuedEventSource.cpp b/src/engine/QueuedEventSource.cpp
new file mode 100644
index 00000000..69ab805a
--- /dev/null
+++ b/src/engine/QueuedEventSource.cpp
@@ -0,0 +1,183 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 <iostream>
+#include "QueuedEventSource.hpp"
+#include "QueuedEvent.hpp"
+#include "PostProcessor.hpp"
+#include "ThreadManager.hpp"
+#include "ProcessContext.hpp"
+
+using namespace std;
+
+namespace Ingen {
+
+
+QueuedEventSource::QueuedEventSource(size_t queued_size, size_t stamped_size)
+ : _front(0)
+ , _back(0)
+ , _prepared_back(0)
+ , _size(queued_size+1)
+ , _blocking_semaphore(0)
+ , _full_semaphore(0)
+ , _stamped_queue(stamped_size)
+{
+ _events = (QueuedEvent**)calloc(_size, sizeof(QueuedEvent*));
+
+ mlock(_events, _size * sizeof(QueuedEvent*));
+
+ Thread::set_context(THREAD_PRE_PROCESS);
+ assert(context() == THREAD_PRE_PROCESS);
+
+ set_name("QueuedEventSource");
+}
+
+
+QueuedEventSource::~QueuedEventSource()
+{
+ Thread::stop();
+
+ free(_events);
+}
+
+
+/** Push an unprepared event onto the queue.
+ */
+void
+QueuedEventSource::push_queued(QueuedEvent* const ev)
+{
+ assert(!ev->is_prepared());
+
+ unsigned back = _back.get();
+ bool full = (((_front.get() - back + _size) % _size) == 1);
+ while (full) {
+ whip();
+ cerr << "WARNING: Event queue full. Waiting..." << endl;
+ _full_semaphore.wait();
+ back = _back.get();
+ full = (((_front.get() - back + _size) % _size) == 1);
+ }
+
+ assert(_events[back] == NULL);
+ _events[back] = ev;
+ _back = (back + 1) % _size;
+ whip();
+}
+
+
+/** Process all events for a cycle.
+ *
+ * Executed events will be pushed to @a dest.
+ */
+void
+QueuedEventSource::process(PostProcessor& dest, ProcessContext& context)
+{
+ assert(ThreadManager::current_thread_id() == THREAD_PROCESS);
+
+ Event* ev = NULL;
+
+ /* 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 unsigned int MAX_QUEUED_EVENTS = context.nframes() / 100;
+
+ unsigned int num_events_processed = 0;
+
+ /* FIXME: Merge these next two loops into one */
+
+ while ((ev = pop_earliest_queued_before(context.end()))) {
+ ev->execute(context);
+ dest.push(ev);
+ if (++num_events_processed > MAX_QUEUED_EVENTS)
+ break;
+ }
+
+ while ((ev = pop_earliest_stamped_before(context.end()))) {
+ ev->execute(context);
+ dest.push(ev);
+ ++num_events_processed;
+ }
+
+ if (_full_semaphore.has_waiter() && num_events_processed > 0)
+ _full_semaphore.post();
+
+ /*if (num_events_processed > 0)
+ dest.whip();*/
+ //else
+ // cerr << "NO PROC: queued: " << unprepared_events() << ", stamped: " << !_stamped_queue.empty() << endl;
+}
+
+
+/** Pops the prepared event at the front of the prepare queue, if it exists.
+ *
+ * This method will only pop events that have been prepared, and are
+ * stamped before the time passed. In other words, it may return NULL
+ * even if there are events pending in the queue. The events returned are
+ * actually QueuedEvents, but after this they are "normal" events and the
+ * engine deals with them just like a realtime in-band event. The engine will
+ * not use the timestamps of the returned events in any way, since it is free
+ * to execute these non-time-stamped events whenever it wants (at whatever rate
+ * it wants).
+ */
+Event*
+QueuedEventSource::pop_earliest_queued_before(const SampleCount time)
+{
+ assert(ThreadManager::current_thread_id() == THREAD_PROCESS);
+
+ const unsigned front = _front.get();
+ QueuedEvent* const front_event = _events[front];
+
+ // Pop
+ if (front_event && front_event->is_prepared() && front_event->time() < time) {
+ _events[front] = NULL;
+ _front = (front + 1) % _size;
+ return front_event;
+ } else {
+ return NULL;
+ }
+}
+
+
+// Private //
+
+
+/** Pre-process a single event */
+void
+QueuedEventSource::_whipped()
+{
+ const unsigned prepared_back = _prepared_back.get();
+ QueuedEvent* const ev = _events[prepared_back];
+ if (!ev)
+ return;
+
+ assert(!ev->is_prepared());
+ ev->pre_process();
+ assert(ev->is_prepared());
+
+ _prepared_back = (prepared_back + 1) % _size;
+
+ // 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 Ingen
+
diff --git a/src/engine/QueuedEventSource.hpp b/src/engine/QueuedEventSource.hpp
new file mode 100644
index 00000000..6dea092d
--- /dev/null
+++ b/src/engine/QueuedEventSource.hpp
@@ -0,0 +1,128 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 QUEUEDEVENTSOURCE_H
+#define QUEUEDEVENTSOURCE_H
+
+#include <cstdlib>
+#include <pthread.h>
+#include "types.hpp"
+#include <raul/Semaphore.hpp>
+#include <raul/AtomicInt.hpp>
+#include <raul/SRSWQueue.hpp>
+#include <raul/Slave.hpp>
+#include "Event.hpp"
+#include "EventSource.hpp"
+
+using Raul::AtomicInt;
+
+namespace Ingen {
+
+class QueuedEvent;
+class PostProcessor;
+
+
+/** Queue of events that need processing before reaching the audio thread.
+ *
+ * Implemented as a deque (ringbuffer) in a circular array. Pushing and
+ * popping are threadsafe, as long as a single thread pushes and a single
+ * thread pops (ie this data structure is threadsafe, but the push and pop
+ * methods themselves are not). Creating an instance of this class spawns
+ * a pre-processing thread to prepare queued events.
+ *
+ * This class is it's own slave. :)
+ */
+class QueuedEventSource : public EventSource, protected Raul::Slave
+{
+public:
+ QueuedEventSource(size_t queued_size, size_t stamped_size);
+ ~QueuedEventSource();
+
+ void activate() { Slave::start(); }
+ void deactivate() { Slave::stop(); }
+
+ void process(PostProcessor& dest, ProcessContext& context);
+
+ void unblock();
+
+protected:
+ void push_queued(QueuedEvent* const ev);
+ inline void push_stamped(Event* const ev) { _stamped_queue.push(ev); }
+ Event* pop_earliest_queued_before(const SampleCount time);
+ inline Event* pop_earliest_stamped_before(const SampleCount time);
+
+ inline bool unprepared_events() { return (_prepared_back.get() != _back.get()); }
+
+ virtual void _whipped(); ///< Prepare 1 event
+
+private:
+ // Note that it's crucially important which functions access which of these
+ // variables, to maintain threadsafeness.
+
+ //(FIXME: make this a separate class?)
+ // 2-part queue for events that require pre-processing:
+ AtomicInt _front; ///< Front of queue
+ AtomicInt _back; ///< Back of entire queue (1 past index of back element)
+ AtomicInt _prepared_back; ///< Back of prepared section (1 past index of back prepared element)
+ const size_t _size;
+ QueuedEvent** _events;
+ Raul::Semaphore _blocking_semaphore;
+
+ Raul::Semaphore _full_semaphore;
+
+ /** Queue for timestamped events (no pre-processing). */
+ Raul::SRSWQueue<Event*> _stamped_queue;
+};
+
+
+/** Pops the realtime (timestamped, not preprocessed) event off the realtime queue.
+ *
+ * Engine will use the sample timestamps of returned events directly and execute the
+ * event with sample accuracy. Timestamps in the past will be bumped forward to
+ * the beginning of the cycle (offset 0), when eg. skipped cycles occur.
+ */
+inline Event*
+QueuedEventSource::pop_earliest_stamped_before(const SampleCount time)
+{
+ Event* ret = NULL;
+
+ if (!_stamped_queue.empty()) {
+ if (_stamped_queue.front()->time() < time) {
+ ret = _stamped_queue.front();
+ _stamped_queue.pop();
+ }
+ }
+
+ return ret;
+}
+
+
+/** Signal that the blocking event is finished.
+ *
+ * When this is called preparing will resume. This MUST be called by
+ * blocking events in their post_process() method.
+ */
+inline void
+QueuedEventSource::unblock()
+{
+ _blocking_semaphore.post();
+}
+
+
+} // namespace Ingen
+
+#endif // QUEUEDEVENTSOURCE_H
diff --git a/src/engine/Responder.hpp b/src/engine/Responder.hpp
new file mode 100644
index 00000000..80d2e24c
--- /dev/null
+++ b/src/engine/Responder.hpp
@@ -0,0 +1,71 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 RESPONDER_H
+#define RESPONDER_H
+
+#include <inttypes.h>
+#include <string>
+#include <raul/SharedPtr.hpp>
+#include "interface/ClientInterface.hpp"
+
+namespace Ingen {
+
+
+/** Class to handle responding to clients.
+ *
+ * This is a glorified std::pair<ClientInterface*, int32_t> for replying
+ * to numbered messages from a client.
+ *
+ * For responses that involve more 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 Responder
+{
+public:
+ Responder(Shared::ClientInterface* client=0, int32_t id=1)
+ : _client(client)
+ , _id(id)
+ {}
+
+ int32_t id() const { return _id; }
+ void set_id(int32_t id) { _id = id; }
+
+ Shared::ClientInterface* client() const { return _client; }
+ void set_client(Shared::ClientInterface* client) { _client = client; }
+
+ void respond_ok() {
+ if (_client)
+ _client->response_ok(_id);
+ }
+
+ void respond_error(const std::string& msg) {
+ if (_client)
+ _client->response_error(_id, msg);
+ }
+
+private:
+ Shared::ClientInterface* _client;
+ int32_t _id;
+};
+
+
+} // namespace Ingen
+
+#endif // RESPONDER_H
+
diff --git a/src/engine/ThreadManager.hpp b/src/engine/ThreadManager.hpp
new file mode 100644
index 00000000..12f27000
--- /dev/null
+++ b/src/engine/ThreadManager.hpp
@@ -0,0 +1,43 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 THREADMANAGER_H
+#define THREADMANAGER_H
+
+#include <raul/Thread.hpp>
+
+using Raul::Thread;
+
+namespace Ingen {
+
+
+enum ThreadID {
+ THREAD_PRE_PROCESS,
+ THREAD_PROCESS,
+ THREAD_POST_PROCESS
+};
+
+
+class ThreadManager {
+public:
+ inline static ThreadID current_thread_id() { return (ThreadID)Thread::get().context(); }
+};
+
+
+} // namespace Ingen
+
+#endif // THREADMANAGER_H
diff --git a/src/engine/TransportNode.cpp b/src/engine/TransportNode.cpp
new file mode 100644
index 00000000..3fc14a43
--- /dev/null
+++ b/src/engine/TransportNode.cpp
@@ -0,0 +1,154 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "TransportNode.hpp"
+#include <jack/transport.h>
+#include "OutputPort.hpp"
+#include "InternalPlugin.hpp"
+#include "JackAudioDriver.hpp"
+#include "PortImpl.hpp"
+#include "util.hpp"
+//#include "Engine.hpp"
+
+namespace Ingen {
+
+
+TransportNode::TransportNode(const string& path, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size)
+: NodeBase(new InternalPlugin(NS_INGEN "transport_node", "transport", "Transport Follower"),
+ path, false, parent, srate, buffer_size)
+{
+#if 0
+ _num_ports = 10;
+ _ports.alloc(_num_ports);
+
+ OutputPort<Sample>* spb_port = new OutputPort<Sample>(this, "Seconds per Beat", 0, 1,
+ // new PortInfo("Seconds per Beat", CONTROL, OUTPUT, 0, 0, 1), 1);
+ _ports.at(0) = spb_port;
+
+ OutputPort<Sample>* bpb_port = new OutputPort<Sample>(this, "Beats per Bar", 1, 1,
+ // new PortInfo("Beats per Bar", CONTROL, OUTPUT, 0, 0, 1), 1);
+ _ports.at(1) = bpb_port;
+
+ OutputPort<Sample>* bar_port = new OutputPort<Sample>(this, "Bar", 3, 1,
+// new PortInfo("Bar", CONTROL, OUTPUT, 0, 0, 1), buffer_size);
+ _ports.at(2) = bar_port;
+
+ OutputPort<Sample>* beat_port = new OutputPort<Sample>(this, "Beat", 3, 1,
+ // new PortInfo("Beat", CONTROL, OUTPUT, 0, 0, 1), buffer_size);
+ _ports.at(3) = beat_port;
+
+ OutputPort<Sample>* frame_port = new OutputPort<Sample>(this, "Frame", 3, 1,
+ // new PortInfo("Frame", CONTROL, OUTPUT, 0, 0, 1), buffer_size);
+ _ports.at(4) = frame_port;
+
+ OutputPort<Sample>* hour_port = new OutputPort<Sample>(this, "Hour", 3, 1,
+ // new PortInfo("Hour", CONTROL, OUTPUT, 0, 0, 1), buffer_size);
+ _ports.at(5) = hour_port;
+
+ OutputPort<Sample>* minute_port = new OutputPort<Sample>(this, "Minute", 3, 1,
+ // new PortInfo("Minute", CONTROL, OUTPUT, 0, 0, 1), buffer_size);
+ _ports.at(6) = minute_port;
+
+ OutputPort<Sample>* second_port = new OutputPort<Sample>(this, "Second", 3, 1,
+ // new PortInfo("Second", CONTROL, OUTPUT, 0, 0, 1), buffer_size);
+ _ports.at(7) = second_port;
+
+ OutputPort<Sample>* trg_port = new OutputPort<Sample>(this, "Beat Tick", 2, 1,
+ // new PortInfo("Beat Tick", AUDIO, OUTPUT, 0, 0, 1), buffer_size);
+ _ports.at(8) = trg_port;
+
+ OutputPort<Sample>* bar_trig_port = new OutputPort<Sample>(this, "Bar Tick", 3, 1,
+ // new PortInfo("Bar Tick", AUDIO, OUTPUT, 0, 0, 1), buffer_size);
+ _ports.at(9) = bar_trig_port;
+#endif
+}
+
+
+void
+TransportNode::process(ProcessContext& context)
+{
+ NodeBase::pre_process(context);
+#if 0
+
+ // FIXME: this will die horribly with any driver other than jack (in theory)
+ const jack_position_t* const position = ((JackAudioDriver*)Engine::instance().audio_driver())->position();
+ jack_transport_state_t state = ((JackAudioDriver*)Engine::instance().audio_driver())->transport_state();
+ double bpm = position->beats_per_minute;
+ float bpb = position->beats_per_bar;
+ float spb = 60.0 / bpm;
+
+ //cerr << "bpm = " << bpm << endl;
+ //cerr << "spb = " << spb << endl;
+
+ if (position->valid & JackPositionBBT) {
+ cerr << "bar: " << position->bar << endl;
+ cerr << "beat: " << position->beat << endl;
+ cerr << "tick: " << position->tick << endl;
+ } else {
+ cerr << "No BBT" << endl;
+ }
+
+ if (position->valid & JackBBTFrameOffset) {
+ cerr << "bbt_offset: " << position->bbt_offset << endl;
+ } else {
+ cerr << "No BBT offset" << endl;
+ }
+
+ if (position->valid & JackPositionTimecode) {
+ double time = position->frame_time;
+ cerr << "Seconds: " << time << " : " << endl;
+ /*time /= 60.0;
+ cerr << "Minutes: " << time << " : ";
+ time /= 60.0;
+ cerr << "Hours: " << time << " : ";*/
+ } else {
+ cerr << "No timecode." << endl;
+ }
+
+
+ ((OutputPort<Sample>*)_ports.at(0))->buffer(0)->set(spb, 0, 0);
+ ((OutputPort<Sample>*)_ports.at(1))->buffer(0)->set(bpb, 0, 0);
+
+ // fill the trigger buffers with zeros
+ ((OutputPort<Sample>*)_ports.at(2))->buffer(0)->set(0.0f, 0, nframes - 1);
+ ((OutputPort<Sample>*)_ports.at(3))->buffer(0)->set(0.0f, 0, nframes - 1);
+
+ // if the transport is rolling, add triggers at the right frame positions
+ if ((position->valid & JackTransportBBT) && (state == JackTransportRolling)) {
+ double frames_per_beat = position->frame_rate * spb;
+ double first_beat = (1.0f - position->tick / position->ticks_per_beat) * frames_per_beat;
+ int first_beat_no = position->beat;
+ if (first_beat >= frames_per_beat) {
+ first_beat -= frames_per_beat;
+ --first_beat_no;
+ }
+ for ( ; first_beat < nframes; first_beat += frames_per_beat) {
+ ((OutputPort<Sample>*)_ports.at(2))->buffer(0)->set(1.0f, size_t(first_beat));
+ if (first_beat_no % int(bpb) == 0) {
+ ((OutputPort<Sample>*)_ports.at(3))->buffer(0)->set(1.0f, size_t(first_beat));
+ ++first_beat_no;
+ }
+ }
+ }
+ #endif
+
+ NodeBase::post_process(context);
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/TransportNode.hpp b/src/engine/TransportNode.hpp
new file mode 100644
index 00000000..af0ed207
--- /dev/null
+++ b/src/engine/TransportNode.hpp
@@ -0,0 +1,45 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 TRANSPORTNODE_H
+#define TRANSPORTNODE_H
+
+#include <string>
+#include <jack/transport.h>
+#include "NodeBase.hpp"
+
+namespace Ingen {
+
+
+/** Transport Node, brings timing information into patches.
+ *
+ * This node uses the Jack transport API to get information about BPM, time
+ * signature, etc.. all sample accurate. Using this you can do
+ * tempo-synced effects or even synthesis, etc.
+ */
+class TransportNode : public NodeBase
+{
+public:
+ TransportNode(const std::string& path, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size);
+
+ virtual void process(ProcessContext& context);
+};
+
+
+} // namespace Ingen
+
+#endif // TRANSPORTNODE_H
diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp
new file mode 100644
index 00000000..45c17354
--- /dev/null
+++ b/src/engine/engine.cpp
@@ -0,0 +1,56 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 CONFIG_H_PATH
+
+#include <raul/Process.hpp>
+#include "engine.hpp"
+#include "Engine.hpp"
+#include "QueuedEngineInterface.hpp"
+#include "tuning.hpp"
+#include "util.hpp"
+
+namespace Ingen {
+
+Engine*
+new_engine(Ingen::Shared::World* world)
+{
+ set_denormal_flags();
+ return new Engine(world);
+}
+
+
+bool
+launch_osc_engine(int port)
+{
+ char port_str[6];
+ snprintf(port_str, 6, "%u", port);
+ const string cmd = string("ingen -e --engine-port=").append(port_str);
+
+ if (Raul::Process::launch(cmd)) {
+ return true;
+ //return SharedPtr<EngineInterface>(new OSCEngineSender(
+ // string("osc.udp://localhost:").append(port_str)));
+ } else {
+ std::cerr << "Failed to launch engine process." << std::endl;
+ //return SharedPtr<EngineInterface>();
+ return false;
+ }
+}
+
+} // namespace Ingen
+
diff --git a/src/engine/engine.hpp b/src/engine/engine.hpp
new file mode 100644
index 00000000..93b426ac
--- /dev/null
+++ b/src/engine/engine.hpp
@@ -0,0 +1,42 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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_H
+#define INGEN_ENGINE_H
+
+namespace Ingen {
+
+namespace Shared { class World; }
+
+class Engine;
+
+extern "C" {
+
+ /** Create a new engine in this process */
+ Engine* new_engine(Ingen::Shared::World* world);
+
+ /** Launch an OSC engine as a completely separate process
+ * \return true if successful
+ */
+ bool launch_osc_engine(int port);
+}
+
+
+} // namespace Ingen
+
+#endif // INGEN_ENGINE_H
+
diff --git a/src/engine/events.hpp b/src/engine/events.hpp
new file mode 100644
index 00000000..14f5230c
--- /dev/null
+++ b/src/engine/events.hpp
@@ -0,0 +1,53 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 EVENTS_H
+#define EVENTS_H
+
+#include CONFIG_H_PATH
+
+#include "AllNotesOffEvent.hpp"
+#include "ClearPatchEvent.hpp"
+#include "ConnectionEvent.hpp"
+#include "CreateNodeEvent.hpp"
+#include "CreatePatchEvent.hpp"
+#include "CreatePortEvent.hpp"
+#include "DeactivateEvent.hpp"
+#include "DestroyEvent.hpp"
+#include "DisconnectAllEvent.hpp"
+#include "DisconnectionEvent.hpp"
+#include "EnablePatchEvent.hpp"
+#include "LoadPluginsEvent.hpp"
+#include "MidiLearnEvent.hpp"
+#include "NoteEvent.hpp"
+#include "PingQueuedEvent.hpp"
+#include "RegisterClientEvent.hpp"
+#include "RenameEvent.hpp"
+#include "RequestAllObjectsEvent.hpp"
+#include "RequestMetadataEvent.hpp"
+#include "RequestObjectEvent.hpp"
+#include "RequestPluginEvent.hpp"
+#include "RequestPluginsEvent.hpp"
+#include "RequestPortValueEvent.hpp"
+#include "SetMetadataEvent.hpp"
+#include "SetPolyphonicEvent.hpp"
+#include "SetPolyphonyEvent.hpp"
+#include "SetPortValueEvent.hpp"
+#include "UnregisterClientEvent.hpp"
+
+#endif // EVENTS_H
+
diff --git a/src/engine/events/AllNotesOffEvent.cpp b/src/engine/events/AllNotesOffEvent.cpp
new file mode 100644
index 00000000..fcb68b31
--- /dev/null
+++ b/src/engine/events/AllNotesOffEvent.cpp
@@ -0,0 +1,71 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "AllNotesOffEvent.hpp"
+#include "Responder.hpp"
+#include "Engine.hpp"
+#include "EngineStore.hpp"
+#include "module/World.hpp"
+#include "shared/Store.hpp"
+
+namespace Ingen {
+
+
+/** Note off with patch explicitly passed - triggered by MIDI.
+ */
+AllNotesOffEvent::AllNotesOffEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, PatchImpl* patch)
+: Event(engine, responder, timestamp),
+ _patch(patch)
+{
+}
+
+
+/** Note off event with lookup - triggered by OSC.
+ */
+AllNotesOffEvent::AllNotesOffEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& patch_path)
+: Event(engine, responder, timestamp),
+ _patch_path(patch_path),
+ _patch(NULL)
+{
+}
+
+
+void
+AllNotesOffEvent::execute(ProcessContext& context)
+{
+ Event::execute(context);
+
+ if (_patch == NULL && _patch_path != "")
+ _patch = _engine.engine_store()->find_patch(_patch_path);
+
+ //if (_patch != NULL)
+ // for (Raul::List<MidiInNode*>::iterator j = _patch->midi_in_nodes().begin(); j != _patch->midi_in_nodes().end(); ++j)
+ // (*j)->all_notes_off(offset);
+}
+
+
+void
+AllNotesOffEvent::post_process()
+{
+ if (_patch != NULL)
+ _responder->respond_ok();
+}
+
+
+} // namespace Ingen
+
+
diff --git a/src/engine/events/AllNotesOffEvent.hpp b/src/engine/events/AllNotesOffEvent.hpp
new file mode 100644
index 00000000..3e4d56b3
--- /dev/null
+++ b/src/engine/events/AllNotesOffEvent.hpp
@@ -0,0 +1,51 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 ALLNOTESOFFEVENT_H
+#define ALLNOTESOFFEVENT_H
+
+#include "Event.hpp"
+#include <string>
+using std::string;
+
+namespace Ingen {
+
+class PatchImpl;
+
+
+/** A note off event for all active voices.
+ *
+ * \ingroup engine
+ */
+class AllNotesOffEvent : public Event
+{
+public:
+ AllNotesOffEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, PatchImpl* patch);
+ AllNotesOffEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& patch_path);
+
+ void execute(ProcessContext& context);
+ void post_process();
+
+private:
+ const string _patch_path;
+ PatchImpl* _patch;
+};
+
+
+} // namespace Ingen
+
+#endif // ALLNOTESOFFEVENT_H
diff --git a/src/engine/events/ClearPatchEvent.cpp b/src/engine/events/ClearPatchEvent.cpp
new file mode 100644
index 00000000..c1fb0749
--- /dev/null
+++ b/src/engine/events/ClearPatchEvent.cpp
@@ -0,0 +1,133 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "ClearPatchEvent.hpp"
+#include "Responder.hpp"
+#include "Engine.hpp"
+#include "PatchImpl.hpp"
+#include "ClientBroadcaster.hpp"
+#include "util.hpp"
+#include "EngineStore.hpp"
+#include "PortImpl.hpp"
+#include "NodeImpl.hpp"
+#include "ConnectionImpl.hpp"
+#include "QueuedEventSource.hpp"
+#include "AudioDriver.hpp"
+#include "MidiDriver.hpp"
+
+namespace Ingen {
+
+
+ClearPatchEvent::ClearPatchEvent(Engine& engine, SharedPtr<Responder> responder, FrameTime time, QueuedEventSource* source, const string& patch_path)
+ : QueuedEvent(engine, responder, time, true, source)
+ , _patch_path(patch_path)
+ , _driver_port(NULL)
+ , _process(false)
+ , _ports_array(NULL)
+ , _compiled_patch(NULL)
+{
+}
+
+
+void
+ClearPatchEvent::pre_process()
+{
+ EngineStore::Objects::iterator patch_iterator = _engine.engine_store()->find(_patch_path);
+
+ if (patch_iterator != _engine.engine_store()->end()) {
+ _patch = PtrCast<PatchImpl>(patch_iterator->second);
+ if (_patch) {
+ _process = _patch->enabled();
+ _removed_table = _engine.engine_store()->remove_children(patch_iterator);
+ _patch->nodes().clear();
+ _patch->connections().clear();
+ _ports_array = _patch->build_ports_array();
+ if (_patch->enabled())
+ _compiled_patch = _patch->compile();
+ }
+ }
+
+ QueuedEvent::pre_process();
+}
+
+
+void
+ClearPatchEvent::execute(ProcessContext& context)
+{
+ QueuedEvent::execute(context);
+
+ if (_patch && _removed_table) {
+ _patch->disable();
+
+ if (_patch->compiled_patch() != NULL) {
+ _engine.maid()->push(_patch->compiled_patch());
+ _patch->compiled_patch(NULL);
+ }
+
+ _patch->clear_ports();
+ _patch->connections().clear();
+ _patch->compiled_patch(_compiled_patch);
+ Raul::Array<PortImpl*>* old_ports = _patch->external_ports();
+ _patch->external_ports(_ports_array);
+ _ports_array = old_ports;
+
+ // Remove driver ports, if necessary
+ if (_patch->parent() == NULL) {
+ for (EngineStore::Objects::iterator i = _removed_table->begin(); i != _removed_table->end(); ++i) {
+ SharedPtr<PortImpl> port = PtrCast<PortImpl>(i->second);
+ if (port && port->type() == DataType::AUDIO)
+ _driver_port = _engine.audio_driver()->remove_port(port->path());
+ else if (port && port->type() == DataType::EVENT)
+ _driver_port = _engine.midi_driver()->remove_port(port->path());
+ }
+ }
+ }
+}
+
+
+void
+ClearPatchEvent::post_process()
+{
+ if (_patch != NULL) {
+ delete _ports_array;
+ delete _driver_port;
+
+ // Restore patch's run state
+ if (_process)
+ _patch->enable();
+ else
+ _patch->disable();
+
+ // Make sure everything's sane
+ assert(_patch->nodes().size() == 0);
+ assert(_patch->num_ports() == 0);
+ assert(_patch->connections().size() == 0);
+
+ // Reply
+ _responder->respond_ok();
+ _engine.broadcaster()->send_patch_cleared(_patch_path);
+ } else {
+ _responder->respond_error(string("Patch ") + _patch_path + " not found");
+ }
+
+ _source->unblock(); // FIXME: can be done earlier in execute?
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/events/ClearPatchEvent.hpp b/src/engine/events/ClearPatchEvent.hpp
new file mode 100644
index 00000000..803721fb
--- /dev/null
+++ b/src/engine/events/ClearPatchEvent.hpp
@@ -0,0 +1,65 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 CLEARPATCHEVENT_H
+#define CLEARPATCHEVENT_H
+
+#include <string>
+#include <raul/Array.hpp>
+#include <raul/Table.hpp>
+#include <raul/Path.hpp>
+#include "QueuedEvent.hpp"
+#include "EngineStore.hpp"
+#include "PatchImpl.hpp"
+
+using std::string;
+
+namespace Ingen {
+
+class PatchImpl;
+class DriverPort;
+
+
+/** Delete all nodes from a patch.
+ *
+ * \ingroup engine
+ */
+class ClearPatchEvent : public QueuedEvent
+{
+public:
+ ClearPatchEvent(Engine& engine, SharedPtr<Responder> responder, FrameTime time, QueuedEventSource* source, const string& patch_path);
+
+ void pre_process();
+ void execute(ProcessContext& context);
+ void post_process();
+
+private:
+ const string _patch_path;
+ SharedPtr<PatchImpl> _patch;
+ DriverPort* _driver_port;
+ bool _process;
+ Raul::Array<PortImpl*>* _ports_array; ///< New (external) ports for Patch
+ CompiledPatch* _compiled_patch; ///< Patch's new process order
+
+ SharedPtr< Table<Path, SharedPtr<Shared::GraphObject> > > _removed_table;
+};
+
+
+} // namespace Ingen
+
+
+#endif // CLEARPATCHEVENT_H
diff --git a/src/engine/events/ConnectionEvent.cpp b/src/engine/events/ConnectionEvent.cpp
new file mode 100644
index 00000000..24b80dee
--- /dev/null
+++ b/src/engine/events/ConnectionEvent.cpp
@@ -0,0 +1,202 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "ConnectionEvent.hpp"
+#include "ConnectionImpl.hpp"
+#include "Engine.hpp"
+#include "InputPort.hpp"
+#include "EngineStore.hpp"
+#include "OutputPort.hpp"
+#include "PatchImpl.hpp"
+#include "PortImpl.hpp"
+#include "Responder.hpp"
+#include "types.hpp"
+
+using std::string;
+namespace Ingen {
+
+
+ConnectionEvent::ConnectionEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& src_port_path, const string& dst_port_path)
+: QueuedEvent(engine, responder, timestamp),
+ _src_port_path(src_port_path),
+ _dst_port_path(dst_port_path),
+ _patch(NULL),
+ _src_port(NULL),
+ _dst_port(NULL),
+ _compiled_patch(NULL),
+ _patch_listnode(NULL),
+ _port_listnode(NULL),
+ _error(NO_ERROR)
+{
+}
+
+
+void
+ConnectionEvent::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;
+ }
+
+ if ( ! (_src_port->type() == _dst_port->type()
+ || ( (_src_port->type() == DataType::CONTROL || _src_port->type() == DataType::AUDIO)
+ && (_dst_port->type() == DataType::CONTROL || _dst_port->type() == DataType::AUDIO) ))) {
+ _error = TYPE_MISMATCH;
+ 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();
+
+ // 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 (_dst_input_port->is_connected_to(_src_output_port)) {
+ if (_patch->has_connection(_src_output_port, _dst_input_port)) {
+ _error = ALREADY_CONNECTED;
+ QueuedEvent::pre_process();
+ return;
+ }
+
+ if (src_node == NULL || dst_node == NULL) {
+ _error = PARENTS_NOT_FOUND;
+ QueuedEvent::pre_process();
+ return;
+ }
+
+ if (_patch != src_node && src_node->parent() != _patch && dst_node->parent() != _patch) {
+ _error = PARENTS_NOT_FOUND;
+ QueuedEvent::pre_process();
+ return;
+ }
+
+ _connection = SharedPtr<ConnectionImpl>(new ConnectionImpl(_src_port, _dst_port));
+ _patch_listnode = new PatchImpl::Connections::Node(_connection);
+ _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(_patch_listnode);
+
+ if (_patch->enabled())
+ _compiled_patch = _patch->compile();
+
+ QueuedEvent::pre_process();
+}
+
+
+void
+ConnectionEvent::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);
+ if (_patch->compiled_patch() != NULL)
+ _engine.maid()->push(_patch->compiled_patch());
+ _patch->compiled_patch(_compiled_patch);
+ }
+}
+
+
+void
+ConnectionEvent::post_process()
+{
+ std::ostringstream ss;
+ if (_error == NO_ERROR) {
+ _responder->respond_ok();
+ _engine.broadcaster()->send_connection(_connection);
+ return;
+ }
+
+ ss << boost::format("Unable to make connection %1% -> %2% (") % _src_port_path % _dst_port_path;
+
+ 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 << ")";
+ _responder->respond_error(ss.str());
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/events/ConnectionEvent.hpp b/src/engine/events/ConnectionEvent.hpp
new file mode 100644
index 00000000..89407957
--- /dev/null
+++ b/src/engine/events/ConnectionEvent.hpp
@@ -0,0 +1,92 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 CONNECTIONEVENT_H
+#define CONNECTIONEVENT_H
+
+#include <string>
+#include <raul/Path.hpp>
+#include "QueuedEvent.hpp"
+#include "PatchImpl.hpp"
+#include "InputPort.hpp"
+#include "types.hpp"
+using std::string;
+
+namespace Raul {
+ template <typename T> class ListNode;
+ template <typename T> class Array;
+}
+
+namespace Ingen {
+
+class PatchImpl;
+class NodeImpl;
+class ConnectionImpl;
+class MidiMessage;
+class PortImpl;
+class InputPort;
+class OutputPort;
+class CompiledPatch;
+
+
+/** Make a Connection between two Ports.
+ *
+ * \ingroup engine
+ */
+class ConnectionEvent : public QueuedEvent
+{
+public:
+ ConnectionEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& src_port_path, const string& 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;
+ PortImpl* _src_port;
+ PortImpl* _dst_port;
+ OutputPort* _src_output_port;
+ InputPort* _dst_input_port;
+
+ CompiledPatch* _compiled_patch; ///< New process order for Patch
+
+ SharedPtr<ConnectionImpl> _connection;
+ PatchImpl::Connections::Node* _patch_listnode;
+ InputPort::Connections::Node* _port_listnode;
+
+ ErrorType _error;
+};
+
+
+} // namespace Ingen
+
+#endif // CONNECTIONEVENT_H
diff --git a/src/engine/events/CreateNodeEvent.cpp b/src/engine/events/CreateNodeEvent.cpp
new file mode 100644
index 00000000..b58bc2c3
--- /dev/null
+++ b/src/engine/events/CreateNodeEvent.cpp
@@ -0,0 +1,151 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 <raul/Path.hpp>
+#include <redlandmm/World.hpp>
+#include "module/World.hpp"
+#include "CreateNodeEvent.hpp"
+#include "Responder.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 "AudioDriver.hpp"
+
+namespace Ingen {
+
+
+CreateNodeEvent::CreateNodeEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& path,
+ const string& plugin_uri, bool polyphonic)
+: QueuedEvent(engine, responder, timestamp),
+ _path(path),
+ _plugin_uri(plugin_uri),
+ _polyphonic(polyphonic),
+ _patch(NULL),
+ _node(NULL),
+ _compiled_patch(NULL),
+ _node_already_exists(false)
+{
+}
+
+
+/** DEPRECATED: Construct from type, library name, and plugin label.
+ *
+ * Do not use.
+ */
+CreateNodeEvent::CreateNodeEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& path,
+ const string& plugin_type, const string& plugin_lib, const string& plugin_label, bool polyphonic)
+: QueuedEvent(engine, responder, timestamp),
+ _path(path),
+ _plugin_type(plugin_type),
+ _plugin_lib(plugin_lib),
+ _plugin_label(plugin_label),
+ _polyphonic(polyphonic),
+ _patch(NULL),
+ _node(NULL),
+ _compiled_patch(NULL),
+ _node_already_exists(false)
+{
+}
+
+
+void
+CreateNodeEvent::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());
+
+ PluginImpl* const plugin = (_plugin_uri != "")
+ ? _engine.node_factory()->plugin(_plugin_uri)
+ : _engine.node_factory()->plugin(_plugin_type, _plugin_lib, _plugin_label);
+
+ if (_patch && plugin) {
+
+ _node = plugin->instantiate(_path.name(), _polyphonic, _patch, _engine);
+
+ if (_node != NULL) {
+ _node->activate();
+
+ // 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));
+ //_node->add_to_store(_engine.engine_store());
+ _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();
+ }
+ }
+ QueuedEvent::pre_process();
+}
+
+
+void
+CreateNodeEvent::execute(ProcessContext& context)
+{
+ QueuedEvent::execute(context);
+
+ if (_node != NULL) {
+ if (_patch->compiled_patch() != NULL)
+ _engine.maid()->push(_patch->compiled_patch());
+ _patch->compiled_patch(_compiled_patch);
+ }
+}
+
+
+void
+CreateNodeEvent::post_process()
+{
+ string msg;
+ if (_node_already_exists) {
+ msg = string("Could not create node - ").append(_path);// + " already exists.";
+ _responder->respond_error(msg);
+ } else if (_patch == NULL) {
+ msg = "Could not find patch '" + _path.parent() +"' for add_node.";
+ _responder->respond_error(msg);
+ } else if (_node == NULL) {
+ msg = "Unable to load node ";
+ msg += _path + " (you're missing the plugin ";
+ if (_plugin_uri != "")
+ msg += _plugin_uri;
+ else
+ msg += _plugin_lib + ":" + _plugin_label + " (" + _plugin_type + ")";
+ msg += ")";
+ _responder->respond_error(msg);
+ } else {
+ _responder->respond_ok();
+ _engine.broadcaster()->send_node(_node, true); // yes, send ports
+ }
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/events/CreateNodeEvent.hpp b/src/engine/events/CreateNodeEvent.hpp
new file mode 100644
index 00000000..c3ef6313
--- /dev/null
+++ b/src/engine/events/CreateNodeEvent.hpp
@@ -0,0 +1,81 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 CREATENODEEVENT_H
+#define CREATENODEEVENT_H
+
+#include "QueuedEvent.hpp"
+#include <raul/Path.hpp>
+#include <string>
+using std::string;
+
+namespace Raul { template <typename T> class Array; }
+template<typename T> class TreeNode;
+
+namespace Ingen {
+
+class PatchImpl;
+class NodeImpl;
+class CompiledPatch;
+
+
+/** An event to load a Node and insert it into a Patch.
+ *
+ * \ingroup engine
+ */
+class CreateNodeEvent : public QueuedEvent
+{
+public:
+ CreateNodeEvent(Engine& engine,
+ SharedPtr<Responder> responder,
+ SampleCount timestamp,
+ const string& node_path,
+ const string& plugin_uri,
+ bool poly);
+
+ // DEPRECATED
+ CreateNodeEvent(Engine& engine,
+ SharedPtr<Responder> responder,
+ SampleCount timestamp,
+ const string& node_path,
+ const string& plugin_type,
+ const string& lib_name,
+ const string& plugin_label,
+ bool poly);
+
+ void pre_process();
+ void execute(ProcessContext& context);
+ void post_process();
+
+private:
+ string _patch_name;
+ Raul::Path _path;
+ string _plugin_uri; ///< If nonempty then type, library, label, are ignored
+ string _plugin_type;
+ string _plugin_lib;
+ string _plugin_label;
+ bool _polyphonic;
+ PatchImpl* _patch;
+ NodeImpl* _node;
+ CompiledPatch* _compiled_patch; ///< Patch's new process order
+ bool _node_already_exists;
+};
+
+
+} // namespace Ingen
+
+#endif // CREATENODEEVENT_H
diff --git a/src/engine/events/CreatePatchEvent.cpp b/src/engine/events/CreatePatchEvent.cpp
new file mode 100644
index 00000000..64fe5c63
--- /dev/null
+++ b/src/engine/events/CreatePatchEvent.cpp
@@ -0,0 +1,157 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "CreatePatchEvent.hpp"
+#include "Responder.hpp"
+#include "PatchImpl.hpp"
+#include "NodeImpl.hpp"
+#include "PluginImpl.hpp"
+#include "Engine.hpp"
+#include "ClientBroadcaster.hpp"
+#include "AudioDriver.hpp"
+#include "EngineStore.hpp"
+
+namespace Ingen {
+
+
+CreatePatchEvent::CreatePatchEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& path, int poly)
+: QueuedEvent(engine, responder, timestamp),
+ _path(path),
+ _patch(NULL),
+ _parent(NULL),
+ _compiled_patch(NULL),
+ _poly(poly),
+ _error(NO_ERROR)
+{
+}
+
+
+void
+CreatePatchEvent::pre_process()
+{
+ if (!Path::is_valid(_path)) {
+ _error = INVALID_PATH;
+ QueuedEvent::pre_process();
+ return;
+ }
+
+ if (_path == "/" || _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_polyphony()))
+ poly = _poly;
+
+ _patch = new PatchImpl(_engine, path.name(), poly, _parent, _engine.audio_driver()->sample_rate(), _engine.audio_driver()->buffer_size(), _poly);
+
+ if (_parent != NULL) {
+ _parent->add_node(new PatchImpl::Nodes::Node(_patch));
+
+ if (_parent->enabled())
+ _compiled_patch = _parent->compile();
+ }
+
+ _patch->activate();
+
+ // Insert into EngineStore
+ //_patch->add_to_store(_engine.engine_store());
+ _engine.engine_store()->add(_patch);
+
+ QueuedEvent::pre_process();
+}
+
+
+void
+CreatePatchEvent::execute(ProcessContext& context)
+{
+ QueuedEvent::execute(context);
+
+ if (_patch != NULL) {
+ if (_parent == NULL) {
+ assert(_path == "/");
+ assert(_patch->parent_patch() == NULL);
+ _engine.audio_driver()->set_root_patch(_patch);
+ } else {
+ assert(_parent != NULL);
+ assert(_path != "/");
+
+ if (_parent->compiled_patch() != NULL)
+ _engine.maid()->push(_parent->compiled_patch());
+ _parent->compiled_patch(_compiled_patch);
+ }
+ }
+}
+
+
+void
+CreatePatchEvent::post_process()
+{
+ if (_responder.get()) {
+ if (_error == NO_ERROR) {
+
+ _responder->respond_ok();
+
+ // Don't send ports/nodes that have been added since prepare()
+ // (otherwise they would be sent twice)
+ _engine.broadcaster()->send_patch(_patch, false);
+
+ } else if (_error == INVALID_PATH) {
+ string msg = "Attempt to create patch with illegal path ";
+ msg.append(_path);
+ _responder->respond_error(msg);
+ } else if (_error == OBJECT_EXISTS) {
+ _responder->respond_ok();
+ /*string msg = "Unable to create patch: ";
+ msg.append(_path).append(" already exists.");
+ _responder->respond_error(msg);*/
+ } else if (_error == PARENT_NOT_FOUND) {
+ string msg = "Unable to create patch: Parent ";
+ msg.append(Path(_path).parent()).append(" not found.");
+ _responder->respond_error(msg);
+ } else if (_error == INVALID_POLY) {
+ string msg = "Unable to create patch ";
+ msg.append(_path).append(": ").append("Invalid polyphony respondered.");
+ _responder->respond_error(msg);
+ } else {
+ _responder->respond_error("Unable to load patch.");
+ }
+ }
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/events/CreatePatchEvent.hpp b/src/engine/events/CreatePatchEvent.hpp
new file mode 100644
index 00000000..733aba17
--- /dev/null
+++ b/src/engine/events/CreatePatchEvent.hpp
@@ -0,0 +1,64 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 CREATEPATCHEVENT_H
+#define CREATEPATCHEVENT_H
+
+#include <string>
+#include <raul/Path.hpp>
+#include "QueuedEvent.hpp"
+
+using std::string;
+
+namespace Raul { template<typename T> class Array; }
+template<typename T> class TreeNode;
+
+namespace Ingen {
+
+class PatchImpl;
+class CompiledPatch;
+
+
+/** Creates a new Patch.
+ *
+ * \ingroup engine
+ */
+class CreatePatchEvent : public QueuedEvent
+{
+public:
+ CreatePatchEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& path, int poly);
+
+ void pre_process();
+ void execute(ProcessContext& context);
+ void post_process();
+
+private:
+ enum ErrorType { NO_ERROR, OBJECT_EXISTS, PARENT_NOT_FOUND, INVALID_POLY, INVALID_PATH };
+
+ const std::string _path;
+ PatchImpl* _patch;
+ PatchImpl* _parent;
+ CompiledPatch* _compiled_patch;
+ int _poly;
+ ErrorType _error;
+};
+
+
+} // namespace Ingen
+
+
+#endif // CREATEPATCHEVENT_H
diff --git a/src/engine/events/CreatePortEvent.cpp b/src/engine/events/CreatePortEvent.cpp
new file mode 100644
index 00000000..e767f522
--- /dev/null
+++ b/src/engine/events/CreatePortEvent.cpp
@@ -0,0 +1,173 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 <raul/Array.hpp>
+#include <raul/List.hpp>
+#include <raul/Maid.hpp>
+#include "Responder.hpp"
+#include "CreatePortEvent.hpp"
+#include "PatchImpl.hpp"
+#include "PluginImpl.hpp"
+#include "Engine.hpp"
+#include "PatchImpl.hpp"
+#include "QueuedEventSource.hpp"
+#include "EngineStore.hpp"
+#include "ClientBroadcaster.hpp"
+#include "PortImpl.hpp"
+#include "AudioDriver.hpp"
+#include "MidiDriver.hpp"
+#include "OSCDriver.hpp"
+#include "DuplexPort.hpp"
+
+namespace Ingen {
+
+
+CreatePortEvent::CreatePortEvent(Engine& engine,
+ SharedPtr<Responder> responder,
+ SampleCount timestamp,
+ const string& path,
+ const string& type,
+ bool is_output,
+ QueuedEventSource* source)
+: QueuedEvent(engine, responder, timestamp, true, source),
+ _error(NO_ERROR),
+ _path(path),
+ _type(type),
+ _is_output(is_output),
+ _data_type(type),
+ _patch(NULL),
+ _patch_port(NULL),
+ _driver_port(NULL)
+{
+ /* This is blocking because of the two different sets of Patch ports, the array used in the
+ * audio thread (inherited from NodeBase), 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).
+ *
+ * FIXME: fix this using RCU
+ */
+
+ if (_data_type == DataType::UNKNOWN) {
+ cerr << "[CreatePortEvent] Unknown port type " << type << endl;
+ _error = UNKNOWN_TYPE;
+ }
+}
+
+
+void
+CreatePortEvent::pre_process()
+{
+ if (_error == UNKNOWN_TYPE || _engine.engine_store()->find_object(_path)) {
+ QueuedEvent::pre_process();
+ return;
+ }
+
+ // FIXME: this is just a mess :/
+
+ _patch = _engine.engine_store()->find_patch(_path.parent());
+
+ if (_patch != NULL) {
+ assert(_patch->path() == _path.parent());
+
+ size_t buffer_size = 1;
+ if (_type != "ingen:Float")
+ buffer_size = _engine.audio_driver()->buffer_size();
+
+ const uint32_t old_num_ports = _patch->num_ports();
+
+ _patch_port = _patch->create_port(_path.name(), _data_type, buffer_size, _is_output);
+
+ 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());
+ else
+ _ports_array = new Raul::Array<PortImpl*>(old_num_ports + 1, NULL);
+
+
+ _ports_array->at(_patch->num_ports()-1) = _patch_port;
+ //_patch_port->add_to_store(_engine.engine_store());
+ _engine.engine_store()->add(_patch_port);
+
+ if (!_patch->parent()) {
+ if (_type == "ingen:AudioPort")
+ _driver_port = _engine.audio_driver()->create_port(
+ dynamic_cast<DuplexPort*>(_patch_port));
+ else if (_type == "ingen:MIDIPort" || _type == "ingen:EventPort")
+ _driver_port = _engine.midi_driver()->create_port(
+ dynamic_cast<DuplexPort*>(_patch_port));
+ else if (_type == "ingen:OSCPort" && _engine.osc_driver())
+ _driver_port = _engine.osc_driver()->create_port(
+ dynamic_cast<DuplexPort*>(_patch_port));
+ }
+
+ assert(_ports_array->size() == _patch->num_ports());
+
+ }
+ }
+ QueuedEvent::pre_process();
+}
+
+
+void
+CreatePortEvent::execute(ProcessContext& context)
+{
+ QueuedEvent::execute(context);
+
+ if (_patch_port) {
+
+ _engine.maid()->push(_patch->external_ports());
+ //_patch->add_port(_port);
+
+ _patch->external_ports(_ports_array);
+ }
+
+ if (_driver_port) {
+ if (_type == "ingen:AudioPort")
+ _engine.audio_driver()->add_port(_driver_port);
+ else if (_type == "ingen:MIDIPort" || _type == "ingen:EventPort")
+ _engine.midi_driver()->add_port(_driver_port);
+ else if (_type == "ingen:OSCPort")
+ cerr << "OSC DRIVER PORT" << endl;
+ }
+
+ if (_source)
+ _source->unblock();
+}
+
+
+void
+CreatePortEvent::post_process()
+{
+ if (_error != NO_ERROR || !_patch_port) {
+ const string msg = string("Could not create port - ").append(_path);
+ _responder->respond_error(msg);
+ } else {
+ _responder->respond_ok();
+ _engine.broadcaster()->send_port(_patch_port);
+ }
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/events/CreatePortEvent.hpp b/src/engine/events/CreatePortEvent.hpp
new file mode 100644
index 00000000..5ddd8aa3
--- /dev/null
+++ b/src/engine/events/CreatePortEvent.hpp
@@ -0,0 +1,72 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 CREATEPORTEVENT_H
+#define CREATEPORTEVENT_H
+
+#include "QueuedEvent.hpp"
+#include <raul/Path.hpp>
+#include <raul/Array.hpp>
+#include "interface/DataType.hpp"
+#include <string>
+using std::string;
+
+template <typename T> class Array;
+
+namespace Ingen {
+
+class PatchImpl;
+class PortImpl;
+class DriverPort;
+
+
+/** An event to add a Port to a Patch.
+ *
+ * \ingroup engine
+ */
+class CreatePortEvent : public QueuedEvent
+{
+public:
+ CreatePortEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& path, const string& type, bool is_output, QueuedEventSource* source);
+
+ void pre_process();
+ void execute(ProcessContext& context);
+ void post_process();
+
+private:
+
+ enum ErrorType {
+ NO_ERROR,
+ UNKNOWN_TYPE
+ };
+
+ ErrorType _error;
+ Raul::Path _path;
+ string _type;
+ bool _is_output;
+ DataType _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;
+};
+
+
+} // namespace Ingen
+
+#endif // CREATEPORTEVENT_H
diff --git a/src/engine/events/DeactivateEvent.cpp b/src/engine/events/DeactivateEvent.cpp
new file mode 100644
index 00000000..a68419f0
--- /dev/null
+++ b/src/engine/events/DeactivateEvent.cpp
@@ -0,0 +1,54 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "DeactivateEvent.hpp"
+#include "Responder.hpp"
+#include "Engine.hpp"
+
+namespace Ingen {
+
+
+DeactivateEvent::DeactivateEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp)
+: QueuedEvent(engine, responder, timestamp)
+{
+}
+
+
+void
+DeactivateEvent::pre_process()
+{
+ QueuedEvent::pre_process();
+}
+
+
+void
+DeactivateEvent::execute(ProcessContext& context)
+{
+ QueuedEvent::execute(context);
+}
+
+
+void
+DeactivateEvent::post_process()
+{
+ _responder->respond_ok();
+ _engine.deactivate();
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/events/DeactivateEvent.hpp b/src/engine/events/DeactivateEvent.hpp
new file mode 100644
index 00000000..5a6750ba
--- /dev/null
+++ b/src/engine/events/DeactivateEvent.hpp
@@ -0,0 +1,43 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 DEACTIVATEEVENT_H
+#define DEACTIVATEEVENT_H
+
+#include "QueuedEvent.hpp"
+
+namespace Ingen {
+
+
+/** Deactivates the engine.
+ *
+ * \ingroup engine
+ */
+class DeactivateEvent : public QueuedEvent
+{
+public:
+ DeactivateEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp);
+
+ void pre_process();
+ void execute(ProcessContext& context);
+ void post_process();
+};
+
+
+} // namespace Ingen
+
+#endif // DEACTIVATEEVENT_H
diff --git a/src/engine/events/DestroyEvent.cpp b/src/engine/events/DestroyEvent.cpp
new file mode 100644
index 00000000..07770c0a
--- /dev/null
+++ b/src/engine/events/DestroyEvent.cpp
@@ -0,0 +1,201 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "DestroyEvent.hpp"
+#include "Responder.hpp"
+#include "Engine.hpp"
+#include "PatchImpl.hpp"
+#include "NodeBase.hpp"
+#include "PluginImpl.hpp"
+#include "AudioDriver.hpp"
+#include "MidiDriver.hpp"
+#include "DisconnectAllEvent.hpp"
+#include "ClientBroadcaster.hpp"
+#include "EngineStore.hpp"
+#include "QueuedEventSource.hpp"
+#include "PortImpl.hpp"
+
+namespace Ingen {
+
+
+DestroyEvent::DestroyEvent(Engine& engine, SharedPtr<Responder> responder, FrameTime time, QueuedEventSource* source, const string& path, bool block)
+ : QueuedEvent(engine, responder, time, source, source)
+ , _path(path)
+ , _store_iterator(engine.engine_store()->end())
+ , _driver_port(NULL)
+ , _patch_node_listnode(NULL)
+ , _patch_port_listnode(NULL)
+ , _ports_array(NULL)
+ , _compiled_patch(NULL)
+ , _disconnect_event(NULL)
+{
+ assert(_source);
+}
+
+
+DestroyEvent::~DestroyEvent()
+{
+ delete _disconnect_event;
+}
+
+
+void
+DestroyEvent::pre_process()
+{
+ _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 != NULL && _path != "/") {
+ assert(_node->parent_patch());
+ _patch_node_listnode = _node->parent_patch()->remove_node(_path.name());
+ if (_patch_node_listnode) {
+ assert(_patch_node_listnode->elem() == _node.get());
+
+ _disconnect_event = new DisconnectAllEvent(_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 destroyed
+ 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.name());
+ if (_patch_port_listnode) {
+ assert(_patch_port_listnode->elem() == _port.get());
+
+ _disconnect_event = new DisconnectAllEvent(_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
+DestroyEvent::execute(ProcessContext& context)
+{
+ QueuedEvent::execute(context);
+
+ if (_patch_node_listnode) {
+ assert(_node);
+
+ if (_disconnect_event)
+ _disconnect_event->execute(context);
+
+ if (_node->parent_patch()->compiled_patch())
+ _engine.maid()->push(_node->parent_patch()->compiled_patch());
+ _node->parent_patch()->compiled_patch(_compiled_patch);
+
+ } else if (_patch_port_listnode) {
+ assert(_port);
+
+ if (_disconnect_event)
+ _disconnect_event->execute(context);
+
+ if (_port->parent_patch()->compiled_patch())
+ _engine.maid()->push(_port->parent_patch()->compiled_patch());
+
+ _port->parent_patch()->compiled_patch(_compiled_patch);
+
+ if (_port->parent_patch()->external_ports())
+ _engine.maid()->push(_port->parent_patch()->external_ports());
+
+ _port->parent_patch()->external_ports(_ports_array);
+
+ if ( ! _port->parent_patch()->parent()) {
+ if (_port->type() == DataType::AUDIO)
+ _driver_port = _engine.audio_driver()->remove_port(_port->path());
+ else if (_port->type() == DataType::EVENT)
+ _driver_port = _engine.midi_driver()->remove_port(_port->path());
+ }
+ }
+
+ if (_source)
+ _source->unblock();
+}
+
+
+void
+DestroyEvent::post_process()
+{
+ if (!_node && !_port) {
+ if (_path == "/") {
+ _responder->respond_error("You can not destroy the root patch (/)");
+ } else {
+ string msg = string("Could not find object ") + _path + " to destroy";
+ _responder->respond_error(msg);
+ }
+ }
+
+ if (_patch_node_listnode) {
+ assert(_node);
+ _node->deactivate();
+ _responder->respond_ok();
+ _engine.broadcaster()->bundle_begin();
+ if (_disconnect_event)
+ _disconnect_event->post_process();
+ _engine.broadcaster()->send_destroyed(_path);
+ _engine.broadcaster()->bundle_end();
+ _engine.maid()->push(_patch_node_listnode);
+ } else if (_patch_port_listnode) {
+ assert(_port);
+ _responder->respond_ok();
+ _engine.broadcaster()->bundle_begin();
+ if (_disconnect_event)
+ _disconnect_event->post_process();
+ _engine.broadcaster()->send_destroyed(_path);
+ _engine.broadcaster()->bundle_end();
+ _engine.maid()->push(_patch_port_listnode);
+ } else {
+ _responder->respond_error("Unable to destroy object");
+ }
+
+ delete _driver_port;
+}
+
+
+} // namespace Ingen
diff --git a/src/engine/events/DestroyEvent.hpp b/src/engine/events/DestroyEvent.hpp
new file mode 100644
index 00000000..b24934f8
--- /dev/null
+++ b/src/engine/events/DestroyEvent.hpp
@@ -0,0 +1,77 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 DESTROYEVENT_H
+#define DESTROYEVENT_H
+
+#include <string>
+#include <raul/Path.hpp>
+#include "QueuedEvent.hpp"
+#include "EngineStore.hpp"
+#include "PatchImpl.hpp"
+
+using std::string;
+
+namespace Raul {
+ template<typename T> class Array;
+ template<typename T> class ListNode;
+}
+template<typename T> class TreeNode;
+
+namespace Ingen {
+
+class GraphObjectImpl;
+class NodeImpl;
+class PortImpl;
+class DriverPort;
+class DisconnectAllEvent;
+class CompiledPatch;
+
+
+/** An event to remove and delete a Node.
+ *
+ * \ingroup engine
+ */
+class DestroyEvent : public QueuedEvent
+{
+public:
+ DestroyEvent(Engine& engine, SharedPtr<Responder> responder, FrameTime timestamp, QueuedEventSource* source, const string& path, bool block = true);
+ ~DestroyEvent();
+
+ void pre_process();
+ void execute(ProcessContext& context);
+ void post_process();
+
+private:
+ Path _path;
+ EngineStore::iterator _store_iterator;
+ SharedPtr<NodeImpl> _node; ///< Non-NULL iff a node
+ SharedPtr<PortImpl> _port; ///< Non-NULL iff a port
+ 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
+ DisconnectAllEvent* _disconnect_event;
+
+ SharedPtr< Table<Path, SharedPtr<Shared::GraphObject> > > _removed_table;
+};
+
+
+} // namespace Ingen
+
+#endif // DESTROYEVENT_H
diff --git a/src/engine/events/DisablePortMonitoringEvent.cpp b/src/engine/events/DisablePortMonitoringEvent.cpp
new file mode 100644
index 00000000..cecc8dfd
--- /dev/null
+++ b/src/engine/events/DisablePortMonitoringEvent.cpp
@@ -0,0 +1,87 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "interface/ClientInterface.hpp"
+#include "events/DisablePortMonitoringEvent.hpp"
+#include "Responder.hpp"
+#include "Engine.hpp"
+#include "PortImpl.hpp"
+#include "EngineStore.hpp"
+#include "ClientBroadcaster.hpp"
+#include "AudioBuffer.hpp"
+
+using std::string;
+
+namespace Ingen {
+
+
+DisablePortMonitoringEvent::DisablePortMonitoringEvent(Engine& engine,
+ SharedPtr<Responder> responder,
+ SampleCount timestamp,
+ const std::string& port_path)
+: QueuedEvent(engine, responder, timestamp),
+ _port_path(port_path),
+ _port(NULL)
+{
+}
+
+
+void
+DisablePortMonitoringEvent::pre_process()
+{
+ _port = _engine.engine_store()->find_port(_port_path);
+
+ QueuedEvent::pre_process();
+}
+
+
+void
+DisablePortMonitoringEvent::execute(ProcessContext& context)
+{
+ QueuedEvent::execute(context);
+
+#if 0
+ assert(_time >= start && _time <= end);
+
+ if (_port != NULL && _port->type() == DataType::FLOAT)
+ _value = ((AudioBuffer*)_port->buffer(0))->value_at(0/*_time - start*/);
+ else
+ _port = NULL; // triggers error response
+#endif
+}
+
+
+void
+DisablePortMonitoringEvent::post_process()
+{
+#if 0
+ string msg;
+ if (!_port) {
+ _responder->respond_error("Unable to find port for get_value responder.");
+ } else if (_responder->client()) {
+ _responder->respond_ok();
+ _responder->client()->control_change(_port_path, _value);
+ } else {
+ _responder->respond_error("Unable to find client to send port value");
+ }
+#endif
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/events/DisablePortMonitoringEvent.hpp b/src/engine/events/DisablePortMonitoringEvent.hpp
new file mode 100644
index 00000000..7a8e23f7
--- /dev/null
+++ b/src/engine/events/DisablePortMonitoringEvent.hpp
@@ -0,0 +1,58 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 DISABLEPORTNOTIFICATIONEVENT_H
+#define DISABLEPORTNOTIFICATIONEVENT_H
+
+#include <string>
+#include "QueuedEvent.hpp"
+#include "types.hpp"
+
+using std::string;
+
+namespace Ingen {
+
+class PortImpl;
+namespace Shared { class ClientInterface; }
+using Shared::ClientInterface;
+
+
+/** Disable sending of dynamic value change notifications for a port.
+ *
+ * \ingroup engine
+ */
+class DisablePortMonitoringEvent : public QueuedEvent
+{
+public:
+ DisablePortMonitoringEvent(Engine& engine,
+ SharedPtr<Responder> responder,
+ SampleCount timestamp,
+ const std::string& port_path);
+
+ void pre_process();
+ void execute(ProcessContext& context);
+ void post_process();
+
+private:
+ const std::string _port_path;
+ Port* _port;
+};
+
+
+} // namespace Ingen
+
+#endif // DISABLEPORTNOTIFICATIONEVENT_H
diff --git a/src/engine/events/DisconnectAllEvent.cpp b/src/engine/events/DisconnectAllEvent.cpp
new file mode 100644
index 00000000..77b1b1b3
--- /dev/null
+++ b/src/engine/events/DisconnectAllEvent.cpp
@@ -0,0 +1,183 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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/List.hpp>
+#include <raul/Maid.hpp>
+#include <raul/Path.hpp>
+#include "ClientBroadcaster.hpp"
+#include "ConnectionImpl.hpp"
+#include "DisconnectAllEvent.hpp"
+#include "DisconnectionEvent.hpp"
+#include "Engine.hpp"
+#include "InputPort.hpp"
+#include "NodeImpl.hpp"
+#include "EngineStore.hpp"
+#include "OutputPort.hpp"
+#include "PatchImpl.hpp"
+#include "PortImpl.hpp"
+#include "Responder.hpp"
+#include "util.hpp"
+
+namespace Ingen {
+
+
+DisconnectAllEvent::DisconnectAllEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& parent_path, const string& node_path)
+ : QueuedEvent(engine, responder, timestamp)
+ , _parent_path(parent_path)
+ , _path(node_path)
+ , _parent(NULL)
+ , _node(NULL)
+ , _port(NULL)
+ , _lookup(true)
+ , _error(NO_ERROR)
+{
+}
+
+
+/** Internal version for use by other events.
+ */
+DisconnectAllEvent::DisconnectAllEvent(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))
+ , _lookup(false)
+ , _error(NO_ERROR)
+{
+}
+
+
+DisconnectAllEvent::~DisconnectAllEvent()
+{
+ for (Raul::List<DisconnectionEvent*>::iterator i = _disconnection_events.begin(); i != _disconnection_events.end(); ++i)
+ delete (*i);
+}
+
+
+void
+DisconnectAllEvent::pre_process()
+{
+ if (_lookup) {
+ _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));
+ }
+
+ if (_node) {
+ for (PatchImpl::Connections::const_iterator i = _parent->connections().begin();
+ i != _parent->connections().end(); ++i) {
+ ConnectionImpl* c = (ConnectionImpl*)i->get();
+ if ((c->src_port()->parent_node() == _node || c->dst_port()->parent_node() == _node)
+ && !c->pending_disconnection()) {
+ DisconnectionEvent* ev = new DisconnectionEvent(_engine,
+ SharedPtr<Responder>(new Responder()), _time, c->src_port(), c->dst_port());
+ ev->pre_process();
+ _disconnection_events.push_back(new Raul::List<DisconnectionEvent*>::Node(ev));
+ c->pending_disconnection(true);
+ }
+ }
+ } else { // _port
+ for (PatchImpl::Connections::const_iterator i = _parent->connections().begin();
+ i != _parent->connections().end(); ++i) {
+ ConnectionImpl* c = (ConnectionImpl*)i->get();
+ if ((c->src_port() == _port || c->dst_port() == _port) && !c->pending_disconnection()) {
+ DisconnectionEvent* ev = new DisconnectionEvent(_engine,
+ SharedPtr<Responder>(new Responder()), _time, c->src_port(), c->dst_port());
+ ev->pre_process();
+ _disconnection_events.push_back(new Raul::List<DisconnectionEvent*>::Node(ev));
+ c->pending_disconnection(true);
+ }
+ }
+ }
+
+ QueuedEvent::pre_process();
+}
+
+
+void
+DisconnectAllEvent::execute(ProcessContext& context)
+{
+ QueuedEvent::execute(context);
+
+ if (_error == NO_ERROR) {
+ for (Raul::List<DisconnectionEvent*>::iterator i = _disconnection_events.begin(); i != _disconnection_events.end(); ++i)
+ (*i)->execute(context);
+ }
+}
+
+
+void
+DisconnectAllEvent::post_process()
+{
+ if (_error == NO_ERROR) {
+ if (_responder)
+ _responder->respond_ok();
+ for (Raul::List<DisconnectionEvent*>::iterator i = _disconnection_events.begin();
+ i != _disconnection_events.end(); ++i)
+ (*i)->post_process();
+ } else {
+ if (_responder) {
+ boost::format fmt("Unable to disconnect %1% (%2%)");
+ fmt % _path;
+ switch (_error) {
+ case INVALID_PARENT_PATH:
+ fmt % string("Invalid parent path: ").append(_parent_path);
+ break;
+ case PARENT_NOT_FOUND:
+ fmt % string("Unable to find parent: ").append(_parent_path);
+ break;
+ case OBJECT_NOT_FOUND:
+ fmt % string("Unable to find object");
+ default:
+ break;
+ }
+ _responder->respond_error(fmt.str());
+ }
+ }
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/events/DisconnectAllEvent.hpp b/src/engine/events/DisconnectAllEvent.hpp
new file mode 100644
index 00000000..6b75e6df
--- /dev/null
+++ b/src/engine/events/DisconnectAllEvent.hpp
@@ -0,0 +1,79 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 DISCONNECTNODEEVENT_H
+#define DISCONNECTNODEEVENT_H
+
+#include <string>
+#include <raul/List.hpp>
+#include <raul/Path.hpp>
+#include "QueuedEvent.hpp"
+
+using std::string;
+
+namespace Ingen {
+
+class DisconnectionEvent;
+class PatchImpl;
+class NodeImpl;
+class Connection;
+class PortImpl;
+class InputPort;
+class OutputPort;
+
+
+/** An event to disconnect all connections to a Node.
+ *
+ * \ingroup engine
+ */
+class DisconnectAllEvent : public QueuedEvent
+{
+public:
+ DisconnectAllEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& parent_path, const string& node_path);
+ DisconnectAllEvent(Engine& engine, PatchImpl* parent, GraphObjectImpl* object);
+ ~DisconnectAllEvent();
+
+ void pre_process();
+ void execute(ProcessContext& context);
+ void post_process();
+
+private:
+ enum ErrorType {
+ NO_ERROR,
+ INVALID_PARENT_PATH,
+ PARENT_NOT_FOUND,
+ OBJECT_NOT_FOUND,
+ };
+
+ Raul::Path _parent_path;
+ Raul::Path _path;
+ PatchImpl* _parent;
+ NodeImpl* _node;
+ PortImpl* _port;
+ Raul::List<DisconnectionEvent*> _disconnection_events;
+
+ bool _lookup;
+ bool _disconnect_parent;
+
+ ErrorType _error;
+};
+
+
+} // namespace Ingen
+
+
+#endif // DISCONNECTNODEEVENT_H
diff --git a/src/engine/events/DisconnectionEvent.cpp b/src/engine/events/DisconnectionEvent.cpp
new file mode 100644
index 00000000..86ad8b4e
--- /dev/null
+++ b/src/engine/events/DisconnectionEvent.cpp
@@ -0,0 +1,213 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "DisconnectionEvent.hpp"
+#include <string>
+#include <raul/Maid.hpp>
+#include <raul/Path.hpp>
+#include "Responder.hpp"
+#include "Engine.hpp"
+#include "ConnectionImpl.hpp"
+#include "InputPort.hpp"
+#include "OutputPort.hpp"
+#include "PatchImpl.hpp"
+#include "ClientBroadcaster.hpp"
+#include "PortImpl.hpp"
+#include "EngineStore.hpp"
+
+using std::string;
+namespace Ingen {
+
+
+//// DisconnectionEvent ////
+
+
+DisconnectionEvent::DisconnectionEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& src_port_path, const string& dst_port_path)
+: QueuedEvent(engine, responder, timestamp),
+ _src_port_path(src_port_path),
+ _dst_port_path(dst_port_path),
+ _patch(NULL),
+ _src_port(NULL),
+ _dst_port(NULL),
+ _lookup(true),
+ _patch_connection(NULL),
+ _compiled_patch(NULL),
+ _error(NO_ERROR)
+{
+}
+
+
+DisconnectionEvent::DisconnectionEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, PortImpl* const src_port, PortImpl* const dst_port)
+: QueuedEvent(engine, responder, timestamp),
+ _src_port_path(src_port->path()),
+ _dst_port_path(dst_port->path()),
+ _patch(src_port->parent_node()->parent_patch()),
+ _src_port(src_port),
+ _dst_port(dst_port),
+ _lookup(false),
+ _compiled_patch(NULL),
+ _error(NO_ERROR)
+{
+ // FIXME: These break for patch ports.. is that ok?
+ /*assert(src_port->is_output());
+ assert(dst_port->is_input());
+ assert(src_port->type() == dst_port->type());
+ assert(src_port->parent_node()->parent_patch()
+ == dst_port->parent_node()->parent_patch()); */
+}
+
+
+void
+DisconnectionEvent::pre_process()
+{
+ if (_lookup) {
+ 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;
+ }
+
+ _dst_input_port = dynamic_cast<InputPort*>(_dst_port);
+ _src_output_port = dynamic_cast<OutputPort*>(_src_port);
+ assert(_src_output_port);
+ assert(_dst_input_port);
+
+ 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 (_dst_input_port->is_connected_to(_src_output_port)) {
+ if (!_patch->has_connection(_src_output_port, _dst_input_port)) {
+ _error = NOT_CONNECTED;
+ QueuedEvent::pre_process();
+ return;
+ }
+
+ if (src_node == NULL || dst_node == NULL) {
+ _error = PARENTS_NOT_FOUND;
+ QueuedEvent::pre_process();
+ return;
+ }
+
+ 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;
+ }
+
+ _patch_connection = _patch->remove_connection(_src_port, _dst_port);
+
+ if (_patch->enabled())
+ _compiled_patch = _patch->compile();
+
+ QueuedEvent::pre_process();
+}
+
+
+void
+DisconnectionEvent::execute(ProcessContext& context)
+{
+ QueuedEvent::execute(context);
+
+ if (_error == NO_ERROR) {
+ InputPort::Connections::Node* const port_connection
+ = _dst_input_port->remove_connection(_src_output_port);
+
+ if (port_connection != NULL) {
+ assert(_patch_connection);
+
+ if (port_connection->elem() != _patch_connection->elem()) {
+ cerr << "ERROR: Corrupt connections:" << endl;
+ cerr << "\t" << port_connection->elem() << ": "
+ << port_connection->elem()->src_port_path()
+ << " -> " << port_connection->elem()->dst_port_path() << endl
+ << "!=" << endl
+ << "\t" << _patch_connection->elem() << ": "
+ << _patch_connection->elem()->src_port_path()
+ << " -> " << _patch_connection->elem()->dst_port_path() << endl;
+ }
+ assert(port_connection->elem() == _patch_connection->elem());
+
+ // Destroy list node, which will drop reference to connection itself
+ _engine.maid()->push(port_connection);
+ _engine.maid()->push(_patch_connection);
+
+ if (_patch->compiled_patch() != NULL)
+ _engine.maid()->push(_patch->compiled_patch());
+ _patch->compiled_patch(_compiled_patch);
+ } else {
+ _error = CONNECTION_NOT_FOUND;
+ }
+ }
+}
+
+
+void
+DisconnectionEvent::post_process()
+{
+ if (_error == NO_ERROR) {
+ _responder->respond_ok();
+ _engine.broadcaster()->send_disconnection(_src_port->path(), _dst_port->path());
+ } else {
+ // FIXME: better error messages
+ string msg = "Unable to disconnect ";
+ msg.append(_src_port_path + " -> " + _dst_port_path);
+ cerr << "DISCONNECTION ERROR " << (unsigned)_error << endl;
+ _responder->respond_error(msg);
+ }
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/events/DisconnectionEvent.hpp b/src/engine/events/DisconnectionEvent.hpp
new file mode 100644
index 00000000..700febeb
--- /dev/null
+++ b/src/engine/events/DisconnectionEvent.hpp
@@ -0,0 +1,90 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 DISCONNECTIONEVENT_H
+#define DISCONNECTIONEVENT_H
+
+#include <string>
+#include <raul/Path.hpp>
+#include "QueuedEvent.hpp"
+#include "types.hpp"
+#include "PatchImpl.hpp"
+using std::string;
+
+namespace Raul {
+ template <typename T> class ListNode;
+ template <typename T> class Array;
+}
+
+namespace Ingen {
+
+class NodeImpl;
+class ConnectionImpl;
+class MidiMessage;
+class PortImpl;
+class InputPort;
+class OutputPort;
+class CompiledPatch;
+
+
+/** Make a Connection between two Ports.
+ *
+ * \ingroup engine
+ */
+class DisconnectionEvent : public QueuedEvent
+{
+public:
+ DisconnectionEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& src_port_path, const string& dst_port_path);
+ DisconnectionEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, PortImpl* const src_port, PortImpl* const dst_port);
+
+ void pre_process();
+ void execute(ProcessContext& context);
+ void post_process();
+
+private:
+
+ enum ErrorType {
+ NO_ERROR,
+ PARENT_PATCH_DIFFERENT,
+ PORT_NOT_FOUND,
+ TYPE_MISMATCH,
+ NOT_CONNECTED,
+ PARENTS_NOT_FOUND,
+ CONNECTION_NOT_FOUND
+ };
+
+ Raul::Path _src_port_path;
+ Raul::Path _dst_port_path;
+
+ PatchImpl* _patch;
+ PortImpl* _src_port;
+ PortImpl* _dst_port;
+ OutputPort* _src_output_port;
+ InputPort* _dst_input_port;
+
+ bool _lookup;
+
+ PatchImpl::Connections::Node* _patch_connection;
+ CompiledPatch* _compiled_patch; ///< New process order for Patch
+
+ ErrorType _error;
+};
+
+
+} // namespace Ingen
+
+#endif // DISCONNECTIONEVENT_H
diff --git a/src/engine/events/EnablePatchEvent.cpp b/src/engine/events/EnablePatchEvent.cpp
new file mode 100644
index 00000000..04759cea
--- /dev/null
+++ b/src/engine/events/EnablePatchEvent.cpp
@@ -0,0 +1,86 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "EnablePatchEvent.hpp"
+#include "Responder.hpp"
+#include "Engine.hpp"
+#include "PatchImpl.hpp"
+#include "util.hpp"
+#include "ClientBroadcaster.hpp"
+#include "EngineStore.hpp"
+
+namespace Ingen {
+
+
+EnablePatchEvent::EnablePatchEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& patch_path, bool enable)
+: QueuedEvent(engine, responder, timestamp),
+ _patch_path(patch_path),
+ _patch(NULL),
+ _compiled_patch(NULL),
+ _enable(enable)
+{
+}
+
+
+void
+EnablePatchEvent::pre_process()
+{
+ _patch = _engine.engine_store()->find_patch(_patch_path);
+
+ if (_enable && _patch) {
+ /* Any event that requires a new process order will set the patch's
+ * compiled_patch to NULL if it is executed when the patch is not
+ * active. So, if the CP is NULL, calculate it here */
+ if (_patch->compiled_patch() == NULL)
+ _compiled_patch = _patch->compile();
+ }
+
+ QueuedEvent::pre_process();
+}
+
+
+void
+EnablePatchEvent::execute(ProcessContext& context)
+{
+ QueuedEvent::execute(context);
+
+ if (_patch != NULL) {
+ if (_enable)
+ _patch->enable();
+ else
+ _patch->disable();
+
+ if (_enable && _patch->compiled_patch() == NULL)
+ _patch->compiled_patch(_compiled_patch);
+ }
+}
+
+
+void
+EnablePatchEvent::post_process()
+{
+ if (_patch != NULL) {
+ _responder->respond_ok();
+ _engine.broadcaster()->send_property_change(_patch_path, "ingen:enabled", (bool)_enable);
+ } else {
+ _responder->respond_error(string("Patch ") + _patch_path + " not found");
+ }
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/events/EnablePatchEvent.hpp b/src/engine/events/EnablePatchEvent.hpp
new file mode 100644
index 00000000..dad1803a
--- /dev/null
+++ b/src/engine/events/EnablePatchEvent.hpp
@@ -0,0 +1,63 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 ENABLEPATCHEVENT_H
+#define ENABLEPATCHEVENT_H
+
+#include <string>
+#include "QueuedEvent.hpp"
+
+using std::string;
+
+namespace Raul { template <typename T> class Array; }
+
+namespace Ingen {
+
+class PatchImpl;
+class NodeImpl;
+class CompiledPatch;
+
+
+/** Enables a patch's DSP processing.
+ *
+ * \ingroup engine
+ */
+class EnablePatchEvent : public QueuedEvent
+{
+public:
+ EnablePatchEvent(Engine& engine,
+ SharedPtr<Responder> responder,
+ SampleCount timestamp,
+ const string& patch_path,
+ bool enable);
+
+ void pre_process();
+ void execute(ProcessContext& context);
+ void post_process();
+
+private:
+ string _patch_path;
+ PatchImpl* _patch;
+ CompiledPatch* _compiled_patch; // Patch's new process order
+ bool _enable;
+};
+
+
+} // namespace Ingen
+
+
+#endif // ENABLEPATCHEVENT_H
diff --git a/src/engine/events/LoadPluginsEvent.cpp b/src/engine/events/LoadPluginsEvent.cpp
new file mode 100644
index 00000000..df5ff5d9
--- /dev/null
+++ b/src/engine/events/LoadPluginsEvent.cpp
@@ -0,0 +1,56 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "LoadPluginsEvent.hpp"
+#include "Responder.hpp"
+#include "Engine.hpp"
+#include "NodeFactory.hpp"
+#include "ClientBroadcaster.hpp"
+#include "QueuedEventSource.hpp"
+
+#include <iostream>
+using std::cerr;
+
+namespace Ingen {
+
+
+LoadPluginsEvent::LoadPluginsEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, QueuedEventSource* source)
+: QueuedEvent(engine, responder, timestamp, true, source)
+{
+ /* FIXME: Not sure why this has to be blocking, but it fixes some nasty bugs.. */
+}
+
+void
+LoadPluginsEvent::pre_process()
+{
+ _engine.node_factory()->load_plugins();
+
+ QueuedEvent::pre_process();
+}
+
+void
+LoadPluginsEvent::post_process()
+{
+ if (_source)
+ _source->unblock();
+
+ _responder->respond_ok();
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/events/LoadPluginsEvent.hpp b/src/engine/events/LoadPluginsEvent.hpp
new file mode 100644
index 00000000..cd9a2884
--- /dev/null
+++ b/src/engine/events/LoadPluginsEvent.hpp
@@ -0,0 +1,46 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 LOADPLUGINSEVENT_H
+#define LOADPLUGINSEVENT_H
+
+#include <list>
+#include "QueuedEvent.hpp"
+
+namespace Ingen {
+
+
+/** Loads all plugins into the internal plugin database (in NodeFactory).
+ *
+ * \ingroup engine
+ */
+class LoadPluginsEvent : public QueuedEvent
+{
+public:
+ LoadPluginsEvent(Engine& engine,
+ SharedPtr<Responder> responder,
+ SampleCount timestamp,
+ QueuedEventSource* source);
+
+ void pre_process();
+ void post_process();
+};
+
+
+} // namespace Ingen
+
+#endif // LOADPLUGINSEVENT_H
diff --git a/src/engine/events/Makefile.am b/src/engine/events/Makefile.am
new file mode 100644
index 00000000..9a8cfbd5
--- /dev/null
+++ b/src/engine/events/Makefile.am
@@ -0,0 +1,63 @@
+MAINTAINERCLEANFILES = Makefile.in
+
+EXTRA_DIST = \
+ AllNotesOffEvent.cpp \
+ AllNotesOffEvent.hpp \
+ ClearPatchEvent.cpp \
+ ClearPatchEvent.hpp \
+ ConnectionEvent.cpp \
+ ConnectionEvent.hpp \
+ CreateNodeEvent.cpp \
+ CreateNodeEvent.hpp \
+ CreatePatchEvent.cpp \
+ CreatePatchEvent.hpp \
+ CreatePortEvent.cpp \
+ CreatePortEvent.hpp \
+ DeactivateEvent.cpp \
+ DeactivateEvent.hpp \
+ DestroyEvent.cpp \
+ DestroyEvent.hpp \
+ DisconnectAllEvent.cpp \
+ DisconnectAllEvent.hpp \
+ DisconnectionEvent.cpp \
+ DisconnectionEvent.hpp \
+ EnablePatchEvent.cpp \
+ EnablePatchEvent.hpp \
+ LoadPluginsEvent.cpp \
+ LoadPluginsEvent.hpp \
+ MidiLearnEvent.cpp \
+ MidiLearnEvent.hpp \
+ NoteEvent.cpp \
+ NoteEvent.hpp \
+ PingQueuedEvent.hpp \
+ RegisterClientEvent.cpp \
+ RegisterClientEvent.hpp \
+ RenameEvent.cpp \
+ RenameEvent.hpp \
+ RequestAllObjectsEvent.cpp \
+ RequestAllObjectsEvent.hpp \
+ RequestMetadataEvent.cpp \
+ RequestMetadataEvent.hpp \
+ RequestObjectEvent.cpp \
+ RequestObjectEvent.hpp \
+ RequestPluginEvent.cpp \
+ RequestPluginEvent.hpp \
+ RequestPluginsEvent.cpp \
+ RequestPluginsEvent.hpp \
+ RequestPortValueEvent.cpp \
+ RequestPortValueEvent.hpp \
+ SendPortActivityEvent.cpp \
+ SendPortActivityEvent.hpp \
+ SendPortValueEvent.cpp \
+ SendPortValueEvent.hpp \
+ SetMetadataEvent.cpp \
+ SetMetadataEvent.hpp \
+ SetPolyphonicEvent.cpp \
+ SetPolyphonicEvent.hpp \
+ SetPolyphonyEvent.cpp \
+ SetPolyphonyEvent.hpp \
+ SetPortValueEvent.cpp \
+ SetPortValueEvent.hpp \
+ UnregisterClientEvent.cpp \
+ UnregisterClientEvent.hpp
+
diff --git a/src/engine/events/MidiLearnEvent.cpp b/src/engine/events/MidiLearnEvent.cpp
new file mode 100644
index 00000000..2f37f30d
--- /dev/null
+++ b/src/engine/events/MidiLearnEvent.cpp
@@ -0,0 +1,89 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "MidiLearnEvent.hpp"
+#include "Responder.hpp"
+#include "Engine.hpp"
+#include "EngineStore.hpp"
+#include "NodeImpl.hpp"
+#include "MidiControlNode.hpp"
+#include "ClientBroadcaster.hpp"
+#include "PluginImpl.hpp"
+
+namespace Ingen {
+
+
+// MidiLearnResponseEvent
+
+void
+MidiLearnResponseEvent::post_process()
+{
+ _engine.broadcaster()->send_port_value(_port_path, _value);
+}
+
+
+
+// MidiLearnEvent
+
+MidiLearnEvent::MidiLearnEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& node_path)
+: QueuedEvent(engine, responder, timestamp),
+ _node_path(node_path),
+ _node(NULL),
+ _response_event(NULL)
+{
+}
+
+
+void
+MidiLearnEvent::pre_process()
+{
+ _node = _engine.engine_store()->find_node(_node_path);
+ _response_event = new MidiLearnResponseEvent(_engine, _node_path + "/Controller_Number", _time);
+
+ QueuedEvent::pre_process();
+}
+
+
+void
+MidiLearnEvent::execute(ProcessContext& context)
+{
+ QueuedEvent::execute(context);
+
+ // FIXME: this isn't very good at all.
+ if (_node != NULL && _node->plugin_impl()->type() == Plugin::Internal
+ && _node->plugin_impl()->uri() == "ingen:control_node") {
+ ((MidiControlNode*)_node)->learn(_response_event);
+ }
+}
+
+
+void
+MidiLearnEvent::post_process()
+{
+ if (_node != NULL) {
+ _responder->respond_ok();
+ } else {
+ string msg = "Did not find node '";
+ msg.append(_node_path).append("' for MIDI learn.");
+ _responder->respond_error(msg);
+ }
+}
+
+
+} // namespace Ingen
+
+
diff --git a/src/engine/events/MidiLearnEvent.hpp b/src/engine/events/MidiLearnEvent.hpp
new file mode 100644
index 00000000..c0fc4a17
--- /dev/null
+++ b/src/engine/events/MidiLearnEvent.hpp
@@ -0,0 +1,85 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 MIDILEARNEVENT_H
+#define MIDILEARNEVENT_H
+
+#include "QueuedEvent.hpp"
+#include "MidiControlNode.hpp"
+#include "types.hpp"
+#include <string>
+using std::string;
+
+namespace Ingen {
+
+class NodeImpl;
+class ControlChangeEvent;
+
+
+/** Response event for a MIDI learn.
+ *
+ * This is a trivial event that sends a control change in it's post_process
+ * method (used by MidiLearnEvent to notify clients when the learn happens)
+ */
+class MidiLearnResponseEvent : public Event
+{
+public:
+ MidiLearnResponseEvent(Engine& engine, const string& port_path, SampleCount timestamp)
+ : Event(engine, SharedPtr<Responder>(), timestamp),
+ _port_path(port_path),
+ _value(0.0f)
+ {}
+
+ void set_value(Sample val) { _value = val; }
+ void post_process();
+
+private:
+ string _port_path;
+ Sample _value;
+};
+
+
+
+/** A MIDI learn event.
+ *
+ * This creates a MidiLearnResponseEvent and passes it to the learning node, which
+ * will push it to the post-processor once the learn happens in order to reply
+ * to the client with the new port (learned controller) value.
+ *
+ * \ingroup engine
+ */
+class MidiLearnEvent : public QueuedEvent
+{
+public:
+ MidiLearnEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& node_path);
+
+ void pre_process();
+ void execute(ProcessContext& context);
+ void post_process();
+
+private:
+ const string _node_path;
+ NodeImpl* _node;
+
+ /// Event to respond with when learned
+ MidiLearnResponseEvent* _response_event;
+};
+
+
+} // namespace Ingen
+
+#endif // MIDILEARNEVENT_H
diff --git a/src/engine/events/NoteEvent.cpp b/src/engine/events/NoteEvent.cpp
new file mode 100644
index 00000000..58842ae6
--- /dev/null
+++ b/src/engine/events/NoteEvent.cpp
@@ -0,0 +1,102 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "NoteEvent.hpp"
+#include "Responder.hpp"
+#include "Engine.hpp"
+#include "EngineStore.hpp"
+#include "NodeImpl.hpp"
+#include "MidiNoteNode.hpp"
+#include "MidiTriggerNode.hpp"
+#include "PluginImpl.hpp"
+#include "InternalPlugin.hpp"
+#include "ProcessContext.hpp"
+
+namespace Ingen {
+
+
+/** Note on with Patch explicitly passed.
+ *
+ * Used to be triggered by MIDI. Not used anymore.
+ */
+NoteEvent::NoteEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, NodeImpl* node, bool on, uchar note_num, uchar velocity)
+: Event(engine, responder, timestamp),
+ _node(node),
+ _on(on),
+ _note_num(note_num),
+ _velocity(velocity)
+{
+}
+
+
+/** Note on with Node lookup.
+ *
+ * Triggered by OSC.
+ */
+NoteEvent::NoteEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& node_path, bool on, uchar note_num, uchar velocity)
+: Event(engine, responder, timestamp),
+ _node(NULL),
+ _node_path(node_path),
+ _on(on),
+ _note_num(note_num),
+ _velocity(velocity)
+{
+}
+
+
+void
+NoteEvent::execute(ProcessContext& context)
+{
+ Event::execute(context);
+ assert(_time >= context.start() && _time <= context.end());
+
+ // Lookup if neccessary
+ if (!_node)
+ _node = _engine.engine_store()->find_node(_node_path);
+
+ // FIXME: barf
+
+ if (_node != NULL && _node->plugin()->type() == Plugin::Internal) {
+ if (_on) {
+ if (_node->plugin_impl()->uri() == NS_INGEN "note_node")
+ ((MidiNoteNode*)_node)->note_on(context, _note_num, _velocity, _time);
+ else if (_node->plugin_impl()->uri() == NS_INGEN "trigger_node")
+ ((MidiTriggerNode*)_node)->note_on(context, _note_num, _velocity, _time);
+ } else {
+ if (_node->plugin_impl()->uri() == NS_INGEN "note_node")
+ ((MidiNoteNode*)_node)->note_off(context, _note_num, _time);
+ else if (_node->plugin_impl()->uri() == NS_INGEN "trigger_node")
+ ((MidiTriggerNode*)_node)->note_off(context, _note_num, _time);
+ }
+ }
+}
+
+
+void
+NoteEvent::post_process()
+{
+ if (_responder) {
+ if (_node)
+ _responder->respond_ok();
+ else
+ _responder->respond_error("Did not find node for note_on");
+ }
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/events/NoteEvent.hpp b/src/engine/events/NoteEvent.hpp
new file mode 100644
index 00000000..31ae9d27
--- /dev/null
+++ b/src/engine/events/NoteEvent.hpp
@@ -0,0 +1,68 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 NOTEEVENT_H
+#define NOTEEVENT_H
+
+#include "Event.hpp"
+#include "types.hpp"
+#include <string>
+using std::string;
+
+namespace Ingen {
+
+class NodeImpl;
+
+
+/** A note on event.
+ *
+ * \ingroup engine
+ */
+class NoteEvent : public Event
+{
+public:
+ NoteEvent(Engine& engine,
+ SharedPtr<Responder> responder,
+ SampleCount timestamp,
+ NodeImpl* node,
+ bool on,
+ uchar note_num,
+ uchar velocity);
+
+ NoteEvent(Engine& engine,
+ SharedPtr<Responder> responder,
+ SampleCount timestamp,
+ const string& node_path,
+ bool on,
+ uchar note_num,
+ uchar velocity);
+
+ void execute(ProcessContext& context);
+ void post_process();
+
+private:
+ NodeImpl* _node;
+ const string _node_path;
+ bool _on;
+ uchar _note_num;
+ uchar _velocity;
+};
+
+
+} // namespace Ingen
+
+#endif // NOTEEVENT_H
diff --git a/src/engine/events/PingQueuedEvent.hpp b/src/engine/events/PingQueuedEvent.hpp
new file mode 100644
index 00000000..08897bfe
--- /dev/null
+++ b/src/engine/events/PingQueuedEvent.hpp
@@ -0,0 +1,48 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 PINGQUEUEDEVENT_H
+#define PINGQUEUEDEVENT_H
+
+#include "QueuedEvent.hpp"
+#include "types.hpp"
+#include "Responder.hpp"
+
+namespace Ingen {
+
+class PortImpl;
+
+
+/** A ping that travels through the pre-processed event queue before responding
+ * (useful for the order guarantee).
+ *
+ * \ingroup engine
+ */
+class PingQueuedEvent : public QueuedEvent
+{
+public:
+ PingQueuedEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp)
+ : QueuedEvent(engine, responder, timestamp)
+ {}
+
+ void post_process() { _responder->respond_ok(); }
+};
+
+
+} // namespace Ingen
+
+#endif // PINGQUEUEDEVENT_H
diff --git a/src/engine/events/RegisterClientEvent.cpp b/src/engine/events/RegisterClientEvent.cpp
new file mode 100644
index 00000000..a8f68e21
--- /dev/null
+++ b/src/engine/events/RegisterClientEvent.cpp
@@ -0,0 +1,55 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "Responder.hpp"
+#include "RegisterClientEvent.hpp"
+#include "Engine.hpp"
+#include "ClientBroadcaster.hpp"
+
+namespace Ingen {
+
+
+RegisterClientEvent::RegisterClientEvent(Engine& engine,
+ SharedPtr<Responder> responder,
+ SampleCount timestamp,
+ const string& uri,
+ ClientInterface* client)
+ : QueuedEvent(engine, responder, timestamp)
+ , _uri(uri)
+ , _client(client)
+{
+}
+
+
+void
+RegisterClientEvent::pre_process()
+{
+ _engine.broadcaster()->register_client(_uri, _client);
+
+ QueuedEvent::pre_process();
+}
+
+
+void
+RegisterClientEvent::post_process()
+{
+ _responder->respond_ok();
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/events/RegisterClientEvent.hpp b/src/engine/events/RegisterClientEvent.hpp
new file mode 100644
index 00000000..9e12b5ba
--- /dev/null
+++ b/src/engine/events/RegisterClientEvent.hpp
@@ -0,0 +1,55 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 REGISTERCLIENTEVENT_H
+#define REGISTERCLIENTEVENT_H
+
+#include "QueuedEvent.hpp"
+#include "interface/ClientInterface.hpp"
+#include <string>
+using std::string;
+using Ingen::Shared::ClientInterface;
+using Ingen::Responder;
+
+namespace Ingen {
+
+
+/** Registers a new client with the OSC system, so it can receive updates.
+ *
+ * \ingroup engine
+ */
+class RegisterClientEvent : public QueuedEvent
+{
+public:
+ RegisterClientEvent(Engine& engine,
+ SharedPtr<Responder> responder,
+ SampleCount timestamp,
+ const string& uri,
+ ClientInterface* client);
+
+ void pre_process();
+ void post_process();
+
+private:
+ string _uri;
+ ClientInterface* _client;
+};
+
+
+} // namespace Ingen
+
+#endif // REGISTERCLIENTEVENT_H
diff --git a/src/engine/events/RenameEvent.cpp b/src/engine/events/RenameEvent.cpp
new file mode 100644
index 00000000..164676aa
--- /dev/null
+++ b/src/engine/events/RenameEvent.cpp
@@ -0,0 +1,152 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "ClientBroadcaster.hpp"
+#include "Engine.hpp"
+#include "NodeImpl.hpp"
+#include "EngineStore.hpp"
+#include "PatchImpl.hpp"
+#include "RenameEvent.hpp"
+#include "Responder.hpp"
+#include "AudioDriver.hpp"
+#include "MidiDriver.hpp"
+
+using namespace std;
+
+namespace Ingen {
+
+
+RenameEvent::RenameEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& path, const string& name)
+: QueuedEvent(engine, responder, timestamp),
+ _old_path(path),
+ _name(name),
+ _new_path("/"),
+ _parent_patch(NULL),
+ _store_iterator(engine.engine_store()->end()),
+ _error(NO_ERROR)
+{
+ /*
+ if (_old_path.parent() == "/")
+ _new_path = string("/") + _name;
+ else
+ _new_path = _old_path.parent() +"/"+ _name;*/
+}
+
+
+RenameEvent::~RenameEvent()
+{
+}
+
+
+void
+RenameEvent::pre_process()
+{
+ if ((!Raul::Path::is_valid_name(_name)) || _name.find("/") != string::npos) {
+ _error = INVALID_NAME;
+ QueuedEvent::pre_process();
+ return;
+ }
+
+ _new_path = _old_path.parent().base() + _name;
+
+ _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<Shared::GraphObject> > > removed
+ = _engine.engine_store()->remove(_store_iterator);
+
+ assert(removed->size() > 0);
+
+ for (Table<Path, SharedPtr<Shared::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 = _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
+RenameEvent::execute(ProcessContext& context)
+{
+ QueuedEvent::execute(context);
+
+ SharedPtr<PortImpl> port = PtrCast<PortImpl>(_store_iterator->second);
+ if (port && port->parent()->parent() == NULL) {
+ DriverPort* driver_port = NULL;
+
+ if (port->type() == DataType::AUDIO)
+ driver_port = _engine.audio_driver()->driver_port(_new_path);
+ else if (port->type() == DataType::EVENT)
+ driver_port = _engine.midi_driver()->driver_port(_new_path);
+
+ if (driver_port) {
+ cerr << "DRIVER PORT :)!" << endl;
+ driver_port->set_name(_new_path);
+ } else {
+ cerr << "NO DRIVER PORT :(" << endl;
+ }
+ }
+}
+
+
+void
+RenameEvent::post_process()
+{
+ string msg = "Unable to rename object - ";
+
+ if (_error == NO_ERROR) {
+ _responder->respond_ok();
+ _engine.broadcaster()->send_rename(_old_path, _new_path);
+ } else {
+ if (_error == OBJECT_EXISTS)
+ msg.append("Object already exists at ").append(_new_path);
+ else if (_error == OBJECT_NOT_FOUND)
+ msg.append("Could not find object ").append(_old_path);
+ else if (_error == OBJECT_NOT_RENAMABLE)
+ msg.append(_old_path).append(" is not renamable");
+ else if (_error == INVALID_NAME)
+ msg.append(_name).append(" is not a valid name");
+
+ _responder->respond_error(msg);
+ }
+}
+
+
+} // namespace Ingen
diff --git a/src/engine/events/RenameEvent.hpp b/src/engine/events/RenameEvent.hpp
new file mode 100644
index 00000000..e230c589
--- /dev/null
+++ b/src/engine/events/RenameEvent.hpp
@@ -0,0 +1,64 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 RENAMEEVENT_H
+#define RENAMEEVENT_H
+
+#include <string>
+#include <raul/Path.hpp>
+#include "QueuedEvent.hpp"
+#include "EngineStore.hpp"
+
+using std::string;
+
+template<typename T> class TreeNode;
+template<typename T> class ListNode;
+
+namespace Ingen {
+
+class PatchImpl;
+
+
+/** An event to change the name of an GraphObjectImpl.
+ *
+ * \ingroup engine
+ */
+class RenameEvent : public QueuedEvent
+{
+public:
+ RenameEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& path, const string& name);
+ ~RenameEvent();
+
+ void pre_process();
+ void execute(ProcessContext& context);
+ void post_process();
+
+private:
+ enum ErrorType { NO_ERROR, OBJECT_NOT_FOUND, OBJECT_EXISTS, OBJECT_NOT_RENAMABLE, INVALID_NAME };
+
+ Path _old_path;
+ string _name;
+ Path _new_path;
+ PatchImpl* _parent_patch;
+ EngineStore::iterator _store_iterator;
+ ErrorType _error;
+};
+
+
+} // namespace Ingen
+
+#endif // RENAMEEVENT_H
diff --git a/src/engine/events/RequestAllObjectsEvent.cpp b/src/engine/events/RequestAllObjectsEvent.cpp
new file mode 100644
index 00000000..d57080aa
--- /dev/null
+++ b/src/engine/events/RequestAllObjectsEvent.cpp
@@ -0,0 +1,59 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "RequestAllObjectsEvent.hpp"
+#include "Responder.hpp"
+#include "Engine.hpp"
+#include "ObjectSender.hpp"
+#include "ClientBroadcaster.hpp"
+#include "EngineStore.hpp"
+
+namespace Ingen {
+
+
+RequestAllObjectsEvent::RequestAllObjectsEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp)
+: QueuedEvent(engine, responder, timestamp)
+{
+}
+
+
+void
+RequestAllObjectsEvent::pre_process()
+{
+ QueuedEvent::pre_process();
+}
+
+
+void
+RequestAllObjectsEvent::post_process()
+{
+ if (_responder->client()) {
+ _responder->respond_ok();
+
+ // Everything is a child of the root patch, so this sends it all
+ PatchImpl* root = _engine.engine_store()->find_patch("/");
+ if (root && _responder->client())
+ ObjectSender::send_patch(_responder->client(), root, true);
+
+ } else {
+ _responder->respond_error("Unable to find client to send all objects");
+ }
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/events/RequestAllObjectsEvent.hpp b/src/engine/events/RequestAllObjectsEvent.hpp
new file mode 100644
index 00000000..0537575a
--- /dev/null
+++ b/src/engine/events/RequestAllObjectsEvent.hpp
@@ -0,0 +1,48 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 REQUESTALLOBJECTSEVENT_H
+#define REQUESTALLOBJECTSEVENT_H
+
+#include <string>
+#include "QueuedEvent.hpp"
+using std::string;
+
+namespace Ingen {
+
+namespace Shared {
+ class ClientInterface;
+} using Shared::ClientInterface;
+
+
+/** A request from a client to send notification of all objects (ie refresh).
+ *
+ * \ingroup engine
+ */
+class RequestAllObjectsEvent : public QueuedEvent
+{
+public:
+ RequestAllObjectsEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp);
+
+ void pre_process();
+ void post_process();
+};
+
+
+} // namespace Ingen
+
+#endif // REQUESTALLOBJECTSEVENT_H
diff --git a/src/engine/events/RequestMetadataEvent.cpp b/src/engine/events/RequestMetadataEvent.cpp
new file mode 100644
index 00000000..733a6a82
--- /dev/null
+++ b/src/engine/events/RequestMetadataEvent.cpp
@@ -0,0 +1,85 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "RequestMetadataEvent.hpp"
+#include <string>
+#include "Responder.hpp"
+#include "Engine.hpp"
+#include "GraphObjectImpl.hpp"
+#include "EngineStore.hpp"
+#include "interface/ClientInterface.hpp"
+#include "ClientBroadcaster.hpp"
+using std::string;
+
+namespace Ingen {
+
+
+RequestMetadataEvent::RequestMetadataEvent(Engine& engine,
+ SharedPtr<Responder> responder,
+ SampleCount timestamp,
+ bool property,
+ const string& node_path,
+ const string& key)
+ : QueuedEvent(engine, responder, timestamp)
+ , _path(node_path)
+ , _property(property)
+ , _key(key)
+ , _object(NULL)
+{
+}
+
+
+void
+RequestMetadataEvent::pre_process()
+{
+ if (_responder->client()) {
+ _object = _engine.engine_store()->find_object(_path);
+ if (_object == NULL) {
+ QueuedEvent::pre_process();
+ return;
+ }
+ }
+
+ if (_property)
+ _value = _object->get_property(_key);
+ else
+ _value = _object->get_variable(_key);
+
+ QueuedEvent::pre_process();
+}
+
+
+void
+RequestMetadataEvent::post_process()
+{
+ if (_responder->client()) {
+ if (!_object) {
+ string msg = "Unable to find variable subject ";
+ msg += _path;
+ _responder->respond_error(msg);
+ } else {
+ _responder->respond_ok();
+ _responder->client()->set_variable(_path, _key, _value);
+ }
+ } else {
+ _responder->respond_error("Unknown client");
+ }
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/events/RequestMetadataEvent.hpp b/src/engine/events/RequestMetadataEvent.hpp
new file mode 100644
index 00000000..f6a18dfc
--- /dev/null
+++ b/src/engine/events/RequestMetadataEvent.hpp
@@ -0,0 +1,62 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 REQUESTMETADATAEVENT_H
+#define REQUESTMETADATAEVENT_H
+
+#include <string>
+#include "QueuedEvent.hpp"
+#include <raul/Atom.hpp>
+using std::string;
+
+namespace Ingen {
+
+class GraphObjectImpl;
+namespace Shared {
+ class ClientInterface;
+} using Shared::ClientInterface;
+
+
+/** A request from a client for a piece of variable.
+ *
+ * \ingroup engine
+ */
+class RequestMetadataEvent : public QueuedEvent
+{
+public:
+ RequestMetadataEvent(Engine& engine,
+ SharedPtr<Responder> responder,
+ SampleCount timestamp,
+ bool property,
+ const string& path,
+ const string& key);
+
+ void pre_process();
+ void post_process();
+
+private:
+ string _path;
+ bool _property;
+ string _key;
+ Raul::Atom _value;
+ GraphObjectImpl* _object;
+};
+
+
+} // namespace Ingen
+
+#endif // REQUESTMETADATAEVENT_H
diff --git a/src/engine/events/RequestObjectEvent.cpp b/src/engine/events/RequestObjectEvent.cpp
new file mode 100644
index 00000000..88479482
--- /dev/null
+++ b/src/engine/events/RequestObjectEvent.cpp
@@ -0,0 +1,98 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "RequestObjectEvent.hpp"
+#include <string>
+#include "interface/ClientInterface.hpp"
+#include "Responder.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 std::string;
+
+namespace Ingen {
+
+
+RequestObjectEvent::RequestObjectEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& path)
+: QueuedEvent(engine, responder, timestamp),
+ _path(path),
+ _object(NULL)
+{
+}
+
+
+void
+RequestObjectEvent::pre_process()
+{
+ _object = _engine.engine_store()->find_object(_path);
+
+ QueuedEvent::pre_process();
+}
+
+
+void
+RequestObjectEvent::execute(ProcessContext& context)
+{
+ QueuedEvent::execute(context);
+ assert(_time >= context.start() && _time <= context.end());
+}
+
+
+void
+RequestObjectEvent::post_process()
+{
+ if (!_object) {
+ _responder->respond_error("Unable to find object requested.");
+
+ } else if (_responder->client()) {
+ PatchImpl* const patch = dynamic_cast<PatchImpl*>(_object);
+ if (patch) {
+ _responder->respond_ok();
+ ObjectSender::send_patch(_responder->client(), patch, true);
+ return;
+ }
+
+ NodeImpl* const node = dynamic_cast<NodeImpl*>(_object);
+ if (node) {
+ _responder->respond_ok();
+ ObjectSender::send_node(_responder->client(), node, true);
+ return;
+ }
+
+ PortImpl* const port = dynamic_cast<PortImpl*>(_object);
+ if (port) {
+ _responder->respond_ok();
+ ObjectSender::send_port(_responder->client(), port);
+ return;
+ }
+
+ _responder->respond_error("Object of unknown type requested.");
+
+ } else {
+ _responder->respond_error("Unable to find client to send object.");
+ }
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/events/RequestObjectEvent.hpp b/src/engine/events/RequestObjectEvent.hpp
new file mode 100644
index 00000000..52459ada
--- /dev/null
+++ b/src/engine/events/RequestObjectEvent.hpp
@@ -0,0 +1,55 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 REQUESTOBJECTEVENT_H
+#define REQUESTOBJECTEVENT_H
+
+#include <string>
+#include "QueuedEvent.hpp"
+#include "types.hpp"
+
+using std::string;
+
+namespace Ingen {
+
+class GraphObjectImpl;
+namespace Shared { class ClientInterface; }
+using Shared::ClientInterface;
+
+
+/** A request from a client to send the value of a port.
+ *
+ * \ingroup engine
+ */
+class RequestObjectEvent : public QueuedEvent
+{
+public:
+ RequestObjectEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& port_path);
+
+ void pre_process();
+ void execute(ProcessContext& context);
+ void post_process();
+
+private:
+ const string _path;
+ GraphObjectImpl* _object;
+};
+
+
+} // namespace Ingen
+
+#endif // REQUESTOBJECTEVENT_H
diff --git a/src/engine/events/RequestPluginEvent.cpp b/src/engine/events/RequestPluginEvent.cpp
new file mode 100644
index 00000000..36358df7
--- /dev/null
+++ b/src/engine/events/RequestPluginEvent.cpp
@@ -0,0 +1,79 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "interface/ClientInterface.hpp"
+#include "RequestPluginEvent.hpp"
+#include "Responder.hpp"
+#include "Engine.hpp"
+#include "PortImpl.hpp"
+#include "EngineStore.hpp"
+#include "ClientBroadcaster.hpp"
+#include "NodeFactory.hpp"
+#include "PluginImpl.hpp"
+#include "ProcessContext.hpp"
+
+using std::string;
+
+namespace Ingen {
+
+
+RequestPluginEvent::RequestPluginEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& uri)
+: QueuedEvent(engine, responder, timestamp),
+ _uri(uri),
+ _plugin(NULL)
+{
+}
+
+
+void
+RequestPluginEvent::pre_process()
+{
+ _plugin = _engine.node_factory()->plugin(_uri);
+
+ QueuedEvent::pre_process();
+}
+
+
+void
+RequestPluginEvent::execute(ProcessContext& context)
+{
+ QueuedEvent::execute(context);
+ assert(_time >= context.start() && _time <= context.end());
+}
+
+
+void
+RequestPluginEvent::post_process()
+{
+ if (!_plugin) {
+ _responder->respond_error("Unable to find plugin requested.");
+
+ } else if (_responder->client()) {
+
+ _responder->respond_ok();
+ assert(_plugin->uri() == _uri);
+ _responder->client()->new_plugin(_uri, _plugin->type_uri(), _plugin->symbol(), _plugin->name());
+
+ } else {
+ _responder->respond_error("Unable to find client to send plugin.");
+ }
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/events/RequestPluginEvent.hpp b/src/engine/events/RequestPluginEvent.hpp
new file mode 100644
index 00000000..8f936098
--- /dev/null
+++ b/src/engine/events/RequestPluginEvent.hpp
@@ -0,0 +1,53 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 REQUESTPLUGINEVENT_H
+#define REQUESTPLUGINEVENT_H
+
+#include <string>
+#include "QueuedEvent.hpp"
+#include "types.hpp"
+
+using std::string;
+
+namespace Ingen {
+
+class PluginImpl;
+
+
+/** A request from a client to send information about a plugin.
+ *
+ * \ingroup engine
+ */
+class RequestPluginEvent : public QueuedEvent
+{
+public:
+ RequestPluginEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& uri);
+
+ void pre_process();
+ void execute(ProcessContext& context);
+ void post_process();
+
+private:
+ const string _uri;
+ const PluginImpl* _plugin;
+};
+
+
+} // namespace Ingen
+
+#endif // REQUESTPLUGINEVENT_H
diff --git a/src/engine/events/RequestPluginsEvent.cpp b/src/engine/events/RequestPluginsEvent.cpp
new file mode 100644
index 00000000..8d7fc1ba
--- /dev/null
+++ b/src/engine/events/RequestPluginsEvent.cpp
@@ -0,0 +1,57 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public 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 "RequestPluginsEvent.hpp"
+#include "Responder.hpp"
+#include "Engine.hpp"
+#include "ClientBroadcaster.hpp"
+#include "NodeFactory.hpp"
+
+namespace Ingen {
+
+
+RequestPluginsEvent::RequestPluginsEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp)
+: QueuedEvent(engine, responder, timestamp)
+{
+}
+
+
+void
+RequestPluginsEvent::pre_process()
+{
+ // Take a copy to send in the post processing thread (to avoid problems
+ // because std::map isn't thread safe)
+ _plugins = _engine.node_factory()->plugins();
+
+ QueuedEvent::pre_process();
+}
+
+
+void
+RequestPluginsEvent::post_process()
+{
+ if (_responder->client()) {
+ _engine.broadcaster()->send_plugins_to(_responder->client(), _plugins);
+ _responder->respond_ok();
+ } else {
+ _responder->respond_error("Unable to find client to send plugins");
+ }
+}
+
+
+} // namespace Ingen
+
diff --git a/src/engine/events/RequestPluginsEvent.hpp b/src/engine/events/RequestPluginsEvent.hpp
new file mode 100644
index 00000000..f6b41d7a
--- /dev/null
+++ b/src/engine/events/RequestPluginsEvent.hpp
@@ -0,0 +1,47 @@
+/* This file is part of Ingen.
+ * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
+ *
+ * Ingen is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+