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.

588 lines
18KB

  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() == "Info")
  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(); //.strip();
  195. if (tag == "Type")
  196. saveState.type = xmlSafeStringChar(text, false);
  197. else if (tag == "Name")
  198. saveState.name = xmlSafeStringChar(text, false);
  199. else if (tag == "Label" || tag == "URI")
  200. saveState.label = xmlSafeStringChar(text, false);
  201. else if (tag == "Binary")
  202. saveState.binary = xmlSafeStringChar(text, false);
  203. else if (tag == "UniqueID")
  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() == "Data")
  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(); //.strip();
  221. // ----------------------------------------------
  222. // Internal Data
  223. if (tag == "Active")
  224. {
  225. saveState.active = bool(text == "Yes");
  226. }
  227. else if (tag == "DryWet")
  228. {
  229. bool ok;
  230. float value = text.toFloat(&ok);
  231. if (ok) saveState.dryWet = value;
  232. }
  233. else if (tag == "Volume")
  234. {
  235. bool ok;
  236. float value = text.toFloat(&ok);
  237. if (ok) saveState.volume = value;
  238. }
  239. else if (tag == "Balance-Left")
  240. {
  241. bool ok;
  242. float value = text.toFloat(&ok);
  243. if (ok) saveState.balanceLeft = value;
  244. }
  245. else if (tag == "Balance-Right")
  246. {
  247. bool ok;
  248. float value = text.toFloat(&ok);
  249. if (ok) saveState.balanceRight = value;
  250. }
  251. else if (tag == "Panning")
  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 == "CurrentProgramIndex")
  260. {
  261. bool ok;
  262. int value = text.toInt(&ok);
  263. if (ok) saveState.currentProgramIndex = value;
  264. }
  265. else if (tag == "CurrentProgramName")
  266. {
  267. saveState.currentProgramName = xmlSafeStringChar(text, false);
  268. }
  269. // ----------------------------------------------
  270. // Midi Program (current)
  271. else if (tag == "CurrentMidiBank")
  272. {
  273. bool ok;
  274. int value = text.toInt(&ok);
  275. if (ok) saveState.currentMidiBank = value;
  276. }
  277. else if (tag == "CurrentMidiProgram")
  278. {
  279. bool ok;
  280. int value = text.toInt(&ok);
  281. if (ok) saveState.currentMidiProgram = value;
  282. }
  283. // ----------------------------------------------
  284. // Parameters
  285. else if (tag == "Parameter")
  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(); //.strip();
  293. if (pTag == "Index")
  294. {
  295. bool ok;
  296. uint index = pText.toUInt(&ok);
  297. if (ok) stateParameter->index = index;
  298. }
  299. else if (pTag == "Name")
  300. {
  301. stateParameter->name = xmlSafeStringChar(pText, false);
  302. }
  303. else if (pTag == "Symbol")
  304. {
  305. stateParameter->symbol = xmlSafeStringChar(pText, false);
  306. }
  307. else if (pTag == "Value")
  308. {
  309. bool ok;
  310. float value = text.toFloat(&ok);
  311. if (ok) stateParameter->value = value;
  312. }
  313. else if (pTag == "MidiChannel")
  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 == "MidiCC")
  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 == "CustomData")
  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(); //.strip();
  341. if (cTag == "Type")
  342. stateCustomData->type = xmlSafeStringChar(cText, false);
  343. else if (cTag == "Key")
  344. stateCustomData->key = xmlSafeStringChar(cText, false);
  345. else if (cTag == "Value")
  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 == "Chunk")
  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. break;
  403. }
  404. info += " </Info>\n\n";
  405. content += info;
  406. }
  407. {
  408. QString data(" <Data>\n");
  409. data += QString(" <Active>%1</Active>\n").arg(saveState.active ? "Yes" : "No");
  410. if (saveState.dryWet != 1.0f)
  411. data += QString(" <DryWet>%1</DryWet>\n").arg(saveState.dryWet);
  412. if (saveState.volume != 1.0f)
  413. data += QString(" <Volume>%1</Volume>\n").arg(saveState.volume);
  414. if (saveState.balanceLeft != -1.0f)
  415. data += QString(" <Balance-Left>%1</Balance-Left>\n").arg(saveState.balanceLeft);
  416. if (saveState.balanceRight != 1.0f)
  417. data += QString(" <Balance-Right>%1</Balance-Right>\n").arg(saveState.balanceRight);
  418. if (saveState.panning != 0.0f)
  419. data += QString(" <Panning>%1</Panning>\n").arg(saveState.panning);
  420. content += data;
  421. }
  422. for (auto it = saveState.parameters.begin(); it != saveState.parameters.end(); ++it)
  423. {
  424. StateParameter* stateParameter = *it;
  425. QString parameter("\n"" <Parameter>\n");
  426. parameter += QString(" <Index>%1</Index>\n").arg(stateParameter->index);
  427. parameter += QString(" <Name>%1</Name>\n").arg(xmlSafeString(stateParameter->name, true));
  428. if (stateParameter->symbol != nullptr && *stateParameter->symbol != 0)
  429. parameter += QString(" <Symbol>%1</Symbol>\n").arg(xmlSafeString(stateParameter->symbol, true));
  430. parameter += QString(" <Value>%1</Value>\n").arg(stateParameter->value);
  431. if (stateParameter->midiCC > 0)
  432. {
  433. parameter += QString(" <MidiCC>%1</MidiCC>\n").arg(stateParameter->midiCC);
  434. parameter += QString(" <MidiChannel>%1</MidiChannel>\n").arg(stateParameter->midiChannel);
  435. }
  436. parameter += " </Parameter>\n";
  437. content += parameter;
  438. }
  439. if (saveState.currentProgramIndex >= 0)
  440. {
  441. QString program("\n");
  442. program += QString(" <CurrentProgramIndex>%1</CurrentProgramIndex>\n").arg(saveState.currentProgramIndex);
  443. program += QString(" <CurrentProgramName>%1</CurrentProgramName>\n").arg(xmlSafeString(saveState.currentProgramName, true));
  444. content += program;
  445. }
  446. if (saveState.currentMidiBank >= 0 && saveState.currentMidiProgram >= 0)
  447. {
  448. QString midiProgram("\n");
  449. midiProgram += QString(" <CurrentMidiBank>%1</CurrentMidiBank>\n").arg(saveState.currentMidiBank);
  450. midiProgram += QString(" <CurrentMidiProgram>%1</CurrentMidiProgram>\n").arg(saveState.currentMidiProgram);
  451. content += midiProgram;
  452. }
  453. for (auto it = saveState.customData.begin(); it != saveState.customData.end(); ++it)
  454. {
  455. StateCustomData* stateCustomData = *it;
  456. QString customData("\n"" <CustomData>\n");
  457. customData += QString(" <Type>%1</Type>\n").arg(xmlSafeString(stateCustomData->type, true));
  458. customData += QString(" <Key>%1</Key>\n").arg(xmlSafeString(stateCustomData->key, true));
  459. if (std::strcmp(stateCustomData->type, CUSTOM_DATA_CHUNK) == 0)
  460. {
  461. customData += " <Value>\n";
  462. customData += QString("%1\n").arg(xmlSafeString(stateCustomData->value, true));
  463. customData += " </Value>\n";
  464. }
  465. else
  466. customData += QString(" <Value>%1</Value>\n").arg(xmlSafeString(stateCustomData->value, true));
  467. customData += " </CustomData>\n";
  468. content += customData;
  469. }
  470. if (saveState.chunk != nullptr && *saveState.chunk != 0)
  471. {
  472. QString chunk("\n"" <Chunk>\n");
  473. chunk += QString("%1\n").arg(saveState.chunk);
  474. chunk += " </Chunk>\n";
  475. content += chunk;
  476. }
  477. content += " </Data>\n";
  478. return content;
  479. }
  480. // -------------------------------------------------
  481. CARLA_BACKEND_END_NAMESPACE
  482. #endif // __CARLA_STATE_UTILS_HPP__