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.

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