summaryrefslogtreecommitdiffstats
path: root/src/server/GraphImpl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/GraphImpl.cpp')
-rw-r--r--src/server/GraphImpl.cpp381
1 files changed, 381 insertions, 0 deletions
diff --git a/src/server/GraphImpl.cpp b/src/server/GraphImpl.cpp
new file mode 100644
index 00000000..81c69b46
--- /dev/null
+++ b/src/server/GraphImpl.cpp
@@ -0,0 +1,381 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2012 David Robillard <http://drobilla.net/>
+
+ Ingen is free software: you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or any later version.
+
+ Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with Ingen. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <cassert>
+
+#include "ingen/Log.hpp"
+#include "ingen/URIs.hpp"
+#include "ingen/World.hpp"
+
+#include "BlockImpl.hpp"
+#include "BufferFactory.hpp"
+#include "DuplexPort.hpp"
+#include "EdgeImpl.hpp"
+#include "Engine.hpp"
+#include "GraphImpl.hpp"
+#include "GraphPlugin.hpp"
+#include "PortImpl.hpp"
+#include "ThreadManager.hpp"
+
+using namespace std;
+
+namespace Ingen {
+namespace Server {
+
+GraphImpl::GraphImpl(Engine& engine,
+ const Raul::Symbol& symbol,
+ uint32_t poly,
+ GraphImpl* parent,
+ SampleRate srate,
+ uint32_t internal_poly)
+ : BlockImpl(new GraphPlugin(engine.world()->uris(),
+ engine.world()->uris().ingen_Graph,
+ Raul::Symbol("graph"),
+ "Ingen Graph"),
+ symbol, poly, parent, srate)
+ , _engine(engine)
+ , _poly_pre(internal_poly)
+ , _poly_process(internal_poly)
+ , _compiled_graph(NULL)
+ , _process(false)
+{
+ assert(internal_poly >= 1);
+ assert(internal_poly <= 128);
+}
+
+GraphImpl::~GraphImpl()
+{
+ delete _compiled_graph;
+ delete _plugin;
+}
+
+void
+GraphImpl::activate(BufferFactory& bufs)
+{
+ BlockImpl::activate(bufs);
+
+ for (Blocks::iterator i = _blocks.begin(); i != _blocks.end(); ++i) {
+ i->activate(bufs);
+ }
+
+ assert(_activated);
+}
+
+void
+GraphImpl::deactivate()
+{
+ if (_activated) {
+ BlockImpl::deactivate();
+
+ for (Blocks::iterator i = _blocks.begin(); i != _blocks.end(); ++i) {
+ if (i->activated()) {
+ i->deactivate();
+ }
+ }
+ }
+}
+
+void
+GraphImpl::disable(ProcessContext& context)
+{
+ _process = false;
+ for (Ports::iterator i = _outputs.begin(); i != _outputs.end(); ++i) {
+ i->clear_buffers();
+ }
+}
+
+bool
+GraphImpl::prepare_internal_poly(BufferFactory& bufs, uint32_t poly)
+{
+ ThreadManager::assert_thread(THREAD_PRE_PROCESS);
+
+ // TODO: Subgraph dynamic polyphony (i.e. changing port polyphony)
+
+ for (Blocks::iterator i = _blocks.begin(); i != _blocks.end(); ++i) {
+ i->prepare_poly(bufs, poly);
+ }
+
+ _poly_pre = poly;
+ return true;
+}
+
+bool
+GraphImpl::apply_internal_poly(ProcessContext& context,
+ BufferFactory& bufs,
+ Raul::Maid& maid,
+ uint32_t poly)
+{
+ // TODO: Subgraph dynamic polyphony (i.e. changing port polyphony)
+
+ for (Blocks::iterator i = _blocks.begin(); i != _blocks.end(); ++i) {
+ i->apply_poly(context, maid, poly);
+ }
+
+ for (Blocks::iterator i = _blocks.begin(); i != _blocks.end(); ++i) {
+ for (uint32_t j = 0; j < i->num_ports(); ++j) {
+ PortImpl* const port = i->port_impl(j);
+ if (port->is_input() && dynamic_cast<InputPort*>(port)->direct_connect())
+ port->setup_buffers(bufs, port->poly(), true);
+ port->connect_buffers();
+ }
+ }
+
+ const bool polyphonic = parent_graph() && (poly == parent_graph()->internal_poly_process());
+ for (Ports::iterator i = _outputs.begin(); i != _outputs.end(); ++i)
+ i->setup_buffers(bufs, polyphonic ? poly : 1, true);
+
+ _poly_process = poly;
+ return true;
+}
+
+/** Run the graph for the specified number of frames.
+ *
+ * Calls all Blocks in (roughly, if parallel) the order _compiled_graph specifies.
+ */
+void
+GraphImpl::process(ProcessContext& context)
+{
+ if (!_process)
+ return;
+
+ BlockImpl::pre_process(context);
+
+ if (_compiled_graph && _compiled_graph->size() > 0) {
+ // Run all blocks
+ for (size_t i = 0; i < _compiled_graph->size(); ++i) {
+ (*_compiled_graph)[i].block()->process(context);
+ }
+ }
+
+ BlockImpl::post_process(context);
+}
+
+void
+GraphImpl::set_buffer_size(Context& context,
+ BufferFactory& bufs,
+ LV2_URID type,
+ uint32_t size)
+{
+ BlockImpl::set_buffer_size(context, bufs, type, size);
+
+ for (size_t i = 0; i < _compiled_graph->size(); ++i)
+ (*_compiled_graph)[i].block()->set_buffer_size(context, bufs, type, size);
+}
+
+// Graph specific stuff
+
+/** Add a block.
+ * Preprocessing thread only.
+ */
+void
+GraphImpl::add_block(BlockImpl& block)
+{
+ ThreadManager::assert_thread(THREAD_PRE_PROCESS);
+ _blocks.push_front(block);
+}
+
+/** Remove a block.
+ * Preprocessing thread only.
+ */
+void
+GraphImpl::remove_block(BlockImpl& block)
+{
+ _blocks.erase(_blocks.iterator_to(block));
+}
+
+void
+GraphImpl::add_edge(SharedPtr<EdgeImpl> c)
+{
+ ThreadManager::assert_thread(THREAD_PRE_PROCESS);
+ _edges.insert(make_pair(make_pair(c->tail(), c->head()), c));
+}
+
+/** Remove a edge.
+ * Preprocessing thread only.
+ */
+SharedPtr<EdgeImpl>
+GraphImpl::remove_edge(const PortImpl* tail, const PortImpl* dst_port)
+{
+ ThreadManager::assert_thread(THREAD_PRE_PROCESS);
+ Edges::iterator i = _edges.find(make_pair(tail, dst_port));
+ if (i != _edges.end()) {
+ SharedPtr<EdgeImpl> c = PtrCast<EdgeImpl>(i->second);
+ _edges.erase(i);
+ return c;
+ } else {
+ return SharedPtr<EdgeImpl>();
+ }
+}
+
+bool
+GraphImpl::has_edge(const PortImpl* tail, const PortImpl* dst_port) const
+{
+ ThreadManager::assert_thread(THREAD_PRE_PROCESS);
+ Edges::const_iterator i = _edges.find(make_pair(tail, dst_port));
+ return (i != _edges.end());
+}
+
+uint32_t
+GraphImpl::num_ports_non_rt() const
+{
+ ThreadManager::assert_not_thread(THREAD_PROCESS);
+ return _inputs.size() + _outputs.size();
+}
+
+/** Create a port. Not realtime safe.
+ */
+DuplexPort*
+GraphImpl::create_port(BufferFactory& bufs,
+ const Raul::Symbol& symbol,
+ PortType type,
+ LV2_URID buffer_type,
+ uint32_t buffer_size,
+ bool is_output,
+ bool polyphonic)
+{
+ if (type == PortType::UNKNOWN) {
+ bufs.engine().log().error(Raul::fmt("Unknown port type %1%\n")
+ % type.uri());
+ return NULL;
+ }
+
+ Raul::Atom value;
+ if (type == PortType::CONTROL || type == PortType::CV)
+ value = bufs.forge().make(0.0f);
+
+ return new DuplexPort(bufs, this, symbol, num_ports_non_rt(), polyphonic, _polyphony,
+ type, buffer_type, value, 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.
+ */
+void
+GraphImpl::remove_port(DuplexPort& port)
+{
+ ThreadManager::assert_thread(THREAD_PRE_PROCESS);
+
+ if (port.is_input()) {
+ _inputs.erase(_inputs.iterator_to(port));
+ } else {
+ _outputs.erase(_outputs.iterator_to(port));
+ }
+}
+
+/** 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
+GraphImpl::clear_ports()
+{
+ ThreadManager::assert_thread(THREAD_PRE_PROCESS);
+
+ _inputs.clear();
+ _outputs.clear();
+}
+
+Raul::Array<PortImpl*>*
+GraphImpl::build_ports_array()
+{
+ ThreadManager::assert_thread(THREAD_PRE_PROCESS);
+
+ const size_t n = _inputs.size() + _outputs.size();
+ Raul::Array<PortImpl*>* const result = new Raul::Array<PortImpl*>(n);
+
+ size_t i = 0;
+
+ for (Ports::iterator p = _inputs.begin(); p != _inputs.end(); ++p, ++i)
+ result->at(i) = &*p;
+
+ for (Ports::iterator p = _outputs.begin(); p != _outputs.end(); ++p, ++i)
+ result->at(i) = &*p;
+
+ assert(i == n);
+
+ return result;
+}
+
+static inline void
+compile_recursive(BlockImpl* n, CompiledGraph* output)
+{
+ if (n == NULL || n->traversed())
+ return;
+
+ n->traversed(true);
+ assert(output != NULL);
+
+ for (std::list<BlockImpl*>::iterator i = n->providers().begin();
+ i != n->providers().end(); ++i)
+ if (!(*i)->traversed())
+ compile_recursive(*i, output);
+
+ output->push_back(CompiledBlock(n, n->providers().size(), n->dependants()));
+}
+
+/** Find the process order for this Graph.
+ *
+ * The process order is a flat list that the graph will execute in order
+ * when its 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.
+ */
+CompiledGraph*
+GraphImpl::compile()
+{
+ ThreadManager::assert_thread(THREAD_PRE_PROCESS);
+
+ CompiledGraph* const compiled_graph = new CompiledGraph();
+
+ for (Blocks::iterator i = _blocks.begin(); i != _blocks.end(); ++i) {
+ i->traversed(false);
+ }
+
+ for (Blocks::iterator i = _blocks.begin(); i != _blocks.end(); ++i) {
+ // Either a sink or connected to our output ports:
+ if (!i->traversed() && i->dependants().empty()) {
+ compile_recursive(&*i, compiled_graph);
+ }
+ }
+
+ // Traverse any blocks we didn't hit yet
+ for (Blocks::iterator i = _blocks.begin(); i != _blocks.end(); ++i) {
+ if (!i->traversed()) {
+ compile_recursive(&*i, compiled_graph);
+ }
+ }
+
+ if (compiled_graph->size() != _blocks.size()) {
+ _engine.log().error(Raul::fmt("Failed to compile graph %1%\n") % _path);
+ delete compiled_graph;
+ return NULL;
+ }
+
+ return compiled_graph;
+}
+
+} // namespace Server
+} // namespace Ingen