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.

601 lines
20KB

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