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.

629 lines
21KB

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