zynaddsubfx

ZynAddSubFX open source synthesizer
Log | Files | Refs | Submodules | LICENSE

commit f57e0ce3a56ab508cce8946f78c94de91ed9c313
parent 8a0ee4a771db7993d14bf2759fa944f870f46443
Author: Johannes Lorenz <j.git@lorenz-ho.me>
Date:   Sun, 12 Jan 2025 15:32:44 +0100

Support MXML4, if available

Improve comments

MXML4: Free memory

Fixup test, so it works on mxml3 and 4

Fix MessageTest for MXML4

Diffstat:
Msrc/CMakeLists.txt | 5++++-
Msrc/Misc/XMLwrapper.cpp | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Msrc/Tests/MessageTest.cpp | 16+++++-----------
Msrc/Tests/PluginTest.cpp | 21++++++++++++++++++++-
4 files changed, 104 insertions(+), 31 deletions(-)

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt @@ -33,7 +33,10 @@ if(PKG_CONFIG_FOUND AND NOT (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")) pkg_check_modules(NTK_IMAGES ntk_images) pkg_check_modules(FFTW3F REQUIRED fftw3f) - pkg_check_modules(MXML REQUIRED mxml) + pkg_check_modules(MXML mxml4) + if(NOT MXML_FOUND) + pkg_check_modules(MXML REQUIRED mxml) + endif() pkg_search_module(LASH lash-1.0) mark_as_advanced(LASH_LIBRARIES) diff --git a/src/Misc/XMLwrapper.cpp b/src/Misc/XMLwrapper.cpp @@ -32,9 +32,30 @@ namespace zyn { int xml_k = 0; bool verbose = false; -const char *XMLwrapper_whitespace_callback(mxml_node_t *node, int where) +#if MXML_MAJOR_VERSION <= 3 +// Mimic datatypes present in mxml4 for compatibility +typedef int mxml_ws_t; +typedef int mxml_descend_t; +// Mimic typenames present in mxml4 for compatibility +constexpr int MXML_DESCEND_ALL = MXML_DESCEND; +constexpr int MXML_DESCEND_NONE = MXML_NO_DESCEND; +constexpr int MXML_TYPE_OPAQUE = MXML_OPAQUE; +constexpr int MXML_TYPE_ELEMENT = MXML_ELEMENT; +constexpr int MXML_TYPE_TEXT = MXML_TEXT; +#endif + +const char *XMLwrapper_whitespace_callback(void*, mxml_node_t *node, mxml_ws_t where) { +#if MXML_MAJOR_VERSION >= 4 + // New node types in MXL4 + if(mxmlGetDirective(node)) // "?xml" directive + return "\n"; + else if(mxmlGetDeclaration(node)) // "!DOCTYPE" declaration + return nullptr; +#endif + const char *name = mxmlGetElement(node); + assert(name); if((where == MXML_WS_BEFORE_OPEN) && (!strcmp(name, "?xml"))) return NULL; @@ -67,13 +88,21 @@ const char *XMLwrapper_whitespace_callback(mxml_node_t *node, int where) return 0; } +#if MXML_MAJOR_VERSION <= 3 +// Wrapper, because int and mxml_ws_t are different types +inline const char *XMLwrapper_whitespace_callback(mxml_node_t *node, int where) +{ + return XMLwrapper_whitespace_callback(nullptr, node, where); +} +#endif + //temporary const overload of mxmlFindElement const mxml_node_t *mxmlFindElement(const mxml_node_t *node, const mxml_node_t *top, const char *name, const char *attr, const char *value, - int descend) + mxml_descend_t descend) { return const_cast<const mxml_node_t *>(mxmlFindElement( const_cast<mxml_node_t *>(node), @@ -92,16 +121,22 @@ XMLwrapper::XMLwrapper() minimal = true; SaveFullXml=false; - node = tree = mxmlNewElement(MXML_NO_PARENT, - "?xml version=\"1.0f\" encoding=\"UTF-8\"?"); + node = tree = mxmlNewXML("1.0"); + assert(node); /* for mxml 2.1f (and older) tree=mxmlNewElement(MXML_NO_PARENT,"?xml"); mxmlElementSetAttr(tree,"version","1.0f"); mxmlElementSetAttr(tree,"encoding","UTF-8"); */ - mxml_node_t *doctype = mxmlNewElement(tree, "!DOCTYPE"); - mxmlElementSetAttr(doctype, "ZynAddSubFX-data", NULL); + mxml_node_t *doctype = +#if MXML_MAJOR_VERSION <= 3 + mxmlNewElement(tree, "!DOCTYPE"); + mxmlElementSetAttr(doctype, "ZynAddSubFX-data", NULL); +#else + mxmlNewDeclaration(tree, "DOCTYPE ZynAddSubFX-data"); +#endif + assert(doctype); node = root = addparams("ZynAddSubFX-data", 4, "version-major", stringFrom<int>( @@ -164,7 +199,7 @@ bool XMLwrapper::hasPadSynth() const "INFORMATION", NULL, NULL, - MXML_DESCEND); + MXML_DESCEND_ALL); mxml_node_t *parameter = mxmlFindElement(tmp, tmp, @@ -204,7 +239,15 @@ char *XMLwrapper::getXMLdata() const { xml_k = 0; +#if MXML_MAJOR_VERSION <= 3 char *xmldata = mxmlSaveAllocString(tree, XMLwrapper_whitespace_callback); +#else + mxml_options_t *options = mxmlOptionsNew(); + mxmlOptionsSetWhitespaceCallback(options, XMLwrapper_whitespace_callback, /*cbdata*/nullptr); + char *xmldata = mxmlSaveAllocString(tree, options); + mxmlOptionsDelete(options); +#endif + return xmldata; } @@ -317,8 +360,15 @@ int XMLwrapper::loadXMLfile(const string &filename) if(xmldata == NULL) return -1; //the file could not be loaded or uncompressed +#if MXML_MAJOR_VERSION <= 3 root = tree = mxmlLoadString(NULL, trimLeadingWhite( - xmldata), MXML_OPAQUE_CALLBACK); + xmldata), MXML_OPAQUE_CALLBACK); +#else + mxml_options_t *options = mxmlOptionsNew(); + mxmlOptionsSetTypeValue(options, MXML_TYPE_OPAQUE); + root = tree = mxmlLoadString(NULL, options, trimLeadingWhite(xmldata)); + mxmlOptionsDelete(options); +#endif delete[] xmldata; @@ -330,7 +380,7 @@ int XMLwrapper::loadXMLfile(const string &filename) "ZynAddSubFX-data", NULL, NULL, - MXML_DESCEND); + MXML_DESCEND_ALL); if(root == NULL) return -3; //the XML doesn't embbed zynaddsubfx data @@ -384,8 +434,15 @@ bool XMLwrapper::putXMLdata(const char *xmldata) if(xmldata == NULL) return false; +#if MXML_MAJOR_VERSION <= 3 root = tree = mxmlLoadString(NULL, trimLeadingWhite( - xmldata), MXML_OPAQUE_CALLBACK); + xmldata), MXML_OPAQUE_CALLBACK); +#else + mxml_options_t *options = mxmlOptionsNew(); + mxmlOptionsSetTypeValue(options, MXML_TYPE_OPAQUE); + root = tree = mxmlLoadString(NULL, options, trimLeadingWhite(xmldata)); + mxmlOptionsDelete(options); +#endif if(tree == NULL) return false; @@ -394,7 +451,7 @@ bool XMLwrapper::putXMLdata(const char *xmldata) "ZynAddSubFX-data", NULL, NULL, - MXML_DESCEND); + MXML_DESCEND_ALL); if(root == NULL) return false; @@ -531,11 +588,11 @@ void XMLwrapper::getparstr(const string &name, char *par, int maxstrlen) const return; if(mxmlGetFirstChild(tmp) == NULL) return; - if(mxmlGetType(mxmlGetFirstChild(tmp)) == MXML_OPAQUE) { + if(mxmlGetType(mxmlGetFirstChild(tmp)) == MXML_TYPE_OPAQUE) { snprintf(par, maxstrlen, "%s", mxmlGetOpaque(mxmlGetFirstChild(tmp))); return; } - if((mxmlGetType(mxmlGetFirstChild(tmp)) == MXML_TEXT) + if((mxmlGetType(mxmlGetFirstChild(tmp)) == MXML_TYPE_TEXT) && (mxmlGetFirstChild(tmp) != NULL)) { snprintf(par, maxstrlen, "%s", mxmlGetText(mxmlGetFirstChild(tmp),NULL)); return; @@ -555,11 +612,11 @@ string XMLwrapper::getparstr(const string &name, if((tmp == NULL) || (mxmlGetFirstChild(tmp) == NULL)) return defaultpar; - if(mxmlGetType(mxmlGetFirstChild(tmp)) == MXML_OPAQUE + if(mxmlGetType(mxmlGetFirstChild(tmp)) == MXML_TYPE_OPAQUE && (mxmlGetOpaque(mxmlGetFirstChild(tmp)) != NULL)) return mxmlGetOpaque(mxmlGetFirstChild(tmp)); - if(mxmlGetType(mxmlGetFirstChild(tmp)) == MXML_TEXT + if(mxmlGetType(mxmlGetFirstChild(tmp)) == MXML_TYPE_TEXT && (mxmlGetText(mxmlGetFirstChild(tmp),NULL) != NULL)) return mxmlGetText(mxmlGetFirstChild(tmp),NULL); @@ -684,8 +741,8 @@ std::vector<XmlNode> XMLwrapper::getBranch(void) const std::vector<XmlNode> res; mxml_node_t *current = mxmlGetFirstChild(node); while(current) { - if(mxmlGetType(current) == MXML_ELEMENT) { -#if MXML_MAJOR_VERSION == 3 + if(mxmlGetType(current) == MXML_TYPE_ELEMENT) { +#if MXML_MAJOR_VERSION >= 3 XmlNode n(mxmlGetElement(current)); int count = mxmlElementGetAttrCount(current); const char *name; @@ -705,7 +762,7 @@ std::vector<XmlNode> XMLwrapper::getBranch(void) const #endif res.push_back(n); } - current = mxmlWalkNext(current, node, MXML_NO_DESCEND); + current = mxmlWalkNext(current, node, MXML_DESCEND_NONE); } return res; } diff --git a/src/Tests/MessageTest.cpp b/src/Tests/MessageTest.cpp @@ -105,22 +105,16 @@ class MessageTest mw->transmitMsg("/presets/copy", "s", "/part0/kit0/adpars/VoicePar0/FMSmp/"); TS_ASSERT_EQUAL_STR("Poscilgen", mw->getPresetsStore().clipboard.type.c_str()); - // a regex would be better here... - // hopefully, mxml will not change its whitespace behavior - assert_non_null(strstr(mw->getPresetsStore().clipboard.data.c_str(), "<par name=\"base_function_par\" value=\"32\" />"), - "base_function_par at right value", __LINE__); - - /* // better test this without string comparison: + //Use XMLwrapper to validate copied XML { XMLwrapper xml; bool couldPutXml = xml.putXMLdata(mw->getPresetsStore().clipboard.data.c_str()); TS_ASSERT(couldPutXml); + xml.enterbranch("Poscilgen"); unsigned char copiedBasefuncPar = xml.getpar127("base_function_par", 0); - TS_ASSERT_EQUALS(copiedBasefuncPar, 32); - }*/ - - //printf("clipboard type: %s\n",mw->getPresetsStore().clipboard.type.c_str()); - //printf("clipboard data:\n%s\n",mw->getPresetsStore().clipboard.data.c_str()); + xml.exitbranch(); + TS_ASSERT_EQUAL_INT(+copiedBasefuncPar, 32); + } TS_ASSERT_EQUAL_INT(osc_dst.Pbasefuncpar, 64); TS_ASSERT_EQUAL_INT(osc_oth.Pbasefuncpar, 64); diff --git a/src/Tests/PluginTest.cpp b/src/Tests/PluginTest.cpp @@ -15,6 +15,7 @@ #include <cstdlib> #include <iostream> #include <fstream> +#include <regex> #include <string> #include "../Misc/MiddleWare.h" #include "../Misc/Master.h" @@ -228,12 +229,30 @@ class PluginTest void testLoadSave(void) { + // Do the load/save const string fname = string(SOURCE_DIR) + "/guitar-adnote.xmz"; - const string fdata = loadfile(fname); + string fdata = loadfile(fname); char *result = NULL; master[0]->putalldata((char*)fdata.c_str()); int res = master[0]->getalldata(&result); + // Fixup, because d44dc9b corrupted guitar-adnote.xmz: + // Replace "1.0f" with "1.0" and "UTF-8" with "utf-8" in `<?xml...` + fdata = std::regex_replace(fdata, + std::regex(R"(<\?xml version="1\.0f" encoding="UTF-8"\?>)"), + R"(<?xml version="1.0" encoding="utf-8"?>)"); + + // Fixups, because guitar-adnote.xmz was saved with MXML3 +#if MXML_MAJOR_VERSION >= 4 + // guitar-adnote has tags ending on " />" - we remove the space + fdata = std::regex_replace(fdata, std::regex(" />"), "/>"); + // Remove trailing newline + if (fdata.size() >= 1 && fdata[fdata.size() - 1] == '\n') { + fdata.pop_back(); + } +#endif + + // Checks TS_ASSERT_EQUAL_INT((int)(fdata.length()+1), res); TS_ASSERT(fdata == result); if(fdata != result)