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
23KB

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