summaryrefslogtreecommitdiffstats
path: root/src/libs/client
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2006-06-10 02:43:54 +0000
committerDavid Robillard <d@drobilla.net>2006-06-10 02:43:54 +0000
commit47246db7e9d71ba694b719001033fba1766a58c4 (patch)
tree25614ea4f2c92033a3cd37f6413df742f09a5369 /src/libs/client
parent98fe0e7056e6697396249531785d3899f94d79be (diff)
downloadingen-47246db7e9d71ba694b719001033fba1766a58c4.tar.gz
ingen-47246db7e9d71ba694b719001033fba1766a58c4.tar.bz2
ingen-47246db7e9d71ba694b719001033fba1766a58c4.zip
More juggling
git-svn-id: http://svn.drobilla.net/lad/grauph@16 a436a847-0d15-0410-975c-d299462d15a1
Diffstat (limited to 'src/libs/client')
-rw-r--r--src/libs/client/ConnectionModel.cpp54
-rw-r--r--src/libs/client/ConnectionModel.h70
-rw-r--r--src/libs/client/ControlModel.h53
-rw-r--r--src/libs/client/DirectSigClientInterface.h112
-rw-r--r--src/libs/client/Makefile.am51
-rw-r--r--src/libs/client/ModelClientInterface.cpp135
-rw-r--r--src/libs/client/ModelClientInterface.h87
-rw-r--r--src/libs/client/ModelEngineInterface.h58
-rw-r--r--src/libs/client/NodeModel.cpp117
-rw-r--r--src/libs/client/NodeModel.h93
-rw-r--r--src/libs/client/OSCEngineInterface.cpp332
-rw-r--r--src/libs/client/OSCEngineInterface.h139
-rw-r--r--src/libs/client/OSCListener.cpp413
-rw-r--r--src/libs/client/OSCListener.h116
-rw-r--r--src/libs/client/OSCModelEngineInterface.cpp364
-rw-r--r--src/libs/client/OSCModelEngineInterface.h117
-rw-r--r--src/libs/client/ObjectController.h43
-rw-r--r--src/libs/client/ObjectModel.cpp64
-rw-r--r--src/libs/client/ObjectModel.h84
-rw-r--r--src/libs/client/PatchLibrarian.cpp834
-rw-r--r--src/libs/client/PatchLibrarian.cpp.new830
-rw-r--r--src/libs/client/PatchLibrarian.h78
-rw-r--r--src/libs/client/PatchModel.cpp229
-rw-r--r--src/libs/client/PatchModel.h88
-rw-r--r--src/libs/client/PluginModel.h105
-rw-r--r--src/libs/client/PortModel.h118
-rw-r--r--src/libs/client/PresetModel.h66
-rw-r--r--src/libs/client/SigClientInterface.h120
-rw-r--r--src/libs/client/ThreadedSigClientInterface.cpp71
-rw-r--r--src/libs/client/ThreadedSigClientInterface.h177
30 files changed, 5218 insertions, 0 deletions
diff --git a/src/libs/client/ConnectionModel.cpp b/src/libs/client/ConnectionModel.cpp
new file mode 100644
index 00000000..1c7541b9
--- /dev/null
+++ b/src/libs/client/ConnectionModel.cpp
@@ -0,0 +1,54 @@
+/* This file is part of Om. Copyright (C) 2005 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "ConnectionModel.h"
+#include "PortModel.h"
+
+namespace LibOmClient {
+
+
+ConnectionModel::ConnectionModel(const Path& src_port, const Path& dst_port)
+: m_src_port_path(src_port),
+ m_dst_port_path(dst_port),
+ m_src_port(NULL),
+ m_dst_port(NULL)
+{
+ // Be sure connection is within one patch
+ assert(m_src_port_path.parent().parent()
+ == m_dst_port_path.parent().parent());
+}
+
+
+const Path&
+ConnectionModel::src_port_path() const
+{
+ if (m_src_port == NULL)
+ return m_src_port_path;
+ else
+ return m_src_port->path();
+}
+
+
+const Path&
+ConnectionModel::dst_port_path() const
+{
+ if (m_dst_port == NULL)
+ return m_dst_port_path;
+ else
+ return m_dst_port->path();
+}
+
+} // namespace LibOmClient
diff --git a/src/libs/client/ConnectionModel.h b/src/libs/client/ConnectionModel.h
new file mode 100644
index 00000000..ef909850
--- /dev/null
+++ b/src/libs/client/ConnectionModel.h
@@ -0,0 +1,70 @@
+/* This file is part of Om. Copyright (C) 2005 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+#ifndef CONNECTIONMODEL_H
+#define CONNECTIONMODEL_H
+
+#include <string>
+#include "util/Path.h"
+#include <cassert>
+using std::string;
+using Om::Path;
+
+namespace LibOmClient {
+
+class PortModel;
+
+
+/** Class to represent a port->port connection in the engine.
+ *
+ * This can either have pointers to the connection ports' models, or just
+ * paths as strings. LibOmClient passes just strings (by necessity), but
+ * clients can set the pointers then they don't have to worry about port
+ * renaming, as the connections will always return the port's path, even
+ * if it changes.
+ *
+ * \ingroup libomclient
+ */
+class ConnectionModel
+{
+public:
+ ConnectionModel(const Path& src_port, const Path& dst_port);
+
+ PortModel* src_port() const { return m_src_port; }
+ PortModel* dst_port() const { return m_dst_port; }
+
+ void set_src_port(PortModel* port) { m_src_port = port; m_src_port_path = ""; }
+ void set_dst_port(PortModel* port) { m_dst_port = port; m_dst_port_path = ""; }
+
+ void src_port_path(const string& s) { m_src_port_path = s; }
+ void dst_port_path(const string& s) { m_dst_port_path = s; }
+
+ const Path& src_port_path() const;
+ const Path& dst_port_path() const;
+ const Path patch_path() const { return src_port_path().parent().parent(); }
+
+private:
+ Path m_src_port_path; ///< Only used if m_src_port == NULL
+ Path m_dst_port_path; ///< Only used if m_dst_port == NULL
+ PortModel* m_src_port;
+ PortModel* m_dst_port;
+};
+
+
+} // namespace LibOmClient
+
+#endif // CONNECTIONMODEL_H
diff --git a/src/libs/client/ControlModel.h b/src/libs/client/ControlModel.h
new file mode 100644
index 00000000..872dcca2
--- /dev/null
+++ b/src/libs/client/ControlModel.h
@@ -0,0 +1,53 @@
+/* This file is part of Om. Copyright (C) 2005 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License alongCont
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef CONTROLMODEL_H
+#define CONTROLMODEL_H
+
+#include <string>
+#include "util/Path.h"
+
+namespace LibOmClient {
+
+
+/** A single port's control setting (in a preset).
+ *
+ * \ingroup libomclient
+ */
+class ControlModel
+{
+public:
+ ControlModel(const Path& port_path, float value)
+ : m_port_path(port_path),
+ m_value(value)
+ {
+ assert(m_port_path.find("//") == string::npos);
+ }
+
+ const Path& port_path() const { return m_port_path; }
+ void port_path(const string& p) { m_port_path = p; }
+ float value() const { return m_value; }
+ void value(float v) { m_value = v; }
+
+private:
+ Path m_port_path;
+ float m_value;
+};
+
+
+} // namespace LibOmClient
+
+#endif // CONTROLMODEL
diff --git a/src/libs/client/DirectSigClientInterface.h b/src/libs/client/DirectSigClientInterface.h
new file mode 100644
index 00000000..12672a48
--- /dev/null
+++ b/src/libs/client/DirectSigClientInterface.h
@@ -0,0 +1,112 @@
+/* This file is part of Om. Copyright (C) 2006 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef DIRECTSIGCLIENTINTERFACE_H
+#define DIRECTSIGCLIENTINTERFACE_H
+
+#include <inttypes.h>
+#include <string>
+#include <sigc++/sigc++.h>
+#include "SigClientInterface.h"
+using std::string;
+
+namespace LibOmClient {
+
+
+/** A direct (nonthreaded) LibSigC++ signal emitting interface for clients to use.
+ *
+ * The signals from SigClientInterface will be emitted in the same thread as the
+ * ClientInterface functions are called. <b>You can not set this directly as an
+ * in-engine client interface and connect the signals to GTK</b>.
+ *
+ * For maximum performance of a monolithic single-client GUI app, it would be
+ * nice if the post processing thread in the engine could actually be the GTK
+ * thread, then you could use this directly and minimize queueing of events and
+ * thread/scheduling overhead.
+ *
+ * sed would have the copyright to this code if it was a legal person.
+ */
+class DirectSigClientInterface : virtual public SigClientInterface
+{
+public:
+ DirectSigClientInterface();
+
+private:
+
+ // ClientInterface function implementations to drive SigClientInterface signals
+
+ virtual void bundle_begin()
+ { emit_bundle_begin(); }
+
+ virtual void bundle_end()
+ { emit_bundle_end(); }
+
+ virtual void error(const string& msg)
+ { emit_error(msg); }
+
+ virtual void num_plugins(uint32_t num)
+ { emit_num_plugins(num); }
+
+ virtual void new_plugin(const string& type, const string& uri, const string& name)
+ { emit_new_plugin(type, uri, name); }
+
+ virtual void new_patch(const string& path, uint32_t poly)
+ { emit_new_patch(path, poly); }
+
+ virtual void new_node(const string& plugin_type, const string& plugin_uri, const string& node_path, bool is_polyphonic, uint32_t num_ports)
+ { emit_new_node(plugin_type, plugin_uri, node_path, is_polyphonic, num_ports); }
+
+ virtual void new_port(const string& path, const string& data_type, bool is_output)
+ { emit_new_port(path, data_type, is_output); }
+
+ virtual void patch_enabled(const string& path)
+ { emit_patch_enabled(path); }
+
+ virtual void patch_disabled(const string& path)
+ { emit_patch_disabled(path); }
+
+ virtual void patch_cleared(const string& path)
+ { emit_patch_cleared(path); }
+
+ virtual void object_renamed(const string& old_path, const string& new_path)
+ { emit_object_renamed(old_path, new_path); }
+
+ virtual void object_destroyed(const string& path)
+ { emit_object_destroyed(path); }
+
+ virtual void connection(const string& src_port_path, const string& dst_port_path)
+ { emit_connection(src_port_path, dst_port_path); }
+
+ virtual void disconnection(const string& src_port_path, const string& dst_port_path)
+ { emit_disconnection(src_port_path, dst_port_path); }
+
+ virtual void metadata_update(const string& subject_path, const string& predicate, const string& value)
+ { emit_metadata_update(subject_path, predicate, value); }
+
+ virtual void control_change(const string& port_path, float value)
+ { emit_control_change(port_path, value); }
+
+ virtual void program_add(const string& node_path, uint32_t bank, uint32_t program, const string& program_name)
+ { emit_program_add(node_path, bank, program, program_name); }
+
+ virtual void program_remove(const string& node_path, uint32_t bank, uint32_t program)
+ { emit_program_remove(node_path, bank, program); }
+};
+
+
+} // namespace LibOmClient
+
+#endif
diff --git a/src/libs/client/Makefile.am b/src/libs/client/Makefile.am
new file mode 100644
index 00000000..24e6a3bb
--- /dev/null
+++ b/src/libs/client/Makefile.am
@@ -0,0 +1,51 @@
+AM_CXXFLAGS = -I$(top_srcdir)/src/common -fno-exceptions -fno-rtti
+
+SUBDIRS = . python supercollider
+
+if BUILD_CONSOLE_CLIENTS
+noinst_LIBRARIES = libomclient.a
+
+libomclient_a_CXXFLAGS = -I$(top_srcdir)/src/common -DPKGDATADIR=\"$(pkgdatadir)\" $(LIBGLADEMM_CFLAGS) $(GNOMECANVASMM_CFLAGS) $(JACK_CFLAGS) $(LXML2_CFLAGS)
+
+libomclient_a_SOURCES = \
+ ClientInterface.h \
+ OSCEngineInterface.h \
+ OSCEngineInterface.cpp \
+ OSCModelEngineInterface.h \
+ OSCModelEngineInterface.cpp \
+ OSCListener.h \
+ OSCListener.cpp \
+ SigClientInterface.h \
+ DirectSigClientInterface.h \
+ ThreadedSigClientInterface.h \
+ ThreadedSigClientInterface.cpp \
+ ModelEngineInterface.h \
+ ModelClientInterface.h \
+ ModelClientInterface.cpp \
+ PresetModel.h \
+ ControlModel.h \
+ ObjectController.h \
+ ObjectModel.h \
+ ObjectModel.cpp \
+ NodeModel.h \
+ NodeModel.cpp \
+ PortModel.h \
+ PatchModel.h \
+ PatchModel.cpp \
+ PluginModel.h \
+ PatchLibrarian.h \
+ PatchLibrarian.cpp \
+ ConnectionModel.h \
+ ConnectionModel.cpp \
+ $(top_srcdir)/src/common/util/Path.h \
+ $(top_srcdir)/src/common/interface/ClientInterface.h \
+ $(top_srcdir)/src/common/interface/EngineInterface.h
+
+SUBDIRS += patch_loader patches demolition
+
+endif # BUILD_CONSOLE_CLIENTS
+
+if BUILD_GTK_CLIENT
+SUBDIRS += gtk
+endif
+
diff --git a/src/libs/client/ModelClientInterface.cpp b/src/libs/client/ModelClientInterface.cpp
new file mode 100644
index 00000000..46754161
--- /dev/null
+++ b/src/libs/client/ModelClientInterface.cpp
@@ -0,0 +1,135 @@
+/* This file is part of Om. Copyright (C) 2005 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "ModelClientInterface.h"
+#include "PatchModel.h"
+#include "ConnectionModel.h"
+#include "PresetModel.h"
+#include "NodeModel.h"
+#include "PluginModel.h"
+
+namespace LibOmClient {
+
+
+void
+ModelClientInterface::new_plugin_model(PluginModel* pi)
+{
+}
+
+
+void
+ModelClientInterface::new_patch_model(PatchModel* pm)
+{
+}
+
+
+void
+ModelClientInterface::new_node_model(NodeModel* nm)
+{
+}
+
+
+void
+ModelClientInterface::new_port_model(PortModel* port_info)
+{
+}
+
+
+void
+ModelClientInterface::connection_model(ConnectionModel* cm)
+{
+}
+
+
+
+/* Implementations of ClientInterface functions to drive
+ * the above functions:
+ */
+
+
+
+void
+ModelClientInterface::new_plugin(const string& type,
+ const string& uri,
+ const string& name)
+{
+ PluginModel* plugin = new PluginModel(type, uri);
+ plugin->name(name);
+ new_plugin_model(plugin);
+}
+
+
+
+void
+ModelClientInterface::new_patch(const string& path, uint32_t poly)
+{
+ PatchModel* pm = new PatchModel(path, poly);
+ PluginModel* pi = new PluginModel(PluginModel::Patch);
+ pm->plugin(pi);
+ new_patch_model(pm);
+}
+
+
+
+void
+ModelClientInterface::new_node(const string& plugin_type,
+ const string& plugin_uri,
+ const string& node_path,
+ bool is_polyphonic,
+ uint32_t num_ports)
+{
+ cerr << "FIXME: NEW NODE\n";
+
+ PluginModel* plugin = new PluginModel(plugin_type, plugin_uri);
+
+ NodeModel* nm = new NodeModel(node_path);
+ nm->plugin(plugin);
+
+ new_node_model(nm);
+}
+
+
+
+void
+ModelClientInterface::new_port(const string& path,
+ const string& type,
+ bool is_output)
+{
+ PortModel::Type ptype = PortModel::CONTROL;
+ if (type != "AUDIO") ptype = PortModel::AUDIO;
+ else if (type != "CONTROL") ptype = PortModel::CONTROL;
+ else if (type != "MIDI") ptype = PortModel::MIDI;
+ else cerr << "[ModelClientInterface] WARNING: Unknown port type received (" << type << ")" << endl;
+
+ PortModel::Direction pdir = is_output ? PortModel::OUTPUT : PortModel::INPUT;
+
+ PortModel* port_model = new PortModel(path, ptype, pdir);
+ new_port_model(port_model);
+}
+
+
+
+void
+ModelClientInterface::connection(const string& src_port_path,
+ const string& dst_port_path)
+{
+ connection_model(new ConnectionModel(src_port_path, dst_port_path));
+}
+
+
+
+
+} // namespace LibOmClient
diff --git a/src/libs/client/ModelClientInterface.h b/src/libs/client/ModelClientInterface.h
new file mode 100644
index 00000000..1f5ea09d
--- /dev/null
+++ b/src/libs/client/ModelClientInterface.h
@@ -0,0 +1,87 @@
+/* This file is part of Om. Copyright (C) 2005 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef MODELCLIENTINTERFACE_H
+#define MODELCLIENTINTERFACE_H
+
+#include <string>
+#include <memory>
+using std::string; using std::auto_ptr;
+#include "interface/ClientInterface.h"
+
+namespace LibOmClient {
+
+class PatchModel;
+class NodeModel;
+class ConnectionModel;
+class PortModel;
+class PluginModel;
+
+
+/** A client interface that creates Model objects to represent the engine's state.
+ *
+ * This calls it's own methods with the models as parameters; clients can inherit
+ * this and implement a class with a similar interface to ClientInterface except
+ * with model classes passed where appropriate instead of primitives.
+ *
+ * \ingroup libomclient
+ */
+class ModelClientInterface : virtual public Om::Shared::ClientInterface
+{
+public:
+ ModelClientInterface(Om::Shared::ClientInterface& extend)
+ : Om::Shared::ClientInterface(extend)
+ {}
+
+ virtual ~ModelClientInterface() {}
+
+ // FIXME: make these auto_ptr's
+
+ virtual void new_plugin_model(PluginModel* pi);
+ virtual void new_patch_model(PatchModel* pm);
+ virtual void new_node_model(NodeModel* nm);
+ virtual void new_port_model(PortModel* port_info);
+ virtual void connection_model(ConnectionModel* cm);
+
+ // ClientInterface functions to drive the above:
+
+ virtual void new_plugin(const string& type,
+ const string& uri,
+ const string& name);
+
+ virtual void new_patch(const string& path, uint32_t poly);
+
+ virtual void new_node(const string& plugin_type,
+ const string& plugin_uri,
+ const string& node_path,
+ bool is_polyphonic,
+ uint32_t num_ports);
+
+ virtual void new_port(const string& path,
+ const string& data_type,
+ bool is_output);
+
+ virtual void connection(const string& src_port_path,
+ const string& dst_port_path);
+
+protected:
+ ModelClientInterface() {}
+};
+
+
+} // namespace LibOmClient
+
+#endif // MODELCLIENTINTERFACE_H
diff --git a/src/libs/client/ModelEngineInterface.h b/src/libs/client/ModelEngineInterface.h
new file mode 100644
index 00000000..aa041aef
--- /dev/null
+++ b/src/libs/client/ModelEngineInterface.h
@@ -0,0 +1,58 @@
+/* This file is part of Om. Copyright (C) 2005 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef MODELENGINEINTERFACE_H
+#define MODELENGINEINTERFACE_H
+
+#include <string>
+#include <lo/lo.h>
+#include "interface/EngineInterface.h"
+using std::string;
+
+/** \defgroup libomclient Client Library
+ */
+
+namespace LibOmClient {
+
+class NodeModel;
+class PresetModel;
+class PatchModel;
+class OSCListener;
+class ModelClientInterface;
+
+
+/** Model-based engine command interface.
+ *
+ * \ingroup libomclient
+ */
+class ModelEngineInterface
+{
+public:
+ virtual ~ModelEngineInterface() {}
+
+ virtual void create_patch_from_model(const PatchModel* pm) = 0;
+ virtual void create_node_from_model(const NodeModel* nm) = 0;
+
+ virtual void set_all_metadata(const NodeModel* nm) = 0;
+ virtual void set_preset(const string& patch_path, const PresetModel* pm) = 0;
+
+protected:
+ ModelEngineInterface() {}
+};
+
+} // namespace LibOmClient
+
+#endif // MODELENGINEINTERFACE_H
diff --git a/src/libs/client/NodeModel.cpp b/src/libs/client/NodeModel.cpp
new file mode 100644
index 00000000..efdae494
--- /dev/null
+++ b/src/libs/client/NodeModel.cpp
@@ -0,0 +1,117 @@
+/* This file is part of Om. Copyright (C) 2005 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "NodeModel.h"
+#include "PatchModel.h"
+#include <cassert>
+
+namespace LibOmClient {
+
+
+NodeModel::NodeModel(const Path& path)
+: ObjectModel(path),
+ m_polyphonic(false),
+ m_plugin(NULL),
+ m_x(0.0f),
+ m_y(0.0f)
+{
+}
+
+NodeModel::~NodeModel()
+{
+ for (PortModelList::iterator i = m_ports.begin(); i != m_ports.end(); ++i)
+ delete(*i);
+}
+
+
+void
+NodeModel::remove_port(const string& port_path)
+{
+ for (PortModelList::iterator i = m_ports.begin(); i != m_ports.end(); ++i) {
+ if ((*i)->path() == port_path) {
+ m_ports.erase(i);
+ break;
+ }
+ }
+}
+
+
+void
+NodeModel::clear()
+{
+ for (PortModelList::iterator i = m_ports.begin(); i != m_ports.end(); ++i)
+ delete (*i);
+
+ m_ports.clear();
+
+ assert(m_ports.empty());
+}
+
+
+void
+NodeModel::set_path(const Path& p)
+{
+ const string old_path = m_path;
+
+ ObjectModel::set_path(p);
+
+ for (PortModelList::iterator i = m_ports.begin(); i != m_ports.end(); ++i)
+ (*i)->set_path(m_path + "/" + (*i)->name());
+
+ if (parent_patch() != NULL && old_path.length() > 0)
+ parent_patch()->rename_node(old_path, p);
+}
+
+
+void
+NodeModel::add_port(PortModel* pm)
+{
+ assert(pm->name() != "");
+ assert(pm->path().length() > m_path.length());
+ assert(pm->path().substr(0, m_path.length()) == m_path);
+ assert(pm->parent() == NULL);
+ assert(get_port(pm->name()) == NULL);
+
+ m_ports.push_back(pm);
+ pm->set_parent(this);
+}
+
+
+PortModel*
+NodeModel::get_port(const string& port_name)
+{
+ assert(port_name.find("/") == string::npos);
+ for (PortModelList::iterator i = m_ports.begin(); i != m_ports.end(); ++i)
+ if ((*i)->name() == port_name)
+ return (*i);
+ return NULL;
+}
+
+
+void
+NodeModel::add_program(int bank, int program, const string& name)
+{
+ m_banks[bank][program] = name;
+}
+void
+NodeModel::remove_program(int bank, int program)
+{
+ m_banks[bank].erase(program);
+ if (m_banks[bank].size() == 0)
+ m_banks.erase(bank);
+}
+
+}
diff --git a/src/libs/client/NodeModel.h b/src/libs/client/NodeModel.h
new file mode 100644
index 00000000..af4171ed
--- /dev/null
+++ b/src/libs/client/NodeModel.h
@@ -0,0 +1,93 @@
+/* This file is part of Om. Copyright (C) 2005 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef NODEMODEL_H
+#define NODEMODEL_H
+
+#include <cstdlib>
+#include <map>
+#include <iostream>
+#include <string>
+#include "ObjectModel.h"
+#include "PortModel.h"
+#include "util/Path.h"
+
+using std::string; using std::map; using std::find;
+using std::cout; using std::cerr; using std::endl;
+
+namespace LibOmClient {
+
+class PatchModel;
+class PluginModel;
+
+
+/** Node model class, used by the client to store engine's state.
+ *
+ * \ingroup libomclient
+ */
+class NodeModel : public ObjectModel
+{
+public:
+ NodeModel(const Path& node_path);
+ virtual ~NodeModel();
+
+ PortModel* get_port(const string& port_name);
+ void add_port(PortModel* pm);
+ void remove_port(const string& port_path);
+
+ virtual void clear();
+
+ const map<int, map<int, string> >& get_programs() const { return m_banks; }
+ void add_program(int bank, int program, const string& name);
+ void remove_program(int bank, int program);
+
+ const PluginModel* plugin() const { return m_plugin; }
+ void plugin(const PluginModel* const pi) { m_plugin = pi; }
+
+ virtual void set_path(const Path& p);
+
+ int num_ports() const { return m_ports.size(); }
+ const PortModelList& ports() const { return m_ports; }
+ virtual bool polyphonic() const { return m_polyphonic; }
+ void polyphonic(bool b) { m_polyphonic = b; }
+ float x() const { return m_x; }
+ void x(float a) { m_x = a; }
+ float y() const { return m_y; }
+ void y(float a) { m_y = a; }
+
+ PatchModel* parent_patch() const { return (PatchModel*)m_parent; }
+
+protected:
+ bool m_polyphonic;
+ PortModelList m_ports; ///< List of ports (instead of map to preserve order)
+ const PluginModel* m_plugin; ///< The plugin this node is an instance of
+ float m_x; ///< Just metadata, here as an optimization for OmGtk
+ float m_y; ///< Just metadata, here as an optimization for OmGtk
+ map<int, map<int, string> > m_banks; ///< DSSI banks
+
+private:
+ // Prevent copies (undefined)
+ NodeModel(const NodeModel& copy);
+ NodeModel& operator=(const NodeModel& copy);
+};
+
+
+typedef map<string, NodeModel*> NodeModelMap;
+
+
+} // namespace LibOmClient
+
+#endif // NODEMODEL_H
diff --git a/src/libs/client/OSCEngineInterface.cpp b/src/libs/client/OSCEngineInterface.cpp
new file mode 100644
index 00000000..5ac598f1
--- /dev/null
+++ b/src/libs/client/OSCEngineInterface.cpp
@@ -0,0 +1,332 @@
+/* This file is part of Om. Copyright (C) 2006 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "OSCEngineInterface.h"
+#include "interface/ClientKey.h"
+
+namespace LibOmClient {
+
+/** Note the sending port is implicitly set by liblo, lo_send by default sends
+ * from the most recently created server, so create the OSC listener before
+ * this to have it all happen on the same port. Yeah, this is a big magic :/
+ */
+OSCEngineInterface::OSCEngineInterface(const string& engine_url)
+: _engine_url(engine_url)
+, _engine_addr(lo_address_new_from_url(engine_url.c_str()))
+, _id(0)
+{
+}
+
+
+OSCEngineInterface::~OSCEngineInterface()
+{
+ lo_address_free(_engine_addr);
+}
+
+
+/* *** EngineInterface implementation below here *** */
+
+
+/** Register with the engine via OSC.
+ *
+ * Note that this does not actually use 'key', since the engine creates
+ * it's own key for OSC clients (namely the incoming URL), for NAT
+ * traversal. It is a parameter to remain compatible with EngineInterface.
+ */
+void
+OSCEngineInterface::register_client(ClientKey key, CountedPtr<ClientInterface> client)
+{
+ // FIXME: use parameters.. er, somehow.
+ assert(_engine_addr);
+ lo_send(_engine_addr, "/om/engine/register_client", "i", next_id());
+}
+
+
+void
+OSCEngineInterface::unregister_client(ClientKey key)
+{
+ assert(_engine_addr);
+ lo_send(_engine_addr, "/om/engine/unregister_client", "i", next_id());
+}
+
+
+
+// Engine commands
+void
+OSCEngineInterface::load_plugins()
+{
+ assert(_engine_addr);
+ lo_send(_engine_addr, "/om/engine/load_plugins", "i", next_id());
+}
+
+
+void
+OSCEngineInterface::activate()
+{
+ assert(_engine_addr);
+ lo_send(_engine_addr, "/om/engine/activate", "i", next_id());
+}
+
+
+void
+OSCEngineInterface::deactivate()
+{
+ assert(_engine_addr);
+ lo_send(_engine_addr, "/om/engine/deactivate", "i", next_id());
+}
+
+
+void
+OSCEngineInterface::quit()
+{
+ assert(_engine_addr);
+ lo_send(_engine_addr, "/om/engine/quit", "i", next_id());
+}
+
+
+
+// Object commands
+
+void
+OSCEngineInterface::create_patch(const string& path,
+ uint32_t poly)
+{
+ assert(_engine_addr);
+ lo_send(_engine_addr, "/om/synth/create_patch", "isi",
+ next_id(),
+ path.c_str(),
+ poly);
+}
+
+
+void
+OSCEngineInterface::create_node(const string& path,
+ const string& plugin_type,
+ const string& plugin_uri,
+ bool polyphonic)
+{
+ assert(_engine_addr);
+ lo_send(_engine_addr, "/om/synth/create_node", "isssi",
+ next_id(),
+ path.c_str(),
+ plugin_type.c_str(),
+ plugin_uri.c_str(),
+ (polyphonic ? 1 : 0));
+}
+
+
+void
+OSCEngineInterface::rename(const string& old_path,
+ const string& new_name)
+{
+ assert(_engine_addr);
+ lo_send(_engine_addr, "/om/synth/rename", "iss",
+ next_id(),
+ old_path.c_str(),
+ new_name.c_str());
+}
+
+
+void
+OSCEngineInterface::destroy(const string& path)
+{
+ assert(_engine_addr);
+ lo_send(_engine_addr, "/om/synth/destroy", "is",
+ next_id(),
+ path.c_str());
+}
+
+
+void
+OSCEngineInterface::clear_patch(const string& patch_path)
+{
+ assert(_engine_addr);
+ lo_send(_engine_addr, "/om/synth/clear_patch", "is",
+ next_id(),
+ patch_path.c_str());
+}
+
+
+void
+OSCEngineInterface::enable_patch(const string& patch_path)
+{
+ assert(_engine_addr);
+ lo_send(_engine_addr, "/om/synth/enable_patch", "is",
+ next_id(),
+ patch_path.c_str());
+}
+
+
+void
+OSCEngineInterface::disable_patch(const string& patch_path)
+{
+ assert(_engine_addr);
+ lo_send(_engine_addr, "/om/synth/disable_patch", "is",
+ next_id(),
+ patch_path.c_str());
+}
+
+
+void
+OSCEngineInterface::connect(const string& src_port_path,
+ const string& dst_port_path)
+{
+ assert(_engine_addr);
+ lo_send(_engine_addr, "/om/synth/connect", "iss",
+ next_id(),
+ src_port_path.c_str(),
+ dst_port_path.c_str());
+}
+
+
+void
+OSCEngineInterface::disconnect(const string& src_port_path,
+ const string& dst_port_path)
+{
+ assert(_engine_addr);
+ lo_send(_engine_addr, "/om/synth/disconnect", "iss",
+ next_id(),
+ src_port_path.c_str(),
+ dst_port_path.c_str());
+}
+
+
+void
+OSCEngineInterface::disconnect_all(const string& node_path)
+{
+ assert(_engine_addr);
+ lo_send(_engine_addr, "/om/synth/disconnect_all", "is",
+ next_id(),
+ node_path.c_str());
+}
+
+
+void
+OSCEngineInterface::set_port_value(const string& port_path,
+ float val)
+{
+ assert(_engine_addr);
+ lo_send(_engine_addr, "/om/synth/set_port_value", "isf",
+ next_id(),
+ port_path.c_str(),
+ val);
+}
+
+
+void
+OSCEngineInterface::set_port_value(const string& port_path,
+ uint32_t voice,
+ float val)
+{
+ assert(_engine_addr);
+ lo_send(_engine_addr, "/om/synth/set_port_value", "isif",
+ next_id(),
+ port_path.c_str(),
+ voice,
+ val);
+}
+
+
+void
+OSCEngineInterface::set_port_value_queued(const string& port_path,
+ float val)
+{
+ assert(_engine_addr);
+ lo_send(_engine_addr, "/om/synth/set_port_value_queued", "isf",
+ next_id(),
+ port_path.c_str(),
+ val);
+}
+
+
+void
+OSCEngineInterface::set_program(const string& node_path,
+ uint32_t bank,
+ uint32_t program)
+{
+ assert(_engine_addr);
+ lo_send(_engine_addr,
+ (string("/dssi") + node_path + "/program").c_str(),
+ "ii",
+ bank,
+ program);
+}
+
+
+void
+OSCEngineInterface::midi_learn(const string& node_path)
+{
+ assert(_engine_addr);
+ lo_send(_engine_addr, "/om/synth/midi_learn", "is",
+ next_id(),
+ node_path.c_str());
+}
+
+
+void
+OSCEngineInterface::set_metadata(const string& obj_path,
+ const string& predicate,
+ const string& value)
+{
+ assert(_engine_addr);
+ lo_send(_engine_addr, "/om/metadata/set", "isss",
+ next_id(),
+ obj_path.c_str(),
+ predicate.c_str(),
+ value.c_str());
+}
+
+
+// Requests //
+
+void
+OSCEngineInterface::ping()
+{
+ assert(_engine_addr);
+ lo_send(_engine_addr, "/om/ping", "i", next_id());
+}
+
+
+void
+OSCEngineInterface::request_port_value(const string& port_path)
+{
+ assert(_engine_addr);
+ lo_send(_engine_addr, "/om/request/port_value", "is",
+ next_id(),
+ port_path.c_str());
+}
+
+
+void
+OSCEngineInterface::request_plugins()
+{
+ assert(_engine_addr);
+ lo_send(_engine_addr, "/om/request/plugins", "i", next_id());
+}
+
+
+void
+OSCEngineInterface::request_all_objects()
+{
+ assert(_engine_addr);
+ lo_send(_engine_addr, "/om/request/all_objects", "i", next_id());
+}
+
+
+
+} // namespace LibOmClient
+
+
diff --git a/src/libs/client/OSCEngineInterface.h b/src/libs/client/OSCEngineInterface.h
new file mode 100644
index 00000000..63157da1
--- /dev/null
+++ b/src/libs/client/OSCEngineInterface.h
@@ -0,0 +1,139 @@
+/* This file is part of Om. Copyright (C) 2006 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef OSCENGINEINTERFACE_H
+#define OSCENGINEINTERFACE_H
+
+#include <inttypes.h>
+#include <string>
+#include <lo/lo.h>
+#include "interface/EngineInterface.h"
+using std::string;
+using Om::Shared::EngineInterface;
+using Om::Shared::ClientInterface;
+using Om::Shared::ClientKey;
+
+
+namespace LibOmClient {
+
+/* OSC (via liblo) interface to the engine.
+ *
+ * Clients can use this opaquely as an EngineInterface* to control the engine
+ * over OSC (whether over a network or not, etc).
+ *
+ * \ingroup libomclient
+ */
+class OSCEngineInterface : public EngineInterface
+{
+public:
+ OSCEngineInterface(const string& engine_url);
+
+ ~OSCEngineInterface();
+
+ string engine_url() { return _engine_url; }
+
+ inline size_t next_id()
+ { if (_id != -1) { _id = (_id == -2) ? 0 : _id+1; } return _id; }
+
+ void enable_responses() { _id = 0; }
+ void disable_responses() { _id = -1; }
+
+
+ /* *** EngineInterface implementation below here *** */
+
+ // Client registration
+ void register_client(ClientKey key, CountedPtr<ClientInterface> client);
+ void unregister_client(ClientKey key);
+
+
+ // Engine commands
+ void load_plugins();
+ void activate();
+ void deactivate();
+ void quit();
+
+
+ // Object commands
+
+ void create_patch(const string& path,
+ uint32_t poly);
+
+ void create_node(const string& path,
+ const string& plugin_type,
+ const string& plugin_uri,
+ bool polyphonic);
+
+ void rename(const string& old_path,
+ const string& new_name);
+
+ void destroy(const string& path);
+
+ void clear_patch(const string& patch_path);
+
+ void enable_patch(const string& patch_path);
+
+ void disable_patch(const string& patch_path);
+
+ void connect(const string& src_port_path,
+ const string& dst_port_path);
+
+ void disconnect(const string& src_port_path,
+ const string& dst_port_path);
+
+ void disconnect_all(const string& node_path);
+
+ void set_port_value(const string& port_path,
+ float val);
+
+ void set_port_value(const string& port_path,
+ uint32_t voice,
+ float val);
+
+ void set_port_value_queued(const string& port_path,
+ float val);
+
+ void set_program(const string& node_path,
+ uint32_t bank,
+ uint32_t program);
+
+ void midi_learn(const string& node_path);
+
+ void set_metadata(const string& obj_path,
+ const string& predicate,
+ const string& value);
+
+ // Requests //
+
+ void ping();
+
+ void request_port_value(const string& port_path);
+
+ void request_plugins();
+
+ void request_all_objects();
+
+protected:
+ string _engine_url;
+ lo_address _engine_addr;
+ int _client_port;
+ int32_t _id;
+};
+
+
+} // namespace LibOmClient
+
+#endif // OSCENGINEINTERFACE_H
+
diff --git a/src/libs/client/OSCListener.cpp b/src/libs/client/OSCListener.cpp
new file mode 100644
index 00000000..503be47d
--- /dev/null
+++ b/src/libs/client/OSCListener.cpp
@@ -0,0 +1,413 @@
+/* This file is part of Om. Copyright (C) 2005 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "OSCListener.h"
+//#include "NodeModel.h"
+//#include "PluginModel.h"
+#include <list>
+#include <cassert>
+#include <cstring>
+#include <iostream>
+using std::cerr; using std::cout; using std::endl;
+
+namespace LibOmClient {
+
+
+/** Construct a OSCListener with a user-provided ModelClientInterface object for notification
+ * of engine events.
+ */
+OSCListener::OSCListener(int listen_port)
+: _listen_port(listen_port),
+ _st(NULL)//,
+// _receiving_node(false),
+// _receiving_node_model(NULL),
+// _receiving_node_num_ports(0),
+// _num_received_ports(0)
+{
+ start();
+}
+
+
+OSCListener::~OSCListener()
+{
+ stop();
+}
+
+
+void
+OSCListener::start()
+{
+ if (_st != NULL)
+ return;
+
+ if (_listen_port == 0) {
+ _st = lo_server_thread_new(NULL, error_cb);
+ _listen_port = lo_server_thread_get_port(_st);
+ } else {
+ char port_str[8];
+ snprintf(port_str, 8, "%d", _listen_port);
+ _st = lo_server_thread_new(port_str, error_cb);
+ }
+
+ if (_st == NULL) {
+ cerr << "[OSCListener] Could not start OSC listener. Aborting." << endl;
+ exit(EXIT_FAILURE);
+ } else {
+ cout << "[OSCListener] Started OSC listener on port " << lo_server_thread_get_port(_st) << endl;
+ }
+
+ // FIXME
+ lo_server_thread_add_method(_st, NULL, NULL, generic_cb, NULL);
+
+ //lo_server_thread_add_method(_st, "/om/response/ok", "i", om_response_ok_cb, this);
+ //lo_server_thread_add_method(_st, "/om/response/error", "is", om_responseerror_cb, this);
+
+ setup_callbacks();
+
+ // Display any uncaught messages to the console
+ //lo_server_thread_add_method(_st, NULL, NULL, unknown_cb, NULL);
+
+ lo_server_thread_start(_st);
+}
+
+
+void
+OSCListener::stop()
+{
+ if (_st != NULL) {
+ //unregister_client();
+ lo_server_thread_free(_st);
+ _st = NULL;
+ }
+}
+
+
+int
+OSCListener::generic_cb(const char* path, const char* types, lo_arg** argv, int argc, void* data, 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");
+
+ /*for (int i=0; i < argc; ++i) {
+ printf(" '%c' ", types[i]);
+ lo_arg_pp(lo_type(types[i]), argv[i]);
+ printf("\n");
+ }
+ printf("\n");*/
+
+ return 1; // not handled
+}
+
+
+void
+OSCListener::error_cb(int num, const char* msg, const char* path)
+{
+ cerr << "Got error from server: " << msg << endl;
+}
+
+
+
+int
+OSCListener::unknown_cb(const char* path, const char* types, lo_arg** argv, int argc, void* data, void* user_data)
+{
+ string msg = "Received unknown OSC message: ";
+ msg += path;
+
+ cerr << msg << endl;
+
+ return 0;
+}
+
+
+void
+OSCListener::setup_callbacks()
+{
+ lo_server_thread_add_method(_st, "/om/num_plugins", "i", num_plugins_cb, this);
+ lo_server_thread_add_method(_st, "/om/plugin", "sss", plugin_cb, this);
+ lo_server_thread_add_method(_st, "/om/new_patch", "si", new_patch_cb, this);
+ lo_server_thread_add_method(_st, "/om/destroyed", "s", destroyed_cb, this);
+ lo_server_thread_add_method(_st, "/om/patch_enabled", "s", patch_enabled_cb, this);
+ lo_server_thread_add_method(_st, "/om/patch_disabled", "s", patch_disabled_cb, this);
+ lo_server_thread_add_method(_st, "/om/patch_cleared", "s", patch_cleared_cb, this);
+ lo_server_thread_add_method(_st, "/om/object_renamed", "ss", object_renamed_cb, this);
+ lo_server_thread_add_method(_st, "/om/new_connection", "ss", connection_cb, this);
+ lo_server_thread_add_method(_st, "/om/disconnection", "ss", disconnection_cb, this);
+ lo_server_thread_add_method(_st, "/om/new_node", "sssii", new_node_cb, this);
+ lo_server_thread_add_method(_st, "/om/new_port", "ssi", new_port_cb, this);
+ lo_server_thread_add_method(_st, "/om/metadata/update", "sss", metadata_update_cb, this);
+ lo_server_thread_add_method(_st, "/om/control_change", "sf", control_change_cb, this);
+ lo_server_thread_add_method(_st, "/om/program_add", "siis", program_add_cb, this);
+ lo_server_thread_add_method(_st, "/om/program_remove", "sii", program_remove_cb, this);
+}
+
+
+/** Catches errors that aren't a direct result of a client request.
+ */
+int
+OSCListener::m_error_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ cerr << "ERROR: " << argv[0]->s << endl;
+ // FIXME
+ //error((char*)argv[0]);
+ return 0;
+}
+
+
+int
+OSCListener::m_new_patch_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ new_patch(&argv[0]->s, argv[1]->i); // path, poly
+ return 0;
+}
+
+
+int
+OSCListener::m_destroyed_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ object_destroyed((const char*)&argv[0]->s);
+ return 0;
+}
+
+
+int
+OSCListener::m_patch_enabled_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ patch_enabled((const char*)&argv[0]->s);
+ return 0;
+}
+
+
+int
+OSCListener::m_patch_disabled_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ patch_disabled((const char*)&argv[0]->s);
+ return 0;
+}
+
+
+int
+OSCListener::m_patch_cleared_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ patch_cleared((const char*)&argv[0]->s);
+ return 0;
+}
+
+
+int
+OSCListener::m_object_renamed_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ object_renamed((const char*)&argv[0]->s, (const char*)&argv[1]->s);
+ return 0;
+}
+
+
+int
+OSCListener::m_connection_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* const src_port_path = &argv[0]->s;
+ const char* const dst_port_path = &argv[1]->s;
+
+ connection(src_port_path, dst_port_path);
+
+ return 0;
+}
+
+
+int
+OSCListener::m_disconnection_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* src_port_path = &argv[0]->s;
+ const char* dst_port_path = &argv[1]->s;
+
+ disconnection(src_port_path, dst_port_path);
+
+ return 0;
+}
+
+
+/** Notification of a new node creation.
+ */
+int
+OSCListener::m_new_node_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* type = &argv[0]->s;
+ const char* uri = &argv[1]->s;
+ const char* node_path = &argv[2]->s;
+ const int32_t poly = argv[3]->i;
+ const int32_t num_ports = argv[4]->i;
+
+ new_node(type, uri, node_path, poly, num_ports);
+
+ /*_receiving_node_model = new NodeModel(node_path);
+ _receiving_node_model->polyphonic((poly == 1));
+ _receiving_node_num_ports = num_ports;
+
+ PluginModel* pi = new PluginModel(type, uri);
+ _receiving_node_model->plugin(pi);
+
+ _receiving_node = true;
+ _num_received_ports = 0;
+ */
+ return 0;
+}
+
+
+/** Notification of a new port creation.
+ */
+int
+OSCListener::m_new_port_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* port_path = &argv[0]->s;
+ const char* type = &argv[1]->s;
+ bool is_output = (argv[2]->i == 1);
+ /*const char* direction = &argv[2]->s;
+ const char* hint = &argv[3]->s;
+ float default_val = argv[4]->f;
+ float min_val = argv[5]->f;
+ float max_val = argv[6]->f;*/
+
+ new_port(port_path, type, is_output);
+#if 0
+ PortModel::Type ptype = PortModel::CONTROL;
+ if (!strcmp(type, "AUDIO")) ptype = PortModel::AUDIO;
+ else if (!strcmp(type, "CONTROL")) ptype = PortModel::CONTROL;
+ else if (!strcmp(type, "MIDI")) ptype = PortModel::MIDI;
+ else cerr << "[OSCListener] WARNING: Unknown port type received (" << type << ")" << endl;
+
+#if 0
+ PortModel::Direction pdir = PortModel::INPUT;
+ if (!strcmp(direction, "INPUT")) pdir = PortModel::INPUT;
+ else if (!strcmp(direction, "OUTPUT")) pdir = PortModel::OUTPUT;
+ else cerr << "[OSCListener] WARNING: Unknown port direction received (" << direction << ")" << endl;
+#endif
+ PortModel::Direction pdir = is_output ? PortModel::OUTPUT : PortModel::INPUT;
+
+/*
+ PortModel::Hint phint = PortModel::NONE;
+ if (!strcmp(hint, "LOGARITHMIC")) phint = PortModel::LOGARITHMIC;
+ else if (!strcmp(hint, "INTEGER")) phint = PortModel::INTEGER;
+ else if (!strcmp(hint, "TOGGLE")) phint = PortModel::TOGGLE;
+
+ PortModel* port_model = new PortModel(port_path, ptype, pdir, phint, default_val, min_val, max_val);
+*/
+ PortModel* port_model = new PortModel(port_path, ptype, pdir);
+ if (m_receiving_node) {
+ assert(m_receiving_node_model);
+ m_receiving_node_model->add_port(port_model);
+ ++m_num_received_ports;
+
+ // If transmission is done, send new node to client
+ if (m_num_received_ports == m_receiving_node_num_ports) {
+ new_node_model(m_receiving_node_model);
+ m_receiving_node = false;
+ m_receiving_node_model = NULL;
+ m_num_received_ports = 0;
+ }
+ } else {
+ new_port_model(port_model);
+ }
+
+#endif
+ return 0;
+}
+
+
+/** Notification of a new or updated piece of metadata.
+ */
+int
+OSCListener::m_metadata_update_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* obj_path = &argv[0]->s;
+ const char* key = &argv[1]->s;
+ const char* value = &argv[2]->s;
+
+ metadata_update(obj_path, key, value);
+
+ return 0;
+}
+
+
+int
+OSCListener::m_control_change_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* const port_path = &argv[0]->s;
+ const float value = argv[1]->f;
+
+ control_change(port_path, value);
+
+ return 0;
+}
+
+
+/** Number of plugins in engine, should precede /om/plugin messages in response
+ * to a /om/send_plugins
+ */
+int
+OSCListener::m_num_plugins_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ /** Not worth it implementing a ModelClientInterface callback for this (?)
+ * Or I'm just lazy. FIXME? */
+ num_plugins(argv[0]->i);
+
+ return 0;
+}
+
+
+/** A plugin info response from the server, in response to a /send_plugins
+ */
+int
+OSCListener::m_plugin_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ assert(argc == 3 && !strcmp(types, "sss"));
+ new_plugin(&argv[0]->s, &argv[1]->s, &argv[2]->s); // type, uri
+
+ return 0;
+}
+
+
+int
+OSCListener::m_program_add_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* node_path = &argv[0]->s;
+ int32_t bank = argv[1]->i;
+ int32_t program = argv[2]->i;
+ const char* name = &argv[3]->s;
+
+ program_add(node_path, bank, program, name);
+
+ return 0;
+}
+
+
+int
+OSCListener::m_program_remove_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
+{
+ const char* node_path = &argv[0]->s;
+ int32_t bank = argv[1]->i;
+ int32_t program = argv[2]->i;
+
+ program_remove(node_path, bank, program);
+
+ return 0;
+}
+
+
+} // namespace LibOmClient
diff --git a/src/libs/client/OSCListener.h b/src/libs/client/OSCListener.h
new file mode 100644
index 00000000..d0b9cc1c
--- /dev/null
+++ b/src/libs/client/OSCListener.h
@@ -0,0 +1,116 @@
+/* This file is part of Om. Copyright (C) 2005 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+#ifndef OSCLISTENER_H
+#define OSCLISTENER_H
+
+#include <cstdlib>
+#include <lo/lo.h>
+#include "interface/ClientInterface.h"
+
+namespace LibOmClient {
+
+//class NodeModel;
+//class PresetModel;
+
+/* 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 m_##name##_cb (LO_HANDLER_ARGS);\
+inline static int name##_cb(LO_HANDLER_ARGS, void* osc_listener)\
+{ return ((OSCListener*)osc_listener)->m_##name##_cb(path, types, argv, argc, msg); }
+
+
+/** Callbacks for "notification band" OSC messages.
+ *
+ * Receives all notification of engine state, but not replies on the "control
+ * band". See OSC namespace documentation for details.
+ *
+ * Right now this class and Comm share the same lo_server_thread and the barrier
+ * between them is a bit odd, but eventually this class will be able to listen
+ * on a completely different port (ie have it's own lo_server_thread) to allow
+ * things like listening to the notification band over TCP while sending commands
+ * on the control band over UDP.
+ *
+ * \ingroup libomclient
+ */
+class OSCListener : virtual public Om::Shared::ClientInterface
+{
+public:
+ OSCListener(int listen_port);
+ ~OSCListener();
+
+ void start();
+ void stop();
+
+ int listen_port() { return _listen_port; }
+ string listen_url() { return lo_server_thread_get_url(_st); }
+
+private:
+ // Prevent copies
+ OSCListener(const OSCListener& copy);
+ OSCListener& operator=(const OSCListener& copy);
+
+ void setup_callbacks();
+
+ static void error_cb(int num, const char* msg, const char* path);
+ static int generic_cb(const char* path, const char* types, lo_arg** argv, int argc, void* data, void* user_data);
+ static int unknown_cb(const char* path, const char* types, lo_arg** argv, int argc, void* data, void* osc_receiver);
+ /*
+ inline static int om_response_ok_cb(const char* path, const char* types, lo_arg** argv, int argc, void* data, void* comm);
+ int m_om_response_ok_cb(const char* path, const char* types, lo_arg** argv, int argc, void* data);
+ inline static int om_response_error_cb(const char* path, const char* types, lo_arg** argv, int argc, void* data, void* comm);
+ int m_om_response_error_cb(const char* path, const char* types, lo_arg** argv, int argc, void* data);
+*/
+ int _listen_port;
+ lo_server_thread _st;
+
+ // Used for receiving nodes - multiple messages are received before
+ // sending an event to the client (via ModelClientInterface)
+ //bool _receiving_node;
+ //NodeModel* _receiving_node_model;
+ //int32_t _receiving_node_num_ports;
+ //int32_t _num_received_ports;
+
+ LO_HANDLER(error);
+ LO_HANDLER(num_plugins);
+ LO_HANDLER(plugin);
+ LO_HANDLER(plugin_list_end);
+ LO_HANDLER(new_patch);
+ LO_HANDLER(destroyed);
+ LO_HANDLER(patch_enabled);
+ LO_HANDLER(patch_disabled);
+ LO_HANDLER(patch_cleared);
+ LO_HANDLER(object_renamed);
+ LO_HANDLER(connection);
+ LO_HANDLER(disconnection);
+ LO_HANDLER(new_node);
+ LO_HANDLER(new_port);
+ LO_HANDLER(metadata_update);
+ LO_HANDLER(control_change);
+ LO_HANDLER(program_add);
+ LO_HANDLER(program_remove);
+};
+
+
+} // namespace LibOmClient
+
+#endif // OSCLISTENER_H
diff --git a/src/libs/client/OSCModelEngineInterface.cpp b/src/libs/client/OSCModelEngineInterface.cpp
new file mode 100644
index 00000000..9e648141
--- /dev/null
+++ b/src/libs/client/OSCModelEngineInterface.cpp
@@ -0,0 +1,364 @@
+/* This file is part of Om. Copyright (C) 2005 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "OSCModelEngineInterface.h"
+#include <list>
+#include <cassert>
+#include <cstdlib>
+#include <cstring>
+#include <iostream>
+#include <unistd.h>
+#include "OSCListener.h"
+#include "PatchModel.h"
+#include "ConnectionModel.h"
+#include "PresetModel.h"
+#include "ControlModel.h"
+#include "NodeModel.h"
+#include "PluginModel.h"
+
+using std::cerr; using std::cout; using std::endl;
+
+namespace LibOmClient {
+
+
+/** Construct a OSCModelEngineInterface with a user-provided ModelClientInterface object for notification
+ * of engine events.
+ */
+OSCModelEngineInterface::OSCModelEngineInterface(const string& engine_url)
+: OSCEngineInterface(engine_url),
+ m_request_id(0),
+ m_is_attached(false),
+ m_is_registered(false)
+ /*m_blocking(false),
+ m_waiting_for_response(false),
+ m_wait_response_id(0),
+ m_response_received(false),
+ m_wait_response_was_affirmative(false),
+ m_response_semaphore(0)*/
+{
+}
+
+
+OSCModelEngineInterface::~OSCModelEngineInterface()
+{
+ detach();
+}
+
+
+/** Attempt to connect to the Om engine and notify it of our existance.
+ *
+ * Passing a client_port of 0 will automatically choose a free port. If the
+ * @a block parameter is true, this function will not return until a connection
+ * has successfully been made.
+ */
+void
+OSCModelEngineInterface::attach(bool block)
+{
+ cerr << "FIXME: listen thread\n";
+ //start_listen_thread(_client_port);
+
+ /*if (engine_url == "") {
+ string local_url = m_osc_listener->listen_url().substr(
+ 0, m_osc_listener->listen_url().find_last_of(":"));
+ local_url.append(":16180");
+ _engine_addr = lo_address_new_from_url(local_url.c_str());
+ } else {
+ _engine_addr = lo_address_new_from_url(engine_url.c_str());
+ }
+ */
+ _engine_addr = lo_address_new_from_url(_engine_url.c_str());
+
+ if (_engine_addr == NULL) {
+ cerr << "Unable to connect, aborting." << endl;
+ exit(EXIT_FAILURE);
+ }
+
+ char* lo_url = lo_address_get_url(_engine_addr);
+ cout << "[OSCModelEngineInterface] Attempting to contact engine at " << lo_url << " ..." << endl;
+
+ this->ping();
+
+ m_is_attached = true; // FIXME
+
+ /*if (block) {
+ set_wait_response_id(request_id);
+
+ while (1) {
+ if (m_response_semaphore.try_wait() != 0) {
+ cout << ".";
+ cout.flush();
+ ping(request_id);
+ usleep(100000);
+ } else {
+ cout << " connected." << endl;
+ m_waiting_for_response = false;
+ break;
+ }
+ }
+ }
+ */
+
+ free(lo_url);
+}
+
+void
+OSCModelEngineInterface::detach()
+{
+ m_is_attached = false;
+}
+
+#if 0
+void
+OSCModelEngineInterface::start_listen_thread(int client_port)
+{
+ if (m_st != NULL)
+ return;
+
+ if (client_port == 0) {
+ m_st = lo_server_thread_new(NULL, error_cb);
+ } else {
+ char port_str[8];
+ snprintf(port_str, 8, "%d", client_port);
+ m_st = lo_server_thread_new(port_str, error_cb);
+ }
+
+ if (m_st == NULL) {
+ cerr << "[OSCModelEngineInterface] Could not start OSC listener. Aborting." << endl;
+ exit(EXIT_FAILURE);
+ } else {
+ cout << "[OSCModelEngineInterface] Started OSC listener on port " << lo_server_thread_get_port(m_st) << endl;
+ }
+
+ lo_server_thread_add_method(m_st, NULL, NULL, generic_cb, NULL);
+
+ lo_server_thread_add_method(m_st, "/om/response/ok", "i", om_response_ok_cb, this);
+ lo_server_thread_add_method(m_st, "/om/response/error", "is", om_response_error_cb, this);
+
+
+ m_osc_listener = new OSCListener(m_st, m_client_hooks);
+ m_osc_listener->setup_callbacks();
+
+ // Display any uncaught messages to the console
+ lo_server_thread_add_method(m_st, NULL, NULL, unknown_cb, NULL);
+
+ lo_server_thread_start(m_st);
+}
+#endif
+
+///// OSC Commands /////
+
+
+
+/** Load a node.
+ */
+void
+OSCModelEngineInterface::create_node_from_model(const NodeModel* nm)
+{
+ assert(_engine_addr);
+
+ // Load by URI
+ if (nm->plugin()->uri().length() > 0) {
+ lo_send(_engine_addr, "/om/synth/create_node", "isssi", next_id(),
+ nm->path().c_str(),
+ nm->plugin()->type_string(),
+ nm->plugin()->uri().c_str(),
+ (nm->polyphonic() ? 1 : 0));
+ // Load by libname, label
+ } else {
+ //assert(nm->plugin()->lib_name().length() > 0);
+ assert(nm->plugin()->plug_label().length() > 0);
+ lo_send(_engine_addr, "/om/synth/create_node", "issssi", next_id(),
+ nm->path().c_str(),
+ nm->plugin()->type_string(),
+ nm->plugin()->lib_name().c_str(),
+ nm->plugin()->plug_label().c_str(),
+ (nm->polyphonic() ? 1 : 0));
+ }
+}
+
+
+/** Create a patch.
+ */
+void
+OSCModelEngineInterface::create_patch_from_model(const PatchModel* pm)
+{
+ assert(_engine_addr);
+ lo_send(_engine_addr, "/om/synth/create_patch", "isi", next_id(), pm->path().c_str(), pm->poly());
+}
+
+
+/** Notify LASH restoring is finished */
+/*
+void
+OSCModelEngineInterface::lash_restore_finished()
+{
+ assert(_engine_addr != NULL);
+ int id = m_request_id++;
+ lo_send(_engine_addr, "/om/lash/restore_finished", "i", id);
+}
+*/
+#if 0
+/** Set/add a piece of metadata.
+ */
+void
+OSCModelEngineInterface::set_metadata(const string& obj_path,
+ const string& key, const string& value)
+{
+ assert(_engine_addr != NULL);
+ int id = m_request_id++;
+
+ // Deal with the "special" DSSI metadata strings
+ if (key.substr(0, 16) == "dssi-configure--") {
+ string path = "/dssi" + obj_path + "/configure";
+ string dssi_key = key.substr(16);
+ lo_send(_engine_addr, path.c_str(), "ss", dssi_key.c_str(), value.c_str());
+ } else if (key == "dssi-program") {
+ string path = "/dssi" + obj_path + "/program";
+ string dssi_bank_str = value.substr(0, value.find("/"));
+ int dssi_bank = atoi(dssi_bank_str.c_str());
+ string dssi_program_str = value.substr(value.find("/")+1);
+ int dssi_program = atoi(dssi_program_str.c_str());
+ lo_send(_engine_addr, path.c_str(), "ii", dssi_bank, dssi_program);
+ }
+
+ // Normal metadata
+ lo_send(_engine_addr, "/om/metadata/set", "isss", id,
+ obj_path.c_str(), key.c_str(), value.c_str());
+}
+#endif
+
+/** Set all pieces of metadata in a NodeModel.
+ */
+void
+OSCModelEngineInterface::set_all_metadata(const NodeModel* nm)
+{
+ assert(_engine_addr != NULL);
+
+ for (map<string, string>::const_iterator i = nm->metadata().begin(); i != nm->metadata().end(); ++i)
+ set_metadata(nm->path(), (*i).first, (*i).second.c_str());
+}
+
+
+/** Set a preset by setting all relevant controls for a patch.
+ */
+void
+OSCModelEngineInterface::set_preset(const string& patch_path, const PresetModel* const pm)
+{
+ assert(patch_path.find("//") == string::npos);
+ for (list<ControlModel>::const_iterator i = pm->controls().begin(); i != pm->controls().end(); ++i) {
+ set_port_value_queued((*i).port_path(), (*i).value());
+ usleep(1000);
+ }
+}
+
+
+///// Requests /////
+
+
+#if 0
+/** Sets the response ID to be waited for on the next call to wait_for_response()
+ */
+
+void
+OSCModelEngineInterface::set_wait_response_id(int id)
+{
+ assert(!m_waiting_for_response);
+ m_wait_response_id = id;
+ m_response_received = false;
+ m_waiting_for_response = true;
+}
+
+
+/** Waits for the response set by set_wait_response() from the server.
+ *
+ * Returns whether or not the response was positive (ie a success message)
+ * or negative (ie an error)
+ */
+bool
+OSCModelEngineInterface::wait_for_response()
+{
+ cerr << "[OSCModelEngineInterface] Waiting for response " << m_wait_response_id << ": ";
+ bool ret = true;
+
+ assert(m_waiting_for_response);
+ assert(!m_response_received);
+
+ while (!m_response_received)
+ m_response_semaphore.wait();
+
+ cerr << " received." << endl;
+
+ m_waiting_for_response = false;
+ ret = m_wait_response_was_affirmative;
+
+ return ret;
+}
+#endif
+
+///// Static OSC callbacks //////
+
+
+//// End static callbacks, member callbacks below ////
+
+/*
+int
+OSCModelEngineInterface::m_om_response_ok_cb(const char* path, const char* types, lo_arg** argv, int argc, void* data)
+{
+ assert(argc == 1 && !strcmp(types, "i"));
+
+ // FIXME
+ if (!m_is_attached)
+ m_is_attached = true;
+
+ if (m_waiting_for_response) {
+ const int request_id = argv[0]->i;
+
+ if (request_id == m_wait_response_id) {
+ m_response_received = true;
+ m_wait_response_was_affirmative = true;
+ m_response_semaphore.post();
+ }
+ }
+
+ return 0;
+}
+
+
+int
+OSCModelEngineInterface::m_om_response_error_cb(const char* path, const char* types, lo_arg** argv, int argc, void* data)
+{
+ assert(argc == 2 && !strcmp(types, "is"));
+
+ const int request_id = argv[0]->i;
+ const char* msg = &argv[1]->s;
+
+ if (m_waiting_for_response) {
+ if (request_id == m_wait_response_id) {
+ m_response_received = true;
+ m_wait_response_was_affirmative = false;
+ m_response_semaphore.post();
+ }
+ }
+
+ cerr << "ERROR: " << msg << endl;
+ //if (m_client_hooks != NULL)
+ // m_client_hooks->error(msg);
+
+ return 0;
+}
+*/
+
+} // namespace LibOmClient
diff --git a/src/libs/client/OSCModelEngineInterface.h b/src/libs/client/OSCModelEngineInterface.h
new file mode 100644
index 00000000..4400a1b4
--- /dev/null
+++ b/src/libs/client/OSCModelEngineInterface.h
@@ -0,0 +1,117 @@
+/* This file is part of Om. Copyright (C) 2005 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef OSCCONTROLLER_H
+#define OSCCONTROLLER_H
+
+#include <string>
+#include <lo/lo.h>
+#include "util/Semaphore.h"
+#include "interface/EngineInterface.h"
+#include "OSCEngineInterface.h"
+#include "ModelEngineInterface.h"
+using std::string;
+
+/** \defgroup libomclient Client Library
+ */
+
+namespace LibOmClient {
+
+class NodeModel;
+class PresetModel;
+class PatchModel;
+class OSCListener;
+class ModelClientInterface;
+
+
+/** Old model-based OSC engine command interface.
+ *
+ * This is an old class from before when the well-defined interfaces between
+ * engine and client were defined. I've wrapped it around OSCEngineInterface
+ * so all the common functions are implemented there, and implemented the
+ * remaining functions using those, for compatibility. Hopefully something
+ * better gets figured out and this can go away completely, but for now this
+ * gets the existing clients working through EngineInterface in the easiest
+ * way possible. This class needs to die.
+ *
+ * Old comment:
+ * Handles all OSC communication on the "control band". For the time being,
+ * manages the OSCListener which handles the "notification band", but this
+ * will change in the future (for complete separation). See OSC namespace
+ * documentation for more details.
+ *
+ * \ingroup libomclient
+ */
+class OSCModelEngineInterface : public OSCEngineInterface, public ModelEngineInterface
+{
+public:
+ //OSCModelEngineInterface(ModelClientInterface* const client_hooks, const string& engine_url);
+ OSCModelEngineInterface(const string& engine_url);
+ ~OSCModelEngineInterface();
+
+ void attach(bool block = true);
+ void detach();
+
+ bool is_attached() { return m_is_attached; }
+
+ // FIXME: reimplement
+ void set_wait_response_id(int32_t id) {}
+ bool wait_for_response() { return false; }
+ int get_next_request_id() { return m_request_id++; }
+
+ /* *** Model alternatives to EngineInterface functions below *** */
+
+ void create_patch_from_model(const PatchModel* pm);
+ void create_node_from_model(const NodeModel* nm);
+
+ void set_all_metadata(const NodeModel* nm);
+ void set_preset(const string& patch_path, const PresetModel* const pm);
+
+protected:
+ void start_listen_thread();
+
+ int m_request_id;
+
+ bool m_is_attached;
+ bool m_is_registered;
+ /*
+ bool m_blocking;
+ bool m_waiting_for_response;
+ int m_wait_response_id;
+ bool m_response_received;
+ bool m_wait_response_was_affirmative;
+
+ Semaphore m_response_semaphore;
+ */
+private:
+ // Prevent copies
+ OSCModelEngineInterface(const OSCModelEngineInterface& copy);
+ OSCModelEngineInterface& operator=(const OSCModelEngineInterface& copy);
+};
+/*
+inline int
+OSCModelEngineInterface::om_response_ok_cb(const char* path, const char* types, lo_arg** argv, int argc, void* data, void* comm) {
+ return ((OSCModelEngineInterface*)comm)->m_om_response_ok_cb(path, types, argv, argc, data);
+}
+
+inline int
+OSCModelEngineInterface::om_response_error_cb(const char* path, const char* types, lo_arg** argv, int argc, void* data, void* comm) {
+ return ((OSCModelEngineInterface*)comm)->m_om_response_error_cb(path, types, argv, argc, data);
+}
+*/
+} // namespace LibOmClient
+
+#endif // OSCCONTROLLER_H
diff --git a/src/libs/client/ObjectController.h b/src/libs/client/ObjectController.h
new file mode 100644
index 00000000..c79ac24d
--- /dev/null
+++ b/src/libs/client/ObjectController.h
@@ -0,0 +1,43 @@
+/* This file is part of Om. Copyright (C) 2005 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef OBJECTCONTROLLER_H
+#define OBJECTCONTROLLER_H
+
+namespace LibOmClient {
+
+
+/** A trivial base class for controllers of an ObjectModel.
+ *
+ * This is so ObjectModels can have pointers to app-specified controllers,
+ * and the pointer relationships in models (ie PatchModel has pointers to
+ * all it's NodeModel children, etc) can be used to find controllers of
+ * Models, rather than having a parallel structure of pointers in the
+ * app's controllers.
+ *
+ * \ingroup libomclient
+ */
+class ObjectController
+{
+public:
+ virtual ~ObjectController() {}
+};
+
+
+} // namespace LibOmClient
+
+
+#endif // OBJECTCONTROLLER_H
diff --git a/src/libs/client/ObjectModel.cpp b/src/libs/client/ObjectModel.cpp
new file mode 100644
index 00000000..cb196f8a
--- /dev/null
+++ b/src/libs/client/ObjectModel.cpp
@@ -0,0 +1,64 @@
+/* This file is part of Om. Copyright (C) 2005 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "ObjectModel.h"
+
+namespace LibOmClient {
+
+
+ObjectModel::ObjectModel(const string& path)
+: m_path(path),
+ m_parent(NULL),
+ m_controller(NULL)
+{
+}
+
+
+/** Get a piece of metadata for this objeect.
+ *
+ * @return Metadata value with key @a key, empty string otherwise.
+ */
+string
+ObjectModel::get_metadata(const string& key) const
+{
+ map<string,string>::const_iterator i = m_metadata.find(key);
+ if (i != m_metadata.end())
+ return (*i).second;
+ else
+ return "";
+}
+
+
+/** The base path for children of this Object.
+ *
+ * (This is here to avoid needing special cases for the root patch everywhere).
+ */
+string
+ObjectModel::base_path() const
+{
+ return (path() == "/") ? "/" : path() + "/";
+}
+
+
+void
+ObjectModel::set_controller(ObjectController* c)
+{
+ assert(m_controller == NULL);
+ m_controller = c;
+}
+
+} // namespace LibOmClient
+
diff --git a/src/libs/client/ObjectModel.h b/src/libs/client/ObjectModel.h
new file mode 100644
index 00000000..be58a00f
--- /dev/null
+++ b/src/libs/client/ObjectModel.h
@@ -0,0 +1,84 @@
+/* This file is part of Om. Copyright (C) 2005 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef OBJECTMODEL_H
+#define OBJECTMODEL_H
+
+#include <cstdlib>
+#include <map>
+#include <iostream>
+#include <string>
+#include <algorithm>
+#include <cassert>
+#include "util/Path.h"
+
+using std::string; using std::map; using std::find;
+using std::cout; using std::cerr; using std::endl;
+using Om::Path;
+
+namespace LibOmClient {
+
+class ObjectController;
+
+
+/** Base class for all OmObject models (NodeModel, PatchModel, PortModel).
+ *
+ * \ingroup libomclient
+ */
+class ObjectModel
+{
+public:
+ ObjectModel(const string& path);
+ ObjectModel() : m_path("/UNINITIALIZED"), m_parent(NULL) {} // FIXME: remove
+
+ virtual ~ObjectModel() {}
+
+ const map<string, string>& metadata() const { return m_metadata; }
+ string get_metadata(const string& key) const;
+ void set_metadata(const string& key, const string& value)
+ { assert(value.length() > 0); m_metadata[key] = value; }
+
+ inline const Path& path() const { return m_path; }
+ virtual void set_path(const Path& p) { m_path = p; }
+
+ ObjectModel* parent() const { return m_parent; }
+ virtual void set_parent(ObjectModel* p) { m_parent = p; }
+
+ ObjectController* controller() const { return m_controller; }
+
+ void set_controller(ObjectController* c);
+
+ // Convenience functions
+ string base_path() const;
+ const string name() const { return m_path.name(); }
+
+protected:
+ Path m_path;
+ ObjectModel* m_parent;
+ ObjectController* m_controller;
+
+ map<string,string> m_metadata;
+
+private:
+ // Prevent copies (undefined)
+ ObjectModel(const ObjectModel& copy);
+ ObjectModel& operator=(const ObjectModel& copy);
+};
+
+
+} // namespace LibOmClient
+
+#endif // OBJECTMODEL_H
diff --git a/src/libs/client/PatchLibrarian.cpp b/src/libs/client/PatchLibrarian.cpp
new file mode 100644
index 00000000..65323435
--- /dev/null
+++ b/src/libs/client/PatchLibrarian.cpp
@@ -0,0 +1,834 @@
+/* This file is part of Om. Copyright (C) 2005 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "PatchLibrarian.h"
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/xpath.h>
+#include "PatchModel.h"
+#include "NodeModel.h"
+#include "ModelClientInterface.h"
+#include "ConnectionModel.h"
+#include "PortModel.h"
+#include "PresetModel.h"
+#include "OSCModelEngineInterface.h"
+#include "PluginModel.h"
+#include "util/Path.h"
+#include <iostream>
+#include <fstream>
+#include <vector>
+#include <utility> // for pair, make_pair
+#include <cassert>
+#include <cstring>
+#include <string>
+#include <unistd.h> // for usleep
+#include <cstdlib> // for atof
+#include <cmath>
+
+using std::string; using std::vector; using std::pair;
+using std::cerr; using std::cout; using std::endl;
+
+namespace LibOmClient {
+
+
+/** Searches for the filename passed in the path, returning the full
+ * path of the file, or the empty string if not found.
+ *
+ * This function tries to be as friendly a black box as possible - if the path
+ * passed is an absolute path and the file is found there, it will return
+ * that path, etc.
+ *
+ * additional_path is a list (colon delimeted as usual) of additional
+ * directories to look in. ie the directory the parent patch resides in would
+ * be a good idea to pass as additional_path, in the case of a subpatch.
+ */
+string
+PatchLibrarian::find_file(const string& filename, const string& additional_path)
+{
+ string search_path = additional_path + ":" + m_patch_path;
+
+ // Try to open the raw filename first
+ std::ifstream is(filename.c_str(), std::ios::in);
+ if (is.good()) {
+ is.close();
+ return filename;
+ }
+
+ string directory;
+ string full_patch_path = "";
+
+ while (search_path != "") {
+ directory = search_path.substr(0, search_path.find(':'));
+ if (search_path.find(':') != string::npos)
+ search_path = search_path.substr(search_path.find(':')+1);
+ else
+ search_path = "";
+
+ full_patch_path = directory +"/"+ filename;
+
+ std::ifstream is;
+ is.open(full_patch_path.c_str(), std::ios::in);
+
+ if (is.good()) {
+ is.close();
+ return full_patch_path;
+ } else {
+ cerr << "[PatchLibrarian] Could not find patch file " << full_patch_path << endl;
+ }
+ }
+
+ return "";
+}
+
+
+/** Save a patch from a PatchModel to a filename.
+ *
+ * The filename passed is the true filename the patch will be saved to (with no prefixing or anything
+ * like that), and the patch_model's filename member will be set accordingly.
+ *
+ * This will break if:
+ * - The filename does not have an extension (ie contain a ".")
+ * - The patch_model has no (Om) path
+ */
+void
+PatchLibrarian::save_patch(PatchModel* patch_model, const string& filename, bool recursive)
+{
+ assert(filename != "");
+ assert(patch_model->path() != "");
+
+ cout << "Saving patch " << patch_model->path() << " to " << filename << endl;
+
+ patch_model->filename(filename);
+
+ string dir = filename.substr(0, filename.find_last_of("/"));
+
+ NodeModel* nm = NULL;
+ PatchModel* spm = NULL; // subpatch model
+
+ xmlDocPtr xml_doc = NULL;
+ xmlNodePtr xml_root_node = NULL;
+ xmlNodePtr xml_node = NULL;
+ xmlNodePtr xml_child_node = NULL;
+ xmlNodePtr xml_grandchild_node = NULL;
+
+ xml_doc = xmlNewDoc((xmlChar*)"1.0");
+ xml_root_node = xmlNewNode(NULL, (xmlChar*)"patch");
+ xmlDocSetRootElement(xml_doc, xml_root_node);
+
+ const size_t temp_buf_length = 255;
+ char temp_buf[temp_buf_length];
+
+ string patch_name;
+ if (patch_model->path() != "/") {
+ patch_name = patch_model->name();
+ } else {
+ patch_name = filename;
+ if (patch_name.find("/") != string::npos)
+ patch_name = patch_name.substr(patch_name.find_last_of("/") + 1);
+ if (patch_name.find(".") != string::npos)
+ patch_name = patch_name.substr(0, patch_name.find_last_of("."));
+ }
+
+ assert(patch_name.length() > 0);
+ xml_node = xmlNewChild(xml_root_node, NULL, (xmlChar*)"name",
+ (xmlChar*)patch_name.c_str());
+
+ snprintf(temp_buf, temp_buf_length, "%zd", patch_model->poly());
+ xml_node = xmlNewChild(xml_root_node, NULL, (xmlChar*)"polyphony", (xmlChar*)temp_buf);
+
+ // Write metadata
+ for (map<string, string>::const_iterator i = patch_model->metadata().begin();
+ i != patch_model->metadata().end(); ++i) {
+ // Dirty hack, don't save coordinates in patch file
+ if ((*i).first != "module-x" && (*i).first != "module-y"
+ && (*i).first != "filename")
+ xml_node = xmlNewChild(xml_root_node, NULL,
+ (xmlChar*)(*i).first.c_str(), (xmlChar*)(*i).second.c_str());
+
+ assert((*i).first != "node");
+ assert((*i).first != "subpatch");
+ assert((*i).first != "name");
+ assert((*i).first != "polyphony");
+ assert((*i).first != "preset");
+ }
+
+ // Save nodes and subpatches
+ for (NodeModelMap::const_iterator i = patch_model->nodes().begin(); i != patch_model->nodes().end(); ++i) {
+ nm = i->second;
+
+ if (nm->plugin()->type() == PluginModel::Patch) { // Subpatch
+ spm = (PatchModel*)i->second;
+ xml_node = xmlNewChild(xml_root_node, NULL, (xmlChar*)"subpatch", NULL);
+
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"name", (xmlChar*)spm->name().c_str());
+
+ string ref_filename;
+ // No path
+ if (spm->filename() == "") {
+ ref_filename = spm->name() + ".om";
+ spm->filename(dir +"/"+ ref_filename);
+ // Absolute path
+ } else if (spm->filename().substr(0, 1) == "/") {
+ // Attempt to make it a relative path, if it's undernath this patch's dir
+ if (dir.substr(0, 1) == "/" && spm->filename().substr(0, dir.length()) == dir) {
+ ref_filename = spm->filename().substr(dir.length()+1);
+ } else { // FIXME: not good
+ ref_filename = spm->filename().substr(spm->filename().find_last_of("/")+1);
+ spm->filename(dir +"/"+ ref_filename);
+ }
+ } else {
+ ref_filename = spm->filename();
+ }
+
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"filename", (xmlChar*)ref_filename.c_str());
+
+ snprintf(temp_buf, temp_buf_length, "%zd", spm->poly());
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"polyphony", (xmlChar*)temp_buf);
+
+ // Write metadata
+ for (map<string, string>::const_iterator i = nm->metadata().begin();
+ i != nm->metadata().end(); ++i) {
+ // Dirty hack, don't save metadata that would be in patch file
+ if ((*i).first != "polyphony" && (*i).first != "filename"
+ && (*i).first != "author" && (*i).first != "description")
+ xml_child_node = xmlNewChild(xml_node, NULL,
+ (xmlChar*)(*i).first.c_str(), (xmlChar*)(*i).second.c_str());
+ }
+
+ if (recursive)
+ save_patch(spm, spm->filename(), true);
+
+ } else { // Normal node
+ xml_node = xmlNewChild(xml_root_node, NULL, (xmlChar*)"node", NULL);
+
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"name", (xmlChar*)nm->name().c_str());
+
+ if (nm->plugin() == NULL) break;
+
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"polyphonic",
+ (xmlChar*)((nm->polyphonic()) ? "true" : "false"));
+
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"type",
+ (xmlChar*)nm->plugin()->type_string());
+ /*
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"plugin-label",
+ (xmlChar*)(nm->plugin()->plug_label().c_str()));
+
+ if (nm->plugin()->type() != PluginModel::Internal) {
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"library-name",
+ (xmlChar*)(nm->plugin()->lib_name().c_str()));
+ }*/
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"plugin-uri",
+ (xmlChar*)(nm->plugin()->uri().c_str()));
+
+ // Write metadata
+ for (map<string, string>::const_iterator i = nm->metadata().begin(); i != nm->metadata().end(); ++i) {
+ // DSSI _hack_ (FIXME: fix OSC to be more like this and not smash DSSI into metadata?)
+ if ((*i).first.substr(0, 16) == "dssi-configure--") {
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"dssi-configure", NULL);
+ xml_grandchild_node = xmlNewChild(xml_child_node, NULL,
+ (xmlChar*)"key", (xmlChar*)(*i).first.substr(16).c_str());
+ xml_grandchild_node = xmlNewChild(xml_child_node, NULL,
+ (xmlChar*)"value", (xmlChar*)(*i).second.c_str());
+ } else if ((*i).first == "dssi-program") {
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"dssi-program", NULL);
+ xml_grandchild_node = xmlNewChild(xml_child_node, NULL,
+ (xmlChar*)"bank", (xmlChar*)(*i).second.substr(0, (*i).second.find("/")).c_str());
+ xml_grandchild_node = xmlNewChild(xml_child_node, NULL,
+ (xmlChar*)"program", (xmlChar*)(*i).second.substr((*i).second.find("/")+1).c_str());
+ } else {
+ xml_child_node = xmlNewChild(xml_node, NULL,
+ (xmlChar*)(*i).first.c_str(), (xmlChar*)(*i).second.c_str());
+ }
+ }
+
+ PortModel* pm = NULL;
+ // Write port metadata, if necessary
+ for (list<PortModel*>::const_iterator i = nm->ports().begin(); i != nm->ports().end(); ++i) {
+ pm = (*i);
+ if (pm->is_input() && pm->user_min() != pm->min_val() || pm->user_max() != pm->max_val()) {
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"port", NULL);
+ xml_grandchild_node = xmlNewChild(xml_child_node, NULL, (xmlChar*)"name",
+ (xmlChar*)pm->path().name().c_str());
+ snprintf(temp_buf, temp_buf_length, "%f", pm->user_min());
+ xml_grandchild_node = xmlNewChild(xml_child_node, NULL, (xmlChar*)"user-min", (xmlChar*)temp_buf);
+ snprintf(temp_buf, temp_buf_length, "%f", pm->user_max());
+ xml_grandchild_node = xmlNewChild(xml_child_node, NULL, (xmlChar*)"user-max", (xmlChar*)temp_buf);
+ }
+ }
+ }
+ }
+
+ // Save connections
+
+ const list<ConnectionModel*>& cl = patch_model->connections();
+ const ConnectionModel* c = NULL;
+
+ for (list<ConnectionModel*>::const_iterator i = cl.begin(); i != cl.end(); ++i) {
+ c = (*i);
+ xml_node = xmlNewChild(xml_root_node, NULL, (xmlChar*)"connection", NULL);
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"source-node",
+ (xmlChar*)c->src_port_path().parent().name().c_str());
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"source-port",
+ (xmlChar*)c->src_port_path().name().c_str());
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"destination-node",
+ (xmlChar*)c->dst_port_path().parent().name().c_str());
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"destination-port",
+ (xmlChar*)c->dst_port_path().name().c_str());
+ }
+
+ // Save control values (ie presets eventually, right now just current control vals)
+
+ xmlNodePtr xml_preset_node = xmlNewChild(xml_root_node, NULL, (xmlChar*)"preset", NULL);
+ xml_node = xmlNewChild(xml_preset_node, NULL, (xmlChar*)"name", (xmlChar*)"default");
+
+ PortModel* pm = NULL;
+
+ // Save node port controls
+ for (NodeModelMap::const_iterator n = patch_model->nodes().begin(); n != patch_model->nodes().end(); ++n) {
+ nm = n->second;
+ for (PortModelList::const_iterator p = nm->ports().begin(); p != nm->ports().end(); ++p) {
+ pm = *p;
+ if (pm->is_input() && pm->is_control()) {
+ float val = pm->value();
+ xml_node = xmlNewChild(xml_preset_node, NULL, (xmlChar*)"control", NULL);
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"node-name",
+ (xmlChar*)nm->name().c_str());
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"port-name",
+ (xmlChar*)pm->path().name().c_str());
+ snprintf(temp_buf, temp_buf_length, "%f", val);
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"value",
+ (xmlChar*)temp_buf);
+ }
+ }
+ }
+
+ // Save patch port controls
+ for (PortModelList::const_iterator p = patch_model->ports().begin();
+ p != patch_model->ports().end(); ++p) {
+ pm = *p;
+ if (pm->is_input() && pm->is_control()) {
+ float val = pm->value();
+ xml_node = xmlNewChild(xml_preset_node, NULL, (xmlChar*)"control", NULL);
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"port-name",
+ (xmlChar*)pm->path().name().c_str());
+ snprintf(temp_buf, temp_buf_length, "%f", val);
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"value",
+ (xmlChar*)temp_buf);
+ }
+ }
+
+ xmlSaveFormatFile(filename.c_str(), xml_doc, 1); // 1 == pretty print
+
+ xmlFreeDoc(xml_doc);
+ xmlCleanupParser();
+}
+
+
+/** Load a patch in to the engine (and client) from a patch file.
+ *
+ * The name and poly from the passed PatchModel are used. If the name is
+ * the empty string, the name will be loaded from the file. If the poly
+ * is 0, it will be loaded from file. Otherwise the given values will
+ * be used.
+ *
+ * If @a wait is set, the patch will be checked for existence before
+ * loading everything in to it (to prevent messing up existing patches
+ * that exist at the path this one should load as).
+ *
+ * If the @a existing parameter is true, the patch will be loaded into a
+ * currently existing patch (ie a merging will take place). Errors will
+ * result if Nodes of conflicting names exist.
+ *
+ * Returns the path of the newly created patch.
+ */
+string
+PatchLibrarian::load_patch(PatchModel* pm, bool wait, bool existing)
+{
+ string filename = pm->filename();
+
+ string additional_path = (pm->parent() == NULL)
+ ? "" : ((PatchModel*)pm->parent())->filename();
+ additional_path = additional_path.substr(0, additional_path.find_last_of("/"));
+
+ filename = find_file(pm->filename(), additional_path);
+
+ size_t poly = pm->poly();
+
+ //cerr << "[PatchLibrarian] Loading patch " << filename << "" << endl;
+
+ const size_t temp_buf_length = 255;
+ char temp_buf[temp_buf_length];
+
+ bool load_name = (pm->path() == "");
+ bool load_poly = (poly == 0);
+
+ xmlDocPtr doc = xmlParseFile(filename.c_str());
+
+ if (doc == NULL ) {
+ cerr << "Unable to parse patch file." << endl;
+ return "";
+ }
+
+ xmlNodePtr cur = xmlDocGetRootElement(doc);
+
+ if (cur == NULL) {
+ cerr << "Empty document." << endl;
+ xmlFreeDoc(doc);
+ return "";
+ }
+
+ if (xmlStrcmp(cur->name, (const xmlChar*) "patch")) {
+ cerr << "File is not an Om patch file, root node != patch" << endl;
+ xmlFreeDoc(doc);
+ return "";
+ }
+
+ xmlChar* key = NULL;
+ cur = cur->xmlChildrenNode;
+ string path;
+
+ pm->filename(filename);
+
+ // Load Patch attributes
+ while (cur != NULL) {
+ key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+
+ if ((!xmlStrcmp(cur->name, (const xmlChar*)"name"))) {
+ if (load_name) {
+ assert(key != NULL);
+ if (pm->parent() != NULL) {
+ path = pm->parent()->base_path() + string((char*)key);
+ } else {
+ path = string("/") + string((char*)key);
+ }
+ assert(path.find("//") == string::npos);
+ assert(path.length() > 0);
+ pm->set_path(path);
+ }
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"polyphony"))) {
+ if (load_poly) {
+ poly = atoi((char*)key);
+ pm->poly(poly);
+ }
+ } else if (xmlStrcmp(cur->name, (const xmlChar*)"connection")
+ && xmlStrcmp(cur->name, (const xmlChar*)"node")
+ && xmlStrcmp(cur->name, (const xmlChar*)"subpatch")
+ && xmlStrcmp(cur->name, (const xmlChar*)"filename")
+ && xmlStrcmp(cur->name, (const xmlChar*)"preset")) {
+ // Don't know what this tag is, add it as metadata without overwriting
+ // (so caller can set arbitrary parameters which will be preserved)
+ if (key != NULL)
+ if (pm->get_metadata((const char*)cur->name) == "")
+ pm->set_metadata((const char*)cur->name, (const char*)key);
+ }
+
+ xmlFree(key);
+ key = NULL; // Avoid a (possible?) double free
+
+ cur = cur->next;
+ }
+
+ if (poly == 0) poly = 1;
+
+ if (!existing) {
+ // Wait until the patch is created or the node creations may fail
+ if (wait) {
+ //int id = m_osc_model_engine_interface->get_next_request_id();
+ //m_osc_model_engine_interface->set_wait_response_id(id);
+ m_osc_model_engine_interface->create_patch_from_model(pm);
+ //bool succeeded = m_osc_model_engine_interface->wait_for_response();
+
+ // If creating the patch failed, bail out so we don't load all these nodes
+ // into an already existing patch
+ /*if (!succeeded) {
+ cerr << "[PatchLibrarian] Patch load failed (patch already exists)" << endl;
+ return "";
+ }*/ // FIXME
+ } else {
+ m_osc_model_engine_interface->create_patch_from_model(pm);
+ }
+ }
+
+
+ // Set the filename metadata. (FIXME)
+ // This isn't so good, considering multiple clients on multiple machines, and
+ // absolute filesystem paths obviously aren't going to be correct. But for now
+ // this is all I can figure out to have Save/Save As work properly for subpatches
+ m_osc_model_engine_interface->set_metadata(pm->path(), "filename", pm->filename());
+
+ // Load nodes
+ NodeModel* nm = NULL;
+ cur = xmlDocGetRootElement(doc)->xmlChildrenNode;
+
+ while (cur != NULL) {
+ if ((!xmlStrcmp(cur->name, (const xmlChar*)"node"))) {
+ nm = parse_node(pm, doc, cur);
+ if (nm != NULL) {
+ m_osc_model_engine_interface->create_node_from_model(nm);
+ m_osc_model_engine_interface->set_all_metadata(nm);
+ for (list<PortModel*>::const_iterator j = nm->ports().begin();
+ j != nm->ports().end(); ++j) {
+ // FIXME: ew
+ snprintf(temp_buf, temp_buf_length, "%f", (*j)->user_min());
+ m_osc_model_engine_interface->set_metadata((*j)->path(), "user-min", temp_buf);
+ snprintf(temp_buf, temp_buf_length, "%f", (*j)->user_max());
+ m_osc_model_engine_interface->set_metadata((*j)->path(), "user-max", temp_buf);
+ }
+ nm = NULL;
+ usleep(10000);
+ }
+ }
+ cur = cur->next;
+ }
+
+ // Load subpatches
+ cur = xmlDocGetRootElement(doc)->xmlChildrenNode;
+ while (cur != NULL) {
+ if ((!xmlStrcmp(cur->name, (const xmlChar*)"subpatch"))) {
+ load_subpatch(pm, doc, cur);
+ }
+ cur = cur->next;
+ }
+
+ // Load connections
+ ConnectionModel* cm = NULL;
+ cur = xmlDocGetRootElement(doc)->xmlChildrenNode;
+ while (cur != NULL) {
+ if ((!xmlStrcmp(cur->name, (const xmlChar*)"connection"))) {
+ cm = parse_connection(pm, doc, cur);
+ if (cm != NULL) {
+ m_osc_model_engine_interface->connect(cm->src_port_path(), cm->dst_port_path());
+ usleep(1000);
+ }
+ }
+ cur = cur->next;
+ }
+
+
+ // Load presets (control values)
+ PresetModel* preset_model = NULL;
+ cur = xmlDocGetRootElement(doc)->xmlChildrenNode;
+ while (cur != NULL) {
+ if ((!xmlStrcmp(cur->name, (const xmlChar*)"preset"))) {
+ preset_model = parse_preset(pm, doc, cur);
+ assert(preset_model != NULL);
+ if (preset_model->name() == "default")
+ m_osc_model_engine_interface->set_preset(pm->path(), preset_model);
+ }
+ cur = cur->next;
+ }
+
+ xmlFreeDoc(doc);
+ xmlCleanupParser();
+
+ m_osc_model_engine_interface->set_all_metadata(pm);
+
+ if (!existing)
+ m_osc_model_engine_interface->enable_patch(pm->path());
+
+ string ret = pm->path();
+ return ret;
+}
+
+
+/** Build a NodeModel given a pointer to a Node in a patch file.
+ */
+NodeModel*
+PatchLibrarian::parse_node(const PatchModel* parent, xmlDocPtr doc, const xmlNodePtr node)
+{
+ NodeModel* nm = new NodeModel("/UNINITIALIZED"); // FIXME: ew
+ PluginModel* plugin = new PluginModel();
+
+ xmlChar* key;
+ xmlNodePtr cur = node->xmlChildrenNode;
+
+ bool found_name = false;
+
+ while (cur != NULL) {
+ key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+
+ if ((!xmlStrcmp(cur->name, (const xmlChar*)"name"))) {
+ nm->set_path(parent->base_path() + (char*)key);
+ found_name = true;
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"polyphonic"))) {
+ nm->polyphonic(!strcmp((char*)key, "true"));
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"type"))) {
+ plugin->set_type((const char*)key);
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"library-name"))) {
+ plugin->lib_name((char*)key);
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"plugin-label"))) {
+ plugin->plug_label((char*)key);
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"plugin-uri"))) {
+ plugin->uri((char*)key);
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"port"))) {
+ xmlNodePtr child = cur->xmlChildrenNode;
+
+ string path;
+ float user_min = 0.0;
+ float user_max = 0.0;
+
+ while (child != NULL) {
+ key = xmlNodeListGetString(doc, child->xmlChildrenNode, 1);
+
+ if ((!xmlStrcmp(child->name, (const xmlChar*)"name"))) {
+ path = nm->base_path() + (char*)key;
+ } else if ((!xmlStrcmp(child->name, (const xmlChar*)"user-min"))) {
+ user_min = atof((char*)key);
+ } else if ((!xmlStrcmp(child->name, (const xmlChar*)"user-max"))) {
+ user_max = atof((char*)key);
+ }
+
+ xmlFree(key);
+ key = NULL; // Avoid a (possible?) double free
+
+ child = child->next;
+ }
+
+ // FIXME: nasty assumptions
+ PortModel* pm = new PortModel(path,
+ PortModel::CONTROL, PortModel::INPUT, PortModel::NONE,
+ 0.0, user_min, user_max);
+ nm->add_port(pm);
+
+ // DSSI hacks. Stored in the patch files as special elements, but sent to
+ // the engine as normal metadata with specially formatted key/values. Not
+ // sure if this is the best way to go about this, but it's the least damaging
+ // right now
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"dssi-program"))) {
+ xmlNodePtr child = cur->xmlChildrenNode;
+
+ string bank;
+ string program;
+
+ while (child != NULL) {
+ key = xmlNodeListGetString(doc, child->xmlChildrenNode, 1);
+
+ if ((!xmlStrcmp(child->name, (const xmlChar*)"bank"))) {
+ bank = (char*)key;
+ } else if ((!xmlStrcmp(child->name, (const xmlChar*)"program"))) {
+ program = (char*)key;
+ }
+
+ xmlFree(key);
+ key = NULL; // Avoid a (possible?) double free
+ child = child->next;
+ }
+ nm->set_metadata("dssi-program", bank +"/"+ program);
+
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"dssi-configure"))) {
+ xmlNodePtr child = cur->xmlChildrenNode;
+
+ string dssi_key;
+ string dssi_value;
+
+ while (child != NULL) {
+ key = xmlNodeListGetString(doc, child->xmlChildrenNode, 1);
+
+ if ((!xmlStrcmp(child->name, (const xmlChar*)"key"))) {
+ dssi_key = (char*)key;
+ } else if ((!xmlStrcmp(child->name, (const xmlChar*)"value"))) {
+ dssi_value = (char*)key;
+ }
+
+ xmlFree(key);
+ key = NULL; // Avoid a (possible?) double free
+
+ child = child->next;
+ }
+ nm->set_metadata(string("dssi-configure--").append(dssi_key), dssi_value);
+
+ } else { // Don't know what this tag is, add it as metadata
+ if (key != NULL)
+ nm->set_metadata((const char*)cur->name, (const char*)key);
+ }
+ xmlFree(key);
+ key = NULL;
+
+ cur = cur->next;
+ }
+
+ if (nm->path() == "") {
+ cerr << "[PatchLibrarian] Malformed patch file (node tag has empty children)" << endl;
+ cerr << "[PatchLibrarian] Node ignored." << endl;
+ delete nm;
+ return NULL;
+ } else {
+ nm->plugin(plugin);
+ return nm;
+ }
+}
+
+
+void
+PatchLibrarian::load_subpatch(PatchModel* parent, xmlDocPtr doc, const xmlNodePtr subpatch)
+{
+ xmlChar *key;
+ xmlNodePtr cur = subpatch->xmlChildrenNode;
+
+ PatchModel* pm = new PatchModel("/UNINITIALIZED", 1); // FIXME: ew
+
+ while (cur != NULL) {
+ key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+
+ if ((!xmlStrcmp(cur->name, (const xmlChar*)"name"))) {
+ if (parent == NULL)
+ pm->set_path(string("/") + (const char*)key);
+ else
+ pm->set_path(parent->base_path() + (const char*)key);
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"polyphony"))) {
+ pm->poly(atoi((const char*)key));
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"filename"))) {
+ pm->filename((const char*)key);
+ } else { // Don't know what this tag is, add it as metadata
+ if (key != NULL && strlen((const char*)key) > 0)
+ pm->set_metadata((const char*)cur->name, (const char*)key);
+ }
+ xmlFree(key);
+ key = NULL;
+
+ cur = cur->next;
+ }
+
+ // This needs to be done after setting the path above, to prevent
+ // NodeModel::set_path from calling it's parent's rename_node with
+ // an invalid (nonexistant) name
+ pm->set_parent(parent);
+
+ load_patch(pm, false);
+}
+
+
+/** Build a ConnectionModel given a pointer to a connection in a patch file.
+ */
+ConnectionModel*
+PatchLibrarian::parse_connection(const PatchModel* parent, xmlDocPtr doc, const xmlNodePtr node)
+{
+ //cerr << "[PatchLibrarian] Parsing connection..." << endl;
+
+ xmlChar *key;
+ xmlNodePtr cur = node->xmlChildrenNode;
+
+ string source_node, source_port, dest_node, dest_port;
+
+ while (cur != NULL) {
+ key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+
+ if ((!xmlStrcmp(cur->name, (const xmlChar*)"source-node"))) {
+ source_node = (char*)key;
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"source-port"))) {
+ source_port = (char*)key;
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"destination-node"))) {
+ dest_node = (char*)key;
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"destination-port"))) {
+ dest_port = (char*)key;
+ }
+
+ xmlFree(key);
+ key = NULL; // Avoid a (possible?) double free
+
+ cur = cur->next;
+ }
+
+ if (source_node == "" || source_port == "" || dest_node == "" || dest_port == "") {
+ cerr << "[PatchLibrarian] Malformed patch file (connection tag has empty children)" << endl;
+ cerr << "[PatchLibrarian] Connection ignored." << endl;
+ return NULL;
+ }
+
+ // FIXME: temporary compatibility, remove any slashes from port names
+ // remove this soon once patches have migrated
+ string::size_type slash_index;
+ while ((slash_index = source_port.find("/")) != string::npos)
+ source_port[slash_index] = '-';
+
+ while ((slash_index = dest_port.find("/")) != string::npos)
+ dest_port[slash_index] = '-';
+
+ ConnectionModel* cm = new ConnectionModel(parent->base_path() + source_node +"/"+ source_port,
+ parent->base_path() + dest_node +"/"+ dest_port);
+
+ return cm;
+}
+
+
+/** Build a PresetModel given a pointer to a preset in a patch file.
+ */
+PresetModel*
+PatchLibrarian::parse_preset(const PatchModel* patch, xmlDocPtr doc, const xmlNodePtr node)
+{
+ xmlNodePtr cur = node->xmlChildrenNode;
+ xmlChar* key;
+
+ PresetModel* pm = new PresetModel(patch->base_path());
+
+ while (cur != NULL) {
+ key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+
+ if ((!xmlStrcmp(cur->name, (const xmlChar*)"name"))) {
+ assert(key != NULL);
+ pm->name((char*)key);
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"control"))) {
+ xmlNodePtr child = cur->xmlChildrenNode;
+
+ string node_name = "", port_name = "";
+ float val = 0.0;
+
+ while (child != NULL) {
+ key = xmlNodeListGetString(doc, child->xmlChildrenNode, 1);
+
+ if ((!xmlStrcmp(child->name, (const xmlChar*)"node-name"))) {
+ node_name = (char*)key;
+ } else if ((!xmlStrcmp(child->name, (const xmlChar*)"port-name"))) {
+ port_name = (char*)key;
+ } else if ((!xmlStrcmp(child->name, (const xmlChar*)"value"))) {
+ val = atof((char*)key);
+ }
+
+ xmlFree(key);
+ key = NULL; // Avoid a (possible?) double free
+
+ child = child->next;
+ }
+
+ if (port_name == "") {
+ string msg = "Unable to parse control in patch file ( node = ";
+ msg.append(node_name).append(", port = ").append(port_name).append(")");
+ cerr << "ERROR: " << msg << endl;
+ //m_client_hooks->error(msg);
+ } else {
+ // FIXME: temporary compatibility, remove any slashes from port name
+ // remove this soon once patches have migrated
+ string::size_type slash_index;
+ while ((slash_index = port_name.find("/")) != string::npos)
+ port_name[slash_index] = '-';
+ pm->add_control(node_name, port_name, val);
+ }
+ }
+ xmlFree(key);
+ key = NULL;
+ cur = cur->next;
+ }
+ if (pm->name() == "") {
+ cerr << "Preset in patch file has no name." << endl;
+ //m_client_hooks->error("Preset in patch file has no name.");
+ pm->name("Unnamed");
+ }
+
+ return pm;
+}
+
+} // namespace LibOmClient
diff --git a/src/libs/client/PatchLibrarian.cpp.new b/src/libs/client/PatchLibrarian.cpp.new
new file mode 100644
index 00000000..dc05c9e3
--- /dev/null
+++ b/src/libs/client/PatchLibrarian.cpp.new
@@ -0,0 +1,830 @@
+/* This file is part of Om. Copyright (C) 2005 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "PatchLibrarian.h"
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/xpath.h>
+#include "PatchModel.h"
+#include "NodeModel.h"
+#include "ConnectionModel.h"
+#include "PortModel.h"
+#include "PresetModel.h"
+#include "OSCController.h"
+#include "PluginModel.h"
+#include "Path.h"
+#include <iostream>
+#include <fstream>
+#include <vector>
+#include <utility> // for pair, make_pair
+#include <cassert>
+#include <cstring>
+#include <string>
+#include <unistd.h> // for usleep
+#include <cstdlib> // for atof
+#include <cmath>
+
+using std::string; using std::vector; using std::pair;
+using std::cerr; using std::cout; using std::endl;
+
+namespace LibOmClient {
+
+
+/** Searches for the filename passed in the path, returning the full
+ * path of the file, or the empty string if not found.
+ *
+ * This function tries to be as friendly a black box as possible - if the path
+ * passed is an absolute path and the file is found there, it will return
+ * that path, etc.
+ *
+ * additional_path is a list (colon delimeted as usual) of additional
+ * directories to look in. ie the directory the parent patch resides in would
+ * be a good idea to pass as additional_path, in the case of a subpatch.
+ */
+string
+PatchLibrarian::find_file(const string& filename, const string& additional_path)
+{
+ string search_path = additional_path + ":" + m_patch_path;
+
+ // Try to open the raw filename first
+ std::ifstream is(filename.c_str(), std::ios::in);
+ if (is.good()) {
+ is.close();
+ return filename;
+ }
+
+ string directory;
+ string full_patch_path = "";
+
+ while (search_path != "") {
+ directory = search_path.substr(0, search_path.find(':'));
+ if (search_path.find(':') != string::npos)
+ search_path = search_path.substr(search_path.find(':')+1);
+ else
+ search_path = "";
+
+ full_patch_path = directory +"/"+ filename;
+
+ std::ifstream is;
+ is.open(full_patch_path.c_str(), std::ios::in);
+
+ if (is.good()) {
+ is.close();
+ return full_patch_path;
+ } else {
+ cerr << "[PatchLibrarian] Could not find patch file " << full_patch_path << endl;
+ }
+ }
+
+ return "";
+}
+
+
+/** Save a patch from a PatchModel to a filename.
+ *
+ * The filename passed is the true filename the patch will be saved to (with no prefixing or anything
+ * like that), and the patch_model's filename member will be set accordingly.
+ *
+ * This will break if:
+ * - The filename does not have an extension (ie contain a ".")
+ * - The patch_model has no (Om) path
+ */
+void
+PatchLibrarian::save_patch(PatchModel* patch_model, const string& filename, bool recursive)
+{
+ assert(filename != "");
+ assert(patch_model->path() != "");
+
+ cout << "Saving patch " << patch_model->path() << " to " << filename << endl;
+
+ patch_model->filename(filename);
+
+ string dir = filename.substr(0, filename.find_last_of("/"));
+
+ NodeModel* nm = NULL;
+ PatchModel* spm = NULL; // subpatch model
+
+ xmlDocPtr xml_doc = NULL;
+ xmlNodePtr xml_root_node = NULL;
+ xmlNodePtr xml_node = NULL;
+ xmlNodePtr xml_child_node = NULL;
+ xmlNodePtr xml_grandchild_node = NULL;
+
+ xml_doc = xmlNewDoc((xmlChar*)"1.0");
+ xml_root_node = xmlNewNode(NULL, (xmlChar*)"patch");
+ xmlDocSetRootElement(xml_doc, xml_root_node);
+
+ const size_t temp_buf_length = 255;
+ char temp_buf[temp_buf_length];
+
+ string patch_name;
+ if (patch_model->path() != "/") {
+ patch_name = patch_model->name();
+ } else {
+ patch_name = filename;
+ if (patch_name.find("/") != string::npos)
+ patch_name = patch_name.substr(patch_name.find_last_of("/") + 1);
+ if (patch_name.find(".") != string::npos)
+ patch_name = patch_name.substr(0, patch_name.find_last_of("."));
+ }
+
+ assert(patch_name.length() > 0);
+ xml_node = xmlNewChild(xml_root_node, NULL, (xmlChar*)"name",
+ (xmlChar*)patch_name.c_str());
+
+ snprintf(temp_buf, temp_buf_length, "%zd", patch_model->poly());
+ xml_node = xmlNewChild(xml_root_node, NULL, (xmlChar*)"polyphony", (xmlChar*)temp_buf);
+
+ // Write metadata
+ for (map<string, string>::const_iterator i = patch_model->metadata().begin();
+ i != patch_model->metadata().end(); ++i) {
+ // Dirty hack, don't save coordinates in patch file
+ if ((*i).first != "module-x" && (*i).first != "module-y"
+ && (*i).first != "filename")
+ xml_node = xmlNewChild(xml_root_node, NULL,
+ (xmlChar*)(*i).first.c_str(), (xmlChar*)(*i).second.c_str());
+ assert((*i).first != "connection");
+ assert((*i).first != "node");
+ assert((*i).first != "subpatch");
+ assert((*i).first != "name");
+ assert((*i).first != "polyphony");
+ assert((*i).first != "preset");
+ }
+
+ // Save nodes and subpatches
+ for (NodeModelMap::const_iterator i = patch_model->nodes().begin(); i != patch_model->nodes().end(); ++i) {
+ nm = i->second;
+
+ if (nm->plugin()->type() == PluginModel::Patch) { // Subpatch
+ spm = (PatchModel*)i->second;
+ xml_node = xmlNewChild(xml_root_node, NULL, (xmlChar*)"subpatch", NULL);
+
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"name", (xmlChar*)spm->name().c_str());
+
+ string ref_filename;
+ // No path
+ if (spm->filename() == "") {
+ ref_filename = spm->name() + ".om";
+ spm->filename(dir +"/"+ ref_filename);
+ // Absolute path
+ } else if (spm->filename().substr(0, 1) == "/") {
+ // Attempt to make it a relative path, if it's undernath this patch's dir
+ if (dir.substr(0, 1) == "/" && spm->filename().substr(0, dir.length()) == dir) {
+ ref_filename = spm->filename().substr(dir.length()+1);
+ } else { // FIXME: not good
+ ref_filename = spm->filename().substr(spm->filename().find_last_of("/")+1);
+ spm->filename(dir +"/"+ ref_filename);
+ }
+ } else {
+ ref_filename = spm->filename();
+ }
+
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"filename", (xmlChar*)ref_filename.c_str());
+
+ snprintf(temp_buf, temp_buf_length, "%zd", spm->poly());
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"polyphony", (xmlChar*)temp_buf);
+
+ // Write metadata
+ for (map<string, string>::const_iterator i = nm->metadata().begin();
+ i != nm->metadata().end(); ++i) {
+ // Dirty hack, don't save metadata that would be in patch file
+ if ((*i).first != "polyphony" && (*i).first != "filename"
+ && (*i).first != "author" && (*i).first != "description")
+ xml_child_node = xmlNewChild(xml_node, NULL,
+ (xmlChar*)(*i).first.c_str(), (xmlChar*)(*i).second.c_str());
+ }
+
+ if (recursive)
+ save_patch(spm, spm->filename(), true);
+
+ } else { // Normal node
+ xml_node = xmlNewChild(xml_root_node, NULL, (xmlChar*)"node", NULL);
+
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"name", (xmlChar*)nm->name().c_str());
+
+ if (nm->plugin() == NULL) break;
+
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"polyphonic",
+ (xmlChar*)((nm->polyphonic()) ? "true" : "false"));
+
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"type",
+ (xmlChar*)nm->plugin()->type_string());
+ /*
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"plugin-label",
+ (xmlChar*)(nm->plugin()->plug_label().c_str()));
+
+ if (nm->plugin()->type() != PluginModel::Internal) {
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"library-name",
+ (xmlChar*)(nm->plugin()->lib_name().c_str()));
+ }*/
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"plugin-uri",
+ (xmlChar*)(nm->plugin()->uri().c_str()));
+
+ // Write metadata
+ for (map<string, string>::const_iterator i = nm->metadata().begin(); i != nm->metadata().end(); ++i) {
+ // DSSI _hack_ (FIXME: fix OSC to be more like this and not smash DSSI into metadata?)
+ if ((*i).first.substr(0, 16) == "dssi-configure--") {
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"dssi-configure", NULL);
+ xml_grandchild_node = xmlNewChild(xml_child_node, NULL,
+ (xmlChar*)"key", (xmlChar*)(*i).first.substr(16).c_str());
+ xml_grandchild_node = xmlNewChild(xml_child_node, NULL,
+ (xmlChar*)"value", (xmlChar*)(*i).second.c_str());
+ } else if ((*i).first == "dssi-program") {
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"dssi-program", NULL);
+ xml_grandchild_node = xmlNewChild(xml_child_node, NULL,
+ (xmlChar*)"bank", (xmlChar*)(*i).second.substr(0, (*i).second.find("/")).c_str());
+ xml_grandchild_node = xmlNewChild(xml_child_node, NULL,
+ (xmlChar*)"program", (xmlChar*)(*i).second.substr((*i).second.find("/")+1).c_str());
+ } else {
+ xml_child_node = xmlNewChild(xml_node, NULL,
+ (xmlChar*)(*i).first.c_str(), (xmlChar*)(*i).second.c_str());
+ }
+ }
+
+ PortModel* pm = NULL;
+ // Write port metadata, if necessary
+ for (list<PortModel*>::const_iterator i = nm->ports().begin(); i != nm->ports().end(); ++i) {
+ pm = (*i);
+ if (pm->is_input() && pm->user_min() != pm->min_val() || pm->user_max() != pm->max_val()) {
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"port", NULL);
+ xml_grandchild_node = xmlNewChild(xml_child_node, NULL, (xmlChar*)"name",
+ (xmlChar*)pm->path().name().c_str());
+ snprintf(temp_buf, temp_buf_length, "%f", pm->user_min());
+ xml_grandchild_node = xmlNewChild(xml_child_node, NULL, (xmlChar*)"user-min", (xmlChar*)temp_buf);
+ snprintf(temp_buf, temp_buf_length, "%f", pm->user_max());
+ xml_grandchild_node = xmlNewChild(xml_child_node, NULL, (xmlChar*)"user-max", (xmlChar*)temp_buf);
+ }
+ }
+ }
+ }
+
+ // Save connections
+
+ const list<ConnectionModel*>& cl = patch_model->connections();
+ const ConnectionModel* c = NULL;
+
+ for (list<ConnectionModel*>::const_iterator i = cl.begin(); i != cl.end(); ++i) {
+ c = (*i);
+ xml_node = xmlNewChild(xml_root_node, NULL, (xmlChar*)"connection", NULL);
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"source-node",
+ (xmlChar*)c->src_port_path().parent().name().c_str());
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"source-port",
+ (xmlChar*)c->src_port_path().name().c_str());
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"destination-node",
+ (xmlChar*)c->dst_port_path().parent().name().c_str());
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"destination-port",
+ (xmlChar*)c->dst_port_path().name().c_str());
+ }
+
+ // Save control values (ie presets eventually, right now just current control vals)
+
+ xmlNodePtr xml_preset_node = xmlNewChild(xml_root_node, NULL, (xmlChar*)"preset", NULL);
+ xml_node = xmlNewChild(xml_preset_node, NULL, (xmlChar*)"name", (xmlChar*)"default");
+
+ PortModel* pm = NULL;
+
+ // Save node port controls
+ for (NodeModelMap::const_iterator n = patch_model->nodes().begin(); n != patch_model->nodes().end(); ++n) {
+ nm = n->second;
+ for (PortModelList::const_iterator p = nm->ports().begin(); p != nm->ports().end(); ++p) {
+ pm = *p;
+ if (pm->is_input() && pm->is_control()) {
+ float val = pm->value();
+ xml_node = xmlNewChild(xml_preset_node, NULL, (xmlChar*)"control", NULL);
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"node-name",
+ (xmlChar*)nm->name().c_str());
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"port-name",
+ (xmlChar*)pm->path().name().c_str());
+ snprintf(temp_buf, temp_buf_length, "%f", val);
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"value",
+ (xmlChar*)temp_buf);
+ }
+ }
+ }
+
+ // Save patch port controls
+ for (PortModelList::const_iterator p = patch_model->ports().begin();
+ p != patch_model->ports().end(); ++p) {
+ pm = *p;
+ if (pm->is_input() && pm->is_control()) {
+ float val = pm->value();
+ xml_node = xmlNewChild(xml_preset_node, NULL, (xmlChar*)"control", NULL);
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"port-name",
+ (xmlChar*)pm->path().name().c_str());
+ snprintf(temp_buf, temp_buf_length, "%f", val);
+ xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"value",
+ (xmlChar*)temp_buf);
+ }
+ }
+
+ xmlSaveFormatFile(filename.c_str(), xml_doc, 1); // 1 == pretty print
+
+ xmlFreeDoc(xml_doc);
+ xmlCleanupParser();
+}
+
+
+/** Load a patch in to the engine (and client) from a patch file.
+ *
+ * The name and poly from the passed PatchModel are used. If the name is
+ * the empty string, the name will be loaded from the file. If the poly
+ * is 0, it will be loaded from file. Otherwise the given values will
+ * be used.
+ *
+ * If @a wait is set, the patch will be checked for existence before
+ * loading everything in to it (to prevent messing up existing patches
+ * that exist at the path this one should load as).
+ *
+ * If the @a existing parameter is true, the patch will be loaded into a
+ * currently existing patch (ie a merging will take place). Errors will
+ * result if Nodes of conflicting names exist.
+ *
+ * Returns the path of the newly created patch.
+ */
+string
+PatchLibrarian::load_patch(PatchModel* pm, bool wait, bool existing)
+{
+ string filename = pm->filename();
+
+ string additional_path = (pm->parent() == NULL)
+ ? "" : ((PatchModel*)pm->parent())->filename();
+ additional_path = additional_path.substr(0, additional_path.find_last_of("/"));
+
+ filename = find_file(pm->filename(), additional_path);
+
+ size_t poly = pm->poly();
+
+ //cerr << "[PatchLibrarian] Loading patch " << filename << "" << endl;
+
+ const size_t temp_buf_length = 255;
+ char temp_buf[temp_buf_length];
+
+ bool load_name = (pm->path() == "");
+ bool load_poly = (poly == 0);
+
+ xmlDocPtr doc = xmlParseFile(filename.c_str());
+
+ if (doc == NULL ) {
+ cerr << "Unable to parse patch file." << endl;
+ return "";
+ }
+
+ xmlNodePtr cur = xmlDocGetRootElement(doc);
+
+ if (cur == NULL) {
+ cerr << "Empty document." << endl;
+ xmlFreeDoc(doc);
+ return "";
+ }
+
+ if (xmlStrcmp(cur->name, (const xmlChar*) "patch")) {
+ cerr << "File is not an Om patch file, root node != patch" << endl;
+ xmlFreeDoc(doc);
+ return "";
+ }
+
+ xmlChar* key = NULL;
+ cur = cur->xmlChildrenNode;
+ string path;
+
+ pm->filename(filename);
+
+ // Load Patch attributes
+ while (cur != NULL) {
+ key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+
+ if ((!xmlStrcmp(cur->name, (const xmlChar*)"name"))) {
+ if (load_name) {
+ assert(key != NULL);
+ if (pm->parent() != NULL) {
+ path = pm->parent()->base_path() + string((char*)key);
+ } else {
+ path = string("/") + string((char*)key);
+ }
+ assert(path.find("//") == string::npos);
+ assert(path.length() > 0);
+ pm->set_path(path);
+ }
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"polyphony"))) {
+ if (load_poly) {
+ poly = atoi((char*)key);
+ pm->poly(poly);
+ }
+ } else if (xmlStrcmp(cur->name, (const xmlChar*)"connection")
+ && xmlStrcmp(cur->name, (const xmlChar*)"node")
+ && xmlStrcmp(cur->name, (const xmlChar*)"subpatch")
+ && xmlStrcmp(cur->name, (const xmlChar*)"preset")) {
+ // Don't know what this tag is, add it as metadata without overwriting
+ // (so caller can set arbitrary parameters which will be preserved)
+ if (key != NULL)
+ if (pm->get_metadata((const char*)cur->name) == "")
+ pm->set_metadata((const char*)cur->name, (const char*)key);
+ }
+
+ xmlFree(key);
+ key = NULL; // Avoid a (possible?) double free
+
+ cur = cur->next;
+ }
+
+ if (poly == 0) poly = 1;
+
+ if (!existing) {
+ // Wait until the patch is created or the node creations may fail
+ if (wait) {
+ int id = m_osc_controller->get_next_request_id();
+ m_osc_controller->set_wait_response_id(id);
+ m_osc_controller->create_patch(pm, id);
+ bool succeeded = m_osc_controller->wait_for_response();
+
+ // If creating the patch failed, bail out so we don't load all these nodes
+ // into an already existing patch
+ if (!succeeded) {
+ cerr << "[PatchLibrarian] Patch load failed (patch already exists)" << endl;
+ return "";
+ }
+ } else {
+ m_osc_controller->create_patch(pm);
+ }
+ }
+
+
+ // Set the filename metadata. (FIXME)
+ // This isn't so good, considering multiple clients on multiple machines, and
+ // absolute filesystem paths obviously aren't going to be correct. But for now
+ // this is all I can figure out to have Save/Save As work properly for subpatches
+ m_osc_controller->set_metadata(pm->path(), "filename", pm->filename());
+
+ // Load nodes
+ NodeModel* nm = NULL;
+ cur = xmlDocGetRootElement(doc)->xmlChildrenNode;
+
+ while (cur != NULL) {
+ if ((!xmlStrcmp(cur->name, (const xmlChar*)"node"))) {
+ nm = parse_node(pm, doc, cur);
+ if (nm != NULL) {
+ m_osc_controller->add_node(nm);
+ m_osc_controller->set_all_metadata(nm);
+ for (list<PortModel*>::const_iterator j = nm->ports().begin();
+ j != nm->ports().end(); ++j) {
+ // FIXME: ew
+ snprintf(temp_buf, temp_buf_length, "%f", (*j)->user_min());
+ m_osc_controller->set_metadata((*j)->path(), "user-min", temp_buf);
+ snprintf(temp_buf, temp_buf_length, "%f", (*j)->user_max());
+ m_osc_controller->set_metadata((*j)->path(), "user-max", temp_buf);
+ }
+ nm = NULL;
+ usleep(10000);
+ }
+ }
+ cur = cur->next;
+ }
+
+ // Load subpatches
+ cur = xmlDocGetRootElement(doc)->xmlChildrenNode;
+ while (cur != NULL) {
+ if ((!xmlStrcmp(cur->name, (const xmlChar*)"subpatch"))) {
+ load_subpatch(pm, doc, cur);
+ }
+ cur = cur->next;
+ }
+
+ // Load connections
+ ConnectionModel* cm = NULL;
+ cur = xmlDocGetRootElement(doc)->xmlChildrenNode;
+ while (cur != NULL) {
+ if ((!xmlStrcmp(cur->name, (const xmlChar*)"connection"))) {
+ cm = parse_connection(pm, doc, cur);
+ if (cm != NULL) {
+ m_osc_controller->connect(cm->src_port_path(), cm->dst_port_path());
+ usleep(1000);
+ }
+ }
+ cur = cur->next;
+ }
+
+
+ // Load presets (control values)
+ PresetModel* preset_model = NULL;
+ cur = xmlDocGetRootElement(doc)->xmlChildrenNode;
+ while (cur != NULL) {
+ if ((!xmlStrcmp(cur->name, (const xmlChar*)"preset"))) {
+ preset_model = parse_preset(pm, doc, cur);
+ assert(preset_model != NULL);
+ if (preset_model->name() == "default")
+ m_osc_controller->set_preset(pm->path(), preset_model);
+ }
+ cur = cur->next;
+ }
+
+ xmlFreeDoc(doc);
+ xmlCleanupParser();
+
+ m_osc_controller->set_all_metadata(pm);
+
+ if (!existing)
+ m_osc_controller->enable_patch(pm->path());
+
+ string ret = pm->path();
+ return ret;
+}
+
+
+/** Build a NodeModel given a pointer to a Node in a patch file.
+ */
+NodeModel*
+PatchLibrarian::parse_node(const PatchModel* parent, xmlDocPtr doc, const xmlNodePtr node)
+{
+ NodeModel* nm = new NodeModel("/UNINITIALIZED"); // FIXME: ew
+ PluginModel* plugin = new PluginModel();
+
+ xmlChar* key;
+ xmlNodePtr cur = node->xmlChildrenNode;
+
+ bool found_name = false;
+
+ while (cur != NULL) {
+ key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+
+ if ((!xmlStrcmp(cur->name, (const xmlChar*)"name"))) {
+ nm->set_path(parent->base_path() + (char*)key);
+ found_name = true;
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"polyphonic"))) {
+ nm->polyphonic(!strcmp((char*)key, "true"));
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"type"))) {
+ plugin->set_type((const char*)key);
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"library-name"))) {
+ plugin->lib_name((char*)key);
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"plugin-label"))) {
+ plugin->plug_label((char*)key);
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"plugin-uri"))) {
+ plugin->uri((char*)key);
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"port"))) {
+ xmlNodePtr child = cur->xmlChildrenNode;
+
+ string path;
+ float user_min = 0.0;
+ float user_max = 0.0;
+
+ while (child != NULL) {
+ key = xmlNodeListGetString(doc, child->xmlChildrenNode, 1);
+
+ if ((!xmlStrcmp(child->name, (const xmlChar*)"name"))) {
+ path = nm->base_path() + (char*)key;
+ } else if ((!xmlStrcmp(child->name, (const xmlChar*)"user-min"))) {
+ user_min = atof((char*)key);
+ } else if ((!xmlStrcmp(child->name, (const xmlChar*)"user-max"))) {
+ user_max = atof((char*)key);
+ }
+
+ xmlFree(key);
+ key = NULL; // Avoid a (possible?) double free
+
+ child = child->next;
+ }
+
+ // FIXME: nasty assumptions
+ PortModel* pm = new PortModel(path,
+ PortModel::CONTROL, PortModel::INPUT, PortModel::NONE,
+ 0.0, user_min, user_max);
+ nm->add_port(pm);
+
+ // DSSI hacks. Stored in the patch files as special elements, but sent to
+ // the engine as normal metadata with specially formatted key/values. Not
+ // sure if this is the best way to go about this, but it's the least damaging
+ // right now
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"dssi-program"))) {
+ xmlNodePtr child = cur->xmlChildrenNode;
+
+ string bank;
+ string program;
+
+ while (child != NULL) {
+ key = xmlNodeListGetString(doc, child->xmlChildrenNode, 1);
+
+ if ((!xmlStrcmp(child->name, (const xmlChar*)"bank"))) {
+ bank = (char*)key;
+ } else if ((!xmlStrcmp(child->name, (const xmlChar*)"program"))) {
+ program = (char*)key;
+ }
+
+ xmlFree(key);
+ key = NULL; // Avoid a (possible?) double free
+ child = child->next;
+ }
+ nm->set_metadata("dssi-program", bank +"/"+ program);
+
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"dssi-configure"))) {
+ xmlNodePtr child = cur->xmlChildrenNode;
+
+ string dssi_key;
+ string dssi_value;
+
+ while (child != NULL) {
+ key = xmlNodeListGetString(doc, child->xmlChildrenNode, 1);
+
+ if ((!xmlStrcmp(child->name, (const xmlChar*)"key"))) {
+ dssi_key = (char*)key;
+ } else if ((!xmlStrcmp(child->name, (const xmlChar*)"value"))) {
+ dssi_value = (char*)key;
+ }
+
+ xmlFree(key);
+ key = NULL; // Avoid a (possible?) double free
+
+ child = child->next;
+ }
+ nm->set_metadata(string("dssi-configure--").append(dssi_key), dssi_value);
+
+ } else { // Don't know what this tag is, add it as metadata
+ if (key != NULL)
+ nm->set_metadata((const char*)cur->name, (const char*)key);
+ }
+ xmlFree(key);
+ key = NULL;
+
+ cur = cur->next;
+ }
+
+ if (nm->path() == "") {
+ cerr << "[PatchLibrarian] Malformed patch file (node tag has empty children)" << endl;
+ cerr << "[PatchLibrarian] Node ignored." << endl;
+ delete nm;
+ return NULL;
+ } else {
+ nm->plugin(plugin);
+ return nm;
+ }
+}
+
+
+void
+PatchLibrarian::load_subpatch(PatchModel* parent, xmlDocPtr doc, const xmlNodePtr subpatch)
+{
+ xmlChar *key;
+ xmlNodePtr cur = subpatch->xmlChildrenNode;
+
+ PatchModel* pm = new PatchModel("", 1); // FIXME: ew
+
+ while (cur != NULL) {
+ key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+
+ if ((!xmlStrcmp(cur->name, (const xmlChar*)"name"))) {
+ if (parent == NULL)
+ pm->set_path(string("/") + (const char*)key);
+ else
+ pm->set_path(parent->base_path() + (const char*)key);
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"polyphony"))) {
+ pm->poly(atoi((const char*)key));
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"filename"))) {
+ pm->filename((const char*)key);
+ } else { // Don't know what this tag is, add it as metadata
+ if (key != NULL && strlen((const char*)key) > 0)
+ pm->set_metadata((const char*)cur->name, (const char*)key);
+ }
+ xmlFree(key);
+ key = NULL;
+
+ cur = cur->next;
+ }
+
+ // This needs to be done after setting the path above, to prevent
+ // NodeModel::set_path from calling it's parent's rename_node with
+ // an invalid (nonexistant) name
+ pm->set_parent(parent);
+
+ load_patch(pm, false);
+}
+
+
+/** Build a ConnectionModel given a pointer to a connection in a patch file.
+ */
+ConnectionModel*
+PatchLibrarian::parse_connection(const PatchModel* parent, xmlDocPtr doc, const xmlNodePtr node)
+{
+ //cerr << "[PatchLibrarian] Parsing connection..." << endl;
+
+ xmlChar *key;
+ xmlNodePtr cur = node->xmlChildrenNode;
+
+ string source_node, source_port, dest_node, dest_port;
+
+ while (cur != NULL) {
+ key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+
+ if ((!xmlStrcmp(cur->name, (const xmlChar*)"source-node"))) {
+ source_node = (char*)key;
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"source-port"))) {
+ source_port = (char*)key;
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"destination-node"))) {
+ dest_node = (char*)key;
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"destination-port"))) {
+ dest_port = (char*)key;
+ }
+
+ xmlFree(key);
+ key = NULL; // Avoid a (possible?) double free
+
+ cur = cur->next;
+ }
+
+ if (source_node == "" || source_port == "" || dest_node == "" || dest_port == "") {
+ cerr << "[PatchLibrarian] Malformed patch file (connection tag has empty children)" << endl;
+ cerr << "[PatchLibrarian] Connection ignored." << endl;
+ return NULL;
+ }
+
+ // FIXME: temporary compatibility, remove any slashes from port names
+ // remove this soon once patches have migrated
+ string::size_type slash_index;
+ while ((slash_index = source_port.find("/")) != string::npos)
+ source_port[slash_index] = '-';
+
+ while ((slash_index = dest_port.find("/")) != string::npos)
+ dest_port[slash_index] = '-';
+
+ ConnectionModel* cm = new ConnectionModel(parent->base_path() + source_node +"/"+ source_port,
+ parent->base_path() + dest_node +"/"+ dest_port);
+
+ return cm;
+}
+
+
+/** Build a PresetModel given a pointer to a preset in a patch file.
+ */
+PresetModel*
+PatchLibrarian::parse_preset(const PatchModel* patch, xmlDocPtr doc, const xmlNodePtr node)
+{
+ xmlNodePtr cur = node->xmlChildrenNode;
+ xmlChar* key;
+
+ PresetModel* pm = new PresetModel(patch->base_path());
+
+ while (cur != NULL) {
+ key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+
+ if ((!xmlStrcmp(cur->name, (const xmlChar*)"name"))) {
+ assert(key != NULL);
+ pm->name((char*)key);
+ } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"control"))) {
+ xmlNodePtr child = cur->xmlChildrenNode;
+
+ string node_name = "", port_name = "";
+ float val = 0.0;
+
+ while (child != NULL) {
+ key = xmlNodeListGetString(doc, child->xmlChildrenNode, 1);
+
+ if ((!xmlStrcmp(child->name, (const xmlChar*)"node-name"))) {
+ node_name = (char*)key;
+ } else if ((!xmlStrcmp(child->name, (const xmlChar*)"port-name"))) {
+ port_name = (char*)key;
+ } else if ((!xmlStrcmp(child->name, (const xmlChar*)"value"))) {
+ val = atof((char*)key);
+ }
+
+ xmlFree(key);
+ key = NULL; // Avoid a (possible?) double free
+
+ child = child->next;
+ }
+
+ if (port_name == "") {
+ string msg = "Unable to parse control in patch file ( node = ";
+ msg.append(node_name).append(", port = ").append(port_name).append(")");
+ m_client_hooks->error(msg);
+ } else {
+ // FIXME: temporary compatibility, remove any slashes from port name
+ // remove this soon once patches have migrated
+ string::size_type slash_index;
+ while ((slash_index = port_name.find("/")) != string::npos)
+ port_name[slash_index] = '-';
+ pm->add_control(node_name, port_name, val);
+ }
+ }
+ xmlFree(key);
+ key = NULL;
+ cur = cur->next;
+ }
+ if (pm->name() == "") {
+ m_client_hooks->error("Preset in patch file has no name.");
+ pm->name("Unnamed");
+ }
+
+ return pm;
+}
+
+} // namespace LibOmClient
diff --git a/src/libs/client/PatchLibrarian.h b/src/libs/client/PatchLibrarian.h
new file mode 100644
index 00000000..ace91f24
--- /dev/null
+++ b/src/libs/client/PatchLibrarian.h
@@ -0,0 +1,78 @@
+/* This file is part of Om. Copyright (C) 2005 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef PATCHLIBRARIAN_H
+#define PATCHLIBRARIAN_H
+
+#include <string>
+#include <libxml/tree.h>
+#include <cassert>
+//#include "DummyModelClientInterface.h"
+
+using std::string;
+
+namespace LibOmClient {
+
+class PatchModel;
+class NodeModel;
+class ConnectionModel;
+class PresetModel;
+class OSCModelEngineInterface;
+class ModelClientInterface;
+
+
+/** Handles all patch saving and loading.
+ *
+ * \ingroup libomclient
+ */
+class PatchLibrarian
+{
+public:
+ // FIXME: return booleans and set an errstr that can be checked?
+
+ PatchLibrarian(OSCModelEngineInterface* const osc_model_engine_interface/*,ModelClientInterface* const client_hooks*/)
+ : m_patch_path("."), m_osc_model_engine_interface(osc_model_engine_interface)//, m_client_hooks(client_hooks)
+ {
+ assert(m_osc_model_engine_interface);
+ //assert(m_client_hooks != NULL);
+ }
+
+// PatchLibrarian(OSCModelEngineInterface* osc_model_engine_interface)
+// : m_osc_model_engine_interface(osc_model_engine_interface), m_client_hooks(new DummyModelClientInterface())
+// {}
+
+ void path(const string& path) { m_patch_path = path; }
+ const string& path() { return m_patch_path; }
+
+ string find_file(const string& filename, const string& additional_path = "");
+
+ void save_patch(PatchModel* patch_model, const string& filename, bool recursive);
+ string load_patch(PatchModel* pm, bool wait = true, bool existing = false);
+
+private:
+ string m_patch_path;
+ OSCModelEngineInterface* const m_osc_model_engine_interface;
+
+ NodeModel* parse_node(const PatchModel* parent, xmlDocPtr doc, const xmlNodePtr cur);
+ ConnectionModel* parse_connection(const PatchModel* parent, xmlDocPtr doc, const xmlNodePtr cur);
+ PresetModel* parse_preset(const PatchModel* parent, xmlDocPtr doc, const xmlNodePtr cur);
+ void load_subpatch(PatchModel* parent, xmlDocPtr doc, const xmlNodePtr cur);
+};
+
+
+} // namespace LibOmClient
+
+#endif // PATCHLIBRARIAN_H
diff --git a/src/libs/client/PatchModel.cpp b/src/libs/client/PatchModel.cpp
new file mode 100644
index 00000000..829c9ca5
--- /dev/null
+++ b/src/libs/client/PatchModel.cpp
@@ -0,0 +1,229 @@
+/* This file is part of Om. Copyright (C) 2005 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "PatchModel.h"
+#include "NodeModel.h"
+#include "ConnectionModel.h"
+#include <cassert>
+#include <iostream>
+
+using std::cerr; using std::cout; using std::endl;
+
+namespace LibOmClient {
+
+
+void
+PatchModel::set_path(const Path& new_path)
+{
+ // FIXME: haack
+ if (new_path == "") {
+ m_path = "";
+ return;
+ }
+
+ NodeModel::set_path(new_path);
+ for (NodeModelMap::iterator i = m_nodes.begin(); i != m_nodes.end(); ++i)
+ (*i).second->set_path(m_path +"/"+ (*i).second->name());
+
+#ifdef DEBUG
+ // Be sure connection paths are updated and sane
+ for (list<ConnectionModel*>::iterator j = m_connections.begin();
+ j != m_connections.end(); ++j) {
+ assert((*j)->src_port_path().parent().parent() == new_path);
+ assert((*j)->src_port_path().parent().parent() == new_path);
+ }
+#endif
+}
+
+
+NodeModel*
+PatchModel::get_node(const string& name)
+{
+ assert(name.find("/") == string::npos);
+ NodeModelMap::iterator i = m_nodes.find(name);
+ return ((i != m_nodes.end()) ? (*i).second : NULL);
+}
+
+
+void
+PatchModel::add_node(NodeModel* nm)
+{
+ assert(nm != NULL);
+ assert(nm->name().find("/") == string::npos);
+ assert(nm->parent() == NULL);
+
+ m_nodes[nm->name()] = nm;
+ nm->set_parent(this);
+}
+
+
+void
+PatchModel::remove_node(const string& name)
+{
+ assert(name.find("/") == string::npos);
+ NodeModelMap::iterator i = m_nodes.find(name);
+ if (i != m_nodes.end()) {
+ delete i->second;
+ m_nodes.erase(i);
+ return;
+ }
+
+ cerr << "[PatchModel::remove_node] " << m_path << ": failed to find node " << name << endl;
+}
+
+
+void
+PatchModel::clear()
+{
+ for (list<ConnectionModel*>::iterator j = m_connections.begin(); j != m_connections.end(); ++j)
+ delete (*j);
+
+ for (NodeModelMap::iterator i = m_nodes.begin(); i != m_nodes.end(); ++i) {
+ (*i).second->clear();
+ delete (*i).second;
+ }
+
+ m_nodes.clear();
+ m_connections.clear();
+
+ NodeModel::clear();
+
+ assert(m_nodes.empty());
+ assert(m_connections.empty());
+ assert(m_ports.empty());
+}
+
+
+/** Updated the map key of the given node.
+ *
+ * The NodeModel must already have it's new path set to @a new_path, or this will
+ * abort with a fatal error.
+ */
+void
+PatchModel::rename_node(const Path& old_path, const Path& new_path)
+{
+ assert(old_path.parent() == path());
+ assert(new_path.parent() == path());
+
+ NodeModelMap::iterator i = m_nodes.find(old_path.name());
+ NodeModel* nm = NULL;
+
+ if (i != m_nodes.end()) {
+ nm = (*i).second;
+ for (list<ConnectionModel*>::iterator j = m_connections.begin(); j != m_connections.end(); ++j) {
+ if ((*j)->src_port_path().parent() == old_path)
+ (*j)->src_port_path(new_path.base_path() + (*j)->src_port_path().name());
+ if ((*j)->dst_port_path().parent() == old_path)
+ (*j)->dst_port_path(new_path.base_path() + (*j)->dst_port_path().name());
+ }
+ m_nodes.erase(i);
+ assert(nm->path() == new_path);
+ m_nodes[new_path.name()] = nm;
+ return;
+ }
+
+ cerr << "[PatchModel::rename_node] " << m_path << ": failed to find node " << old_path << endl;
+}
+
+
+ConnectionModel*
+PatchModel::get_connection(const string& src_port_path, const string& dst_port_path)
+{
+ for (list<ConnectionModel*>::iterator i = m_connections.begin(); i != m_connections.end(); ++i)
+ if ((*i)->src_port_path() == src_port_path && (*i)->dst_port_path() == dst_port_path)
+ return (*i);
+ return NULL;
+}
+
+
+/** Add a connection to this patch.
+ *
+ * Ownership of @a cm is taken, it will be deleted along with this PatchModel.
+ * If @a cm only contains paths (not pointers to the actual ports), the ports
+ * will be found and set. The ports referred to not existing as children of
+ * this patch is a fatal error.
+ */
+void
+PatchModel::add_connection(ConnectionModel* cm)
+{
+ assert(cm != NULL);
+ assert(cm->src_port_path().parent().parent() == m_path);
+ assert(cm->dst_port_path().parent().parent() == m_path);
+ assert(cm->patch_path() == path());
+
+ ConnectionModel* existing = get_connection(cm->src_port_path(), cm->dst_port_path());
+
+ if (existing != NULL) {
+ delete cm;
+ return;
+ }
+
+ NodeModel* src_node = get_node(cm->src_port_path().parent().name());
+ PortModel* src_port = (src_node == NULL) ? NULL : src_node->get_port(cm->src_port_path().name());
+ NodeModel* dst_node = get_node(cm->dst_port_path().parent().name());
+ PortModel* dst_port = (dst_node == NULL) ? NULL : dst_node->get_port(cm->dst_port_path().name());
+
+ assert(src_port != NULL);
+ assert(dst_port != NULL);
+
+ // Find source port pointer to 'resolve' connection if necessary
+ if (cm->src_port() != NULL)
+ assert(cm->src_port() == src_port);
+ else
+ cm->set_src_port(src_port);
+
+ // Find dest port pointer to 'resolve' connection if necessary
+ if (cm->dst_port() != NULL)
+ assert(cm->dst_port() == dst_port);
+ else
+ cm->set_dst_port(dst_port);
+
+ assert(cm->src_port() != NULL);
+ assert(cm->dst_port() != NULL);
+
+ m_connections.push_back(cm);
+}
+
+
+void
+PatchModel::remove_connection(const string& src_port_path, const string& dst_port_path)
+{
+ ConnectionModel* cm = NULL;
+ for (list<ConnectionModel*>::iterator i = m_connections.begin(); i != m_connections.end(); ++i) {
+ cm = (*i);
+ if (cm->src_port_path() == src_port_path && cm->dst_port_path() == dst_port_path) {
+ delete cm;
+ m_connections.erase(i);
+ assert(get_connection(src_port_path, dst_port_path) == NULL); // no duplicates
+ return;
+ }
+ }
+ cerr << "[PatchModel::remove_connection] WARNING: Failed to find connection " <<
+ src_port_path << " -> " << dst_port_path << endl;
+ return;
+}
+
+
+bool
+PatchModel::polyphonic() const
+{
+ return (m_parent == NULL)
+ ? (m_poly > 1)
+ : (m_poly > 1) && m_poly == parent_patch()->poly() && m_poly > 1;
+}
+
+
+} // namespace LibOmClient
diff --git a/src/libs/client/PatchModel.h b/src/libs/client/PatchModel.h
new file mode 100644
index 00000000..12871933
--- /dev/null
+++ b/src/libs/client/PatchModel.h
@@ -0,0 +1,88 @@
+/* This file is part of Om. Copyright (C) 2005 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef PATCHMODEL_H
+#define PATCHMODEL_H
+
+#include <cassert>
+#include <list>
+#include <string>
+#include <map>
+#include "NodeModel.h"
+
+using std::list; using std::string; using std::map;
+
+namespace LibOmClient {
+
+class ConnectionModel;
+
+
+/** Client's model of a patch.
+ *
+ * \ingroup libomclient
+ */
+class PatchModel : public NodeModel
+{
+public:
+ PatchModel(const string& patch_path, uint poly)
+ : NodeModel(patch_path),
+ m_enabled(false),
+ m_poly(poly)
+ {}
+
+ const NodeModelMap& nodes() const { return m_nodes; }
+ const list<ConnectionModel*>& connections() const { return m_connections; }
+
+ virtual void set_path(const Path& path);
+
+ NodeModel* get_node(const string& node_name);
+ void add_node(NodeModel* nm);
+ void remove_node(const string& name);
+
+ void rename_node(const Path& old_path, const Path& new_path);
+ void rename_node_port(const Path& old_path, const Path& new_path);
+ ConnectionModel* get_connection(const string& src_port_path, const string& dst_port_path);
+ void add_connection(ConnectionModel* cm);
+ void remove_connection(const string& src_port_path, const string& dst_port_path);
+
+ virtual void clear();
+
+ size_t poly() const { return m_poly; }
+ void poly(size_t p) { m_poly = p; }
+ const string& filename() const { return m_filename; }
+ void filename(const string& f) { m_filename = f; }
+ bool enabled() const { return m_enabled; }
+ void enabled(bool b) { m_enabled = b; }
+ bool polyphonic() const;
+
+private:
+ // Prevent copies (undefined)
+ PatchModel(const PatchModel& copy);
+ PatchModel& operator=(const PatchModel& copy);
+
+ NodeModelMap m_nodes;
+ list<ConnectionModel*> m_connections;
+ string m_filename;
+ bool m_enabled;
+ size_t m_poly;
+};
+
+typedef map<string, PatchModel*> PatchModelMap;
+
+
+} // namespace LibOmClient
+
+#endif // PATCHMODEL_H
diff --git a/src/libs/client/PluginModel.h b/src/libs/client/PluginModel.h
new file mode 100644
index 00000000..85365d64
--- /dev/null
+++ b/src/libs/client/PluginModel.h
@@ -0,0 +1,105 @@
+/* This file is part of Om. Copyright (C) 2005 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef PLUGINMODEL_H
+#define PLUGINMODEL_H
+
+#include <string>
+using std::string;
+
+namespace LibOmClient {
+
+
+/** Model for a plugin available for loading.
+ *
+ * \ingroup libomclient
+ */
+class PluginModel
+{
+public:
+ enum Type { LV2, LADSPA, DSSI, Internal, Patch };
+
+ // FIXME: remove
+ PluginModel() {}
+
+ PluginModel(const string& type_string, const string& uri)
+ : m_uri(uri)
+ { set_type(type_string); }
+
+ PluginModel(Type type)
+ : m_type(type)
+ {}
+
+ PluginModel(Type type, const string& uri, const string& name)
+ : m_type(type),
+ m_uri(uri),
+ m_name(name)
+ {}
+
+ PluginModel(Type type, const string& lib_name, const string& plug_label, const string& name)
+ : m_type(type),
+ m_lib_name(lib_name),
+ m_plug_label(plug_label),
+ m_name(name)
+ {}
+
+ //PluginModel() {}
+
+ Type type() const { return m_type; }
+ void type(Type t) { m_type = t; }
+ const string& uri() const { return m_uri; }
+ void uri(const string& s) { m_uri = s; }
+ const string& lib_name() const { return m_lib_name; }
+ void lib_name(const string& s) { m_lib_name = s; }
+ const string& plug_label() const { return m_plug_label; }
+ void plug_label(const string& s) { m_plug_label = s; }
+ const string& name() const { return m_name; }
+ void name(const string& s) { m_name = s; }
+
+ const char* const type_string() const {
+ if (m_type == LV2) return "LV2";
+ else if (m_type == LADSPA) return "LADSPA";
+ else if (m_type == DSSI) return "DSSI";
+ else if (m_type == Internal) return "Internal";
+ else if (m_type == Patch) return "Patch";
+ else return "";
+ }
+
+ void set_type(const string& type_string) {
+ if (type_string == "LV2") m_type = LV2;
+ else if (type_string == "LADSPA") m_type = LADSPA;
+ else if (type_string == "DSSI") m_type = DSSI;
+ else if (type_string == "Internal") m_type = Internal;
+ else if (type_string == "Patch") m_type = Patch;
+ }
+
+private:
+ // Prevent copies
+ PluginModel(const PluginModel& copy);
+ PluginModel& operator=(const PluginModel& copy);
+
+ Type m_type;
+ string m_uri;
+ string m_lib_name;
+ string m_plug_label;
+ string m_name;
+};
+
+
+} // namespace Om
+
+#endif // PLUGINMODEL_H
+
diff --git a/src/libs/client/PortModel.h b/src/libs/client/PortModel.h
new file mode 100644
index 00000000..6d0895cf
--- /dev/null
+++ b/src/libs/client/PortModel.h
@@ -0,0 +1,118 @@
+/* This file is part of Om. Copyright (C) 2005 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef PORTMODEL_H
+#define PORTMODEL_H
+
+#include <cstdlib>
+#include <string>
+#include <list>
+#include "ObjectModel.h"
+using std::string; using std::list;
+
+namespace LibOmClient {
+
+/* Model of a port.
+ *
+ * \ingroup libomclient.
+ */
+class PortModel : public ObjectModel
+{
+public:
+ enum Type { CONTROL, AUDIO, MIDI };
+ enum Direction { INPUT, OUTPUT };
+ enum Hint { NONE, INTEGER, TOGGLE, LOGARITHMIC };
+
+ PortModel(const string& path, Type type, Direction dir, Hint hint,
+ float default_val, float min, float max)
+ : ObjectModel(path),
+ m_type(type),
+ m_direction(dir),
+ m_hint(hint),
+ m_default_val(default_val),
+ m_min_val(min),
+ m_user_min(min),
+ m_max_val(max),
+ m_user_max(max),
+ m_current_val(default_val),
+ m_connected(false)
+ {
+ }
+
+ PortModel(const string& path, Type type, Direction dir)
+ : ObjectModel(path),
+ m_type(type),
+ m_direction(dir),
+ m_hint(NONE),
+ m_default_val(0.0f),
+ m_min_val(0.0f),
+ m_user_min(0.0f),
+ m_max_val(0.0f),
+ m_user_max(0.0f),
+ m_current_val(0.0f),
+ m_connected(false)
+ {
+ }
+
+ inline float min_val() const { return m_min_val; }
+ inline float user_min() const { return m_user_min; }
+ inline void user_min(float f) { m_user_min = f; }
+ inline float default_val() const { return m_default_val; }
+ inline void default_val(float f) { m_default_val = f; }
+ inline float max_val() const { return m_max_val; }
+ inline float user_max() const { return m_user_max; }
+ inline void user_max(float f) { m_user_max = f; }
+ inline float value() const { return m_current_val; }
+ inline void value(float f) { m_current_val = f; }
+ inline bool connected() { return m_connected; }
+ inline void connected(bool b) { m_connected = b; }
+ inline Type type() { return m_type; }
+
+ inline bool is_input() const { return (m_direction == INPUT); }
+ inline bool is_output() const { return (m_direction == OUTPUT); }
+ inline bool is_audio() const { return (m_type == AUDIO); }
+ inline bool is_control() const { return (m_type == CONTROL); }
+ inline bool is_midi() const { return (m_type == MIDI); }
+ inline bool is_logarithmic() const { return (m_hint == LOGARITHMIC); }
+ inline bool is_integer() const { return (m_hint == INTEGER); }
+ inline bool is_toggle() const { return (m_hint == TOGGLE); }
+
+ inline bool operator==(const PortModel& pm)
+ { return (m_path == pm.m_path); }
+
+private:
+ // Prevent copies (undefined)
+ PortModel(const PortModel& copy);
+ PortModel& operator=(const PortModel& copy);
+
+ Type m_type;
+ Direction m_direction;
+ Hint m_hint;
+ float m_default_val;
+ float m_min_val;
+ float m_user_min;
+ float m_max_val;
+ float m_user_max;
+ float m_current_val;
+ bool m_connected;
+};
+
+typedef list<PortModel*> PortModelList;
+
+
+} // namespace LibOmClient
+
+#endif // PORTMODEL_H
diff --git a/src/libs/client/PresetModel.h b/src/libs/client/PresetModel.h
new file mode 100644
index 00000000..bd187dfc
--- /dev/null
+++ b/src/libs/client/PresetModel.h
@@ -0,0 +1,66 @@
+/* This file is part of Om. Copyright (C) 2005 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License alongCont
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef PRESETMODEL_H
+#define PRESETMODEL_H
+
+#include <string>
+#include <list>
+#include "ControlModel.h"
+
+using std::string; using std::list;
+
+namespace LibOmClient {
+
+
+/** Model of a preset (a collection of control settings).
+ *
+ * \ingroup libomclient
+ */
+class PresetModel
+{
+public:
+ PresetModel(const string& base_path)
+ : m_base_path(base_path)
+ {}
+
+ /** Add a control value to this preset. An empty string for a node_name
+ * means the port is on the patch itself (not a node in the patch).
+ */
+ void add_control(const string& node_name,
+ const string& port_name, float value)
+ {
+ if (node_name != "")
+ m_controls.push_back(ControlModel(m_base_path + node_name +"/"+ port_name, value));
+ else
+ m_controls.push_back(ControlModel(m_base_path + port_name, value));
+ }
+
+ const string& name() { return m_name; }
+ void name(const string& n) { m_name = n; }
+
+ const list<ControlModel>& controls() const { return m_controls; }
+
+private:
+ string m_name;
+ string m_base_path;
+ list<ControlModel> m_controls;
+};
+
+
+} // namespace LibOmClient
+
+#endif // PRESETMODEL
diff --git a/src/libs/client/SigClientInterface.h b/src/libs/client/SigClientInterface.h
new file mode 100644
index 00000000..f384f239
--- /dev/null
+++ b/src/libs/client/SigClientInterface.h
@@ -0,0 +1,120 @@
+/* This file is part of Om. Copyright (C) 2006 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef SIGCLIENTINTERFACE_H
+#define SIGCLIENTINTERFACE_H
+
+#include <inttypes.h>
+#include <string>
+#include <sigc++/sigc++.h>
+#include "interface/ClientInterface.h"
+using std::string;
+
+namespace LibOmClient {
+
+
+/** A LibSigC++ signal emitting interface for clients to use.
+ *
+ * sed would have the copyright to this code if it was a legal person.
+ */
+class SigClientInterface : virtual public Om::Shared::ClientInterface, public sigc::trackable
+{
+public:
+ sigc::signal<void> bundle_begin_sig;
+ sigc::signal<void> bundle_end_sig;
+ sigc::signal<void, const string&> error_sig;
+ sigc::signal<void, uint32_t> num_plugins_sig;
+ sigc::signal<void, const string&, const string&, const string&> new_plugin_sig;
+ sigc::signal<void, const string&, uint32_t> new_patch_sig;
+ sigc::signal<void, const string&, const string&, const string&, bool, uint32_t> new_node_sig;
+ sigc::signal<void, const string&, const string&, bool> new_port_sig;
+ sigc::signal<void, const string&> patch_enabled_sig;
+ sigc::signal<void, const string&> patch_disabled_sig;
+ sigc::signal<void, const string&> patch_cleared_sig;
+ sigc::signal<void, const string&, const string&> object_renamed_sig;
+ sigc::signal<void, const string&> object_destroyed_sig;
+ sigc::signal<void, const string&, const string&> connection_sig;
+ sigc::signal<void, const string&, const string&> disconnection_sig;
+ sigc::signal<void, const string&, const string&, const string&> metadata_update_sig;
+ sigc::signal<void, const string&, float> control_change_sig;
+ sigc::signal<void, const string&, uint32_t, uint32_t, const string&> program_add_sig;
+ sigc::signal<void, const string&, uint32_t, uint32_t> program_remove_sig;
+
+ inline void emit_bundle_begin()
+ { bundle_begin_sig.emit(); }
+
+ inline void emit_bundle_end()
+ { bundle_end_sig.emit(); }
+
+ inline void emit_error(const string& msg)
+ { error_sig.emit(msg); }
+
+ inline void emit_num_plugins(uint32_t num)
+ { num_plugins_sig.emit(num); }
+
+ inline void emit_new_plugin(const string& type, const string& uri, const string& name)
+ { new_plugin_sig.emit(type, uri, name); }
+
+ inline void emit_new_patch(const string& path, uint32_t poly)
+ { new_patch_sig.emit(path, poly); }
+
+ inline void emit_new_node(const string& plugin_type, const string& plugin_uri, const string& node_path, bool is_polyphonic, uint32_t num_ports)
+ { new_node_sig.emit(plugin_type, plugin_uri, node_path, is_polyphonic, num_ports); }
+
+ inline void emit_new_port(const string& path, const string& data_type, bool is_output)
+ { new_port_sig.emit(path, data_type, is_output); }
+
+ inline void emit_patch_enabled(const string& path)
+ { patch_enabled_sig.emit(path); }
+
+ inline void emit_patch_disabled(const string& path)
+ { patch_disabled_sig.emit(path); }
+
+ inline void emit_patch_cleared(const string& path)
+ { patch_cleared_sig.emit(path); }
+
+ inline void emit_object_renamed(const string& old_path, const string& new_path)
+ { object_renamed_sig.emit(old_path, new_path); }
+
+ inline void emit_object_destroyed(const string& path)
+ { object_destroyed_sig.emit(path); }
+
+ inline void emit_connection(const string& src_port_path, const string& dst_port_path)
+ { connection_sig.emit(src_port_path, dst_port_path); }
+
+ inline void emit_disconnection(const string& src_port_path, const string& dst_port_path)
+ { disconnection_sig.emit(src_port_path, dst_port_path); }
+
+ inline void emit_metadata_update(const string& subject_path, const string& predicate, const string& value)
+ { metadata_update_sig.emit(subject_path, predicate, value); }
+
+ inline void emit_control_change(const string& port_path, float value)
+ { control_change_sig.emit(port_path, value); }
+
+ inline void emit_program_add(const string& node_path, uint32_t bank, uint32_t program, const string& program_name)
+ { program_add_sig.emit(node_path, bank, program, program_name); }
+
+ inline void emit_program_remove(const string& node_path, uint32_t bank, uint32_t program)
+ { program_remove_sig.emit(node_path, bank, program); }
+
+protected:
+ SigClientInterface() {}
+};
+
+
+} // namespace LibOmClient
+
+#endif
diff --git a/src/libs/client/ThreadedSigClientInterface.cpp b/src/libs/client/ThreadedSigClientInterface.cpp
new file mode 100644
index 00000000..860d91f6
--- /dev/null
+++ b/src/libs/client/ThreadedSigClientInterface.cpp
@@ -0,0 +1,71 @@
+/* This file is part of Om. Copyright (C) 2006 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "ThreadedSigClientInterface.h"
+#include <iostream>
+using std::cerr; using std::endl;
+
+namespace LibOmClient {
+
+
+/** Push an event (from the engine, ie 'new patch') on to the queue.
+ */
+void
+ThreadedSigClientInterface::push_sig(Closure ev)
+{
+ cerr << "-- pushing event\n";
+
+ bool success = false;
+ bool first = true;
+
+ // (Very) slow busy-wait if the queue is full
+ // FIXME: Make this wait on a signal from process_sigs iff this happens
+ while (!success) {
+ success = _sigs.push(ev);
+ if (!success) {
+ if (first) {
+ cerr << "[ThreadedSigClientInterface] WARNING: (Client) event queue full. Waiting to try again..." << endl;
+ first = false;
+ }
+ usleep(200000); // 100 milliseconds (2* rate process_sigs is called)
+ }
+ }
+}
+
+
+/** Process all queued events that came from the OSC thread.
+ *
+ * This function should be called from the Gtk thread to emit signals and cause
+ * the connected methods to execute.
+ */
+bool
+ThreadedSigClientInterface::emit_signals()
+{
+ // Process a maximum of queue-size events, to prevent locking the GTK
+ // thread indefinitely while processing continually arriving events
+ size_t num_processed = 0;
+ while (!_sigs.is_empty() && num_processed++ < _sigs.capacity()/2) {
+ cerr << "-- emitting event\n";
+ Closure& ev = _sigs.pop();
+ ev();
+ ev.disconnect();
+ }
+
+ return true;
+}
+
+
+} // namespace LibOmClient
diff --git a/src/libs/client/ThreadedSigClientInterface.h b/src/libs/client/ThreadedSigClientInterface.h
new file mode 100644
index 00000000..16259531
--- /dev/null
+++ b/src/libs/client/ThreadedSigClientInterface.h
@@ -0,0 +1,177 @@
+/* This file is part of Om. Copyright (C) 2006 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef THREADEDSIGCLIENTINTERFACE_H
+#define THREADEDSIGCLIENTINTERFACE_H
+
+#include <inttypes.h>
+#include <string>
+#include <sigc++/sigc++.h>
+#include "interface/ClientInterface.h"
+#include "SigClientInterface.h"
+#include "util/Queue.h"
+using std::string;
+
+/** Returns nothing and takes no parameters (because they have all been bound) */
+typedef sigc::slot<void> Closure;
+
+namespace LibOmClient {
+
+
+/** A LibSigC++ signal emitting interface for clients to use.
+ *
+ * This emits signals (possibly) in a different thread than the ClientInterface
+ * functions are called. It must be explicitly driven with the emit_signals()
+ * function, which fires all enqueued signals up until the present. You can
+ * use this in a GTK idle callback for receiving thread safe engine signals.
+ */
+class ThreadedSigClientInterface : virtual public SigClientInterface
+{
+public:
+ ThreadedSigClientInterface(uint32_t queue_size)
+ : _sigs(queue_size)
+ , error_slot(sigc::mem_fun((SigClientInterface*)this, &SigClientInterface::emit_error))
+ //, new_plugin_slot(sigc::mem_fun((SigClientInterface*)this, &SigClientInterface::emit_new_plugin_model))
+ //, new_patch_slot(sigc::mem_fun((SigClientInterface*)this, &SigClientInterface::emit_new_patch_model))
+ //, new_node_slot(sigc::mem_fun((SigClientInterface*)this, &SigClientInterface::emit_new_node_model))
+ //, new_port_slot(sigc::mem_fun((SigClientInterface*)this, &SigClientInterface::emit_new_port_model))
+ //, connection_slot(sigc::mem_fun((SigClientInterface*)this, &SigClientInterface::emit_connection_model))
+ , new_plugin_slot(sigc::mem_fun((SigClientInterface*)this, &SigClientInterface::emit_new_plugin))
+ , new_patch_slot(sigc::mem_fun((SigClientInterface*)this, &SigClientInterface::emit_new_patch))
+ , new_node_slot(sigc::mem_fun((SigClientInterface*)this, &SigClientInterface::emit_new_node))
+ , new_port_slot(sigc::mem_fun((SigClientInterface*)this, &SigClientInterface::emit_new_port))
+ , connection_slot(sigc::mem_fun((SigClientInterface*)this, &SigClientInterface::emit_connection))
+ , patch_enabled_slot(sigc::mem_fun((SigClientInterface*)this, &SigClientInterface::emit_patch_enabled))
+ , patch_disabled_slot(sigc::mem_fun((SigClientInterface*)this, &SigClientInterface::emit_patch_disabled))
+ , patch_cleared_slot(sigc::mem_fun((SigClientInterface*)this, &SigClientInterface::emit_patch_cleared))
+ , object_destroyed_slot(sigc::mem_fun((SigClientInterface*)this, &SigClientInterface::emit_object_destroyed))
+ , object_renamed_slot(sigc::mem_fun((SigClientInterface*)this, &SigClientInterface::emit_object_renamed))
+ , disconnection_slot(sigc::mem_fun((SigClientInterface*)this, &SigClientInterface::emit_disconnection))
+ , metadata_update_slot(sigc::mem_fun((SigClientInterface*)this, &SigClientInterface::emit_metadata_update))
+ , control_change_slot(sigc::mem_fun((SigClientInterface*)this, &SigClientInterface::emit_control_change))
+ , program_add_slot(sigc::mem_fun((SigClientInterface*)this, &SigClientInterface::emit_program_add))
+ , program_remove_slot(sigc::mem_fun((SigClientInterface*)this, &SigClientInterface::emit_program_remove))
+ {}
+
+
+ // FIXME
+ void bundle_begin() {}
+ void bundle_end() {}
+
+ void num_plugins(uint32_t num) { _num_plugins = num; }
+
+ void error(const string& msg)
+ { push_sig(sigc::bind(error_slot, msg)); }
+ /*
+ void new_plugin_model(PluginModel* const pm)
+ { push_sig(sigc::bind(new_plugin_slot, pm)); }
+
+ void new_patch_model(PatchModel* const pm)
+ { push_sig(sigc::bind(new_patch_slot, pm)); }
+
+ void new_node_model(NodeModel* const nm)
+ { assert(nm); push_sig(sigc::bind(new_node_slot, nm)); }
+
+ void new_port_model(PortModel* const pm)
+ { push_sig(sigc::bind(new_port_slot, pm)); }
+
+ void connection_model(ConnectionModel* const cm)
+ { push_sig(sigc::bind(connection_slot, cm)); }
+ */
+ void new_plugin(const string& type, const string& uri, const string& name)
+ { push_sig(sigc::bind(new_plugin_slot, type, uri, name)); }
+
+ void new_patch(const string& path, uint32_t poly)
+ { push_sig(sigc::bind(new_patch_slot, path, poly)); }
+
+ void new_node(const string& plugin_type, const string& plugin_uri, const string& node_path, bool is_polyphonic, uint32_t num_ports)
+ { push_sig(sigc::bind(new_node_slot, plugin_type, plugin_uri, node_path, is_polyphonic, num_ports)); }
+
+ void new_port(const string& path, const string& data_type, bool is_output)
+ { push_sig(sigc::bind(new_port_slot, path, data_type, is_output)); }
+
+ void connection(const string& src_port_path, const string& dst_port_path)
+ { push_sig(sigc::bind(connection_slot, src_port_path, dst_port_path)); }
+
+ void object_destroyed(const string& path)
+ { push_sig(sigc::bind(object_destroyed_slot, path)); }
+
+ void patch_enabled(const string& path)
+ { push_sig(sigc::bind(patch_enabled_slot, path)); }
+
+ void patch_disabled(const string& path)
+ { push_sig(sigc::bind(patch_disabled_slot, path)); }
+
+ void patch_cleared(const string& path)
+ { push_sig(sigc::bind(patch_cleared_slot, path)); }
+
+ void object_renamed(const string& old_path, const string& new_path)
+ { push_sig(sigc::bind(object_renamed_slot, old_path, new_path)); }
+
+ void disconnection(const string& src_port_path, const string& dst_port_path)
+ { push_sig(sigc::bind(disconnection_slot, src_port_path, dst_port_path)); }
+
+ void metadata_update(const string& path, const string& key, const string& value)
+ { push_sig(sigc::bind(metadata_update_slot, path, key, value)); }
+
+ void control_change(const string& port_path, float value)
+ { push_sig(sigc::bind(control_change_slot, port_path, value)); }
+
+ void program_add(const string& path, uint32_t bank, uint32_t program, const string& name)
+ { push_sig(sigc::bind(program_add_slot, path, bank, program, name)); }
+
+ void program_remove(const string& path, uint32_t bank, uint32_t program)
+ { push_sig(sigc::bind(program_remove_slot, path, bank, program)); }
+
+ /** Process all queued events - Called from GTK thread to emit signals. */
+ bool emit_signals();
+
+private:
+ void push_sig(Closure ev);
+
+ Queue<Closure> _sigs;
+ uint32_t _num_plugins;
+
+ sigc::slot<void> bundle_begin_slot;
+ sigc::slot<void> bundle_end_slot;
+ sigc::slot<void, uint32_t> num_plugins_slot;
+ sigc::slot<void, string> error_slot;
+ /*sigc::slot<void, PluginModel*> new_plugin_slot;
+ sigc::slot<void, PatchModel*> new_patch_slot;
+ sigc::slot<void, NodeModel*> new_node_slot;
+ sigc::slot<void, PortModel*> new_port_slot;
+ sigc::slot<void, ConnectionModel*> connection_slot; */
+ sigc::slot<void, string, string, string> new_plugin_slot;
+ sigc::slot<void, string, uint32_t> new_patch_slot;
+ sigc::slot<void, string, string, string, bool, int> new_node_slot;
+ sigc::slot<void, string, string, bool> new_port_slot;
+ sigc::slot<void, string, string> connection_slot;
+ sigc::slot<void, string> patch_enabled_slot;
+ sigc::slot<void, string> patch_disabled_slot;
+ sigc::slot<void, string> patch_cleared_slot;
+ sigc::slot<void, string> object_destroyed_slot;
+ sigc::slot<void, string, string> object_renamed_slot;
+ sigc::slot<void, string, string> disconnection_slot;
+ sigc::slot<void, string, string, string> metadata_update_slot;
+ sigc::slot<void, string, float> control_change_slot;
+ sigc::slot<void, string, uint32_t, uint32_t, const string&> program_add_slot;
+ sigc::slot<void, string, uint32_t, uint32_t> program_remove_slot;
+};
+
+
+} // namespace LibOmClient
+
+#endif