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.

589 lines
19KB

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