diff options
Diffstat (limited to 'src/libs/client')
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 |