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.

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