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.

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