From 35a5d92cfcf6815553a0939c3e2bf77c1108fd31 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sat, 30 Sep 2006 06:47:00 +0000 Subject: Work on RDF serialization (only (partial) saving so far). git-svn-id: http://svn.drobilla.net/lad/ingen@146 a436a847-0d15-0410-975c-d299462d15a1 --- configure.ac | 29 +- src/common/util/Atom.h | 12 +- src/common/util/LibloAtom.h | 2 +- src/common/util/Makefile.am | 3 +- src/common/util/Path.h | 28 +- src/common/util/RedlandAtom.h | 77 +++ src/libs/client/ConnectionModel.cpp | 2 + src/libs/client/ConnectionModel.h | 2 + src/libs/client/DeprecatedSerializer.cpp | 662 ++++++++++++++++++++++ src/libs/client/DeprecatedSerializer.h | 85 +++ src/libs/client/Makefile.am | 13 +- src/libs/client/ObjectModel.h | 5 +- src/libs/client/PatchLibrarian.cpp | 918 ------------------------------- src/libs/client/PatchLibrarian.h | 87 --- src/libs/client/PatchModel.h | 14 +- src/libs/client/Serializer.cpp | 525 ++++++++++++++++++ src/libs/client/Serializer.h | 118 ++++ src/libs/engine/Makefile.am | 2 + src/progs/demolition/Makefile.am | 4 +- src/progs/demolition/demolition.cpp | 8 +- src/progs/ingenuity/Loader.cpp | 42 +- src/progs/ingenuity/Loader.h | 16 +- src/progs/ingenuity/Makefile.am | 11 +- src/progs/ingenuity/NodeModule.cpp | 12 +- src/progs/ingenuity/PatchCanvas.cpp | 8 +- src/progs/ingenuity/PatchPortModule.cpp | 16 +- src/progs/patch_loader/Makefile.am | 10 +- src/progs/patch_loader/patch_loader.cpp | 6 +- 28 files changed, 1616 insertions(+), 1101 deletions(-) create mode 100644 src/common/util/RedlandAtom.h create mode 100644 src/libs/client/DeprecatedSerializer.cpp create mode 100644 src/libs/client/DeprecatedSerializer.h delete mode 100644 src/libs/client/PatchLibrarian.cpp delete mode 100644 src/libs/client/PatchLibrarian.h create mode 100644 src/libs/client/Serializer.cpp create mode 100644 src/libs/client/Serializer.h diff --git a/configure.ac b/configure.ac index b9ed65c1..be4d1178 100644 --- a/configure.ac +++ b/configure.ac @@ -1,4 +1,4 @@ -AC_PREREQ(2.59) +C_PREREQ(2.59) AC_INIT([ingen],[0.4.0pre],[drobilla@connect.carleton.ca]) AC_CONFIG_SRCDIR([src/common/util/CountedPtr.h]) AC_CONFIG_SRCDIR([src/common/interface/EngineInterface.h]) @@ -40,10 +40,10 @@ AC_SUBST(LOSC_CFLAGS) # Check for pthreads AC_CHECK_LIB(pthread, pthread_create, [], AC_MSG_ERROR([*** Ingen requires POSIX threads support])) -AC_CHECK_HEADER(pthread.h) +AC_CHECK_HEADER([pthread.h]) # Check for POSIX semaphores in rt library -AC_CHECK_HEADERS([semaphore.h]) +AC_CHECK_HEADER([semaphore.h]) AC_CHECK_LIB([rt], [sem_post]) # Check for LASH @@ -83,10 +83,10 @@ fi pointer_debug="no" AC_ARG_ENABLE(debug, [AS_HELP_STRING(--enable-pointer-debug, [Enable smart pointer debugging (no)])], - [pointer-debug="$enableval"]) + [pointer_debug="$enableval"]) if test "$pointer_debug" = "yes"; then - CFLAGS+="-DBOOST_SP_ENABLE_DEBUG_HOOKS" - CXXFLAGS+="-DBOOST_SP_ENABLE_DEBUG_HOOKS" + CFLAGS+=" -DBOOST_SP_ENABLE_DEBUG_HOOKS" + CXXFLAGS+=" -DBOOST_SP_ENABLE_DEBUG_HOOKS" fi # Use strict flags? @@ -172,7 +172,7 @@ AC_ARG_ENABLE([server], # Command-line clients build_console_clients="yes" AC_ARG_ENABLE([console-clients], - AS_HELP_STRING(--enable-console-clients, [Build command-line clients (yes) - Requires: libxml2, libsigc++]), + AS_HELP_STRING(--enable-console-clients, [Build command-line clients (yes) - Requires: libxml2, librdf, libsigc++]), [ if test x$enable_console_clients = xno ; then build_console_clients=no ; fi ]) # Gtk client (Ingenuity) @@ -235,7 +235,7 @@ if test "$build_server" = "yes" -o "$monolothic_ingenuity"; then # Check for LADSPA if test "$build_ladspa" = "yes"; then build_ladspa="no" - AC_CHECK_HEADER(ladspa.h, [build_ladspa="yes"], + AC_CHECK_HEADER([ladspa.h], [build_ladspa="yes"], [AC_MSG_WARN([You don't seem to build ladspa.h, Ingen will not be very useful!])]) fi if test "$build_ladspa" = "yes"; then @@ -264,7 +264,7 @@ if test "$build_server" = "yes" -o "$monolothic_ingenuity"; then fi if test "$build_dssi" = "yes"; then build_dssi="no" - AC_CHECK_HEADER(dssi.h, [build_dssi="yes"], + AC_CHECK_HEADER([dssi.h], [build_dssi="yes"], [AC_MSG_WARN([You do not seem to have dssi.h, Ingen will be built without DSSI support])]) if test "$build_dssi" = "yes"; then AC_DEFINE(HAVE_DSSI, 1, [Has dssi.h]) @@ -291,11 +291,20 @@ AM_CONDITIONAL(WITH_DSSI, [test "$build_dssi" = "yes"]) if test "$build_console_clients" = "yes"; then - # Check for libxml2 + # Check for libxml2 # FIXME: deprecated, make optional PKG_CHECK_MODULES(LXML2, libxml-2.0 >= 2.6.0) AC_SUBST(LXML2_LIBS) AC_SUBST(LXML2_CFLAGS) + # Check for redland (for RDF serialization) + # FIXME: WTF? + # PKG_CHECK_MODULES(REDLAND, redland >= 0.4.0) + AC_SUBST(REDLAND_LIBS, "-lrdf") + AC_SUBST(REDLAND_CFLAGS, "") + AC_CHECK_LIB(rdf, librdf_new_world, [], + AC_MSG_ERROR([*** Console clients require Redland (librdf)])) + AC_CHECK_HEADER([redland.h]) + # Check for sigc++ (FIXME: make this only necessary where.. uh.. necessary) PKG_CHECK_MODULES(LSIGCPP, sigc++-2.0) AC_SUBST(LSIGCPP_CFLAGS) diff --git a/src/common/util/Atom.h b/src/common/util/Atom.h index f28b9339..2a6ae982 100644 --- a/src/common/util/Atom.h +++ b/src/common/util/Atom.h @@ -20,6 +20,9 @@ #include #include #include +#include + +using std::string; /** An OSC atom (fundamental data types OSC messages are composed of). @@ -34,10 +37,11 @@ public: BLOB }; - Atom() : _type(NIL), _blob_val(0) {} - Atom(int32_t val) : _type(INT), _int_val(val) {} - Atom(float val) : _type(FLOAT), _float_val(val) {} - Atom(const char* val) : _type(STRING), _string_val(strdup(val)) {} + Atom() : _type(NIL), _blob_val(0) {} + Atom(int32_t val) : _type(INT), _int_val(val) {} + Atom(float val) : _type(FLOAT), _float_val(val) {} + Atom(const char* val) : _type(STRING), _string_val(strdup(val)) {} + Atom(const string& val) : _type(STRING), _string_val(strdup(val.c_str())) {} Atom(void* val) : _type(BLOB), _blob_size(sizeof(val)), _blob_val(malloc(_blob_size)) { memcpy(_blob_val, val, sizeof(_blob_size)); } diff --git a/src/common/util/LibloAtom.h b/src/common/util/LibloAtom.h index 02a469fe..db76a807 100644 --- a/src/common/util/LibloAtom.h +++ b/src/common/util/LibloAtom.h @@ -23,7 +23,7 @@ /** Support for serializing an Atom to/from liblo messages. * - * (Here to prevent a unnecessary liblo dependency on Atom). + * (Here to prevent a unnecessary liblo dependency for Atom). */ class LibloAtom { public: diff --git a/src/common/util/Makefile.am b/src/common/util/Makefile.am index 845a5d9a..6bddb707 100644 --- a/src/common/util/Makefile.am +++ b/src/common/util/Makefile.am @@ -8,4 +8,5 @@ EXTRA_DIST = \ Thread.h \ Slave.h \ Atom.h \ - LibloAtom.h + LibloAtom.h \ + RedlandAtom.h diff --git a/src/common/util/Path.h b/src/common/util/Path.h index 2fcf5774..bcb047ad 100644 --- a/src/common/util/Path.h +++ b/src/common/util/Path.h @@ -17,6 +17,7 @@ #ifndef PATH_H #define PATH_H +#include #include #include using std::string; @@ -137,7 +138,7 @@ public: /** Convert a string to a valid name (or "method" - tokens between slashes) * - * This will strip all slashes and always return a valid name/method. + * This will strip all slashes, etc, and always return a valid name/method. */ static string nameify(const std::basic_string& str) { @@ -155,18 +156,22 @@ public: /** Replace any invalid characters in @a str with a suitable replacement. + * + * Makes a pretty name - underscores are a valid character, but this chops + * both spaces and underscores, uppercasing the next letter, to create + * uniform CamelCase names that look nice */ static void replace_invalid_chars(string& str, bool replace_slash = false) { for (size_t i=0; i < str.length(); ++i) { - if (str[i] == ' ') { - str[i] = '_'; + if (str[i] == ' ' || str[i] == '_') { + str[i+1] = std::toupper(str[i+1]); // capitalize next char + str = str.substr(0, i) + str.substr(i+1); // chop space/underscore } else if (str[i] == '[' || str[i] == '{') { str[i] = '('; } else if (str[i] == ']' || str[i] == '}') { str[i] = ')'; } else if (str[i] < 32 || str.at(i) > 126 - || str[i] == ' ' || str[i] == '#' || str[i] == '*' || str[i] == ',' @@ -175,6 +180,21 @@ public: str[i] = '.'; } } + + // Chop brackets + while (true) { + + const string::size_type open = str.find("("); + const string::size_type close = str.find(")"); + + if (open != string::npos) { + if (close != string::npos) + str.erase(open, (close - open) + 1); + } else { + break; + } + + } } diff --git a/src/common/util/RedlandAtom.h b/src/common/util/RedlandAtom.h new file mode 100644 index 00000000..6b5658cf --- /dev/null +++ b/src/common/util/RedlandAtom.h @@ -0,0 +1,77 @@ +/* This file is part of Ingen. Copyright (C) 2006 Dave Robillard. + * + * Ingen is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef REDLAND_ATOM_H +#define REDLAND_ATOM_H + +#include +#include "util/Atom.h" + +#define U(x) ((const unsigned char*)(x)) + +/** Support for serializing an Atom to/from RDF (via redland aka librdf). + * + * (Here to prevent a unnecessary redland dependency for Atom). + */ +class RedlandAtom { +public: + static librdf_node* atom_to_node(librdf_world* world, const Atom& atom) { + char tmp_buf[32]; + + switch (atom.type()) { + //case NIL: + // (see below) + //break; + case Atom::INT: + snprintf(tmp_buf, 32, "%d", atom.get_int32()); + return librdf_new_node_from_typed_literal(world, U(tmp_buf), NULL, librdf_new_uri(world, U("http://www.w3.org/2001/XMLSchema#integer"))); + break; + case Atom::FLOAT: + snprintf(tmp_buf, 32, "%f", atom.get_float()); + return librdf_new_node_from_typed_literal(world, U(tmp_buf), NULL, librdf_new_uri(world, U("http://www.w3.org/2001/XMLSchema#float"))); + break; + case Atom::STRING: + return librdf_new_node_from_literal(world, U(atom.get_string()), NULL, 0); + case Atom::BLOB: + cerr << "WARNING: Unserializable atom!" << endl; + return NULL; + default: // This catches Atom::Type::NIL too + return librdf_new_node(world); // blank node + } + } + + static Atom node_to_atom(librdf_node* node) { + /*switch (type) { + case 'i': + return Atom(arg->i); + case 'f': + return Atom(arg->f); + case 's': + return Atom(&arg->s); + //case 'b' + // FIXME: How to get a blob from a lo_arg? + //return Atom(arg->b); + default: + return Atom(); + }*/ + cerr << "FIXME: node_to_atom\n"; + return Atom(); + } + +}; + + +#endif // REDLAND_ATOM_H diff --git a/src/libs/client/ConnectionModel.cpp b/src/libs/client/ConnectionModel.cpp index e120207d..e7f66077 100644 --- a/src/libs/client/ConnectionModel.cpp +++ b/src/libs/client/ConnectionModel.cpp @@ -101,6 +101,8 @@ ConnectionModel::patch_path() const return patch_path; } +typedef list > ConnectionList; + } // namespace Client } // namespace Ingen diff --git a/src/libs/client/ConnectionModel.h b/src/libs/client/ConnectionModel.h index 4c0d18f8..e1530f88 100644 --- a/src/libs/client/ConnectionModel.h +++ b/src/libs/client/ConnectionModel.h @@ -68,6 +68,8 @@ private: CountedPtr _dst_port; }; +typedef list > ConnectionList; + } // namespace Client } // namespace Ingen diff --git a/src/libs/client/DeprecatedSerializer.cpp b/src/libs/client/DeprecatedSerializer.cpp new file mode 100644 index 00000000..1526c527 --- /dev/null +++ b/src/libs/client/DeprecatedSerializer.cpp @@ -0,0 +1,662 @@ +/* This file is part of Ingen. Copyright (C) 2006 Dave Robillard. + * + * Ingen is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "DeprecatedSerializer.h" +#include +#include +#include +#include +#include "PatchModel.h" +#include "NodeModel.h" +#include "ConnectionModel.h" +#include "PortModel.h" +#include "PresetModel.h" +#include "ModelEngineInterface.h" +#include "PluginModel.h" +#include "util/Path.h" +#include +#include +#include +#include // for pair, make_pair +#include +#include +#include +#include // for atof +#include + +using std::string; using std::vector; using std::pair; +using std::cerr; using std::cout; using std::endl; + +namespace Ingen { +namespace Client { + + +/** 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 +DeprecatedSerializer::find_file(const string& filename, const string& additional_path) +{ + string search_path = additional_path + ":" + _patch_search_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 << "[DeprecatedSerializer] Could not find patch file " << full_patch_path << endl; + } + } + + return ""; +} + + +string +DeprecatedSerializer::translate_load_path(const string& path) +{ + std::map::iterator t = _load_path_translations.find(path); + + if (t != _load_path_translations.end()) { + return (*t).second; + } else { + assert(Path::is_valid(path)); + return path; + } +} + + +/** 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. + * + * @param wait If true 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). + * + * @param existing If 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. + * + * @param parent_path Patch to load this patch as a child of (empty string to load + * to the root patch) + * + * @param name Name of this patch (loaded/generated if the empty string) + * + * @param initial_data will be set last, so values passed there will override + * any values loaded from the patch file. + * + * Returns the path of the newly created patch. + */ +string +DeprecatedSerializer::load_patch(const string& filename, + const string& parent_path, + const string& name, + size_t poly, + MetadataMap initial_data, + bool existing) +{ + cerr << "[DeprecatedSerializer] Loading patch " << filename << "" << endl; + + Path path = "/"; // path of the new patch + + const bool load_name = (name == ""); + const bool load_poly = (poly == 0); + + if (initial_data.find("filename") == initial_data.end()) + initial_data["filename"] = Atom(filename.c_str()); // FIXME: URL? + + xmlDocPtr doc = xmlParseFile(filename.c_str()); + + if (!doc) { + cerr << "Unable to parse patch file." << endl; + return ""; + } + + xmlNodePtr cur = xmlDocGetRootElement(doc); + + if (!cur) { + cerr << "Empty document." << endl; + xmlFreeDoc(doc); + return ""; + } + + if (xmlStrcmp(cur->name, (const xmlChar*) "patch")) { + cerr << "File is not an Ingen patch file (root node != )" << endl; + xmlFreeDoc(doc); + return ""; + } + + xmlChar* key = NULL; + cur = cur->xmlChildrenNode; + + // 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 (parent_path != "") + path = Path(parent_path).base() + Path::nameify((char*)key); + } + } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"polyphony"))) { + if (load_poly) { + poly = atoi((char*)key); + } + } 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) + if (initial_data.find((const char*)cur->name) == initial_data.end()) + initial_data[(const char*)cur->name] = (const char*)key; + } + + xmlFree(key); + key = NULL; // Avoid a (possible?) double free + + cur = cur->next; + } + + if (poly == 0) + poly = 1; + + // Create it, if we're not merging + if (!existing) + _engine->create_patch_with_data(path, poly, initial_data); + + // Load nodes + cur = xmlDocGetRootElement(doc)->xmlChildrenNode; + while (cur != NULL) { + if ((!xmlStrcmp(cur->name, (const xmlChar*)"node"))) + load_node(path, doc, cur); + + cur = cur->next; + } + + // Load subpatches + cur = xmlDocGetRootElement(doc)->xmlChildrenNode; + while (cur != NULL) { + if ((!xmlStrcmp(cur->name, (const xmlChar*)"subpatch"))) { + load_subpatch(path, doc, cur); + } + cur = cur->next; + } + + // Load connections + cur = xmlDocGetRootElement(doc)->xmlChildrenNode; + while (cur != NULL) { + if ((!xmlStrcmp(cur->name, (const xmlChar*)"connection"))) { + load_connection(path, doc, cur); + } + cur = cur->next; + } + + + // Load presets (control values) + cerr << "FIXME: load preset\n"; + /*cur = xmlDocGetRootElement(doc)->xmlChildrenNode; + while (cur != NULL) { + if ((!xmlStrcmp(cur->name, (const xmlChar*)"preset"))) { + load_preset(pm, doc, cur); + assert(preset_model != NULL); + if (preset_model->name() == "default") + _engine->set_preset(pm->path(), preset_model); + } + cur = cur->next; + } + */ + + xmlFreeDoc(doc); + xmlCleanupParser(); + + // Done above.. late enough? + //_engine->set_metadata_map(path, initial_data); + + if (!existing) + _engine->enable_patch(path); + + _load_path_translations.clear(); + + return path; +} + + +/** Build a NodeModel given a pointer to a Node in a patch file. + */ +bool +DeprecatedSerializer::load_node(const Path& parent, xmlDocPtr doc, const xmlNodePtr node) +{ + xmlChar* key; + xmlNodePtr cur = node->xmlChildrenNode; + + string path = ""; + bool polyphonic = false; + + string plugin_uri; + + string plugin_type; // deprecated + string library_name; // deprecated + string plugin_label; // deprecated + + MetadataMap initial_data; + + while (cur != NULL) { + key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); + + if ((!xmlStrcmp(cur->name, (const xmlChar*)"name"))) { + path = parent.base() + Path::nameify((char*)key); + } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"polyphonic"))) { + polyphonic = !strcmp((char*)key, "true"); + } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"type"))) { + plugin_type = (const char*)key; + } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"library-name"))) { + library_name = (char*)key; + } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"plugin-label"))) { + plugin_label = (char*)key; + } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"plugin-uri"))) { + plugin_uri = (char*)key; + } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"port"))) { + cerr << "FIXME: load port\n"; +#if 0 + xmlNodePtr child = cur->xmlChildrenNode; + + string port_name; + 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"))) { + port_name = Path::nameify((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; + } + + assert(path.length() > 0); + assert(Path::is_valid(path)); + + // FIXME: /nasty/ assumptions + CountedPtr pm(new PortModel(Path(path).base() + port_name, + PortModel::CONTROL, PortModel::INPUT, PortModel::NONE, + 0.0, user_min, user_max)); + //pm->set_parent(nm); + nm->add_port(pm); +#endif + + // 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"))) { + cerr << "FIXME: load dssi program\n"; +#if 0 + 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", Atom(bank.append("/").append(program).c_str())); +#endif + + } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"dssi-configure"))) { + cerr << "FIXME: load dssi configure\n"; +#if 0 + 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), Atom(dssi_value.c_str())); +#endif + } else { // Don't know what this tag is, add it as metadata + if (key) { + + // Hack to make module-x and module-y set as floats + char* endptr = NULL; + float fval = strtof((const char*)key, &endptr); + if (endptr != (char*)key && *endptr == '\0') + initial_data[(const char*)cur->name] = Atom(fval); + else + initial_data[(const char*)cur->name] = Atom((const char*)key); + } + } + xmlFree(key); + key = NULL; + + cur = cur->next; + } + + if (path == "") { + cerr << "[DeprecatedSerializer] Malformed patch file (node tag has empty children)" << endl; + cerr << "[DeprecatedSerializer] Node ignored." << endl; + return false; + } + + // Compatibility hacks for old patches that represent patch ports as nodes + if (plugin_uri == "") { + cerr << "WARNING: Loading deprecated Node. Resave! " << path << endl; + bool is_port = false; + + if (plugin_type == "Internal") { + if (plugin_label == "audio_input") { + _engine->create_port(path, "AUDIO", false); + is_port = true; + } else if (plugin_label == "audio_output") { + _engine->create_port(path, "AUDIO", true); + is_port = true; + } else if (plugin_label == "control_input") { + _engine->create_port(path, "CONTROL", false); + is_port = true; + } else if (plugin_label == "control_output" ) { + _engine->create_port(path, "CONTROL", true); + is_port = true; + } else if (plugin_label == "midi_input") { + _engine->create_port(path, "MIDI", false); + is_port = true; + } else if (plugin_label == "midi_output" ) { + _engine->create_port(path, "MIDI", true); + is_port = true; + } + } + + if (is_port) { + const string old_path = path; + const string new_path = Path::pathify(old_path); + + // Set up translations (for connections etc) to alias both the old + // module path and the old module/port path to the new port path + _load_path_translations[old_path] = new_path; + _load_path_translations[old_path + "/in"] = new_path; + _load_path_translations[old_path + "/out"] = new_path; + + path = new_path; + + _engine->set_metadata_map(path, initial_data); + + return CountedPtr(); + + } else { + if (plugin_label == "note_in") { + plugin_uri = "ingen:note_node"; + } else if (plugin_label == "control_input") { + plugin_uri = "ingen:control_node"; + } else if (plugin_label == "transport") { + plugin_uri = "ingen:transport_node"; + } else if (plugin_label == "trigger_in") { + plugin_uri = "ingen:trigger_node"; + } else { + cerr << "WARNING: Unknown deprecated node (label " << plugin_label + << ")." << endl; + } + + if (plugin_uri != "") + _engine->create_node(path, plugin_uri, polyphonic); + else + _engine->create_node(path, plugin_type, library_name, plugin_label, polyphonic); + + _engine->set_metadata_map(path, initial_data); + + return true; + } + + // Not deprecated + } else { + _engine->create_node(path, plugin_uri, polyphonic); + _engine->set_metadata_map(path, initial_data); + return true; + } + + // (shouldn't get here) +} + + +bool +DeprecatedSerializer::load_subpatch(const Path& parent, xmlDocPtr doc, const xmlNodePtr subpatch) +{ + xmlChar *key; + xmlNodePtr cur = subpatch->xmlChildrenNode; + + string name = ""; + string filename = ""; + size_t poly = 0; + + MetadataMap initial_data; + + while (cur != NULL) { + key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); + + if ((!xmlStrcmp(cur->name, (const xmlChar*)"name"))) { + name = (const char*)key; + } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"polyphony"))) { + poly = atoi((const char*)key); + } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"filename"))) { + filename = (const char*)key; + } else { // Don't know what this tag is, add it as metadata + if (key != NULL && strlen((const char*)key) > 0) + initial_data[(const char*)cur->name] = Atom((const char*)key); + } + xmlFree(key); + key = NULL; + + cur = cur->next; + } + + // load_patch sets the passed metadata last, so values stored in the parent + // will override values stored in the child patch file + string path = load_patch(filename, parent, name, poly, initial_data, false); + + return false; +} + + +/** Build a ConnectionModel given a pointer to a connection in a patch file. + */ +bool +DeprecatedSerializer::load_connection(const Path& parent, xmlDocPtr doc, const xmlNodePtr node) +{ + 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 << "ERROR: Malformed patch file (connection tag has empty children)" << endl; + cerr << "ERROR: Connection ignored." << endl; + return false; + } + + // Compatibility fixes for old (fundamentally broken) patches + source_node = Path::nameify(source_node); + source_port = Path::nameify(source_port); + dest_node = Path::nameify(dest_node); + dest_port = Path::nameify(dest_port); + + _engine->connect( + translate_load_path(parent.base() + source_node +"/"+ source_port), + translate_load_path(parent.base() + dest_node +"/"+ dest_port)); + + return true; +} + + +/** Build a PresetModel given a pointer to a preset in a patch file. + */ +bool +DeprecatedSerializer::load_preset(const Path& parent, xmlDocPtr doc, const xmlNodePtr node) +{ + cerr << "FIXME: load preset\n"; +#if 0 + xmlNodePtr cur = node->xmlChildrenNode; + xmlChar* key; + + PresetModel* pm = new PresetModel(patch->path().base()); + + 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; + } + + // Compatibility fixes for old patch files + node_name = Path::nameify(node_name); + port_name = Path::nameify(port_name); + + 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; +#endif + return false; +} + +} // namespace Client +} // namespace Ingen diff --git a/src/libs/client/DeprecatedSerializer.h b/src/libs/client/DeprecatedSerializer.h new file mode 100644 index 00000000..645c03f7 --- /dev/null +++ b/src/libs/client/DeprecatedSerializer.h @@ -0,0 +1,85 @@ +/* This file is part of Ingen. Copyright (C) 2006 Dave Robillard. + * + * Ingen is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef PATCHLIBRARIAN_H +#define PATCHLIBRARIAN_H + +#include +#include +#include +#include +#include +#include "util/CountedPtr.h" +#include "util/Path.h" +#include "ObjectModel.h" + +using std::string; + +namespace Ingen { +namespace Client { + +class PatchModel; +class NodeModel; +class ConnectionModel; +class PresetModel; +class ModelEngineInterface; + + +/** Loads deprecated (XML) patch files (from the Om days). + * + * \ingroup IngenClient + */ +class DeprecatedSerializer +{ +public: + DeprecatedSerializer(CountedPtr engine) + : _patch_search_path("."), _engine(engine) + { + assert(_engine); + } + + void path(const string& path) { _patch_search_path = path; } + const string& path() { return _patch_search_path; } + + string find_file(const string& filename, const string& additional_path = ""); + + string load_patch(const string& filename, + const string& parent_path, + const string& name, + size_t poly, + MetadataMap initial_data, + bool existing = false); + +private: + string translate_load_path(const string& path); + + string _patch_search_path; + CountedPtr _engine; + + /// Translations of paths from the loading file to actual paths (for deprecated patches) + std::map _load_path_translations; + + bool load_node(const Path& parent, xmlDocPtr doc, const xmlNodePtr cur); + bool load_connection(const Path& parent, xmlDocPtr doc, const xmlNodePtr cur); + bool load_preset(const Path& parent, xmlDocPtr doc, const xmlNodePtr cur); + bool load_subpatch(const Path& parent, xmlDocPtr doc, const xmlNodePtr cur); +}; + + +} // namespace Client +} // namespace Ingen + +#endif // PATCHLIBRARIAN_H diff --git a/src/libs/client/Makefile.am b/src/libs/client/Makefile.am index 42f1fddc..47e675c2 100644 --- a/src/libs/client/Makefile.am +++ b/src/libs/client/Makefile.am @@ -1,11 +1,12 @@ AM_CXXFLAGS = -I$(top_srcdir)/src/common if BUILD_CLIENT_LIB -noinst_LTLIBRARIES = libomclient.la +noinst_LTLIBRARIES = libingenclient.la -libomclient_la_CXXFLAGS = -I$(top_srcdir)/src/common -DPKGDATADIR=\"$(pkgdatadir)\" $(LXML2_CFLAGS) $(LSIGCPP_CFLAGS) +libingenclient_la_CXXFLAGS = -I$(top_srcdir)/src/common -DPKGDATADIR=\"$(pkgdatadir)\" @LXML2_CFLAGS@ @REDLAND_CFLAGS@ @LSIGCPP_CFLAGS@ +libingenclient_la_LIBADD = @LXML2_LIBS@ @LOSC_LIBS@ @REDLAND_LIBS@ @LSIGCPP_LIBS@ -libomclient_la_SOURCES = \ +libingenclient_la_SOURCES = \ ClientInterface.h \ OSCEngineSender.h \ OSCEngineSender.cpp \ @@ -29,8 +30,10 @@ libomclient_la_SOURCES = \ PatchModel.h \ PatchModel.cpp \ PluginModel.h \ - PatchLibrarian.h \ - PatchLibrarian.cpp \ + Serializer.h \ + Serializer.cpp \ + DeprecatedSerializer.h \ + DeprecatedSerializer.cpp \ ConnectionModel.h \ ConnectionModel.cpp \ Store.h \ diff --git a/src/libs/client/ObjectModel.h b/src/libs/client/ObjectModel.h index 5ef03b85..bc780cba 100644 --- a/src/libs/client/ObjectModel.h +++ b/src/libs/client/ObjectModel.h @@ -64,7 +64,6 @@ public: protected: friend class Store; - friend class PatchLibrarian; // FIXME: remove ObjectModel(const Path& path); @@ -81,8 +80,8 @@ protected: { _metadata[key] = value; metadata_update_sig.emit(key, value); } - Path _path; - CountedPtr _parent; + Path _path; + CountedPtr _parent; MetadataMap _metadata; diff --git a/src/libs/client/PatchLibrarian.cpp b/src/libs/client/PatchLibrarian.cpp deleted file mode 100644 index 00687993..00000000 --- a/src/libs/client/PatchLibrarian.cpp +++ /dev/null @@ -1,918 +0,0 @@ -/* This file is part of Ingen. Copyright (C) 2006 Dave Robillard. - * - * Ingen is free software; you can redistribute it and/or modify it under the - * terms of the GNU General Public License as published by the Free Software - * Foundation; either version 2 of the License, or (at your option) any later - * version. - * - * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include "PatchLibrarian.h" -#include -#include -#include -#include -#include "PatchModel.h" -#include "NodeModel.h" -#include "ConnectionModel.h" -#include "PortModel.h" -#include "PresetModel.h" -#include "ModelEngineInterface.h" -#include "PluginModel.h" -#include "util/Path.h" -#include -#include -#include -#include // for pair, make_pair -#include -#include -#include -#include // for atof -#include - -using std::string; using std::vector; using std::pair; -using std::cerr; using std::cout; using std::endl; - -namespace Ingen { -namespace Client { - - -/** 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 + ":" + _patch_search_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 ""; -} - - -string -PatchLibrarian::translate_load_path(const string& path) -{ - std::map::iterator t = _load_path_translations.find(path); - - if (t != _load_path_translations.end()) { - return (*t).second; - } else { - assert(Path::is_valid(path)); - return path; - } -} - - -/** 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 (Ingen) path - */ -#if 0 -void -PatchLibrarian::save_patch(CountedPtr patch_model, const string& filename, bool recursive) -{ - assert(filename != ""); - assert(patch_model->path() != ""); - - cout << "Saving patch " << patch_model->path() << " to " << filename << endl; - - if (patch_model->filename() != filename) - cerr << "Warning: Saving patch to file other than filename stored in model." << endl; - - string dir = filename.substr(0, filename.find_last_of("/")); - - NodeModel* nm = NULL; - - 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->path().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 (MetadataMap::const_iterator i = patch_model->metadata().begin(); - i != patch_model->metadata().end(); ++i) { - cerr << "FIXME: metadata save" << endl; - // 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.get(); - - if (nm->plugin()->type() == PluginModel::Patch) { // Subpatch - CountedPtr spm = PtrCast(i->second); - assert(spm); - - xml_node = xmlNewChild(xml_root_node, NULL, (xmlChar*)"subpatch", NULL); - - xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"name", (xmlChar*)spm->path().name().c_str()); - - string ref_filename; - // No path - if (spm->filename() == "") { - ref_filename = spm->path().name() + ".om"; - cerr << "FIXME: subpatch filename" << endl; - //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); - cerr << "FIXME: subpatch filename (2)" << endl; - //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 (MetadataMap::const_iterator i = nm->metadata().begin(); - i != nm->metadata().end(); ++i) { - cerr << "FIXME: save metadata\n"; - // 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->path().name().c_str()); - - if (!nm->plugin()) 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 (MetadataMap::const_iterator i = nm->metadata().begin(); i != nm->metadata().end(); ++i) { - cerr << "FIXME: Save metadata\n"; - /* - // 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()); - } - */ - } - - // Write port metadata, if necessary - for (PortModelList::const_iterator i = nm->ports().begin(); i != nm->ports().end(); ++i) { - cerr << "FIXME: save metadata\n"; - /* - const PortModel* const pm = i->get(); - 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 >& cl = patch_model->connections(); - const ConnectionModel* c = NULL; - - for (list >::const_iterator i = cl.begin(); i != cl.end(); ++i) { - c = (*i).get(); - 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.get(); - for (PortModelList::const_iterator p = nm->ports().begin(); p != nm->ports().end(); ++p) { - pm = (*p).get(); - 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->path().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).get(); - 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(); -} -#endif - - -/** 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. - * - * @param wait If true 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). - * - * @param existing If 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. - * - * @param parent_path Patch to load this patch as a child of (empty string to load - * to the root patch) - * - * @param name Name of this patch (loaded/generated if the empty string) - * - * @param initial_data will be set last, so values passed there will override - * any values loaded from the patch file. - * - * Returns the path of the newly created patch. - */ -string -PatchLibrarian::load_patch(const string& filename, - const string& parent_path, - const string& name, - size_t poly, - MetadataMap initial_data, - bool existing) -{ - cerr << "[PatchLibrarian] Loading patch " << filename << "" << endl; - - Path path = "/"; // path of the new patch - - const bool load_name = (name == ""); - const bool load_poly = (poly == 0); - - if (initial_data.find("filename") == initial_data.end()) - initial_data["filename"] = Atom(filename.c_str()); // FIXME: URL? - - xmlDocPtr doc = xmlParseFile(filename.c_str()); - - if (!doc) { - cerr << "Unable to parse patch file." << endl; - return ""; - } - - xmlNodePtr cur = xmlDocGetRootElement(doc); - - if (!cur) { - cerr << "Empty document." << endl; - xmlFreeDoc(doc); - return ""; - } - - if (xmlStrcmp(cur->name, (const xmlChar*) "patch")) { - cerr << "File is not an Ingen patch file (root node != )" << endl; - xmlFreeDoc(doc); - return ""; - } - - xmlChar* key = NULL; - cur = cur->xmlChildrenNode; - - // 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 (parent_path != "") - path = Path(parent_path).base() + Path::nameify((char*)key); - } - } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"polyphony"))) { - if (load_poly) { - poly = atoi((char*)key); - } - } 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) - if (initial_data.find((const char*)cur->name) == initial_data.end()) - initial_data[(const char*)cur->name] = (const char*)key; - } - - xmlFree(key); - key = NULL; // Avoid a (possible?) double free - - cur = cur->next; - } - - if (poly == 0) - poly = 1; - - // Create it, if we're not merging - if (!existing) - _engine->create_patch_with_data(path, poly, initial_data); - - // Load nodes - cur = xmlDocGetRootElement(doc)->xmlChildrenNode; - while (cur != NULL) { - if ((!xmlStrcmp(cur->name, (const xmlChar*)"node"))) - load_node(path, doc, cur); - - cur = cur->next; - } - - // Load subpatches - cur = xmlDocGetRootElement(doc)->xmlChildrenNode; - while (cur != NULL) { - if ((!xmlStrcmp(cur->name, (const xmlChar*)"subpatch"))) { - load_subpatch(path, doc, cur); - } - cur = cur->next; - } - - // Load connections - cur = xmlDocGetRootElement(doc)->xmlChildrenNode; - while (cur != NULL) { - if ((!xmlStrcmp(cur->name, (const xmlChar*)"connection"))) { - load_connection(path, doc, cur); - } - cur = cur->next; - } - - - // Load presets (control values) - cerr << "FIXME: load preset\n"; - /*cur = xmlDocGetRootElement(doc)->xmlChildrenNode; - while (cur != NULL) { - if ((!xmlStrcmp(cur->name, (const xmlChar*)"preset"))) { - load_preset(pm, doc, cur); - assert(preset_model != NULL); - if (preset_model->name() == "default") - _engine->set_preset(pm->path(), preset_model); - } - cur = cur->next; - } - */ - - xmlFreeDoc(doc); - xmlCleanupParser(); - - // Done above.. late enough? - //_engine->set_metadata_map(path, initial_data); - - if (!existing) - _engine->enable_patch(path); - - _load_path_translations.clear(); - - return path; -} - - -/** Build a NodeModel given a pointer to a Node in a patch file. - */ -bool -PatchLibrarian::load_node(const Path& parent, xmlDocPtr doc, const xmlNodePtr node) -{ - xmlChar* key; - xmlNodePtr cur = node->xmlChildrenNode; - - string path = ""; - bool polyphonic = false; - - string plugin_uri; - - string plugin_type; // deprecated - string library_name; // deprecated - string plugin_label; // deprecated - - MetadataMap initial_data; - - while (cur != NULL) { - key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); - - if ((!xmlStrcmp(cur->name, (const xmlChar*)"name"))) { - path = parent.base() + Path::nameify((char*)key); - } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"polyphonic"))) { - polyphonic = !strcmp((char*)key, "true"); - } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"type"))) { - plugin_type = (const char*)key; - } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"library-name"))) { - library_name = (char*)key; - } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"plugin-label"))) { - plugin_label = (char*)key; - } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"plugin-uri"))) { - plugin_uri = (char*)key; - } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"port"))) { - cerr << "FIXME: load port\n"; -#if 0 - xmlNodePtr child = cur->xmlChildrenNode; - - string port_name; - 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"))) { - port_name = Path::nameify((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; - } - - assert(path.length() > 0); - assert(Path::is_valid(path)); - - // FIXME: /nasty/ assumptions - CountedPtr pm(new PortModel(Path(path).base() + port_name, - PortModel::CONTROL, PortModel::INPUT, PortModel::NONE, - 0.0, user_min, user_max)); - //pm->set_parent(nm); - nm->add_port(pm); -#endif - - // 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"))) { - cerr << "FIXME: load dssi program\n"; -#if 0 - 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", Atom(bank.append("/").append(program).c_str())); -#endif - - } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"dssi-configure"))) { - cerr << "FIXME: load dssi configure\n"; -#if 0 - 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), Atom(dssi_value.c_str())); -#endif - } else { // Don't know what this tag is, add it as metadata - if (key) { - - // Hack to make module-x and module-y set as floats - char* endptr = NULL; - float fval = strtof((const char*)key, &endptr); - if (endptr != (char*)key && *endptr == '\0') - initial_data[(const char*)cur->name] = Atom(fval); - else - initial_data[(const char*)cur->name] = Atom((const char*)key); - } - } - xmlFree(key); - key = NULL; - - cur = cur->next; - } - - if (path == "") { - cerr << "[PatchLibrarian] Malformed patch file (node tag has empty children)" << endl; - cerr << "[PatchLibrarian] Node ignored." << endl; - return false; - } - - // Compatibility hacks for old patches that represent patch ports as nodes - if (plugin_uri == "") { - cerr << "WARNING: Loading deprecated Node. Resave! " << path << endl; - bool is_port = false; - - if (plugin_type == "Internal") { - if (plugin_label == "audio_input") { - _engine->create_port(path, "AUDIO", false); - is_port = true; - } else if (plugin_label == "audio_output") { - _engine->create_port(path, "AUDIO", true); - is_port = true; - } else if (plugin_label == "control_input") { - _engine->create_port(path, "CONTROL", false); - is_port = true; - } else if (plugin_label == "control_output" ) { - _engine->create_port(path, "CONTROL", true); - is_port = true; - } else if (plugin_label == "midi_input") { - _engine->create_port(path, "MIDI", false); - is_port = true; - } else if (plugin_label == "midi_output" ) { - _engine->create_port(path, "MIDI", true); - is_port = true; - } - } - - if (is_port) { - const string old_path = path; - const string new_path = Path::pathify(old_path); - - // Set up translations (for connections etc) to alias both the old - // module path and the old module/port path to the new port path - _load_path_translations[old_path] = new_path; - _load_path_translations[old_path + "/in"] = new_path; - _load_path_translations[old_path + "/out"] = new_path; - - path = new_path; - - _engine->set_metadata_map(path, initial_data); - - return CountedPtr(); - - } else { - if (plugin_label == "note_in") { - plugin_uri = "ingen:note_node"; - } else if (plugin_label == "control_input") { - plugin_uri = "ingen:control_node"; - } else if (plugin_label == "transport") { - plugin_uri = "ingen:transport_node"; - } else if (plugin_label == "trigger_in") { - plugin_uri = "ingen:trigger_node"; - } else { - cerr << "WARNING: Unknown deprecated node (label " << plugin_label - << ")." << endl; - } - - if (plugin_uri != "") - _engine->create_node(path, plugin_uri, polyphonic); - else - _engine->create_node(path, plugin_type, library_name, plugin_label, polyphonic); - - _engine->set_metadata_map(path, initial_data); - - return true; - } - - // Not deprecated - } else { - _engine->create_node(path, plugin_uri, polyphonic); - _engine->set_metadata_map(path, initial_data); - return true; - } - - // (shouldn't get here) -} - - -bool -PatchLibrarian::load_subpatch(const Path& parent, xmlDocPtr doc, const xmlNodePtr subpatch) -{ - xmlChar *key; - xmlNodePtr cur = subpatch->xmlChildrenNode; - - string name = ""; - string filename = ""; - size_t poly = 0; - - MetadataMap initial_data; - - while (cur != NULL) { - key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); - - if ((!xmlStrcmp(cur->name, (const xmlChar*)"name"))) { - name = (const char*)key; - } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"polyphony"))) { - poly = atoi((const char*)key); - } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"filename"))) { - filename = (const char*)key; - } else { // Don't know what this tag is, add it as metadata - if (key != NULL && strlen((const char*)key) > 0) - initial_data[(const char*)cur->name] = Atom((const char*)key); - } - xmlFree(key); - key = NULL; - - cur = cur->next; - } - - // load_patch sets the passed metadata last, so values stored in the parent - // will override values stored in the child patch file - string path = load_patch(filename, parent, name, poly, initial_data, false); - - return false; -} - - -/** Build a ConnectionModel given a pointer to a connection in a patch file. - */ -bool -PatchLibrarian::load_connection(const Path& parent, xmlDocPtr doc, const xmlNodePtr node) -{ - 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 << "ERROR: Malformed patch file (connection tag has empty children)" << endl; - cerr << "ERROR: Connection ignored." << endl; - return false; - } - - // Compatibility fixes for old (fundamentally broken) patches - source_node = Path::nameify(source_node); - source_port = Path::nameify(source_port); - dest_node = Path::nameify(dest_node); - dest_port = Path::nameify(dest_port); - - _engine->connect( - translate_load_path(parent.base() + source_node +"/"+ source_port), - translate_load_path(parent.base() + dest_node +"/"+ dest_port)); - - return true; -} - - -/** Build a PresetModel given a pointer to a preset in a patch file. - */ -bool -PatchLibrarian::load_preset(const Path& parent, xmlDocPtr doc, const xmlNodePtr node) -{ - cerr << "FIXME: load preset\n"; -#if 0 - xmlNodePtr cur = node->xmlChildrenNode; - xmlChar* key; - - PresetModel* pm = new PresetModel(patch->path().base()); - - 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; - } - - // Compatibility fixes for old patch files - node_name = Path::nameify(node_name); - port_name = Path::nameify(port_name); - - 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; -#endif - return false; -} - -} // namespace Client -} // namespace Ingen diff --git a/src/libs/client/PatchLibrarian.h b/src/libs/client/PatchLibrarian.h deleted file mode 100644 index 893d5cdc..00000000 --- a/src/libs/client/PatchLibrarian.h +++ /dev/null @@ -1,87 +0,0 @@ -/* This file is part of Ingen. Copyright (C) 2006 Dave Robillard. - * - * Ingen is free software; you can redistribute it and/or modify it under the - * terms of the GNU General Public License as published by the Free Software - * Foundation; either version 2 of the License, or (at your option) any later - * version. - * - * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#ifndef PATCHLIBRARIAN_H -#define PATCHLIBRARIAN_H - -#include -#include -#include -#include -#include -#include "util/CountedPtr.h" -#include "util/Path.h" -#include "ObjectModel.h" - -using std::string; - -namespace Ingen { -namespace Client { - -class PatchModel; -class NodeModel; -class ConnectionModel; -class PresetModel; -class ModelEngineInterface; - - -/** Handles all patch saving and loading. - * - * \ingroup IngenClient - */ -class PatchLibrarian -{ -public: - PatchLibrarian(CountedPtr engine) - : _patch_search_path("."), _engine(engine) - { - assert(_engine); - } - - void path(const string& path) { _patch_search_path = path; } - const string& path() { return _patch_search_path; } - - string find_file(const string& filename, const string& additional_path = ""); - - //void save_patch(CountedPtr patch_model, const string& filename, bool recursive); - - string load_patch(const string& filename, - const string& parent_path, - const string& name, - size_t poly, - MetadataMap initial_data, - bool existing = false); - -private: - string translate_load_path(const string& path); - - string _patch_search_path; - CountedPtr _engine; - - /// Translations of paths from the loading file to actual paths (for deprecated patches) - std::map _load_path_translations; - - bool load_node(const Path& parent, xmlDocPtr doc, const xmlNodePtr cur); - bool load_connection(const Path& parent, xmlDocPtr doc, const xmlNodePtr cur); - bool load_preset(const Path& parent, xmlDocPtr doc, const xmlNodePtr cur); - bool load_subpatch(const Path& parent, xmlDocPtr doc, const xmlNodePtr cur); -}; - - -} // namespace Client -} // namespace Ingen - -#endif // PATCHLIBRARIAN_H diff --git a/src/libs/client/PatchModel.h b/src/libs/client/PatchModel.h index b5f9d428..a7679b15 100644 --- a/src/libs/client/PatchModel.h +++ b/src/libs/client/PatchModel.h @@ -41,8 +41,8 @@ class Store; class PatchModel : public NodeModel { public: - const NodeModelMap& nodes() const { return m_nodes; } - const list >& connections() const { return m_connections; } + const NodeModelMap& nodes() const { return m_nodes; } + const ConnectionList& connections() const { return m_connections; } CountedPtr get_connection(const string& src_port_path, const string& dst_port_path) const; CountedPtr get_node(const string& node_name) const; @@ -91,11 +91,11 @@ private: PatchModel(const PatchModel& copy); PatchModel& operator=(const PatchModel& copy); - NodeModelMap m_nodes; - list > m_connections; - string m_filename; - bool m_enabled; - size_t m_poly; + NodeModelMap m_nodes; + ConnectionList m_connections; + string m_filename; + bool m_enabled; + size_t m_poly; }; typedef map > PatchModelMap; diff --git a/src/libs/client/Serializer.cpp b/src/libs/client/Serializer.cpp new file mode 100644 index 00000000..e82389d4 --- /dev/null +++ b/src/libs/client/Serializer.cpp @@ -0,0 +1,525 @@ +/* This file is part of Ingen. Copyright (C) 2006 Dave Robillard. + * + * Ingen is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include // pair, make_pair +#include +#include +#include // atof +#include +#include +#include "Serializer.h" +#include "PatchModel.h" +#include "NodeModel.h" +#include "ConnectionModel.h" +#include "PortModel.h" +#include "PresetModel.h" +#include "ModelEngineInterface.h" +#include "PluginModel.h" +#include "util/Path.h" +#include "util/Atom.h" +#include "util/RedlandAtom.h" + +#define U(x) ((const unsigned char*)(x)) + +using std::string; using std::vector; using std::pair; +using std::cerr; using std::cout; using std::endl; + +namespace Ingen { +namespace Client { + + +Serializer::Serializer(CountedPtr engine) + : _world(librdf_new_world()) + , _serializer(0) + , _patch_search_path(".") + , _engine(engine) +{ + librdf_world_open(_world); + + //_prefixes["xsd"] = "http://www.w3.org/2001/XMLSchema#"; + _prefixes["rdf"] = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; + _prefixes["ingen"] = "http://codeson.net/ns/ingen#"; + _prefixes["ingenuity"] = "http://codeson.net/ns/ingenuity#"; +} + + +Serializer::~Serializer() +{ + + librdf_free_world(_world); +} + + +void +Serializer::create() +{ + _serializer = librdf_new_serializer(_world, "rdfxml-abbrev", NULL, librdf_new_uri(_world, U(NS_INGEN()))); + + setup_prefixes(); +} + +void +Serializer::destroy() +{ + librdf_free_serializer(_serializer); + _serializer = NULL; +} + + +void +Serializer::setup_prefixes() +{ + for (map::const_iterator i = _prefixes.begin(); i != _prefixes.end(); ++i) { + librdf_serializer_set_namespace(_serializer, + librdf_new_uri(_world, U(i->second.c_str())), i->first.c_str()); + } +} + + +/** Expands the prefix of URI, if the prefix is registered. + * + * If uri is not a valid URI, the empty string is returned (so invalid URIs won't be serialized). + */ +string +Serializer::expand_uri(const string& uri) +{ + // FIXME: slow, stupid + for (map::const_iterator i = _prefixes.begin(); i != _prefixes.end(); ++i) + if (uri.substr(0, i->first.length()+1) == i->first + ":") + return i->second + uri.substr(i->first.length()+1); + + /*librdf_uri* redland_uri = librdf_new_uri(_world, U(uri.c_str())); + string ret = (redland_uri) ? uri : ""; + librdf_free_uri(redland_uri); + return ret;*/ + + // FIXME: find a correct way to validate a URI + if (uri.find(":") == string::npos && uri.find("/") == string::npos) + return ""; + else + return uri; +} + + +/** 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 +Serializer::find_file(const string& filename, const string& additional_path) +{ + string search_path = additional_path + ":" + _patch_search_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 << "[Serializer] 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. + * + * FIXME: make this take a URI. network loading for free. + * + * This will break if: + * - The filename does not have an extension (ie contain a ".") + * - The patch_model has no (Ingen) path + */ +void +Serializer::save_patch(CountedPtr patch_model, const string& filename, bool recursive) +{ + librdf_storage* storage = librdf_new_storage(_world, "hashes", NULL, "hash-type='memory'"); + + librdf_model* model = librdf_new_model(_world, storage, NULL); + assert(model); + + const string uri = string("file://") + filename; + add_patch_to_rdf(model, patch_model, uri); + + create(); + + //librdf_serializer_serialize_model_to_file_handle(serializer, stdout, NULL, model); + librdf_serializer_serialize_model_to_file(_serializer, + filename.c_str(), NULL, model); + + destroy(); + + librdf_storage_close(storage); + librdf_free_storage(storage); + librdf_free_model(model); +} + + +void +Serializer::add_resource_to_rdf(librdf_model* rdf, + const string& subject_uri, const string& predicate_uri, const string& object_uri) +{ + + librdf_node* subject = librdf_new_node_from_uri_string(_world, U(subject_uri.c_str())); + add_resource_to_rdf(rdf, subject, predicate_uri, object_uri); +} + +void +Serializer::add_resource_to_rdf(librdf_model* rdf, + librdf_node* subject, const string& predicate_uri, const string& object_uri) +{ + + librdf_node* predicate = librdf_new_node_from_uri_string(_world, U(predicate_uri.c_str())); + librdf_node* object = librdf_new_node_from_uri_string(_world, U(object_uri.c_str())); + + librdf_model_add(rdf, subject, predicate, object); +} + + +void +Serializer::add_atom_to_rdf(librdf_model* rdf, + const string& subject_uri, const string& predicate_uri, const Atom& atom) +{ + librdf_node* subject = librdf_new_node_from_uri_string(_world, U(subject_uri.c_str())); + librdf_node* predicate = librdf_new_node_from_uri_string(_world, U(predicate_uri.c_str())); + librdf_node* object = RedlandAtom::atom_to_node(_world, atom); + + librdf_model_add(rdf, subject, predicate, object); +} + + +void +Serializer::add_patch_to_rdf(librdf_model* rdf, + CountedPtr patch, const string& uri_unused) +{ + const string uri = "#"; + + add_resource_to_rdf(rdf, + uri.c_str(), + NS_RDF("type"), + NS_INGEN("Patch")); + + if (patch->path().name().length() > 0) { + add_atom_to_rdf(rdf, + uri.c_str(), + NS_INGEN("name"), + Atom(patch->path().name().c_str())); + } + + add_atom_to_rdf(rdf, + uri.c_str(), + NS_INGEN("polyphony"), + Atom((int)patch->poly())); + + for (NodeModelMap::const_iterator n = patch->nodes().begin(); n != patch->nodes().end(); ++n) { + add_node_to_rdf(rdf, n->second, "#"); + add_resource_to_rdf(rdf, "#", NS_INGEN("node"), uri + n->second->path().name()); + } + + for (PortModelList::const_iterator p = patch->ports().begin(); p != patch->ports().end(); ++p) { + add_port_to_rdf(rdf, *p, uri); + add_resource_to_rdf(rdf, "#", NS_INGEN("port"), uri + (*p)->path().name()); + } + + for (ConnectionList::const_iterator c = patch->connections().begin(); c != patch->connections().end(); ++c) { + add_connection_to_rdf(rdf, *c, uri); + } + + //_engine->set_metadata(patch->path(), "uri", uri); +} + + +void +Serializer::add_node_to_rdf(librdf_model* rdf, + CountedPtr node, const string ns_prefix) +{ + const string node_uri = ns_prefix + node->path().name(); + + add_resource_to_rdf(rdf, + node_uri.c_str(), + NS_RDF("type"), + NS_INGEN("Node")); + + /*add_atom_to_rdf(rdf, + node_uri_ref.c_str(), + NS_INGEN("name"), + Atom(node->path().name()));*/ + + for (PortModelList::const_iterator p = node->ports().begin(); p != node->ports().end(); ++p) { + add_port_to_rdf(rdf, *p, node_uri + "/"); + add_resource_to_rdf(rdf, node_uri, NS_INGEN("port"), node_uri + "/" + (*p)->path().name()); + } + + for (MetadataMap::const_iterator m = node->metadata().begin(); m != node->metadata().end(); ++m) { + if (expand_uri(m->first) != "") { + add_atom_to_rdf(rdf, + node_uri.c_str(), + expand_uri(m->first.c_str()).c_str(), + m->second); + } + } +} + + +void +Serializer::add_port_to_rdf(librdf_model* rdf, + CountedPtr port, const string ns_prefix) +{ + const string port_uri_ref = ns_prefix + port->path().name(); + + add_resource_to_rdf(rdf, + port_uri_ref.c_str(), + NS_RDF("type"), + NS_INGEN("Port")); + + for (MetadataMap::const_iterator m = port->metadata().begin(); m != port->metadata().end(); ++m) { + if (expand_uri(m->first) != "") { + add_atom_to_rdf(rdf, + port_uri_ref.c_str(), + expand_uri(m->first).c_str(), + m->second); + } + } +} + + +void +Serializer::add_connection_to_rdf(librdf_model* rdf, + CountedPtr connection, const string ns_prefix) +{ + librdf_node* c = librdf_new_node(_world); + + const string src_port_rel_path = connection->src_port_path().substr(connection->patch_path().length()); + const string dst_port_rel_path = connection->dst_port_path().substr(connection->patch_path().length()); + + librdf_statement* s = librdf_new_statement_from_nodes(_world, c, + librdf_new_node_from_uri_string(_world, U(NS_INGEN("source"))), + librdf_new_node_from_uri_string(_world, U((ns_prefix + src_port_rel_path).c_str()))); + librdf_model_add_statement(rdf, s); + + librdf_statement* d = librdf_new_statement_from_nodes(_world, c, + librdf_new_node_from_uri_string(_world, U(NS_INGEN("destination"))), + librdf_new_node_from_uri_string(_world, U((ns_prefix + dst_port_rel_path).c_str()))); + librdf_model_add_statement(rdf, d); + + add_resource_to_rdf(rdf, c, NS_RDF("type"), NS_INGEN("Connection")); +} + + +/** 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. + * + * @param wait If true 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). + * + * @param existing If 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. + * + * @param parent_path Patch to load this patch as a child of (empty string to load + * to the root patch) + * + * @param name Name of this patch (loaded/generated if the empty string) + * + * @param initial_data will be set last, so values passed there will override + * any values loaded from the patch file. + * + * Returns the path of the newly created patch. + */ +string +Serializer::load_patch(const string& filename, + const string& parent_path, + const string& name, + size_t poly, + MetadataMap initial_data, + bool existing) +{ +#if 0 + cerr << "[Serializer] Loading patch " << filename << "" << endl; + + Path path = "/"; // path of the new patch + + const bool load_name = (name == ""); + const bool load_poly = (poly == 0); + + if (initial_data.find("filename") == initial_data.end()) + initial_data["filename"] = Atom(filename.c_str()); // FIXME: URL? + + xmlDocPtr doc = xmlParseFile(filename.c_str()); + + if (!doc) { + cerr << "Unable to parse patch file." << endl; + return ""; + } + + xmlNodePtr cur = xmlDocGetRootElement(doc); + + if (!cur) { + cerr << "Empty document." << endl; + xmlFreeDoc(doc); + return ""; + } + + if (xmlStrcmp(cur->name, (const xmlChar*) "patch")) { + cerr << "File is not an Ingen patch file (root node != )" << endl; + xmlFreeDoc(doc); + return ""; + } + + xmlChar* key = NULL; + cur = cur->xmlChildrenNode; + + // 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 (parent_path != "") + path = Path(parent_path).base() + Path::nameify((char*)key); + } + } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"polyphony"))) { + if (load_poly) { + poly = atoi((char*)key); + } + } 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) + if (initial_data.find((const char*)cur->name) == initial_data.end()) + initial_data[(const char*)cur->name] = (const char*)key; + } + + xmlFree(key); + key = NULL; // Avoid a (possible?) double free + + cur = cur->next; + } + + if (poly == 0) + poly = 1; + + // Create it, if we're not merging + if (!existing) + _engine->create_patch_with_data(path, poly, initial_data); + + // Load nodes + cur = xmlDocGetRootElement(doc)->xmlChildrenNode; + while (cur != NULL) { + if ((!xmlStrcmp(cur->name, (const xmlChar*)"node"))) + load_node(path, doc, cur); + + cur = cur->next; + } + + // Load subpatches + cur = xmlDocGetRootElement(doc)->xmlChildrenNode; + while (cur != NULL) { + if ((!xmlStrcmp(cur->name, (const xmlChar*)"subpatch"))) { + load_subpatch(path, doc, cur); + } + cur = cur->next; + } + + // Load connections + cur = xmlDocGetRootElement(doc)->xmlChildrenNode; + while (cur != NULL) { + if ((!xmlStrcmp(cur->name, (const xmlChar*)"connection"))) { + load_connection(path, doc, cur); + } + cur = cur->next; + } + + + // Load presets (control values) + cerr << "FIXME: load preset\n"; + /*cur = xmlDocGetRootElement(doc)->xmlChildrenNode; + while (cur != NULL) { + if ((!xmlStrcmp(cur->name, (const xmlChar*)"preset"))) { + load_preset(pm, doc, cur); + assert(preset_model != NULL); + if (preset_model->name() == "default") + _engine->set_preset(pm->path(), preset_model); + } + cur = cur->next; + } + */ + + xmlFreeDoc(doc); + xmlCleanupParser(); + + // Done above.. late enough? + //_engine->set_metadata_map(path, initial_data); + + if (!existing) + _engine->enable_patch(path); + + _load_path_translations.clear(); + + return path; +#endif + return "/FIXME"; +} + +} // namespace Client +} // namespace Ingen diff --git a/src/libs/client/Serializer.h b/src/libs/client/Serializer.h new file mode 100644 index 00000000..b2dbf000 --- /dev/null +++ b/src/libs/client/Serializer.h @@ -0,0 +1,118 @@ +/* This file is part of Ingen. Copyright (C) 2006 Dave Robillard. + * + * Ingen is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef PATCHLIBRARIAN_H +#define PATCHLIBRARIAN_H + +#include +#include +#include +#include +#include +#include "util/CountedPtr.h" +#include "util/Path.h" +#include "util/Atom.h" +#include "ObjectModel.h" + +using std::string; + +namespace Ingen { +namespace Client { + +class PatchModel; +class NodeModel; +class PortModel; +class ConnectionModel; +class PresetModel; +class ModelEngineInterface; + + +/** Namespace prefix macros. */ +#define NS_RDF(x) "http://www.w3.org/1999/02/22-rdf-syntax-ns#" x +#define NS_INGEN(x) "http://codeson.net/ns/ingen#" x + + +/** Handles all patch saving and loading. + * + * \ingroup IngenClient + */ +class Serializer +{ +public: + Serializer(CountedPtr engine); + ~Serializer(); + + void path(const string& path) { _patch_search_path = path; } + const string& path() { return _patch_search_path; } + + string find_file(const string& filename, const string& additional_path = ""); + + void save_patch(CountedPtr patch_model, const string& filename, bool recursive); + + string load_patch(const string& filename, + const string& parent_path, + const string& name, + size_t poly, + MetadataMap initial_data, + bool existing = false); + +private: + + // Model -> RDF + + void add_patch_to_rdf(librdf_model* rdf, + CountedPtr patch, const string& uri); + + void add_node_to_rdf(librdf_model* rdf, + CountedPtr node, const string ns_prefix=""); + + void add_port_to_rdf(librdf_model* rdf, + CountedPtr port, const string ns_prefix=""); + + void add_connection_to_rdf(librdf_model* rdf, + CountedPtr connection, const string port_ns_prefix=""); + + + // Triple -> RDF + + void add_resource_to_rdf(librdf_model* model, + const string& subject_uri, const string& predicate_uri, const string& object_uri); + + void add_resource_to_rdf(librdf_model* model, + librdf_node* subject, const string& predicate_uri, const string& object_uri); + + void add_atom_to_rdf(librdf_model* model, + const string& subject_uri, const string& predicate_uri, const Atom& atom); + + + void create(); + void destroy(); + void setup_prefixes(); + string expand_uri(const string& uri); + + + librdf_world* _world; + librdf_serializer* _serializer; + string _patch_search_path; + map _prefixes; + CountedPtr _engine; +}; + + +} // namespace Client +} // namespace Ingen + +#endif // PATCHLIBRARIAN_H diff --git a/src/libs/engine/Makefile.am b/src/libs/engine/Makefile.am index 3747515b..95b684d5 100644 --- a/src/libs/engine/Makefile.am +++ b/src/libs/engine/Makefile.am @@ -3,6 +3,8 @@ DIST_SUBDIRS = events AM_CXXFLAGS = @JACK_CFLAGS@ @LOSC_CFLAGS@ @ALSA_CFLAGS@ @LASH_CFLAGS@ @SLV2_CFLAGS@ -I$(top_srcdir)/src/common -I$(top_srcdir)/src/libs/engine/events +libingen_la_LIBADD = @JACK_LIBS@ @LOSC_LIBS@ @ALSA_LIBS@ @LASH_LIBS@ @SLV2_LIBS@ + MAINTAINERCLEANFILES = Makefile.in noinst_LTLIBRARIES = libingen.la diff --git a/src/progs/demolition/Makefile.am b/src/progs/demolition/Makefile.am index b01b062b..fe2b20b6 100644 --- a/src/progs/demolition/Makefile.am +++ b/src/progs/demolition/Makefile.am @@ -1,11 +1,11 @@ EXTRA_DIST = README om_demolition_CXXFLAGS = -I$(top_srcdir)/src/libs/client -I$(top_srcdir)/src/common -DPKGDATADIR=\"$(pkgdatadir)\" $(LXML2_CFLAGS) $(LOSC_CFLAGS) $(LSIGCPP_CFLAGS) -om_demolition_LDADD = ../../libs/client/libomclient.la $(LOSC_LIBS) $(LXML2_LIBS) $(LSIGCPP_LIBS) +om_demolition_LDADD = ../../libs/client/libingenclient.la $(LOSC_LIBS) $(LXML2_LIBS) $(LSIGCPP_LIBS) bin_PROGRAMS = om_demolition -om_demolition_DEPENDENCIES = ../../libs/client/libomclient.la +om_demolition_DEPENDENCIES = ../../libs/client/libingenclient.la om_demolition_SOURCES = \ demolition.cpp \ diff --git a/src/progs/demolition/demolition.cpp b/src/progs/demolition/demolition.cpp index 3015200e..3693b5f3 100644 --- a/src/progs/demolition/demolition.cpp +++ b/src/progs/demolition/demolition.cpp @@ -229,8 +229,8 @@ create_patch() engine->create_patch(path, (rand()%8)+1); // Spread them out a bit for easier monitoring with ingenuity - engine->set_metadata(path, "module-x", 1600 + rand()%800 - 400); - engine->set_metadata(path, "module-y", 1200 + rand()%700 - 350); + engine->set_metadata(path, "ingenuity:module-x", 1600 + rand()%800 - 400); + engine->set_metadata(path, "ingenuity:module-y", 1200 + rand()%700 - 350); } @@ -255,8 +255,8 @@ add_node() engine->create_node(path, random_plugin(), rand()%2); // Spread them out a bit for easier monitoring with ingenuity - engine->set_metadata(path, "module-x", 1600 + rand()%800 - 400); - engine->set_metadata(path, "module-y", 1200 + rand()%700 - 350); + engine->set_metadata(path, "ingenuity:module-x", 1600 + rand()%800 - 400); + engine->set_metadata(path, "ingenuity:module-y", 1200 + rand()%700 - 350); } diff --git a/src/progs/ingenuity/Loader.cpp b/src/progs/ingenuity/Loader.cpp index 59d718c7..485e450d 100644 --- a/src/progs/ingenuity/Loader.cpp +++ b/src/progs/ingenuity/Loader.cpp @@ -18,7 +18,7 @@ #include #include #include -#include "PatchLibrarian.h" +#include "Serializer.h" #include "PatchModel.h" using std::cout; using std::endl; @@ -26,9 +26,9 @@ namespace Ingenuity { Loader::Loader(CountedPtr engine) -: _patch_librarian(new PatchLibrarian(engine)) +: _serializer(new Serializer(engine)) { - assert(_patch_librarian != NULL); + assert(_serializer != NULL); // FIXME: rework this so the thread is only present when it's doing something (save mem) start(); @@ -37,7 +37,7 @@ Loader::Loader(CountedPtr engine) Loader::~Loader() { - delete _patch_librarian; + delete _serializer; } @@ -45,12 +45,12 @@ void Loader::_whipped() { _mutex.lock(); + + while ( ! _events.empty() ) { + _events.front()(); + _events.pop_front(); + } - Closure& ev = _event; - ev(); - ev.disconnect(); - - _cond.signal(); _mutex.unlock(); } @@ -65,23 +65,29 @@ Loader::load_patch(const string& filename, { _mutex.lock(); - _event = sigc::hide_return(sigc::bind( - sigc::mem_fun(_patch_librarian, &PatchLibrarian::load_patch), - filename, parent_path, name, poly, initial_data, existing)); + _events.push_back(sigc::hide_return(sigc::bind( + sigc::mem_fun(_serializer, &Serializer::load_patch), + filename, parent_path, name, poly, initial_data, existing))); - whip(); - - _cond.wait(_mutex); _mutex.unlock(); + + whip(); } void Loader::save_patch(CountedPtr model, const string& filename, bool recursive) { - cerr << "FIXME: (loader) save patch\n"; - //cout << "[Loader] Saving patch " << filename << endl; - //set_event(new SavePatchEvent(m_patch_librarian, model, filename, recursive)); + _mutex.lock(); + + _events.push_back(sigc::hide_return(sigc::bind( + sigc::mem_fun(_serializer, &Serializer::save_patch), + model, filename, recursive))); + + _mutex.unlock(); + + whip(); } + } // namespace Ingenuity diff --git a/src/progs/ingenuity/Loader.h b/src/progs/ingenuity/Loader.h index a33945a1..7459378e 100644 --- a/src/progs/ingenuity/Loader.h +++ b/src/progs/ingenuity/Loader.h @@ -18,6 +18,7 @@ #define LOADERTHREAD_H #include +#include #include #include "util/Thread.h" #include "util/Slave.h" @@ -26,9 +27,10 @@ #include "ModelEngineInterface.h" #include "ObjectModel.h" using std::string; +using std::list; namespace Ingen { namespace Client { - class PatchLibrarian; + class Serializer; class PatchModel; } } using namespace Ingen::Client; @@ -42,6 +44,9 @@ namespace Ingenuity { * blocking everything else, so the app can respond to the incoming events * caused as a result of the patch loading, while the patch loads. * + * Implemented as a slave with a list of closures (events) which processes + * all events in the (mutex protected) list each time it's whipped. + * * \ingroup Ingenuity */ class Loader : public Slave @@ -50,7 +55,7 @@ public: Loader(CountedPtr engine); ~Loader(); - PatchLibrarian& librarian() { return *_patch_librarian; } + Serializer& serializer() const { return *_serializer; } void load_patch(const string& filename, const string& parent_path, @@ -69,10 +74,9 @@ private: void _whipped(); - PatchLibrarian* const _patch_librarian; - Mutex _mutex; - Condition _cond; - Closure _event; + Serializer* const _serializer; + Mutex _mutex; + list _events; }; diff --git a/src/progs/ingenuity/Makefile.am b/src/progs/ingenuity/Makefile.am index 48b53f33..3044a2fc 100644 --- a/src/progs/ingenuity/Makefile.am +++ b/src/progs/ingenuity/Makefile.am @@ -6,14 +6,15 @@ MAINTAINERCLEANFILES = Makefile.in sharefilesdir = $(pkgdatadir) dist_sharefiles_DATA = ingenuity.glade om-icon.png -AM_CXXFLAGS = -DGTK_DISABLE_DEPRECATED -DGDK_DISABLE_DEPRECATED -I$(top_srcdir)/src/common -I$(top_srcdir)/src/libs/client -DPKGDATADIR=\"$(pkgdatadir)\" @GTKMM_CFLAGS@ @LIBGLADEMM_CFLAGS@ @GNOMECANVASMM_CFLAGS@ @LOSC_CFLAGS@ @LASH_CFLAGS@ @FLOWCANVAS_CFLAGS@ -ingenuity_LDADD = @GTKMM_LIBS@ @LIBGLADEMM_LIBS@ @GNOMECANVASMM_LIBS@ @LOSC_LIBS@ @LASH_LIBS@ @FLOWCANVAS_LIBS@ ../../libs/client/libomclient.la -ingenuity_DEPENDENCIES = ../../libs/client/libomclient.la +ingenuity_CXXFLAGS = -DGTK_DISABLE_DEPRECATED -DGDK_DISABLE_DEPRECATED -I$(top_srcdir)/src/common -I$(top_srcdir)/src/libs/client -DPKGDATADIR=\"$(pkgdatadir)\" @GTKMM_CFLAGS@ @LIBGLADEMM_CFLAGS@ @GNOMECANVASMM_CFLAGS@ @LOSC_CFLAGS@ @LASH_CFLAGS@ @FLOWCANVAS_CFLAGS@ +ingenuity_LDADD = @GTKMM_LIBS@ @LIBGLADEMM_LIBS@ @GNOMECANVASMM_LIBS@ @LOSC_LIBS@ @LASH_LIBS@ @FLOWCANVAS_LIBS@ ../../libs/client/libingenclient.la +ingenuity_DEPENDENCIES = ../../libs/client/libingenclient.la # FIXME: make engine have a separate include dir if MONOLITHIC_INGENUITY -AM_CXXFLAGS += -I$(top_srcdir)/src/libs -ingenuity_LDADD += @JACK_LIBS@ @ALSA_LIBS@ @LASH_LIBS@ @SLV2_LIBS@ -lrt ../../libs/engine/libingen.la +ingenuity_CXXFLAGS += -I$(top_srcdir)/src/libs +#ingenuity_LDADD += @JACK_LIBS@ @ALSA_LIBS@ @LASH_LIBS@ @SLV2_LIBS@ -lrt ../../libs/engine/libingen.la +ingenuity_LDADD += ../../libs/engine/libingen.la ingenuity_DEPENDENCIES += ../../libs/engine/libingen.la endif diff --git a/src/progs/ingenuity/NodeModule.cpp b/src/progs/ingenuity/NodeModule.cpp index 8545e493..29af857e 100644 --- a/src/progs/ingenuity/NodeModule.cpp +++ b/src/progs/ingenuity/NodeModule.cpp @@ -104,13 +104,13 @@ NodeModule::store_location() const float x = static_cast(property_x()); const float y = static_cast(property_y()); - const Atom& existing_x = m_node->get_metadata("module-x"); - const Atom& existing_y = m_node->get_metadata("module-y"); + const Atom& existing_x = m_node->get_metadata("ingenuity:canvas-x"); + const Atom& existing_y = m_node->get_metadata("ingenuity:canvas-y"); if (existing_x.type() != Atom::FLOAT || existing_y.type() != Atom::FLOAT || existing_x.get_float() != x || existing_y.get_float() != y) { - App::instance().engine()->set_metadata(m_node->path(), "module-x", Atom(x)); - App::instance().engine()->set_metadata(m_node->path(), "module-y", Atom(y)); + App::instance().engine()->set_metadata(m_node->path(), "ingenuity:canvas-x", Atom(x)); + App::instance().engine()->set_metadata(m_node->path(), "ingenuity:canvas-y", Atom(y)); } } @@ -126,9 +126,9 @@ NodeModule::on_right_click(GdkEventButton* event) void NodeModule::metadata_update(const string& key, const Atom& value) { - if (key == "module-x" && value.type() == Atom::FLOAT) + if (key == "ingenuity:canvas-x" && value.type() == Atom::FLOAT) move_to(value.get_float(), property_y()); - else if (key == "module-y" && value.type() == Atom::FLOAT) + else if (key == "ingenuity:canvas-y" && value.type() == Atom::FLOAT) move_to(property_x(), value.get_float()); } diff --git a/src/progs/ingenuity/PatchCanvas.cpp b/src/progs/ingenuity/PatchCanvas.cpp index 9419b252..707b8856 100644 --- a/src/progs/ingenuity/PatchCanvas.cpp +++ b/src/progs/ingenuity/PatchCanvas.cpp @@ -217,9 +217,9 @@ PatchCanvas::connect(const LibFlowCanvas::Port* src_port, const LibFlowCanvas::P CountedPtr pm(new PluginModel(PluginModel::Internal, "", "midi_control_in", "")); CountedPtr nm(new NodeModel(pm, m_patch->path().base() + src->name() + "-" + dst->name(), false)); - nm->set_metadata("module-x", Atom((float) + nm->set_metadata("canvas-x", Atom((float) (dst->module()->property_x() - dst->module()->width() - 20))); - nm->set_metadata("module-y", Atom((float) + nm->set_metadata("canvas-y", Atom((float) (dst->module()->property_y()))); App::instance().engine()->create_node_from_model(nm.get()); App::instance().engine()->connect(src->model()->path(), nm->path() + "/MIDI_In"); @@ -332,8 +332,8 @@ PatchCanvas::get_initial_data() { MetadataMap result; - result["module-x"] = Atom((float)m_last_click_x); - result["module-y"] = Atom((float)m_last_click_y); + result["ingenuity:canvas-x"] = Atom((float)m_last_click_x); + result["ingenuity:canvas-y"] = Atom((float)m_last_click_y); return result; } diff --git a/src/progs/ingenuity/PatchPortModule.cpp b/src/progs/ingenuity/PatchPortModule.cpp index 369a04b1..8843882b 100644 --- a/src/progs/ingenuity/PatchPortModule.cpp +++ b/src/progs/ingenuity/PatchPortModule.cpp @@ -51,8 +51,8 @@ PatchPortModule::PatchPortModule(PatchCanvas* canvas, CountedPtr port resize(); - const Atom& x_atom = port->get_metadata("module-x"); - const Atom& y_atom = port->get_metadata("module-y"); + const Atom& x_atom = port->get_metadata("ingenuity:canvas-x"); + const Atom& y_atom = port->get_metadata("ingenuity:canvas-y"); if (x_atom && y_atom && x_atom.type() == Atom::FLOAT && y_atom.type() == Atom::FLOAT) { move_to(x_atom.get_float(), y_atom.get_float()); @@ -73,13 +73,13 @@ PatchPortModule::store_location() const float x = static_cast(property_x()); const float y = static_cast(property_y()); - const Atom& existing_x = m_port->get_metadata("module-x"); - const Atom& existing_y = m_port->get_metadata("module-y"); + const Atom& existing_x = m_port->get_metadata("ingenuity:canvas-x"); + const Atom& existing_y = m_port->get_metadata("ingenuity:canvas-y"); if (existing_x.type() != Atom::FLOAT || existing_y.type() != Atom::FLOAT || existing_x.get_float() != x || existing_y.get_float() != y) { - App::instance().engine()->set_metadata(m_port->path(), "module-x", Atom(x)); - App::instance().engine()->set_metadata(m_port->path(), "module-y", Atom(y)); + App::instance().engine()->set_metadata(m_port->path(), "ingenuity:canvas-x", Atom(x)); + App::instance().engine()->set_metadata(m_port->path(), "ingenuity:canvas-y", Atom(y)); } } @@ -87,9 +87,9 @@ PatchPortModule::store_location() void PatchPortModule::metadata_update(const string& key, const Atom& value) { - if (key == "module-x" && value.type() == Atom::FLOAT) + if (key == "ingenuity:canvas-x" && value.type() == Atom::FLOAT) move_to(value.get_float(), property_y()); - else if (key == "module-y" && value.type() == Atom::FLOAT) + else if (key == "ingenuity:canvas-y" && value.type() == Atom::FLOAT) move_to(property_x(), value.get_float()); } diff --git a/src/progs/patch_loader/Makefile.am b/src/progs/patch_loader/Makefile.am index c14814ff..33947a36 100644 --- a/src/progs/patch_loader/Makefile.am +++ b/src/progs/patch_loader/Makefile.am @@ -1,13 +1,13 @@ EXTRA_DIST = README -om_patch_loader_CXXFLAGS = -I$(top_srcdir)/src/libs/client -I$(top_srcdir)/src/common -DPKGDATADIR=\"$(pkgdatadir)\" $(LXML2_CFLAGS) $(LOSC_CFLAGS) $(LSIGCPP_CFLAGS) -om_patch_loader_LDADD = ../../libs/client/libomclient.la $(LOSC_LIBS) $(LXML2_LIBS) $(LSIGCPP_LIBS) +ingen_load_CXXFLAGS = -I$(top_srcdir)/src/libs/client -I$(top_srcdir)/src/common @LSIGCPP_CFLAGS@ +ingen_load_LDADD = ../../libs/client/libingenclient.la -bin_PROGRAMS = om_patch_loader +bin_PROGRAMS = ingen_load -om_patch_loader_DEPENDENCIES = ../../libs/client/libomclient.la +ingen_load_DEPENDENCIES = ../../libs/client/libingenclient.la -om_patch_loader_SOURCES = \ +ingen_load_SOURCES = \ patch_loader.cpp \ cmdline.h \ cmdline.c diff --git a/src/progs/patch_loader/patch_loader.cpp b/src/progs/patch_loader/patch_loader.cpp index 69a06407..b0d8bc94 100644 --- a/src/progs/patch_loader/patch_loader.cpp +++ b/src/progs/patch_loader/patch_loader.cpp @@ -16,7 +16,7 @@ #include "OSCModelEngineInterface.h" -#include "PatchLibrarian.h" +#include "Serializer.h" #include "PatchModel.h" #include "util/Path.h" #include @@ -53,7 +53,7 @@ int main(int argc, char** argv) CountedPtr engine(new OSCModelEngineInterface(engine_url)); - PatchLibrarian librarian(engine); + Serializer serializer(engine); /* Connect to engine */ engine->attach(-1, client_port); @@ -74,7 +74,7 @@ int main(int argc, char** argv) cerr << "FIXME: load patch" << endl; //CountedPtr pm(new PatchModel("", 0)); //pm->filename(args_info.inputs[i]); - //librarian.load_patch(pm, true); + //serializer.load_patch(pm, true); } return 0; -- cgit v1.2.1