Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

994 lines
38KB

  1. /*
  2. * Carla State utils
  3. * Copyright (C) 2012-2014 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. #include "CarlaStateUtils.hpp"
  18. #include "CarlaBackendUtils.hpp"
  19. #include "CarlaMathUtils.hpp"
  20. #include "CarlaMIDI.h"
  21. #ifdef HAVE_JUCE_LATER
  22. # include "juce_core.h"
  23. using juce::String;
  24. using juce::XmlElement;
  25. #else
  26. # include <QtCore/QString>
  27. # include <QtXml/QDomNode>
  28. #endif
  29. CARLA_BACKEND_START_NAMESPACE
  30. // -----------------------------------------------------------------------
  31. // xmlSafeString
  32. #ifdef HAVE_JUCE_LATER
  33. static String xmlSafeString(const String& string, const bool toXml)
  34. {
  35. String newString(string);
  36. if (toXml)
  37. return newString.replace("&","&amp;").replace("<","&lt;").replace(">","&gt;").replace("'","&apos;").replace("\"","&quot;");
  38. else
  39. return newString.replace("&lt;","<").replace("&gt;",">").replace("&apos;","'").replace("&quot;","\"").replace("&amp;","&");
  40. }
  41. #else
  42. static QString xmlSafeString(const QString& string, const bool toXml)
  43. {
  44. QString newString(string);
  45. if (toXml)
  46. return newString.replace("&","&amp;").replace("<","&lt;").replace(">","&gt;").replace("'","&apos;").replace("\"","&quot;");
  47. else
  48. return newString.replace("&lt;","<").replace("&gt;",">").replace("&apos;","'").replace("&quot;","\"").replace("&amp;","&");
  49. }
  50. #endif
  51. // -----------------------------------------------------------------------
  52. // xmlSafeStringCharDup
  53. #ifdef HAVE_JUCE_LATER
  54. static const char* xmlSafeStringCharDup(const String& string, const bool toXml)
  55. {
  56. return carla_strdup(xmlSafeString(string, toXml).toRawUTF8());
  57. }
  58. #else
  59. static const char* xmlSafeStringCharDup(const QString& string, const bool toXml)
  60. {
  61. return carla_strdup(xmlSafeString(string, toXml).toUtf8().constData());
  62. }
  63. #endif
  64. // -----------------------------------------------------------------------
  65. // StateParameter
  66. StateParameter::StateParameter() noexcept
  67. : isInput(true),
  68. index(-1),
  69. name(nullptr),
  70. symbol(nullptr),
  71. value(0.0f),
  72. midiChannel(0),
  73. midiCC(-1) {}
  74. StateParameter::~StateParameter() noexcept
  75. {
  76. if (name != nullptr)
  77. {
  78. delete[] name;
  79. name = nullptr;
  80. }
  81. if (symbol != nullptr)
  82. {
  83. delete[] symbol;
  84. symbol = nullptr;
  85. }
  86. }
  87. // -----------------------------------------------------------------------
  88. // StateCustomData
  89. StateCustomData::StateCustomData() noexcept
  90. : type(nullptr),
  91. key(nullptr),
  92. value(nullptr) {}
  93. StateCustomData::~StateCustomData() noexcept
  94. {
  95. if (type != nullptr)
  96. {
  97. delete[] type;
  98. type = nullptr;
  99. }
  100. if (key != nullptr)
  101. {
  102. delete[] key;
  103. key = nullptr;
  104. }
  105. if (value != nullptr)
  106. {
  107. delete[] value;
  108. value = nullptr;
  109. }
  110. }
  111. // -----------------------------------------------------------------------
  112. // StateSave
  113. StateSave::StateSave() noexcept
  114. : type(nullptr),
  115. name(nullptr),
  116. label(nullptr),
  117. binary(nullptr),
  118. uniqueId(0),
  119. active(false),
  120. dryWet(1.0f),
  121. volume(1.0f),
  122. balanceLeft(-1.0f),
  123. balanceRight(1.0f),
  124. panning(0.0f),
  125. ctrlChannel(-1),
  126. currentProgramIndex(-1),
  127. currentProgramName(nullptr),
  128. currentMidiBank(-1),
  129. currentMidiProgram(-1),
  130. chunk(nullptr) {}
  131. StateSave::~StateSave() noexcept
  132. {
  133. clear();
  134. }
  135. void StateSave::clear() noexcept
  136. {
  137. if (type != nullptr)
  138. {
  139. delete[] type;
  140. type = nullptr;
  141. }
  142. if (name != nullptr)
  143. {
  144. delete[] name;
  145. name = nullptr;
  146. }
  147. if (label != nullptr)
  148. {
  149. delete[] label;
  150. label = nullptr;
  151. }
  152. if (binary != nullptr)
  153. {
  154. delete[] binary;
  155. binary = nullptr;
  156. }
  157. if (currentProgramName != nullptr)
  158. {
  159. delete[] currentProgramName;
  160. currentProgramName = nullptr;
  161. }
  162. if (chunk != nullptr)
  163. {
  164. delete[] chunk;
  165. chunk = nullptr;
  166. }
  167. uniqueId = 0;
  168. active = false;
  169. dryWet = 1.0f;
  170. volume = 1.0f;
  171. balanceLeft = -1.0f;
  172. balanceRight = 1.0f;
  173. panning = 0.0f;
  174. ctrlChannel = -1;
  175. currentProgramIndex = -1;
  176. currentMidiBank = -1;
  177. currentMidiProgram = -1;
  178. for (StateParameterItenerator it = parameters.begin(); it.valid(); it.next())
  179. {
  180. StateParameter* const stateParameter(it.getValue());
  181. delete stateParameter;
  182. }
  183. for (StateCustomDataItenerator it = customData.begin(); it.valid(); it.next())
  184. {
  185. StateCustomData* const stateCustomData(it.getValue());
  186. delete stateCustomData;
  187. }
  188. parameters.clear();
  189. customData.clear();
  190. }
  191. // -----------------------------------------------------------------------
  192. // fillFromXmlElement
  193. #ifdef HAVE_JUCE_LATER
  194. void StateSave::fillFromXmlElement(const XmlElement* const xmlElement)
  195. {
  196. clear();
  197. CARLA_SAFE_ASSERT_RETURN(xmlElement != nullptr,);
  198. for (XmlElement* elem = xmlElement->getFirstChildElement(); elem != nullptr; elem = elem->getNextElement())
  199. {
  200. String tagName(elem->getTagName());
  201. // ---------------------------------------------------------------
  202. // Info
  203. if (tagName.equalsIgnoreCase("info"))
  204. {
  205. for (XmlElement* xmlInfo = elem->getFirstChildElement(); xmlInfo != nullptr; xmlInfo = xmlInfo->getNextElement())
  206. {
  207. const String& tag(xmlInfo->getTagName());
  208. const String text(xmlInfo->getAllSubText().trim());
  209. if (tag.equalsIgnoreCase("type"))
  210. type = xmlSafeStringCharDup(text, false);
  211. else if (tag.equalsIgnoreCase("name"))
  212. name = xmlSafeStringCharDup(text, false);
  213. else if (tag.equalsIgnoreCase("label") || tag.equalsIgnoreCase("uri"))
  214. label = xmlSafeStringCharDup(text, false);
  215. else if (tag.equalsIgnoreCase("binary") || tag.equalsIgnoreCase("bundle") || tag.equalsIgnoreCase("filename"))
  216. binary = xmlSafeStringCharDup(text, false);
  217. else if (tag.equalsIgnoreCase("uniqueid"))
  218. uniqueId = text.getLargeIntValue();
  219. }
  220. }
  221. // ---------------------------------------------------------------
  222. // Data
  223. else if (tagName.equalsIgnoreCase("data"))
  224. {
  225. for (XmlElement* xmlData = elem->getFirstChildElement(); xmlData != nullptr; xmlData = xmlData->getNextElement())
  226. {
  227. const String& tag(xmlData->getTagName());
  228. const String text(xmlData->getAllSubText().trim());
  229. // -------------------------------------------------------
  230. // Internal Data
  231. if (tag.equalsIgnoreCase("active"))
  232. {
  233. active = (text.equalsIgnoreCase("yes") || text.equalsIgnoreCase("true"));
  234. }
  235. else if (tag.equalsIgnoreCase("drywet"))
  236. {
  237. dryWet = carla_fixValue(0.0f, 1.0f, text.getFloatValue());
  238. }
  239. else if (tag.equalsIgnoreCase("volume"))
  240. {
  241. volume = carla_fixValue(0.0f, 1.27f, text.getFloatValue());
  242. }
  243. else if (tag.equalsIgnoreCase("balanceleft") || tag.equalsIgnoreCase("balance-left"))
  244. {
  245. balanceLeft = carla_fixValue(-1.0f, 1.0f, text.getFloatValue());
  246. }
  247. else if (tag.equalsIgnoreCase("balanceright") || tag.equalsIgnoreCase("balance-right"))
  248. {
  249. balanceRight = carla_fixValue(-1.0f, 1.0f, text.getFloatValue());
  250. }
  251. else if (tag.equalsIgnoreCase("panning"))
  252. {
  253. panning = carla_fixValue(-1.0f, 1.0f, text.getFloatValue());
  254. }
  255. else if (tag.equalsIgnoreCase("controlchannel") || tag.equalsIgnoreCase("control-channel"))
  256. {
  257. const int value(text.getIntValue());
  258. if (value >= 1 && value <= MAX_MIDI_CHANNELS)
  259. ctrlChannel = static_cast<int8_t>(value-1);
  260. }
  261. // -------------------------------------------------------
  262. // Program (current)
  263. else if (tag.equalsIgnoreCase("currentprogramindex") || tag.equalsIgnoreCase("current-program-index"))
  264. {
  265. const int value(text.getIntValue());
  266. if (value >= 1)
  267. currentProgramIndex = value-1;
  268. }
  269. else if (tag.equalsIgnoreCase("currentprogramname") || tag.equalsIgnoreCase("current-program-name"))
  270. {
  271. currentProgramName = xmlSafeStringCharDup(text, false);
  272. }
  273. // -------------------------------------------------------
  274. // Midi Program (current)
  275. else if (tag.equalsIgnoreCase("currentmidibank") || tag.equalsIgnoreCase("current-midi-bank"))
  276. {
  277. const int value(text.getIntValue());
  278. if (value >= 1)
  279. currentMidiBank = value-1;
  280. }
  281. else if (tag.equalsIgnoreCase("currentmidiprogram") || tag.equalsIgnoreCase("current-midi-program"))
  282. {
  283. const int value(text.getIntValue());
  284. if (value >= 1)
  285. currentMidiProgram = value-1;
  286. }
  287. // -------------------------------------------------------
  288. // Parameters
  289. else if (tag.equalsIgnoreCase("parameter"))
  290. {
  291. StateParameter* const stateParameter(new StateParameter());
  292. for (XmlElement* xmlSubData = xmlData->getFirstChildElement(); xmlSubData != nullptr; xmlSubData = xmlSubData->getNextElement())
  293. {
  294. const String& pTag(xmlSubData->getTagName());
  295. const String pText(xmlSubData->getAllSubText().trim());
  296. if (pTag.equalsIgnoreCase("index"))
  297. {
  298. const int index(pText.getIntValue());
  299. if (index >= 0)
  300. stateParameter->index = index;
  301. }
  302. else if (pTag.equalsIgnoreCase("name"))
  303. {
  304. stateParameter->name = xmlSafeStringCharDup(pText, false);
  305. }
  306. else if (pTag.equalsIgnoreCase("symbol"))
  307. {
  308. stateParameter->symbol = xmlSafeStringCharDup(pText, false);
  309. }
  310. else if (pTag.equalsIgnoreCase("value"))
  311. {
  312. stateParameter->value = pText.getFloatValue();
  313. }
  314. else if (pTag.equalsIgnoreCase("midichannel") || pTag.equalsIgnoreCase("midi-channel"))
  315. {
  316. const int channel(pText.getIntValue());
  317. if (channel >= 1 && channel <= MAX_MIDI_CHANNELS)
  318. stateParameter->midiChannel = static_cast<uint8_t>(channel-1);
  319. }
  320. else if (pTag.equalsIgnoreCase("midicc") || pTag.equalsIgnoreCase("midi-cc"))
  321. {
  322. const int cc(pText.getIntValue());
  323. if (cc >= 1 && cc < 0x5F)
  324. stateParameter->midiCC = static_cast<int16_t>(cc);
  325. }
  326. }
  327. parameters.append(stateParameter);
  328. }
  329. // -------------------------------------------------------
  330. // Custom Data
  331. else if (tag.equalsIgnoreCase("customdata") || tag.equalsIgnoreCase("custom-data"))
  332. {
  333. StateCustomData* const stateCustomData(new StateCustomData());
  334. for (XmlElement* xmlSubData = xmlData->getFirstChildElement(); xmlSubData != nullptr; xmlSubData = xmlSubData->getNextElement())
  335. {
  336. const String& cTag(xmlSubData->getTagName());
  337. const String cText(xmlSubData->getAllSubText().trim());
  338. if (cTag.equalsIgnoreCase("type"))
  339. stateCustomData->type = xmlSafeStringCharDup(cText, false);
  340. else if (cTag.equalsIgnoreCase("key"))
  341. stateCustomData->key = xmlSafeStringCharDup(cText, false);
  342. else if (cTag.equalsIgnoreCase("value"))
  343. stateCustomData->value = xmlSafeStringCharDup(cText, false);
  344. }
  345. customData.append(stateCustomData);
  346. }
  347. // -------------------------------------------------------
  348. // Chunk
  349. else if (tag.equalsIgnoreCase("chunk"))
  350. {
  351. chunk = xmlSafeStringCharDup(text, false);
  352. }
  353. }
  354. }
  355. }
  356. }
  357. #else
  358. void StateSave::fillFromXmlNode(const QDomNode& xmlNode)
  359. {
  360. clear();
  361. CARLA_SAFE_ASSERT_RETURN(! xmlNode.isNull(),);
  362. for (QDomNode node = xmlNode.firstChild(); ! node.isNull(); node = node.nextSibling())
  363. {
  364. QString tagName(node.toElement().tagName());
  365. // ---------------------------------------------------------------
  366. // Info
  367. if (tagName.compare("info", Qt::CaseInsensitive) == 0)
  368. {
  369. for (QDomNode xmlInfo = node.toElement().firstChild(); ! xmlInfo.isNull(); xmlInfo = xmlInfo.nextSibling())
  370. {
  371. const QString tag(xmlInfo.toElement().tagName());
  372. const QString text(xmlInfo.toElement().text().trimmed());
  373. if (tag.compare("type", Qt::CaseInsensitive) == 0)
  374. {
  375. type = xmlSafeStringCharDup(text, false);
  376. }
  377. else if (tag.compare("name", Qt::CaseInsensitive) == 0)
  378. {
  379. name = xmlSafeStringCharDup(text, false);
  380. }
  381. else if (tag.compare("label", Qt::CaseInsensitive) == 0 || tag.compare("uri", Qt::CaseInsensitive) == 0)
  382. {
  383. label = xmlSafeStringCharDup(text, false);
  384. }
  385. else if (tag.compare("binary", Qt::CaseInsensitive) == 0 || tag.compare("bundle", Qt::CaseInsensitive) == 0 || tag.compare("filename", Qt::CaseInsensitive) == 0)
  386. {
  387. binary = xmlSafeStringCharDup(text, false);
  388. }
  389. else if (tag.compare("uniqueid", Qt::CaseInsensitive) == 0)
  390. {
  391. bool ok;
  392. const qlonglong uniqueIdTry(text.toLongLong(&ok));
  393. if (ok) uniqueId = static_cast<int64_t>(uniqueIdTry);
  394. }
  395. }
  396. }
  397. // ---------------------------------------------------------------
  398. // Data
  399. else if (tagName.compare("data", Qt::CaseInsensitive) == 0)
  400. {
  401. for (QDomNode xmlData = node.toElement().firstChild(); ! xmlData.isNull(); xmlData = xmlData.nextSibling())
  402. {
  403. const QString tag(xmlData.toElement().tagName());
  404. const QString text(xmlData.toElement().text().trimmed());
  405. // -------------------------------------------------------
  406. // Internal Data
  407. if (tag.compare("active", Qt::CaseInsensitive) == 0)
  408. {
  409. active = (text.compare("yes", Qt::CaseInsensitive) == 0 || text.compare("true", Qt::CaseInsensitive) == 0);
  410. }
  411. else if (tag.compare("drywet", Qt::CaseInsensitive) == 0)
  412. {
  413. bool ok;
  414. const float value(text.toFloat(&ok));
  415. if (ok) dryWet = carla_fixValue(0.0f, 1.0f, value);
  416. }
  417. else if (tag.compare("volume", Qt::CaseInsensitive) == 0)
  418. {
  419. bool ok;
  420. const float value(text.toFloat(&ok));
  421. if (ok) volume = carla_fixValue(0.0f, 1.27f, value);
  422. }
  423. else if (tag.compare("balanceleft", Qt::CaseInsensitive) == 0 || tag.compare("balance-left", Qt::CaseInsensitive) == 0)
  424. {
  425. bool ok;
  426. const float value(text.toFloat(&ok));
  427. if (ok) balanceLeft = carla_fixValue(-1.0f, 1.0f, value);
  428. }
  429. else if (tag.compare("balanceright", Qt::CaseInsensitive) == 0 || tag.compare("balance-right", Qt::CaseInsensitive) == 0)
  430. {
  431. bool ok;
  432. const float value(text.toFloat(&ok));
  433. if (ok) balanceRight = carla_fixValue(-1.0f, 1.0f, value);
  434. }
  435. else if (tag.compare("panning", Qt::CaseInsensitive) == 0)
  436. {
  437. bool ok;
  438. const float value(text.toFloat(&ok));
  439. if (ok) panning = carla_fixValue(-1.0f, 1.0f, value);
  440. }
  441. else if (tag.compare("controlchannel", Qt::CaseInsensitive) == 0 || tag.compare("control-channel", Qt::CaseInsensitive) == 0)
  442. {
  443. bool ok;
  444. const short value(text.toShort(&ok));
  445. if (ok && value >= 1 && value <= MAX_MIDI_CHANNELS)
  446. ctrlChannel = static_cast<int8_t>(value-1);
  447. }
  448. // -------------------------------------------------------
  449. // Program (current)
  450. else if (tag.compare("currentprogramindex", Qt::CaseInsensitive) == 0 || tag.compare("current-program-index", Qt::CaseInsensitive) == 0)
  451. {
  452. bool ok;
  453. const int value(text.toInt(&ok));
  454. if (ok && value >= 1)
  455. currentProgramIndex = value-1;
  456. }
  457. else if (tag.compare("currentprogramname", Qt::CaseInsensitive) == 0 || tag.compare("current-program-name", Qt::CaseInsensitive) == 0)
  458. {
  459. currentProgramName = xmlSafeStringCharDup(text, false);
  460. }
  461. // -------------------------------------------------------
  462. // Midi Program (current)
  463. else if (tag.compare("currentmidibank", Qt::CaseInsensitive) == 0 || tag.compare("current-midi-bank", Qt::CaseInsensitive) == 0)
  464. {
  465. bool ok;
  466. const int value(text.toInt(&ok));
  467. if (ok && value >= 1)
  468. currentMidiBank = value-1;
  469. }
  470. else if (tag.compare("currentmidiprogram", Qt::CaseInsensitive) == 0 || tag.compare("current-midi-program", Qt::CaseInsensitive) == 0)
  471. {
  472. bool ok;
  473. const int value(text.toInt(&ok));
  474. if (ok && value >= 1)
  475. currentMidiProgram = value-1;
  476. }
  477. // -------------------------------------------------------
  478. // Parameters
  479. else if (tag.compare("parameter", Qt::CaseInsensitive) == 0)
  480. {
  481. StateParameter* const stateParameter(new StateParameter());
  482. for (QDomNode xmlSubData = xmlData.toElement().firstChild(); ! xmlSubData.isNull(); xmlSubData = xmlSubData.nextSibling())
  483. {
  484. const QString pTag(xmlSubData.toElement().tagName());
  485. const QString pText(xmlSubData.toElement().text().trimmed());
  486. if (pTag.compare("index", Qt::CaseInsensitive) == 0)
  487. {
  488. bool ok;
  489. const int index(pText.toInt(&ok));
  490. if (ok && index >= 0) stateParameter->index = index;
  491. }
  492. else if (pTag.compare("name", Qt::CaseInsensitive) == 0)
  493. {
  494. stateParameter->name = xmlSafeStringCharDup(pText, false);
  495. }
  496. else if (pTag.compare("symbol", Qt::CaseInsensitive) == 0)
  497. {
  498. stateParameter->symbol = xmlSafeStringCharDup(pText, false);
  499. }
  500. else if (pTag.compare("value", Qt::CaseInsensitive) == 0)
  501. {
  502. bool ok;
  503. const float value(pText.toFloat(&ok));
  504. if (ok) stateParameter->value = value;
  505. }
  506. else if (pTag.compare("midichannel", Qt::CaseInsensitive) == 0 || pTag.compare("midi-channel", Qt::CaseInsensitive) == 0)
  507. {
  508. bool ok;
  509. const ushort channel(pText.toUShort(&ok));
  510. if (ok && channel >= 1 && channel <= MAX_MIDI_CHANNELS)
  511. stateParameter->midiChannel = static_cast<uint8_t>(channel-1);
  512. }
  513. else if (pTag.compare("midicc", Qt::CaseInsensitive) == 0 || pTag.compare("midi-cc", Qt::CaseInsensitive) == 0)
  514. {
  515. bool ok;
  516. const int cc(pText.toInt(&ok));
  517. if (ok && cc >= 1 && cc < 0x5F)
  518. stateParameter->midiCC = static_cast<int16_t>(cc);
  519. }
  520. }
  521. parameters.append(stateParameter);
  522. }
  523. // -------------------------------------------------------
  524. // Custom Data
  525. else if (tag.compare("customdata", Qt::CaseInsensitive) == 0 || tag.compare("custom-data", Qt::CaseInsensitive) == 0)
  526. {
  527. StateCustomData* const stateCustomData(new StateCustomData());
  528. for (QDomNode xmlSubData = xmlData.toElement().firstChild(); ! xmlSubData.isNull(); xmlSubData = xmlSubData.nextSibling())
  529. {
  530. const QString cTag(xmlSubData.toElement().tagName());
  531. const QString cText(xmlSubData.toElement().text().trimmed());
  532. if (cTag.compare("type", Qt::CaseInsensitive) == 0)
  533. stateCustomData->type = xmlSafeStringCharDup(cText, false);
  534. else if (cTag.compare("key", Qt::CaseInsensitive) == 0)
  535. stateCustomData->key = xmlSafeStringCharDup(cText, false);
  536. else if (cTag.compare("value", Qt::CaseInsensitive) == 0)
  537. stateCustomData->value = xmlSafeStringCharDup(cText, false);
  538. }
  539. customData.append(stateCustomData);
  540. }
  541. // -------------------------------------------------------
  542. // Chunk
  543. else if (tag.compare("chunk", Qt::CaseInsensitive) == 0)
  544. {
  545. chunk = xmlSafeStringCharDup(text, false);
  546. }
  547. }
  548. }
  549. }
  550. }
  551. #endif
  552. // -----------------------------------------------------------------------
  553. // fillXmlStringFromStateSave
  554. #ifdef HAVE_JUCE_LATER
  555. String StateSave::toString() const
  556. {
  557. String content;
  558. {
  559. String infoXml(" <Info>\n");
  560. infoXml << " <Type>" << String(type != nullptr ? type : "") << "</Type>\n";
  561. infoXml << " <Name>" << xmlSafeString(name, true) << "</Name>\n";
  562. switch (getPluginTypeFromString(type))
  563. {
  564. case PLUGIN_NONE:
  565. break;
  566. case PLUGIN_INTERNAL:
  567. infoXml << " <Label>" << xmlSafeString(label, true) << "</Label>\n";
  568. break;
  569. case PLUGIN_LADSPA:
  570. infoXml << " <Binary>" << xmlSafeString(binary, true) << "</Binary>\n";
  571. infoXml << " <Label>" << xmlSafeString(label, true) << "</Label>\n";
  572. infoXml << " <UniqueID>" << uniqueId << "</UniqueID>\n";
  573. break;
  574. case PLUGIN_DSSI:
  575. infoXml << " <Binary>" << xmlSafeString(binary, true) << "</Binary>\n";
  576. infoXml << " <Label>" << xmlSafeString(label, true) << "</Label>\n";
  577. break;
  578. case PLUGIN_LV2:
  579. infoXml << " <Bundle>" << xmlSafeString(binary, true) << "</Bundle>\n";
  580. infoXml << " <URI>" << xmlSafeString(label, true) << "</URI>\n";
  581. break;
  582. case PLUGIN_VST:
  583. infoXml << " <Binary>" << xmlSafeString(binary, true) << "</Binary>\n";
  584. infoXml << " <UniqueID>" << uniqueId << "</UniqueID>\n";
  585. break;
  586. case PLUGIN_VST3:
  587. // TODO?
  588. infoXml << " <Binary>" << xmlSafeString(binary, true) << "</Binary>\n";
  589. infoXml << " <UniqueID>" << uniqueId << "</UniqueID>\n";
  590. break;
  591. case PLUGIN_AU:
  592. // TODO?
  593. infoXml << " <Binary>" << xmlSafeString(binary, true) << "</Binary>\n";
  594. infoXml << " <UniqueID>" << uniqueId << "</UniqueID>\n";
  595. break;
  596. case PLUGIN_JACK:
  597. infoXml << " <Binary>" << xmlSafeString(binary, true) << "</Binary>\n";
  598. break;
  599. case PLUGIN_REWIRE:
  600. infoXml << " <Label>" << xmlSafeString(label, true) << "</Label>\n";
  601. break;
  602. case PLUGIN_FILE_CSD:
  603. case PLUGIN_FILE_GIG:
  604. case PLUGIN_FILE_SF2:
  605. case PLUGIN_FILE_SFZ:
  606. infoXml << " <Filename>" << xmlSafeString(binary, true) << "</Filename>\n";
  607. infoXml << " <Label>" << xmlSafeString(label, true) << "</Label>\n";
  608. break;
  609. }
  610. infoXml << " </Info>\n\n";
  611. content << infoXml;
  612. }
  613. content << " <Data>\n";
  614. {
  615. String dataXml;
  616. dataXml << " <Active>" << (active ? "Yes" : "No") << "</Active>\n";
  617. if (dryWet != 1.0f)
  618. dataXml << " <DryWet>" << dryWet << "</DryWet>\n";
  619. if (volume != 1.0f)
  620. dataXml << " <Volume>" << volume << "</Volume>\n";
  621. if (balanceLeft != -1.0f)
  622. dataXml << " <Balance-Left>" << balanceLeft << "</Balance-Left>\n";
  623. if (balanceRight != 1.0f)
  624. dataXml << " <Balance-Right>" << balanceRight << "</Balance-Right>\n";
  625. if (panning != 0.0f)
  626. dataXml << " <Panning>" << panning << "</Panning>\n";
  627. if (ctrlChannel < 0)
  628. dataXml << " <ControlChannel>N</ControlChannel>\n";
  629. else
  630. dataXml << " <ControlChannel>" << int(ctrlChannel+1) << "</ControlChannel>\n";
  631. content << dataXml;
  632. }
  633. for (StateParameterItenerator it = parameters.begin(); it.valid(); it.next())
  634. {
  635. StateParameter* const stateParameter(it.getValue());
  636. String parameterXml("\n"" <Parameter>\n");
  637. parameterXml << " <Index>" << String(stateParameter->index) << "</Index>\n";
  638. parameterXml << " <Name>" << xmlSafeString(stateParameter->name, true) << "</Name>\n";
  639. if (stateParameter->symbol != nullptr && stateParameter->symbol[0] != '\0')
  640. parameterXml << " <Symbol>" << xmlSafeString(stateParameter->symbol, true) << "</Symbol>\n";
  641. if (stateParameter->isInput)
  642. parameterXml << " <Value>" << stateParameter->value << "</Value>\n";
  643. if (stateParameter->midiCC > 0)
  644. {
  645. parameterXml << " <MidiCC>" << stateParameter->midiCC << "</MidiCC>\n";
  646. parameterXml << " <MidiChannel>" << stateParameter->midiChannel+1 << "</MidiChannel>\n";
  647. }
  648. parameterXml << " </Parameter>\n";
  649. content << parameterXml;
  650. }
  651. if (currentProgramIndex >= 0 && currentProgramName != nullptr && currentProgramName[0] != '\0')
  652. {
  653. // ignore 'default' program
  654. if (currentProgramIndex > 0 || ! String(currentProgramName).equalsIgnoreCase("default"))
  655. {
  656. String programXml("\n");
  657. programXml << " <CurrentProgramIndex>" << currentProgramIndex+1 << "</CurrentProgramIndex>\n";
  658. programXml << " <CurrentProgramName>" << xmlSafeString(currentProgramName, true) << "</CurrentProgramName>\n";
  659. content << programXml;
  660. }
  661. }
  662. if (currentMidiBank >= 0 && currentMidiProgram >= 0)
  663. {
  664. String midiProgramXml("\n");
  665. midiProgramXml << " <CurrentMidiBank>" << currentMidiBank+1 << "</CurrentMidiBank>\n";
  666. midiProgramXml << " <CurrentMidiProgram>" << currentMidiProgram+1 << "</CurrentMidiProgram>\n";
  667. content << midiProgramXml;
  668. }
  669. for (StateCustomDataItenerator it = customData.begin(); it.valid(); it.next())
  670. {
  671. StateCustomData* const stateCustomData(it.getValue());
  672. CARLA_SAFE_ASSERT_CONTINUE(stateCustomData->type != nullptr && stateCustomData->type[0] != '\0');
  673. CARLA_SAFE_ASSERT_CONTINUE(stateCustomData->key != nullptr && stateCustomData->key[0] != '\0');
  674. CARLA_SAFE_ASSERT_CONTINUE(stateCustomData->value != nullptr);
  675. String customDataXml("\n"" <CustomData>\n");
  676. customDataXml << " <Type>" << xmlSafeString(stateCustomData->type, true) << "</Type>\n";
  677. customDataXml << " <Key>" << xmlSafeString(stateCustomData->key, true) << "</Key>\n";
  678. if (std::strcmp(stateCustomData->type, CUSTOM_DATA_TYPE_CHUNK) == 0 || std::strlen(stateCustomData->value) >= 128)
  679. {
  680. customDataXml << " <Value>\n";
  681. customDataXml << xmlSafeString(stateCustomData->value, true);
  682. customDataXml << " </Value>\n";
  683. }
  684. else
  685. {
  686. customDataXml << " <Value>";
  687. customDataXml << xmlSafeString(stateCustomData->value, true);
  688. customDataXml << "</Value>\n";
  689. }
  690. customDataXml << " </CustomData>\n";
  691. content << customDataXml;
  692. }
  693. if (chunk != nullptr && chunk[0] != '\0')
  694. {
  695. String chunkXml("\n"" <Chunk>\n");
  696. chunkXml << chunk << "\n </Chunk>\n";
  697. content << chunkXml;
  698. }
  699. content << " </Data>\n";
  700. return content;
  701. }
  702. #else
  703. QString StateSave::toString() const
  704. {
  705. QString content;
  706. {
  707. QString infoXml(" <Info>\n");
  708. infoXml += QString(" <Type>%1</Type>\n").arg((type != nullptr) ? type : "");
  709. infoXml += QString(" <Name>%1</Name>\n").arg(xmlSafeString(name, true));
  710. switch (getPluginTypeFromString(type))
  711. {
  712. case PLUGIN_NONE:
  713. break;
  714. case PLUGIN_INTERNAL:
  715. infoXml += QString(" <Label>%1</Label>\n").arg(xmlSafeString(label, true));
  716. break;
  717. case PLUGIN_LADSPA:
  718. infoXml += QString(" <Binary>%1</Binary>\n").arg(xmlSafeString(binary, true));
  719. infoXml += QString(" <Label>%1</Label>\n").arg(xmlSafeString(label, true));
  720. infoXml += QString(" <UniqueID>%1</UniqueID>\n").arg(uniqueId);
  721. break;
  722. case PLUGIN_DSSI:
  723. infoXml += QString(" <Binary>%1</Binary>\n").arg(xmlSafeString(binary, true));
  724. infoXml += QString(" <Label>%1</Label>\n").arg(xmlSafeString(label, true));
  725. break;
  726. case PLUGIN_LV2:
  727. infoXml += QString(" <Bundle>%1</Bundle>\n").arg(xmlSafeString(binary, true));
  728. infoXml += QString(" <URI>%1</URI>\n").arg(xmlSafeString(label, true));
  729. break;
  730. case PLUGIN_VST:
  731. infoXml += QString(" <Binary>%1</Binary>\n").arg(xmlSafeString(binary, true));
  732. infoXml += QString(" <UniqueID>%1</UniqueID>\n").arg(uniqueId);
  733. break;
  734. case PLUGIN_VST3:
  735. // TODO?
  736. infoXml += QString(" <Binary>%1</Binary>\n").arg(xmlSafeString(binary, true));
  737. infoXml += QString(" <UniqueID>%1</UniqueID>\n").arg(uniqueId);
  738. break;
  739. case PLUGIN_AU:
  740. // TODO?
  741. infoXml += QString(" <Binary>%1</Binary>\n").arg(xmlSafeString(binary, true));
  742. infoXml += QString(" <UniqueID>%1</UniqueID>\n").arg(uniqueId);
  743. break;
  744. case PLUGIN_JACK:
  745. infoXml += QString(" <Binary>%1</Binary>\n").arg(xmlSafeString(binary, true));
  746. break;
  747. case PLUGIN_REWIRE:
  748. infoXml += QString(" <Label>%1</Label>\n").arg(xmlSafeString(label, true));
  749. break;
  750. case PLUGIN_FILE_CSD:
  751. case PLUGIN_FILE_GIG:
  752. case PLUGIN_FILE_SF2:
  753. case PLUGIN_FILE_SFZ:
  754. infoXml += QString(" <Filename>%1</Filename>\n").arg(xmlSafeString(binary, true));
  755. infoXml += QString(" <Label>%1</Label>\n").arg(xmlSafeString(label, true));
  756. break;
  757. }
  758. infoXml += " </Info>\n\n";
  759. content += infoXml;
  760. }
  761. content += " <Data>\n";
  762. {
  763. QString dataXml;
  764. dataXml += QString(" <Active>%1</Active>\n").arg(active ? "Yes" : "No");
  765. if (dryWet != 1.0f)
  766. dataXml += QString(" <DryWet>%1</DryWet>\n").arg(dryWet, 0, 'g', 7);
  767. if (volume != 1.0f)
  768. dataXml += QString(" <Volume>%1</Volume>\n").arg(volume, 0, 'g', 7);
  769. if (balanceLeft != -1.0f)
  770. dataXml += QString(" <Balance-Left>%1</Balance-Left>\n").arg(balanceLeft, 0, 'g', 7);
  771. if (balanceRight != 1.0f)
  772. dataXml += QString(" <Balance-Right>%1</Balance-Right>\n").arg(balanceRight, 0, 'g', 7);
  773. if (panning != 0.0f)
  774. dataXml += QString(" <Panning>%1</Panning>\n").arg(panning, 0, 'g', 7);
  775. if (ctrlChannel < 0)
  776. dataXml += QString(" <ControlChannel>N</ControlChannel>\n");
  777. else
  778. dataXml += QString(" <ControlChannel>%1</ControlChannel>\n").arg(ctrlChannel+1);
  779. content += dataXml;
  780. }
  781. for (StateParameterItenerator it = parameters.begin(); it.valid(); it.next())
  782. {
  783. StateParameter* const stateParameter(it.getValue());
  784. QString parameterXml("\n"" <Parameter>\n");
  785. parameterXml += QString(" <Index>%1</Index>\n").arg(stateParameter->index);
  786. parameterXml += QString(" <Name>%1</Name>\n").arg(xmlSafeString(stateParameter->name, true));
  787. if (stateParameter->symbol != nullptr && stateParameter->symbol[0] != '\0')
  788. parameterXml += QString(" <Symbol>%1</Symbol>\n").arg(xmlSafeString(stateParameter->symbol, true));
  789. if (stateParameter->isInput)
  790. parameterXml += QString(" <Value>%1</Value>\n").arg(stateParameter->value, 0, 'g', 15);
  791. if (stateParameter->midiCC > 0)
  792. {
  793. parameterXml += QString(" <MidiCC>%1</MidiCC>\n").arg(stateParameter->midiCC);
  794. parameterXml += QString(" <MidiChannel>%1</MidiChannel>\n").arg(stateParameter->midiChannel+1);
  795. }
  796. parameterXml += " </Parameter>\n";
  797. content += parameterXml;
  798. }
  799. if (currentProgramIndex >= 0 && currentProgramName != nullptr && currentProgramName[0] != '\0')
  800. {
  801. // ignore 'default' program
  802. if (currentProgramIndex > 0 || QString(currentProgramName).compare("default", Qt::CaseInsensitive) != 0)
  803. {
  804. QString programXml("\n");
  805. programXml += QString(" <CurrentProgramIndex>%1</CurrentProgramIndex>\n").arg(currentProgramIndex+1);
  806. programXml += QString(" <CurrentProgramName>%1</CurrentProgramName>\n").arg(xmlSafeString(currentProgramName, true));
  807. content += programXml;
  808. }
  809. }
  810. if (currentMidiBank >= 0 && currentMidiProgram >= 0)
  811. {
  812. QString midiProgramXml("\n");
  813. midiProgramXml += QString(" <CurrentMidiBank>%1</CurrentMidiBank>\n").arg(currentMidiBank+1);
  814. midiProgramXml += QString(" <CurrentMidiProgram>%1</CurrentMidiProgram>\n").arg(currentMidiProgram+1);
  815. content += midiProgramXml;
  816. }
  817. for (StateCustomDataItenerator it = customData.begin(); it.valid(); it.next())
  818. {
  819. StateCustomData* const stateCustomData(it.getValue());
  820. CARLA_SAFE_ASSERT_CONTINUE(stateCustomData->type != nullptr && stateCustomData->type[0] != '\0');
  821. CARLA_SAFE_ASSERT_CONTINUE(stateCustomData->key != nullptr && stateCustomData->key[0] != '\0');
  822. CARLA_SAFE_ASSERT_CONTINUE(stateCustomData->value != nullptr);
  823. QString customDataXml("\n"" <CustomData>\n");
  824. customDataXml += QString(" <Type>%1</Type>\n").arg(xmlSafeString(stateCustomData->type, true));
  825. customDataXml += QString(" <Key>%1</Key>\n").arg(xmlSafeString(stateCustomData->key, true));
  826. if (std::strcmp(stateCustomData->type, CUSTOM_DATA_TYPE_CHUNK) == 0 || std::strlen(stateCustomData->value) >= 128)
  827. {
  828. customDataXml += " <Value>\n";
  829. customDataXml += QString("%1\n").arg(xmlSafeString(stateCustomData->value, true));
  830. customDataXml += " </Value>\n";
  831. }
  832. else
  833. customDataXml += QString(" <Value>%1</Value>\n").arg(xmlSafeString(stateCustomData->value, true));
  834. customDataXml += " </CustomData>\n";
  835. content += customDataXml;
  836. }
  837. if (chunk != nullptr && chunk[0] != '\0')
  838. {
  839. QString chunkXml("\n"" <Chunk>\n");
  840. chunkXml += QString("%1\n").arg(chunk);
  841. chunkXml += " </Chunk>\n";
  842. content += chunkXml;
  843. }
  844. content += " </Data>\n";
  845. return content;
  846. }
  847. #endif
  848. // -----------------------------------------------------------------------
  849. CARLA_BACKEND_END_NAMESPACE