3 * Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com>
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.
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.
15 * For a full copy of the GNU General Public License see the doc/GPL.txt file.
18 #include "CarlaStateUtils.hpp"
20 #include "CarlaBackendUtils.hpp"
21 #include "CarlaMathUtils.hpp"
22 #include "CarlaMIDI.h"
24 #include "water/streams/MemoryOutputStream.h"
25 #include "water/xml/XmlElement.h"
29 using water::MemoryOutputStream
;
31 using water::XmlElement
;
33 CARLA_BACKEND_START_NAMESPACE
35 // -----------------------------------------------------------------------
36 // getNewLineSplittedString
38 static void getNewLineSplittedString(MemoryOutputStream
& stream
, const String
& string
)
40 static const int kLineWidth
= 120;
43 const int length
= string
.length();
44 const char* const raw
= string
.toUTF8();
46 stream
.preallocate(static_cast<std::size_t>(length
+ length
/kLineWidth
+ 3));
48 for (; i
+kLineWidth
< length
; i
+= kLineWidth
)
50 stream
.write(raw
+i
, kLineWidth
);
51 stream
.writeByte('\n');
57 // -----------------------------------------------------------------------
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 */
63 static std::string
replaceStdString(const std::string
& original
, const std::string
& before
, const std::string
& after
)
65 std::string::const_iterator current
= original
.begin(), end
= original
.end(), next
;
68 for (; (next
= std::search(current
, end
, before
.begin(), before
.end())) != end
;)
70 retval
.append(current
, next
);
72 current
= next
+ static_cast<ssize_t
>(before
.size());
74 retval
.append(current
, next
);
78 static std::string
xmlSafeStringFast(const char* const cstring
, const bool toXml
)
80 std::string
string(cstring
);
84 string
= replaceStdString(string
, "&","&");
85 string
= replaceStdString(string
, "<","<");
86 string
= replaceStdString(string
, ">",">");
87 string
= replaceStdString(string
, "'","'");
88 string
= replaceStdString(string
, "\"",""");
92 string
= replaceStdString(string
, "<","<");
93 string
= replaceStdString(string
, ">",">");
94 string
= replaceStdString(string
, "'","'");
95 string
= replaceStdString(string
, ""","\"");
96 string
= replaceStdString(string
, "&","&");
102 // -----------------------------------------------------------------------
103 // xmlSafeStringCharDup
106 static const char* xmlSafeStringCharDup(const char* const cstring, const bool toXml)
108 return carla_strdup(xmlSafeString(cstring, toXml).toRawUTF8());
112 static const char* xmlSafeStringCharDup(const String
& string
, const bool toXml
)
114 return carla_strdup(xmlSafeString(string
, toXml
).toRawUTF8());
117 // -----------------------------------------------------------------------
120 CarlaStateSave::Parameter::Parameter() noexcept
125 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
127 mappedControlIndex(CONTROL_INDEX_NONE
),
129 mappedRangeValid(false),
131 mappedMaximum(1.0f
) {}
136 CarlaStateSave::Parameter::~Parameter() noexcept
143 if (symbol
!= nullptr)
150 // -----------------------------------------------------------------------
153 CarlaStateSave::CustomData::CustomData() noexcept
158 CarlaStateSave::CustomData::~CustomData() noexcept
170 if (value
!= nullptr)
177 bool CarlaStateSave::CustomData::isValid() const noexcept
179 if (type
== nullptr || type
[0] == '\0') return false;
180 if (key
== nullptr || key
[0] == '\0') return false;
181 if (value
== nullptr) return false;
185 // -----------------------------------------------------------------------
188 CarlaStateSave::CarlaStateSave() noexcept
194 options(PLUGIN_OPTIONS_NULL
),
196 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
205 currentProgramIndex(-1),
206 currentProgramName(nullptr),
208 currentMidiProgram(-1),
213 CarlaStateSave::~CarlaStateSave() noexcept
218 void CarlaStateSave::clear() noexcept
230 if (label
!= nullptr)
235 if (binary
!= nullptr)
240 if (currentProgramName
!= nullptr)
242 delete[] currentProgramName
;
243 currentProgramName
= nullptr;
245 if (chunk
!= nullptr)
252 options
= PLUGIN_OPTIONS_NULL
;
254 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
264 currentProgramIndex
= -1;
265 currentMidiBank
= -1;
266 currentMidiProgram
= -1;
268 for (ParameterItenerator it
= parameters
.begin2(); it
.valid(); it
.next())
270 Parameter
* const stateParameter(it
.getValue(nullptr));
271 delete stateParameter
;
274 for (CustomDataItenerator it
= customData
.begin2(); it
.valid(); it
.next())
276 CustomData
* const stateCustomData(it
.getValue(nullptr));
277 delete stateCustomData
;
284 // -----------------------------------------------------------------------
285 // fillFromXmlElement
287 bool CarlaStateSave::fillFromXmlElement(const XmlElement
* const xmlElement
)
289 CARLA_SAFE_ASSERT_RETURN(xmlElement
!= nullptr, false);
293 for (XmlElement
* elem
= xmlElement
->getFirstChildElement(); elem
!= nullptr; elem
= elem
->getNextElement())
295 const String
& tagName(elem
->getTagName());
297 // ---------------------------------------------------------------
300 if (tagName
== "Info")
302 for (XmlElement
* xmlInfo
= elem
->getFirstChildElement(); xmlInfo
!= nullptr; xmlInfo
= xmlInfo
->getNextElement())
304 const String
& tag(xmlInfo
->getTagName());
305 const String
text(xmlInfo
->getAllSubText().trim());
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
== "Bundle" || tag
== "Filename")
314 binary
= xmlSafeStringCharDup(text
, false);
315 else if (tag
== "UniqueID")
316 uniqueId
= text
.getLargeIntValue();
320 // ---------------------------------------------------------------
323 else if (tagName
== "Data")
325 for (XmlElement
* xmlData
= elem
->getFirstChildElement(); xmlData
!= nullptr; xmlData
= xmlData
->getNextElement())
327 const String
& tag(xmlData
->getTagName());
328 const String
text(xmlData
->getAllSubText().trim());
330 // -------------------------------------------------------
333 /**/ if (tag
== "Options")
335 const int value(text
.getHexValue32());
337 options
= static_cast<uint
>(value
);
339 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
340 else if (tag
== "Active")
342 active
= (text
== "Yes");
344 else if (tag
== "DryWet")
346 dryWet
= carla_fixedValue(0.0f
, 1.0f
, text
.getFloatValue());
348 else if (tag
== "Volume")
350 volume
= carla_fixedValue(0.0f
, 1.27f
, text
.getFloatValue());
352 else if (tag
== "Balance-Left")
354 balanceLeft
= carla_fixedValue(-1.0f
, 1.0f
, text
.getFloatValue());
356 else if (tag
== "Balance-Right")
358 balanceRight
= carla_fixedValue(-1.0f
, 1.0f
, text
.getFloatValue());
360 else if (tag
== "Panning")
362 panning
= carla_fixedValue(-1.0f
, 1.0f
, text
.getFloatValue());
364 else if (tag
== "ControlChannel")
366 if (! text
.startsWithIgnoreCase("n"))
368 const int value(text
.getIntValue());
369 if (value
>= 1 && value
<= MAX_MIDI_CHANNELS
)
370 ctrlChannel
= static_cast<int8_t>(value
-1);
375 // -------------------------------------------------------
378 else if (tag
== "CurrentProgramIndex")
380 const int value(text
.getIntValue());
382 currentProgramIndex
= value
-1;
384 else if (tag
== "CurrentProgramName")
386 currentProgramName
= xmlSafeStringCharDup(text
, false);
389 // -------------------------------------------------------
390 // Midi Program (current)
392 else if (tag
== "CurrentMidiBank")
394 const int value(text
.getIntValue());
396 currentMidiBank
= value
-1;
398 else if (tag
== "CurrentMidiProgram")
400 const int value(text
.getIntValue());
402 currentMidiProgram
= value
-1;
405 // -------------------------------------------------------
408 else if (tag
== "Parameter")
410 Parameter
* const stateParameter(new Parameter());
411 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
412 bool hasMappedMinimum
= false, hasMappedMaximum
= false;
415 for (XmlElement
* xmlSubData
= xmlData
->getFirstChildElement(); xmlSubData
!= nullptr; xmlSubData
= xmlSubData
->getNextElement())
417 const String
& pTag(xmlSubData
->getTagName());
418 const String
pText(xmlSubData
->getAllSubText().trim());
420 /**/ if (pTag
== "Index")
422 const int index(pText
.getIntValue());
424 stateParameter
->index
= index
;
426 else if (pTag
== "Name")
428 stateParameter
->name
= xmlSafeStringCharDup(pText
, false);
430 else if (pTag
== "Symbol" || pTag
== "Identifier")
432 stateParameter
->symbol
= xmlSafeStringCharDup(pText
, false);
434 else if (pTag
== "Value")
436 stateParameter
->dummy
= false;
437 stateParameter
->value
= pText
.getFloatValue();
439 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
440 else if (pTag
== "MidiChannel")
442 const int channel(pText
.getIntValue());
443 if (channel
>= 1 && channel
<= MAX_MIDI_CHANNELS
)
444 stateParameter
->midiChannel
= static_cast<uint8_t>(channel
-1);
446 else if (pTag
== "MidiCC")
448 const int cc(pText
.getIntValue());
449 if (cc
> 0 && cc
< MAX_MIDI_CONTROL
)
450 stateParameter
->mappedControlIndex
= static_cast<int16_t>(cc
);
452 else if (pTag
== "MappedControlIndex")
454 const int ctrl(pText
.getIntValue());
455 if (ctrl
> CONTROL_INDEX_NONE
&& ctrl
<= CONTROL_INDEX_MAX_ALLOWED
)
456 if (ctrl
!= CONTROL_INDEX_MIDI_LEARN
)
457 stateParameter
->mappedControlIndex
= static_cast<int16_t>(ctrl
);
459 else if (pTag
== "MappedMinimum")
461 hasMappedMinimum
= true;
462 stateParameter
->mappedMinimum
= pText
.getFloatValue();
464 else if (pTag
== "MappedMaximum")
466 hasMappedMaximum
= true;
467 stateParameter
->mappedMaximum
= pText
.getFloatValue();
472 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
473 if (hasMappedMinimum
&& hasMappedMaximum
)
474 stateParameter
->mappedRangeValid
= true;
477 parameters
.append(stateParameter
);
480 // -------------------------------------------------------
483 else if (tag
== "CustomData")
485 CustomData
* const stateCustomData(new CustomData());
488 for (XmlElement
* xmlSubData
= xmlData
->getFirstChildElement(); xmlSubData
!= nullptr; xmlSubData
= xmlSubData
->getNextElement())
490 const String
& cTag(xmlSubData
->getTagName());
495 stateCustomData
->type
= xmlSafeStringCharDup(xmlSubData
->getAllSubText().trim(), false);
499 if (stateCustomData
->type
== nullptr || stateCustomData
->type
[0] == '\0')
501 carla_stderr("Reading CustomData type failed");
502 delete stateCustomData
;
506 // now fill in key and value, knowing what the type is
507 for (XmlElement
* xmlSubData
= xmlData
->getFirstChildElement(); xmlSubData
!= nullptr; xmlSubData
= xmlSubData
->getNextElement())
509 const String
& cTag(xmlSubData
->getTagName());
510 String
cText(xmlSubData
->getAllSubText());
512 /**/ if (cTag
== "Key")
514 stateCustomData
->key
= xmlSafeStringCharDup(cText
.trim(), false);
516 else if (cTag
== "Value")
518 // save operation adds a newline and newline+space around the string in some cases
519 const int len
= cText
.length();
520 if (std::strcmp(stateCustomData
->type
, CUSTOM_DATA_TYPE_CHUNK
) == 0 || len
>= 128+6)
522 CARLA_SAFE_ASSERT_CONTINUE(len
>= 6);
523 cText
= cText
.substring(1, len
- 5);
526 stateCustomData
->value
= xmlSafeStringCharDup(cText
, false);
530 if (stateCustomData
->isValid())
532 customData
.append(stateCustomData
);
536 carla_stderr("Reading CustomData property failed, missing data");
537 delete stateCustomData
;
541 // -------------------------------------------------------
544 else if (tag
== "Chunk")
546 chunk
= carla_strdup(text
.toRawUTF8());
555 // -----------------------------------------------------------------------
556 // fillXmlStringFromStateSave
558 void CarlaStateSave::dumpToMemoryStream(MemoryOutputStream
& content
) const
560 const PluginType pluginType
= getPluginTypeFromString(type
);
563 MemoryOutputStream infoXml
;
565 infoXml
<< " <Info>\n";
566 infoXml
<< " <Type>" << String(type
!= nullptr ? type
: "") << "</Type>\n";
567 infoXml
<< " <Name>" << xmlSafeString(name
, true) << "</Name>\n";
573 case PLUGIN_INTERNAL
:
574 infoXml
<< " <Label>" << xmlSafeString(label
, true) << "</Label>\n";
577 infoXml
<< " <Binary>" << xmlSafeString(binary
, true) << "</Binary>\n";
578 infoXml
<< " <Label>" << xmlSafeString(label
, true) << "</Label>\n";
579 infoXml
<< " <UniqueID>" << water::int64(uniqueId
) << "</UniqueID>\n";
582 infoXml
<< " <Binary>" << xmlSafeString(binary
, true) << "</Binary>\n";
583 infoXml
<< " <Label>" << xmlSafeString(label
, true) << "</Label>\n";
586 infoXml
<< " <URI>" << xmlSafeString(label
, true) << "</URI>\n";
589 infoXml
<< " <Binary>" << xmlSafeString(binary
, true) << "</Binary>\n";
590 infoXml
<< " <UniqueID>" << water::int64(uniqueId
) << "</UniqueID>\n";
593 infoXml
<< " <Binary>" << xmlSafeString(binary
, true) << "</Binary>\n";
594 infoXml
<< " <Label>" << xmlSafeString(label
, true) << "</Label>\n";
597 infoXml
<< " <Bundle>" << xmlSafeString(binary
, true) << "</Bundle>\n";
598 infoXml
<< " <Identifier>" << xmlSafeString(label
, true) << "</Identifier>\n";
601 infoXml
<< " <Binary>" << xmlSafeString(binary
, true) << "</Binary>\n";
602 infoXml
<< " <Identifier>" << xmlSafeString(label
, true) << "</Identifier>\n";
608 infoXml
<< " <Filename>" << xmlSafeString(binary
, true) << "</Filename>\n";
609 infoXml
<< " <Label>" << xmlSafeString(label
, true) << "</Label>\n";
612 infoXml
<< " <Filename>" << xmlSafeString(binary
, true) << "</Filename>\n";
615 infoXml
<< " <Filename>" << xmlSafeString(binary
, true) << "</Filename>\n";
616 infoXml
<< " <Setup>" << xmlSafeString(label
, true) << "</Setup>\n";
618 case PLUGIN_TYPE_COUNT
:
622 infoXml
<< " </Info>\n\n";
627 content
<< " <Data>\n";
629 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
631 MemoryOutputStream dataXml
;
633 dataXml
<< " <Active>" << (active
? "Yes" : "No") << "</Active>\n";
635 if (carla_isNotEqual(dryWet
, 1.0f
))
636 dataXml
<< " <DryWet>" << String(dryWet
, 7) << "</DryWet>\n";
637 if (carla_isNotEqual(volume
, 1.0f
))
638 dataXml
<< " <Volume>" << String(volume
, 7) << "</Volume>\n";
639 if (carla_isNotEqual(balanceLeft
, -1.0f
))
640 dataXml
<< " <Balance-Left>" << String(balanceLeft
, 7) << "</Balance-Left>\n";
641 if (carla_isNotEqual(balanceRight
, 1.0f
))
642 dataXml
<< " <Balance-Right>" << String(balanceRight
, 7) << "</Balance-Right>\n";
643 if (carla_isNotEqual(panning
, 0.0f
))
644 dataXml
<< " <Panning>" << String(panning
, 7) << "</Panning>\n";
647 dataXml
<< " <ControlChannel>N</ControlChannel>\n";
649 dataXml
<< " <ControlChannel>" << int(ctrlChannel
+1) << "</ControlChannel>\n";
651 dataXml
<< " <Options>0x" << String::toHexString(static_cast<int>(options
)) << "</Options>\n";
657 for (ParameterItenerator it
= parameters
.begin2(); it
.valid(); it
.next())
659 Parameter
* const stateParameter(it
.getValue(nullptr));
660 CARLA_SAFE_ASSERT_CONTINUE(stateParameter
!= nullptr);
662 MemoryOutputStream parameterXml
;
664 parameterXml
<< "\n";
665 parameterXml
<< " <Parameter>\n";
666 parameterXml
<< " <Index>" << String(stateParameter
->index
) << "</Index>\n";
667 parameterXml
<< " <Name>" << xmlSafeString(stateParameter
->name
, true) << "</Name>\n";
669 if (stateParameter
->symbol
!= nullptr && stateParameter
->symbol
[0] != '\0')
671 if (pluginType
== PLUGIN_CLAP
)
672 parameterXml
<< " <Identifier>" << xmlSafeString(stateParameter
->symbol
, true) << "</Identifier>\n";
674 parameterXml
<< " <Symbol>" << xmlSafeString(stateParameter
->symbol
, true) << "</Symbol>\n";
677 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
678 if (stateParameter
->mappedControlIndex
> CONTROL_INDEX_NONE
&& stateParameter
->mappedControlIndex
<= CONTROL_INDEX_MAX_ALLOWED
)
680 parameterXml
<< " <MidiChannel>" << stateParameter
->midiChannel
+1 << "</MidiChannel>\n";
681 parameterXml
<< " <MappedControlIndex>" << stateParameter
->mappedControlIndex
<< "</MappedControlIndex>\n";
683 if (stateParameter
->mappedRangeValid
)
685 parameterXml
<< " <MappedMinimum>" << String(stateParameter
->mappedMinimum
, 15) << "</MappedMinimum>\n";
686 parameterXml
<< " <MappedMaximum>" << String(stateParameter
->mappedMaximum
, 15) << "</MappedMaximum>\n";
689 // backwards compatibility for older carla versions
690 if (stateParameter
->mappedControlIndex
> 0 && stateParameter
->mappedControlIndex
< MAX_MIDI_CONTROL
)
691 parameterXml
<< " <MidiCC>" << stateParameter
->mappedControlIndex
<< "</MidiCC>\n";
695 if (! stateParameter
->dummy
)
696 parameterXml
<< " <Value>" << String(stateParameter
->value
, 15) << "</Value>\n";
698 parameterXml
<< " </Parameter>\n";
700 content
<< parameterXml
;
703 if (currentProgramIndex
>= 0 && currentProgramName
!= nullptr && currentProgramName
[0] != '\0')
705 // ignore 'default' program
706 if (currentProgramIndex
> 0 || ! String(currentProgramName
).equalsIgnoreCase("default"))
708 MemoryOutputStream programXml
;
711 programXml
<< " <CurrentProgramIndex>" << currentProgramIndex
+1 << "</CurrentProgramIndex>\n";
712 programXml
<< " <CurrentProgramName>" << xmlSafeString(currentProgramName
, true) << "</CurrentProgramName>\n";
714 content
<< programXml
;
718 if (currentMidiBank
>= 0 && currentMidiProgram
>= 0)
720 MemoryOutputStream midiProgramXml
;
722 midiProgramXml
<< "\n";
723 midiProgramXml
<< " <CurrentMidiBank>" << currentMidiBank
+1 << "</CurrentMidiBank>\n";
724 midiProgramXml
<< " <CurrentMidiProgram>" << currentMidiProgram
+1 << "</CurrentMidiProgram>\n";
726 content
<< midiProgramXml
;
729 for (CustomDataItenerator it
= customData
.begin2(); it
.valid(); it
.next())
731 CustomData
* const stateCustomData(it
.getValue(nullptr));
732 CARLA_SAFE_ASSERT_CONTINUE(stateCustomData
!= nullptr);
733 CARLA_SAFE_ASSERT_CONTINUE(stateCustomData
->isValid());
735 MemoryOutputStream customDataXml
;
737 customDataXml
<< "\n";
738 customDataXml
<< " <CustomData>\n";
739 customDataXml
<< " <Type>" << xmlSafeString(stateCustomData
->type
, true) << "</Type>\n";
740 customDataXml
<< " <Key>" << xmlSafeString(stateCustomData
->key
, true) << "</Key>\n";
742 if (std::strcmp(stateCustomData
->type
, CUSTOM_DATA_TYPE_CHUNK
) == 0 || std::strlen(stateCustomData
->value
) >= 128)
744 customDataXml
<< " <Value>\n";
745 customDataXml
<< xmlSafeStringFast(stateCustomData
->value
, true);
746 customDataXml
<< "\n </Value>\n";
750 customDataXml
<< " <Value>";
751 customDataXml
<< xmlSafeStringFast(stateCustomData
->value
, true);
752 customDataXml
<< "</Value>\n";
755 customDataXml
<< " </CustomData>\n";
757 content
<< customDataXml
;
760 if (chunk
!= nullptr && chunk
[0] != '\0')
762 MemoryOutputStream chunkXml
, chunkSplt
;
763 getNewLineSplittedString(chunkSplt
, chunk
);
765 chunkXml
<< "\n <Chunk>\n";
766 chunkXml
<< chunkSplt
;
767 chunkXml
<< "\n </Chunk>\n";
772 content
<< " </Data>\n";
775 // -----------------------------------------------------------------------
777 CARLA_BACKEND_END_NAMESPACE