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.

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