1 /*
2  * Carla State utils
3  * Copyright (C) 2012-2020 Filipe Coelho <falktx@falktx.com>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of
8  * the License, or any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * For a full copy of the GNU General Public License see the doc/GPL.txt file.
16  */
17 
18 #include "CarlaStateUtils.hpp"
19 
20 #include "CarlaBackendUtils.hpp"
21 #include "CarlaMathUtils.hpp"
22 #include "CarlaMIDI.h"
23 
24 #include "water/streams/MemoryOutputStream.h"
25 #include "water/xml/XmlElement.h"
26 
27 #include <string>
28 
29 using water::MemoryOutputStream;
30 using water::String;
31 using water::XmlElement;
32 
33 CARLA_BACKEND_START_NAMESPACE
34 
35 // -----------------------------------------------------------------------
36 // getNewLineSplittedString
37 
getNewLineSplittedString(MemoryOutputStream & stream,const String & string)38 static void getNewLineSplittedString(MemoryOutputStream& stream, const String& string)
39 {
40     static const int kLineWidth = 120;
41 
42     int i = 0;
43     const int length = string.length();
44     const char* const raw = string.toUTF8();
45 
46     stream.preallocate(static_cast<std::size_t>(length + length/kLineWidth + 3));
47 
48     for (; i+kLineWidth < length; i += kLineWidth)
49     {
50         stream.write(raw+i, kLineWidth);
51         stream.writeByte('\n');
52     }
53 
54     stream << (raw+i);
55 }
56 
57 // -----------------------------------------------------------------------
58 // xmlSafeStringFast
59 
60 /* Based on some code by James Kanze from stackoverflow
61  * https://stackoverflow.com/questions/7724011/in-c-whats-the-fastest-way-to-replace-all-occurrences-of-a-substring-within */
62 
replaceStdString(const std::string & original,const std::string & before,const std::string & after)63 static std::string replaceStdString(const std::string& original, const std::string& before, const std::string& after)
64 {
65     std::string::const_iterator current = original.begin(), end = original.end(), next;
66     std::string retval;
67 
68     for (; (next = std::search(current, end, before.begin(), before.end())) != end;)
69     {
70         retval.append(current, next);
71         retval.append(after);
72         current = next + static_cast<ssize_t>(before.size());
73     }
74     retval.append(current, next);
75     return retval;
76 }
77 
xmlSafeStringFast(const char * const cstring,const bool toXml)78 static std::string xmlSafeStringFast(const char* const cstring, const bool toXml)
79 {
80     std::string string(cstring);
81 
82     if (toXml)
83     {
84         string = replaceStdString(string, "&","&amp;");
85         string = replaceStdString(string, "<","&lt;");
86         string = replaceStdString(string, ">","&gt;");
87         string = replaceStdString(string, "'","&apos;");
88         string = replaceStdString(string, "\"","&quot;");
89     }
90     else
91     {
92         string = replaceStdString(string, "&lt;","<");
93         string = replaceStdString(string, "&gt;",">");
94         string = replaceStdString(string, "&apos;","'");
95         string = replaceStdString(string, "&quot;","\"");
96         string = replaceStdString(string, "&amp;","&");
97     }
98 
99     return string;
100 }
101 
102 // -----------------------------------------------------------------------
103 // xmlSafeStringCharDup
104 
105 /*
106 static const char* xmlSafeStringCharDup(const char* const cstring, const bool toXml)
107 {
108     return carla_strdup(xmlSafeString(cstring, toXml).toRawUTF8());
109 }
110 */
111 
xmlSafeStringCharDup(const String & string,const bool toXml)112 static const char* xmlSafeStringCharDup(const String& string, const bool toXml)
113 {
114     return carla_strdup(xmlSafeString(string, toXml).toRawUTF8());
115 }
116 
117 // -----------------------------------------------------------------------
118 // StateParameter
119 
Parameter()120 CarlaStateSave::Parameter::Parameter() noexcept
121     : dummy(true),
122       index(-1),
123       name(nullptr),
124       symbol(nullptr),
125 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
126       value(0.0f),
127       mappedControlIndex(CONTROL_INDEX_NONE),
128       midiChannel(0),
129       mappedRangeValid(false),
130       mappedMinimum(0.0f),
131       mappedMaximum(1.0f) {}
132 #else
133       value(0.0f) {}
134 #endif
135 
~Parameter()136 CarlaStateSave::Parameter::~Parameter() noexcept
137 {
138     if (name != nullptr)
139     {
140         delete[] name;
141         name = nullptr;
142     }
143     if (symbol != nullptr)
144     {
145         delete[] symbol;
146         symbol = nullptr;
147     }
148 }
149 
150 // -----------------------------------------------------------------------
151 // StateCustomData
152 
CustomData()153 CarlaStateSave::CustomData::CustomData() noexcept
154     : type(nullptr),
155       key(nullptr),
156       value(nullptr) {}
157 
~CustomData()158 CarlaStateSave::CustomData::~CustomData() noexcept
159 {
160     if (type != nullptr)
161     {
162         delete[] type;
163         type = nullptr;
164     }
165     if (key != nullptr)
166     {
167         delete[] key;
168         key = nullptr;
169     }
170     if (value != nullptr)
171     {
172         delete[] value;
173         value = nullptr;
174     }
175 }
176 
isValid() const177 bool CarlaStateSave::CustomData::isValid() const noexcept
178 {
179     if (type  == nullptr || type[0] == '\0') return false;
180     if (key   == nullptr || key [0] == '\0') return false;
181     if (value == nullptr)                    return false;
182     return true;
183 }
184 
185 // -----------------------------------------------------------------------
186 // StateSave
187 
CarlaStateSave()188 CarlaStateSave::CarlaStateSave() noexcept
189     : type(nullptr),
190       name(nullptr),
191       label(nullptr),
192       binary(nullptr),
193       uniqueId(0),
194       options(0x0),
195       temporary(false),
196 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
197       active(false),
198       dryWet(1.0f),
199       volume(1.0f),
200       balanceLeft(-1.0f),
201       balanceRight(1.0f),
202       panning(0.0f),
203       ctrlChannel(-1),
204 #endif
205       currentProgramIndex(-1),
206       currentProgramName(nullptr),
207       currentMidiBank(-1),
208       currentMidiProgram(-1),
209       chunk(nullptr),
210       parameters(),
211       customData() {}
212 
~CarlaStateSave()213 CarlaStateSave::~CarlaStateSave() noexcept
214 {
215     clear();
216 }
217 
clear()218 void CarlaStateSave::clear() noexcept
219 {
220     if (type != nullptr)
221     {
222         delete[] type;
223         type = nullptr;
224     }
225     if (name != nullptr)
226     {
227         delete[] name;
228         name = nullptr;
229     }
230     if (label != nullptr)
231     {
232         delete[] label;
233         label = nullptr;
234     }
235     if (binary != nullptr)
236     {
237         delete[] binary;
238         binary = nullptr;
239     }
240     if (currentProgramName != nullptr)
241     {
242         delete[] currentProgramName;
243         currentProgramName = nullptr;
244     }
245     if (chunk != nullptr)
246     {
247         delete[] chunk;
248         chunk = nullptr;
249     }
250 
251     uniqueId = 0;
252     options  = 0x0;
253 
254 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
255     active = false;
256     dryWet = 1.0f;
257     volume = 1.0f;
258     balanceLeft  = -1.0f;
259     balanceRight = 1.0f;
260     panning      = 0.0f;
261     ctrlChannel  = -1;
262 #endif
263 
264     currentProgramIndex = -1;
265     currentMidiBank     = -1;
266     currentMidiProgram  = -1;
267 
268     for (ParameterItenerator it = parameters.begin2(); it.valid(); it.next())
269     {
270         Parameter* const stateParameter(it.getValue(nullptr));
271         delete stateParameter;
272     }
273 
274     for (CustomDataItenerator it = customData.begin2(); it.valid(); it.next())
275     {
276         CustomData* const stateCustomData(it.getValue(nullptr));
277         delete stateCustomData;
278     }
279 
280     parameters.clear();
281     customData.clear();
282 }
283 
284 // -----------------------------------------------------------------------
285 // fillFromXmlElement
286 
fillFromXmlElement(const XmlElement * const xmlElement)287 bool CarlaStateSave::fillFromXmlElement(const XmlElement* const xmlElement)
288 {
289     CARLA_SAFE_ASSERT_RETURN(xmlElement != nullptr, false);
290 
291     clear();
292 
293     for (XmlElement* elem = xmlElement->getFirstChildElement(); elem != nullptr; elem = elem->getNextElement())
294     {
295         const String& tagName(elem->getTagName());
296 
297         // ---------------------------------------------------------------
298         // Info
299 
300         if (tagName == "Info")
301         {
302             for (XmlElement* xmlInfo = elem->getFirstChildElement(); xmlInfo != nullptr; xmlInfo = xmlInfo->getNextElement())
303             {
304                 const String& tag(xmlInfo->getTagName());
305                 const String  text(xmlInfo->getAllSubText().trim());
306 
307                 /**/ if (tag == "Type")
308                     type = xmlSafeStringCharDup(text, false);
309                 else if (tag == "Name")
310                     name = xmlSafeStringCharDup(text, false);
311                 else if (tag == "Label" || tag == "URI" || tag == "Identifier" || tag == "Setup")
312                     label = xmlSafeStringCharDup(text, false);
313                 else if (tag == "Binary" || tag == "Filename")
314                     binary = xmlSafeStringCharDup(text, false);
315                 else if (tag == "UniqueID")
316                     uniqueId = text.getLargeIntValue();
317             }
318         }
319 
320         // ---------------------------------------------------------------
321         // Data
322 
323         else if (tagName == "Data")
324         {
325             for (XmlElement* xmlData = elem->getFirstChildElement(); xmlData != nullptr; xmlData = xmlData->getNextElement())
326             {
327                 const String& tag(xmlData->getTagName());
328                 const String  text(xmlData->getAllSubText().trim());
329 
330 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
331                 // -------------------------------------------------------
332                 // Internal Data
333 
334                 /**/ if (tag == "Active")
335                 {
336                     active = (text == "Yes");
337                 }
338                 else if (tag == "DryWet")
339                 {
340                     dryWet = carla_fixedValue(0.0f, 1.0f, text.getFloatValue());
341                 }
342                 else if (tag == "Volume")
343                 {
344                     volume = carla_fixedValue(0.0f, 1.27f, text.getFloatValue());
345                 }
346                 else if (tag == "Balance-Left")
347                 {
348                     balanceLeft = carla_fixedValue(-1.0f, 1.0f, text.getFloatValue());
349                 }
350                 else if (tag == "Balance-Right")
351                 {
352                     balanceRight = carla_fixedValue(-1.0f, 1.0f, text.getFloatValue());
353                 }
354                 else if (tag == "Panning")
355                 {
356                     panning = carla_fixedValue(-1.0f, 1.0f, text.getFloatValue());
357                 }
358                 else if (tag == "ControlChannel")
359                 {
360                     if (! text.startsWithIgnoreCase("n"))
361                     {
362                         const int value(text.getIntValue());
363                         if (value >= 1 && value <= MAX_MIDI_CHANNELS)
364                             ctrlChannel = static_cast<int8_t>(value-1);
365                     }
366                 }
367                 else if (tag == "Options")
368                 {
369                     const int value(text.getHexValue32());
370                     if (value > 0)
371                         options = static_cast<uint>(value);
372                 }
373 #else
374                 if (false) {}
375 #endif
376 
377                 // -------------------------------------------------------
378                 // Program (current)
379 
380                 else if (tag == "CurrentProgramIndex")
381                 {
382                     const int value(text.getIntValue());
383                     if (value >= 1)
384                         currentProgramIndex = value-1;
385                 }
386                 else if (tag == "CurrentProgramName")
387                 {
388                     currentProgramName = xmlSafeStringCharDup(text, false);
389                 }
390 
391                 // -------------------------------------------------------
392                 // Midi Program (current)
393 
394                 else if (tag == "CurrentMidiBank")
395                 {
396                     const int value(text.getIntValue());
397                     if (value >= 1)
398                         currentMidiBank = value-1;
399                 }
400                 else if (tag == "CurrentMidiProgram")
401                 {
402                     const int value(text.getIntValue());
403                     if (value >= 1)
404                         currentMidiProgram = value-1;
405                 }
406 
407                 // -------------------------------------------------------
408                 // Parameters
409 
410                 else if (tag == "Parameter")
411                 {
412                     Parameter* const stateParameter(new Parameter());
413 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
414                     bool hasMappedMinimum = false, hasMappedMaximum = false;
415 #endif
416 
417                     for (XmlElement* xmlSubData = xmlData->getFirstChildElement(); xmlSubData != nullptr; xmlSubData = xmlSubData->getNextElement())
418                     {
419                         const String& pTag(xmlSubData->getTagName());
420                         const String  pText(xmlSubData->getAllSubText().trim());
421 
422                         /**/ if (pTag == "Index")
423                         {
424                             const int index(pText.getIntValue());
425                             if (index >= 0)
426                                 stateParameter->index = index;
427                         }
428                         else if (pTag == "Name")
429                         {
430                             stateParameter->name = xmlSafeStringCharDup(pText, false);
431                         }
432                         else if (pTag == "Symbol")
433                         {
434                             stateParameter->symbol = xmlSafeStringCharDup(pText, false);
435                         }
436                         else if (pTag == "Value")
437                         {
438                             stateParameter->dummy = false;
439                             stateParameter->value = pText.getFloatValue();
440                         }
441 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
442                         else if (pTag == "MidiChannel")
443                         {
444                             const int channel(pText.getIntValue());
445                             if (channel >= 1 && channel <= MAX_MIDI_CHANNELS)
446                                 stateParameter->midiChannel = static_cast<uint8_t>(channel-1);
447                         }
448                         else if (pTag == "MidiCC")
449                         {
450                             const int cc(pText.getIntValue());
451                             if (cc > 0 && cc < MAX_MIDI_CONTROL)
452                                 stateParameter->mappedControlIndex = static_cast<int16_t>(cc);
453                         }
454                         else if (pTag == "MappedControlIndex")
455                         {
456                             const int ctrl(pText.getIntValue());
457                             if (ctrl > CONTROL_INDEX_NONE && ctrl <= CONTROL_INDEX_MAX_ALLOWED)
458                                 if (ctrl != CONTROL_INDEX_MIDI_LEARN)
459                                     stateParameter->mappedControlIndex = static_cast<int16_t>(ctrl);
460                         }
461                         else if (pTag == "MappedMinimum")
462                         {
463                             hasMappedMinimum = true;
464                             stateParameter->mappedMinimum = pText.getFloatValue();
465                         }
466                         else if (pTag == "MappedMaximum")
467                         {
468                             hasMappedMaximum = true;
469                             stateParameter->mappedMaximum = pText.getFloatValue();
470                         }
471 #endif
472                     }
473 
474 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
475                     if (hasMappedMinimum && hasMappedMaximum)
476                         stateParameter->mappedRangeValid = true;
477 #endif
478 
479                     parameters.append(stateParameter);
480                 }
481 
482                 // -------------------------------------------------------
483                 // Custom Data
484 
485                 else if (tag == "CustomData")
486                 {
487                     CustomData* const stateCustomData(new CustomData());
488 
489                     for (XmlElement* xmlSubData = xmlData->getFirstChildElement(); xmlSubData != nullptr; xmlSubData = xmlSubData->getNextElement())
490                     {
491                         const String& cTag(xmlSubData->getTagName());
492                         const String  cText(xmlSubData->getAllSubText().trim());
493 
494                         /**/ if (cTag == "Type")
495                             stateCustomData->type = xmlSafeStringCharDup(cText, false);
496                         else if (cTag == "Key")
497                             stateCustomData->key = xmlSafeStringCharDup(cText, false);
498                         else if (cTag == "Value")
499                             stateCustomData->value = carla_strdup(cText.toRawUTF8()); //xmlSafeStringCharDup(cText, false);
500                     }
501 
502                     if (stateCustomData->isValid())
503                         customData.append(stateCustomData);
504                     else
505                         carla_stderr("Reading CustomData property failed, missing data");
506                 }
507 
508                 // -------------------------------------------------------
509                 // Chunk
510 
511                 else if (tag == "Chunk")
512                 {
513                     chunk = carla_strdup(text.toRawUTF8());
514                 }
515             }
516         }
517     }
518 
519     return true;
520 }
521 
522 // -----------------------------------------------------------------------
523 // fillXmlStringFromStateSave
524 
dumpToMemoryStream(MemoryOutputStream & content) const525 void CarlaStateSave::dumpToMemoryStream(MemoryOutputStream& content) const
526 {
527     {
528         MemoryOutputStream infoXml;
529 
530         infoXml << "  <Info>\n";
531         infoXml << "   <Type>" << String(type != nullptr ? type : "") << "</Type>\n";
532         infoXml << "   <Name>" << xmlSafeString(name, true) << "</Name>\n";
533 
534         switch (getPluginTypeFromString(type))
535         {
536         case PLUGIN_NONE:
537             break;
538         case PLUGIN_INTERNAL:
539             infoXml << "   <Label>"    << xmlSafeString(label, true)  << "</Label>\n";
540             break;
541         case PLUGIN_LADSPA:
542             infoXml << "   <Binary>"   << xmlSafeString(binary, true) << "</Binary>\n";
543             infoXml << "   <Label>"    << xmlSafeString(label, true)  << "</Label>\n";
544             infoXml << "   <UniqueID>" << water::int64(uniqueId)       << "</UniqueID>\n";
545             break;
546         case PLUGIN_DSSI:
547             infoXml << "   <Binary>"   << xmlSafeString(binary, true) << "</Binary>\n";
548             infoXml << "   <Label>"    << xmlSafeString(label, true)  << "</Label>\n";
549             break;
550         case PLUGIN_LV2:
551             infoXml << "   <URI>"      << xmlSafeString(label, true)  << "</URI>\n";
552             break;
553         case PLUGIN_VST2:
554             infoXml << "   <Binary>"   << xmlSafeString(binary, true) << "</Binary>\n";
555             infoXml << "   <UniqueID>" << water::int64(uniqueId)       << "</UniqueID>\n";
556             break;
557         case PLUGIN_VST3:
558             infoXml << "   <Binary>"   << xmlSafeString(binary, true) << "</Binary>\n";
559             infoXml << "   <Label>"    << xmlSafeString(label, true)  << "</Label>\n";
560             break;
561         case PLUGIN_AU:
562             infoXml << "   <Identifier>" << xmlSafeString(label, true) << "</Identifier>\n";
563             break;
564         case PLUGIN_DLS:
565         case PLUGIN_GIG:
566         case PLUGIN_SF2:
567             infoXml << "   <Filename>"   << xmlSafeString(binary, true) << "</Filename>\n";
568             infoXml << "   <Label>"      << xmlSafeString(label, true)  << "</Label>\n";
569             break;
570         case PLUGIN_SFZ:
571             infoXml << "   <Filename>"   << xmlSafeString(binary, true) << "</Filename>\n";
572             break;
573         case PLUGIN_JACK:
574             infoXml << "   <Filename>"   << xmlSafeString(binary, true) << "</Filename>\n";
575             infoXml << "   <Setup>"      << xmlSafeString(label, true)  << "</Setup>\n";
576             break;
577         }
578 
579         infoXml << "  </Info>\n\n";
580 
581         content << infoXml;
582     }
583 
584     content << "  <Data>\n";
585 
586 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
587     {
588         MemoryOutputStream dataXml;
589 
590         dataXml << "   <Active>" << (active ? "Yes" : "No") << "</Active>\n";
591 
592         if (carla_isNotEqual(dryWet, 1.0f))
593             dataXml << "   <DryWet>"        << String(dryWet, 7)       << "</DryWet>\n";
594         if (carla_isNotEqual(volume, 1.0f))
595             dataXml << "   <Volume>"        << String(volume, 7)       << "</Volume>\n";
596         if (carla_isNotEqual(balanceLeft, -1.0f))
597             dataXml << "   <Balance-Left>"  << String(balanceLeft, 7)  << "</Balance-Left>\n";
598         if (carla_isNotEqual(balanceRight, 1.0f))
599             dataXml << "   <Balance-Right>" << String(balanceRight, 7) << "</Balance-Right>\n";
600         if (carla_isNotEqual(panning, 0.0f))
601             dataXml << "   <Panning>"       << String(panning, 7)      << "</Panning>\n";
602 
603         if (ctrlChannel < 0)
604             dataXml << "   <ControlChannel>N</ControlChannel>\n";
605         else
606             dataXml << "   <ControlChannel>" << int(ctrlChannel+1) << "</ControlChannel>\n";
607 
608         dataXml << "   <Options>0x" << String::toHexString(static_cast<int>(options)) << "</Options>\n";
609 
610         content << dataXml;
611     }
612 #endif
613 
614     for (ParameterItenerator it = parameters.begin2(); it.valid(); it.next())
615     {
616         Parameter* const stateParameter(it.getValue(nullptr));
617         CARLA_SAFE_ASSERT_CONTINUE(stateParameter != nullptr);
618 
619         MemoryOutputStream parameterXml;
620 
621         parameterXml << "\n";
622         parameterXml << "   <Parameter>\n";
623         parameterXml << "    <Index>" << String(stateParameter->index)             << "</Index>\n";
624         parameterXml << "    <Name>"  << xmlSafeString(stateParameter->name, true) << "</Name>\n";
625 
626         if (stateParameter->symbol != nullptr && stateParameter->symbol[0] != '\0')
627             parameterXml << "    <Symbol>" << xmlSafeString(stateParameter->symbol, true) << "</Symbol>\n";
628 
629 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
630         if (stateParameter->mappedControlIndex > CONTROL_INDEX_NONE && stateParameter->mappedControlIndex <= CONTROL_INDEX_MAX_ALLOWED)
631         {
632             parameterXml << "    <MidiChannel>"   << stateParameter->midiChannel+1 << "</MidiChannel>\n";
633             parameterXml << "    <MappedControlIndex>" << stateParameter->mappedControlIndex << "</MappedControlIndex>\n";
634 
635             if (stateParameter->mappedRangeValid)
636             {
637                 parameterXml << "    <MappedMinimum>" << String(stateParameter->mappedMinimum, 15) << "</MappedMinimum>\n";
638                 parameterXml << "    <MappedMaximum>" << String(stateParameter->mappedMaximum, 15) << "</MappedMaximum>\n";
639             }
640 
641             // backwards compatibility for older carla versions
642             if (stateParameter->mappedControlIndex > 0 && stateParameter->mappedControlIndex < MAX_MIDI_CONTROL)
643                 parameterXml << "    <MidiCC>" << stateParameter->mappedControlIndex << "</MidiCC>\n";
644         }
645 #endif
646 
647         if (! stateParameter->dummy)
648             parameterXml << "    <Value>" << String(stateParameter->value, 15) << "</Value>\n";
649 
650         parameterXml << "   </Parameter>\n";
651 
652         content << parameterXml;
653     }
654 
655     if (currentProgramIndex >= 0 && currentProgramName != nullptr && currentProgramName[0] != '\0')
656     {
657         // ignore 'default' program
658         if (currentProgramIndex > 0 || ! String(currentProgramName).equalsIgnoreCase("default"))
659         {
660             MemoryOutputStream programXml;
661 
662             programXml << "\n";
663             programXml << "   <CurrentProgramIndex>" << currentProgramIndex+1                   << "</CurrentProgramIndex>\n";
664             programXml << "   <CurrentProgramName>"  << xmlSafeString(currentProgramName, true) << "</CurrentProgramName>\n";
665 
666             content << programXml;
667         }
668     }
669 
670     if (currentMidiBank >= 0 && currentMidiProgram >= 0)
671     {
672         MemoryOutputStream midiProgramXml;
673 
674         midiProgramXml << "\n";
675         midiProgramXml << "   <CurrentMidiBank>"    << currentMidiBank+1    << "</CurrentMidiBank>\n";
676         midiProgramXml << "   <CurrentMidiProgram>" << currentMidiProgram+1 << "</CurrentMidiProgram>\n";
677 
678         content << midiProgramXml;
679     }
680 
681     for (CustomDataItenerator it = customData.begin2(); it.valid(); it.next())
682     {
683         CustomData* const stateCustomData(it.getValue(nullptr));
684         CARLA_SAFE_ASSERT_CONTINUE(stateCustomData != nullptr);
685         CARLA_SAFE_ASSERT_CONTINUE(stateCustomData->isValid());
686 
687         MemoryOutputStream customDataXml;
688 
689         customDataXml << "\n";
690         customDataXml << "   <CustomData>\n";
691         customDataXml << "    <Type>" << xmlSafeString(stateCustomData->type, true) << "</Type>\n";
692         customDataXml << "    <Key>"  << xmlSafeString(stateCustomData->key, true)  << "</Key>\n";
693 
694         if (std::strcmp(stateCustomData->type, CUSTOM_DATA_TYPE_CHUNK) == 0 || std::strlen(stateCustomData->value) >= 128)
695         {
696             customDataXml << "    <Value>\n";
697             customDataXml << xmlSafeStringFast(stateCustomData->value, true);
698             customDataXml << "\n    </Value>\n";
699         }
700         else
701         {
702             customDataXml << "    <Value>";
703             customDataXml << xmlSafeStringFast(stateCustomData->value, true);
704             customDataXml << "</Value>\n";
705         }
706 
707         customDataXml << "   </CustomData>\n";
708 
709         content << customDataXml;
710     }
711 
712     if (chunk != nullptr && chunk[0] != '\0')
713     {
714         MemoryOutputStream chunkXml, chunkSplt;
715         getNewLineSplittedString(chunkSplt, chunk);
716 
717         chunkXml << "\n   <Chunk>\n";
718         chunkXml << chunkSplt;
719         chunkXml << "\n   </Chunk>\n";
720 
721         content << chunkXml;
722     }
723 
724     content << "  </Data>\n";
725 }
726 
727 // -----------------------------------------------------------------------
728 
729 CARLA_BACKEND_END_NAMESPACE
730