1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
|
Handling structured data stored as XML
--------------------------------------
NOTE: In spring 2025, XML handling was reworked, because the project was confronted
with the necessity to support Version 3.x and 4.x of lib-MXML, which are
incompatible on API and ABI level.
Yoshimi maintaines a large amount of _stuctured data_ persistently,
stored as (optionally gzip-compressed) XML files.
- Configuration
- Instance state
- complete »Patch state«
- Instruments
- Presets
- Microtonal scales
- MIDI-learn data
The actual XML handling relies on lib-MXML (»Mini-XML)
https://www.msweet.org/mxml/ written by Michael R. Sweet
The access to the details of this storage format handling
is structured into several layers of abstraction (See XMLStore.h|cpp)
- XMLStore is the access point to setup a storage with structured data,
and to load or save such a _data tree_ into XML format persistently.
- XMLtree is a smart-pointer (with automatic memory managment),
referring to some location in a data tree. It provides functions
to add _XML attributes_ and _Yoshimi data parameters,_ including
the flexible handling of defaults and migration from older versions.
- XMLtree::Node is the abstracted access to low-level XML manipulation
functions, an can only be used from within class XMLtree; it inherits
from a _policy base class_ -- which allows to accommodate differences
in the API of lib-MXML versions
- the C-functions of lib-MXML are used for all access to actual data,
for navigation in the tree, parsing and pretty-printing and memory management.
The client code from the Yoshimi core has to deal only with XMLStore and XMLtree
objects; both are lightweight _value objects_ and can be moved (not copied).
They can be placed into local variables on the stack (no need for a heap alloc).
Since they rely on the ref-count by MXML, they clean-up automatically when
going out of scope.
Class XMLStore holds a `Metadata` record to maintain the version numer
of the data format, which is identical to the version number of the code base
managing the data. An ZynAddSubFX compatibility mode is provided, while the
Yoshimi-native format uses a different XML DOCTYPE and a different root element.
Each XML store is marked with an »XML type« to indicate which kind of data
storage it manages. See the Enum `TOPLEVEL::XML` in `globals.h`
Usage Patterns
~~~~~~~~~~~~~~
Store data
^^^^^^^^^^
- create a new XMLStore marked for a specific XML-type
XMLStore xml{TOPLEVEL::XML::Config};
- add a top-level data element
XMLtree conf = xml.addElm("CONFIGURATION");
- add parameters to this node
conf.addPar_int ("sound_buffer_size" , buffersize);
conf.addPar_bool("hide_system_errors" , hideErrors);
conf.addPar_str ("linux_jack_server" , jackServer);
- add a nested sub-node to the tree
XMLtree xmlKit = conf.addElm("INSTRUMENT_KIT");
xmlKit.addPar_int("kit_mode", Pkitmode);
- save the data tree created thus far
auto& logger = synth.getRuntime().getLogger();
uint compLev = synth.getRuntime().gzipCompression;
bool success = xml.saveXMLfile(configFile, logger, compLev);
Load data
^^^^^^^^^
- create a XMLStore instance from an XML file
XMLStore xml{configFile, getLogger()};
bool success = not xml.empty();
- enter a top-level node in the data tree loaded
XMLtree conf = xml.getElm("CONFIGURATION");
if (not conf)
Log("ERROR ....");
- retrieve parameter data
oscilsize = conf.getPar_int ("oscil_size", oscilsize, MIN_OSCIL_SIZE, MAX_OSCIL_SIZE);
hideErrors = conf.getPar_bool("hide_system_errors", hideErrors);
jackServer = conf.getPar_str ("linux_jack_server");
- optionally enter a nested node
if (XMLtree xmlKit = xmlInstrument.getElm("INSTRUMENT_KIT"))
{
Pkitmode = xmlKit.getPar_127("kit_mode", Pkitmode);
}
|