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.

663 lines
22KB

  1. /*
  2. * Carla State utils
  3. * Copyright (C) 2012-2013 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. #ifndef CARLA_STATE_UTILS_HPP_INCLUDED
  18. #define CARLA_STATE_UTILS_HPP_INCLUDED
  19. #include "CarlaBackendUtils.hpp"
  20. #include "CarlaMIDI.h"
  21. #include "RtList.hpp"
  22. #if 1
  23. # include <QtXml/QDomNode>
  24. #else
  25. # include "juce_core/AppConfig.h"
  26. # include "juce_core/juce_core.h"
  27. using juce::String;
  28. #endif
  29. CARLA_BACKEND_START_NAMESPACE
  30. // -----------------------------------------------------------------------
  31. struct StateParameter {
  32. uint32_t index;
  33. const char* name;
  34. const char* symbol;
  35. float value;
  36. uint8_t midiChannel;
  37. int16_t midiCC;
  38. StateParameter() noexcept
  39. : index(0),
  40. name(nullptr),
  41. symbol(nullptr),
  42. value(0.0f),
  43. midiChannel(0),
  44. midiCC(-1) {}
  45. ~StateParameter()
  46. {
  47. if (name != nullptr)
  48. {
  49. delete[] name;
  50. name = nullptr;
  51. }
  52. if (symbol != nullptr)
  53. {
  54. delete[] symbol;
  55. symbol = nullptr;
  56. }
  57. }
  58. CARLA_DECLARE_NON_COPY_STRUCT(StateParameter)
  59. };
  60. struct StateCustomData {
  61. const char* type;
  62. const char* key;
  63. const char* value;
  64. StateCustomData() noexcept
  65. : type(nullptr),
  66. key(nullptr),
  67. value(nullptr) {}
  68. ~StateCustomData()
  69. {
  70. if (type != nullptr)
  71. {
  72. delete[] type;
  73. type = nullptr;
  74. }
  75. if (key != nullptr)
  76. {
  77. delete[] key;
  78. key = nullptr;
  79. }
  80. if (value != nullptr)
  81. {
  82. delete[] value;
  83. value = nullptr;
  84. }
  85. }
  86. CARLA_DECLARE_NON_COPY_STRUCT(StateCustomData)
  87. };
  88. typedef NonRtList<StateParameter*> StateParameterList;
  89. typedef NonRtList<StateCustomData*> StateCustomDataList;
  90. typedef NonRtList<StateParameter*>::Itenerator StateParameterItenerator;
  91. typedef NonRtList<StateCustomData*>::Itenerator StateCustomDataItenerator;
  92. struct SaveState {
  93. const char* type;
  94. const char* name;
  95. const char* label;
  96. const char* binary;
  97. long uniqueID;
  98. bool active;
  99. float dryWet;
  100. float volume;
  101. float balanceLeft;
  102. float balanceRight;
  103. float panning;
  104. int8_t ctrlChannel;
  105. int32_t currentProgramIndex;
  106. const char* currentProgramName;
  107. int32_t currentMidiBank;
  108. int32_t currentMidiProgram;
  109. const char* chunk;
  110. StateParameterList parameters;
  111. StateCustomDataList customData;
  112. SaveState() noexcept
  113. : type(nullptr),
  114. name(nullptr),
  115. label(nullptr),
  116. binary(nullptr),
  117. uniqueID(0),
  118. active(false),
  119. dryWet(1.0f),
  120. volume(1.0f),
  121. balanceLeft(-1.0f),
  122. balanceRight(1.0f),
  123. panning(0.0f),
  124. ctrlChannel(-1),
  125. currentProgramIndex(-1),
  126. currentProgramName(nullptr),
  127. currentMidiBank(-1),
  128. currentMidiProgram(-1),
  129. chunk(nullptr) {}
  130. ~SaveState()
  131. {
  132. reset();
  133. }
  134. void reset()
  135. {
  136. if (type != nullptr)
  137. {
  138. delete[] type;
  139. type = nullptr;
  140. }
  141. if (name != nullptr)
  142. {
  143. delete[] name;
  144. name = nullptr;
  145. }
  146. if (label != nullptr)
  147. {
  148. delete[] label;
  149. label = nullptr;
  150. }
  151. if (binary != nullptr)
  152. {
  153. delete[] binary;
  154. binary = nullptr;
  155. }
  156. if (currentProgramName != nullptr)
  157. {
  158. delete[] currentProgramName;
  159. currentProgramName = nullptr;
  160. }
  161. if (chunk != nullptr)
  162. {
  163. delete[] chunk;
  164. chunk = nullptr;
  165. }
  166. uniqueID = 0;
  167. active = false;
  168. dryWet = 1.0f;
  169. volume = 1.0f;
  170. balanceLeft = -1.0f;
  171. balanceRight = 1.0f;
  172. panning = 0.0f;
  173. ctrlChannel = -1;
  174. currentProgramIndex = -1;
  175. currentMidiBank = -1;
  176. currentMidiProgram = -1;
  177. for (StateParameterItenerator it = parameters.begin(); it.valid(); it.next())
  178. {
  179. StateParameter* const stateParameter(*it);
  180. delete stateParameter;
  181. }
  182. for (StateCustomDataItenerator it = customData.begin(); it.valid(); it.next())
  183. {
  184. StateCustomData* const stateCustomData(*it);
  185. delete stateCustomData;
  186. }
  187. parameters.clear();
  188. customData.clear();
  189. }
  190. CARLA_DECLARE_NON_COPY_STRUCT(SaveState)
  191. };
  192. // -----------------------------------------------------------------------
  193. #if 1
  194. static inline
  195. QString xmlSafeString(const QString& string, const bool toXml)
  196. {
  197. QString newString(string);
  198. if (toXml)
  199. return newString.replace("&","&amp;").replace("<","&lt;").replace(">","&gt;").replace("'","&apos;").replace("\"","&quot;");
  200. else
  201. return newString.replace("&amp;","&").replace("&lt;","<").replace("&gt;",">").replace("&apos;","'").replace("&quot;","\"");
  202. }
  203. static inline
  204. const char* xmlSafeStringCharDup(const QString& string, const bool toXml)
  205. {
  206. return carla_strdup(xmlSafeString(string, toXml).toUtf8().constData());
  207. }
  208. #else
  209. static inline
  210. String xmlSafeString(const String& string, const bool toXml)
  211. {
  212. String newString(string);
  213. if (toXml)
  214. return newString.replace("&","&amp;").replace("<","&lt;").replace(">","&gt;").replace("'","&apos;").replace("\"","&quot;");
  215. else
  216. return newString.replace("&amp;","&").replace("&lt;","<").replace("&gt;",">").replace("&apos;","'").replace("&quot;","\"");
  217. }
  218. static inline
  219. const char* xmlSafeStringCharDup(const String& string, const bool toXml)
  220. {
  221. return carla_strdup(xmlSafeString(string, toXml).toRawUTF8());
  222. }
  223. #endif
  224. // -----------------------------------------------------------------------
  225. static inline
  226. void fillSaveStateFromXmlNode(SaveState& saveState, const QDomNode& xmlNode)
  227. {
  228. saveState.reset();
  229. if (xmlNode.isNull())
  230. return;
  231. QDomNode node(xmlNode.firstChild());
  232. while (! node.isNull())
  233. {
  234. // ---------------------------------------------------------------
  235. // Info
  236. if (node.toElement().tagName().compare("Info", Qt::CaseInsensitive) == 0)
  237. {
  238. QDomNode xmlInfo(node.toElement().firstChild());
  239. while (! xmlInfo.isNull())
  240. {
  241. const QString tag(xmlInfo.toElement().tagName());
  242. const QString text(xmlInfo.toElement().text().trimmed());
  243. if (tag.compare("Type", Qt::CaseInsensitive) == 0)
  244. saveState.type = xmlSafeStringCharDup(text, false);
  245. else if (tag.compare("Name", Qt::CaseInsensitive) == 0)
  246. saveState.name = xmlSafeStringCharDup(text, false);
  247. else if (tag.compare("Label", Qt::CaseInsensitive) == 0 || tag.compare("URI", Qt::CaseInsensitive) == 0)
  248. saveState.label = xmlSafeStringCharDup(text, false);
  249. else if (tag.compare("Binary", Qt::CaseInsensitive) == 0)
  250. saveState.binary = xmlSafeStringCharDup(text, false);
  251. else if (tag.compare("UniqueID", Qt::CaseInsensitive) == 0)
  252. {
  253. bool ok;
  254. const long uniqueID(text.toLong(&ok));
  255. if (ok) saveState.uniqueID = uniqueID;
  256. }
  257. xmlInfo = xmlInfo.nextSibling();
  258. }
  259. }
  260. // ---------------------------------------------------------------
  261. // Data
  262. else if (node.toElement().tagName().compare("Data", Qt::CaseInsensitive) == 0)
  263. {
  264. QDomNode xmlData(node.toElement().firstChild());
  265. while (! xmlData.isNull())
  266. {
  267. const QString tag(xmlData.toElement().tagName());
  268. const QString text(xmlData.toElement().text().trimmed());
  269. // -------------------------------------------------------
  270. // Internal Data
  271. if (tag.compare("Active", Qt::CaseInsensitive) == 0)
  272. {
  273. saveState.active = (text.compare("Yes", Qt::CaseInsensitive) == 0);
  274. }
  275. else if (tag.compare("DryWet", Qt::CaseInsensitive) == 0)
  276. {
  277. bool ok;
  278. const float value(text.toFloat(&ok));
  279. if (ok) saveState.dryWet = value;
  280. }
  281. else if (tag.compare("Volume", Qt::CaseInsensitive) == 0)
  282. {
  283. bool ok;
  284. const float value(text.toFloat(&ok));
  285. if (ok) saveState.volume = value;
  286. }
  287. else if (tag.compare("Balance-Left", Qt::CaseInsensitive) == 0)
  288. {
  289. bool ok;
  290. const float value(text.toFloat(&ok));
  291. if (ok) saveState.balanceLeft = value;
  292. }
  293. else if (tag.compare("Balance-Right", Qt::CaseInsensitive) == 0)
  294. {
  295. bool ok;
  296. const float value(text.toFloat(&ok));
  297. if (ok) saveState.balanceRight = value;
  298. }
  299. else if (tag.compare("Panning", Qt::CaseInsensitive) == 0)
  300. {
  301. bool ok;
  302. const float value(text.toFloat(&ok));
  303. if (ok) saveState.panning = value;
  304. }
  305. else if (tag.compare("ControlChannel", Qt::CaseInsensitive) == 0)
  306. {
  307. bool ok;
  308. const short value(text.toShort(&ok));
  309. if (ok && value >= 1 && value < INT8_MAX)
  310. saveState.ctrlChannel = static_cast<int8_t>(value-1);
  311. }
  312. // -------------------------------------------------------
  313. // Program (current)
  314. else if (tag.compare("CurrentProgramIndex", Qt::CaseInsensitive) == 0)
  315. {
  316. bool ok;
  317. const int value(text.toInt(&ok));
  318. if (ok && value >= 1)
  319. saveState.currentProgramIndex = value-1;
  320. }
  321. else if (tag.compare("CurrentProgramName", Qt::CaseInsensitive) == 0)
  322. {
  323. saveState.currentProgramName = xmlSafeStringCharDup(text, false);
  324. }
  325. // -------------------------------------------------------
  326. // Midi Program (current)
  327. else if (tag.compare("CurrentMidiBank", Qt::CaseInsensitive) == 0)
  328. {
  329. bool ok;
  330. const int value(text.toInt(&ok));
  331. if (ok && value >= 1)
  332. saveState.currentMidiBank = value-1;
  333. }
  334. else if (tag.compare("CurrentMidiProgram", Qt::CaseInsensitive) == 0)
  335. {
  336. bool ok;
  337. const int value(text.toInt(&ok));
  338. if (ok && value >= 1)
  339. saveState.currentMidiProgram = value-1;
  340. }
  341. // -------------------------------------------------------
  342. // Parameters
  343. else if (tag.compare("Parameter", Qt::CaseInsensitive) == 0)
  344. {
  345. StateParameter* const stateParameter(new StateParameter());
  346. QDomNode xmlSubData(xmlData.toElement().firstChild());
  347. while (! xmlSubData.isNull())
  348. {
  349. const QString pTag(xmlSubData.toElement().tagName());
  350. const QString pText(xmlSubData.toElement().text().trimmed());
  351. if (pTag.compare("Index", Qt::CaseInsensitive) == 0)
  352. {
  353. bool ok;
  354. const uint index(pText.toUInt(&ok));
  355. if (ok) stateParameter->index = index;
  356. }
  357. else if (pTag.compare("Name", Qt::CaseInsensitive) == 0)
  358. {
  359. stateParameter->name = xmlSafeStringCharDup(pText, false);
  360. }
  361. else if (pTag.compare("Symbol", Qt::CaseInsensitive) == 0)
  362. {
  363. stateParameter->symbol = xmlSafeStringCharDup(pText, false);
  364. }
  365. else if (pTag.compare("Value", Qt::CaseInsensitive) == 0)
  366. {
  367. bool ok;
  368. const float value(pText.toFloat(&ok));
  369. if (ok) stateParameter->value = value;
  370. }
  371. else if (pTag.compare("MidiChannel", Qt::CaseInsensitive) == 0)
  372. {
  373. bool ok;
  374. const ushort channel(pText.toUShort(&ok));
  375. if (ok && channel >= 1 && channel < MAX_MIDI_CHANNELS)
  376. stateParameter->midiChannel = static_cast<uint8_t>(channel-1);
  377. }
  378. else if (pTag.compare("MidiCC", Qt::CaseInsensitive) == 0)
  379. {
  380. bool ok;
  381. const int cc(pText.toInt(&ok));
  382. if (ok && cc >= 1 && cc < INT16_MAX)
  383. stateParameter->midiCC = static_cast<int16_t>(cc);
  384. }
  385. xmlSubData = xmlSubData.nextSibling();
  386. }
  387. saveState.parameters.append(stateParameter);
  388. }
  389. // -------------------------------------------------------
  390. // Custom Data
  391. else if (tag.compare("CustomData", Qt::CaseInsensitive) == 0)
  392. {
  393. StateCustomData* const stateCustomData(new StateCustomData());
  394. QDomNode xmlSubData(xmlData.toElement().firstChild());
  395. while (! xmlSubData.isNull())
  396. {
  397. const QString cTag(xmlSubData.toElement().tagName());
  398. const QString cText(xmlSubData.toElement().text().trimmed());
  399. if (cTag.compare("Type", Qt::CaseInsensitive) == 0)
  400. stateCustomData->type = xmlSafeStringCharDup(cText, false);
  401. else if (cTag.compare("Key", Qt::CaseInsensitive) == 0)
  402. stateCustomData->key = xmlSafeStringCharDup(cText, false);
  403. else if (cTag.compare("Value", Qt::CaseInsensitive) == 0)
  404. stateCustomData->value = xmlSafeStringCharDup(cText, false);
  405. xmlSubData = xmlSubData.nextSibling();
  406. }
  407. saveState.customData.append(stateCustomData);
  408. }
  409. // -------------------------------------------------------
  410. // Chunk
  411. else if (tag.compare("Chunk", Qt::CaseInsensitive) == 0)
  412. {
  413. saveState.chunk = xmlSafeStringCharDup(text, false);
  414. }
  415. // -------------------------------------------------------
  416. xmlData = xmlData.nextSibling();
  417. }
  418. }
  419. // ---------------------------------------------------------------
  420. node = node.nextSibling();
  421. }
  422. }
  423. // -----------------------------------------------------------------------
  424. static inline
  425. void fillXmlStringFromSaveState(QString& content, const SaveState& saveState)
  426. {
  427. content.clear();
  428. {
  429. QString info(" <Info>\n");
  430. info += QString(" <Type>%1</Type>\n").arg(saveState.type);
  431. info += QString(" <Name>%1</Name>\n").arg(xmlSafeString(saveState.name, true));
  432. switch (getPluginTypeFromString(saveState.type))
  433. {
  434. case PLUGIN_NONE:
  435. break;
  436. case PLUGIN_INTERNAL:
  437. info += QString(" <Label>%1</Label>\n").arg(xmlSafeString(saveState.label, true));
  438. break;
  439. case PLUGIN_LADSPA:
  440. info += QString(" <Binary>%1</Binary>\n").arg(xmlSafeString(saveState.binary, true));
  441. info += QString(" <Label>%1</Label>\n").arg(xmlSafeString(saveState.label, true));
  442. info += QString(" <UniqueID>%1</UniqueID>\n").arg(saveState.uniqueID);
  443. break;
  444. case PLUGIN_DSSI:
  445. info += QString(" <Binary>%1</Binary>\n").arg(xmlSafeString(saveState.binary, true));
  446. info += QString(" <Label>%1</Label>\n").arg(xmlSafeString(saveState.label, true));
  447. break;
  448. case PLUGIN_LV2:
  449. info += QString(" <URI>%1</URI>\n").arg(xmlSafeString(saveState.label, true));
  450. break;
  451. case PLUGIN_VST:
  452. info += QString(" <Binary>%1</Binary>\n").arg(xmlSafeString(saveState.binary, true));
  453. info += QString(" <UniqueID>%1</UniqueID>\n").arg(saveState.uniqueID);
  454. break;
  455. case PLUGIN_VST3:
  456. // TODO?
  457. info += QString(" <Binary>%1</Binary>\n").arg(xmlSafeString(saveState.binary, true));
  458. info += QString(" <UniqueID>%1</UniqueID>\n").arg(saveState.uniqueID);
  459. break;
  460. case PLUGIN_GIG:
  461. case PLUGIN_SF2:
  462. case PLUGIN_SFZ:
  463. info += QString(" <Binary>%1</Binary>\n").arg(xmlSafeString(saveState.binary, true));
  464. info += QString(" <Label>%1</Label>\n").arg(xmlSafeString(saveState.label, true));
  465. break;
  466. }
  467. info += " </Info>\n\n";
  468. content += info;
  469. }
  470. {
  471. QString data(" <Data>\n");
  472. data += QString(" <Active>%1</Active>\n").arg(saveState.active ? "Yes" : "No");
  473. if (saveState.dryWet != 1.0f)
  474. data += QString(" <DryWet>%1</DryWet>\n").arg(saveState.dryWet);
  475. if (saveState.volume != 1.0f)
  476. data += QString(" <Volume>%1</Volume>\n").arg(saveState.volume);
  477. if (saveState.balanceLeft != -1.0f)
  478. data += QString(" <Balance-Left>%1</Balance-Left>\n").arg(saveState.balanceLeft);
  479. if (saveState.balanceRight != 1.0f)
  480. data += QString(" <Balance-Right>%1</Balance-Right>\n").arg(saveState.balanceRight);
  481. if (saveState.panning != 0.0f)
  482. data += QString(" <Panning>%1</Panning>\n").arg(saveState.panning);
  483. if (saveState.ctrlChannel < 0)
  484. data += QString(" <ControlChannel>N</ControlChannel>\n");
  485. else
  486. data += QString(" <ControlChannel>%1</ControlChannel>\n").arg(saveState.ctrlChannel+1);
  487. content += data;
  488. }
  489. for (StateParameterItenerator it = saveState.parameters.begin(); it.valid(); it.next())
  490. {
  491. StateParameter* const stateParameter(*it);
  492. QString parameter("\n"" <Parameter>\n");
  493. parameter += QString(" <Index>%1</Index>\n").arg(stateParameter->index);
  494. parameter += QString(" <Name>%1</Name>\n").arg(xmlSafeString(stateParameter->name, true));
  495. if (stateParameter->symbol != nullptr && *stateParameter->symbol != '\0')
  496. parameter += QString(" <Symbol>%1</Symbol>\n").arg(xmlSafeString(stateParameter->symbol, true));
  497. parameter += QString(" <Value>%1</Value>\n").arg(stateParameter->value);
  498. if (stateParameter->midiCC > 0)
  499. {
  500. parameter += QString(" <MidiCC>%1</MidiCC>\n").arg(stateParameter->midiCC);
  501. parameter += QString(" <MidiChannel>%1</MidiChannel>\n").arg(stateParameter->midiChannel+1);
  502. }
  503. parameter += " </Parameter>\n";
  504. content += parameter;
  505. }
  506. if (saveState.currentProgramIndex >= 0 && saveState.currentProgramName != nullptr)
  507. {
  508. // ignore 'default' program
  509. #ifdef __USE_GNU
  510. if ((saveState.currentProgramIndex > 0 || strcasecmp(saveState.currentProgramName, "default") != 0))
  511. #else
  512. if ((saveState.currentProgramIndex > 0 || std::strcmp(saveState.currentProgramName, "Default") != 0))
  513. #endif
  514. {
  515. QString program("\n");
  516. program += QString(" <CurrentProgramIndex>%1</CurrentProgramIndex>\n").arg(saveState.currentProgramIndex+1);
  517. program += QString(" <CurrentProgramName>%1</CurrentProgramName>\n").arg(xmlSafeString(saveState.currentProgramName, true));
  518. content += program;
  519. }
  520. }
  521. if (saveState.currentMidiBank >= 0 && saveState.currentMidiProgram >= 0)
  522. {
  523. QString midiProgram("\n");
  524. midiProgram += QString(" <CurrentMidiBank>%1</CurrentMidiBank>\n").arg(saveState.currentMidiBank+1);
  525. midiProgram += QString(" <CurrentMidiProgram>%1</CurrentMidiProgram>\n").arg(saveState.currentMidiProgram+1);
  526. content += midiProgram;
  527. }
  528. for (StateCustomDataItenerator it = saveState.customData.begin(); it.valid(); it.next())
  529. {
  530. StateCustomData* const stateCustomData(*it);
  531. QString customData("\n"" <CustomData>\n");
  532. customData += QString(" <Type>%1</Type>\n").arg(xmlSafeString(stateCustomData->type, true));
  533. customData += QString(" <Key>%1</Key>\n").arg(xmlSafeString(stateCustomData->key, true));
  534. if (std::strcmp(stateCustomData->type, CUSTOM_DATA_CHUNK) == 0 || std::strlen(stateCustomData->value) >= 128)
  535. {
  536. customData += " <Value>\n";
  537. customData += QString("%1\n").arg(xmlSafeString(stateCustomData->value, true));
  538. customData += " </Value>\n";
  539. }
  540. else
  541. customData += QString(" <Value>%1</Value>\n").arg(xmlSafeString(stateCustomData->value, true));
  542. customData += " </CustomData>\n";
  543. content += customData;
  544. }
  545. if (saveState.chunk != nullptr && *saveState.chunk != '\0')
  546. {
  547. QString chunk("\n"" <Chunk>\n");
  548. chunk += QString("%1\n").arg(saveState.chunk);
  549. chunk += " </Chunk>\n";
  550. content += chunk;
  551. }
  552. content += " </Data>\n";
  553. }
  554. // -----------------------------------------------------------------------
  555. CARLA_BACKEND_END_NAMESPACE
  556. #endif // CARLA_STATE_UTILS_HPP_INCLUDED