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.

570 lines
20KB

  1. /*
  2. * Carla State utils
  3. * Copyright (C) 2012-2014 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. #include "CarlaStateUtils.hpp"
  18. #include "CarlaBackendUtils.hpp"
  19. #include "CarlaMathUtils.hpp"
  20. #include "CarlaMIDI.h"
  21. #include "juce_core.h"
  22. using juce::String;
  23. using juce::XmlElement;
  24. CARLA_BACKEND_START_NAMESPACE
  25. // -----------------------------------------------------------------------
  26. // xmlSafeString
  27. static String xmlSafeString(const String& string, const bool toXml)
  28. {
  29. String newString(string);
  30. if (toXml)
  31. return newString.replace("&","&amp;").replace("<","&lt;").replace(">","&gt;").replace("'","&apos;").replace("\"","&quot;");
  32. else
  33. return newString.replace("&lt;","<").replace("&gt;",">").replace("&apos;","'").replace("&quot;","\"").replace("&amp;","&");
  34. }
  35. // -----------------------------------------------------------------------
  36. // xmlSafeStringCharDup
  37. static const char* xmlSafeStringCharDup(const String& string, const bool toXml)
  38. {
  39. return carla_strdup(xmlSafeString(string, toXml).toRawUTF8());
  40. }
  41. // -----------------------------------------------------------------------
  42. // StateParameter
  43. StateParameter::StateParameter() noexcept
  44. : isInput(true),
  45. index(-1),
  46. name(nullptr),
  47. symbol(nullptr),
  48. value(0.0f),
  49. midiChannel(0),
  50. midiCC(-1) {}
  51. StateParameter::~StateParameter() noexcept
  52. {
  53. if (name != nullptr)
  54. {
  55. delete[] name;
  56. name = nullptr;
  57. }
  58. if (symbol != nullptr)
  59. {
  60. delete[] symbol;
  61. symbol = nullptr;
  62. }
  63. }
  64. // -----------------------------------------------------------------------
  65. // StateCustomData
  66. StateCustomData::StateCustomData() noexcept
  67. : type(nullptr),
  68. key(nullptr),
  69. value(nullptr) {}
  70. StateCustomData::~StateCustomData() noexcept
  71. {
  72. if (type != nullptr)
  73. {
  74. delete[] type;
  75. type = nullptr;
  76. }
  77. if (key != nullptr)
  78. {
  79. delete[] key;
  80. key = nullptr;
  81. }
  82. if (value != nullptr)
  83. {
  84. delete[] value;
  85. value = nullptr;
  86. }
  87. }
  88. // -----------------------------------------------------------------------
  89. // StateSave
  90. StateSave::StateSave() noexcept
  91. : type(nullptr),
  92. name(nullptr),
  93. label(nullptr),
  94. binary(nullptr),
  95. uniqueId(0),
  96. active(false),
  97. dryWet(1.0f),
  98. volume(1.0f),
  99. balanceLeft(-1.0f),
  100. balanceRight(1.0f),
  101. panning(0.0f),
  102. ctrlChannel(-1),
  103. currentProgramIndex(-1),
  104. currentProgramName(nullptr),
  105. currentMidiBank(-1),
  106. currentMidiProgram(-1),
  107. chunk(nullptr) {}
  108. StateSave::~StateSave() noexcept
  109. {
  110. clear();
  111. }
  112. void StateSave::clear() noexcept
  113. {
  114. if (type != nullptr)
  115. {
  116. delete[] type;
  117. type = nullptr;
  118. }
  119. if (name != nullptr)
  120. {
  121. delete[] name;
  122. name = nullptr;
  123. }
  124. if (label != nullptr)
  125. {
  126. delete[] label;
  127. label = nullptr;
  128. }
  129. if (binary != nullptr)
  130. {
  131. delete[] binary;
  132. binary = nullptr;
  133. }
  134. if (currentProgramName != nullptr)
  135. {
  136. delete[] currentProgramName;
  137. currentProgramName = nullptr;
  138. }
  139. if (chunk != nullptr)
  140. {
  141. delete[] chunk;
  142. chunk = nullptr;
  143. }
  144. uniqueId = 0;
  145. active = false;
  146. dryWet = 1.0f;
  147. volume = 1.0f;
  148. balanceLeft = -1.0f;
  149. balanceRight = 1.0f;
  150. panning = 0.0f;
  151. ctrlChannel = -1;
  152. currentProgramIndex = -1;
  153. currentMidiBank = -1;
  154. currentMidiProgram = -1;
  155. for (StateParameterItenerator it = parameters.begin(); it.valid(); it.next())
  156. {
  157. StateParameter* const stateParameter(it.getValue());
  158. delete stateParameter;
  159. }
  160. for (StateCustomDataItenerator it = customData.begin(); it.valid(); it.next())
  161. {
  162. StateCustomData* const stateCustomData(it.getValue());
  163. delete stateCustomData;
  164. }
  165. parameters.clear();
  166. customData.clear();
  167. }
  168. // -----------------------------------------------------------------------
  169. // fillFromXmlElement
  170. bool StateSave::fillFromXmlElement(const XmlElement* const xmlElement)
  171. {
  172. CARLA_SAFE_ASSERT_RETURN(xmlElement != nullptr, false);
  173. clear();
  174. for (XmlElement* elem = xmlElement->getFirstChildElement(); elem != nullptr; elem = elem->getNextElement())
  175. {
  176. const String& tagName(elem->getTagName());
  177. // ---------------------------------------------------------------
  178. // Info
  179. if (tagName.equalsIgnoreCase("info"))
  180. {
  181. for (XmlElement* xmlInfo = elem->getFirstChildElement(); xmlInfo != nullptr; xmlInfo = xmlInfo->getNextElement())
  182. {
  183. const String& tag(xmlInfo->getTagName());
  184. const String text(xmlInfo->getAllSubText().trim());
  185. if (tag.equalsIgnoreCase("type"))
  186. type = xmlSafeStringCharDup(text, false);
  187. else if (tag.equalsIgnoreCase("name"))
  188. name = xmlSafeStringCharDup(text, false);
  189. else if (tag.equalsIgnoreCase("label") || tag.equalsIgnoreCase("uri"))
  190. label = xmlSafeStringCharDup(text, false);
  191. else if (tag.equalsIgnoreCase("binary") || tag.equalsIgnoreCase("bundle") || tag.equalsIgnoreCase("filename"))
  192. binary = xmlSafeStringCharDup(text, false);
  193. else if (tag.equalsIgnoreCase("uniqueid"))
  194. uniqueId = text.getLargeIntValue();
  195. }
  196. }
  197. // ---------------------------------------------------------------
  198. // Data
  199. else if (tagName.equalsIgnoreCase("data"))
  200. {
  201. for (XmlElement* xmlData = elem->getFirstChildElement(); xmlData != nullptr; xmlData = xmlData->getNextElement())
  202. {
  203. const String& tag(xmlData->getTagName());
  204. const String text(xmlData->getAllSubText().trim());
  205. // -------------------------------------------------------
  206. // Internal Data
  207. if (tag.equalsIgnoreCase("active"))
  208. {
  209. active = (text.equalsIgnoreCase("yes") || text.equalsIgnoreCase("true"));
  210. }
  211. else if (tag.equalsIgnoreCase("drywet"))
  212. {
  213. dryWet = carla_fixValue(0.0f, 1.0f, text.getFloatValue());
  214. }
  215. else if (tag.equalsIgnoreCase("volume"))
  216. {
  217. volume = carla_fixValue(0.0f, 1.27f, text.getFloatValue());
  218. }
  219. else if (tag.equalsIgnoreCase("balanceleft") || tag.equalsIgnoreCase("balance-left"))
  220. {
  221. balanceLeft = carla_fixValue(-1.0f, 1.0f, text.getFloatValue());
  222. }
  223. else if (tag.equalsIgnoreCase("balanceright") || tag.equalsIgnoreCase("balance-right"))
  224. {
  225. balanceRight = carla_fixValue(-1.0f, 1.0f, text.getFloatValue());
  226. }
  227. else if (tag.equalsIgnoreCase("panning"))
  228. {
  229. panning = carla_fixValue(-1.0f, 1.0f, text.getFloatValue());
  230. }
  231. else if (tag.equalsIgnoreCase("controlchannel") || tag.equalsIgnoreCase("control-channel"))
  232. {
  233. if (! text.startsWithIgnoreCase("n"))
  234. {
  235. const int value(text.getIntValue());
  236. if (value >= 1 && value <= MAX_MIDI_CHANNELS)
  237. ctrlChannel = static_cast<int8_t>(value-1);
  238. }
  239. }
  240. // -------------------------------------------------------
  241. // Program (current)
  242. else if (tag.equalsIgnoreCase("currentprogramindex") || tag.equalsIgnoreCase("current-program-index"))
  243. {
  244. const int value(text.getIntValue());
  245. if (value >= 1)
  246. currentProgramIndex = value-1;
  247. }
  248. else if (tag.equalsIgnoreCase("currentprogramname") || tag.equalsIgnoreCase("current-program-name"))
  249. {
  250. currentProgramName = xmlSafeStringCharDup(text, false);
  251. }
  252. // -------------------------------------------------------
  253. // Midi Program (current)
  254. else if (tag.equalsIgnoreCase("currentmidibank") || tag.equalsIgnoreCase("current-midi-bank"))
  255. {
  256. const int value(text.getIntValue());
  257. if (value >= 1)
  258. currentMidiBank = value-1;
  259. }
  260. else if (tag.equalsIgnoreCase("currentmidiprogram") || tag.equalsIgnoreCase("current-midi-program"))
  261. {
  262. const int value(text.getIntValue());
  263. if (value >= 1)
  264. currentMidiProgram = value-1;
  265. }
  266. // -------------------------------------------------------
  267. // Parameters
  268. else if (tag.equalsIgnoreCase("parameter"))
  269. {
  270. StateParameter* const stateParameter(new StateParameter());
  271. for (XmlElement* xmlSubData = xmlData->getFirstChildElement(); xmlSubData != nullptr; xmlSubData = xmlSubData->getNextElement())
  272. {
  273. const String& pTag(xmlSubData->getTagName());
  274. const String pText(xmlSubData->getAllSubText().trim());
  275. if (pTag.equalsIgnoreCase("index"))
  276. {
  277. const int index(pText.getIntValue());
  278. if (index >= 0)
  279. stateParameter->index = index;
  280. }
  281. else if (pTag.equalsIgnoreCase("name"))
  282. {
  283. stateParameter->name = xmlSafeStringCharDup(pText, false);
  284. }
  285. else if (pTag.equalsIgnoreCase("symbol"))
  286. {
  287. stateParameter->symbol = xmlSafeStringCharDup(pText, false);
  288. }
  289. else if (pTag.equalsIgnoreCase("value"))
  290. {
  291. stateParameter->value = pText.getFloatValue();
  292. }
  293. else if (pTag.equalsIgnoreCase("midichannel") || pTag.equalsIgnoreCase("midi-channel"))
  294. {
  295. const int channel(pText.getIntValue());
  296. if (channel >= 1 && channel <= MAX_MIDI_CHANNELS)
  297. stateParameter->midiChannel = static_cast<uint8_t>(channel-1);
  298. }
  299. else if (pTag.equalsIgnoreCase("midicc") || pTag.equalsIgnoreCase("midi-cc"))
  300. {
  301. const int cc(pText.getIntValue());
  302. if (cc >= 1 && cc < 0x5F)
  303. stateParameter->midiCC = static_cast<int16_t>(cc);
  304. }
  305. }
  306. parameters.append(stateParameter);
  307. }
  308. // -------------------------------------------------------
  309. // Custom Data
  310. else if (tag.equalsIgnoreCase("customdata") || tag.equalsIgnoreCase("custom-data"))
  311. {
  312. StateCustomData* const stateCustomData(new StateCustomData());
  313. for (XmlElement* xmlSubData = xmlData->getFirstChildElement(); xmlSubData != nullptr; xmlSubData = xmlSubData->getNextElement())
  314. {
  315. const String& cTag(xmlSubData->getTagName());
  316. const String cText(xmlSubData->getAllSubText().trim());
  317. if (cTag.equalsIgnoreCase("type"))
  318. stateCustomData->type = xmlSafeStringCharDup(cText, false);
  319. else if (cTag.equalsIgnoreCase("key"))
  320. stateCustomData->key = xmlSafeStringCharDup(cText, false);
  321. else if (cTag.equalsIgnoreCase("value"))
  322. stateCustomData->value = xmlSafeStringCharDup(cText, false);
  323. }
  324. customData.append(stateCustomData);
  325. }
  326. // -------------------------------------------------------
  327. // Chunk
  328. else if (tag.equalsIgnoreCase("chunk"))
  329. {
  330. chunk = xmlSafeStringCharDup(text, false);
  331. }
  332. }
  333. }
  334. }
  335. return true;
  336. }
  337. // -----------------------------------------------------------------------
  338. // fillXmlStringFromStateSave
  339. String StateSave::toString() const
  340. {
  341. String content;
  342. {
  343. String infoXml(" <Info>\n");
  344. infoXml << " <Type>" << String(type != nullptr ? type : "") << "</Type>\n";
  345. infoXml << " <Name>" << xmlSafeString(name, true) << "</Name>\n";
  346. switch (getPluginTypeFromString(type))
  347. {
  348. case PLUGIN_NONE:
  349. break;
  350. case PLUGIN_INTERNAL:
  351. infoXml << " <Label>" << xmlSafeString(label, true) << "</Label>\n";
  352. break;
  353. case PLUGIN_LADSPA:
  354. infoXml << " <Binary>" << xmlSafeString(binary, true) << "</Binary>\n";
  355. infoXml << " <Label>" << xmlSafeString(label, true) << "</Label>\n";
  356. infoXml << " <UniqueID>" << uniqueId << "</UniqueID>\n";
  357. break;
  358. case PLUGIN_DSSI:
  359. infoXml << " <Binary>" << xmlSafeString(binary, true) << "</Binary>\n";
  360. infoXml << " <Label>" << xmlSafeString(label, true) << "</Label>\n";
  361. break;
  362. case PLUGIN_LV2:
  363. infoXml << " <Bundle>" << xmlSafeString(binary, true) << "</Bundle>\n";
  364. infoXml << " <URI>" << xmlSafeString(label, true) << "</URI>\n";
  365. break;
  366. case PLUGIN_VST:
  367. infoXml << " <Binary>" << xmlSafeString(binary, true) << "</Binary>\n";
  368. infoXml << " <UniqueID>" << uniqueId << "</UniqueID>\n";
  369. break;
  370. case PLUGIN_VST3:
  371. // TODO?
  372. infoXml << " <Binary>" << xmlSafeString(binary, true) << "</Binary>\n";
  373. infoXml << " <UniqueID>" << uniqueId << "</UniqueID>\n";
  374. break;
  375. case PLUGIN_AU:
  376. // TODO?
  377. infoXml << " <Binary>" << xmlSafeString(binary, true) << "</Binary>\n";
  378. infoXml << " <UniqueID>" << uniqueId << "</UniqueID>\n";
  379. break;
  380. case PLUGIN_GIG:
  381. case PLUGIN_SF2:
  382. infoXml << " <Filename>" << xmlSafeString(binary, true) << "</Filename>\n";
  383. infoXml << " <Label>" << xmlSafeString(label, true) << "</Label>\n";
  384. break;
  385. case PLUGIN_SFZ:
  386. infoXml << " <Filename>" << xmlSafeString(binary, true) << "</Filename>\n";
  387. break;
  388. }
  389. infoXml << " </Info>\n\n";
  390. content << infoXml;
  391. }
  392. content << " <Data>\n";
  393. {
  394. String dataXml;
  395. dataXml << " <Active>" << (active ? "Yes" : "No") << "</Active>\n";
  396. if (dryWet != 1.0f)
  397. dataXml << " <DryWet>" << String(dryWet, 7) << "</DryWet>\n";
  398. if (volume != 1.0f)
  399. dataXml << " <Volume>" << String(volume, 7) << "</Volume>\n";
  400. if (balanceLeft != -1.0f)
  401. dataXml << " <Balance-Left>" << String(balanceLeft, 7) << "</Balance-Left>\n";
  402. if (balanceRight != 1.0f)
  403. dataXml << " <Balance-Right>" << String(balanceRight, 7) << "</Balance-Right>\n";
  404. if (panning != 0.0f)
  405. dataXml << " <Panning>" << String(panning, 7) << "</Panning>\n";
  406. if (ctrlChannel < 0)
  407. dataXml << " <ControlChannel>N</ControlChannel>\n";
  408. else
  409. dataXml << " <ControlChannel>" << int(ctrlChannel+1) << "</ControlChannel>\n";
  410. content << dataXml;
  411. }
  412. for (StateParameterItenerator it = parameters.begin(); it.valid(); it.next())
  413. {
  414. StateParameter* const stateParameter(it.getValue());
  415. String parameterXml("\n"" <Parameter>\n");
  416. parameterXml << " <Index>" << String(stateParameter->index) << "</Index>\n";
  417. parameterXml << " <Name>" << xmlSafeString(stateParameter->name, true) << "</Name>\n";
  418. if (stateParameter->symbol != nullptr && stateParameter->symbol[0] != '\0')
  419. parameterXml << " <Symbol>" << xmlSafeString(stateParameter->symbol, true) << "</Symbol>\n";
  420. if (stateParameter->isInput)
  421. parameterXml << " <Value>" << String(stateParameter->value, 15) << "</Value>\n";
  422. if (stateParameter->midiCC > 0)
  423. {
  424. parameterXml << " <MidiCC>" << stateParameter->midiCC << "</MidiCC>\n";
  425. parameterXml << " <MidiChannel>" << stateParameter->midiChannel+1 << "</MidiChannel>\n";
  426. }
  427. parameterXml << " </Parameter>\n";
  428. content << parameterXml;
  429. }
  430. if (currentProgramIndex >= 0 && currentProgramName != nullptr && currentProgramName[0] != '\0')
  431. {
  432. // ignore 'default' program
  433. if (currentProgramIndex > 0 || ! String(currentProgramName).equalsIgnoreCase("default"))
  434. {
  435. String programXml("\n");
  436. programXml << " <CurrentProgramIndex>" << currentProgramIndex+1 << "</CurrentProgramIndex>\n";
  437. programXml << " <CurrentProgramName>" << xmlSafeString(currentProgramName, true) << "</CurrentProgramName>\n";
  438. content << programXml;
  439. }
  440. }
  441. if (currentMidiBank >= 0 && currentMidiProgram >= 0)
  442. {
  443. String midiProgramXml("\n");
  444. midiProgramXml << " <CurrentMidiBank>" << currentMidiBank+1 << "</CurrentMidiBank>\n";
  445. midiProgramXml << " <CurrentMidiProgram>" << currentMidiProgram+1 << "</CurrentMidiProgram>\n";
  446. content << midiProgramXml;
  447. }
  448. for (StateCustomDataItenerator it = customData.begin(); it.valid(); it.next())
  449. {
  450. StateCustomData* const stateCustomData(it.getValue());
  451. CARLA_SAFE_ASSERT_CONTINUE(stateCustomData->type != nullptr && stateCustomData->type[0] != '\0');
  452. CARLA_SAFE_ASSERT_CONTINUE(stateCustomData->key != nullptr && stateCustomData->key[0] != '\0');
  453. CARLA_SAFE_ASSERT_CONTINUE(stateCustomData->value != nullptr);
  454. String customDataXml("\n"" <CustomData>\n");
  455. customDataXml << " <Type>" << xmlSafeString(stateCustomData->type, true) << "</Type>\n";
  456. customDataXml << " <Key>" << xmlSafeString(stateCustomData->key, true) << "</Key>\n";
  457. if (std::strcmp(stateCustomData->type, CUSTOM_DATA_TYPE_CHUNK) == 0 || std::strlen(stateCustomData->value) >= 128)
  458. {
  459. customDataXml << " <Value>\n";
  460. customDataXml << xmlSafeString(stateCustomData->value, true);
  461. customDataXml << "\n </Value>\n";
  462. }
  463. else
  464. {
  465. customDataXml << " <Value>";
  466. customDataXml << xmlSafeString(stateCustomData->value, true);
  467. customDataXml << "</Value>\n";
  468. }
  469. customDataXml << " </CustomData>\n";
  470. content << customDataXml;
  471. }
  472. if (chunk != nullptr && chunk[0] != '\0')
  473. {
  474. String chunkXml("\n"" <Chunk>\n");
  475. chunkXml << chunk << "\n </Chunk>\n";
  476. content << chunkXml;
  477. }
  478. content << " </Data>\n";
  479. return content;
  480. }
  481. // -----------------------------------------------------------------------
  482. CARLA_BACKEND_END_NAMESPACE